Skip to content
This repository
Browse code

Fixed a massive problem about colliding plugins. Made a duplicate check.

  • Loading branch information...
commit a73ce18c2dcf251d39d4c5415280307d58cb722f 1 parent effc745
Torsten Raudssus Getty authored
65 lib/DDG/Block.pm
@@ -4,6 +4,7 @@ package DDG::Block;
4 4 use Moo::Role;
5 5 use Carp;
6 6 use Class::Load ':all';
  7 +use POSIX qw(strftime);
7 8
8 9 requires qw(
9 10 request
@@ -79,10 +80,12 @@ has plugins => (
79 80
80 81 sub _build_plugins { die (ref shift)." requires plugins" }
81 82
82   -=attr return_one
  83 +=attr allow_missing_plugins
83 84
84   -This attribute defines if the block should stop if there is a hit which gives
85   -a result. By default this is on.
  85 +This attribute defines if the block should die on missing plugins, or if he
  86 +should just go on. If given a CODEREF, then this CODEREF will get executed
  87 +with the block as first parameter, and the missing class name as second. By
  88 +default this is disabled.
86 89
87 90 =cut
88 91
@@ -92,12 +95,10 @@ has allow_missing_plugins => (
92 95 default => sub { 0 },
93 96 );
94 97
95   -=attr allow_missing_plugins
  98 +=attr return_one
96 99
97   -This attribute defines if the block should die on missing plugins, or if he
98   -should just go on. If given a CODEREF, then this CODEREF will get executed
99   -with the block as first parameter, and the missing class name as second. By
100   -default this is disabled.
  100 +This attribute defines if the block should stop if there is a hit which gives
  101 +a result. By default this is on.
101 102
102 103 =cut
103 104
@@ -107,6 +108,35 @@ has return_one => (
107 108 default => sub { 1 },
108 109 );
109 110
  111 +=attr allow_duplicate
  112 +
  113 +This attribute defines if the block is only allowing one instance of every
  114 +plugin be called once, even if they hit cause of several cases. By default
  115 +this is off, and should stay off.
  116 +
  117 +=cut
  118 +
  119 +has allow_duplicate => (
  120 + #isa => 'Bool',
  121 + is => 'ro',
  122 + default => sub { 0 },
  123 +);
  124 +
  125 +=attr debug_trace
  126 +
  127 +=cut
  128 +
  129 +has debug_trace => (
  130 + #isa => 'Bool',
  131 + is => 'ro',
  132 + default => sub { defined $ENV{DDG_BLOCK_TRACE} && $ENV{DDG_BLOCK_TRACE} ? 1 : 0 },
  133 +);
  134 +
  135 +sub trace {
  136 + return unless shift->debug_trace;
  137 + print STDERR ('[DDG_BLOCK_TRACE] ',join(" ",@_),"\n");
  138 +}
  139 +
110 140 =attr before_build
111 141
112 142 A coderef that is executed before the build of the plugins. It gets the block
@@ -251,7 +281,7 @@ sub parse_trigger { shift; shift; }
251 281
252 282 =method empty_trigger
253 283
254   -Ggets called, if the plugin doesnt deliver any trigger, here you can wrap this to your own specific
  284 +Gets called, if the plugin doesnt deliver any trigger, here you can wrap this to your own specific
255 285 definition. Its so far only used in the L<DDG::Block::Words>, to disallow empty triggers totally. By default
256 286 it returns B<undef>.
257 287
@@ -259,4 +289,21 @@ it returns B<undef>.
259 289
260 290 sub empty_trigger { return undef }
261 291
  292 +=method handle_request_matches
  293 +
  294 +This function is used for calling I<handle_request_matches> on the plugin,
  295 +which is implemented there via L<DDG::Meta::RequestHandler>.
  296 +
  297 +=cut
  298 +
  299 +sub handle_request_matches {
  300 + my ( $self, $plugin, $request, @args ) = @_;
  301 + my $plugin_class = ref $plugin;
  302 + unless ($self->allow_duplicate) {
  303 + return () if grep { $_ eq $plugin_class } @{$request->seen_plugins};
  304 + }
  305 + push @{$request->seen_plugins}, $plugin_class;
  306 + return $plugin->handle_request_matches($request, @args);
  307 +}
  308 +
262 309 1;
82 lib/DDG/Block/Words.pm
@@ -7,7 +7,7 @@ with qw( DDG::Block );
7 7
8 8 sub BUILD {
9 9 my ( $self ) = @_;
10   - for (reverse @{$self->plugin_objs}) {
  10 + for (@{$self->plugin_objs}) {
11 11 my $triggers = $_->[0];
12 12 my $plugin = $_->[1];
13 13 for (@{$triggers}) {
@@ -63,10 +63,12 @@ sub _set_word_plugin {
63 63 my $word_count = scalar @split_word;
64 64 $self->_words_plugins->{$type}->{$key} = {} unless defined $self->_words_plugins->{$type}->{$key};
65 65 if ($word_count eq 1) {
66   - $self->_words_plugins->{$type}->{$key}->{1} = $plugin;
  66 + $self->_words_plugins->{$type}->{$key}->{1} = [] unless defined $self->_words_plugins->{$type}->{$key}->{$word_count};
  67 + push @{$self->_words_plugins->{$type}->{$key}->{1}}, $plugin;
67 68 } else {
68 69 $self->_words_plugins->{$type}->{$key}->{$word_count} = {} unless defined $self->_words_plugins->{$type}->{$key}->{$word_count};
69   - $self->_words_plugins->{$type}->{$key}->{$word_count}->{$word} = $plugin;
  70 + $self->_words_plugins->{$type}->{$key}->{$word_count}->{$word} = [] unless defined $self->_words_plugins->{$type}->{$key}->{$word_count}->{$word};
  71 + push @{$self->_words_plugins->{$type}->{$key}->{$word_count}->{$word}}, $plugin;
70 72 }
71 73 }
72 74
@@ -98,13 +100,46 @@ sub _build__words_plugins {{
98 100 sub request {
99 101 my ( $self, $request ) = @_;
100 102 my @results;
  103 + $self->trace( "Query raw: ", "'".$request->query_raw."'" );
  104 + #
  105 + # Mapping positions of keywords in the request
  106 + # to a flat array which we can access stepwise.
  107 + #
  108 + # So @poses is an array of the positions inside
  109 + # the triggers hash.
  110 + #
  111 + ################################################
101 112 my %triggers = %{$request->triggers};
102 113 my $max = scalar keys %triggers;
103 114 my @poses = sort { $a <=> $b } keys %triggers;
  115 + $self->trace( "Trigger word positions: ", @poses );
104 116 for my $cnt (0..$max-1) {
  117 + #
  118 + # We do split up this into a flat array to have it
  119 + # easier to determine if the query is starting, ending
  120 + # or still in the beginning, this is very essential
  121 + # for the following steps.
  122 + #
105 123 my $start = $cnt == 0 ? 1 : 0;
106 124 my $end = $cnt == $max-1 ? 1 : 0;
107 125 for my $word (@{$request->triggers->{$poses[$cnt]}}) {
  126 + $self->trace( "Testing word:", "'".$word."'" );
  127 + #
  128 + # Checking if any of the plugins have this specific word
  129 + # in the start end or any trigger. start and end of course
  130 + # only if its first or last word in the query.
  131 + #
  132 + # It gives back a touple of 2 elements, a bool which defines
  133 + # if there COULD BE more words after it (so this fits for
  134 + # any and start triggers), the second is the part of the
  135 + # prepared trigger set of the blocks which is responsible
  136 + # for this word.
  137 + #
  138 + # The keys inside the hitstruct define the words count it
  139 + # additional carries. This allows to kick out the ones which
  140 + # are not fitting anymore into the length of the query (by
  141 + # wordcount)
  142 + #
108 143 if (my ( $begin, $hitstruct ) =
109 144 $start && defined $self->_words_plugins->{start}->{$word}
110 145 ? ( 1 => $self->_words_plugins->{start}->{$word} )
@@ -113,12 +148,31 @@ sub request {
113 148 : defined $self->_words_plugins->{any}->{$word}
114 149 ? ( 1 => $self->_words_plugins->{any}->{$word} )
115 150 : undef) {
  151 + ######################################################
  152 + $self->trace("Got a hit with","'".$word."'","!", $begin ? "And it's just the beginning..." : "");
  153 + #
  154 + # $cnt is the specific position inside our flat array of
  155 + # positions inside the query.
  156 + #
116 157 my $pos = $poses[$cnt];
  158 + #
  159 + # This for loop is only executed if for the specific word
  160 + # that is triggered is having "more then one word" triggers
  161 + # that are attached to it. In this case it iterates through
  162 + # all those different combination and tries to match it
  163 + # with the request of the query.
  164 + #
117 165 for my $word_count (sort { $b <=> $a } grep { $_ > 1 } keys %{$hitstruct}) {
  166 + ############################################################
  167 + $self->trace( "Checking additional multiword triggers with length of", $word_count);
118 168 my @sofar_words = @{$triggers{$pos}};
119   - for (@sofar_words) {
120   - push @results, $hitstruct->{$word_count}->{$_}->handle_request_matches($request,$pos) if defined $hitstruct->{$word_count}->{$_};
121   - return @results if $self->return_one && @results;
  169 + for my $sofar_word (@sofar_words) {
  170 + if (defined $hitstruct->{$word_count}->{$sofar_word}) {
  171 + for (@{$hitstruct->{$word_count}->{$sofar_word}}) {
  172 + push @results, $self->handle_request_matches($_,$request,$pos);
  173 + return @results if $self->return_one && @results;
  174 + }
  175 + }
122 176 }
123 177 my @next_poses_key = grep { $_ >= 0 } $begin ? ($cnt+1)..($cnt+$word_count-1) : ($cnt-$word_count-1)..($cnt-1);
124 178 my @next_poses = grep { defined $_ && defined $triggers{$_} } @poses[@next_poses_key];
@@ -131,16 +185,24 @@ sub request {
131 185 my $new_next_word = $begin
132 186 ? join(" ",$current_sofar_word,$next_trigger)
133 187 : join(" ",$next_trigger,$current_sofar_word);
134   - push @results, $hitstruct->{$word_count}->{$new_next_word}->handle_request_matches($request,( $pos < $next_pos ) ? ( $pos,$next_pos ) : ( $next_pos,$pos ) ) if defined $hitstruct->{$word_count}->{$new_next_word};
135   - return @results if $self->return_one && @results;
  188 + if (defined $hitstruct->{$word_count}->{$new_next_word}) {
  189 + for (@{$hitstruct->{$word_count}->{$new_next_word}}) {
  190 + push @results, $self->handle_request_matches($_,$request,( $pos < $next_pos ) ? ( $pos,$next_pos ) : ( $next_pos,$pos ));
  191 + return @results if $self->return_one && @results;
  192 + }
  193 + }
136 194 push @new_next_words, $new_next_word;
137 195 }
138 196 }
139 197 push @sofar_words, @new_next_words;
140 198 }
141 199 }
142   - push @results, $hitstruct->{1}->handle_request_matches($request,$poses[$cnt]) if defined $hitstruct->{1};
143   - return @results if $self->return_one && @results;
  200 + if (defined $hitstruct->{1}) {
  201 + push @results, $self->handle_request_matches($_,$request,$poses[$cnt]) for @{$hitstruct->{1}};
  202 + return @results if $self->return_one && @results;
  203 + }
  204 + } else {
  205 + $self->trace("No hit with","'".$word."'");
144 206 }
145 207 }
146 208 }
7 lib/DDG/Manual/Translation.pod
Source Rendered
... ... @@ -1,8 +1,7 @@
1   -=encoding utf-8
2   -
3   -=head1 NAME
  1 +# PODNAME: DDG::Manual::Translation
  2 +# ABSTRACT: Overview of the translation system of DuckDuckGo
4 3
5   -DDG::Manual::Translation - Overview of the translation system of DuckDuckGo
  4 +=encoding utf-8
6 5
7 6 =head1 THE MISSION
8 7
15 lib/DDG/Request.pm
@@ -350,6 +350,21 @@ has wordcount => (
350 350 );
351 351 sub _build_wordcount { scalar @{shift->words} }
352 352
  353 +=attr seen_plugins
  354 +
  355 +This array contains all the plugins which already worked with this request.
  356 +This means all the plugins which are triggered. If they gave back a result or
  357 +not, doesn't matter here. This list is used by L<DDG::Block/allow_duplicate>.
  358 +
  359 +=cut
  360 +
  361 +has seen_plugins => (
  362 + is => 'rw',
  363 + lazy => 1,
  364 + builder => '_build_seen_plugins',
  365 +);
  366 +sub _build_seen_plugins {[]}
  367 +
353 368 #
354 369 # LANGUAGE / LOCATION / IP
355 370 #
13 t/35-block.t
@@ -57,6 +57,8 @@ BEGIN {
57 57 DDGTest::Goodie::WoBlockTwo
58 58 DDGTest::Goodie::WoBlockThree
59 59 DDGTest::Goodie::WoBlockArr
  60 + DDGTest::Goodie::CollideOne
  61 + DDGTest::Goodie::CollideTwo
60 62 )];
61 63 my $before_wp = 0;
62 64 my $after_wp = 0;
@@ -104,7 +106,7 @@ BEGIN {
104 106
105 107 my @queries = (
106 108 'aROUNd two' => {
107   - wo => [zci('two','woblockone')],
  109 + wo => [zci('two','woblockone'),zci('aROUNd','woblocktwo')],
108 110 re => [],
109 111 },
110 112 'wikipedia blub' => {
@@ -171,11 +173,14 @@ BEGIN {
171 173 wo => [zci('a|or|b|or|c','woblockarr')],
172 174 re => [],
173 175 },
174   -
  176 + 'collide' => {
  177 + wo => [zci('collide','collideone'),zci('collide','collidetwo')],
  178 + re => [],
  179 + },
175 180 # Triggers multiple plugins.
176 181 'or two' => {
177   - wo => [zci('or|two','woblockarr'), zci('or','woblocktwo')],
178   - re => [],
  182 + wo => [zci('or|two','woblockarr'), zci('or','woblocktwo')],
  183 + re => [],
179 184 },
180 185
181 186 );
9 t/lib/DDGTest/Goodie/CollideOne.pm
... ... @@ -0,0 +1,9 @@
  1 +package DDGTest::Goodie::CollideOne;
  2 +
  3 +use DDG::Goodie;
  4 +
  5 +triggers any => 'collide';
  6 +
  7 +handle query_parts => sub { join('|',@_) };
  8 +
  9 +1;
9 t/lib/DDGTest/Goodie/CollideTwo.pm
... ... @@ -0,0 +1,9 @@
  1 +package DDGTest::Goodie::CollideTwo;
  2 +
  3 +use DDG::Goodie;
  4 +
  5 +triggers any => 'collide';
  6 +
  7 +handle query_parts => sub { join('|',@_) };
  8 +
  9 +1;

0 comments on commit a73ce18

Please sign in to comment.
Something went wrong with that request. Please try again.