Skip to content

Commit 70f047d

Browse files
committed
Use download counts in search
1 parent 045a97d commit 70f047d

File tree

5 files changed

+108
-136
lines changed

5 files changed

+108
-136
lines changed

src/packages/override.gleam

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,15 @@ pub fn expand_search_term(term: String) -> List(String) {
4545
term -> [term]
4646
}
4747
}
48+
49+
pub fn is_core_package(name: String) -> Bool {
50+
case name {
51+
"gleam_stdlib"
52+
| "gleam_javascript"
53+
| "gleam_erlang"
54+
| "gleam_otp"
55+
| "gleam_json"
56+
| "gleam_time" -> True
57+
_ -> False
58+
}
59+
}

src/packages/router.gleam

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import gleam/time/calendar
1010
import gleam/time/timestamp
1111
import gleam/uri
1212
import packages/storage
13-
import packages/text_search
1413
import packages/web.{type Context}
1514
import packages/web/page
1615
import wisp.{type Request, type Response}
@@ -142,17 +141,8 @@ fn internet_points(ctx: Context) -> Response {
142141
fn search(request: Request, context: Context) -> Response {
143142
let search_term = get_search_parameter(request)
144143
let assert Ok(packages) = case search_term {
145-
"" -> storage.list_packages(context.db)
146-
_ -> text_search.lookup(context.search_index, search_term)
147-
}
148-
let assert Ok(packages) =
149-
storage.ranked_package_summaries(context.db, packages, search_term)
150-
let packages = case search_term {
151-
"" ->
152-
list.sort(packages, fn(a, b) {
153-
timestamp.compare(b.updated_in_hex_at, a.updated_in_hex_at)
154-
})
155-
_ -> packages
144+
"" -> storage.packages_most_recent_first(context.db)
145+
_ -> storage.search_packages(context.db, context.search_index, search_term)
156146
}
157147

158148
page.packages_list(packages, search_term)

src/packages/storage.gleam

Lines changed: 63 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import gleam/bool
21
import gleam/dict
32
import gleam/dynamic/decode.{type Decoder}
43
import gleam/float
@@ -14,6 +13,7 @@ import gleam/time/calendar
1413
import gleam/time/timestamp.{type Timestamp}
1514
import packages/error.{type Error}
1615
import packages/override
16+
import packages/text_search
1717
import storail.{type Collection}
1818

1919
pub opaque type Database {
@@ -370,72 +370,63 @@ pub fn list_packages(database: Database) -> Result(List(String), Error) {
370370
}
371371
}
372372

373-
type Groups {
374-
Groups(
375-
exact: List(Package),
376-
regular: List(Package),
377-
v0: List(Package),
378-
old: List(Package),
379-
)
380-
}
381-
382-
pub fn ranked_package_summaries(
383-
database: Database,
384-
packages: List(String),
373+
pub fn search_packages(
374+
db: Database,
375+
search: text_search.TextSearchIndex,
385376
search_term: String,
386377
) -> Result(List(Package), Error) {
387-
let gleam_v1 =
388-
timestamp.from_calendar(
389-
calendar.Date(2024, calendar.March, 4),
390-
calendar.TimeOfDay(0, 0, 0, 0),
391-
calendar.utc_offset,
392-
)
393-
394-
use packages <- result.map({
395-
use name <- list.try_map(packages)
396-
get_package(database, name)
397-
})
378+
let bool = fn(b) {
379+
case b {
380+
True -> 1
381+
False -> 0
382+
}
383+
}
384+
use found <- result.try(text_search.lookup(search, search_term))
385+
use packages <- result.map(
386+
list.try_map(found, fn(found) {
387+
use package <- result.map(get_package(db, found.name))
388+
389+
let exact_package_name_match = bool(search_term == package.name)
390+
let is_not_v0 = bool(!string.starts_with(package.latest_version, "0."))
391+
let is_core_package = bool(override.is_core_package(package.name))
392+
let updated_at =
393+
float.round(timestamp.to_unix_seconds(package.updated_in_hex_at))
394+
395+
// This is the value we use to determine what order packages should be
396+
// shown by. Later list values only take effect if the earlier ones are
397+
// equal.
398+
let ordering_key = [
399+
exact_package_name_match,
400+
is_not_v0,
401+
found.match_count,
402+
is_core_package,
403+
package.downloads_recent,
404+
updated_at,
405+
]
406+
#(ordering_key, package)
407+
}),
408+
)
398409

399-
let groups = Groups([], [], [], [])
400-
401-
let groups =
402-
list.fold(packages, groups, fn(groups, package) {
403-
// The ordering of the clauses matter. Something can be both v0 and old,
404-
// and which group it goes into impacts the final ordering.
405-
406-
use <- bool.lazy_guard(package.name == search_term, fn() {
407-
Groups(..groups, exact: [package, ..groups.exact])
408-
})
409-
410-
let is_old =
411-
timestamp.compare(package.updated_in_hex_at, gleam_v1) == order.Lt
412-
use <- bool.lazy_guard(is_old, fn() {
413-
Groups(..groups, old: [package, ..groups.old])
414-
})
415-
416-
let is_zero_version = string.starts_with(package.latest_version, "0.")
417-
use <- bool.lazy_guard(is_zero_version, fn() {
418-
Groups(..groups, v0: [package, ..groups.v0])
419-
})
420-
421-
Groups(..groups, regular: [package, ..groups.regular])
422-
})
423-
424-
let Groups(exact:, regular:, v0:, old:) = groups
425-
// This list is ordered backwards, so the later in the list the higher it
426-
// will be shown in the UI.
427-
[
428-
// Packages published before Gleam v1.0.0 are likely outdated.
429-
old,
430-
// v0 versions are discouraged, so they are shown lower.
431-
v0,
432-
// Regular versions are not prioritised in any particular way.
433-
regular,
434-
// Exact matches for the search term come first.
435-
exact,
436-
]
437-
|> list.flatten
438-
|> list.reverse
410+
packages
411+
|> list.sort(fn(a, b) { list_compare(b.0, a.0, int.compare) })
412+
|> list.map(fn(pair) { pair.1 })
413+
}
414+
415+
fn list_compare(
416+
a: List(t),
417+
b: List(t),
418+
compare: fn(t, t) -> order.Order,
419+
) -> order.Order {
420+
case a, b {
421+
[], [] -> order.Eq
422+
[], _ -> order.Lt
423+
_, [] -> order.Gt
424+
[a1, ..a], [b1, ..b] ->
425+
case compare(a1, b1) {
426+
order.Eq -> list_compare(a, b, compare)
427+
order.Gt as order | order.Lt as order -> order
428+
}
429+
}
439430
}
440431

441432
pub fn try_fold_packages(
@@ -564,3 +555,11 @@ fn date_string(timestamp: Timestamp) -> String {
564555
|> timestamp.to_rfc3339(calendar.utc_offset)
565556
|> string.slice(0, 10)
566557
}
558+
559+
pub fn packages_most_recent_first(db: Database) -> Result(List(Package), Error) {
560+
use packages <- result.try(list_packages(db))
561+
use packages <- result.map(list.try_map(packages, get_package(db, _)))
562+
list.sort(packages, fn(a, b) {
563+
timestamp.compare(b.updated_in_hex_at, a.updated_in_hex_at)
564+
})
565+
}

src/packages/text_search.gleam

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import ethos.{type BagTable}
22
import gleam/dict
3-
import gleam/int
43
import gleam/list
54
import gleam/option
6-
import gleam/order
75
import gleam/result
86
import gleam/string
97
import packages/error.{type Error}
@@ -46,10 +44,12 @@ pub fn update(
4644
insert(index, name, description)
4745
}
4846

47+
/// Find all matches for the given search term. The list is not returned in any
48+
/// order, but each found item is returned with a match count.
4949
pub fn lookup(
5050
index: TextSearchIndex,
5151
phrase: String,
52-
) -> Result(List(String), Error) {
52+
) -> Result(List(Found), Error) {
5353
let phrase = string.lowercase(phrase)
5454
stem_words(phrase)
5555
|> list.flat_map(override.expand_search_term)
@@ -61,32 +61,15 @@ pub fn lookup(
6161
dict.upsert(counters, name, fn(x) { option.unwrap(x, 0) + 1 })
6262
})
6363
|> dict.to_list
64-
|> list.map(fn(pair) {
65-
case pair.0 {
66-
// Rank up proritised packages
67-
"gleam_stdlib"
68-
| "gleam_javascript"
69-
| "gleam_erlang"
70-
| "gleam_otp"
71-
| "gleam_json"
72-
| "gleam_time" -> #(pair.0, pair.1 + 10)
73-
_ -> pair
74-
}
75-
})
76-
|> list.sort(fn(a, b) {
77-
case a, b {
78-
// Exact matches come first
79-
#(name, _), _ if name == phrase -> order.Lt
80-
_, #(name, _) if name == phrase -> order.Gt
81-
// Otherwise compare the score
82-
_, _ -> int.compare(b.1, a.1)
83-
}
84-
})
85-
|> list.map(fn(pair) { pair.0 })
64+
|> list.map(fn(pair) { Found(pair.0, pair.1) })
8665
})
8766
|> result.replace_error(error.EtsTableError)
8867
}
8968

69+
pub type Found {
70+
Found(name: String, match_count: Int)
71+
}
72+
9073
fn remove(index: TextSearchIndex, name: String) -> Result(Nil, Error) {
9174
ethos.delete_value(index.table, name)
9275
|> result.replace_error(error.EtsTableError)

0 commit comments

Comments
 (0)