Subject: Re: [bug#58136] [PATCH] ui: Improve sort order when searching package names.

From: Lars-Dominik Braun <[email-masked]>
Date: Wed, 12 Oct 2022 13:24:08 +0200

Hi simon,

> In addition to your proposal which LGTM, maybe we could also use the
> ’upstream-name’ properties.  Most of the time, the Guix name matches the
> upstream name, but sometimes not.  Although, it would not fix the issue
> for ggplot2 since there is no upstream-name for this package. :-)
I agree that using the upstream-name would be a good idea.

>  2. set the “namespace” weight to 1 (or 2 if you prefer)
> 
>     Otherwise, for example, generic name as CSV could artificially bump
>     the relevance and hide relevant packages.  For instance, compare
> 
>        guix search csv
The issue here is we don’t know what the user is searching for. If we
add more weight to the package name then usually libraries (rust-csv,
ghc-csv, …) win. Imo a search for “csv” should return tools to
manipulate CSV files like csvkit, csvdiff, xlsx2csv, … Just like
“json” should yield tools like jq, json.sh and possibly others which
I cannot find right now. But maybe I’m searching for a C library that
parses CSV instead. And then what…?

As for ggplot2, the particular issue seems to be that scores are added
for each match and the description for some of our packages contains
“ggplot2” alot. So I tried using MAX instead of +, which works,
but results in little variation of scores and thus weird sort order
(descending by name). It does not feel like an improvement either.

Cheers,
Lars

-- 
Lars-Dominik Braun
Wissenschaftlicher Mitarbeiter/Research Associate

www.leibniz-psychology.org
ZPID - Leibniz-Institut für Psychologie /
ZPID - Leibniz Institute for Psychology
Universitätsring 15
D-54296 Trier - Germany
Tel.: +49–651–201-4964
diff --git a/guix/packages.scm b/guix/packages.scm
index 94e464cd01..9934501cdb 100644
--- a/guix/packages.scm
+++ b/guix/packages.scm
@@ -86,6 +86,7 @@ (define-module (guix packages)
             this-package
             package-name
             package-upstream-name
+            package-upstream-name*
             package-version
             package-full-name
             package-source
@@ -657,6 +658,38 @@ (define (package-upstream-name package)
   (or (assq-ref (package-properties package) 'upstream-name)
       (package-name package)))
 
+(define (package-upstream-name* package)
+  "Return the upstream name of PACKAGE, which could be different from the name
+it has in Guix."
+  (let ((namespaces (list "cl-"
+                          "ecl-"
+                          "emacs-"
+                          "ghc-"
+                          "go-"
+                          "guile-"
+                          "java-"
+                          "julia-"
+                          "lua-"
+                          "minetest-"
+                          "node-"
+                          "ocaml-"
+                          "perl-"
+                          "python-"
+                          "r-"
+                          "ruby-"
+                          "rust-"
+                          "sbcl-"
+                          "texlive-"))
+        (name (package-name package)))
+    (or (assq-ref (package-properties package) 'upstream-name)
+        (let loop ((prefixes namespaces))
+          (match prefixes
+            ('() name)
+            ((prefix rest ...)
+              (if (string-prefix? prefix name)
+                (substring name (string-length prefix))
+                (loop (cdr prefixes)))))))))
+
 (define (hidden-package p)
   "Return a \"hidden\" version of P--i.e., one that 'fold-packages' and thus,
 user interfaces, ignores."
diff --git a/guix/ui.scm b/guix/ui.scm
index dad2b853ac..da16a50f9f 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -1623,10 +1623,23 @@ (define (relevance obj regexps metrics)
   (define (score regexp str)
     (fold-matches regexp str 0
                   (lambda (m score)
-                    (+ score
-                       (if (string=? (match:substring m) str)
-                           5             ;exact match
-                           1)))))
+                    (let* ((start (- (match:start m) 1))
+                           (end (match:end m))
+                           (left (if (>= start 0) (string-ref str start) #f))
+                           (right (if (< end (string-length str)) (string-ref str end) #f))
+                           (delimiter-classes '(Cc Cf Pd Pe Pf Pi Po Ps Sk Zs Zl Zp))
+                           (delim-left (or (member (and=> left char-general-category) delimiter-classes) (eq? left #f)))
+                           (delim-right (or (member (and=> right char-general-category) delimiter-classes) (eq? right #f))))
+                      (max score
+                        (cond
+                          ;; regexp is a full match for str.
+                          ((and (eq? left #f) (eq? right #f)) 4)
+                          ;; regexp matches a single word in str.
+                          ((and delim-left delim-right) 3)
+                          ;; regexp matches the beginning or end of a word in str.
+                          ((or delim-left delim-right) 2)
+                          ;; Everything else.
+                          (#t 1)))))))
 
   (define (regexp->score regexp)
     (let ((score-regexp (lambda (str) (score regexp str))))
@@ -1635,10 +1648,11 @@ (define (regexp->score regexp)
                 ((field . weight)
                  (match (field obj)
                    (#f  relevance)
+                   ('() relevance)
                    ((? string? str)
-                    (+ relevance (* (score-regexp str) weight)))
+                    (max relevance (* (score-regexp str) weight)))
                    ((lst ...)
-                    (+ relevance (* weight (apply + (map score-regexp lst)))))))))
+                    (max relevance (* weight (apply max (map score-regexp lst)))))))))
             0 metrics)))
 
   (let loop ((regexps regexps)
@@ -1655,7 +1669,8 @@ (define (regexp->score regexp)
 (define %package-metrics
   ;; Metrics used to compute the "relevance score" of a package against a set
   ;; of regexps.
-  `((,package-name . 4)
+  `((,package-name . 5)
+    (,package-upstream-name* . 1)
 
     ;; Match against uncommon outputs.
     (,(lambda (package)