# vim:ts=4 sw=4 expandtab smarttab smartindent autoindent cindent package Nour::Script; use strict; use warnings; # ABSTRACT: script bootstrap: let's you stop wasting time rewriting the same code for every script and start mashing code for the core process at hand. use Moose::Role; with 'Nour::Base'; use namespace::autoclean; use String::CamelCase qw/decamelize/; use Getopt::Long qw/:config pass_through/; use Pod::Usage; use Nour::Logger; use Nour::Config; use Nour::Database; use String::CamelCase qw/camelize decamelize/; use Carp qw/longmess shortmess/; # $Carp::Verbose = 1; logger: { has _logger => ( is => 'rw' , isa => 'Nour::Logger' , handles => [ qw/debug error fatal info log warn/ ] , required => 1 , lazy => 1 , default => sub { return new Nour::Logger; } ); }; config: { has _config => ( is => 'rw' , isa => 'Nour::Config' , handles => [ qw/config/ ] , required => 1 , lazy => 1 , default => sub { my $self = shift; my $conf = new Nour::Config ( -base => $self->_config_path ); case_exception: { if ( -e $self->_config_path_auto .'.yml' and not -d $self->_config_path_auto ) { my $name = pop @{ [ split /\//, $self->_config_path_auto ] }; do { my %conf = %{ delete $conf->config->{ $name } }; delete $conf->config->{ $_ } for keys %{ scalar $conf->config }; $conf->config( \%conf ); } if exists $conf->config->{ $name } and ref $conf->config->{ $name } eq 'HASH'; } }; return $conf; } ); sub _config_path_auto { my $self = shift; my $path = $self->path( qw/config/, map { decamelize $_ } split /::/, ref $self ); $path =~ s/\/$//; return $path; } has _config_path => ( is => 'rw' , isa => 'Str' , lazy => 1 , required => 1 , default => sub { my $self = shift; my $path = $self->_config_path_auto; return $path if -d $path; # use ./config/whatever/package as the base if that exists my @path = split /\//, $path; my $file = pop( @path ) .'.yml'; $path = join '/', @path; return $path if -d $path and -e "$path/$file"; # or use ./config/whatever/ as the base if ./config/whatever/package.yml exists return $self->path( 'config' ); # otherwise use ./config/ as the base } ); }; database: { has _database => ( is => 'rw' , isa => 'Nour::Database' , handles => [ qw/db/ ] , lazy => 1 , required => 1 , default => sub { my $self = shift; my %conf = $self->config->{database} ? %{ $self->config->{database} } : ( # default options here ); $conf{ '-opts' }{database} = $self->option->{mode} if $self->option->{mode} and not grep { $_ eq '--database' # --database will get processed by nour::database } @ARGV; $conf{ '-opts' }{log} = $self->log->mojo unless $self->option->{silent}; #_logger->_logger; return new Nour::Database ( %conf ); } ); }; options: { has _option => ( is => 'rw' , isa => 'HashRef' , required => 1 , lazy => 1 , default => sub { my ( $self, %opts, %conf ) = @_; %conf = %{ $self->config->{option} } if exists $self->config->{option}; $self->merge_hash( \%opts, $conf{default} ) if $conf{default}; GetOptions( \%opts , qw/ mode=s verbose+ help|? / , silent => sub { $opts{verbose} = 0; $opts{silent} = 1; } , $conf{getopts} ? @{ $conf{getopts} } : () ) or pod2usage( 1 ); pod2usage( 1 ) if $opts{help}; return \%opts; } ); sub option { my $self = shift; my @args = @_; if ( @args and defined $args[0] ) { return $self->_option->{ $args[0] } if scalar @args eq 1 and not ref $args[0]; my %option = ref $args[0] eq 'HASH' ? %{ $args[0] } : @args; for my $key ( keys %option ) { $self->_option->{ $key } = $option{ $key }; } } return $self->_option; } sub options { shift->option( @_ ) } }; runtime: { before run => sub { my $self = shift; $self->info( ref $self ); if ( -e $self->_config_path_auto .'.yml' ) { $self->debug( 'using config (from '. $self->_config_path_auto .'.yml)', $self->config ); } elsif ( -d $self->_config_path and keys %{ scalar $self->config } ) { $self->debug( 'using config (from '. $self->_config_path .')', $self->config ); } else { $self->debug( 'not using config; if you want to decouple config from code, you should place your YAML configuration in one of these places:' ); $self->debug( '- '. $self->_config_path_auto .'.yml' ); $self->debug( '- '. $self->_config_path_auto .'/' ); $self->debug( '- '. $self->path( 'config' ) .'/' ); } $self->debug( 'using options (coalesced from config and command-line)', $self->option ) if keys %{ $self->option }; }; sub run { shift->warn( 'sub run {} says you should override this method' ); } after run => sub { my $self = shift; $self->info( ref( $self ) .' finished' ); }; around run => sub { my $orig = shift; my $self = shift; local $SIG{__DIE__} = sub { my $carp = longmess @_; $self->error( "custom carp ". $carp ); }; return $self->$orig( @_ ); }; }; silence_is_golden: { do { my $method = $_; around $method => sub { my ( $next, $self, @args ) = @_; return $self->$next( @args ) unless $self->option->{silent}; return $self->$next( @args ) if $method eq 'log' and not @args; return; }; } for qw/debug info log/; }; 1; __END__ =pod =encoding UTF-8 =head1 NAME Nour::Script - script bootstrap: let's you stop wasting time rewriting the same code for every script and start mashing code for the core process at hand. =head1 VERSION version 0.08 =head1 SYNOPSIS Here's the quickest example: #!/usr/bin/env perl { package Script::Generate; use Moose; with 'Nour::Script'; sub run { my ( $self ) = @_; $self->debug( 'testing' ); } 1; } Script::Generate->new->run; =head1 USAGE =head2 Configuration setup No documentation here yet, uses L. =head2 Command-line options In your script configuration, include an 'option' hash with two properties: - getopts: an array of defined options (using L syntax) - default: a hash of default values for the defined options here's an example: --- other: configuration stuff: bananas option: getopts: - source=s - target:s - width:i - bigger+ - crop - force! default: force: 1 Configuration goes in either ./config/your/package/name/ or ./config/your/package/name.yml (scripts have to be defined as packages for this extension because it's written as a Moose::Role mix-in). See L for details. By default, these options are configured: - verbose+ - silent - help|? - mode=s The mode option is used in conjunction with L configuration (if you set that up). =head2 Database utility No documentation here yet, uses L. =head2 Using the logger No documentation here yet, uses L. =head1 NAME Nour::Script =head1 AUTHOR Nour Sharabash =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2014 by Nour Sharabash. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut