Check-in by ben on 2024-09-29 18:39:15

When displaying a user's list of lists, sort by name and id.

 INSERTED    DELETED
       33         12 src/lists/index.dcgi.m4
       45          0 src/util.awk
       78         12 TOTAL over 2 changed files

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -8,37 +8,42 @@
include(src/config.awk)
incl(src/api.awk)
incl(src/cgi.awk)
incl(src/util.awk)

-function main(     cmd, iaout, id, is_private, item_count, item_id, \
-    label, name, url)
+function main(     cmd, count, fields, iaout, i, id, is_private, item, \
+    item_count, item_id, label, name, record, records, url)
{
-    print search "'s Lists"
-    print ""
-
    iaout = gettemp()

    url = api_ssl_endpoint "/services/users/" search "/lists"
    api_request(url, "GET", iaout)

    # format list as a gopher directory (menu)
    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
    FS = "\t"
-    name = ""
+    count = 0
+    delete fields[0]
    id = 0
    is_private = 0
+    item = ""
    item_count = 0
    item_id = ""
+    name = ""
+    record = ""
+    delete records[0]

    while ((cmd | getline) > 0) {
        if ($1 == ".value[]" && $2 == "o") {
-            # print information for previous list
+            # add information for previous list
            if (!is_private && length(name) > 0 && item_count > 0) {
                label = shorten_left(name, 50)
-                printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
-                    item_count, label, cgipath, search, id, server, port
+                item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+                    item_count, label, cgipath, search, id, server, port)
+                record = label "\t" id "\t" item
+                count++
+                records[count] = record
            }
        } else if ($1 == ".value[].list_name" && $2 == "s") {
            name = $3
            id = 0
            is_private = 0
@@ -53,15 +58,31 @@
            item_count++
        }
    }
    close(cmd)

-    # print information for previous list
+    # add information for previous list
    if (!is_private && length(name) > 0 && item_count > 0) {
        label = shorten_left(name, 50)
-        printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
-            item_count, label, cgipath, search, id, server, port
+        item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+            item_count, label, cgipath, search, id, server, port)
+        record = label "\t" id "\t" item
+        count++
+        records[count] = record
+    }
+
+    # sort lists by label and id
+    hsort(records, count)
+
+    print search "'s Lists"
+    print ""
+
+    for (i = 1; i <= count; i++) {
+        record = records[i]
+        split(record, fields, /\t/)
+        item = fields[3]
+        print item
    }

    print ""
    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port


Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -51,10 +51,55 @@
        print "Error: mktemp failed, no tmpfile"
        exit
    }
    return retval
}
+
+# heapsort
+#
+# Unstable, and unlike merge and quicksort it relies on random-access
+# so has poorer cache performance.
+#
+# Advantage over quicksort is that its worst-case same as avg: O(n log n)
+#
+# This presentation based on http://dada.perl.it/shootout/heapsort.lua5.html
+#
+# From: <https://raw.githubusercontent.com/
+# dubiousjim/awkenough/refs/heads/master/library.awk>
+#
+# Requires 1-based numerically indexed arrays.
+
+function hsort(A, n,   c, p, t, i) {
+    if (!n) {
+        n = 1
+        while (n in A) n++
+        n--
+    }
+    i = int(n/2) + 1
+    while (1) {
+        if (i > 1) {
+            i--
+            t = A[i]
+        } else {
+            t = A[n]
+            A[n] = A[1]
+            n--
+            if (n == 1) {
+                A[1] = t
+                return
+            }
+        }
+        for (p = i; (c = 2*p) <= n; p = c) {
+            if ((c < n) && (A[c] < A[c+1]))
+                c++
+            if (t < A[c])
+                A[p] = A[c]
+            else break
+        }
+        A[p] = t
+    }
+}

function human_size(bytes) {
    if (bytes > size_gb) {
        retval = sprintf("%.1fG", bytes / size_gb)
    } else if (bytes > size_mb) {