File Coverage

blib/lib/App/DuckPAN/DDG.pm
Criterion Covered Total %
statement 15 98 15.3
branch 0 36 0.0
condition 0 3 0.0
subroutine 5 9 55.5
pod 0 3 0.0
total 20 149 13.4


line stmt bran cond sub pod time code
1             package App::DuckPAN::DDG;
2             our $AUTHORITY = 'cpan:DDG';
3             # ABSTRACT: DDG related functionality of duckpan
4             $App::DuckPAN::DDG::VERSION = '1019';
5 2     2   1938 use Moo;
  2         5  
  2         9  
6             with 'App::DuckPAN::HasApp';
7              
8 2     2   603 use Module::Pluggable::Object;
  2         4  
  2         51  
9 2     2   9 use Class::Load ':all';
  2         4  
  2         252  
10 2     2   1050 use Data::Printer return_value => 'dump';
  2         21995  
  2         16  
11 2     2   2644 use List::Util qw (first);
  2         4  
  2         1271  
12              
13             # This function tells the user which modules / Instant Answers failed to load
14             sub show_failed_modules {
15 0     0 0   my ($self, $failed_to_load) = @_;
16              
17 0 0         if (%$failed_to_load) {
18 0           $self->app->emit_notice("These Instant Answers were not loaded:");
19 0           $self->app->emit_notice(p($failed_to_load, colored => $self->app->colors));
20             $self->app->emit_notice(
21             "To learn more about installing Perl dependencies, please read http://docs.duckduckhack.com/resources/other-dev-environments.html#dealing-with-installation-issues.",
22             "Note: You can ignore these errors if you're not working on these Instant Answers."
23 0 0   0     ) if first { /dependencies/ } values %$failed_to_load;
  0            
24             }
25             }
26              
27             # This function tells the user which modules / Instant Answers have no associated metadata
28             sub show_no_metadata_modules {
29 0     0 0   my ($self, $no_metadata) = @_;
30              
31 0 0         if (@$no_metadata) {
32 0           $self->app->emit_notice("No metadata was found for these Instant Answers:");
33 0           $self->app->emit_notice(p($no_metadata, colored => $self->app->colors));
34 0           $self->app->emit_notice(
35             "Using temporary Metadata instead.",
36             "If you have created an IA Page, please ensure it is in 'development' status or later.",
37             "To update the IA Page status, you will need to open a Pull Request.",
38             "More info: https://docs.duckduckhack.com/submitting/submitting-overview.html\n"
39             );
40             }
41             }
42              
43             sub get_blocks_from_current_dir {
44 0     0 0   my ($self, @args) = @_;
45              
46 0 0         $self->emit_and_exit(1, 'You need to have the DDG distribution installed', 'To get the installation command, please run: duckpan check')
47             unless ($self->app->get_local_ddg_version);
48              
49 0           my $type = $self->app->get_ia_type;
50             my $finder = Module::Pluggable::Object->new(
51 0           search_path => [$type->{dir}],
52             );
53 0 0         if (scalar @args == 0) {
54 0 0         $self->app->emit_and_exit(1, "No Fathead ID passed as argument.", "Please specify a Fathead ID e.g. 'duckpan server mdn_css'") if $type->{name} eq "Fathead";
55 0           my @plugins = $finder->plugins;
56 0           push @args, sort { $a cmp $b } @plugins;
  0            
57             @args = map {
58 0           $_ =~ s!/!::!g;
  0            
59 0           my @parts = split('::', $_);
60 0           shift @parts;
61 0           join('::', @parts);
62             } @args;
63             }
64             else {
65             my @fatheads = grep {
66 0           $type->{name} eq "Fathead"
  0            
67             } @args;
68 0           $self->app->fathead->selected(@fatheads);
69              
70             @args = grep {
71 0 0         $type->{name} eq "Spice" || $type->{name} eq "Goodie"
  0            
72             } @args;
73             }
74 0           require lib;
75 0           lib->import('lib');
76 0           $self->app->emit_info("Loading Instant Answers...");
77              
78             # This list contains all of the classes that loaded successfully.
79 0           my @successfully_loaded;
80              
81             # This hash contains all of the modules that failed.
82             # The key contains the module name and the value contains the dependency that wasn't met.
83             my %failed_to_load;
84              
85 0           my @no_metadata;
86              
87 0           my (%blocks_plugins, @UC_TRIGGERS);
88             # This loop goes through each Goodie / Spice, and it tries to load it.
89 0           foreach my $arg (@args) {
90              
91             # Let's try to load each Goodie / Spice module
92             # and see if they load successfully.
93 0           my $class;
94              
95             # Attempt to load Metadata based on passed ID
96 0 0         if (my $ia = $self->app->get_ia_by_name($arg)) {
    0          
    0          
97 0           $class = $ia->{perl_module};
98             }
99              
100             # Check if input resembles an ID
101             # i.e. lowercased alphanumeric string, possibly containing underscores
102             # Assumes no lowercased Perl package names exist (e.g. DDG::Goodie::lowercase)
103             elsif ($arg =~ /^[a-z0-9\_]+$/) {
104 0           my @msg = (
105             "Could not retrieve Instant Answers Metadata for ID: $arg.",
106             "If a local Perl Module exists, please provide the module name instead of the ID.",
107             "Or, if you have created an IA Page for any of these, please ensure its status is 'development' or 'testing'.",
108             "To update the IA Page status, you will need to open a Pull Request.",
109             "More info: https://docs.duckduckhack.com/submitting/submitting-overview.html\n"
110             );
111 0           $self->app->emit_notice(@msg);
112 0           $failed_to_load{$arg} = "No metadata found. See details above.";
113 0           next;
114             }
115              
116             # Check if Perl Package name provided
117             # We don't have Goodie packages with
118             elsif ($arg =~ /^(DDG::(?:Goodie|Spice|Fathead)::)?[A-Z]+[a-zA-Z0-9]*$/) {
119 0           push @no_metadata, $arg;
120 0 0         $class = $1 ? $arg : "DDG::$type->{name}::$arg";
121             }
122              
123             # Bad input
124             else {
125 0           $failed_to_load{$arg} = "Invalid Instant Answer ID or Perl package name";
126 0           next;
127             }
128              
129 0           my ($load_success, $load_error_message) = try_load_class($class);
130              
131             # If they load successfully, $load_success would be a 1.
132             # Otherwise, it would be a 0.
133 0 0         if ($load_success) {
134             # Since we only want the successful classes to trigger, we
135             # collect all of the ones that triggered successfully in a temporary list.
136 0           push @successfully_loaded, $class;
137              
138             # Display to the user when a class has been successfully loaded.
139 0           $self->app->emit_debug(" - $class (" . $class->triggers_block_type . ")");
140              
141 0 0         unless ($blocks_plugins{$class->triggers_block_type}) {
142 0           $blocks_plugins{$class->triggers_block_type} = [];
143             }
144              
145 0           my $triggers_block_type = $class->triggers_block_type;
146 0           push @{$blocks_plugins{$triggers_block_type}}, $class;
  0            
147              
148             # We could potentially do other IA-specific checks here
149             # Check for useless uppercase Words triggers so we can warn
150 0 0         if($triggers_block_type eq 'Words'){
151 0           my $trigger_types = $class->get_triggers;
152              
153 0           UC_TRIGGER: for my $triggers (values %$trigger_types){
154 0           for my $t (@$triggers){
155 0 0         if($t =~ /\p{Uppercase}/){
156 0           push @UC_TRIGGERS, $class;
157             #warn "is $t uppercase?\n";
158             # the first one found should be sufficient.
159 0           last UC_TRIGGER;
160             }
161             }
162             }
163             }
164             }
165             else {
166             # Get the module name that needs to be installed by the user.
167 0 0         if ($load_error_message =~ /Can't locate ([^\.]+).pm in \@INC/) {
168 0           $load_error_message = $1;
169 0           $load_error_message =~ s/\//::/g;
170              
171 0           $failed_to_load{$class} = "Please install $load_error_message and any other required dependencies to use this instant answer.";
172             }
173             else {
174             # We just set the value to whatever the error message was if it failed for some other reason.
175 0           $failed_to_load{$class} = $load_error_message;
176             }
177             }
178             }
179              
180             # Since @args can contain modules that we don't want to trigger (since they didn't load in the first place),
181             # and @successfully_loaded does, we just use what's in @successfully_loaded.
182 0           @args = @successfully_loaded;
183              
184             # Now let's tell the user which Instant Answers have no metadata
185 0           $self->show_no_metadata_modules(\@no_metadata);
186              
187             # Now let's tell the user why some of the modules failed.
188 0           $self->show_failed_modules(\%failed_to_load);
189              
190             # Always bail if we have no Instant Answers to work with.
191 0 0 0       unless (@successfully_loaded || $self->app->fathead->selected) {
192 0           $self->app->emit_and_exit(1, "No Instant Answers loaded.");
193             }
194              
195 0 0         if(@UC_TRIGGERS){
196 0           $self->app->emit_notice('Detected potential UPPERCASE triggers in the following Instant Answers. If yours is listed, check it out! Only lowercase will work.' . "\n"
197             . p(@UC_TRIGGERS, colored => $self->app->colors));
198             }
199              
200 0           my @blocks;
201 0           for (keys %blocks_plugins) {
202 0           my $block_class = 'DDG::Block::' . $_;
203 0           load_class($block_class);
204             push @blocks,
205             $block_class->new(
206 0           plugins => $blocks_plugins{$_},
207             return_one => 0
208             );
209             }
210 0           load_class('DDG::Request');
211 0           return \@blocks;
212             }
213              
214             1;
215              
216             __END__
217              
218             =pod
219              
220             =head1 NAME
221              
222             App::DuckPAN::DDG - DDG related functionality of duckpan
223              
224             =head1 VERSION
225              
226             version 1019
227              
228             =head1 AUTHOR
229              
230             DuckDuckGo <open@duckduckgo.com>, Zach Thompson <zach@duckduckgo.com>, Zaahir Moolla <moollaza@duckduckgo.com>, Torsten Raudssus <torsten@raudss.us> L<https://raudss.us/>
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             This software is Copyright (c) 2013 by DuckDuckGo, Inc. L<https://duckduckgo.com/>.
235              
236             This is free software, licensed under:
237              
238             The Apache License, Version 2.0, January 2004
239              
240             =cut