1# ex:ts=8 sw=4: 2# $OpenBSD: Search.pm,v 1.33 2023/06/13 09:07:17 espie Exp $ 3# 4# Copyright (c) 2007 Marc Espie <espie@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18use v5.36; 19 20package OpenBSD::Search; 21sub match_locations($self, $o) 22{ 23 require OpenBSD::PackageLocation; 24 25 my @l = map {$o->new_location($_)} $self->match($o); 26 return \@l; 27} 28 29sub keep_all($self) 30{ 31 $self->{keep_all} = 1; 32 return $self; 33} 34 35package OpenBSD::Search::PkgSpec; 36our @ISA=(qw(OpenBSD::Search)); 37 38sub filter($self, @list) 39{ 40 return $self->{spec}->match_ref(\@list); 41} 42 43sub filter_libs($self, @list) 44{ 45 return $self->{spec}->match_libs_ref(\@list); 46} 47 48sub match_locations($self, $o) 49{ 50 return $self->{spec}->match_locations($o->locations_list); 51} 52 53sub filter_locations($self, $l) 54{ 55 return $self->{spec}->match_locations($l); 56} 57 58sub new($class, $pattern, $with_partial = 0) 59{ 60 require OpenBSD::PkgSpec; 61 62 bless { spec => $class->spec_class->new($pattern, $with_partial)}, 63 $class; 64} 65 66sub add_pkgpath_hint($self, $pkgpath) 67{ 68 $self->{pkgpath} = $pkgpath; 69 return $self; 70} 71 72sub spec_class($) 73{ "OpenBSD::PkgSpec" } 74 75sub is_valid($self) 76{ 77 return $self->{spec}->is_valid; 78} 79 80package OpenBSD::Search::Exact; 81our @ISA=(qw(OpenBSD::Search::PkgSpec)); 82sub spec_class($) 83{ "OpenBSD::PkgSpec::Exact" } 84 85package OpenBSD::Search::Stem; 86our @ISA=(qw(OpenBSD::Search)); 87 88sub new($class, $stem) 89{ 90 # TODO this is where we currently handle "branch" matches 91 # but it's likely the stem/ % mechanisms should be seen as more 92 # generic cases of PackageSpecs eventually to better results 93 if ($stem =~ m/^(.*)\%(.*)/) { 94 return ($class->_new($1), 95 OpenBSD::Search::FilterLocation->match_partialpath($2)); 96 } else { 97 return $class->_new($stem); 98 } 99} 100 101sub _new($class, $stem) 102{ 103 if ($stem =~ m/^(.*)\-\-(.*)/) { 104 # XXX 105 return OpenBSD::Search::Exact->new("$1-*-$2"); 106 } 107 return bless {"$stem" => 1}, $class; 108} 109 110sub split($class, $pkgname) 111{ 112 require OpenBSD::PackageName; 113 114 return $class->new(OpenBSD::PackageName::splitstem($pkgname)); 115} 116 117sub add_stem($self, $extra) 118{ 119 $self->{$extra} = 1; 120 121} 122 123sub match($self, $o) 124{ 125 my @r = (); 126 for my $k (keys %$self) { 127 push(@r, $o->stemlist->find($k)); 128 } 129 return @r; 130} 131 132sub _keep($self, $stem) 133{ 134 return defined $self->{$stem}; 135} 136 137sub filter($self, @l) 138{ 139 my @result = (); 140 require OpenBSD::PackageName; 141 for my $pkg (@l) { 142 if ($self->_keep(OpenBSD::PackageName::splitstem($pkg))) { 143 push(@result, $pkg); 144 } 145 } 146 return @result; 147} 148 149package OpenBSD::Search::PartialStem; 150our @ISA=(qw(OpenBSD::Search::Stem)); 151 152sub match($self, $o) 153{ 154 my @r = (); 155 for my $k (keys %$self) { 156 push(@r, $o->stemlist->find_partial($k)); 157 } 158 return @r; 159} 160 161sub _keep($self, $stem) 162{ 163 for my $partial (keys %$self) { 164 if ($stem =~ /\Q$partial\E/) { 165 return 1; 166 } 167 } 168 return 0; 169} 170 171package OpenBSD::Search::FilterLocation; 172our @ISA=(qw(OpenBSD::Search)); 173sub new($class, $code) 174{ 175 return bless {code => $code}, $class; 176} 177 178sub filter_locations($self, $l) 179{ 180 return &{$self->{code}}($l); 181} 182 183sub more_recent_than($class, $name, $rfound) 184{ 185 require OpenBSD::PackageName; 186 187 my $f = OpenBSD::PackageName->from_string($name); 188 189 return $class->new( 190sub($l) { 191 my $r = []; 192 for my $e (@$l) { 193 if ($f->{version}->compare($e->pkgname->{version}) <= 0) { 194 push(@$r, $e); 195 } 196 if (ref $rfound) { 197 $$rfound = 1; 198 } 199 } 200 return $r; 201 }); 202} 203 204sub keep_most_recent($class) 205{ 206 return $class->new( 207sub($l) { 208 # no need to filter 209 return $l if @$l <= 1; 210 211 require OpenBSD::PackageName; 212 my $h = {}; 213 # we have to prove we have to keep it 214 while (my $e = pop @$l) { 215 my $stem = $e->pkgname->{stem}; 216 my $keep = 1; 217 # so let's compare with every element in $h with the same stem 218 for my $f (@{$h->{$e->pkgname->{stem}}}) { 219 # if this is not the same flavors, 220 # we don't filter 221 if ($f->pkgname->flavor_string ne $e->pkgname->flavor_string) { 222 next; 223 } 224 # unsigned packages will break here 225 my $u = $e->update_info; 226 if (!defined $u) { 227 $keep = 0; 228 last; 229 } 230 # okay, now we need to prove there's a common pkgpath 231 if (!$u->match_pkgpath($f->update_info)) { 232 next; 233 } 234 235 if ($f->pkgname->{version}->compare($e->pkgname->{version}) < 0) { 236 $f = $e; 237 } 238 $keep = 0; 239 last; 240 241 } 242 if ($keep) { 243 push(@{$h->{$e->pkgname->{stem}}}, $e); 244 } 245 } 246 my $largest = []; 247 push @$largest, map {@$_} values %$h; 248 return $largest; 249} 250 ); 251} 252 253sub match_partialpath($class, $subdir) 254{ 255 return $class->new( 256sub($l) { 257 if (@$l == 0) { 258 return $l; 259 } 260 my @l2 = (); 261 for my $loc (@$l) { 262 if (!$loc) { 263 next; 264 } 265 my $p2 = $loc->update_info; 266 if (!$p2) { 267 next; 268 } 269 if ($p2->pkgpath->partial_match($subdir)) { 270 push(@l2, $loc); 271 } 272 } 273 return \@l2; 274} 275 ); 276} 277 2781; 279