diff --git a/lib/App/DuckPAN.pm b/lib/App/DuckPAN.pm index 5e102f1..9a4a08e 100644 --- a/lib/App/DuckPAN.pm +++ b/lib/App/DuckPAN.pm @@ -22,6 +22,7 @@ use Encode; use Path::Class; use Perl::Version; use File::Path; +use IO::All; our $VERSION ||= '9.999'; @@ -53,7 +54,6 @@ sub _ua_string { my ($self) = @_; my $class = ref $self || $self; my $version = $class->VERSION; - return "$class/$version"; } @@ -80,6 +80,71 @@ has term => ( sub _build_term { Term::ReadLine->new('duckpan') } +has ia_types => ( + is => 'ro', + lazy => 1, + builder => '_build_ia_types', +); + +sub _build_ia_types { + my @ddg_bits = ('lib', 'DDG'); + my $t_dir = io('t'); + return [{ + name => 'Goodie', + dir => dir(@ddg_bits, 'Goodie'), + supported => 1, + templates => { + code => { + in => io('template/lib/DDG/Goodie/Example.pm'), + out => io(join '/', @ddg_bits, 'Goodie') + }, + test => { + in => io('template/t/Example.t'), + out => $t_dir + }, + }, + }, + { + name => 'Spice', + dir => dir(@ddg_bits, 'Spice'), + supported => 1, + templates => { + code => { + in => io('template/lib/DDG/Spice/Example.pm'), + out => io(join '/', @ddg_bits, 'Spice') + }, + test => { + in => io('template/t/Example.t'), + out => $t_dir + }, + handlebars => { + in => io('template/share/spice/example/example.handlebars'), + out => io('share/spice') + }, + js => { + in => io('template/share/spice/example/example.js'), + out => io('share/spice') + }, + }, + }, + { + name => 'Fathead', + dir => dir(@ddg_bits, 'Fathead'), + supported => 0 + }, + { + name => 'Longtail', + dir => dir(@ddg_bits, 'Longtail'), + supported => 0 + }, + { + name => 'DuckPAN', + dir => dir('lib', 'App', 'DuckPAN'), + supported => -1 # Supported, with warning. + }, + ]; +} + sub get_reply { my ( $self, $prompt, %params ) = @_; my $return = $self->term->get_reply( prompt => $prompt, %params ); @@ -144,7 +209,7 @@ has perl => ( lazy => 1, ); -sub _build_perl { +sub _build_perl { load_class('App::DuckPAN::Perl'); App::DuckPAN::Perl->new( app => shift ); } @@ -155,7 +220,7 @@ has ddg => ( lazy => 1, ); -sub _build_ddg { +sub _build_ddg { load_class('App::DuckPAN::DDG'); App::DuckPAN::DDG->new( app => shift ); } @@ -195,6 +260,7 @@ sub execute { sub print_text { shift; + return unless @_; for (@_) { print "\n"; my @words = split(/\s+/,$_); @@ -213,6 +279,14 @@ sub print_text { print "\n"; } +sub exit_with_msg { + my ($self, $exit_code, @msg) = @_; + + $self->print_text('[ERROR] ' . shift @msg) if (@msg); + $self->print_text(@msg); + exit $exit_code; +} + sub camel_to_underscore { my ($self, $name) = @_; # Replace first capital by lowercase @@ -382,6 +456,22 @@ sub checking_dukgo_user { $response->code == 302 ? 1 : 0; # workaround, need something in dukgo } +sub get_ia_type { + my ($self) = @_; + + my $ia_type = first { -d $_->{dir} } @{$self->ia_types}; + + $self->exit_with_msg(-1, 'Must be run from the root of a checked-out Instant Answer repository.') unless ($ia_type); + + if ($ia_type->{supported} == 0) { + $self->exit_with_msg(-1, "Sorry, DuckPAN does not support " . $ia_type->{name} . " yet!"); + } elsif ($ia_type->{supported} == -1) { + $self->print_text("[WARN] You are in the " . $ia_type->{name} . " directory. I assume you know what you're doing?"); + } + + return $ia_type; +} + sub BUILD { my ( $self ) = @_; if ($^O eq 'MSWin32') { @@ -436,11 +526,11 @@ B: We invite you to join us at B<#duckduckgo> on B for any queries and lively discussion. B: - + L B: - + L =cut diff --git a/lib/App/DuckPAN/Cmd/New.pm b/lib/App/DuckPAN/Cmd/New.pm index 6286f5e..79cd442 100644 --- a/lib/App/DuckPAN/Cmd/New.pm +++ b/lib/App/DuckPAN/Cmd/New.pm @@ -16,110 +16,65 @@ use Text::Xslate qw(mark_raw); use IO::All; sub run { - my ( $self, @args ) = @_; + my ($self, @args) = @_; # Check which IA repo we're in... - my $type = ""; - if (-d "./lib/DDG/Goodie") { - $type = "Goodie"; - } elsif (-d "./lib/DDG/Spice") { - $type = "Spice"; - } elsif (-d "./lib/DDG/Fathead") { - $type = "Fathead"; - $self->app->print_text("[ERROR] Sorry, DuckPAN does not support Fatheads yet!"); - exit -1; - } elsif (-d "./lib/DDG/Longtail") { - $type = "Longtail"; - $self->app->print_text("[ERROR] Sorry, DuckPAN does not support Longtails yet!"); - exit -1; - } else { - $self->app->print_text("[ERROR] No lib/DDG/Goodie, lib/DDG/Spice, lib/DDG/Fathead or lib/DDG/Longtail found"); - exit -1; - } + my $type = $self->app->get_ia_type(); # Instant Answer name as parameter my $entered_name = (@args) ? join(' ', @args) : $self->app->get_reply('Please enter a name for your Instant Answer'); - $entered_name =~ s/\//::/g; #change "/" to "::" for easier handling + $self->app->exit_with_msg(-1, "Must supply a name for your Instant Answer.") unless $entered_name; + $entered_name =~ s/\//::/g; #change "/" to "::" for easier handling my $name = $self->app->phrase_to_camel($entered_name); - my ($path, $lc_path) = ("", ""); - my $package_name = $name; - my $separated_name = $package_name; + my ($package_name, $separated_name, $path, $lc_path) = ($name, $name, "", ""); $separated_name =~ s/::/ /g; if ($entered_name =~ m/::/) { my @path_parts = split(/::/, $entered_name); - $name = pop @path_parts; - $path = join("/", @path_parts); - $lc_path = join("/", map { $self->app->camel_to_underscore($_) } @path_parts); + if (scalar @path_parts > 1) { + $name = pop @path_parts; + $path = join("/", @path_parts); + $lc_path = join("/", map { $self->app->camel_to_underscore($_) } @path_parts); + } else { + $self->app->exit_with_msg(-1, "Malformed input. Please provide a properly formatted package name for your Instant Answer."); + } } - my $lc_name = $self->app->camel_to_underscore($name); + my $lc_name = $self->app->camel_to_underscore($name); + my $filepath = ($path eq "") ? $name : "$path/$name"; + my $lc_filepath = ($lc_path eq "") ? $lc_name : "$lc_path/$lc_name"; + if (scalar $lc_path) { + $lc_path =~ s/\//_/g; #safe to modify, we already used this in $lc_filepath + $lc_name = $lc_path . "_" . $lc_name; + } - # %templates forms the spine data structure which is used - # as a guide to discovering content which is moved around - my %templates = ( - Goodie => { - dirs => [ - "./lib/DDG/Goodie/$path", - "./t/$path", - ], - files => { - "./template/lib/DDG/Goodie/Example.pm" => "./lib/DDG/Goodie/$path/$name.pm", - "./template/t/Example.t" => "./t/$path/$name.t", - }, - }, + $self->app->exit_with_msg(-1, "No templates exist for this IA Type: " . $type->{name}) if (!defined $type->{templates}); - Spice => { - share_dir => "./share/spice/$lc_path/$lc_name", - dirs => [ - "./lib/DDG/Spice/$path", - "./t/$path", - "./share/spice/$lc_path", - "./share/spice/$lc_path", - ], - files => { - "./template/lib/DDG/Spice/Example.pm" => "./lib/DDG/Spice/$path/$name.pm", - "./template/t/Example.t" => "./t/$path/$name.t", - "./template/share/spice/example/example.handlebars" => "./share/spice/$lc_path/$lc_name/$lc_name.handlebars", - "./template/share/spice/example/example.js" => "./share/spice/$lc_path/$lc_name/$lc_name.js" - } - }, - Fathead => { - # [TODO] Implement Fathead templates - }, - Longtail => { - # [TODO] Implement Fathead templates - } + my %template_info = %{$type->{templates}}; + my $tx = Text::Xslate->new(); + my %files = ( + test => ["$filepath.t"], + code => ["$filepath.pm"], + handlebars => [$lc_filepath, "$lc_name.handlebars"], + js => [$lc_filepath, "$lc_name.js"]); + my %vars = ( + ia_name => $name, + ia_package_name => $package_name, + ia_name_separated => $separated_name, + lia_name => $lc_name, + ia_path => $filepath ); - - while (my ($source, $dest) = each(%{$templates{$type}{'files'}})) { - my $io = io($dest); - if ($io->exists) { - my $filename = $io->filename; - my $filepath= $io->filepath; - $self->app->print_text("[ERROR] File already exists: \"$filename\" in $filepath"); - exit -1; - } - unless (-e $source) { - $self->app->print_text("[ERROR] Template does not exist: $source"); - exit -1; - } - - my $tx = Text::Xslate->new(); - $lc_path =~ s/\//_/g; - my %vars = ( - ia_name => $name, - ia_package_name => $package_name, - ia_name_separated => $separated_name, - lia_name => $lc_path."_".$lc_name, - ia_path => $path - ); - - my $content = $tx->render($source, \%vars); - $io->file->assert->append($content); #create file path and append to file + foreach my $template_type (sort keys %template_info) { + my ($source, $dest) = ($template_info{$template_type}{in}, $template_info{$template_type}{out}); + $self->app->exit_with_msg(-1, 'Template does not exist: ' . $source) unless ($source->exists); + # Update dest based on type: + $dest = $dest->file(join '/', @{$files{$template_type}}); + $self->app->exit_with_msg(-1, 'File already exists: "' . $dest->filename . '" in ' . $dest->filepath) if ($dest->exists); + my $content = $tx->render("$source", \%vars); + $dest->file->assert->append($content); #create file path and append to file $self->app->print_text("Created file: $dest"); } - $self->app->print_text("Successfully created $type: $package_name"); + $self->app->print_text("Successfully created " . $type->{name} . ": $package_name"); } 1; diff --git a/lib/App/DuckPAN/Perl.pm b/lib/App/DuckPAN/Perl.pm index 212eb72..e9c010c 100644 --- a/lib/App/DuckPAN/Perl.pm +++ b/lib/App/DuckPAN/Perl.pm @@ -59,8 +59,8 @@ sub get_local_version { } sub cpanminus_install_error { - shift->app->print_text( - "[ERROR] Failure on installation of modules!", + shift->app->exit_with_msg(1, + "Failure on installation of modules!", "There are several possible explanations and fixes for this error:", "1. The download from CPAN was unsuccessful - Please restart this installer.", "2. Some other error occured - Please read the `build.log` mentioned in the errors and see if you can fix the problem yourself.", @@ -68,7 +68,6 @@ sub cpanminus_install_error { "https://github.com/duckduckgo/p5-app-duckpan/issues", "Make sure to attach the `build.log` file if it exists. Otherwise, copy/paste the output you see." ); - exit 1; } sub duckpan_install { @@ -85,7 +84,6 @@ sub duckpan_install { if (is_success(getstore($self->app->duckpan_packages,$tempfile))) { my $packages = Parse::CPAN::Packages::Fast->new($tempfile); my @to_install; - my $error = 0; for (@modules) { my $module = $packages->package($_); if ($module) { @@ -118,17 +116,14 @@ sub duckpan_install { } push @to_install, $duckpan_module_url if ($install_it && !(first { $_ eq $duckpan_module_url } @to_install)); } else { - $self->app->print_text("[ERROR] Can't find package ".$_." on ".$self->app->duckpan."\n"); - $error = 1; + $self->app->exit_with_msg(1, "Can't find package ".$_." on ".$self->app->duckpan); } } - return 1 if $error; return 0 unless @to_install; unshift @to_install,'-f' if ($force_install); # cpanm will do the actual forcing. return system("cpanm ".join(" ",@to_install)); } else { - $self->app->print_text("[ERROR] Can't reach duckpan at ".$self->app->duckpan."!\n"); - return 1; + $self->app->exit_with_msg(1, "Can't reach duckpan at ".$self->app->duckpan."!"); } }