summaryrefslogtreecommitdiff
path: root/Hutch
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-08-07 03:27:37 -0500
committerChristian Cleberg <[email protected]>2026-08-07 03:27:37 -0500
commit89c90d11d80fde63997ca029e14806df0319a541 (patch)
treed4b6b57bc7648d8b1137b1d22bb09d6f2d44b093 /Hutch
parent6c26cf048119bf4ff6af039991d3e34d76bac2ad (diff)
downloadhutch-89c90d11d80fde63997ca029e14806df0319a541.tar.gz
hutch-89c90d11d80fde63997ca029e14806df0319a541.tar.bz2
hutch-89c90d11d80fde63997ca029e14806df0319a541.zip
Sync bundled man page catalog from man.sr.ht (#7)
The More tab's official man-page list was a hardcoded array, updated by hand. Move it to a checked-in Hutch/Resources/man-pages.json (bundled via the synchronized group) loaded by a new ManPageCatalog, with the previous list kept as a built-in fallback. Add scripts/sync_man_pages.py, which re-derives the catalog from the man.sr.ht landing page, and a weekly scheduled workflow that runs it and opens a pull request when the list diverges upstream. The script refuses to write a suspiciously short list so a markup change can't gut the catalog. This first sync also picks up chat.sr.ht, which upstream added since the list was last hand-edited. Tests cover catalog loading, the fallback, and JSON decoding.
Diffstat (limited to 'Hutch')
-rw-r--r--Hutch/Resources/man-pages.json50
-rw-r--r--Hutch/Views/More/ManPageBrowserView.swift19
-rw-r--r--Hutch/Views/More/ManPageCatalog.swift43
3 files changed, 97 insertions, 15 deletions
diff --git a/Hutch/Resources/man-pages.json b/Hutch/Resources/man-pages.json
new file mode 100644
index 0000000..7adf284
--- /dev/null
+++ b/Hutch/Resources/man-pages.json
@@ -0,0 +1,50 @@
+[
+ {
+ "title": "builds.sr.ht",
+ "url": "https://man.sr.ht/builds.sr.ht/"
+ },
+ {
+ "title": "chat.sr.ht",
+ "url": "https://man.sr.ht/chat.sr.ht/"
+ },
+ {
+ "title": "git.sr.ht",
+ "url": "https://man.sr.ht/git.sr.ht/"
+ },
+ {
+ "title": "hg.sr.ht",
+ "url": "https://man.sr.ht/hg.sr.ht/"
+ },
+ {
+ "title": "hub.sr.ht",
+ "url": "https://man.sr.ht/hub.sr.ht/"
+ },
+ {
+ "title": "lists.sr.ht",
+ "url": "https://man.sr.ht/lists.sr.ht/"
+ },
+ {
+ "title": "man.sr.ht",
+ "url": "https://man.sr.ht/man.sr.ht/"
+ },
+ {
+ "title": "meta.sr.ht",
+ "url": "https://man.sr.ht/meta.sr.ht/"
+ },
+ {
+ "title": "paste.sr.ht",
+ "url": "https://man.sr.ht/paste.sr.ht/"
+ },
+ {
+ "title": "sr.ht",
+ "url": "https://man.sr.ht/sr.ht/"
+ },
+ {
+ "title": "srht.site",
+ "url": "https://srht.site/"
+ },
+ {
+ "title": "todo.sr.ht",
+ "url": "https://man.sr.ht/todo.sr.ht/"
+ }
+]
diff --git a/Hutch/Views/More/ManPageBrowserView.swift b/Hutch/Views/More/ManPageBrowserView.swift
index c22eafe..575b3ae 100644
--- a/Hutch/Views/More/ManPageBrowserView.swift
+++ b/Hutch/Views/More/ManPageBrowserView.swift
@@ -1,26 +1,15 @@
import SwiftUI
/// Entry point for the man.sr.ht browser in the More tab.
-/// Shows a pre-populated list of official sr.ht man pages.
+/// Shows the official sr.ht man pages from the bundled catalog, which the
+/// scheduled sync workflow keeps current with man.sr.ht.
struct ManPageBrowserView: View {
- private let officialDocs: [(title: String, url: URL)] = [
- ("sr.ht", URL(string: "https://man.sr.ht/sr.ht/")!),
- ("hub.sr.ht", URL(string: "https://man.sr.ht/hub.sr.ht/")!),
- ("git.sr.ht", URL(string: "https://man.sr.ht/git.sr.ht/")!),
- ("hg.sr.ht", URL(string: "https://man.sr.ht/hg.sr.ht/")!),
- ("lists.sr.ht", URL(string: "https://man.sr.ht/lists.sr.ht/")!),
- ("todo.sr.ht", URL(string: "https://man.sr.ht/todo.sr.ht/")!),
- ("builds.sr.ht", URL(string: "https://man.sr.ht/builds.sr.ht/")!),
- ("paste.sr.ht", URL(string: "https://man.sr.ht/paste.sr.ht/")!),
- ("man.sr.ht", URL(string: "https://man.sr.ht/man.sr.ht/")!),
- ("meta.sr.ht", URL(string: "https://man.sr.ht/meta.sr.ht/")!),
- ("srht.site", URL(string: "https://srht.site/")!)
- ]
+ private let officialDocs = ManPageCatalog.load()
var body: some View {
List {
Section("Official Man Pages") {
- ForEach(officialDocs, id: \.title) { doc in
+ ForEach(officialDocs) { doc in
NavigationLink(value: MoreRoute.manPage(doc.url)) {
Text(doc.title)
}
diff --git a/Hutch/Views/More/ManPageCatalog.swift b/Hutch/Views/More/ManPageCatalog.swift
new file mode 100644
index 0000000..faff0c5
--- /dev/null
+++ b/Hutch/Views/More/ManPageCatalog.swift
@@ -0,0 +1,43 @@
+import Foundation
+
+struct ManPageCatalogEntry: Decodable, Hashable, Identifiable {
+ let title: String
+ let url: URL
+
+ var id: String { title }
+}
+
+enum ManPageCatalog {
+ /// Official sr.ht man pages, loaded from the bundled `man-pages.json` that
+ /// the scheduled sync workflow keeps in step with man.sr.ht. Falls back to a
+ /// built-in list if the resource is missing or unreadable, so the browser is
+ /// never empty.
+ static func load(bundle: Bundle = .main) -> [ManPageCatalogEntry] {
+ guard let url = bundle.url(forResource: "man-pages", withExtension: "json"),
+ let data = try? Data(contentsOf: url),
+ let entries = try? JSONDecoder().decode([ManPageCatalogEntry].self, from: data),
+ !entries.isEmpty else {
+ return fallback
+ }
+ return entries
+ }
+
+ static let fallback: [ManPageCatalogEntry] = [
+ entry("builds.sr.ht", "https://man.sr.ht/builds.sr.ht/"),
+ entry("chat.sr.ht", "https://man.sr.ht/chat.sr.ht/"),
+ entry("git.sr.ht", "https://man.sr.ht/git.sr.ht/"),
+ entry("hg.sr.ht", "https://man.sr.ht/hg.sr.ht/"),
+ entry("hub.sr.ht", "https://man.sr.ht/hub.sr.ht/"),
+ entry("lists.sr.ht", "https://man.sr.ht/lists.sr.ht/"),
+ entry("man.sr.ht", "https://man.sr.ht/man.sr.ht/"),
+ entry("meta.sr.ht", "https://man.sr.ht/meta.sr.ht/"),
+ entry("paste.sr.ht", "https://man.sr.ht/paste.sr.ht/"),
+ entry("sr.ht", "https://man.sr.ht/sr.ht/"),
+ entry("srht.site", "https://srht.site/"),
+ entry("todo.sr.ht", "https://man.sr.ht/todo.sr.ht/")
+ ]
+
+ private static func entry(_ title: String, _ urlString: String) -> ManPageCatalogEntry {
+ ManPageCatalogEntry(title: title, url: URL(string: urlString)!)
+ }
+}