oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1// Package query reads the shape of a search box, not its meaning.
2//
3// It exists so that one decision — is this person looking something up,
4// or asking about the corpus? — has a single home, is unit-testable
5// without a browser, and is made on the server. The browser script that
6// acts on it only honours a flag the server set; it does not re-derive
7// the rule, because two copies of a heuristic drift and the drift is
8// silent.
9package query
10
11import (
12 "strings"
13 "unicode"
14)
15
16// openers are the words that, first in a query, mean the person is
17// asking rather than looking up.
18//
19// Interrogatives and auxiliaries only. Each one costs a false positive
20// when it is also an ordinary search term ("will" the noun, "can" the
21// verb), and that trade is deliberately one-sided: a missed prime makes
22// someone press Enter twice, while a wrong prime navigates away from a
23// page they were still reading. The safe direction is not priming.
24var openers = map[string]bool{
25 "who": true, "whom": true, "whose": true, "what": true, "when": true,
26 "where": true, "which": true, "why": true, "how": true,
27
28 "am": true, "is": true, "are": true, "was": true, "were": true,
29 "do": true, "does": true, "did": true,
30 "can": true, "could": true, "should": true, "would": true, "will": true,
31 "has": true, "have": true, "had": true, "may": true, "might": true,
32 "must": true,
33}
34
35// PrimesFirstResult reports whether the first of results results should
36// be the one Enter goes to.
37//
38// Both terms of the decision live here rather than at the call site,
39// because only one of them is visible from outside. An HTTP test cannot
40// see the results > 0 term at all: with no results the template renders
41// no list items, so priming nothing and priming a result that does not
42// exist produce byte-identical markup. A guard whose failure is
43// invisible is a guard written to pass, so this is the level the guard
44// is tested at.
45func PrimesFirstResult(q string, results int) bool {
46 return results > 0 && !IsQuestion(q)
47}
48
49// IsQuestion reports whether a query reads as a question about the
50// corpus rather than a lookup in it.
51//
52// Two signals, both cheap and both explicable to the person typing: it
53// ends in a question mark, or it opens with an interrogative or an
54// auxiliary. Nothing about length — "notes from the March offsite" is
55// six words and still a lookup, and a rule that punished it would make
56// Enter unreliable in exactly the case it exists to serve.
57func IsQuestion(q string) bool {
58 q = strings.TrimSpace(q)
59 if q == "" {
60 return false
61 }
62 if strings.HasSuffix(q, "?") {
63 return true
64 }
65 return openers[firstWord(q)]
66}
67
68// firstWord is the leading run of letters, lowercased.
69//
70// Letters only, and only the leading run, so that "who's" opens with
71// "who" while "whatever" and "canary" open with themselves — a prefix
72// match here would have made every query beginning "can…" or "will…"
73// unprimeable.
74func firstWord(q string) string {
75 var b strings.Builder
76 for _, r := range q {
77 if !unicode.IsLetter(r) {
78 break
79 }
80 b.WriteRune(unicode.ToLower(r))
81 }
82 return b.String()
83}
84