# Ruby implementation of glibc strverscmp-workalike, with some improvements. # # Copyright © 2021 Nick Bowler # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # This implementation is partially adapted from the GNU C Library, covered by # the following copyright and permission notice: # # Copyright (C) 1997-2018 Free Software Foundation, Inc. # # The GNU C Library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. module SVC ZERO = "0".ord NINE = "9".ord A_UP = "A".ord A_LO = "a".ord Z_UP = "Z".ord Z_LO = "z".ord DOT = ".".ord def SVC.my_ord(s) s ? s.ord : 0 end def SVC.isdigit(x) return x >= ZERO && x <= NINE end def SVC.isdigitnz(x) return x > ZERO && x <= NINE end def SVC.isalpha(x) return x >= A_UP && x <= A_LO || x >= A_LO && x <= Z_LO end def SVC.rank(x, xs, i) return 3 if isdigit(x) return 2 if x == DOT and isdigit(my_ord(xs[i+1])) return 1 if isalpha(x) return 0 end end def strverscmp(as, bs) state = :normal i = 0 # locate first difference, tracking context while true a, b = SVC.my_ord(as[i]), SVC.my_ord(bs[i]) break if a == 0 or a != b i = i+1 if a == SVC::ZERO state = :zeroes if state == :normal or state == :dot elsif a > SVC::ZERO and a <= SVC::NINE state = :integral if state == :normal or state == :dot state = :fractional if state == :zeroes elsif a == SVC::DOT state = :dot unless state == :normal else state = :normal end end # found first differing character diff = a - b case state when :fractional return diff when :normal return diff unless SVC.isdigitnz(a) and SVC.isdigitnz(b) when :zeroes ra, rb = SVC.rank(a, as, i), SVC.rank(b, bs, i) if ( cmp = ra <=> rb ) != 0 return cmp if a == SVC::DOT and SVC.isalpha(b) return cmp if b == SVC::DOT and SVC.isalpha(a) return -cmp end return diff when :integral ra, rb = SVC.rank(a, as, i), SVC.rank(b, bs, i) return ra <=> rb if ra != rb return diff unless SVC.isdigit(b) end while SVC.isdigit(a) return 1 unless SVC.isdigit(b) i = i + 1 a, b = SVC.my_ord(as[i]), SVC.my_ord(bs[i]) end return -1 if SVC.isdigit(b) return diff end