diff --git a/README.md b/README.md index 869b554..a7f854a 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,28 @@ A project template overrides any engine template of the same name, so a bespoke inheriting none of the engine's later fixes to it, so prefer a block override where one will do. +## The default Contributing page + +Every project's guide gets a `guide/contributing.html`, in the nav right +after `index.md`, even without writing one. It comes from the engine's own +`resources/content/contributing.md`, on the same footing as `base.html` or +`404.html`: shared chrome, not a project's content, which is the point — +"how to open an issue against a jlt-commons project" is the same answer +everywhere, so it lives in one place instead of N copies that drift. + +A project with something project-specific to say (a note on known-broken +areas, its own escalation path, anything beyond the shared etiquette) +writes its own `docs/guide/contributing.md`. That file is discovered the +same way any other guide page is, and having one there replaces the +engine's default outright rather than merging with it. + +This is the general shape for any future default page: the engine's +`discover-doc-ids` adds its own basenames to a project's guide-dir root +group (never a nested subdirectory) for names the project has not written +itself, and `doc-source` resolves each one to the project's file if it +exists, else the engine's bundled default. `contributing.md` is the only +one today. + ## Markdown Standard markdown through `markdown-clj`, plus: diff --git a/resources/content/contributing.md b/resources/content/contributing.md new file mode 100644 index 0000000..031fc38 --- /dev/null +++ b/resources/content/contributing.md @@ -0,0 +1,57 @@ +# Contributing + +This project is built by the community and the team behind it, and +suggestions for improvement are welcome. If something is missing, unclear, +or just plain wrong, open an issue for a question or a gap, and a pull +request for anything you have already worked out. + +## Etiquette + +Adapted from the Clojure community's own +[etiquette guide](https://clojure.org/community/etiquette), since it says +what we would want to say ourselves and says it well. + +Issues, pull requests and discussion here are for people who make things. +Most messages should have one of these forms: + +- I made something. Here is my contribution. +- I am trying to use this and having trouble, please help. +- I can help you with that. +- I am trying to build something on top of this and having trouble, please + help. +- I can help you build something. + +They are not the place for opinion pieces or diatribes, and not the place +for advocacy about what "ought" to be built. If you think something ought +to exist, the fastest way to find out is to build it and send a pull +request. Otherwise, respect that other people get to choose what they do +with their time. + +Disagreements about how something has been, or will be, done should take +the form of technical arguments. A technical argument that gets, and +gives, respect: + +- Keeps it short. +- Sticks to the facts. +- Uses logic. +- Leaves people out of it. +- Avoids rhetorical devices: superfluous or opinion-laden adjectives, + claims to speak for the community or that everyone agrees with you, + threats of what will happen unless things go your way, any flavour of + "the sky is falling." + +If you are not the one doing the work, restrict your input to short +technical arguments supporting your position. If someone has already made +your point, a "+1" is enough, and keeping posts short is worth doing on +its own. + +Ignoring these guidelines costs the time of the people who make things, +which is worth caring about if you intend to be one of them. + +--- + +This is this project's default Contributing page, shipped by +[docs-engine](https://github.com/jlt-commons/docs-engine) so every +jlt-commons project's site carries the same one without copying it around. +A project with something project-specific to add writes its own +`docs/guide/contributing.md`, which replaces this page entirely. diff --git a/src/site/core.clj b/src/site/core.clj index e376a45..c2d7560 100644 --- a/src/site/core.clj +++ b/src/site/core.clj @@ -75,27 +75,47 @@ (into ["index.md"] (remove #{"index.md"} sorted)) (vec sorted)))) +(def ^:private default-root-doc-basenames + "Basenames the engine supplies for the guide's root directory even when + the project has not written one itself — the same relationship + base.html and 404.html already have to a project (shared chrome, not + project content), applied to a page instead of a template. A + project's own file of the same name always wins; see doc-source. Only + the root directory gets defaults, not a nested docs/guide/ subtree. + + Currently just \"contributing.md\": every jlt-commons project's site + gets a Contributing page without copying its content into every + project, and a project with something project-specific to add writes + docs/guide/contributing.md like any other page, which replaces this + one entirely." + #{"contributing.md"}) + (defn discover-doc-ids "Auto-discovered nav order: every *.md anywhere under guide-dir (any - depth), as POSIX-style paths relative to guide-dir. Ordered - directory-by-directory so it reads top-to-bottom like the guide's - intended sequence: directories + depth), as POSIX-style paths relative to guide-dir, PLUS the engine's + own default-root-doc-basenames for any of those the project has not + written itself. Ordered directory-by-directory so it reads top-to- + bottom like the guide's intended sequence: directories themselves in sorted order (guide-dir's own root first, since '' sorts before any name), each directory's own files pinning ITS index.md first if present, else sorted. Used when a project has no projects//docpages.edn curated-order override. + A doc-id this returns does not always name a file guide-dir actually + has — see doc-source, which resolves each one to either the project's + own file or the engine's bundled default. + For a flat guide-dir (the shape every project had before one introduced nested subdirectories under docs/guide/) this reduces to - exactly the previous flat behavior: one directory group (the root), - its files sorted with index.md pinned first. Plain alphabetical sort - on full relative paths would get a nested project's per-directory - index.md wrong when numbered siblings start above 01 (verified - against a sample nested project's section-three/, whose files are - index.md, 02-getting-started.md, 03-closing.md — alphabetical-on-full- - path would order index.md LAST, after 02/03, since digits sort below - the letter 'i'); grouping by directory and pinning per-group avoids - that." + exactly the previous flat behavior plus the defaults: one directory + group (the root), its files sorted with index.md pinned first. Plain + alphabetical sort on full relative paths would get a nested project's + per-directory index.md wrong when numbered siblings start above 01 + (verified against a sample nested project's section-three/, whose + files are index.md, 02-getting-started.md, 03-closing.md — + alphabetical-on-full-path would order index.md LAST, after 02/03, + since digits sort below the letter 'i'); grouping by directory and + pinning per-group avoids that." [guide-dir] (let [root (.toPath (io/file guide-dir)) all-rel (->> (file-seq (io/file guide-dir)) @@ -104,12 +124,36 @@ (map (fn [rel] (str/replace rel java.io.File/separator "/")))) dir-of (fn [rel] (let [i (str/last-index-of rel "/")] (if i (subs rel 0 i) ""))) base-of (fn [rel] (let [i (str/last-index-of rel "/")] (if i (subs rel (inc i)) rel))) - by-dir (group-by dir-of all-rel)] + by-dir (group-by dir-of all-rel) + ;; The defaults join the root group only, and only the ones the + ;; project has not already written under that name — the + ;; override. For the root group a relative path IS its basename, + ;; so adding basenames straight into by-dir's "" entry is safe. + by-dir (update by-dir "" + (fn [root-rels] + (into (vec root-rels) + (remove (set root-rels) default-root-doc-basenames))))] (vec (mapcat (fn [dir] (let [bases (pin-index-first (map base-of (get by-dir dir)))] (map (fn [base] (if (= dir "") base (str dir "/" base))) bases))) (sort (keys by-dir)))))) +(defn doc-source + "doc-id's markdown text: the project's own guide-dir/doc-id if it + exists, else the engine's own bundled default for that name (see + default-root-doc-basenames) — the override mechanism. Throws if + neither exists, which discover-doc-ids should make impossible for any + doc-id it actually returned." + [guide-dir doc-id] + (let [project-file (io/file guide-dir doc-id)] + (if (fs/exists? project-file) + (slurp project-file) + (if-let [res (io/resource (str "content/" doc-id))] + (slurp res) + (throw (ex-info (str "no source for doc-id " doc-id + " — neither the project nor the engine has it") + {:guide-dir (str guide-dir) :doc-id doc-id})))))) + (def ^:private mermaid-marker "What site.markdown/mermaidify emits, and what a project writes by hand in its own template when it wants a diagram outside a markdown fence." @@ -136,15 +180,17 @@ (defn render-all-docs "doc-id -> {:title :toc-html :body-html :slug}, for every doc-id. - Always renders via md/rewrite-nested-doc-links (built per doc-id, - from its own path relative to guide-dir) rather than defaulting to - plain rewrite-doc-links — the nested-aware rewriter produces - byte-identical output to the flat one for every flat (no-'/') doc-id, - so this is safe for every existing project, not just nested ones." + Source text comes from doc-source, so a doc-id the project never wrote + itself still renders from the engine's own default. Always renders via + md/rewrite-nested-doc-links (built per doc-id, from its own path + relative to guide-dir) rather than defaulting to plain + rewrite-doc-links — the nested-aware rewriter produces byte-identical + output to the flat one for every flat (no-'/') doc-id, so this is safe + for every existing project, not just nested ones." [guide-dir doc-ids] (into {} (for [doc-id doc-ids] - (let [raw (slurp (io/file guide-dir doc-id)) + (let [raw (doc-source guide-dir doc-id) {:keys [title toc-html body-html]} (md/render-doc-page raw (md/rewrite-nested-doc-links doc-id))] [doc-id {:title title :toc-html toc-html :body-html body-html diff --git a/test/site/core_test.clj b/test/site/core_test.clj index c5abef1..ff29edc 100644 --- a/test/site/core_test.clj +++ b/test/site/core_test.clj @@ -68,12 +68,12 @@ (fs/create-dirs (io/file docs "media")) (spit (io/file docs "media" "x.gif") "GIF89a")) (let [site (cond-> {:title "jlt-commons" :description "d" :github-url "https://example.invalid" - :base-path base - :guide-dir guide - :templates-dir templates - :output-dir (io/file (str tmp) "_site") - :home-template (when home-template? "home.html") - :asset-dirs (when assets? [(io/file docs "media")])} + :base-path base + :guide-dir guide + :templates-dir templates + :output-dir (io/file (str tmp) "_site") + :home-template (when home-template? "home.html") + :asset-dirs (when assets? [(io/file docs "media")])} (some? mermaid-override) (assoc :mermaid mermaid-override))] (core/generate! site) {:out (:output-dir site) @@ -209,3 +209,38 @@ (is (false? (core/mermaid-needed? {} nil))) (is (true? (core/mermaid-needed? {:mermaid true} "no diagram here"))) (is (false? (core/mermaid-needed? {:mermaid false} "
x
")))) + +;; A project that has never written its own Contributing page still gets +;; the engine's default one, so nothing about "how to engage with this +;; project" needs copying into every project that uses this engine. + +(deftest a-project-with-no-contributing-page-gets-the-engine-default + (let [{:keys [out]} (build-fixture-site! "/some-lib") + contributing (slurp (io/file out "guide" "contributing.html"))] + (is (str/includes? contributing "Etiquette")) + (is (str/includes? contributing "docs-engine")))) + +(deftest the-default-contributing-page-is-in-nav-right-after-index + ;; index.md is pinned first; among what is left, "contributing.md" sorts + ;; alphabetically ahead of "plain.md", so this is also a sort-order check, + ;; not just a presence check. + (let [{:keys [doc]} (build-fixture-site! "/some-lib")] + (is (str/includes? doc "href=\"/some-lib/guide/contributing.html\"")) + (let [index-at (str/index-of doc "/guide/index.html") + contrib-at (str/index-of doc "/guide/contributing.html") + plain-at (str/index-of doc "/guide/plain.html")] + (is (< index-at contrib-at plain-at))))) + +(deftest a-project-with-its-own-contributing-page-overrides-the-default + (let [tmp (fs/create-temp-dir {:prefix "jltc-site"}) + docs (io/file (str tmp) "docs") + guide (io/file docs "guide")] + (fs/create-dirs guide) + (spit (io/file guide "index.md") "# Intro\n") + (spit (io/file guide "contributing.md") "# Contributing\n\nOur own house rules.\n") + (let [site {:title "t" :description "d" :base-path "" :guide-dir guide + :output-dir (io/file (str tmp) "_site")}] + (core/generate! site) + (let [contributing (slurp (io/file (:output-dir site) "guide" "contributing.html"))] + (is (str/includes? contributing "Our own house rules.")) + (is (not (str/includes? contributing "Etiquette")))))))