package App::CPANModulesUtils;

use 5.010001;
use strict 'subs', 'vars';
use warnings;
use Log::ger;

our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2021-11-26'; # DATE
our $DIST = 'App-CPANModulesUtils'; # DIST
our $VERSION = '0.007'; # VERSION

our %SPEC;

$SPEC{gen_acme_cpanmodules_module_from_acme_cpanlists_list} = {
    v => 1.1,
    summary => 'Generate an Acme::CPANModules module file from an Acme::CPANLists module list',
    args => {
        input_module => {
            schema => 'perl::modname*',
            req => 1,
            pos => 0,
            description => <<'_',

"Acme::CPANLists::" will be prepended if module name does not begin with it.

_
        },
        input_list_index => {
            schema => 'nonnegint*',
            cmdline_aliases => {n=>{}},
            description => <<'_',

Required if the Acme::CPANLists module contains more than one module list.

_
        },
        output_module => {
            schema => 'perl::modname*',
            description => <<'_',

"Acme::CPANModules::" will be prepended if module name does not begin with it.

Will default to the Acme::CPANLists module name, with CPANLists replaced by
CPANModules.

_
        },
        output_file => {
            schema => 'filename*',
            description => <<'_',

will default to "lib/Acme/CPANModules/<YourModuleName>.pm

_
            #cmdline_aliases => {o=>{}},
        },
        overwrite => {
            schema => 'true*',
            cmdline_aliases => {O=>{}},
        },
    },
};
sub gen_acme_cpanmodules_module_from_acme_cpanlists_list {
    require Data::Dump;
    require File::Path;
    require File::Slurper;

    my %args = @_;

    my $input_module = $args{input_module};
    $input_module = "Acme::CPANLists::$input_module"
        unless $input_module =~ /\AAcme::CPANLists::/;

    my $output_module = $args{output_module} // do {
        (my $val = $input_module) =~ s/CPANLists/CPANModules/;
        $val;
    };
    log_trace "output_module=$output_module";
    $output_module = "Acme::CPANModules::$input_module"
        unless $output_module =~ /\AAcme::CPANModules::/;

    my $output_file = $args{output_file} // do {
        (my $val = "lib/$output_module.pm") =~ s!::!/!g;
        $val;
    };

    my $idx = $args{input_list_index};
    (my $input_module_pm = "$input_module.pm") =~ s!::!/!g;
    require $input_module_pm;
    my $module_lists = \@{"$input_module\::Module_Lists"};
    @$module_lists or return [412, "$input_module doesn't contain any module list"];
    if (@$module_lists > 1) {
        defined $idx or return [400, "Please specify input_list_index because $input_module contains more than one module list"];
        $idx < @$module_lists or return [400, "There is no module list #$idx in $input_module"];
    }
    $idx //= 0;

    if (-f $output_file) {
        $args{overwrite} or return [412, "Output file $output_file already exists, specify another file or --overwrite"];
    } else {
        if ($output_file =~ m!/.!) {
            (my $dir = $output_file) =~ s!(.+)/.+!$1!;
            File::Path::make_path($dir);
        }
    }

    (my $prog = $0) =~ s!.+/!!;

    my $output = join(
        "",

        "# This file was first automatically generated by $prog on ", (scalar localtime), " from module list in ", $input_module, " version ", (${"$input_module\::VERSION"} // "dev"), ".\n",
        "\n",

        "package $output_module;\n",
        "\n",

        "use strict;\n\n",

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

        "our \$LIST = ", Data::Dump::dump($module_lists->[$idx]), ";\n",
        "\n",

        "1;\n",
        "# ABSTRACT: ", ($module_lists->[$idx]{summary} // "(no summary)"), "\n",
        "\n",
    );

    log_info "Writing output to $output_file ...";
    File::Slurper::write_text($output_file, $output);
    [200];
}

$SPEC{gen_acme_cpanmodules_module_from_webpage} = {
    v => 1.1,
    summary => 'Generate an Acme::CPANModules module file a webpage',
    description => <<'_',

This routine will download the webpage, extract the links in it, extract the
CPAN modules mentioned in the links, and use the extracted CPAN modules as
entries for the Acme::CPANModules module.

_
    args => {
        url => {
            schema => 'str*', # XXX url
            req => 1,
            pos => 0,
        },
        output_module => {
            schema => 'perl::modname*',
            req => 1,
            pos => 1,
            description => <<'_',

"Acme::CPANModules::" will be prepended if module name does not begin with it.

_
        },
        output_file => {
            schema => 'filename*',
            pos => 2,
            description => <<'_',

will default to "lib/Acme/CPANModules/<YourModuleName>.pm

_
            #cmdline_aliases => {o=>{}},
        },
        overwrite => {
            schema => 'true*',
            cmdline_aliases => {O=>{}},
        },
        description => {
            summary => 'Add description to the list',
            schema => 'str*',
        },
    },
};
sub gen_acme_cpanmodules_module_from_webpage {
    require CPAN::Module::FromURL;
    require Data::Dump;
    require File::Path;
    require File::Slurper;
    require HTTP::Tiny;
    require HTML::LinkExtor;

    my %args = @_;
    my $url = $args{url};
    my $output_module = $args{output_module};
    $output_module = "Acme::CPANModules::$output_module"
        unless $output_module =~ /\AAcme::CPANModules::/;
    #log_trace "output_module=$output_module";
    my $output_file = $args{output_file} // do {
        (my $val = "lib/$output_module.pm") =~ s!::!/!g;
        $val;
    };

    if (-f $output_file) {
        $args{overwrite} or return [412, "Output file $output_file already exists, specify another file or --overwrite"];
    } else {
        if ($output_file =~ m!/.!) {
            (my $dir = $output_file) =~ s!(.+)/.+!$1!;
            File::Path::make_path($dir);
        }
    }

    my $list = {
        summary => $args{summary} // "List of modules mentioned in $url",
        entries => [],
    };

    $list->{description} = $args{description} if $args{description};

    my $res = HTTP::Tiny->new->get($url);
    return [500, "Couldn't retrieve URL $url: $res->{status} - $res->{reason}"] unless $res->{success};

    my $p = HTML::LinkExtor->new;
    $p->parse($res->{content});

    my %link_urls;
    for my $link ($p->links) {
        my $tag = shift @$link;
        next unless $tag eq 'a';
        my %attrs = @$link;
        #use DD; dd \%attrs;
        my $link_url = $attrs{href};
        next if $link_urls{$link_url}++;
        my $mod = CPAN::Module::FromURL::extract_cpan_module_from_url($link_url);
        next unless defined $mod;
        push @{ $list->{entries} }, {module=>$mod};
    }

    (my $prog = $0) =~ s!.+/!!;

    my $output = join(
        "",

        "# This file was first automatically generated by $prog on ", (scalar localtime), " from webpage at $url.\n",
        "\n",

        "package $output_module;\n",
        "\n",

        "use strict;\n\n",

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

        "our \$LIST = ", Data::Dump::dump($list), ";\n",
        "\n",

        "1;\n",
        "# ABSTRACT:\n",
        "\n",

        "=head1 DESCRIPTION\n\n",
        "This L<Acme::CPANModules> module was first automatically generated from list of modules mentioned at L<$url>.\n\n",

        "=cut\n\n",
    );

    log_info "Writing output to $output_file ...";
    File::Slurper::write_text($output_file, $output);
    [200];
}

$SPEC{acme_cpanmodules_for} = {
    v => 1.1,
    summary => 'List Acme::CPANModules distributions that mention specified modules',
    description => <<'_',

This utility consults <prog:lcpan> (local indexed CPAN mirror) to check if there
are <pm:Acme::CPANModules> distributions that mention specified modules. This is
done by checking the presence of a dependency with the relationship
`x_mentions`.

See also <pm:App::lcpan> which has a "x-mentions-for-mod" subcommand that does
the same thing.

_
    args => {
        modules => {
            schema => ['array*', of=>'perl::modname*'],
            req => 1,
            pos => 0,
            greedy => 1,
        },
    },
    links => [
        # clashes with SEE ALSO in this POD
        #{url=>'pm:App::lcpan', summary=>'lcpan also has a "x-mentions-for-mod" subcommand that does the same thing'},
    ],
};
sub acme_cpanmodules_for {
    require App::lcpan::Call;

    my %args = @_;

    my $res = App::lcpan::Call::call_lcpan_script(
        argv => ["rdeps", "--rel", "x_mentions", @{ $args{modules} }],
    );

    return $res unless $res->[0] == 200;

    return [200, "OK", [grep {/\AAcme-CPANModules/}
                            map {$_->{dist}} @{ $res->[2] }]];
}

1;
# ABSTRACT: Command-line utilities related to Acme::CPANModules

__END__

=pod

=encoding UTF-8

=head1 NAME

App::CPANModulesUtils - Command-line utilities related to Acme::CPANModules

=head1 VERSION

This document describes version 0.007 of App::CPANModulesUtils (from Perl distribution App-CPANModulesUtils), released on 2021-11-26.

=head1 SYNOPSIS

=head1 DESCRIPTION

This distribution includes the following command-line utilities related to
L<Acme::CPANModules>:

=over

=item * L<acme-cpanmodules-for>

=item * L<draw-feature-matrix-from-acme-cpanmodules-module>

=item * L<gen-acme-cpanmodules-module-from-acme-cpanlists-list>

=item * L<gen-acme-cpanmodules-module-from-webpage>

=back

=head1 FUNCTIONS


=head2 acme_cpanmodules_for

Usage:

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

List Acme::CPANModules distributions that mention specified modules.

This utility consults L<lcpan> (local indexed CPAN mirror) to check if there
are L<Acme::CPANModules> distributions that mention specified modules. This is
done by checking the presence of a dependency with the relationship
C<x_mentions>.

See also L<App::lcpan> which has a "x-mentions-for-mod" subcommand that does
the same thing.

This function is not exported.

Arguments ('*' denotes required arguments):

=over 4

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


=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_acme_cpanmodules_module_from_acme_cpanlists_list

Usage:

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

Generate an Acme::CPANModules module file from an Acme::CPANLists module list.

This function is not exported.

Arguments ('*' denotes required arguments):

=over 4

=item * B<input_list_index> => I<nonnegint>

Required if the Acme::CPANLists module contains more than one module list.

=item * B<input_module>* => I<perl::modname>

"Acme::CPANLists::" will be prepended if module name does not begin with it.

=item * B<output_file> => I<filename>

will default to "lib/Acme/CPANModules/<YourModuleName>.pm

=item * B<output_module> => I<perl::modname>

"Acme::CPANModules::" will be prepended if module name does not begin with it.

Will default to the Acme::CPANLists module name, with CPANLists replaced by
CPANModules.

=item * B<overwrite> => I<true>


=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_acme_cpanmodules_module_from_webpage

Usage:

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

Generate an Acme::CPANModules module file a webpage.

This routine will download the webpage, extract the links in it, extract the
CPAN modules mentioned in the links, and use the extracted CPAN modules as
entries for the Acme::CPANModules module.

This function is not exported.

Arguments ('*' denotes required arguments):

=over 4

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

Add description to the list.

=item * B<output_file> => I<filename>

will default to "lib/Acme/CPANModules/<YourModuleName>.pm

=item * B<output_module>* => I<perl::modname>

"Acme::CPANModules::" will be prepended if module name does not begin with it.

=item * B<overwrite> => I<true>

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


=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/App-CPANModulesUtils>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-CPANModulesUtils>.

=head1 SEE ALSO

L<Acme::CPANModules>

=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>, and sometimes one or two other
Dist::Zilla plugin and/or Pod::Weaver::Plugin. 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) 2021, 2020, 2019, 2018 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=App-CPANModulesUtils>

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
