App::Cmd
Writing Maintainable Commands
TMTOWTDI
There’s More Than
 One Way To Do It
Web
Web

• Mason
Web

• Mason
• Catalyst
Web

• Mason
• Catalyst
• CGI::Application
Web

• Mason
• Catalyst
• CGI::Application
• Maypole
Web

• Mason
• Catalyst
• CGI::Application
• Maypole
• Continuity
Web

• Mason              • RayApp
• Catalyst
• CGI::Application
• Maypole
• Continuity
Web

• Mason              • RayApp
• Catalyst           • Gantry
• CGI::Application
• Maypole
• Continuity
Web

• Mason              • RayApp
• Catalyst           • Gantry
• CGI::Application   • Tripletail
• Maypole
• Continuity
Web

• Mason              • RayApp
• Catalyst           • Gantry
• CGI::Application   • Tripletail
• Maypole            • ...
Web

• Mason              • RayApp
• Catalyst           • Gantry
• CGI::Application   • Tripletail
• Maypole            • ...
Daemons
Daemons

• POE
Daemons

• POE
• Danga
Daemons

• POE
• Danga
• Net::Server
Daemons

• POE
• Danga
• Net::Server
• Daemon::Generic
Daemons

• POE               • Proc::Daemon
• Danga
• Net::Server
• Daemon::Generic
Daemons

• POE               • Proc::Daemon
• Danga             • Net::Daemon
• Net::Server
• Daemon::Generic
Daemons

• POE               • Proc::Daemon
• Danga             • Net::Daemon
• Net::Server       • MooseX::Daemonize
• Da...
Daemons

• POE               • Proc::Daemon
• Danga             • Net::Daemon
• Net::Server       • MooseX::Daemonize
• Da...
TMTOWTDI
TMTOWTDI


• All the big problem sets have a few solutions!
TMTOWTDI


• All the big problem sets have a few solutions!
• So, when I needed to write a CLI app, I
  checked CPAN...
Command-Line Apps
Command-Line Apps


• App::CLI
Command-Line Apps
Command-Line Apps



    :-(
Everybody writes
command-line apps!
Why are there no
  good tools?
Second-Class Citizens
Second-Class Citizens

• That’s how we view them.
Second-Class Citizens

• That’s how we view them.
• They’re
Second-Class Citizens

• That’s how we view them.
• They’re
 • hard to test
Second-Class Citizens

• That’s how we view them.
• They’re
 • hard to test
 • not reusable components
Second-Class Citizens

• That’s how we view them.
• They’re
 • hard to test
 • not reusable components
 • hard to add more...
Here’s an Example
Example Script


$ sink 30min “server mx-pa-1 crashed!”
Example Script

$ sink --list

who   | time | event
------+-------+----------------------------
rjbs | 30min | server mx-p...
Example Script

GetOptions(%opt, ...);

if ($opt{list}) {
  die if @ARGV;
  @events = Events->get_all;
} else {
  my ($dur...
Example Script

$ sink --list --user jcap

who   | time | event
------+-------+----------------------------
jcap | 2hr    ...
Example Script
GetOptions(%opt, ...);

if ($opt{list}) {
  die if @ARGV;
  @events = $opt{user}
          ? Events->get(us...
Example Script
GetOptions(%opt, ...);

if ($opt{list}) {
  die if @ARGV;
  @events = $opt{user}
          ? Events->get(us...
Example Script

$ sink --start ‘putting out oil fire‘
Event begun! use --finish to finish event

$ sink --list --open
18. ...
Insult to Injury
Insult to Injury

• ...well, that’s going to take a lot of
  testing.
Insult to Injury

• ...well, that’s going to take a lot of
  testing.
• How can we test it?
Insult to Injury

• ...well, that’s going to take a lot of
  testing.
• How can we test it?
• my $output = `sink @args`;
Insult to Injury

• ...well, that’s going to take a lot of
  testing.
• How can we test it?
• my $output = `sink @args`;
•...
Here’s a Solution
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’

  App
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’

    Command
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’

                Options
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’

                                 Args
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’
“do” command
“do” command
sub run {
“do” command
sub run {
  my ($self, $opt, $args) = @_;
“do” command
sub run {
  my ($self, $opt, $args) = @_;
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start   = parse_ago($opt->{ago});
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

  my $start = parse_ago($opt->{ago});
  my $length = parse_duratio...
“do” command
sub run {
  my ($self, $opt, $args) = @_;

    my $start = parse_ago($opt->{ago});
    my $length = parse_dur...
“do” command
“do” command

sub opt_desc {
“do” command

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
“do” command

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
  [ “for=s”,   “how long you did this for”,
“do” command

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
  [ “for=s”,   “how long you did this for”,
 ...
“do” command

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
  [ “for=s”,   “how long you did this for”,
 ...
“do” command
“do” command

sub validate_args {
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;

  if (@$args != 1) {
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;

  if (@$args != 1) {
    $self->usage_error(“provide on...
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;

  if (@$args != 1) {
    $self->usage_error(“provide on...
“do” command

sub validate_args {
  my ($self, $opt, $args) = @_;

    if (@$args != 1) {
      $self->usage_error(“provid...
package Sink::Command::Do;
use base ‘App::Cmd::Command’;

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
 ...
package Sink::Command::Do;
use base ‘App::Cmd::Command’;

sub opt_desc {
  [ “start=s”, “when you started doing this” ],
 ...
Extra Scaffolding
Extra Scaffolding
package Sink;
Extra Scaffolding
package Sink;
use base ‘App::Cmd’;
Extra Scaffolding
package Sink;
use base ‘App::Cmd’;




1;
Extra Scaffolding


use Sink;
Sink->run;
Testing Your App
Testing App::Cmd
Testing App::Cmd
use Test::More tests => 3;
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd
use Test::More tests => 3;
use Test::Output;

my $error;
my $stdout = do {
  local @ARGV = qw(do --for 8h...
Testing App::Cmd

use Test::More tests => 3;
use Test::App::Cmd;
use Sink;

my ($stdout, $error) = test_app(
   Sink => qw...
Testing App::Cmd

use Test::More tests => π;
use Sink::Command::Do;

eval {
   Sink::Command::Do->validate_args(
      { f...
Growing Your App
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’
Command Breakdown


$ sink do --for 1hr --ago 1d ‘rebuild raid’

    Command
package Sink::Command::List;
use base ‘App::Cmd::Command’;

sub opt_desc { ... }

sub validate_args { ... }

sub run { ......
package Sink::Command::List;
use base ‘App::Cmd::Command’;

sub opt_desc {
  [ “open”,     “only unfinished events”    ],
...
package Sink::Command::Start;
use base ‘App::Cmd::Command’;

sub opt_desc { ... }

sub validate_args { ... }

sub run { .....
package Sink::Command::Start;
use base ‘App::Cmd::Command’;

sub opt_desc { return }

sub validate_args {
  shift->usage_e...
More Commands!

$ sink do --for 1hr --ago 1d ‘rebuild raid’

$ sink list --open

$ sink start ‘porting PHP to ASP.NET’
More Commands!
$ sink
sink help <command>

Available commands:
  commands: list the application’s commands
      help: dis...
Command Listing

package Sink::Command::Start;

=head1 NAME

Sink::Command::Start - start a new task

=cut
Command Listing


package Sink::Command::Start;

sub abstract { ‘start a new task’; }
Command Listing

$ sink commands

Available commands:
  commands: list the application’s commands
      help: display a co...
Command Listing
Command Listing

$ sink help list
Command Listing

$ sink help list
Command Listing

$ sink help list

sink list [long options...]
Command Listing

$ sink help list

sink list [long options...]
    -u --user     only events for this user
Command Listing

$ sink help list

sink list [long options...]
    -u --user     only events for this user
    --open     ...
Core Commands
Core Commands

• Where did “help” and “commands”
  come from?
Core Commands

• Where did “help” and “commands”
  come from?
• App::Cmd::Command::help
Core Commands

• Where did “help” and “commands”
  come from?
• App::Cmd::Command::help
• App::Cmd::Command::commands
Default Command
package Sink;
use base ‘App::Cmd’;




1;
Default Command
package Sink;
use base ‘App::Cmd’;

sub default_command { ‘help’ } # default




1;
Default Command
package Sink;
use base ‘App::Cmd’;

sub default_command { ‘list’ }




1;
One-Command
 Applications
Simple Example


$ ckmail check -a work -a home
No new mail.
Simple Example


$ ckmail -a work -a home
No new mail.
The Lousy Way
The Lousy Way

• create Ckmail::Command::Check
The Lousy Way

• create Ckmail::Command::Check
• make empty Ckmail.pm
The Lousy Way

• create Ckmail::Command::Check
• make empty Ckmail.pm
• make “check” the default command
The Simple Way

package Ckmail::Command::Check;
use base ‘App::Cmd::Command’;

sub opt_desc { ... }

sub run { ... }

1;
The Simple Way

package Ckmail::Command::Check;
use base ‘App::Cmd::Simple’;

sub opt_desc { ... }

sub run { ... }

1;
The Simple Way


use Ckmail;
Ckmail->run;
The Simple Way


use Ckmail::Command::Check;
Ckmail::Command::Check->run;
App::Cmd::Simple
App::Cmd::Simple

• You write a command...
App::Cmd::Simple

• You write a command...
• ...but you use it like an App::Cmd.
App::Cmd::Simple

• You write a command...
• ...but you use it like an App::Cmd.
• Later, you can just demote it.
Any
Questions?
Thank
 You!
Upcoming SlideShare
Loading in...5
×

Writing Modular Command-line Apps with App::Cmd

14,805

Published on

It's easy to write command-line programs in Perl. There are a million option parsers to choose from, and Perl makes it easy to deal with input, output, and all that stuff.

Once your program has gotten beyond just taking a few switches, though, it can be difficult to maintain a clear interface and well-tested code. App::Cmd is a lightweight framework for writing easy to manage CLI programs.

This talk provides an introduction to writing programs with App::Cmd.

Published in: Technology, Business

Writing Modular Command-line Apps with App::Cmd

  1. 1. App::Cmd Writing Maintainable Commands
  2. 2. TMTOWTDI
  3. 3. There’s More Than One Way To Do It
  4. 4. Web
  5. 5. Web • Mason
  6. 6. Web • Mason • Catalyst
  7. 7. Web • Mason • Catalyst • CGI::Application
  8. 8. Web • Mason • Catalyst • CGI::Application • Maypole
  9. 9. Web • Mason • Catalyst • CGI::Application • Maypole • Continuity
  10. 10. Web • Mason • RayApp • Catalyst • CGI::Application • Maypole • Continuity
  11. 11. Web • Mason • RayApp • Catalyst • Gantry • CGI::Application • Maypole • Continuity
  12. 12. Web • Mason • RayApp • Catalyst • Gantry • CGI::Application • Tripletail • Maypole • Continuity
  13. 13. Web • Mason • RayApp • Catalyst • Gantry • CGI::Application • Tripletail • Maypole • CGI::ExApp • Continuity
  14. 14. Web • Mason • RayApp • Catalyst • Gantry • CGI::Application • Tripletail • Maypole • CGI::ExApp • Continuity • OpenInteract
  15. 15. Daemons
  16. 16. Daemons • POE
  17. 17. Daemons • POE • Danga
  18. 18. Daemons • POE • Danga • Net::Server
  19. 19. Daemons • POE • Danga • Net::Server • Daemon::Generic
  20. 20. Daemons • POE • Proc::Daemon • Danga • Net::Server • Daemon::Generic
  21. 21. Daemons • POE • Proc::Daemon • Danga • Net::Daemon • Net::Server • Daemon::Generic
  22. 22. Daemons • POE • Proc::Daemon • Danga • Net::Daemon • Net::Server • MooseX::Daemonize • Daemon::Generic
  23. 23. Daemons • POE • Proc::Daemon • Danga • Net::Daemon • Net::Server • MooseX::Daemonize • Daemon::Generic • Event
  24. 24. TMTOWTDI
  25. 25. TMTOWTDI • All the big problem sets have a few solutions!
  26. 26. TMTOWTDI • All the big problem sets have a few solutions! • So, when I needed to write a CLI app, I checked CPAN...
  27. 27. Command-Line Apps
  28. 28. Command-Line Apps • App::CLI
  29. 29. Command-Line Apps
  30. 30. Command-Line Apps :-(
  31. 31. Everybody writes command-line apps!
  32. 32. Why are there no good tools?
  33. 33. Second-Class Citizens
  34. 34. Second-Class Citizens • That’s how we view them.
  35. 35. Second-Class Citizens • That’s how we view them. • They’re
  36. 36. Second-Class Citizens • That’s how we view them. • They’re • hard to test
  37. 37. Second-Class Citizens • That’s how we view them. • They’re • hard to test • not reusable components
  38. 38. Second-Class Citizens • That’s how we view them. • They’re • hard to test • not reusable components • hard to add more behavior later
  39. 39. Here’s an Example
  40. 40. Example Script $ sink 30min “server mx-pa-1 crashed!”
  41. 41. Example Script $ sink --list who | time | event ------+-------+---------------------------- rjbs | 30min | server mx-pa-1 crashed!
  42. 42. Example Script GetOptions(%opt, ...); if ($opt{list}) { die if @ARGV; @events = Events->get_all; } else { my ($duration, $desc) = @ARGV; Event->new($duration, $desc); }
  43. 43. Example Script $ sink --list --user jcap who | time | event ------+-------+---------------------------- jcap | 2hr | redeploy exigency subsystem
  44. 44. Example Script GetOptions(%opt, ...); if ($opt{list}) { die if @ARGV; @events = $opt{user} ? Events->get(user => $opt{user}) : Events->get_all; } else { my ($duration, $desc) = @ARGV; Event->new($duration, $desc); }
  45. 45. Example Script GetOptions(%opt, ...); if ($opt{list}) { die if @ARGV; @events = $opt{user} ? Events->get(user => $opt{user}) : Events->get_all; } else { my ($duration, $desc) = @ARGV; die if $opt{user}; Event->new($duration, $desc); }
  46. 46. Example Script $ sink --start ‘putting out oil fire‘ Event begun! use --finish to finish event $ sink --list --open 18. putting out oil fire $ sink --finish 18 Event finished! Total time taken: 23 min
  47. 47. Insult to Injury
  48. 48. Insult to Injury • ...well, that’s going to take a lot of testing.
  49. 49. Insult to Injury • ...well, that’s going to take a lot of testing. • How can we test it?
  50. 50. Insult to Injury • ...well, that’s going to take a lot of testing. • How can we test it? • my $output = `sink @args`;
  51. 51. Insult to Injury • ...well, that’s going to take a lot of testing. • How can we test it? • my $output = `sink @args`; • IPC::Run3 (or one of those)
  52. 52. Here’s a Solution
  53. 53. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’
  54. 54. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’ App
  55. 55. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’ Command
  56. 56. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’ Options
  57. 57. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’ Args
  58. 58. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’
  59. 59. “do” command
  60. 60. “do” command sub run {
  61. 61. “do” command sub run { my ($self, $opt, $args) = @_;
  62. 62. “do” command sub run { my ($self, $opt, $args) = @_;
  63. 63. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago});
  64. 64. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for});
  65. 65. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0];
  66. 66. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0];
  67. 67. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create(
  68. 68. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start,
  69. 69. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length,
  70. 70. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc;
  71. 71. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; );
  72. 72. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; );
  73. 73. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; ); print “event created!”;
  74. 74. “do” command sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; ); print “event created!”; }
  75. 75. “do” command
  76. 76. “do” command sub opt_desc {
  77. 77. “do” command sub opt_desc { [ “start=s”, “when you started doing this” ],
  78. 78. “do” command sub opt_desc { [ “start=s”, “when you started doing this” ], [ “for=s”, “how long you did this for”,
  79. 79. “do” command sub opt_desc { [ “start=s”, “when you started doing this” ], [ “for=s”, “how long you did this for”, { required => 1} ],
  80. 80. “do” command sub opt_desc { [ “start=s”, “when you started doing this” ], [ “for=s”, “how long you did this for”, { required => 1} ], }
  81. 81. “do” command
  82. 82. “do” command sub validate_args {
  83. 83. “do” command sub validate_args { my ($self, $opt, $args) = @_;
  84. 84. “do” command sub validate_args { my ($self, $opt, $args) = @_;
  85. 85. “do” command sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) {
  86. 86. “do” command sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) { $self->usage_error(“provide one argument”);
  87. 87. “do” command sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) { $self->usage_error(“provide one argument”); }
  88. 88. “do” command sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) { $self->usage_error(“provide one argument”); } }
  89. 89. package Sink::Command::Do; use base ‘App::Cmd::Command’; sub opt_desc { [ “start=s”, “when you started doing this” ], [ “for=s”, “how long you did this for”, { required => 1} ], } sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) { $self->usage_error(“provide one argument”); } } sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; ); print “event created!”; } 1;
  90. 90. package Sink::Command::Do; use base ‘App::Cmd::Command’; sub opt_desc { [ “start=s”, “when you started doing this” ], [ “for=s”, “how long you did this for”, { required => 1} ], } sub validate_args { my ($self, $opt, $args) = @_; if (@$args != 1) { $self->usage_error(“provide one argument”); } } sub run { my ($self, $opt, $args) = @_; my $start = parse_ago($opt->{ago}); my $length = parse_duration($opt->{for}); my $desc = $args->[0]; Sink::Event->create( start => $start, finish => $start + $length, desc => $desc; ); print “event created!”; } 1;
  91. 91. Extra Scaffolding
  92. 92. Extra Scaffolding package Sink;
  93. 93. Extra Scaffolding package Sink; use base ‘App::Cmd’;
  94. 94. Extra Scaffolding package Sink; use base ‘App::Cmd’; 1;
  95. 95. Extra Scaffolding use Sink; Sink->run;
  96. 96. Testing Your App
  97. 97. Testing App::Cmd
  98. 98. Testing App::Cmd use Test::More tests => 3;
  99. 99. Testing App::Cmd use Test::More tests => 3; use Test::Output;
  100. 100. Testing App::Cmd use Test::More tests => 3; use Test::Output;
  101. 101. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error;
  102. 102. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do {
  103. 103. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’);
  104. 104. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub {
  105. 105. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@;
  106. 106. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; });
  107. 107. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; }); }
  108. 108. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; }); }
  109. 109. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; }); } like $stdout, qr/^event created!$/;
  110. 110. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; }); } like $stdout, qr/^event created!$/; is Sink::Event->get_count, 1;
  111. 111. Testing App::Cmd use Test::More tests => 3; use Test::Output; my $error; my $stdout = do { local @ARGV = qw(do --for 8hr ‘sleeping’); stdout_from(sub { eval { Sink->run; 1 } or $error = $@; }); } like $stdout, qr/^event created!$/; is Sink::Event->get_count, 1; ok ! $error;
  112. 112. Testing App::Cmd use Test::More tests => 3; use Test::App::Cmd; use Sink; my ($stdout, $error) = test_app( Sink => qw(do --for 8hr ‘sleeping’) ); like $stdout, qr/^event created!$/; is Sink::Event->get_count, 1; ok ! $error;
  113. 113. Testing App::Cmd use Test::More tests => π; use Sink::Command::Do; eval { Sink::Command::Do->validate_args( { for => ‘1hr’ }, [ 1, 2, 3 ], ); }; like $@, qr/one arg/;
  114. 114. Growing Your App
  115. 115. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’
  116. 116. Command Breakdown $ sink do --for 1hr --ago 1d ‘rebuild raid’ Command
  117. 117. package Sink::Command::List; use base ‘App::Cmd::Command’; sub opt_desc { ... } sub validate_args { ... } sub run { ... } 1;
  118. 118. package Sink::Command::List; use base ‘App::Cmd::Command’; sub opt_desc { [ “open”, “only unfinished events” ], [ “user|u=s”, “only events for this user” ], } sub validate_args { shift->usage_error(’no args allowed’) if @{ $_[1] } } sub run { ... } 1;
  119. 119. package Sink::Command::Start; use base ‘App::Cmd::Command’; sub opt_desc { ... } sub validate_args { ... } sub run { ... } 1;
  120. 120. package Sink::Command::Start; use base ‘App::Cmd::Command’; sub opt_desc { return } sub validate_args { shift->usage_error(’one args required’) if @{ $_[1] } != 1 } sub run { ... } 1;
  121. 121. More Commands! $ sink do --for 1hr --ago 1d ‘rebuild raid’ $ sink list --open $ sink start ‘porting PHP to ASP.NET’
  122. 122. More Commands! $ sink sink help <command> Available commands: commands: list the application’s commands help: display a command’s help screen do: (unknown) list: (unknown) start: (unknown)
  123. 123. Command Listing package Sink::Command::Start; =head1 NAME Sink::Command::Start - start a new task =cut
  124. 124. Command Listing package Sink::Command::Start; sub abstract { ‘start a new task’; }
  125. 125. Command Listing $ sink commands Available commands: commands: list the application’s commands help: display a command’s help screen do: record that you did something list: list existing events start: start a new task
  126. 126. Command Listing
  127. 127. Command Listing $ sink help list
  128. 128. Command Listing $ sink help list
  129. 129. Command Listing $ sink help list sink list [long options...]
  130. 130. Command Listing $ sink help list sink list [long options...] -u --user only events for this user
  131. 131. Command Listing $ sink help list sink list [long options...] -u --user only events for this user --open only unfinished events
  132. 132. Core Commands
  133. 133. Core Commands • Where did “help” and “commands” come from?
  134. 134. Core Commands • Where did “help” and “commands” come from? • App::Cmd::Command::help
  135. 135. Core Commands • Where did “help” and “commands” come from? • App::Cmd::Command::help • App::Cmd::Command::commands
  136. 136. Default Command package Sink; use base ‘App::Cmd’; 1;
  137. 137. Default Command package Sink; use base ‘App::Cmd’; sub default_command { ‘help’ } # default 1;
  138. 138. Default Command package Sink; use base ‘App::Cmd’; sub default_command { ‘list’ } 1;
  139. 139. One-Command Applications
  140. 140. Simple Example $ ckmail check -a work -a home No new mail.
  141. 141. Simple Example $ ckmail -a work -a home No new mail.
  142. 142. The Lousy Way
  143. 143. The Lousy Way • create Ckmail::Command::Check
  144. 144. The Lousy Way • create Ckmail::Command::Check • make empty Ckmail.pm
  145. 145. The Lousy Way • create Ckmail::Command::Check • make empty Ckmail.pm • make “check” the default command
  146. 146. The Simple Way package Ckmail::Command::Check; use base ‘App::Cmd::Command’; sub opt_desc { ... } sub run { ... } 1;
  147. 147. The Simple Way package Ckmail::Command::Check; use base ‘App::Cmd::Simple’; sub opt_desc { ... } sub run { ... } 1;
  148. 148. The Simple Way use Ckmail; Ckmail->run;
  149. 149. The Simple Way use Ckmail::Command::Check; Ckmail::Command::Check->run;
  150. 150. App::Cmd::Simple
  151. 151. App::Cmd::Simple • You write a command...
  152. 152. App::Cmd::Simple • You write a command... • ...but you use it like an App::Cmd.
  153. 153. App::Cmd::Simple • You write a command... • ...but you use it like an App::Cmd. • Later, you can just demote it.
  154. 154. Any Questions?
  155. 155. Thank You!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×