TL;DR

Searching for CLI modules on MetaCPAN returns 1690 results. And still I wrote another, Yet Another CLI framework. This is about why mine is the one you want to use.

Introduction

CLI clients, we all write them, we all want them, but the boilerplate is just horrible. I wanted to get rid of it: Burn it with 🔥.

At my previous dayjob we had something I wrote, or co-wrote, my boss wanted a sort of chef like API. It became zsknife, but had a huge downside. You needed to register each command in the main module and I didn’t like that at all. I forked the concept internally, made it so you didn’t needed to register, but it lacked discovery.

Unfinished business

When I wanted to write a git helper/client in Perl I took the pattern of my former (or back than current) employer and improved it. The rules where simple:

  1. A CLI module should be just a run() statement
  2. A CLI module should be able to describe itself via POD
  3. A CLI module should be able to trigger a --help and --man
  4. A CLI module didn’t need to subscribe itself
  5. A CLI module should be able to have actions and subactions

The use case for me was to have an API similar to this: Verbs that explain the action you want to execute. Perhaps a noun too, for when it’s about a domain of sorts:

git lab repo create
git lab mr create
git lab mr update

# Or when the tooling itself is the domain
mytool update
mytool release

I never finished the git helper project… but!

YA::CLI

CLI modules on MetaCPAN, I saw a lot! And I wrote another, hence the name, YA::CLI , Yet Another CLI framework.

How does one use it? The meat of a command or subcommand is this:

package MyApp::CLI::MR::Create;
use Moo;
with 'YA::CLI::ActionRole';

sub action { 'mr' };
sub subaction { 'create' };

# Read Getopt::Long for cli_options
sub cli_options { return ('dry-run|n', 'force|f') }

sub run {
  ...;
}

That’s it. Help, manpages, option parsing (via Getopt::Long ), given to you for free. Add a role and you give your app super powers. The action role handles dispatch, you just write the logic. Testing is a plain object instantiation, no subprocess, no @ARGV gymnastics. The hardest part of writing a new command is deciding what it should do.

An example test for another module of mine:

{
  my $log_before = qx{git log --oneline};

  write_file('public/index.xml', $initial_rss);

  my $cmd = Blog::Release::CLI::RSS->new(config => $config);
  $cmd->run();

  my $log_after = qx{git log --oneline};
  is($log_before, $log_after, 'no git commits made');
}

SOS - POD to the rescue

You can also tell YA::CLI where to get the POD from, you just tell it where to get it:

sub usage_pod {
    return 0;    # or undef - pod from the script
    return 1;    # pod from the file itself
    return 'pl'; # similar to 0 or undef
    return '/path/to/file'; # this is used as a file for pod
}

You don’t need to call Usage::Pod yourself.

You can add POD to MyApp::CLI and have a help/man page, similar to what happens when you run git without arguments.

Argument handling

You don’t need @ARGV, you can get the args via $self->_args. All options that don’t have an attribute (via has) are stashed away in $self->_cli_args->{'your-name-here'}.

Try it out

Try it yourself:

cpanm YA::CLI

Now you write the script:

#!perl
use strict;
use warnings;

# PODNAME: myapp.pl
# ABSTRACT: yet another app

require MyApp::CLI;
MyApp::CLI->run();

You write some glue code:

package MyApp::CLI;
# ABSTRACT: Command-line interface for myapp
use Moo;
use namespace::autoclean;

extends 'YA::CLI';

# you can leave this out if you don't want it:
sub exclude_search_path {
  return ['MyApp::CLI::Role::Base'];
}

1;

And finally your first action handler:

package MyApp::CLI::MyAction;
use Moo;
with 'YA::CLI::ActionRole';

sub action { 'myaction' };

# Read Getopt::Long for cli_options
sub cli_options { return ('dry-run|n', 'force|f') }

sub run {
  ...;
}

Writing CLI scripts just became easy, no need to worry about Getopt::Long, Pod::Usage and other boilerplate. Just write a testable module and you are good to go.

YA::Goodbye