From 89c90d11d80fde63997ca029e14806df0319a541 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 7 Aug 2026 03:27:37 -0500 Subject: 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. --- Hutch/Views/More/ManPageBrowserView.swift | 19 +++----------- Hutch/Views/More/ManPageCatalog.swift | 43 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 Hutch/Views/More/ManPageCatalog.swift (limited to 'Hutch/Views/More') 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)!) + } +} -- cgit v1.2.3