167d43fbcSespie# ex:ts=8 sw=4:
2*039cbdaaSespie# $OpenBSD: PackageName.pm,v 1.58 2023/06/13 09:07:17 espie Exp $
36f05d20cSespie#
46fcf288dSespie# Copyright (c) 2003-2010 Marc Espie <espie@openbsd.org>
56f05d20cSespie#
667d43fbcSespie# Permission to use, copy, modify, and distribute this software for any
767d43fbcSespie# purpose with or without fee is hereby granted, provided that the above
867d43fbcSespie# copyright notice and this permission notice appear in all copies.
96f05d20cSespie#
1067d43fbcSespie# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1167d43fbcSespie# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1267d43fbcSespie# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1367d43fbcSespie# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1467d43fbcSespie# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1567d43fbcSespie# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1667d43fbcSespie# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
176f05d20cSespie
18*039cbdaaSespieuse v5.36;
190fbefeddSespie
206f05d20cSespiepackage OpenBSD::PackageName;
216f05d20cSespie
22*039cbdaaSespiesub url2pkgname($name)
235c18493bSespie{
245c18493bSespie	$name =~ s|.*/||;
255c18493bSespie	$name =~ s|\.tgz$||;
265c18493bSespie
275c18493bSespie	return $name;
285c18493bSespie}
295c18493bSespie
30f56c88b3Sbernd# see packages-specs(7)
31*039cbdaaSespiesub splitname($n)
326f05d20cSespie{
33b62674ebSespie	if ($n =~ /^(.*?)\-(\d.*)$/o) {
34a7ceec02Sespie		my $stem = $1;
35a7ceec02Sespie		my $rest = $2;
3603258ab2Sespie		my @all = split /\-/o, $rest;
376f05d20cSespie		return ($stem, @all);
386f05d20cSespie	} else {
39b62674ebSespie		return ($n);
406f05d20cSespie	}
416f05d20cSespie}
426f05d20cSespie
431f9ffadcSespiemy $cached = {};
441f9ffadcSespie
45*039cbdaaSespiesub from_string($class, $s)
464a9f97dfSespie{
47b62674ebSespie	return $cached->{$s} //= $class->new_from_string($s);
48516945adSespie}
491f9ffadcSespie
50*039cbdaaSespiesub new_from_string($class, $n)
511f9ffadcSespie{
52b62674ebSespie	if ($n =~ /^(.*?)\-(\d.*)$/o) {
53a7ceec02Sespie		my $stem = $1;
54a7ceec02Sespie		my $rest = $2;
5503258ab2Sespie		my @all = split /\-/o, $rest;
564a9f97dfSespie		my $version = OpenBSD::PackageName::version->from_string(shift @all);
574a9f97dfSespie		return bless {
584a9f97dfSespie			stem => $stem,
594a9f97dfSespie			version => $version,
601f9ffadcSespie			flavors => { map {($_, 1)} @all },
614a9f97dfSespie		}, "OpenBSD::PackageName::Name";
624a9f97dfSespie	} else {
634a9f97dfSespie		return bless {
64b62674ebSespie			stem => $n,
654a9f97dfSespie		}, "OpenBSD::PackageName::Stem";
664a9f97dfSespie	}
674a9f97dfSespie}
684a9f97dfSespie
69*039cbdaaSespiesub splitstem($s)
70f5d79ea0Sespie{
71b62674ebSespie	if ($s =~ /^(.*?)\-\d/o) {
72a7ceec02Sespie		return $1;
734a9f97dfSespie	} else {
74b62674ebSespie		return $s;
754a9f97dfSespie	}
76f5d79ea0Sespie}
77f5d79ea0Sespie
78*039cbdaaSespiesub pkg2stem($pkg)
79699c12dfSespie{
80*039cbdaaSespie	my $s = splitstem($pkg);
81699c12dfSespie	$s =~ tr/A-Z/a-z/;
82699c12dfSespie	return $s;
83699c12dfSespie
84699c12dfSespie}
85*039cbdaaSespiesub is_stem($s)
86d18f6f74Sespie{
87b62674ebSespie	if ($s =~ m/\-\d/o || $s eq '-') {
88d18f6f74Sespie		return 0;
89d18f6f74Sespie	} else {
90d18f6f74Sespie		return 1;
91d18f6f74Sespie	}
92d18f6f74Sespie}
93d18f6f74Sespie
94*039cbdaaSespiesub compile_stemlist(@p)
959b6421f6Sespie{
969b6421f6Sespie	my $hash = {};
97*039cbdaaSespie	for my $n (@p) {
98699c12dfSespie		$hash->{pkg2stem($n)}{$n} = 1;
999b6421f6Sespie	}
1009b6421f6Sespie	bless $hash, "OpenBSD::PackageLocator::_compiled_stemlist";
1019b6421f6Sespie}
1029b6421f6Sespie
103*039cbdaaSespiesub avail2stems(@p)
104646f71ecSespie{
105*039cbdaaSespie	return compile_stemlist(@p);
106646f71ecSespie}
107646f71ecSespie
1089b6421f6Sespiepackage OpenBSD::PackageLocator::_compiled_stemlist;
1099b6421f6Sespie
110*039cbdaaSespiesub find($self, $stem)
1119b6421f6Sespie{
112b1986bc3Sespie	$stem =~ tr/A-Z/a-z/;
113b28823c4Sespie	return keys %{$self->{$stem}};
1149b6421f6Sespie}
1159b6421f6Sespie
116*039cbdaaSespiesub add($self, $pkgname)
117efa23318Sespie{
118699c12dfSespie	$self->{OpenBSD::PackageName::pkg2stem($pkgname)}{$pkgname} = 1;
119efa23318Sespie}
120efa23318Sespie
121*039cbdaaSespiesub delete($self, $pkgname)
122efa23318Sespie{
123699c12dfSespie	my $stem = OpenBSD::PackageName::pkg2stem($pkgname);
124699c12dfSespie	delete $self->{$stem}{$pkgname};
125efa23318Sespie	if(keys %{$self->{$stem}} == 0) {
126efa23318Sespie		delete $self->{$stem};
127efa23318Sespie	}
128efa23318Sespie}
129efa23318Sespie
130*039cbdaaSespiesub find_partial($self, $partial)
13134a4c4c6Sespie{
13234a4c4c6Sespie	my @result = ();
13334a4c4c6Sespie	while (my ($stem, $pkgs) = each %$self) {
13434a4c4c6Sespie		next unless $stem =~ /\Q$partial\E/i;
13534a4c4c6Sespie		push(@result, keys %$pkgs);
13634a4c4c6Sespie	}
13734a4c4c6Sespie	return @result;
13834a4c4c6Sespie}
13934a4c4c6Sespie
140516945adSespiepackage OpenBSD::PackageName::dewey;
1414a9f97dfSespie
142516945adSespiemy $cache = {};
143516945adSespie
144*039cbdaaSespiesub from_string($class, $string)
1454a9f97dfSespie{
146e5e97783Sespie	my $o = bless { deweys => [ split(/\./o, $string) ],
147e5e97783Sespie		suffix => '', suffix_value => 0}, $class;
148044f8b13Sjca	if ($o->{deweys}->[-1] =~ m/^(\d+)(rc|alpha|beta|pre|pl)(\d*)$/) {
1494a9f97dfSespie		$o->{deweys}->[-1] = $1;
150e5e97783Sespie		$o->{suffix} = $2;
151e5e97783Sespie		$o->{suffix_value} = $3;
1524a9f97dfSespie	}
153516945adSespie	return $o;
1544a9f97dfSespie}
1554a9f97dfSespie
156*039cbdaaSespiesub make($class, $string)
1574a9f97dfSespie{
1587eb3bd1aSespie	return $cache->{$string} //= $class->from_string($string);
1594a9f97dfSespie}
1604a9f97dfSespie
161*039cbdaaSespiesub to_string($self)
1624a9f97dfSespie{
163fcba77b1Sespie	my $r = join('.', @{$self->{deweys}});
164e5e97783Sespie	if ($self->{suffix}) {
165e5e97783Sespie		$r .= $self->{suffix} . $self->{suffix_value};
1664a9f97dfSespie	}
167516945adSespie	return $r;
1680e2c7b07Sespie}
1690e2c7b07Sespie
170*039cbdaaSespiesub suffix_compare($a, $b)
171e5e97783Sespie{
172e5e97783Sespie	if ($a->{suffix} eq $b->{suffix}) {
173e5e97783Sespie		return $a->{suffix_value} <=> $b->{suffix_value};
174e5e97783Sespie	}
175e5e97783Sespie	if ($a->{suffix} eq 'pl') {
176e5e97783Sespie		return 1;
177e5e97783Sespie	}
178e5e97783Sespie	if ($b->{suffix} eq 'pl') {
179e5e97783Sespie		return -1;
180e5e97783Sespie	}
181e5e97783Sespie
182e5e97783Sespie	if ($a->{suffix} gt $b->{suffix}) {
183e5e97783Sespie		return -suffix_compare($b, $a);
184e5e97783Sespie	}
185044f8b13Sjca	# order is '', alpha, beta, pre, rc
186e5e97783Sespie	# we know that a < b,
187e5e97783Sespie	if ($a->{suffix} eq '') {
188e5e97783Sespie		return 1;
189e5e97783Sespie	}
190044f8b13Sjca	if ($a->{suffix} eq 'alpha') {
191044f8b13Sjca		return -1;
192044f8b13Sjca	}
193e5e97783Sespie	if ($a->{suffix} eq 'beta') {
194e5e97783Sespie		return -1;
195e5e97783Sespie	}
196e5e97783Sespie	# refuse to compare pre vs. rc
197e5e97783Sespie	return 0;
198e5e97783Sespie}
199e5e97783Sespie
200*039cbdaaSespiesub compare($a, $b)
2014a9f97dfSespie{
2024a9f97dfSespie	# Try a diff in dewey numbers first
2034a9f97dfSespie	for (my $i = 0; ; $i++) {
2044a9f97dfSespie		if (!defined $a->{deweys}->[$i]) {
2054a9f97dfSespie			if (!defined $b->{deweys}->[$i]) {
2061daa8d72Sespie				last;
2074a9f97dfSespie			} else {
2084a9f97dfSespie				return -1;
2094a9f97dfSespie			}
2104a9f97dfSespie		}
2114a9f97dfSespie		if (!defined $b->{deweys}->[$i]) {
2124a9f97dfSespie			return 1;
2134a9f97dfSespie		}
2144a9f97dfSespie		my $r = dewey_compare($a->{deweys}->[$i],
2154a9f97dfSespie			$b->{deweys}->[$i]);
2164a9f97dfSespie		return $r if $r != 0;
2174a9f97dfSespie	}
218e5e97783Sespie	return suffix_compare($a, $b);
2194a9f97dfSespie}
2204a9f97dfSespie
221*039cbdaaSespiesub dewey_compare($a, $b)
2224a9f97dfSespie{
2234a9f97dfSespie	# numerical comparison
224cc24e6f2Sespie	if ($a =~ m/^\d+$/o and $b =~ m/^\d+$/o) {
2254a9f97dfSespie		return $a <=> $b;
2264a9f97dfSespie	}
2274a9f97dfSespie	# added lowercase letter
228cc24e6f2Sespie	if ("$a.$b" =~ m/^(\d+)([a-z]?)\.(\d+)([a-z]?)$/o) {
2294a9f97dfSespie		my ($an, $al, $bn, $bl) = ($1, $2, $3, $4);
2304a9f97dfSespie		if ($an != $bn) {
2314a9f97dfSespie			return $an <=> $bn;
2324a9f97dfSespie		} else {
2334a9f97dfSespie			return $al cmp $bl;
2344a9f97dfSespie		}
2354a9f97dfSespie	}
2364a9f97dfSespie	return $a cmp $b;
2374a9f97dfSespie}
2384a9f97dfSespie
239516945adSespiepackage OpenBSD::PackageName::version;
240516945adSespie
241*039cbdaaSespiesub p($self)
242516945adSespie{
243516945adSespie	return defined $self->{p} ? $self->{p} : -1;
244516945adSespie}
245516945adSespie
246*039cbdaaSespiesub v($self)
247516945adSespie{
248516945adSespie	return defined $self->{v} ? $self->{v} : -1;
249516945adSespie}
250516945adSespie
251*039cbdaaSespiesub from_string($class, $string)
252516945adSespie{
253516945adSespie	my $o = bless {}, $class;
254516945adSespie	if ($string =~ m/^(.*)v(\d+)$/o) {
255516945adSespie		$o->{v} = $2;
256516945adSespie		$string = $1;
257516945adSespie	}
258516945adSespie	if ($string =~ m/^(.*)p(\d+)$/o) {
259516945adSespie		$o->{p} = $2;
260516945adSespie		$string = $1;
261516945adSespie	}
262516945adSespie	$o->{dewey} = OpenBSD::PackageName::dewey->make($string);
263516945adSespie
264516945adSespie	return $o;
265516945adSespie}
266516945adSespie
267*039cbdaaSespiesub to_string($o)
268516945adSespie{
269516945adSespie	my $string = $o->{dewey}->to_string;
270516945adSespie	if (defined $o->{p}) {
271516945adSespie		$string .= 'p'.$o->{p};
272516945adSespie	}
273516945adSespie	if (defined $o->{v}) {
274516945adSespie		$string .= 'v'.$o->{v};
275516945adSespie	}
276516945adSespie	return $string;
277516945adSespie}
278516945adSespie
279*039cbdaaSespiesub pnum_compare($a, $b)
280516945adSespie{
281516945adSespie	return $a->p <=> $b->p;
282516945adSespie}
283516945adSespie
284*039cbdaaSespiesub compare($a, $b)
285516945adSespie{
286516945adSespie	# Simple case: epoch number
287516945adSespie	if ($a->v != $b->v) {
288516945adSespie		return $a->v <=> $b->v;
289516945adSespie	}
290516945adSespie	# Simple case: only p number differs
291516945adSespie	if ($a->{dewey} eq $b->{dewey}) {
292516945adSespie		return $a->pnum_compare($b);
293516945adSespie	}
294516945adSespie
295516945adSespie	return $a->{dewey}->compare($b->{dewey});
296516945adSespie}
297516945adSespie
298*039cbdaaSespiesub has_issues($self)
2996fcf288dSespie{
3006fcf288dSespie	if ($self->{dewey}{deweys}[-1] =~ m/v\d+$/ && defined $self->{p}) {
3016fcf288dSespie		return ("correct order is pNvM");
3026fcf288dSespie	} else {
3036fcf288dSespie		return ();
3046fcf288dSespie	}
3056fcf288dSespie}
3066fcf288dSespie
3074a9f97dfSespiepackage OpenBSD::PackageName::Stem;
308*039cbdaaSespiesub to_string($o)
3094a9f97dfSespie{
3104a9f97dfSespie	return $o->{stem};
3114a9f97dfSespie}
3124a9f97dfSespie
313*039cbdaaSespiesub to_pattern($o)
3144a9f97dfSespie{
3154a9f97dfSespie	return $o->{stem}.'-*';
3164a9f97dfSespie}
3174a9f97dfSespie
318*039cbdaaSespiesub has_issues($self)
3196fcf288dSespie{
3206fcf288dSespie	return ("is a stem");
3216fcf288dSespie}
3226fcf288dSespie
32341787c8eSespiepackage OpenBSD::PackageName::Name;
324*039cbdaaSespiesub flavor_string($o)
325e451b54dSespie{
326e451b54dSespie	return join('-', sort keys %{$o->{flavors}});
327e451b54dSespie}
328e451b54dSespie
329*039cbdaaSespiesub to_string($o)
33041787c8eSespie{
331e451b54dSespie	return join('-', $o->{stem}, $o->{version}->to_string,
332fcba77b1Sespie	    sort keys %{$o->{flavors}});
33341787c8eSespie}
33441787c8eSespie
335*039cbdaaSespiesub to_pattern($o)
33641787c8eSespie{
337e451b54dSespie	return join('-', $o->{stem}, '*', $o->flavor_string);
33841787c8eSespie}
33941787c8eSespie
340*039cbdaaSespiesub compare($a, $b)
341e1eeb5e0Sespie{
342e1eeb5e0Sespie	if ($a->{stem} ne $b->{stem} || $a->flavor_string ne $b->flavor_string) {
343e1eeb5e0Sespie		return undef;
344e1eeb5e0Sespie	}
345e1eeb5e0Sespie	return $a->{version}->compare($b->{version});
346e1eeb5e0Sespie}
347e1eeb5e0Sespie
348*039cbdaaSespiesub has_issues($self)
3496fcf288dSespie{
3506fcf288dSespie	return ((map {"flavor $_ can't start with digit"}
3516fcf288dSespie	    	grep { /^\d/ } keys %{$self->{flavors}}),
3526fcf288dSespie		$self->{version}->has_issues);
3536fcf288dSespie}
3546fcf288dSespie
3556f05d20cSespie1;
356