Group
Extension

Perinci-CmdLine-Gen/lib/Perinci/CmdLine/Gen.pm

package Perinci::CmdLine::Gen;

use 5.010001;
use strict;
use warnings;
use Log::ger;

use Data::Dump qw(dump);
use File::Which;
use String::Indent qw(indent);

use Exporter qw(import);
our @EXPORT_OK = qw(
                       gen_perinci_cmdline_script
                       gen_pericmd_script
               );

our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2022-10-19'; # DATE
our $DIST = 'Perinci-CmdLine-Gen'; # DIST
our $VERSION = '0.502'; # VERSION

our %SPEC;

sub _pa {
    state $pa = do {
        require Perinci::Access;
        my $pa = Perinci::Access->new;
        $pa;
    };
    $pa;
}

sub _riap_request {
    my ($action, $url, $extras, $main_args) = @_;

    local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0
        unless $main_args->{ssl_verify_hostname};

    _pa()->request($action => $url, %{$extras // {}});
}

$SPEC{gen_pericmd_script} = {
    v => 1.1,
    summary => 'Generate Perinci::CmdLine CLI script',
    args => {

        output_file => {
            summary => 'Path to output file',
            schema => ['filename*'],
            default => '-',
            cmdline_aliases => { o=>{} },
            tags => ['category:output'],
        },
        overwrite => {
            schema => [bool => default => 0],
            summary => 'Whether to overwrite output if previously exists',
            tags => ['category:output'],
        },

        url => {
            summary => 'URL to function (or package, if you have subcommands)',
            schema => 'riap::url*',
            req => 1,
            pos => 0,
        },
        subcommands => {
            'x.name.is_plural' => 1,
            summary => 'Hash of subcommand entries, where each entry is "url[:summary]"',
            'summary.alt.plurality.singular' => 'Subcommand name with function URL and optional summary',
            schema => ['hash*', of=>'str*'],
            description => <<'_',

An optional summary can follow the URL, e.g.:

    URL[:SUMMARY]

Example (on CLI):

    --subcommand add=/My/App/add_item --subcommand bin='/My/App/bin_item:Delete an item'

_
            cmdline_aliases => { s=>{} },
        },
        subcommands_from_package_functions => {
            summary => "Form subcommands from functions under package's URL",
            schema => ['bool', is=>1],
            description => <<'_',

This is an alternative to the `subcommands` option. Instead of specifying each
subcommand's name and URL, you can also specify that subcommand names are from
functions under the package URL in `url`. So for example if `url` is `/My/App/`,
hen all functions under `/My/App` are listed first. If the functions are:

    foo
    bar
    baz_qux

then the subcommands become:

    foo => /My/App/foo
    bar => /My/App/bar
    "baz-qux" => /My/App/baz_qux

_
        },
        default_subcommand => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => 'str*',
        },
        include_package_functions_match => {
            schema => 're*',
            summary => 'Only include package functions matching this pattern',
            links => [
                'subcommands_from_package_functions',
                'exclude_package_functions_match',
            ],
        },
        exclude_package_functions_match => {
            schema => 're*',
            summary => 'Exclude package functions matching this pattern',
            links => [
                'subcommands_from_package_functions',
                'include_package_functions_match',
            ],
        },
        cmdline => {
            summary => 'Specify module to use',
            schema  => 'perl::modname*',
            default => 'Perinci::CmdLine::Any',
            completion => ['classic', 'inline', 'lite'],
            cmdline_aliases => {
                classic => { code=>sub{ $_[0]{cmdline} = 'classic' }, is_flag=>1, summary => 'Shortcut for --cmdline=classic' },
                inline  => { code=>sub{ $_[0]{cmdline} = 'inline'  }, is_flag=>1, summary => 'Shortcut for --cmdline=inline'  },
                lite    => { code=>sub{ $_[0]{cmdline} = 'lite'    }, is_flag=>1, summary => 'Shortcut for --cmdline=lite'    },
            },
        },
        prefer_lite => {
            summary => 'Prefer Perinci::CmdLine::Lite backend',
            'summary.alt.bool.not' => 'Prefer Perinci::CmdLine::Classic backend',
            schema  => 'bool',
            default => 1,
        },
        allow_unknown_opts => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => 'bool',
        },
        pass_cmdline_object => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            description => <<'_',

Currently irrelevant when generating with Perinci::CmdLine::Inline.

_
            schema  => 'bool',
        },
        pack_deps => {
            summary => 'Whether to pack dependencies in Perinci::CmdLine::Inline script',
            schema => 'bool*',
            description => <<'_',

Will be passed to <pm:Perinci::CmdLine>'s `gen_inline_pericmd_script`'s
`pack_deps` option.

_
            tags => ['variant:inline'],
        },
        log => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema  => 'bool',
        },
        extra_urls_for_version => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['array*', of=>'str*'],
        },
        default_log_level => {
            schema  => ['str', in=>[qw/trace debug info warn error fatal none/]],
        },
        ssl_verify_hostname => {
            summary => q[If set to 0, will add: $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;' to code],
            schema  => 'bool',
            default => 1,
        },
        code_before_instantiate_cmdline => {
            schema => 'str',
        },
        code_after_end => {
            schema => 'str',
        },
        read_config => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => 'bool',
        },
        config_filename => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['any*', of=>[
                'str*',
                'hash*',
                ['array*', of=>['any*', of=>['str*','hash*']]],
            ]],
        },
        config_dirs => {
            'x.name.is_plural' => 1,
            'x.name.singular' => 'config_dir',
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['array*', of=>'str*'],
        },
        read_env => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => 'bool',
        },
        env_name => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => 'str',
        },
        load_module => {
            summary => 'Load extra modules',
            schema => ['array', of=>'perl::modname*'],
        },
        allow_prereq => {
            summary => 'Allow script to depend on these modules',
            schema => ['array*', of=>'perl::modname*'],
            description => <<'_',

Sometimes, as in the case of using `Perinci::CmdLine::Inline`, dependency to
some modules (e.g. non-core XS modules) are prohibited because the goal is to
have a free-standing script. This option allows whitelisting some extra modules.

If you use `Perinci::CmdLine::Inline`, this option will be passed to it.

_
            tags => ['variant:inline'],
        },
        interpreter_path => {
            summary => 'What to put on shebang line',
            schema => 'str',
        },
        script_name => {
            schema => 'str',
        },
        script_summary => {
            schema => 'str',
        },
        script_version => {
            summary => 'Use this for version number instead',
            schema => 'str',
        },
        default_format => {
            summary => 'Set default format',
            schema  => 'str',
        },
        skip_format => {
            summary => 'Assume that function returns raw text which needs no formatting',
            schema  => 'bool',
        },
        use_cleanser => {
            summary => 'Whether to use data cleansing before outputting to JSON',
            schema  => 'bool',
        },
        use_utf8 => {
            summary => 'Whether to set utf8 flag on output, will be passed to Perinci::CmdLine constructor',
            schema  => 'bool',
        },
        default_dry_run => {
            summary => 'Whether to set default_dry_run, will be passed to Perinci::CmdLine constructor',
            schema  => 'bool',
        },
        per_arg_json => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['bool*'],
        },
        per_arg_yaml => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['bool*'],
        },
        validate_args => {
            summary => 'Will be passed to Perinci::CmdLine constructor',
            schema => ['bool*'],
        },

        pod => {
            summary => 'Whether to generate POD or not',
            schema => ['bool*'],
            description => <<'_',

Currently only Perinci::CmdLine::Inline generates POD.

_
            default => 1,
        },

        copt_version_enable => {
            schema => 'bool*',
            default => 1,
        },
        copt_version_getopt => {
            schema => 'str*',
        },
        copt_help_enable => {
            schema => 'bool*',
            default => 1,
        },
        copt_help_getopt => {
            schema => 'str*',
        },
        copt_naked_res_enable => {
            schema => 'bool*',
            default => 1,
        },
        copt_naked_res_getopt => {
            schema => 'str*',
        },
        copt_naked_res_default => {
            schema => 'bool*',
        },
    },
};
sub gen_pericmd_script {
    my %args = @_;

    local $Data::Dump::INDENT = "    ";

    # XXX schema
    $args{output_file} //= '-';
    $args{cmdline} //= 'Perinci::CmdLine::Any';
    $args{prefer_lite} //= 1;
    $args{ssl_verify_hostname} //= 1;
    $args{pod} //= 1;
    $args{copt_version_enable}    //= 1;
    $args{copt_help_enable}       //= 1;
    $args{copt_naked_res_enable}  //= 1;

    my $output_file = $args{output_file};

    my $script_name = $args{script_name};
    unless ($script_name) {
        if ($output_file eq '-') {
            $script_name = 'script';
        } else {
            $script_name = $output_file;
            $script_name =~ s!.+[\\/]!!;
        }
    }

    my $cmdline_mod = "Perinci::CmdLine::Any";
    my $cmdline_mod_ver = 0;
    if ($args{cmdline}) {
        my $val = $args{cmdline};
        if ($val eq 'any') {
            $cmdline_mod = "Perinci::CmdLine::Any";
        } elsif ($val eq 'classic') {
            $cmdline_mod = "Perinci::CmdLine::Classic";
        } elsif ($val eq 'lite') {
            $cmdline_mod = "Perinci::CmdLine::Lite";
        } elsif ($val eq 'inline') {
            $cmdline_mod = "Perinci::CmdLine::Inline";
        } else {
            $cmdline_mod = $val;
        }
    }

    my $subcommands;
    if ($args{subcommands} && keys %{$args{subcommands}}) {
        $subcommands = {};
        for my $sc_name (keys %{ $args{subcommands} }) {
            my ($sc_url, $sc_summary) = split /:/, $args{subcommands}{$sc_name}, 2;
            $subcommands->{$sc_name} = {
                url => $sc_url,
                (summary => $sc_summary) x !!(defined $sc_summary && length $sc_summary),
            };
        }
    } elsif ($args{subcommands_from_package_functions}) {
        my $res = _riap_request(child_metas => $args{url} => {detail=>1}, \%args);
        return [500, "Can't child_metas $args{url}: $res->[0] - $res->[1]"]
            unless $res->[0] == 200;
        $subcommands = {};
        for my $uri (keys %{ $res->[2] }) {
            next unless $uri =~ /\A\w+\z/; # functions only
            my $meta = $res->[2]{$uri};
            if ($args{include_package_functions_match}) {
                next unless $uri =~ /$args{include_package_functions_match}/;
            }
            if ($args{exclude_package_functions_match}) {
                next if $uri =~ /$args{exclude_package_functions_match}/;
            }
            (my $sc_name = $uri) =~ s/_/-/g;
            $subcommands->{$sc_name} = {
                url     => "$args{url}$uri",
                (summary => $meta->{summary}) x !!(defined $meta->{summary}),
            };
        }
    }

    # request metadata to get summary (etc)
    my $meta;
    {
        my $res = _riap_request(meta => $args{url} => {}, \%args);
        if ($res->[0] == 200) {
            $meta = $res->[2];
        } else {
            warn "Can't meta $args{url}: $res->[0] - $res->[1]"
                if $args{-cmdline};
            $meta = {v=>1.1, _note=>'No meta', args=>{}};
        }
    }

    my $gen_sig = join(
        "",
        "# Note: This script is a CLI",
        ($meta->{args} ? " for Riap function $args{url}" : ""), # a quick hack to guess meta is func metadata (XXX should've done an info Riap request)
        "\n",
        "# and generated automatically using ", __PACKAGE__,
        " version ", ($Perinci::CmdLine::Gen::VERSION // '?'), "\n",
    );

    my $extra_modules = {};

    # generate code
    my $code;
    if ($cmdline_mod eq 'Perinci::CmdLine::Inline') {
        require Perinci::CmdLine::Inline;
        $cmdline_mod_ver = $Perinci::CmdLine::Inline::VERSION;
        my $res = Perinci::CmdLine::Inline::gen_inline_pericmd_script(
            url => "$args{url}",
            script_name => $args{script_name},
            script_summary => $args{script_summary},
            script_version => $args{script_version},
            subcommands => $subcommands,
            (default_subcommand => $args{default_subcommand}) x !!$args{default_subcommand},
            log => $args{log},
            (extra_urls_for_version => $args{extra_urls_for_version}) x !!$args{extra_urls_for_version},
            include => $args{load_module},
            code_after_shebang => $gen_sig,
            (code_before_parse_cmdline_options => $args{code_before_instantiate_cmdline}) x !!$args{code_before_instantiate_cmdline},
            (code_after_end => $args{code_after_end}) x !!$args{code_after_end},
            read_config => $args{read_config},
            config_filename => $args{config_filename},
            config_dirs => $args{config_dirs},
            read_env => $args{read_env},
            env_name => $args{env_name},
            shebang => $args{interpreter_path},
            (default_format => $args{default_format}) x !!$args{default_format},
            skip_format => $args{skip_format} ? 1:0,
            (use_cleanser => $args{use_cleanser} ? 1:0) x !!(defined $args{use_cleanser}),
            (use_utf8 => $args{use_utf8} ? 1:0) x !!(defined $args{use_utf8}),
            (default_dry_run => $args{default_dry_run} ? 1:0) x !!(defined $args{default_dry_run}),
            (allow_prereq => $args{allow_prereq}) x !!$args{allow_prereq},
            (per_arg_json => $args{per_arg_json} ? 1:0) x !!(defined $args{per_arg_json}),
            (per_arg_yaml => $args{per_arg_yaml} ? 1:0) x !!(defined $args{per_arg_yaml}),
            (pack_deps => $args{pack_deps}) x !!(defined $args{pack_deps}),
            (validate_args => $args{validate_args}) x !!(defined $args{validate_args}),
            (pod => $args{pod}) x !!(defined $args{pod}),
            # XXX copt_* not yet observed
        );
        return $res if $res->[0] != 200;
        $code = $res->[2];
        for (keys %{ $res->[3]{'func.raw_result'}{req_modules} }) {
            $extra_modules->{$_} = $res->[3]{'func.raw_result'}{req_modules}{$_};
        }
    } else {
        $extra_modules->{'Log::ger'} = '0.038' if $args{log};
        # determine minimum required version
        if ($cmdline_mod =~ /\APerinci::CmdLine::(Lite|Any)\z/) {
            if ($cmdline_mod eq 'Perinci::CmdLine::Lite') {
                $cmdline_mod_ver = "1.915";
            } else {
                $extra_modules->{"Perinci::CmdLine::Any"} = "0.154";
                $extra_modules->{"Perinci::CmdLine::Lite"} = "1.924";
            }
        } elsif ($cmdline_mod =~ /\APerinci::CmdLine::Classic\z/) {
            $extra_modules->{"Perinci::CmdLine::Base"} = "1.924";
            $extra_modules->{"Perinci::CmdLine::Classic"} = "1.770";
        }

        $code = join(
            "",
            "#!", ($args{interpreter_path} // $^X), "\n",
            "\n",
            $gen_sig,
            "\n",
            "use 5.010001;\n",
            "use strict;\n",
            "use warnings;\n",
            ($args{log} ? "use Log::ger;\n" : ""),
            "\n",

            ($args{load_module} && @{$args{load_module}} ?
                 join("", map {"use $_;\n"} @{$args{load_module}})."\n" : ""),

            "use $cmdline_mod",
            ($cmdline_mod eq 'Perinci::CmdLine::Any' &&
                 defined($args{prefer_lite}) && !$args{prefer_lite} ? " -prefer_lite=>0" : ""),
            ";\n\n",

            ($args{ssl_verify_hostname} ?
                 "" : '$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;' . "\n\n"),

            "# AUTHORITY\n",
            "# DATE\n",
            "# DIST\n",
            "# VERSION\n",
            "\n",

            ($args{code_before_instantiate_cmdline} ? "# code_before_instantiate_cmdline\n" . $args{code_before_instantiate_cmdline} . "\n\n" : ""),

            "my \$cmdline = $cmdline_mod->new(\n",
            "    url => ", dump("$args{url}"), ",\n",
            (defined($subcommands) ? "    subcommands => " . indent("    ", dump($subcommands), {first_line_indent=>""}) . ",\n" : ""),
            "    program_name => " . dump($script_name) . ",\n",
            (defined($args{default_subcommand}) ? "    default_subcommand => " . dump($args{default_subcommand}) . ",\n" : ""),
            (defined($args{log}) ? "    log => " . dump($args{log}) . ",\n" : ""),
            ($args{default_log_level} ? "    log_level => " . dump($args{default_log_level}) . ",\n" : ""),
            (defined($args{allow_unknown_opts}) ? "    allow_unknown_opts => " . dump($args{allow_unknown_opts}) . ",\n" : ""),
            (defined($args{pass_cmdline_object}) ? "    pass_cmdline_object => " . dump($args{pass_cmdline_object}) . ",\n" : ""),
            (defined($args{extra_urls_for_version}) ? "    extra_urls_for_version => " . dump($args{extra_urls_for_version}) . ",\n" : ""),
            (defined($args{read_config}) ? "    read_config => " . ($args{read_config} ? 1:0) . ",\n" : ""),
            (defined($args{config_filename}) ? "    config_filename => " . dump(ref($args{config_filename}) eq 'ARRAY' && @{$args{config_filename}}==1 ? $args{config_filename}[0] : $args{config_filename}) . ",\n" : ""),
            (defined($args{config_dirs}) ? "    config_dirs => " . dump($args{config_dirs}) . ",\n" : ""),
            (defined($args{read_env})    ? "    read_env => " . ($args{read_env} ? 1:0) . ",\n" : ""),
            (defined($args{env_name})    ? "    env_name => " . dump($args{env_name}) . ",\n" : ""),
            ($args{default_format} ? "    default_format => " . dump($args{default_format}) . ",\n" : ""),
            ($args{skip_format} ? "    skip_format => 1,\n" : ""),
            (defined($args{use_utf8}) ? "    use_utf8 => " . dump($args{use_utf8}) . ",\n" : ""),
            (defined($args{use_cleanser}) ? "    use_cleanser => " . dump($args{use_cleanser}) . ",\n" : ""),
            (defined($args{default_dry_run}) ? "    default_dry_run => " . dump($args{default_dry_run}) . ",\n" : ""),
            (defined($args{per_arg_json}) ? "    per_arg_json => " . dump($args{per_arg_json}) . ",\n" : ""),
            (defined($args{per_arg_yaml}) ? "    per_arg_yaml => " . dump($args{per_arg_yaml}) . ",\n" : ""),
            (defined($args{validate_args}) ? "    validate_args => " . dump($args{validate_args}) . ",\n" : ""),
            ");\n\n",

            (!$args{copt_version_enable} ? "delete \$cmdline->{common_opts}{version};\n\n" :
                 defined($args{copt_version_getopt}) ? "\$cmdline->{common_opts}{version}{getopt} = ".dump($args{copt_version_getopt}).";\n\n" : ""),

            (!$args{copt_help_enable} ? "delete \$cmdline->{common_opts}{help};\n\n" :
                 defined($args{copt_help_getopt}) ? "\$cmdline->{common_opts}{help}{getopt} = ".dump($args{copt_help_getopt}).";\n\n" : ""),

            (!$args{copt_naked_res_enable} ? "delete \$cmdline->{common_opts}{naked_res};\n\n" : (
                (defined($args{copt_naked_res_getopt})  ? "\$cmdline->{common_opts}{naked_res}{getopt}  = ".dump($args{copt_naked_res_getopt}).";\n\n" : ""),
                (defined($args{copt_naked_res_default}) ? "\$cmdline->{common_opts}{naked_res}{default} = ".dump($args{copt_naked_res_default}).";\n\n" : ""),
            )),

            "\$cmdline->run;\n",
            "\n",
        );

        # abstract line
        $code .= "# ABSTRACT: " . ($args{script_summary} // $meta->{summary} // $script_name) . "\n";

        # podname
        $code .= "# PODNAME: $script_name\n";

        $code .= "# code_after_end\n" . $args{code_after_end} . "\n\n"
            if $args{code_after_end};

    } # END generate code

    if ($output_file ne '-') {
        log_trace("Outputing result to %s ...", $output_file);
        if ((-f $output_file) && !$args{overwrite}) {
            return [409, "Output file '$output_file' already exists (please use --overwrite if you want to override)"];
        }
        open my($fh), ">", $output_file
            or return [500, "Can't open '$output_file' for writing: $!"];

        print $fh $code;
        close $fh
            or return [500, "Can't write '$output_file': $!"];

        chmod 0755, $output_file or do {
            log_warn("Can't 'chmod 0755, $output_file': $!");
        };

        my $output_name = $output_file;
        $output_name =~ s!.+[\\/]!!;

        if (which("shcompgen") && which($output_name)) {
            log_trace("We have shcompgen in PATH and output ".
                          "$output_name is also in PATH, running shcompgen ...");
            system "shcompgen", "generate", $output_name;
        }

        $code = "";
    }

    [200, "OK", $code, {
        'func.cmdline_module' => $cmdline_mod,
        'func.cmdline_module_version' => $cmdline_mod_ver,
        'func.cmdline_module_inlined' => ($cmdline_mod eq 'Perinci::CmdLine::Inline' ? 1:0),
        'func.extra_modules' => $extra_modules,
        'func.script_name' => 0,
    }];
}

# alias
{
    no warnings 'once';
    *gen_perinci_cmdline_script = \&gen_pericmd_script;
    $SPEC{gen_perinci_cmdline_script} = $SPEC{gen_pericmd_script};
}

1;
# ABSTRACT: Generate Perinci::CmdLine CLI script

__END__

=pod

=encoding UTF-8

=head1 NAME

Perinci::CmdLine::Gen - Generate Perinci::CmdLine CLI script

=head1 VERSION

This document describes version 0.502 of Perinci::CmdLine::Gen (from Perl distribution Perinci-CmdLine-Gen), released on 2022-10-19.

=head1 FUNCTIONS


=head2 gen_pericmd_script

Usage:

 gen_pericmd_script(%args) -> [$status_code, $reason, $payload, \%result_meta]

Generate Perinci::CmdLine CLI script.

This function is not exported by default, but exportable.

Arguments ('*' denotes required arguments):

=over 4

=item * B<allow_prereq> => I<array[perl::modname]>

Allow script to depend on these modules.

Sometimes, as in the case of using C<Perinci::CmdLine::Inline>, dependency to
some modules (e.g. non-core XS modules) are prohibited because the goal is to
have a free-standing script. This option allows whitelisting some extra modules.

If you use C<Perinci::CmdLine::Inline>, this option will be passed to it.

=item * B<allow_unknown_opts> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<cmdline> => I<perl::modname> (default: "Perinci::CmdLine::Any")

Specify module to use.

=item * B<code_after_end> => I<str>

(No description)

=item * B<code_before_instantiate_cmdline> => I<str>

(No description)

=item * B<config_dirs> => I<array[str]>

Will be passed to Perinci::CmdLine constructor.

=item * B<config_filename> => I<str|hash|array[str|hash]>

Will be passed to Perinci::CmdLine constructor.

=item * B<copt_help_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_help_getopt> => I<str>

(No description)

=item * B<copt_naked_res_default> => I<bool>

(No description)

=item * B<copt_naked_res_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_naked_res_getopt> => I<str>

(No description)

=item * B<copt_version_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_version_getopt> => I<str>

(No description)

=item * B<default_dry_run> => I<bool>

Whether to set default_dry_run, will be passed to Perinci::CmdLine constructor.

=item * B<default_format> => I<str>

Set default format.

=item * B<default_log_level> => I<str>

(No description)

=item * B<default_subcommand> => I<str>

Will be passed to Perinci::CmdLine constructor.

=item * B<env_name> => I<str>

Will be passed to Perinci::CmdLine constructor.

=item * B<exclude_package_functions_match> => I<re>

Exclude package functions matching this pattern.

=item * B<extra_urls_for_version> => I<array[str]>

Will be passed to Perinci::CmdLine constructor.

=item * B<include_package_functions_match> => I<re>

Only include package functions matching this pattern.

=item * B<interpreter_path> => I<str>

What to put on shebang line.

=item * B<load_module> => I<array[perl::modname]>

Load extra modules.

=item * B<log> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<output_file> => I<filename> (default: "-")

Path to output file.

=item * B<overwrite> => I<bool> (default: 0)

Whether to overwrite output if previously exists.

=item * B<pack_deps> => I<bool>

Whether to pack dependencies in Perinci::CmdLine::Inline script.

Will be passed to L<Perinci::CmdLine>'s C<gen_inline_pericmd_script>'s
C<pack_deps> option.

=item * B<pass_cmdline_object> => I<bool>

Will be passed to Perinci::CmdLine constructor.

Currently irrelevant when generating with Perinci::CmdLine::Inline.

=item * B<per_arg_json> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<per_arg_yaml> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<pod> => I<bool> (default: 1)

Whether to generate POD or not.

Currently only Perinci::CmdLine::Inline generates POD.

=item * B<prefer_lite> => I<bool> (default: 1)

Prefer Perinci::CmdLine::Lite backend.

=item * B<read_config> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<read_env> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<script_name> => I<str>

(No description)

=item * B<script_summary> => I<str>

(No description)

=item * B<script_version> => I<str>

Use this for version number instead.

=item * B<skip_format> => I<bool>

Assume that function returns raw text which needs no formatting.

=item * B<ssl_verify_hostname> => I<bool> (default: 1)

If set to 0, will add: $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;' to code.

=item * B<subcommands> => I<hash>

Hash of subcommand entries, where each entry is "url[:summary]".

An optional summary can follow the URL, e.g.:

 URL[:SUMMARY]

Example (on CLI):

 --subcommand add=/My/App/add_item --subcommand bin='/My/App/bin_item:Delete an item'

=item * B<subcommands_from_package_functions> => I<bool>

Form subcommands from functions under package's URL.

This is an alternative to the C<subcommands> option. Instead of specifying each
subcommand's name and URL, you can also specify that subcommand names are from
functions under the package URL in C<url>. So for example if C<url> is C</My/App/>,
hen all functions under C</My/App> are listed first. If the functions are:

 foo
 bar
 baz_qux

then the subcommands become:

 foo => /My/App/foo
 bar => /My/App/bar
 "baz-qux" => /My/App/baz_qux

=item * B<url>* => I<riap::url>

URL to function (or package, if you have subcommands).

=item * B<use_cleanser> => I<bool>

Whether to use data cleansing before outputting to JSON.

=item * B<use_utf8> => I<bool>

Whether to set utf8 flag on output, will be passed to Perinci::CmdLine constructor.

=item * B<validate_args> => I<bool>

Will be passed to Perinci::CmdLine constructor.


=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)



=head2 gen_perinci_cmdline_script

Usage:

 gen_perinci_cmdline_script(%args) -> [$status_code, $reason, $payload, \%result_meta]

Generate Perinci::CmdLine CLI script.

This function is not exported by default, but exportable.

Arguments ('*' denotes required arguments):

=over 4

=item * B<allow_prereq> => I<array[perl::modname]>

Allow script to depend on these modules.

Sometimes, as in the case of using C<Perinci::CmdLine::Inline>, dependency to
some modules (e.g. non-core XS modules) are prohibited because the goal is to
have a free-standing script. This option allows whitelisting some extra modules.

If you use C<Perinci::CmdLine::Inline>, this option will be passed to it.

=item * B<allow_unknown_opts> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<cmdline> => I<perl::modname> (default: "Perinci::CmdLine::Any")

Specify module to use.

=item * B<code_after_end> => I<str>

(No description)

=item * B<code_before_instantiate_cmdline> => I<str>

(No description)

=item * B<config_dirs> => I<array[str]>

Will be passed to Perinci::CmdLine constructor.

=item * B<config_filename> => I<str|hash|array[str|hash]>

Will be passed to Perinci::CmdLine constructor.

=item * B<copt_help_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_help_getopt> => I<str>

(No description)

=item * B<copt_naked_res_default> => I<bool>

(No description)

=item * B<copt_naked_res_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_naked_res_getopt> => I<str>

(No description)

=item * B<copt_version_enable> => I<bool> (default: 1)

(No description)

=item * B<copt_version_getopt> => I<str>

(No description)

=item * B<default_dry_run> => I<bool>

Whether to set default_dry_run, will be passed to Perinci::CmdLine constructor.

=item * B<default_format> => I<str>

Set default format.

=item * B<default_log_level> => I<str>

(No description)

=item * B<default_subcommand> => I<str>

Will be passed to Perinci::CmdLine constructor.

=item * B<env_name> => I<str>

Will be passed to Perinci::CmdLine constructor.

=item * B<exclude_package_functions_match> => I<re>

Exclude package functions matching this pattern.

=item * B<extra_urls_for_version> => I<array[str]>

Will be passed to Perinci::CmdLine constructor.

=item * B<include_package_functions_match> => I<re>

Only include package functions matching this pattern.

=item * B<interpreter_path> => I<str>

What to put on shebang line.

=item * B<load_module> => I<array[perl::modname]>

Load extra modules.

=item * B<log> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<output_file> => I<filename> (default: "-")

Path to output file.

=item * B<overwrite> => I<bool> (default: 0)

Whether to overwrite output if previously exists.

=item * B<pack_deps> => I<bool>

Whether to pack dependencies in Perinci::CmdLine::Inline script.

Will be passed to L<Perinci::CmdLine>'s C<gen_inline_pericmd_script>'s
C<pack_deps> option.

=item * B<pass_cmdline_object> => I<bool>

Will be passed to Perinci::CmdLine constructor.

Currently irrelevant when generating with Perinci::CmdLine::Inline.

=item * B<per_arg_json> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<per_arg_yaml> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<pod> => I<bool> (default: 1)

Whether to generate POD or not.

Currently only Perinci::CmdLine::Inline generates POD.

=item * B<prefer_lite> => I<bool> (default: 1)

Prefer Perinci::CmdLine::Lite backend.

=item * B<read_config> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<read_env> => I<bool>

Will be passed to Perinci::CmdLine constructor.

=item * B<script_name> => I<str>

(No description)

=item * B<script_summary> => I<str>

(No description)

=item * B<script_version> => I<str>

Use this for version number instead.

=item * B<skip_format> => I<bool>

Assume that function returns raw text which needs no formatting.

=item * B<ssl_verify_hostname> => I<bool> (default: 1)

If set to 0, will add: $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;' to code.

=item * B<subcommands> => I<hash>

Hash of subcommand entries, where each entry is "url[:summary]".

An optional summary can follow the URL, e.g.:

 URL[:SUMMARY]

Example (on CLI):

 --subcommand add=/My/App/add_item --subcommand bin='/My/App/bin_item:Delete an item'

=item * B<subcommands_from_package_functions> => I<bool>

Form subcommands from functions under package's URL.

This is an alternative to the C<subcommands> option. Instead of specifying each
subcommand's name and URL, you can also specify that subcommand names are from
functions under the package URL in C<url>. So for example if C<url> is C</My/App/>,
hen all functions under C</My/App> are listed first. If the functions are:

 foo
 bar
 baz_qux

then the subcommands become:

 foo => /My/App/foo
 bar => /My/App/bar
 "baz-qux" => /My/App/baz_qux

=item * B<url>* => I<riap::url>

URL to function (or package, if you have subcommands).

=item * B<use_cleanser> => I<bool>

Whether to use data cleansing before outputting to JSON.

=item * B<use_utf8> => I<bool>

Whether to set utf8 flag on output, will be passed to Perinci::CmdLine constructor.

=item * B<validate_args> => I<bool>

Will be passed to Perinci::CmdLine constructor.


=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/Perinci-CmdLine-Gen>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-Perinci-CmdLine-Gen>.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 CONTRIBUTING


To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.

Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:

 % prove -l

If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015 by perlancar <perlancar@cpan.org>.

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

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-CmdLine-Gen>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.