From c0c02f966518a01585b1f43bf5bdf35551c70e28 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 16 Feb 2021 20:32:02 -0500 Subject: [PATCH] Implement version-aware sorting of file listings. Based on glibc strverscmp, except with better awareness of version components. Now slotifier-1.1.tar.gz will appear after slotifier-1, as expected. --- layouts/listing.xhtml | 2 +- lib/strverscmp.rb | 117 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 lib/strverscmp.rb diff --git a/layouts/listing.xhtml b/layouts/listing.xhtml index 2e053d5..aad5608 100644 --- a/layouts/listing.xhtml +++ b/layouts/listing.xhtml @@ -54,7 +54,7 @@ end NameLast ModifiedSize -<% files.keys.sort.each do |key| %> +<% files.keys.sort{ |a, b| strverscmp(a, b) }.each do |key| %> <% if files[key][:type] %> diff --git a/lib/strverscmp.rb b/lib/strverscmp.rb new file mode 100644 index 0000000..1f90c62 --- /dev/null +++ b/lib/strverscmp.rb @@ -0,0 +1,117 @@ +# 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 -- 2.43.0