]> git.draconx.ca Git - homepage.git/blob - lib/strverscmp.rb
Improve script hack compatibility.
[homepage.git] / lib / strverscmp.rb
1 # Ruby implementation of glibc strverscmp-workalike, with some improvements.
2 #
3 # Copyright © 2021 Nick Bowler
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 #
18 # This implementation is partially adapted from the GNU C Library, covered by
19 # the following copyright and permission notice:
20 #
21 #   Copyright (C) 1997-2018 Free Software Foundation, Inc.
22 #
23 #   The GNU C Library is free software; you can redistribute it and/or
24 #   modify it under the terms of the GNU Lesser General Public
25 #   License as published by the Free Software Foundation; either
26 #   version 2.1 of the License, or (at your option) any later version.
27
28 module SVC
29     ZERO = "0".ord
30     NINE = "9".ord
31     A_UP = "A".ord
32     A_LO = "a".ord
33     Z_UP = "Z".ord
34     Z_LO = "z".ord
35     DOT  = ".".ord
36
37     def SVC.my_ord(s)
38         s ? s.ord : 0
39     end
40
41     def SVC.isdigit(x)
42         return x >= ZERO && x <= NINE
43     end
44
45     def SVC.isdigitnz(x)
46         return x > ZERO && x <= NINE
47     end
48
49     def SVC.isalpha(x)
50         return x >= A_UP && x <= A_LO ||
51                x >= A_LO && x <= Z_LO
52     end
53
54     def SVC.rank(x, xs, i)
55         return 3 if isdigit(x)
56         return 2 if x == DOT and isdigit(my_ord(xs[i+1]))
57         return 1 if isalpha(x)
58         return 0
59     end
60 end
61
62 def strverscmp(as, bs)
63     state = :normal
64     i = 0
65
66     # locate first difference, tracking context
67     while true
68         a, b = SVC.my_ord(as[i]), SVC.my_ord(bs[i])
69
70         break if a == 0 or a != b
71         i = i+1
72
73         if a == SVC::ZERO
74             state = :zeroes if state == :normal or state == :dot
75         elsif a > SVC::ZERO and a <= SVC::NINE
76             state = :integral if state == :normal or state == :dot
77             state = :fractional if state == :zeroes
78         elsif a == SVC::DOT
79             state = :dot unless state == :normal
80         else
81             state = :normal
82         end
83     end
84
85     # found first differing character
86     diff = a - b
87
88     case state
89     when :fractional
90         return diff
91     when :normal
92         return diff unless SVC.isdigitnz(a) and SVC.isdigitnz(b)
93     when :zeroes
94         ra, rb = SVC.rank(a, as, i), SVC.rank(b, bs, i)
95
96         if ( cmp = ra <=> rb ) != 0
97             return cmp if a == SVC::DOT and SVC.isalpha(b)
98             return cmp if b == SVC::DOT and SVC.isalpha(a)
99             return -cmp
100         end
101
102         return diff
103     when :integral
104         ra, rb = SVC.rank(a, as, i), SVC.rank(b, bs, i)
105         return ra <=> rb if ra != rb
106         return diff unless SVC.isdigit(b)
107     end
108
109     while SVC.isdigit(a)
110         return 1 unless SVC.isdigit(b)
111         i = i + 1
112         a, b = SVC.my_ord(as[i]), SVC.my_ord(bs[i])
113     end
114
115     return -1 if SVC.isdigit(b)
116     return diff
117 end