Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
6.8 KiB

  1. package PPM::Search;
  2. use strict;
  3. use Data::Dumper;
  4. $PPM::Search::VERSION = '3.00';
  5. # Convert a glob into a regex.
  6. sub glob_to_regex {
  7. my ($glob, $casei) = @_;
  8. my $i = $casei ? '(?i)': '';
  9. # If the user specified any globs, remove the implicit globs surrounding
  10. # their query:
  11. my $globs = ($glob =~ /[?*]/);
  12. my $l = $globs ? '^' : '';
  13. my $r = $globs ? '$' : '';
  14. $glob =~ s/\./\\./g;
  15. $glob =~ s/\?/./g;
  16. $glob =~ s/\*/.*?/g;
  17. return qr/$l$i$glob$r/;
  18. }
  19. sub new {
  20. my ($pkg, $query, $casei) = @_;
  21. $pkg = ref($pkg) || $pkg;
  22. my $self = bless {
  23. 'query' => $query,
  24. 'casei' => $casei,
  25. }, $pkg;
  26. my ($terms, $left) = $self->_query($self->{'query'});
  27. $self->{'terms'} = $terms;
  28. $self;
  29. }
  30. sub match {
  31. die "Must override match() method in subclass!";
  32. }
  33. sub search {
  34. my ($self, @pkgs) = @_;
  35. $self->do_search($self->{'terms'}, \@pkgs);
  36. }
  37. sub do_search {
  38. my ($self, $terms, $matches) = @_;
  39. my $op = shift @$terms;
  40. return $self->do_and($terms, $matches) if $op eq 'and';
  41. return $self->do_or ($terms, $matches) if $op eq 'or';
  42. warn "Invalid search.\n";
  43. return ();
  44. }
  45. sub do_and {
  46. my $self = shift;
  47. my ($terms, $matches) = @_;
  48. my @matches = @$matches;
  49. for my $term (@$terms) {
  50. if (ref $term eq 'HASH') {
  51. @matches =
  52. grep { my $o = $self->match($_, $term->{field}, $term->{value});
  53. $term->{not} ? not $o : $o
  54. } @matches;
  55. }
  56. elsif (ref $term eq 'ARRAY') {
  57. @matches = $self->do_search($term, \@matches);
  58. }
  59. }
  60. return @matches;
  61. }
  62. sub do_or {
  63. my $self = shift;
  64. my ($terms, $matches) = @_;
  65. my @matches;
  66. my %matches;
  67. for my $term (@$terms) {
  68. my @new;
  69. if (ref $term eq 'HASH') {
  70. @new = (grep {my $o = $self->match($_, $term->{field}, $term->{value});
  71. $term->{not} ? not $o : $o }
  72. grep { not $matches{$_->name} }
  73. @$matches);
  74. }
  75. elsif (ref $term eq 'ARRAY') {
  76. @new = $self->do_search($term, $matches);
  77. }
  78. for my $n (@new) {
  79. $matches{$n->name}++ and next;
  80. push @matches, $n;
  81. }
  82. }
  83. return @matches;
  84. }
  85. # Parse the query:
  86. sub _query {
  87. my $self = shift;
  88. my $query = shift;
  89. my ($terms, $left) = $self->_terms($query);
  90. return ($terms, $left) if ref $terms eq 'ARRAY';
  91. ($terms, $left) = $self->_termopterms($query);
  92. return ($terms, $left) if ref $terms eq 'ARRAY';
  93. return (undef, $query);
  94. }
  95. sub _termopterms {
  96. my $self = shift;
  97. my $query = shift;
  98. my @terms = ('or', ['and']);
  99. my ($yes1, $yes2, $left) = (undef, undef, $query);
  100. while(1) {
  101. ($yes1, $left) = $self->_term($left);
  102. warn "Query syntax error: '$left'\n" and return (undef, $left)
  103. unless defined $yes1;
  104. ($yes2, $left) = $self->_op($left);
  105. push @{$terms[$#terms]}, $yes1;
  106. last unless defined $yes2;
  107. push @terms, ['and'] if $yes2 =~ /or/i;
  108. }
  109. return \@terms, $left;
  110. }
  111. sub _terms {
  112. my $self = shift;
  113. my $query = shift;
  114. my @terms = ('and');
  115. my ($yes, $left) = (undef, $query);
  116. while (1) {
  117. ($yes, $left)=$self->_term($left);
  118. last unless defined $yes;
  119. push @terms, $yes;
  120. }
  121. return undef, $query unless $left eq '';
  122. return \@terms, $left;
  123. }
  124. sub _term {
  125. my $self = shift;
  126. my $query = shift;
  127. my ($yes, $left) = $self->_term_1($query);
  128. return ($yes, $left) if defined $yes;
  129. ($yes, $left) = $self->_term_2($query);
  130. return ($yes, $left) if defined $yes;
  131. ($yes, $left) = $self->_term_3($query);
  132. return ($yes, $left) if defined $yes;
  133. return (undef, $query);
  134. }
  135. sub _term_1 {
  136. my $self = shift;
  137. my $query = shift;
  138. my $term = { not => 0 };
  139. my ($yes, $left) = (undef, $query);
  140. ($yes, $left) = $self->_not($left);
  141. $term->{not} = 1 if defined $yes;
  142. ($yes, $left) = $self->_field($left);
  143. return (undef, $query) unless defined $yes;
  144. return (undef, $query) unless $left =~ /^=/;
  145. $term->{field} = $yes;
  146. ($yes, $left) = $self->_glob2regex($self->_glob(substr($left, 1)));
  147. return (undef, $query) unless defined $yes;
  148. $term->{value} = $yes;
  149. return ($term, $left);
  150. }
  151. sub _term_2 {
  152. my $self = shift;
  153. my $query = shift;
  154. my $term = { not => 0 };
  155. my ($yes, $left) = (undef, $query);
  156. ($yes, $left) = $self->_not($left);
  157. $term->{not} = 1 if defined $yes;
  158. ($yes, $left) = $self->_glob2regex($self->_glob($left));
  159. return (undef, $query) unless defined $yes;
  160. $term->{value} = $yes;
  161. $term->{field} = "NAME";
  162. return ($term, $left);
  163. }
  164. sub _term_3 {
  165. my $self = shift;
  166. my $query = shift;
  167. my ($yes, $left) = (undef, $query);
  168. return (undef, $query) unless $left =~ s/^\s*\(//;
  169. ($yes, $left) = $self->_query($left);
  170. return (undef, $query) unless defined $yes;
  171. return (undef, $query) unless $left =~ s/^\s*\)//;
  172. return ($yes, $left);
  173. }
  174. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  175. sub _op {
  176. my $self = shift;
  177. my $query = shift;
  178. return 'and', $query if $query =~ s/^\s*and\s+//i;
  179. return 'or', $query if $query =~ s/^\s*or\s+//i;
  180. return undef, $query;
  181. }
  182. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  183. sub _not {
  184. my $self = shift;
  185. my $query = shift;
  186. return 'not', $query if $query =~ s/^\s*not\s+//i;
  187. return undef, $query;
  188. }
  189. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  190. sub _field {
  191. my $self = shift;
  192. my $query = shift;
  193. return $1, $query
  194. if $query =~ s/^\s*([A-Za-z_][A-Za-z0-9_]*)//;
  195. return undef, $query;
  196. }
  197. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  198. sub _glob {
  199. my $self = shift;
  200. my $query = shift;
  201. my ($yes, $left);
  202. ($yes, $left) = $self->_glob_1($query);
  203. return ($yes, $left) if defined $yes;
  204. ($yes, $left) = $self->_glob_2($query);
  205. return ($yes, $left) if defined $yes;
  206. return undef, $query;
  207. }
  208. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  209. sub _glob_1 {
  210. my $self = shift;
  211. my $query = shift;
  212. return $1, substr($query, length($1))
  213. if $query =~ /^([][\-:\.^\$,\w*?\\]+)/;
  214. return undef, $query;
  215. }
  216. my $quoted_re = qr'(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\")|(?:\')(?:[^\\\']*(?:\\.[^\\\']*)*)(?:\')|(?:\`)(?:[^\\\`]*(?:\\.[^\\\`]*)*)(?:\`))';
  217. # Returns (OP, REMAINDER) or (undef, QUERY) on failure
  218. sub _glob_2 {
  219. my $self = shift;
  220. my $query = shift;
  221. if ($query =~ s/^($quoted_re)//) {
  222. my $quoted = $1;
  223. substr($quoted, 0, 1) = "";
  224. substr($quoted, -1) = "";
  225. return $quoted, $query;
  226. }
  227. return undef, $query;
  228. }
  229. sub _glob2regex {
  230. my $self = shift;
  231. my $glob = shift;
  232. return (undef, @_) unless defined $glob;
  233. return glob_to_regex($glob, $self->{casei}), @_;
  234. }
  235. 1;