name: title layout: true class: center, middle, inverse --- # Building Command-Line Apps .footnote.right[[Chicago Perl Mongers](http://chicago.pm.org)] --- layout: false class: center, middle # You're working with text --- class: center, middle layout: true --- # You like the command-line efficient and friendly --- # You don't need a GUI --- class: center, middle # Shell scripts aren't strong enough • poor scalability • bytestreams aren't everything --- class: center, middle # Perl is an elegant solution for a more civilized age --- class: center, middle # TIMTOWTDI There Is More Than One Way To Do It --- template: title # Core Parts --- # Input • Arguments: `<argument>` • Options: `[--option] [-h] [-R3]` • Standard Input: `STDIN` --- # Output • Standard Output: `STDOUT` • Standard Error: `STDERR` • Exit Code: (0-255) --- # User Documentation --- # Code Organization --- template: title # Basic Scripts --- layout: false class: middle .center[ # Start off right ] ```perl use strict; use warnings; ``` --- class: middle .center[ # Arguments ## .code[@ARGV] ] ```perl use strict; use warnings; use feature qw( say ); say "Hello, $ARGV[0]!"; ``` ``` $ perl greet.pl Chicago Hello, Chicago! ``` --- class: middle .center[ # Options ## .code[Getopt::Long] ] ```perl use feature qw( say ); use Getopt::Long; my %opt = ( greeting => 'Hello', ); GetOptions( \%opt, 'greeting:s', ); say "$opt{greeting}, $ARGV[0]!"; ``` ``` $ perl greet.pl Chicago Hello, Chicago! $ perl greet.pl --greeting Goodbye Cleveland Goodbye, Cleveland! ``` --- class: middle .center[ # Option Options ] ```perl use Getopt::Long; my %opt; GetOptions( \%opt, 'boolean', # --boolean 'increment+', # --increment --increment --increment 'toggle!', # --toggle || --no-toggle 'required=s', # --required "Value" 'optional:s', # --optional "Value" || --optional 'multiple=s@', # --multiple "Foo" --multiple "Bar" 'pairs=s%', # --pairs foo=1 --pairs bar=1 'name|n', # --name || -n ); ``` --- class: center middle # GetOptions edits @ARGV Leaves what it doesn't know about: your arguments --- class: middle center ![It's hopeless](image/hopeless.png) --- class: middle .center[ # Standard Input ] ```perl use feature qw( say ); while ( my $line =
) { my @emails = ( $line =~ /(\S+[@]\S+)/g ); say for @emails; } ``` ``` $ perl find_emails.pl < /var/spool/mail/victim flannel@selfies.com shabby@chic.com Wes@Anderson.com batch@Godard.org trust@fund.org ``` --- class: middle center ![Perl!](image/perl.png) --- class: middle .center[ # The Diamond Operator `<>` Filenames in @ARGV, otherwise STDIN ] ```perl use feature qw( say ); while ( my $line = <> ) { my @emails = ( $line =~ /(\S+[@]\S+)/g ); say for @emails; } ``` ``` $ perl find_emails.pl < /var/spool/mail/victim $ perl find_emails.pl /var/spool/mail/victim ``` --- class: middle .center[ # Standard Output ] ```perl use feature qw( say ); say 'Hello, World!'; ``` ``` $ perl hello.pl Hello, World! ``` --- class: middle .center[ # Standard Error ] ```perl warn "Look out, World!" die "I'm going down!\n" ``` ``` $ perl warn.pl Look out, World! at warn.pl line 2. I'm going down! ``` --- class: middle .center[ # STDOUT vs STDERR ] ```perl use feature qw( say ); say "Hello, World!" warn "Look out, World!" die "I'm going down!\n" ``` ``` $ perl stdouterr.pl 1>stdout.txt 2>stderr.txt $ cat stdout.txt Hello, World! $ cat stderr.txt Look out, World! at stdouterr.pl line 4. I'm going down! ``` --- class: middle .center[ # Exit Status ] ``` $ perl hello.pl Hello, World! $ echo $? 0 $ perl warn.pl Look out, World! at warn.pl line 2. I'm going down! $ echo $? 255 ``` --- class: middle .center[ # 0 is good Everything else is bad ] ``` $ perl hello.pl && perl warn.pl Hello, World! Look out, World! at warn.pl line 2. I'm going down! $ perl warn.pl && perl hello.pl Look out, World! at warn.pl line 2. I'm going down! ``` --- class: middle .center[ # Choose your exit ] ```perl use feature qw( say ); say "Exit code $ARGV[0]"; exit $ARGV[0]; ``` ``` $ perl exit.pl 0 Exit code 0 $ echo $? 0 $ perl exit.pl 1 Exit code 1 $ echo $? 1 ``` --- class: center middle # User Documentation Pod::Usage --- class: middle .center[ # Plain Old Documentation ] ``` =head1 NAME greet.pl - Say hello! =head1 SYNOPSIS perl greet.pl [--greeting
]
=head1 ARGUMENTS =head2 who Who to greet. Defaults to "World" =head1 OPTIONS =head2 greeting The greeting to use. Defaults to "Hello" ``` --- class: middle .center[ # Read The Fine Manual ] ``` $ perldoc greet.pl GREET(1) User Contributed Perl Documentation GREET(1) NAME greet.pl - Say hello! SYNOPSIS perl greet.pl [--greeting
] [
] ARGUMENTS who Who to greet. Defaults to "World". OPTIONS greeting The greeting to use. Defaults to "Hello". perl v5.14.2 2013-05-23 GREET(1) ``` --- class: middle .center[ # Help the User Give them a `-h`and ] ```perl use Getopt::Long; use Pod::Usage; my %opt; GetOptions( \%opt, 'help|h', ); pod2usage(0) if $opt{help}; ``` ``` $ perl greet.pl -h Usage: perl greet.pl [--greeting
] [
] perl -h|--help Arguments: who: Who to greet. Defaults to "World". Options: greeting: The greeting to use. Defaults to "Hello". help|h: Display help and usage ``` --- class: middle .center[ # Scold the User Tell them what they did wrong ] ```perl use Getopt::Long; use Pod::Usage; my %opt; GetOptions( \%opt, 'help|h', ); pod2usage(0) if $opt{help}; pod2usage("ERROR: Must give someone to greet") if !@ARGV; ``` ``` $ perl greet.pl ERROR: Must give someone to greet Usage: perl greet.pl [--greeting
] [
] perl -h|--help ``` --- class: middle .center[ # SHBANG! ] ```perl #!/usr/bin/env perl use feature qw( say ); say "Hello, World!"; ``` ``` $ ./hello.pl bash: permission denied: ./hello.pl $ chmod +x hello.pl $ ./hello.pl Hello, World! ``` --- class: middle .center[ # Basic Script Boilerplate ] ```perl #!/usr/bin/env perl use strict; use warnings; use Getopt::Long; use Pod::Usage; my %opt; GetOptions( \%opt, 'help|h', ); pod2usage(0) if $opt{help}; ### Put your code here =head1 NAME =head1 SYNOPSIS =head1 ARGUMENTS =head1 OPTIONS =head1 EXIT CODES =head1 LICENSE AND COPYRIGHT ``` --- template: title # Modulinos Scripts as Modules --- class: center middle # Code Organization --- class: center middle # Reusable Components --- class: center middle # Testable Scripts --- class: middle .center[ # .code[int main(argv)] ] ```perl package modulino; sub main { my ( $argv ) = @_; ### Put your code here return 0; } exit main( \@ARGV ); ``` ``` $ perl modulino.pm $ ./modulino.pm ``` --- class: middle .center[ # Add the options ] ```perl use Getopt::Long qw( GetOptionsFromArray ); sub main { my ( $argv ) = @_; my %opt; GetOptionsFromArray( $argv, \%opt, 'help|h', ); ### Put your code here return 0; } exit main( \@ARGV ); ``` --- class: middle .center[ # Use as a Module ] ```perl exit main( \@ARGV ) if !caller(0); ``` --- class: middle .center[ # The Greeting Modulino ] ```perl #!/usr/bin/env perl package greet; use strict; use warnings; use feature qw( say ); use Getopt::Long qw( GetOptionsFromArray ); use Pod::Usage; sub main { my ( $argv ) = @_; my %opt; GetOptionsFromArray( $argv, \%opt, 'greeting:s', 'help|h', ); pod2usage(0) if $opt{help}; pod2usage("ERROR: Must give someone to greet") if !@{$argv}; $opt{greeting} ||= "Hello"; say "$opt{greeting}, $argv->[0]!"; return 0; } exit main( \@ARGV ) if !caller(0); 1; #
.pm did not return a true value ``` --- class: middle .center[ # Run the Modulino ] ``` $ perl greet.pm --greeting "Good Morning" "St. Louis" Good Morning, St. Louis! $ chmod +x greet.pm $ ./greet.pm Chicago.PM Hello, Chicago.PM ``` --- class: middle .center[ # Test the Modulino ] ```perl use Test::More; use Capture::Tiny qw( capture ); use greet; my ( $stdout, $stderr, $exit ) = capture { greet::main( [ "Chicago.PM" ] ); }; is $stdout, "Hello, Chicago.PM!\n", 'correct greeting'; is $exit, 0, 'exit code: success'; done_testing; ``` ``` $ prove -v greet.t greet.t .. ok 1 - correct greeting ok 2 - exit code: success 1..2 ok All tests successful. Files=1, Tests=2, 0 wallclock secs (...) Result: PASS ``` --- class: middle center # Add more methods .code[Sub::Exporter] --- class: middle center # Inheritance --- class: middle .center[ # Modulino Boilerplate ] ```perl #!/usr/bin/env perl package modulino; use strict; use warnings; use Getopt::Long qw( GetOptionsFromArray ); use Pod::Usage; sub main { my ( $argv ) = @_; my %opt; GetOptionsFromArray( $argv, \%opt, ### Your options here 'help|h', ); pod2usage(0) if $opt{help}; ### Your code here return 0; } exit main( \@ARGV ) if ( !caller(0) ); 1; =head1 NAME =head1 SYNOPSIS =head1 ARGUMENTS =head1 OPTIONS =head1 LICENSE AND COPYRIGHT ``` --- template: title # MooseX::Runnable Moose-based Modulino --- class: center middle # Existing Module .code[My::Module] --- class: center middle # Runnable Modulino .code[mx-run My::Module] --- class: middle .center[ # Hello.pm ] ```perl package Runnable::Hello; use feature qw( say ); use Moose; with 'MooseX::Runnable'; sub run { my ( $self ) = @_; say "Hello, World!"; return 0; } 1; ``` ``` $ mx-run Runnable::Hello Hello, World! ``` --- class: center middle # Run From Anywhere .code[@INC $ENV{PERL5LIB}] --- class: middle .center[ # Arguments ] ```perl package Runnable::Greet; use feature qw( say ); use Moose; with 'MooseX::Runnable'; sub run { my ( $self, $who ) = @_; say "Hello, $who!"; return 0; } 1; ``` ``` $ mx-run Runnable::Greet Chicago.PM Hello, Chicago.PM! ``` --- class: middle .center[ # Show usage instructions ] ```perl sub run { my ( $self, $who ) = @_; die "Must give someone to greet!\n\n" . $self->usage unless $who; say "Hello, $who!"; return 0; } 1; ``` ``` $ mx-run Runnable::Greet Must give someone to greet! usage: mx-run [-?h] [long options...] -h -? --usage --help Prints this usage information. --greeting ``` --- class: middle .center[ # Options .code[MooseX::Getopt] ] ```perl package Runnable::Greet; use feature qw( say ); use Moose; with 'MooseX::Runnable', 'MooseX::Getopt'; has greeting => ( is => 'ro', isa => 'Str', default => 'Hello', ); sub run { my ( $self, $who ) = @_; die "Must give someone to greet!\n\n" . $self->usage unless $who; say $self->greeting . ", $who!"; return 0; } 1; ``` ``` $ mx-run Runnable::Greet --greeting Bonjour Chicago.PM Bonjour, Chicago.PM! ``` --- class: middle center # Caution: Two in One! Command-line and Object APIs can conflict --- class: middle .center[ # Avoiding Conflict `MooseX::Getopt` ignores _attr and NoGetopt attributes ] ```perl has _private => ( ... ); # cannot be set from command-line has more_private => ( traits => [qw( NoGetopt )], ); ``` --- class: middle center # Attributes and Documentation overlap We're pragmatists, not purists --- template: title # App::Cmd Framework for large apps --- class: middle .center[ ## A digression ] ``` $ git help usage: git [--version] [--exec-path[=
]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=
] [--work-tree=
] [--namespace=
] [-c name=value] [--help]
[
] The most commonly used git commands are: add Add file contents to the index branch List, create, or delete branches checkout Checkout a branch or paths to the working tree clone Clone a repository into a new directory commit Record changes to the repository diff Show changes between commits, commit and working tree, etc init Create an empty git repository or reinitialize an existing one log Show commit logs merge Join two or more development histories together pull Fetch from and merge with another repository or a local branch push Update remote refs along with associated objects rebase Forward-port local commits to the updated upstream head reset Reset current HEAD to the specified state rm Remove files from the working tree and from the index status Show the working tree status tag Create, list, delete or verify a tag object signed with GPG See 'git help
' for more information on a specific command. ``` --- class: middle center # App::Cmd Framework Manages Complexity --- class: middle center # Command Modules Each module, a command --- class: middle .center[ # 1: Application Module ] The entry point ```perl package App::Say; use App::Cmd::Setup -app; 1; ``` --- class: middle .center[ # 2: Command Module ] ```perl package App::Say::Command::hello; use feature qw( say ); use App::Cmd::Setup -command; sub execute { my ( $self, $opt, $args ) = @_; say "Hello, World!"; return 0; } 1; ``` --- class: middle .center[ # 3: The Runner Script ] ```perl #!/usr/bin/env perl use App::Say; App::Say->run; ``` ``` $ perl say.pl hello Hello, World! ``` --- class: middle .center[ # Arguments ] ```perl package App::Say::Command::greet; use feature qw( say ); use App::Cmd::Setup -command; sub execute { my ( $self, $opt, $args ) = @_; say "Hello, $args->[0]"; return 0; }; 1; ``` ``` $ perl say.pl greet Chicago.PM Hello, Chicago.PM ``` --- class: middle .center[ # Options ] ```perl package App::Say::Command::greet; use feature qw( say ); use App::Cmd::Setup -command; sub opt_spec { return ( [ 'greeting:s' => 'A greeting. Defaults to "Hello"' ], ) } sub execute { my ( $self, $opt, $args ) = @_; $opt->{greeting} ||= "Hello"; say "$opt->{greeting}, $args->[0]"; return 0; }; 1; ``` ``` $ perl say.pl greet --greeting Bonjour Chicago.PM Bonjour, Chicago.PM ``` --- template: title # What Next? --- class: center middle # Read The Fine Manual • Getopt::Long • Pod::Usage • MooseX::Runnable • MooseX::Getopt • Capture::Tiny • App::Cmd --- class: center middle # .code[system() exec() qx()] ## .code[$?] in .code[perldoc perlvar] --- template: title Thank You