#!perl

use strict;
use warnings;

use SPVM();
use SPVM::Builder::DependencyAnalyzer;
use SPVM::Builder::Util;
use FindBin;

SPVM::Builder::Util::getopt
  # spvm,spvmcc, spvmdeps shared
  'h|help'          => \my $help,
  'v|version'       => \my $show_version,
  'I|include-dir=s' => \my @include_dirs,
  
  # spvmdeps only
  'json' => \my $json_option_on,
  'cpanm' => \my $cpanm_option_on,
  'cpanfile' => \my $cpanfile_option_on,
  'with-version' => \my $with_version_option_on,
  'exclude=s' => \my @excluded_class_names,
  'resource-info' => \my $show_resource_info,
;

if ($help) {
  print SPVM::Builder::Util::extract_usage;
  exit 0;
}
elsif ($show_version) {
  my $version_string = "spvmdeps v$SPVM::VERSION";
  print "$version_string\n";
  exit 0;
}

my $script_name = shift;

my $source = "";

$0 = $script_name;

FindBin::again();

eval { $source = SPVM::Builder::Util::slurp_binary($script_name); };

my $include_dirs_by_lib_directive = SPVM::Builder::Util::parse_lib_directive($source, $FindBin::Bin);

unshift @INC, map { $_ =~ s/[\\\/]SPVM$//; $_; } @include_dirs, @$include_dirs_by_lib_directive;

my $dependency_analyzer = SPVM::Builder::DependencyAnalyzer->new(
  script_name => $script_name,
  with_version => $with_version_option_on,
  excluded_class_names => \@excluded_class_names,
);

unless (defined $script_name) {
  die "[spvmdeps command]spvmdeps command needs SCRIPT_NAME."
}

if ($show_resource_info) {
  
  my $resource_info = $dependency_analyzer->dump_resource_info;
  
  print $resource_info;
}
elsif ($cpanm_option_on) {
  
  my $cpanm_commands = $dependency_analyzer->to_cpanm_commands;
  
  my $cpanm_commands_lines = SPVM::Builder::DependencyAnalyzer->to_lines($cpanm_commands);
  
  print $cpanm_commands_lines;
}
elsif ($cpanfile_option_on) {
  
  my $cpanfile_commands = $dependency_analyzer->to_cpanfile_commands;
  
  my $cpanfile_commands_lines = SPVM::Builder::DependencyAnalyzer->to_lines($cpanfile_commands);
  
  print $cpanfile_commands_lines;
}
elsif ($json_option_on) {
  my $json = $dependency_analyzer->to_json;
  
  print $json;
}
else {
  my $class_names = $dependency_analyzer->to_class_names;
  
  my $class_names_lines = SPVM::Builder::DependencyAnalyzer->to_lines($class_names);
  
  print $class_names_lines;
}

=encoding utf8

=head1 Name

spvmdeps - Class Dependencies Analyzer

=head1 Description

C<spvmdeps> command is a class dependencies analyzer.

=head1 Usage

  Usage: spvmdeps [OPTIONS] SCRIPT_NAME
    
    spvmdeps myapp.spvm
    
    spvmdeps --with-version myapp.spvm
    
    spvmdeps --cpanm --with-version myapp.spvm
    
    spvmdeps --cpanfile --with-version myapp.spvm
    
  Options:
    -h, --help                      Shows this message
    -v, --version                   Shows the version
    -I, --include-dir DIRECTORY     Adds a include directory
    --json                          Prints JSON that contains all dependent class information.
    --cpanm                         Prints cpanm commands that contains dependent cpan modules
    --cpanfile                      Prints cpanfile that contains dependent cpan modules
    --with-version                  Output(default, --cpanm, --json) contains version information.
    --exclude CLASS_NAME            Excludes a class name. This option can be used repeatedly.
    --resource-info                 Shows config files of dependent resources

=head1 Details

  spvmdeps [OPTIONS] SCRIPT_NAME

The C<spvmdeps> command prints class dependency information.

Output Examples:

  Sys
  Regex
  Foo
  Bar
  
  # --with-version
  Sys 1.201
  Regex 0.980
  Foo (version_from Sys)
  Bar

I<OPTIONS> are L<options|/"Options">.

I<SCRIPT_NAME> is a script name that contains a L<bootstrap method|SPVM::Document::Language::Class/"Bootstrap Method"> in an L<anon class|SPVM::Document::Language::Class/"Anon Class">.

  class {
    static method main : void () {
      
    }
  }

See L<Class Search Directories|SPVM::Document::Language::Class/"Class Search Directories"> about default class search directories.

See L<SPVM::Document::EnvironmentVariables> about available environment variables.

=head1 Options

=head2 --help

Outputs how to use the C<spvmdeps> command to standard output.

=head2 -h

  -h

Same as L</"--help">.

=head2 --version

Outputs the version of the C<spvmdeps> command to standard output. This version is the same as the version of L<SPVM>.

=head2 -v

  -v

Same as L</"--version">.

=head2 --include-dir

  --include-dir DIRECTORY

Prepends I<DIRECTORY> to L<class search directories|SPVM::Document::Language::Class/"Class Search Directories">

This option can be specified multiple times.

  --include-dir dir1 --include-dir dir2

In this case, class search directories becomes the following.

  [dir1, dir2, default_dirs]

=head2 -I

  -I DIRECTORY

Same as L</"--include-dir">.

=head2 --json

  --json

Prints JSON that contains all dependent class information.

Output Examples:
  
  [
    {"class_name":"Address"},
    {"class_name":"Bool"},
    {"class_name":"Byte"},
    {"class_name":"CommandInfo"},
    {"class_name":"Double"},
    {"class_name":"Error"},
    {"class_name":"Error::Compile"},
    {"class_name":"Error::NotSupported"},
    {"class_name":"Error::System"},
    {"class_name":"Float"},
    {"class_name":"Int"},
    {"class_name":"Long"},
    {"class_name":"SPVM"},
    {"class_name":"Short"},
    {"class_name":"TestCase::NativeAPI2"},
    {"class_name":"TestCase::Precompile"}
  ]
  
  # --with-version
  [
    {"class_name":"Address","version_from":"SPVM"},
    {"class_name":"Bool","version_from":"SPVM"},
    {"class_name":"Byte","version_from":"SPVM"},
    {"class_name":"CommandInfo","version_from":"SPVM"},
    {"class_name":"Double","version_from":"SPVM"},
    {"class_name":"Error","version_from":"SPVM"},
    {"class_name":"Error::Compile","version_from":"SPVM"},
    {"class_name":"Error::NotSupported","version_from":"SPVM"},
    {"class_name":"Error::System","version_from":"SPVM"},
    {"class_name":"Float","version_from":"SPVM"},
    {"class_name":"Int","version_from":"SPVM"},
    {"class_name":"Long","version_from":"SPVM"},
    {"class_name":"SPVM","version":"0.990036"},
    {"class_name":"Short","version_from":"SPVM"},
    {"class_name":"TestCase::NativeAPI2","version":"1.002"},
    {"class_name":"TestCase::Precompile","version":"2.005"}
  ]

=head2 --cpanm

  --cpanm

Prints cpanm commands that contains dependent CPAN modules. Classes its version is specified are treated as dependent CPAN modules.

Output Examples:
  
  cpanm SPVM
  cpanm SPVM::Sys
  cpanm SPVM::Regex
  
  # --with-version
  cpanm SPVM@1.001
  cpanm SPVM::Sys@1.201
  cpanm SPVM::Regex@0.980

=head2 --cpanfile

  --cpanfile

Prints cpanfile that contains dependent CPAN modules. Classes its version is specified are treated as dependent CPAN modules.

Output Examples:

  requires 'SPVM';
  requires 'SPVM::Sys';
  requires 'SPVM::Regex';
  
  # --with-version
  requires 'SPVM', '== 1.001';
  requires 'SPVM::Sys', '== 1.201';
  requires 'SPVM::Regex', '== 0.980';

=head2 --with-version

  --with-version

Output(default, L</"--cpanm">, L</"--cpanfile">, L</"--json">) contains version information.

=head2 --exclude

  --exclude=<class_name>

Excludes a class name. This option can be used repeatedly.

Examples:

  --exclude TestCase::Foo

Wildcard C<*> can be used.

  --exclude TestCase::*

=head2 --resource-info

  --resource-info

Shows all config files loading resources.

=head1 lib Directive

If the source code specified by I<SCRIPT_NAME> contains lib directives, The directories specified by lib directive is prepeneded to L<class search directories|SPVM::Document::Language::Class/"Class Search Directories">.
  
  #lib "$FindBin::Bin/lib"
  
  class {
  
  }

This directories specified by lib directive is placed after the directories specified by L</"--include-dir"> option.

=head1 Copyright & License

Copyright 2023 Yuki Kimoto. All Rights Reserved.

MIT License.
