CLI::Osprey(3pm)

πŸ’– CLI::Osprey πŸ’–

okay bestie, so this is the manual page for CLI::Osprey β€” full name CLI::Osprey(3pm), filed under User Contributed Perl Documentation, and honestly? it's iconic. grab a boba, we're doing this. πŸ§‹βœ¨


πŸŽ€ NAME πŸŽ€

CLI::Osprey β€” it's literally MooX::Options + MooX::Cmd + Sanity. that's the whole vibe. πŸ’…

🌟 VERSION 🌟

version 0.09

πŸ¦‹ SYNOPSIS πŸ¦‹

so in Hello.pm you'd write:

package Hello;
use Moo;
use CLI::Osprey;

option 'message' => (
    is => 'ro',
    format => 's',
    doc => 'The message to display',
    default => 'Hello world!',
);

sub run {
    my ($self) = @_;
    print $self->message, "\n";
}

and then in hello.pl:

use Hello;
Hello->new_with_options->run;

that's it. that's the whole app. slay. πŸ”₯

πŸ‘‘ DESCRIPTION πŸ‘‘

CLI::Osprey is a module that helps you write commandline apps using the M* OO modules β€” that's Moose, Moo, and Mo, bestie, all of them. ✨

the way it works is: you structure your app as one or more modules, and those modules get instantiated with the commandline arguments as attributes. arguments get parsed using Getopt::Long::Descriptive, and then β€” this is the good part β€” both long and short help messages and complete manual pages are generated automatically for you. no cap. πŸ™Œ

your app can be a single command with options, or it can have sub-commands (think git-style, iykyk). and those sub-commands can be defined as modules (with their own options, obviously) or as simple coderefs. flexible queen energy. πŸ’…

🌈 Differences from MooX::Options 🌈

so Osprey is deliberately similar to MooX::Options, and porting an app over from MooX::Options should be pretty simple in most cases. but there are a few important differences, and you need to know them:

  • Osprey is pure-perl, with no mandatory XS dependencies. what does that mean for you? it means it can be used in fatpacked scripts, and in all those other situations where you might need to run on a bunch of diverse machines where a C compiler and control over the environment just aren't guaranteed. πŸ™ˆ
  • Osprey's support for sub-commands is built in from the very beginning. we genuinely think this makes for a better experience than MooX::Options + MooX::Cmd. periodt. πŸ’…
  • here's a big one: while MooX::Options requires an option's primary name to be the same as the attribute that holds it, and MooX::Cmd derives a sub-command's name from the name of the module that implements it β€” Osprey separates these things. so your Perl identifier naming conventions do not get to dictate your command line interface. the CLI is the main character, not the variable name. πŸ‘‘
  • Osprey doesn't use an automatic module finder (like Module::Pluggable) to locate modules for sub-commands. their names are given explicitly. yes, that's a small amount of extra typing, but it gives you more control and less fragility. worth it. 🫢

now, there are also a few things that MooX::Options has that Osprey lacks. they might get added in the future, but honestly I haven't seen the need yet. currently known missing features are JSON options, config_from_file support, autosplit, and autorange.

for JSON support though, you can just use a coercion on the attribute, turning it from a string into a ref via decode_json. problem solved, bestie. ✨

and if you want to default an app's options from a config file, you may want to do something like this in your script file:

use JSON 'decode_json';
use Path::Tiny;

MyApp->new_with_options(
    map decode_json(path($_)->slurp),
    grep -f,
    "$ENV{HOME}/.myapprc"
)->run;

provided that prefer_commandline is true (and yes, that's the default πŸ’–), any options in .myapprc will be used as defaults if that file exists β€” but they'll still be overrideable from the commandline. best of both worlds, no notes.


πŸ’« IMPORTED METHODS πŸ’«

these are the methods that get imported into a class that uses CLI::Osprey. free gifts, bestie. 🎁

πŸ“ new_with_options

parses commandline arguments, validates them, and calls the new method with the resulting parameters. any parameters you pass to new_with_options will also be passed to new β€” and the prefer_commandline import option controls which one overrides which. drama resolved. πŸ’…

πŸ“ option

the option keyword acts like has (and yes, it accepts all of the arguments that has does), but it also registers the attribute as a commandline option. see "OPTION PARAMETERS" for usage. ✨

πŸ“ osprey_usage($code, @messages)

displays a short usage message β€” the same as if the app was invoked with the -h option. it also displays the lines of text in @messages, if any are passed in. and if $code is passed a defined value, it exits with that as a status. 😌

πŸ“ osprey_help($code)

displays a more substantial usage message β€” the same as if the app was invoked with the --help option. and again, if $code is passed a defined value, it exits with that as a status.

πŸ“ osprey_man

displays a manual page for the app, containing long descriptive text (if provided) about each command and option, then exits. it's giving documentation, it's giving self-care. πŸ“–βœ¨


🧚 IMPORT PARAMETERS 🧚

the parameters you pass to use CLI::Osprey serve two roles: they customize Osprey's behavior, and they provide information about the app and its options for use in the usage messages. two jobs, one icon. πŸ‘‘

🌸 abbreviate

default: true.

if abbreviate is set to a true value, then long options can be abbreviated to the point of uniqueness. meaning: --long-option-name can be called as --lon, as long as there are no other options starting with those same letters. also, an option can always be called by its full name, even if that full name is a prefix of some longer option's name. but if abbreviate is false, then options must always be called by their full names (or by a defined short name). no shortcuts, bestie. 🚫

🌸 added_order

default: true.

if added_order is set to a true value, then two options with the same order (or no order at all) will appear in the help text in the same order as their option keywords were executed. if it's false, they'll appear in alphabetical order instead. ✨

🌸 desc

default: none.

a short description of the command, to be shown at the top of the manual page β€” and in the listing of subcommands, if this command happens to be a subcommand. 🌟

🌸 description_pod

default: none.

a description of any length, in POD format, to be included as the "DESCRIPTION" section of the command's manual page. go off, write a whole essay. πŸ“

🌸 extra_pod

default: none.

arbitrary extra POD to be included between the "DESCRIPTION" and "OPTIONS" sections of the manual page. it's the little bonus content between the chapters. πŸŽ€

🌸 getopt_options

default: ['require_order'].

contains a list of options to control option parsing behavior (see "Configuring Getopt::Long" in Getopt::Long). but β€” heads up β€” note that many of these are not actually helpful with Osprey, and using permute will likely break subcommands entirely. 😭 also, MooX::Options calls this parameter flavour, just so you know the lore.

🌸 prefer_commandline

default: true.

if true, command-line options override key/value pairs passed to new_with_options. if false, the reverse is true. pick your fighter. βš”οΈ

🌸 preserve_argv

default: false.

if true, the @ARGV array will be localized for the duration of new_with_options, and will be left in the same state after option parsing as it was before. if false, the @ARGV array will be modified by option parsing β€” removing any recognized options, values, and subcommands, and leaving behind any positional parameters, or anything after and including a -- separator. so, yeah, know which one you want. πŸ’€

🌸 usage_string

default: "USAGE: $program_name %o"

provides the header of the usage message printed in response to the -h option, or in response to an error during option processing. the format of the string is described in "$usage_desc" in Getopt::Long::Descriptive. ✨

🌸 on_demand

default: false

if set to a true value, the commands' modules won't be loaded at compile time β€” they'll only be loaded if the command is actually invoked. super useful for minimizing compile time if your application has a lot of commands, or if the commands are on the heavy side. πŸ‹οΈ

but bestie, be careful: note that enabling this feature may interfere with the ability to fatpack the application. 😭

πŸ’… OPTION PARAMETERS πŸ’…

πŸŽ€ doc

default: None.

documentation for the option, used in --help output. for best results, it should be no more than a short paragraph. keep it cute, keep it brief. ✨

πŸŽ€ format

default: None (i.e. boolean).

the format of the option argument β€” same as Getopt::Long. an option with no format is a boolean, meaning it doesn't take an additional argument. the other formats are:

  • s β€” string
  • i β€” decimal integer
  • o β€” integer (supports 0x for hex, 0b for binary, and 0 for octal).
  • f β€” floating-point number

πŸŽ€ format_doc

default: depends on format.

describes the type of an option's argument. for example, if the string option copy-to specifies a hostname, you can give it format_doc => "hostname" and it'll display as --copy-to hostname in the help text, instead of --copy-to string. it's giving customization. πŸ’…

πŸŽ€ hidden

default: false.

a "hidden" option will be recognized, but not listed in automatically generated documentation. it's there, it works, it's just shy. πŸ™ˆ

πŸŽ€ negatable

default: false.

adds the --no- version of the option, which sets it to a false value. equivalent to ! in Getopt::Long. flip the switch, bestie. πŸ”„

πŸŽ€ option

default: Same as the attribute name, with underscores replaced by hyphens.

allows the command-line option for an attribute to differ from the attribute name β€” it's like init_arg, except for the commandline. freedom of expression. πŸ¦‹

πŸŽ€ long_doc

default: none.

long documentation of the option, for the manual page. this is POD, so POD formatting is available, and paragraphs need to be separated by "\n\n". if it's not provided, the short documentation will be used instead. no pressure. πŸ“š

πŸŽ€ order

default: None.

allows you to control the order that options are listed in the help text. options without an order attribute are sorted by the order their option statements are executed, if added_order is true β€” and by alphabetical order otherwise. options without an explicit order are placed as though they had order 9999, so use small values to sort before the automatically-sorted options, and values of 10000 or greater to sort at the end. do the math, queen. πŸ”’

πŸŽ€ repeatable

default: false.

allows an option to be specified more than once. when used on a "boolean" option with no format, each appearance of the option will increment the value by 1 (equivalent to + in Getopt::Long). when used on an option with arguments, it produces an arrayref β€” one value per appearance of the option. ✨

πŸŽ€ required

default: false.

this is a Moo/Moose feature honored by Osprey. a "required" attribute must be passed on the commandline, unless it's passed to the constructor. generated documentation will show the option as non-optional. no skipping it, bestie. 🚨

πŸŽ€ short

default: None.

gives an option a single-character "short" form β€” e.g. -v for --verbose. one letter, so much power. ⚑

πŸŽ€ spacer_before

default: false.

causes a blank line to appear before this option in help output. it's giving breathing room. 🌬️

πŸŽ€ spacer_after

default: false.

causes a blank line to appear after this option in help output. symmetric queen. 🌬️

🌸 SUBCOMMANDS 🌸

Okay bestie, this is the part where things get iconic. An Osprey command can have subcommands, and those subcommands can have their own options, their own docs, their whole own thing β€” so you can build a whole complicated app under one single command like it's nothing. πŸ’… Osprey parses the options for every command in the chain, then constructs them top-to-bottom, and each subcommand gets handed a reference to its parent. Family reunion but make it architecture. 🫢

πŸŽ€ Subcommand Classes

A subcommand can just be another class, and that class also says use CLI::Osprey. Like so, queen:

package MyApp;
use Moo;
use CLI::Osprey;

option verbose => (
    is => 'ro',
    short => 'v',
);

subcommand frobnicate => 'MyApp::Frobnicate';

package MyApp::Frobnicate;
use Moo;
use CLI::Osprey;

option target => (
    is => 'ro',
    format => 's',
);

sub run {
    my ($self) = @_;
    if ($self->parent_command->verbose) {
        say "Be dangerous, and unpredictable... and make a lot of noise.";
    }
    $self->do_something_with($self->target);
}

πŸ¦‹ Inline Subcommands

Now if making a whole separate class feels like too much (it's giving extra, honestly), you can also pass a coderef as the subcommand. Perfect for when a class would be excessive. Watch this:

package Greet;
use Moo;
use CLI::Osprey;

option target => (
    is => 'ro',
    default => "world",
);

subcommand hello => sub {
    my ($self, $parent) = @_;
    say "Hello ", $parent->target;
};

subcommand goodbye => sub {
    my ($self, $parent) = @_;
    say "Goodbye ", $parent->target;
};

And you'd invoke that whole vibe as greet --target world hello. ✨ Also important, no cap: inline subcommands are implemented using CLI::Osprey::InlineSubcommand. πŸ’–


πŸ’œ THANKS πŸ’œ

Real talk, this module is based heavily on code from MooX::Options and takes strong inspiration from MooX::Cmd and MooX::Options::Actions. Massive shoutout to celogeek, Jens Reshack, Getty, Tom Bloor, and literally all the contributors to those modules. 🫢 Thanks to mst for prodding me to do this (the peer pressure worked, and it slayed). And thanks Grinnz for helping me update my dzillage. πŸ˜­πŸ’–

πŸ‘‘ AUTHOR πŸ‘‘

Andrew Rodland <arodland@cpan.org>

This software is copyright (c) 2025 by Andrew Rodland.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


and that's the whole page bestie, you ate it UP πŸ’…βœ¨

This was CLI::Osprey(3pm) from perl v5.42.0, dated 2026-01-16 β€” go forth and build something iconic, mwah πŸŽ€πŸ¦‹πŸ’–

πŸ“¦ where this page comes from