Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
57 changes: 57 additions & 0 deletions resources/content/contributing.md
Original file line number Diff line number Diff line change
@@ -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.
84 changes: 65 additions & 19 deletions src/site/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>/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))
Expand All @@ -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."
Expand All @@ -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
Expand Down
47 changes: 41 additions & 6 deletions test/site/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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} "<pre class=\"mermaid\">x</pre>"))))

;; 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")))))))