")
43 |
44 | (let [output-path (str dir "/docs")
45 | javadoc-path (str dir "/api")
46 | resolve-apilink #(javadoc/resolve-link javadoc-path %)
47 | _ (dinodoc/generate {:inputs [{:path dir
48 | :output-path "."}]
49 | :output-path output-path
50 | :resolve-apilink #(some->> (resolve-apilink %) (str "api/"))})
51 | data (fsdata output-path)]
52 | (is (str/includes?
53 | (get-in data ["index.md"])
54 | "Link to method: [`demo.Greeter.greet`](pathname://./api/demo/Greeter.html#greet(java.lang.String))"))))))
55 |
--------------------------------------------------------------------------------
/examples/openapi/README.md:
--------------------------------------------------------------------------------
1 | # OpenAPI HTTP APIs
2 |
3 | HTTP API documentation specified by [OpenAPI](https://www.openapis.org/) can be rendered using the [docusaurus-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs) plugin.
4 |
5 | Examples:
6 |
7 | - [Petstore](https://dinodoc.pages.dev/examples/openapi/petstore) - classic OpenAPI example ([spec](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml))
8 | - [Museum](https://dinodoc.pages.dev/examples/openapi/museum) - newer OpenAPI example ([spec](https://github.com/Redocly/museum-openapi-example/blob/main/openapi.yaml))
9 |
10 | ### Plugin Configuration
11 |
12 | Follow the plugin [installation and configuraiton](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs?tab=readme-ov-file#installation-existing-docusaurus-site) instructions. As an example you can also refer to a [diff](https://github.com/dundalek/dinodoc/commit/e704c7decfbeda80145be4fd0007bec7436a3bb0) adding the plugin to an existing site.
13 |
14 | ### Reitit routes
15 |
16 | [Reitit](https://github.com/metosin/reitit) router library has [OpenAPI support](https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md) and is able to generate spec which can be rendered as documentation.
17 |
18 | Example:
19 |
20 | - [Reitit](https://dinodoc.pages.dev/examples/openapi/reitit) - based routes in [code](https://github.com/metosin/reitit/blob/7e00de835d33460c2e4b19c6aca4df452f869528/examples/openapi/src/example/server.clj#L31-L110) and extracted with a [script](https://github.com/dundalek/dinodoc/blob/main/examples/openapi/doc.clj).
21 |
22 | **⚠️ Warning**: Using `requiring-resolve` to access route data will result in code being evaluated, therefore it only needs to be run on trusted sources!
23 |
24 | ## Plugin comparison
25 |
26 | There are other plugins for Docusaurus that render OpenAPI spec. Here is a brief overview:
27 |
28 | - [docusaurus-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)
29 | - This is the one chosen in the examples above.
30 | - Good default look, shows HTTP method badges in the sidebar.
31 | - [redocusaurus](https://github.com/rohit-gohri/redocusaurus)
32 | - Wraps [redoc](https://github.com/Redocly/redoc) to be embedded inside Docusarus.
33 | - It seems to work well and supports many features, but it has a slightly different look from Docusaurus, it looks a bit out of place.
34 | - [docusaurus-openapi](https://github.com/cloud-annotations/docusaurus-openapi)
35 | - It looks a bit too plain compared to the other plugins
36 |
--------------------------------------------------------------------------------
/components/statecharts/test/src/dinodoc/statecharts/example_test.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.statecharts.example-test
2 | (:require
3 | [clojure.test :refer [deftest is testing]]
4 | [example.statecharts :as example :refer [machine]]
5 | [statecharts.core :as fsm]))
6 |
7 | ;; Running the machine itself to make sure the source representation is valid.
8 | (deftest example-machine
9 | (let [service (fsm/service machine)]
10 | (is (= {:_state :red} (fsm/start service)))
11 | (is (= {:_state :green} (fsm/send service :timer)))
12 | (is (= {:_state :yellow} (fsm/send service :timer)))
13 | (is (= "Firing the traffic cameras!\n"
14 | (with-out-str
15 | (is (= {:_state :red} (fsm/send service :timer))))))))
16 |
17 | (deftest regions-example
18 | (let [service (fsm/service example/regions)
19 | initial-state {:_state {:bc :B :de :D}}]
20 | (is (= initial-state (fsm/start service)))
21 | (testing "first flick event transitions both regions"
22 | (is (= {:_state {:bc :C :de :E}} (fsm/send service :flick))))
23 | (testing "second flick event only transitions :de region"
24 | (is (= {:_state {:bc :C :de :D}} (fsm/send service :flick))))
25 | (testing "final after-1s event transitions the :bc region back to initial state"
26 | (is (= initial-state (fsm/send service :after-1s))))))
27 |
28 | (deftest dog-walk-example
29 | (testing "go home when walking"
30 | (let [service (fsm/service example/dog-walk)]
31 | (is (= {:_state :waiting} (fsm/start service)))
32 | (is (= {:_state [:on-a-walk :walking]} (fsm/send service :leave-home)))
33 | (is (= {:_state :walk-complete} (fsm/send service :arrive-home)))))
34 |
35 | (testing "go home when running"
36 | (let [service (fsm/service example/dog-walk)]
37 | (is (= {:_state :waiting} (fsm/start service)))
38 | (is (= {:_state [:on-a-walk :walking]} (fsm/send service :leave-home)))
39 | (is (= {:_state [:on-a-walk :running]} (fsm/send service :speed-up)))
40 | (is (= {:_state :walk-complete} (fsm/send service :arrive-home)))))
41 |
42 | (testing "go home after walking-running-walking"
43 | (let [service (fsm/service example/dog-walk)]
44 | (is (= {:_state :waiting} (fsm/start service)))
45 | (is (= {:_state [:on-a-walk :walking]} (fsm/send service :leave-home)))
46 | (is (= {:_state [:on-a-walk :running]} (fsm/send service :speed-up)))
47 | (is (= {:_state [:on-a-walk :walking]} (fsm/send service :slow-down)))
48 | (is (= {:_state :walk-complete} (fsm/send service :arrive-home))))))
49 |
--------------------------------------------------------------------------------
/src/dinodoc/tbls.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.tbls
2 | (:require
3 | [babashka.fs :as fs]
4 | [clojure.string :as str]
5 | [dinodoc.generator :as generator]
6 | [dinodoc.impl.fs :refer [create-local-temp-dir]]
7 | [dinodoc.tbls.impl :as impl]
8 | [babashka.process :refer [shell]]))
9 |
10 | (deftype ^:private TblsGenerator [opts tmp-dir dbdoc-dir]
11 | generator/Generator
12 | (prepare-index [_]
13 | (let [{:keys [dsn]} opts]
14 | (try
15 | ;; tbls seems to have `docPath` option to use different output path than `dbdoc`.
16 | ;; But it seems to be only available when using yaml config but not passable via CLI,
17 | ;; so lets use a cwd insted.
18 | ;;
19 | ;; Because of that when using `sqlite:` the path must be absolute.
20 | ;; Could some auto handling or at least an assertion.
21 | (shell {:dir tmp-dir} "tbls" "doc" "--dsn" dsn "--rm-dist")
22 | ;; Uncomment to use Mermaid for diagrams:
23 | ; "--er-format" "mermaid")
24 |
25 | (catch java.io.IOException e
26 | (throw (ex-info "Something went wrong, please make sure to have `tbls` program installed." {} e))))))
27 | (resolve-link [_ target]
28 | (or (impl/resolve-link dbdoc-dir target)
29 | ;; This is a temporary hack to make multiple resolutions work for the dbschema example.
30 | ;; To be implemented in generic way in the future.
31 | (let [[prefix target] (str/split target #":" 2)]
32 | (when (= (:UNSTABLE_prefix opts) prefix)
33 | (impl/resolve-link dbdoc-dir target)))))
34 |
35 | (generate [_ {:keys [output-path]}]
36 | (let [{:keys [title]} opts]
37 | (fs/move dbdoc-dir output-path)
38 |
39 | (fs/update-file
40 | (str output-path "/README.md")
41 | (fn [content]
42 | (str "---\nsidebar_position: 0\n---\n\n"
43 | (cond-> content
44 | title (str/replace-first #"(?m)^# .*$" (str "# " title))))))
45 |
46 | ;; Strip out distracting tbls footer, credits are in the readme
47 | (doseq [file (fs/glob output-path "*.md")]
48 | (fs/update-file
49 | (fs/file file)
50 | str/replace "---\n\n> Generated by [tbls](https://github.com/k1LoW/tbls)" "")))))
51 |
52 | (defn make-generator
53 | "Options:
54 |
55 | - `:dsn` - Data Source Name aka connection string
56 | - `:title` - to override the inferred one (string, optional)"
57 | [opts]
58 | (let [tmp-dir (str (create-local-temp-dir))
59 | dbdoc-dir (str tmp-dir "/dbdoc")]
60 | (->TblsGenerator opts tmp-dir dbdoc-dir)))
61 |
--------------------------------------------------------------------------------
/website/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | - create an ordered group of docs
4 | - render a sidebar for each doc of that group
5 | - provide next/previous navigation
6 |
7 | The sidebars can be generated from the filesystem, or explicitly defined here.
8 |
9 | Create as many sidebars as you want.
10 | */
11 |
12 | // @ts-check
13 |
14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
15 | const sidebars = {
16 | // By default, Docusaurus generates a sidebar from the docs folder structure
17 | docSidebar: [{ type: 'autogenerated', dirName: 'docs' }],
18 | examplesPolylith: [{ type: 'autogenerated', dirName: 'examples/polylith' }],
19 | examplesPromesa: [{ type: 'autogenerated', dirName: 'examples/promesa' }],
20 | examplesReitit: [{ type: 'autogenerated', dirName: 'examples/reitit' }],
21 | examplesRing: [{ type: 'autogenerated', dirName: 'examples/ring' }],
22 | examplesStatecharts: [{ type: 'autogenerated', dirName: 'examples/statecharts' }],
23 | examplesStructurizr: [{ type: 'autogenerated', dirName: 'examples/structurizr' }],
24 | examplesTs: [{ type: 'autogenerated', dirName: 'examples/ts' }],
25 | dbschema: [{ type: 'autogenerated', dirName: 'examples/dbschema' }],
26 | contextmapper: [{ type: 'autogenerated', dirName: 'examples/contextmapper' }],
27 | contextive: [{ type: 'autogenerated', dirName: 'examples/contextive' }],
28 |
29 | kotlin: [{ type: 'autogenerated', dirName: 'examples/kotlin' }],
30 | java: [{ type: 'autogenerated', dirName: 'examples/java' }],
31 |
32 | openapi: [{
33 | type: "category",
34 | label: "Petstore",
35 | link: {
36 | type: "generated-index",
37 | slug: "/examples/openapi/petstore",
38 | },
39 | items: require("./docs/examples/openapi/petstore/sidebar.js"),
40 | }, {
41 | type: "category",
42 | label: "Museum",
43 | link: {
44 | type: "generated-index",
45 | slug: "/examples/openapi/museum",
46 | },
47 | items: require("./docs/examples/openapi/museum/sidebar.js"),
48 | }, {
49 | type: "category",
50 | label: "Reitit",
51 | link: {
52 | type: "generated-index",
53 | slug: "/examples/openapi/reitit",
54 | },
55 | items: require("./docs/examples/openapi/reitit/sidebar.js"),
56 | }],
57 |
58 | // But you can create a sidebar manually
59 | /*
60 | torialSidebar: [
61 | 'intro',
62 | 'hello',
63 |
64 | type: 'category',
65 | label: 'Tutorial',
66 | items: ['tutorial-basics/create-a-document'],
67 | },
68 | ,
69 | */
70 | };
71 |
72 | module.exports = sidebars;
73 |
--------------------------------------------------------------------------------
/experiments/stack-graphs/src/dinodoc/stack_graphs/main.clj:
--------------------------------------------------------------------------------
1 | (ns stack-graphs.main
2 | (:require
3 | [cheshire.core :as json]
4 | [clojure.java.shell :as sh]
5 | [loom.graph :as g]
6 | [loom.io :as lio]))
7 |
8 | (def graph (json/parse-string (slurp "target/graph.json") true))
9 |
10 | (def nodes
11 | (->> graph
12 | :nodes
13 | (map (fn [node]
14 | [(-> node :id :local_id) node]))
15 | (into {})))
16 |
17 | (def g
18 | (apply g/digraph
19 | (->> graph
20 | :edges
21 | (map (fn [{:keys [source sink]}]
22 | [(:local_id source) (:local_id sink)])))))
23 |
24 | ;; Visualize using graphviz
25 | (do
26 | (spit "target/graph.dot"
27 | (lio/dot-str g
28 | {:node-label (fn [id]
29 | (let [node (get nodes id)
30 | {:keys [syntax_type]} (:source_info node)]
31 | (cond-> (format "%s %d %s" (:type node) id (:symbol node))
32 | syntax_type (str " (" syntax_type ")"))))}))
33 | (sh/sh "dot" "-Tpng" "target/graph.dot" "-o" "target/graph.png"))
34 |
35 | ;; Nodes
36 |
37 | (->> graph
38 | :nodes
39 | (map :type)
40 | (frequencies))
41 |
42 | {"root" 1,
43 | "jump_to_scope" 1,
44 | "scope" 510,
45 | "push_symbol" 85,
46 | "pop_symbol" 58,
47 | "push_scoped_symbol" 7}
48 |
49 | (->> graph
50 | :nodes
51 | (count))
52 |
53 | (->> graph
54 | :nodes
55 | (filter (comp #{"root"} :type)))
56 |
57 | (g/out-edges g 1)
58 | (g/in-edges g 1)
59 |
60 | (->> graph
61 | :nodes
62 | (mapcat keys)
63 | (frequencies))
64 |
65 | {:is_exported 510,
66 | :symbol 150,
67 | :type 662,
68 | :scope 7,
69 | :debug_info 662,
70 | :is_definition 58,
71 | :id 662,
72 | :is_reference 92,
73 | :source_info 644}
74 |
75 | (->> graph
76 | :nodes
77 | (filter :is_exported))
78 |
79 | (->> graph
80 | :nodes
81 | (filter :is_definition)
82 | (map #(-> %
83 | (dissoc :debug_info)
84 | (update :source_info dissoc :span)
85 | (update :id dissoc :file))))
86 |
87 | (->> graph
88 | :nodes
89 | (filter #(-> % :source_info :syntax_type)))
90 |
91 | ;; Edges
92 |
93 | (->> graph
94 | :edges
95 | (mapcat keys)
96 | (frequencies))
97 |
98 | {:source 450, :sink 450, :precedence 450, :debug_info 450}
99 |
100 | (->> graph
101 | :edges
102 | (first))
103 |
--------------------------------------------------------------------------------
/components/antora/src/dinodoc/antora/impl.clj:
--------------------------------------------------------------------------------
1 | (ns ^:no-doc dinodoc.antora.impl
2 | (:require
3 | [babashka.fs :as fs]
4 | [clojure.java.shell :as shell]
5 | [clojure.string :as str]))
6 |
7 | (defn generate-navigation-items [path base-path level]
8 | (->> (fs/list-dir path)
9 | (sort-by fs/file-name)
10 | (mapcat (fn [f]
11 | (cond
12 | (fs/directory? f)
13 | (let [[child other] (fs/glob f "*.adoc")]
14 | (if (and (nil? other) (= (some-> child fs/file-name) "index.adoc"))
15 | (generate-navigation-items f base-path level)
16 | (cons (str level " " (fs/file-name f))
17 | (generate-navigation-items f base-path (str level "*")))))
18 |
19 | (= (fs/extension f) "adoc")
20 | [(str level
21 | " xref:" (str/replace (str f) base-path "")
22 | "[]")])))))
23 |
24 | (defn- replace-md-adoc-links [input]
25 | ;; naive regex implementation, it would be better to replace over AST
26 | (str/replace input #"\]\(([^)]+)\)"
27 | (fn [[_ link]]
28 | (let [replacement (cond-> link
29 | (not (str/includes? link "://"))
30 | (str/replace #"\.md$" ".adoc"))]
31 | (str "](" replacement ")")))))
32 |
33 | (defn- replace-heading-ids [input]
34 | ;; Post-processing to use the `[#id]` syntax instead of legacy `[[id]]`,
35 | ;; otherwise asciidoctor would not parse ids starting with dash `-`.
36 | ;;
37 | ;; Pandoc hardcodes `[[` in output: https://github.com/jgm/pandoc/blob/c29c18a0038b62718cf3133247177e3cb8ebf871/src/Text/Pandoc/Writers/AsciiDoc.hs#L200
38 | ;; AsciiDoctor checks `BlockAnchorRx` regex https://github.com/asciidoctor/asciidoctor/blob/935a0a3a2fe05ba7d239d8e774ada20df5879599/lib/asciidoctor/parser.rb#L2051
39 | ;; which cannot start with alphanum or underscore: https://github.com/asciidoctor/asciidoctor/blob/935a0a3a2fe05ba7d239d8e774ada20df5879599/lib/asciidoctor/rx.rb#L165
40 | (str/replace input #"(?m)^\[\[(.*)\]\]$" "[#$1]"))
41 |
42 | (defn md->adoc [input]
43 | (-> (shell/sh "pandoc" "--from" "markdown"
44 | ;; Disabling `auto_identifiers` feature so that explicit heading ids are used to make links to vars work properly.
45 | "--to" "asciidoc-auto_identifiers"
46 | "--shift-heading-level-by=-1"
47 | "--standalone"
48 | :in (replace-md-adoc-links input))
49 | :out
50 | replace-heading-ids))
51 |
--------------------------------------------------------------------------------
/components/dokka/src/dinodoc/dokka.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.dokka
2 | (:require
3 | [babashka.fs :as fs]
4 | [cheshire.core :as json]
5 | [clojure.java.io :as io]
6 | [clojure.string :as str])
7 | (:import
8 | (kotlinx.coroutines Dispatchers)
9 | (org.jetbrains.dokka MainKt)))
10 |
11 | ; args ["-sourceSet" "-src src/main"
12 | ; "-outputDir" "docs"]
13 | (defn- dokka-main [args]
14 | (MainKt/main (into-array String args)))
15 |
16 | ;; Port of MainKt/main as an entrypoint for potential customization
17 | #_(defn dokka-main [args]
18 | (let [globalArguments (GlobalArguments. (into-array String args))
19 | configuration (MainKt/initializeConfiguration globalArguments)]
20 | (.generate (DokkaGenerator. configuration (.getLogger globalArguments)))))
21 |
22 | ;; By default Dokka finalizes coroutines when it finishes generating which shuts down all threads.
23 | ;; We don't want that because we need to run fixups on the output afterwards.
24 | ;; There is a `finalizeCoroutines` option which can be set to false, but it is not exposed via CLI.
25 | ;; We cannot modify configuration objects because they have immutable fields and only way to modify them is via Kotlin Scope functions. However, I am not aware of a way to use scoped functions from Clojure.
26 | ;; As a workaround, we can use JSON config to pass the `finalizeCoroutines` option. The downside is we need to do it via a temporary file on disk.
27 | (defn- dokka [opts]
28 | (let [config-file (str (fs/create-temp-file))]
29 | (with-open [writer (io/writer config-file)]
30 | (json/generate-stream opts writer))
31 | (try
32 | (dokka-main [config-file])
33 | (finally
34 | (fs/delete config-file)))))
35 |
36 | (defn- finalize-coroutines []
37 | (.shutdown Dispatchers/INSTANCE))
38 |
39 | (defn generate [{:keys [source-paths output-path]}]
40 | (let [;; Options reference: https://kotlinlang.org/docs/dokka-cli.html#json-configuration
41 | opts {:finalizeCoroutines false
42 | :outputDir output-path
43 | :sourceSets [{:sourceSetID {:scopeId "moduleName"
44 | :sourceSetName "main"}
45 | :sourceRoots source-paths}]}]
46 | (fs/delete-tree output-path)
47 | (fs/create-dirs output-path)
48 | (dokka opts)
49 | ;; Replace
to make output compatible with MDX
50 | ;; https://github.com/Kotlin/dokka/issues/1616
51 | (doseq [f (fs/glob output-path "**.md")]
52 | (fs/update-file (fs/file f) str/replace "
" "
"))
53 | ;; Finalize coroutines manually since we set finalizeCoroutines to false
54 | (finalize-coroutines)))
55 |
--------------------------------------------------------------------------------
/test/output/docs/api/codox/example/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_label: example
3 | title: codox.example
4 | toc_min_heading_level: 2
5 | toc_max_heading_level: 4
6 | custom_edit_url:
7 | ---
8 |
9 | ClojureScript version.
10 |
11 | *deprecated in 2.0 | added in 1.1*
12 |
13 |
14 |
15 |
16 |
17 | ### \*conn\* {#-STAR-conn-STAR-}
18 |
19 |
20 | A dynamic var.
21 |
22 | *dynamic*
23 |
24 |
25 | ### \->CljsRecord {#--GT-CljsRecord}
26 | ``` clojure
27 | (->CljsRecord aah choo)
28 | ```
29 |
30 |
31 | ### CljsRecord {#CljsRecord}
32 |
33 |
34 | ### Foop {#Foop}
35 |
36 |
37 | An example protocol.
38 |
39 | *protocol*
40 |
41 |
42 | #### barp {#barp}
43 | ``` clojure
44 | (barp x y)
45 | ```
46 |
47 |
48 | Another protocol function.
49 |
50 | *protocol | deprecated*
51 |
52 |
53 | #### foop {#foop}
54 | ``` clojure
55 | (foop x)
56 | ```
57 |
58 |
59 | A protocol function belonging to the protocol Foom.
60 |
61 | *protocol*
62 |
63 |
64 | ### bar {#bar}
65 | ``` clojure
66 | (bar x & body)
67 | ```
68 |
69 |
70 | This is an example macro.
71 |
72 | *macro*
73 |
74 |
75 | ### baz {#baz}
76 |
77 |
78 | This is an example var.
79 |
80 | ### foo {#foo}
81 | ``` clojure
82 | (foo x)
83 | (foo x y & z)
84 | ```
85 |
86 |
87 | This is an example function.
88 |
89 | ### foobar {#foobar}
90 | ``` clojure
91 | (foobar x)
92 | ```
93 |
94 |
95 | An obsolete function.
96 |
97 | *deprecated*
98 |
99 |
100 | ### foobaz {#foobaz}
101 | ``` clojure
102 | (foobaz x)
103 | ```
104 |
105 |
106 | An obsolete function with a specific version.
107 |
108 | *deprecated in 1.1*
109 |
110 |
111 | ### foom {#foom}
112 | ``` clojure
113 | (foom x)
114 | ```
115 |
116 |
117 | An example multimethod.
118 |
119 | ### map\->CljsRecord {#map--GT-CljsRecord}
120 | ``` clojure
121 | (map->CljsRecord m)
122 | ```
123 |
124 |
125 | ### markbar {#markbar}
126 | ``` clojure
127 | (markbar x)
128 | ```
129 |
130 |
131 | See [`foo`](#foo), and also [[example2/bar]].
132 |
133 | ### markfoo {#markfoo}
134 | ``` clojure
135 | (markfoo x)
136 | ```
137 |
138 |
139 | A docstring that selectively uses **markdown**.
140 |
141 | ### quz {#quz}
142 | ``` clojure
143 | (quz x)
144 | ```
145 |
146 |
147 | Another example function.
148 |
149 | *added in 1.1*
150 |
151 |
152 | ### quzbar {#quzbar}
153 | ``` clojure
154 | (quzbar x)
155 | ```
156 |
157 |
158 | A function with a lifespan.
159 |
160 | *deprecated in 1.1 | added in 1.0*
161 |
162 |
163 | ### quzquz {#quzquz}
164 | ``` clojure
165 | (quzquz x)
166 | ```
167 |
168 |
169 | This is a ClojureScript-only function.
170 |
171 | ### zoo? {#zoo-QMARK-}
172 | ``` clojure
173 | (zoo? x)
174 | ```
175 |
176 |
177 | An example predicate.
178 |
--------------------------------------------------------------------------------
/components/structurizr/README.md:
--------------------------------------------------------------------------------
1 | # Structurizr Architecture docs
2 |
3 | Render architecture documentation with diagrams based on the [C4 model](https://c4model.com/) expressed in [Structurizr DSL](https://structurizr.com/).
4 |
5 | Examples:
6 |
7 | - [Big Bank plc](https://dinodoc.pages.dev/examples/structurizr/Big%20Bank%20plc-0/)
8 | - [Financial Risk System](https://dinodoc.pages.dev/examples/structurizr/Financial%20Risk%20System-0/)
9 |
10 | Use [[dinodoc.structurizr/generate]] with a path to Structurizr workspace:
11 |
12 | ```clojure
13 | (require '[dinodoc.structurizr :as structurizr]))
14 |
15 | (structurizr/generate
16 | {:workspace-file "some/path/workspace.dsl"
17 | :output-path "docs"})
18 | ```
19 |
20 | The [mermaid plugin](https://docusaurus.io/docs/markdown-features/diagrams) needs to be added to render diagrams.
21 |
22 | ## Rendered structure
23 |
24 | - At the top-level we render a workspace with a list of systems and a system landscape diagram if is defined.
25 | - Elements are nested in a sidebar hierarchy: workspace -> system -> container -> component
26 | - On element page we show diagrams containing given element and also any associated deployment nodes.
27 | - Diagrams are rendered using Mermaid and are clickable to navigate to element pages.
28 |
29 | ### Considerations
30 |
31 | - IDs
32 | Structurizr DSL uses human-specified IDs to refer to elements, but these are not available programmatically in the model.
33 | Instead auto-generated integer IDs are available.
34 | We want to have human-readable URL slugs therefore element names are used, but those are not guaranteed to be unique.
35 | To make sure there are no conflicts, IDs are appended to names like `Internet%20Banking%20System-7`.
36 | The concern is that these auto-ids might not be stable, which would make the URLs potentially unstable.
37 | In the future we should probably add custom slugification with disambiguation to avoid conflicts.
38 | - Currently [people](https://docs.structurizr.com/dsl/language#person) are only rendered in diagrams. Would it be useful to render a list of users? What additional info would be useful to render for each user?
39 | - Deployment diagrams are displayed on system element pages. However, deployment nodes can be organized in `deploymentNodes` hierarchy. Would it be useful to render it somehow?
40 | - Model includes [tags](https://docs.structurizr.com/dsl/language#tags).
41 | We might render them on element page.
42 | Also could perhaps render a page that lists elements grouped by tags.
43 | However, this sounds like something that would be better served using a dynamic UI instead of a static one.
44 | - Are [groups](https://docs.structurizr.com/dsl/language#group) just for visual appearance in diagrams or would it make sense to take groups into account when rendering the structure?
45 |
--------------------------------------------------------------------------------
/src/dinodoc/javadoc.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.javadoc
2 | (:require
3 | [babashka.fs :as fs]
4 | [babashka.process :refer [shell]]
5 | [clojure.string :as str]
6 | [dinodoc.generator :as generator]
7 | [dinodoc.impl.fs :refer [create-local-temp-dir]]
8 | [hickory.core :as h]
9 | [hickory.select :as hs]))
10 |
11 | (defn- segments->path [segments]
12 | (str (str/join "/" segments)
13 | ".html"))
14 |
15 | (defn- file->id-map [file]
16 | (let [tree (-> (slurp file)
17 | h/parse h/as-hickory)]
18 | (->> (hs/select (hs/attr :id) tree)
19 | (map (comp :id :attrs))
20 | (keep (fn [html-id]
21 | (when-not (str/includes? html-id "-")
22 | (if-some [[match id] (re-matches #"(.*)\(.*\)" html-id)]
23 | [id match]
24 | [html-id html-id]))))
25 | (into {}))))
26 |
27 | (defn resolve-link [javadoc-path definition]
28 | (let [segments (str/split definition #"\.")
29 | target (segments->path segments)]
30 | (if (fs/exists? (fs/file javadoc-path target))
31 | target
32 | (let [target (segments->path (butlast segments))
33 | target-file (fs/file javadoc-path target)]
34 | (when (fs/exists? target-file)
35 | ;; Possible optimization: could cache/memoize by file
36 | (let [id-map (file->id-map target-file)]
37 | (when-some [anchor (get id-map (last segments))]
38 | (str target "#" anchor))))))))
39 |
40 | (comment
41 | (def tree (-> (h/parse "foo")
42 | (h/as-hickory)))
43 |
44 | (def tree (-> (slurp "examples/javadoc/static/examples/javadoc/api/demo/Greeter.html")
45 | h/parse h/as-hickory))
46 |
47 | (->> (hs/select (hs/attr :id) tree)
48 | (map (fn [{:keys [tag attrs]}]
49 | [tag (:id attrs)])))
50 |
51 | (->> (hs/select (hs/attr :id) tree)
52 | (map (comp :id :attrs))
53 | (keep (fn [html-id]
54 | (when-not (str/includes? html-id "-")
55 | (if-some [[match id] (re-matches #"(.*)\(.*\)" html-id)]
56 | [id match]
57 | [html-id html-id]))))
58 | (into {})))
59 |
60 | (deftype ^:private JavadocGenerator [opts tmp-dir]
61 | generator/Generator
62 | (prepare-index [_]
63 | (let [{:keys [sourcepath subpackages]} opts]
64 | (shell "javadoc -sourcepath" sourcepath "-subpackages" subpackages "-d" tmp-dir)))
65 | (resolve-link [_ target]
66 | (resolve-link tmp-dir target))
67 | (generate [_ {:keys [output-path]}]
68 | (fs/move tmp-dir output-path)))
69 |
70 | (defn make-generator
71 | "Options:
72 |
73 | - `:sourcepath` - path source files (javadoc `-sourcepath` option)
74 | - `:subpackages` - subpackages to include (javadoc `-subpackages` option)"
75 | [opts]
76 | (->JavadocGenerator opts (str (create-local-temp-dir))))
77 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:paths ["script"]
2 | :tasks {:requires [[build-examples]]
3 |
4 | test {:task (clojure "-M:test" #_#_"--plugin" "notifier" "--watch")}
5 | test:once {:task (clojure "-M:test")}
6 | test:ci {:task (clojure "-M:test" "--skip-meta" "skip-ci")}
7 | test:coverage {:task (clojure "-M:test:coverage")}
8 | test:init-submodules (shell "git submodule update --init test/projects/codox test/projects/promesa examples/structurizr/examples")
9 |
10 | website {:depends [website:content website:build example:antora]}
11 | website:start {:task (shell {:dir "website"} "bun start")}
12 | website:build {:task (shell {:dir "website"} "bun run build")}
13 | website:docs {:task (build-examples/build-dinodoc-docs)}
14 | website:content
15 | {:depends
16 | [website:docs
17 | example:polylith
18 | example:promesa
19 | example:reitit
20 | example:ring
21 | example:structurizr
22 | example:statecharts
23 | example:openapi
24 | example:dbschema
25 | example:kotlin
26 | example:java
27 | example:javadoc
28 | example:rust
29 | example:contextmapper
30 | example:contextive]}
31 |
32 | ;; Debug test samples at http://localhost:3000/docs/samples/example/
33 | samples:copy {:task (build-examples/copy-test-samples)}
34 |
35 | example:polylith {:task (build-examples/build-example-docs "polylith")}
36 | example:promesa {:task (build-examples/build-example-docs "promesa")}
37 | example:reitit {:task (build-examples/build-example-docs "reitit")}
38 | example:ring {:task (build-examples/build-example-docs "ring")}
39 | example:structurizr {:task (build-examples/build-example-docs "structurizr")}
40 | example:statecharts {:task (build-examples/build-example-docs "statecharts")}
41 | example:dbschema {:task (build-examples/build-example-docs "dbschema")}
42 |
43 | example:kotlin {:task (build-examples/build-example-docs "kotlin")}
44 | example:java {:task (build-examples/build-example-docs "java")}
45 | example:javadoc {:task (build-examples/build-example-docs "javadoc")}
46 | example:rust {:task (build-examples/build-example-docs "rust")}
47 | example:contextmapper {:task (build-examples/build-example-docs "contextmapper")}
48 | example:contextive {:task (build-examples/build-example-docs "contextive")}
49 |
50 | example:openapi:reitit {:task (shell {:dir "examples/openapi"} "clojure -M:doc")}
51 | example:openapi {:depends [example:openapi:reitit]
52 | :task (shell {:dir "website"} "bun run docusaurus gen-api-docs all")}
53 |
54 | example:antora {:task (do
55 | (shell {:dir "examples/antora"} "bb generate")
56 | (build-examples/copy-antora-example))}
57 |
58 | deploy {:depends [website]
59 | :task (shell "bunx wrangler pages deploy --project-name dinodoc website/build")}}}
60 |
--------------------------------------------------------------------------------
/components/antora/test/src/dinodoc/antora_test.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.antora-test
2 | (:require
3 | [clojure.test :refer [deftest is testing]]
4 | [dinodoc.antora :as antora]
5 | [dinodoc.antora.impl :as impl]
6 | [dinodoc.fs-helpers :as fsh :refer [fsdata with-temp-dir]]))
7 |
8 | (deftest generate-navigation-basic
9 | (with-temp-dir
10 | (fn [{:keys [dir fspit]}]
11 | (fspit "b.adoc" "")
12 | (fspit "a.adoc" "")
13 | (is (= "* xref:a.adoc[]\n* xref:b.adoc[]"
14 | (antora/generate-navigation dir))))))
15 |
16 | (deftest generate-navigation-ignored-files
17 | (with-temp-dir
18 | (fn [{:keys [dir fspit]}]
19 | (fspit "a.adoc" "")
20 | (fspit "b.txt" "")
21 | (testing "ignores non-adoc files"
22 | (is (= "* xref:a.adoc[]"
23 | (antora/generate-navigation dir)))))))
24 |
25 | (deftest generate-navigation-collapse-index
26 | (with-temp-dir
27 | (fn [{:keys [dir fspit]}]
28 | (fspit "a/index.adoc" "")
29 | (testing "collapses directory with only one index file into single entry"
30 | (is (= "* xref:a/index.adoc[]"
31 | (antora/generate-navigation dir))))))
32 | (with-temp-dir
33 | (fn [{:keys [dir fspit]}]
34 | (fspit "a/index.adoc" "")
35 | (fspit "a/index.md" "")
36 | (testing "works the same with extra index md file"
37 | (is (= "* xref:a/index.adoc[]"
38 | (antora/generate-navigation dir)))))))
39 |
40 | (deftest generate-navigation-nested
41 | (with-temp-dir
42 | (fn [{:keys [dir fspit]}]
43 | (fspit "a.adoc" "")
44 | (fspit "b/b1.adoc" "")
45 | (fspit "b/b2.adoc" "")
46 | (fspit "c.adoc" "")
47 | (is (= "* xref:a.adoc[]\n* b\n** xref:b/b1.adoc[]\n** xref:b/b2.adoc[]\n* xref:c.adoc[]"
48 | (antora/generate-navigation dir))))))
49 |
50 | (deftest md->adoc-test
51 | (is (= "= h1\n\n" (impl/md->adoc "# h1\n\n")))
52 | (is (= "link:example.com[Example]\n" (impl/md->adoc "[Example](example.com)\n")))
53 |
54 | ;; it would be better if the output did not includ heading id `[#h2]` when not explicitly defined
55 | (is (= "= h1\n\n[#h2]\n== h2\n" (impl/md->adoc "# h1\n\n## h2\n")))
56 | (is (= "[#h2]\n== h2\n" (impl/md->adoc "## h2\n"))))
57 |
58 | (deftest md->adoc-test-heading-ids
59 | (is (= "[#greet]\n== greet\n" (impl/md->adoc "## greet {#greet}"))))
60 |
61 | (deftest ^{:skip-ci "current version of pandoc is 3.1, pandoc on ubuntu 22.04 in CI is 2.9 which is old and works differently"}
62 | md->adoc-test-heading-ids-special
63 | (is (= "[#-main]\n== -main\n" (impl/md->adoc "## -main {#-main}"))))
64 |
65 | (deftest md->adoc-md-links-fixup
66 | (is (= "link:foo.adoc[]\n" (impl/md->adoc "[](foo.md)")))
67 | (is (= "https://example/foo.md[]\n" (impl/md->adoc "[](https://example/foo.md)"))))
68 |
69 | (deftest transform-directory
70 | (with-temp-dir
71 | (fn [{:keys [dir fspit]}]
72 | (let [foo-content "## foo {#foo}"]
73 | (fspit "foo.md" foo-content)
74 | (antora/transform-directory dir)
75 | (is (= {"foo.md" foo-content
76 | "foo.adoc" "[#foo]\n== foo\n"}
77 | (fsdata dir)))))))
78 |
--------------------------------------------------------------------------------
/examples/openapi/reitit.json:
--------------------------------------------------------------------------------
1 | {"openapi":"3.1.0","x-id":["reitit.openapi/default"],"info":{"title":"my-api","description":"openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring","version":"0.0.1"},"components":{"securitySchemes":{"auth":{"type":"apiKey","in":"header","name":"Example-Api-Key"}}},"paths":{"/pizza":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"color":{"type":"string"},"pineapple":{"type":"boolean"}},"required":["color","pineapple"],"additionalProperties":false},"description":"Fetch a pizza as json","examples":{"white":{"description":"White pizza with pineapple","value":{"color":"white","pineapple":true}},"red":{"description":"Red pizza","value":{"color":"red","pineapple":false}}}},"application/edn":{"schema":{"type":"object","properties":{"color":{"type":"string"},"pineapple":{"type":"boolean"}},"required":["color","pineapple"],"additionalProperties":false},"description":"Fetch a pizza as edn","examples":{"red":{"description":"Red pizza with pineapple","value":"{:color :red, :pineapple true}"}}}}}},"summary":"Fetch a pizza | Multiple content-types, multiple examples"},"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"color":{"type":"string"},"pineapple":{"type":"boolean"}},"required":["color","pineapple"],"additionalProperties":false},"description":"Create a pizza using json","examples":{"purple":{"value":{"color":"purple","pineapple":false}}}},"application/edn":{"schema":{"type":"object","properties":{"color":{"type":"string"},"pineapple":{"type":"boolean"}},"required":["color","pineapple"],"additionalProperties":false},"description":"Create a pizza using EDN","examples":{"purple":{"value":"{:color :purple, :pineapple false}"}}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"],"additionalProperties":false},"description":"Success"},"application/edn":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"],"additionalProperties":false},"description":"Success"}}}},"summary":"Create a pizza | Multiple content-types, multiple examples"}},"/contact":{"get":{"parameters":[{"in":"query","name":"limit","required":false,"schema":{"title":"How many results to return? Optional.","type":"integer","default":30,"example":10}},{"in":"query","name":"email","required":true,"schema":{"title":"Email address to search for","type":"string","format":"email"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"Heidi"},"email":{"type":"string","example":"heidi@alps.ch"}},"required":["name","email"],"additionalProperties":false}}}}}},"summary":"Search for a contact | Customizing via malli properties"}},"/secure/get":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string"}},"required":["secret"],"additionalProperties":false}}}},"401":{"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}},"tags":["secure"],"summary":"endpoint authenticated with a header","security":[{"auth":[]}]}}}}
--------------------------------------------------------------------------------
/src/dinodoc/contextive.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.contextive
2 | (:require
3 | [babashka.fs :as fs]
4 | [clj-yaml.core :as yaml]
5 | [clojure.java.io :as io]
6 | [dinodoc.generator :as generator]
7 | [slugify.core :refer [slugify]]))
8 |
9 | (deftype ^:private ContextiveGenerator [opts ^:volatile-mutable bounded-contexts ^:volatile-mutable index]
10 | generator/Generator
11 | (prepare-index [_]
12 | (let [{:keys [definitions-file]} opts
13 | {:keys [contexts]} (yaml/parse-string (slurp definitions-file))]
14 | (set! bounded-contexts contexts)
15 | (set! index
16 | (->> contexts
17 | (reduce (fn [m {:keys [terms] context-name :name}]
18 | (reduce (fn [m {term-name :name}]
19 | (let [target (str context-name "#" (slugify term-name))]
20 | (-> m
21 | (assoc term-name target)
22 | (assoc (str context-name ":" term-name) target))))
23 | m terms))
24 | {})))))
25 | (resolve-link [_ target]
26 | (get index target))
27 | (generate [_ {:keys [output-path]}]
28 | (let [path (str output-path "/index.md")]
29 |
30 | (fs/create-dirs (fs/parent path))
31 | (with-open [out (io/writer path)]
32 | (binding [*out* out]
33 | (doseq [{context-name :name} bounded-contexts]
34 | (println "# Bounded Contexts")
35 | (println)
36 | (println (str "- [" context-name "](" context-name "/)")))))
37 |
38 | (doseq [{:keys [terms domainVisionStatement] context-name :name} bounded-contexts]
39 | (assert (string? context-name))
40 | (let [path (str output-path "/" context-name "/index.md")]
41 | (fs/create-dirs (fs/parent path))
42 | (with-open [out (io/writer path)]
43 | (binding [*out* out]
44 | (println (str "# " context-name))
45 | (println)
46 | (println domainVisionStatement)
47 | (doseq [{:keys [definition examples] term-name :name} terms]
48 | (assert (string? term-name))
49 | (println)
50 | (println "##" term-name (str "{#" (slugify term-name) "}"))
51 | (when definition
52 | (println)
53 | (println definition))
54 | (when (seq examples)
55 | (println)
56 | (println "Examples:")
57 | (println)
58 | (doseq [example examples]
59 | (println "-" example)))))))))))
60 |
61 | (defn make-generator
62 | "Options:
63 |
64 | - `:definitions-file` - path to .contextive/definitions.yml file"
65 | [opts]
66 | (->ContextiveGenerator opts nil nil))
67 |
68 | (comment
69 | (def definitions
70 | (yaml/parse-string (slurp "examples/contextive/.contextive/definitions.yml")))
71 |
72 | (->> (:contexts definitions)
73 | first
74 | :terms
75 | first)
76 |
77 | (doto (make-generator {:definitions-file "examples/contextive/.contextive/definitions.yml"})
78 | (generator/prepare-index)
79 | (generator/generate {:output-path "examples/contextive/docs"})))
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "test/projects/codox"]
2 | path = test/projects/codox
3 | url = https://github.com/weavejester/codox
4 | [submodule "test/projects/promesa"]
5 | path = test/projects/promesa
6 | url = https://github.com/funcool/promesa
7 | [submodule "examples/ring/projects/ring"]
8 | path = examples/ring/projects/ring
9 | url = https://github.com/ring-clojure/ring
10 | [submodule "examples/ring/projects/ring-accept"]
11 | path = examples/ring/projects/ring-accept
12 | url = https://github.com/ring-clojure/ring-accept
13 | [submodule "examples/ring/projects/ring-anti-forgery"]
14 | path = examples/ring/projects/ring-anti-forgery
15 | url = https://github.com/ring-clojure/ring-anti-forgery
16 | [submodule "examples/ring/projects/ring-codec"]
17 | path = examples/ring/projects/ring-codec
18 | url = https://github.com/ring-clojure/ring-codec
19 | [submodule "examples/ring/projects/ring-cps"]
20 | path = examples/ring/projects/ring-cps
21 | url = https://github.com/ring-clojure/ring-cps
22 | [submodule "examples/ring/projects/ring-defaults"]
23 | path = examples/ring/projects/ring-defaults
24 | url = https://github.com/ring-clojure/ring-defaults
25 | [submodule "examples/ring/projects/ring-headers"]
26 | path = examples/ring/projects/ring-headers
27 | url = https://github.com/ring-clojure/ring-headers
28 | [submodule "examples/ring/projects/ring-json"]
29 | path = examples/ring/projects/ring-json
30 | url = https://github.com/ring-clojure/ring-json
31 | [submodule "examples/ring/projects/ring-mock"]
32 | path = examples/ring/projects/ring-mock
33 | url = https://github.com/ring-clojure/ring-mock
34 | [submodule "examples/ring/projects/ring-session-timeout"]
35 | path = examples/ring/projects/ring-session-timeout
36 | url = https://github.com/ring-clojure/ring-session-timeout
37 | [submodule "examples/ring/projects/ring-spec"]
38 | path = examples/ring/projects/ring-spec
39 | url = https://github.com/ring-clojure/ring-spec
40 | [submodule "examples/ring/projects/ring-ssl"]
41 | path = examples/ring/projects/ring-ssl
42 | url = https://github.com/ring-clojure/ring-ssl
43 | [submodule "examples/ring/projects/ring-websocket-async"]
44 | path = examples/ring/projects/ring-websocket-async
45 | url = https://github.com/ring-clojure/ring-websocket-async
46 | [submodule "examples/ring/wiki"]
47 | path = examples/ring/wiki
48 | url = https://github.com/ring-clojure/ring.wiki.git
49 | [submodule "examples/polylith/polylith"]
50 | path = examples/polylith/polylith
51 | url = https://github.com/polyfy/polylith
52 | [submodule "examples/promesa/promesa"]
53 | path = examples/promesa/promesa
54 | url = https://github.com/funcool/promesa
55 | [submodule "examples/reitit/reitit"]
56 | path = examples/reitit/reitit
57 | url = https://github.com/metosin/reitit
58 | [submodule "examples/structurizr/examples"]
59 | path = examples/structurizr/examples
60 | url = https://github.com/structurizr/examples
61 | [submodule "examples/openapi/petstore"]
62 | path = examples/openapi/petstore
63 | url = https://github.com/swagger-api/swagger-petstore
64 | [submodule "examples/openapi/museum"]
65 | path = examples/openapi/museum
66 | url = https://github.com/Redocly/museum-openapi-example
67 | [submodule "examples/dbschema/chinook"]
68 | path = examples/dbschema/chinook
69 | url = https://github.com/lerocha/chinook-database
70 | [submodule "examples/dbschema/pagila"]
71 | path = examples/dbschema/pagila
72 | url = https://github.com/devrimgunduz/pagila
73 |
--------------------------------------------------------------------------------
/test/src/dinodoc/impl/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.impl.core-test
2 | (:require
3 | [clojure.test :refer [deftest is]]
4 | [dinodoc.impl.core :as impl]))
5 |
6 | (deftest path-to-root-test
7 | (is (= "." (impl/path-to-root "/controllers.md")))
8 | (is (= ".." (impl/path-to-root "/frontend/controllers.md")))
9 | (is (= "../.." (impl/path-to-root "/frontend/foo/controllers.md")))
10 |
11 | (is (= "." (impl/path-to-root "controllers.md")))
12 | (is (= ".." (impl/path-to-root "frontend/controllers.md")))
13 | (is (= "../.." (impl/path-to-root "frontend/foo/controllers.md"))))
14 |
15 | (deftest replace-links-test
16 | (is (= "[title](b.md)"
17 | (impl/replace-links "[title](b.md)" {:source "a.md"
18 | :link-map {"a.md" "a.md"
19 | "b.md" "b.md"}})))
20 | (is (= "[title](b.md)"
21 | (impl/replace-links "[title](./b.md)" {:source "a.md"
22 | :link-map {"a.md" "a.md"
23 | "b.md" "b.md"}})))
24 | (is (= "[title](b.md)"
25 | (impl/replace-links "[title](b.md)" {:source "a.md"
26 | :link-map {"a.md" "a.md"}})))
27 | (is (= "[title](../b.md)"
28 | (impl/replace-links "[title](b.md)" {:source "a.md"
29 | :link-map {"a.md" "foo/aa.md"
30 | "b.md" "b.md"}})))
31 | (is (= "[title](bar/bb.md)"
32 | (impl/replace-links "[title](b.md)" {:source "a.md"
33 | :link-map {"a.md" "a.md"
34 | "b.md" "bar/bb.md"}})))
35 | (is (= "[title](../bar/bb.md)"
36 | (impl/replace-links "[title](b.md)" {:source "a.md"
37 | :link-map {"a.md" "foo/aa.md"
38 | "b.md" "bar/bb.md"}})))
39 |
40 | (is (= "[title](../bar/bb.md)"
41 | (impl/replace-links "[title](./b.md)" {:source "doc/a.md"
42 | :link-map {"doc/a.md" "foo/aa.md"
43 | "doc/b.md" "bar/bb.md"}})))
44 | (is (= "[title](../bar/bb.md)"
45 | (impl/replace-links "[title](../b.md)" {:source "aa/a.md"
46 | :link-map {"aa/a.md" "aa/a.md"
47 | "b.md" "bar/bb.md"}})))
48 | (is (= "[title](bar/bb.md#hash)"
49 | (impl/replace-links "[title](b.md#hash)" {:source "a.md"
50 | :link-map {"a.md" "a.md"
51 | "b.md" "bar/bb.md"}})))
52 | (is (= "[title][ref]\n\n[ref]: bb.md\n"
53 | (impl/replace-links "[title][ref]\n\n[ref]: b.md\n"
54 | {:source "a.md"
55 | :link-map {"a.md" "a.md"
56 | "b.md" "bb.md"}})))
57 | (is (= "[title][ref]\n\n[ref]: bb.md"
58 | (impl/replace-links "[title][ref]\n\n[ref]: b.md"
59 | {:source "a.md"
60 | :link-map {"a.md" "a.md"
61 | "b.md" "bb.md"}}))))
62 |
--------------------------------------------------------------------------------
/components/statecharts/src/dinodoc/statecharts.clj:
--------------------------------------------------------------------------------
1 | (ns dinodoc.statecharts
2 | (:require
3 | [clojure.string :as str]
4 | [dinodoc.impl.git :as git]
5 | [dinodoc.impl.quickdoc.impl :as impl]))
6 |
7 | (defn- with-code-block [f]
8 | (println "```mermaid")
9 | (f)
10 | (println "```"))
11 |
12 | (defn- render-event-name [state]
13 | ;; will need to add some string escaping
14 | (if (keyword? state)
15 | (name state)
16 | state))
17 |
18 | (defn- render-state-name [state]
19 | ;; will need to add some string escaping
20 | (-> (if (keyword? state)
21 | (name state)
22 | state)
23 | (str/replace #"-" "_")))
24 |
25 | (defn- render-states [name machine]
26 | (println "state" (render-state-name name) "{")
27 | (if (= (:type machine) :parallel)
28 | ;; Alternative to render parallel regions would be to separate transitions with `--`:
29 | ;; https://mermaid.js.org/syntax/stateDiagram.html#concurrency
30 | ;; But given clj-statecharts defines regions in a map, we will always have
31 | ;; a key for regions so they can rendered as nested states.
32 | (doseq [[name states] (:regions machine)]
33 | (render-states name states))
34 | (do
35 | (when-some [initial (:initial machine)]
36 | (println "[*] -->" (render-state-name initial)))
37 | (doseq [[source-state target-machine] (:states machine)]
38 | (doseq [[event transitions] (:on target-machine)]
39 | (assert (sequential? transitions)
40 | "Expecting a sequence of transitions, did you perhaps forget to wrap the machine in (fsm/machine)?")
41 | (doseq [{:keys [target actions]} transitions]
42 | (println (render-state-name source-state) "-->"
43 | (render-state-name target) ":"
44 | (render-event-name event))))
45 | (when (seq (:states target-machine))
46 | (render-states source-state target-machine)))))
47 | (println "}"))
48 |
49 | (defn render-machine-diagram
50 | "Render given machine as a mermaid state diagram to standard output."
51 | [machine]
52 | (println "stateDiagram-v2")
53 | (render-states (:id machine) machine))
54 |
55 | (defn render-machine-block
56 | "Render given machine as a mermaid state diagram wrapped in a markdown fenced code block to standard output."
57 | [machine]
58 | (with-code-block #(render-machine-diagram machine)))
59 |
60 | (defn render-machine-var
61 | "Render a var bound to a machine to standard output.
62 | Using a var has the benefit of being able to include link to the source file including the line location."
63 | ([machine-var] (render-machine-var machine-var {}))
64 | ([machine-var {:keys [filename-add-prefix]}]
65 | (let [{:keys [name file line doc]} (meta machine-var)
66 | machine @machine-var
67 | ;; consider ability to allow passing repo info to avoid running detection for each diagram
68 | repo-info (git/detect-repo-info ".")
69 | source-url (when-some [{:keys [url branch]} repo-info]
70 | (impl/var-source
71 | {:filename file :row line :end-row line}
72 | {:github/repo url
73 | :git/branch branch
74 | :filename-add-prefix filename-add-prefix}))]
75 | (println)
76 | (println "##" name)
77 | (when doc
78 | (println)
79 | (println doc))
80 | (println)
81 | (render-machine-block machine)
82 | (when source-url
83 | (println)
84 | (println (str "[source](" source-url ")"))))))
85 |
--------------------------------------------------------------------------------
/test/output/docs/api/samples/source/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_label: source
3 | title: samples.source
4 | toc_min_heading_level: 2
5 | toc_max_heading_level: 4
6 | custom_edit_url:
7 | ---
8 |
9 | docstring on namespace
10 |
11 | *deprecated*
12 |
13 |
14 |
15 |
16 |
17 | ### \*dynamic\-var\-example\* {#-STAR-dynamic-var-example-STAR-}
18 |
19 |
20 | We need to escape var names otherwise earmuffs are not shown but interpreted as italic.
21 |
22 | *dynamic*
23 |
24 |
25 | ### << {#-LT--LT-}
26 | ``` clojure
27 | (<<)
28 | ```
29 |
30 |
31 | Angle brackets in var name
32 |
33 | ### Foo {#Foo}
34 | ``` clojure
35 | (Foo)
36 | ```
37 |
38 |
39 | Foo with different case, link does not clash with [`foo`](#foo).
40 |
41 | ### \_underscore\-surrounded\_ {#-underscore-surrounded-}
42 | ``` clojure
43 | (_underscore-surrounded_)
44 | ```
45 |
46 |
47 | ### a\-macro {#a-macro}
48 | ``` clojure
49 | (a-macro)
50 | ```
51 |
52 |
53 | hello
54 |
55 | *macro*
56 |
57 |
58 | ### added\-and\-deprecated {#added-and-deprecated}
59 | ``` clojure
60 | (added-and-deprecated)
61 | ```
62 |
63 |
64 | *deprecated | added in 8.0*
65 |
66 |
67 | ### added\-tag {#added-tag}
68 | ``` clojure
69 | (added-tag)
70 | ```
71 |
72 |
73 | *added*
74 |
75 |
76 | ### added\-version {#added-version}
77 | ``` clojure
78 | (added-version)
79 | ```
80 |
81 |
82 | *added in 8.0*
83 |
84 |
85 | ### deprecated\-tag {#deprecated-tag}
86 | ``` clojure
87 | (deprecated-tag)
88 | ```
89 |
90 |
91 | Doc string
92 |
93 | *deprecated*
94 |
95 |
96 | ### deprecated\-version {#deprecated-version}
97 | ``` clojure
98 | (deprecated-version)
99 | ```
100 |
101 |
102 | Doc string
103 |
104 | *deprecated in 9.0*
105 |
106 |
107 | ### foo {#foo}
108 | ``` clojure
109 | (foo)
110 | ```
111 |
112 |
113 | Hello, there is also [`Foo`](#Foo) that differs in casing.
114 |
115 | ### has\->arrow {#has--GT-arrow}
116 | ``` clojure
117 | (has->arrow)
118 | ```
119 |
120 |
121 | ### links\-backticks {#links-backticks}
122 | ``` clojure
123 | (links-backticks)
124 | ```
125 |
126 |
127 | Link to a var in the current namespace: [`foo`](#foo)
128 |
129 | Link to a qualified var: [`samples.crossplatform/some-clj-fn`](../../samples/crossplatform/#some-clj-fn)
130 |
131 | Link to a namespace: [`samples.protocols`](../../samples/protocols/)
132 |
133 | Link to a var containing a special character: [`has->arrow`](#has--GT-arrow)
134 |
135 |
136 | ### links\-backticks\-in\-codeblock {#links-backticks-in-codeblock}
137 | ``` clojure
138 | (links-backticks-in-codeblock)
139 | ```
140 |
141 |
142 | In a code block should ideally not be replaced.
143 | ```clojure
144 | ;; Link to a var in the current namespace: [`foo`](#foo)
145 |
146 | ;; Link to a qualified var: [`samples.crossplatform/some-clj-fn`](../../samples/crossplatform/#some-clj-fn)
147 |
148 | ;; Link to a namespace: [`samples.protocols`](../../samples/protocols/)
149 | ```
150 |
151 | ### links\-backticks\-multiple\-occurences {#links-backticks-multiple-occurences}
152 | ``` clojure
153 | (links-backticks-multiple-occurences)
154 | ```
155 |
156 |
157 | Same link referenced multiple times will not get messed up: [`foo`](#foo) and [`foo`](#foo)
158 |
159 | ### links\-wikilinks {#links-wikilinks}
160 | ``` clojure
161 | (links-wikilinks)
162 | ```
163 |
164 |
165 | Link to a var in the current namespace: [`foo`](#foo)
166 |
167 | Link to a qualified var: [`samples.crossplatform/some-clj-fn`](../../samples/crossplatform/#some-clj-fn)
168 |
169 | Link to a namespace: [`samples.protocols`](../../samples/protocols/)
170 |
171 | Link with a title supported by codox: [[samples.crossplatform/some-clj-fn|some-title]]
172 |
--------------------------------------------------------------------------------
/components/structurizr/test/output/json-big-bank-plc/systems/index.md:
--------------------------------------------------------------------------------
1 | # Big Bank plc
2 |
3 | This is an example workspace to illustrate the key features of Structurizr, via the DSL, based around a fictional online banking system.
4 |
5 | Systems:
6 |
7 | - [ATM](./ATM-6/)
8 | - [E-mail System](./E-mail%20System-5/)
9 | - [Internet Banking System](./Internet%20Banking%20System-7/)
10 | - [Mainframe Banking System](./Mainframe%20Banking%20System-4/)
11 |
12 |
13 | ### SystemLandscape
14 |
15 | ```mermaid
16 | graph TB
17 | linkStyle default fill:transparent
18 |
19 | subgraph diagram [System Landscape]
20 | style diagram fill:transparent,stroke:#ffffff
21 |
22 | subgraph enterprise [Big Bank plc]
23 | style enterprise fill:transparent,stroke:#444444,color:#444444
24 |
25 | 2["Customer Service Staff
[Person]
Customer service staff within
the bank.
"]
26 | style 2 fill:#999999,stroke:#6b6b6b,color:#ffffff
27 | 3["Back Office Staff
[Person]
Administration and support
staff within the bank.
"]
28 | style 3 fill:#999999,stroke:#6b6b6b,color:#ffffff
29 | 4["Mainframe Banking System
[Software System]
Stores all of the core
banking information about
customers, accounts,
transactions, etc.
"]
30 | click 4 "Mainframe%20Banking%20System-4/" "Mainframe%20Banking%20System-4/"
31 | style 4 fill:#999999,stroke:#6b6b6b,color:#ffffff
32 | 5["E-mail System
[Software System]
The internal Microsoft
Exchange e-mail system.
"]
33 | click 5 "E-mail%20System-5/" "E-mail%20System-5/"
34 | style 5 fill:#999999,stroke:#6b6b6b,color:#ffffff
35 | 6["ATM
[Software System]
Allows customers to withdraw
cash.
"]
36 | click 6 "ATM-6/" "ATM-6/"
37 | style 6 fill:#999999,stroke:#6b6b6b,color:#ffffff
38 | 7["Internet Banking System
[Software System]
Allows customers to view
information about their bank
accounts, and make payments.
"]
39 | click 7 "Internet%20Banking%20System-7/" "Internet%20Banking%20System-7/"
40 | style 7 fill:#1168bd,stroke:#0b4884,color:#ffffff
41 | end
42 |
43 | 1["Personal Banking Customer
[Person]
A customer of the bank, with
personal bank accounts.
"]
44 | style 1 fill:#08427b,stroke:#052e56,color:#ffffff
45 |
46 | 1-. "Views account balances, and
makes payments using
" .->7
47 | 7-. "Gets account information
from, and makes payments
using
" .->4
48 | 7-. "Sends e-mail using
" .->5
49 | 5-. "Sends e-mails to
" .->1
50 | 1-. "Asks questions to
[Telephone]
" .->2
51 | 2-. "Uses
" .->4
52 | 1-. "Withdraws cash using
" .->6
53 | 6-. "Uses
" .->4
54 | 3-. "Uses
" .->4
55 | end
56 | ```
57 |
58 |
--------------------------------------------------------------------------------
/examples/contextive/.contextive/definitions.yml:
--------------------------------------------------------------------------------
1 | # Source: https://github.com/chrissimon-au/tdd-ddd-demo-dotnet/blob/891aeb03e19cb5df71a8246f499716115372af97/.contextive/definitions.yml
2 | #
3 | # MIT License
4 | #
5 | # Copyright (c) 2023 Chris Simon
6 | #
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy
8 | # of this software and associated documentation files (the "Software"), to deal
9 | # in the Software without restriction, including without limitation the rights
10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | # copies of the Software, and to permit persons to whom the Software is
12 | # furnished to do so, subject to the following conditions:
13 | #
14 | # The above copyright notice and this permission notice shall be included in all
15 | # copies or substantial portions of the Software.
16 | #
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | # SOFTWARE.
24 | contexts:
25 | - name: University
26 | domainVisionStatement: To allow students to register and enrol in courses.
27 | terms:
28 | - name: student
29 | definition: A person who would like to study a course.
30 | examples:
31 | - The student has registered at the university.
32 | - The student has enroled in the course.
33 | - name: register
34 | definition: The process of a student registering with the university. They will not yet be enroled in a course after registering.
35 | examples:
36 | - The student has registered and is intending to enrol in 3 courses.
37 | - name: room
38 | definition: A physical space that a course can be delivered in. Since it has a physical size it has a limited capacity for the number of students who can attend a course in the room.
39 | examples:
40 | - The room doesn't yet have any courses assigned to it.
41 | - name: capacity
42 | definition: The number of students who can enrol in a course that is happening in the room.
43 | examples:
44 | - The capacity of the room is 10.
45 | - name: setup
46 | definition: The process of defining a new room that is available at the university. Usually only done once when the university is setting up the system, but may be done if they reconfigure their layouts.
47 | examples:
48 | - The admin has setup a new room.
49 | - name: course
50 | definition: A unit of study that is focussed on a topic or group of related topics.
51 | examples:
52 | - 'The student is enroling in the "Ancient History: Myths and Legends" course.'
53 | - 10 students have enroled in the Test-Driven Development course.
54 | - name: catalog
55 | definition: The list of courses that are available for students to enrol in.
56 | examples:
57 | - The catalog has 10 courses available.
58 | - name: include
59 | definition: The process of including a course in the catalog.
60 | examples:
61 | - The administrator hasn't yet included my course in the catalog.
62 | - name: enrol
63 | definition: To enrol in a course is for a student to tell the university that they will study that course.
64 | examples:
65 | - I would like to enrol in Urban Geography.
66 | - name: enroling
67 | definition: The process of a student enroling in a course.
68 | examples:
69 | - I am enroling in Advanced Domain Modeling.
70 | - name: enrolment
71 | definition: A student enrols in a course and when they do we record their enrolment. The enrolment keeps track of which student has enroled in which course.
72 | examples:
73 | - A course has 5 enrolments so far.
74 | - name: schedule
75 | definition: Either the process of creating a schedule, or the schedule itself, where the schedule is a definition of which courses are assigned to which rooms.
76 | examples:
77 | - I will be scheduling the courses on Friday, after enrolments are closed.
78 |
--------------------------------------------------------------------------------
/website/src/css/docusaurus-openapi-docs.css:
--------------------------------------------------------------------------------
1 | /* https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/blob/main/demo/src/css/custom.css */
2 |
3 | /* Override --ifm text-decoration */
4 | a:any-link:hover {
5 | text-decoration: none;
6 | }
7 |
8 | /* Sidebar Method labels */
9 | .api-method>.menu__link {
10 | align-items: center;
11 | justify-content: start;
12 | }
13 |
14 | .api-method>.menu__link::before {
15 | width: 50px;
16 | height: 20px;
17 | font-size: 12px;
18 | line-height: 20px;
19 | text-transform: uppercase;
20 | font-weight: 600;
21 | border-radius: 0.25rem;
22 | border: 1px solid;
23 | margin-right: var(--ifm-spacing-horizontal);
24 | text-align: center;
25 | flex-shrink: 0;
26 | border-color: transparent;
27 | color: white;
28 | }
29 |
30 | .get>.menu__link::before {
31 | content: "get";
32 | background-color: var(--ifm-color-primary);
33 | }
34 |
35 | .post>.menu__link::before {
36 | content: "post";
37 | background-color: var(--openapi-code-green);
38 | }
39 |
40 | .delete>.menu__link::before {
41 | content: "del";
42 | background-color: var(--openapi-code-red);
43 | }
44 |
45 | .put>.menu__link::before {
46 | content: "put";
47 | background-color: var(--openapi-code-blue);
48 | }
49 |
50 | .patch>.menu__link::before {
51 | content: "patch";
52 | background-color: var(--openapi-code-orange);
53 | }
54 |
55 | .head>.menu__link::before {
56 | content: "head";
57 | background-color: var(--ifm-color-secondary-darkest);
58 | }
59 |
60 | .event>.menu__link::before {
61 | content: "event";
62 | background-color: var(--ifm-color-secondary-darkest);
63 | }
64 |
65 | /* GitHub Header Link */
66 | .header-github-link:hover {
67 | opacity: 0.6;
68 | }
69 |
70 | .header-github-link:before {
71 | content: "";
72 | width: 24px;
73 | height: 24px;
74 | display: flex;
75 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat;
76 | }
77 |
78 | html[data-theme="dark"] .header-github-link:before {
79 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat;
80 | }
81 |
82 | /* Medium Header Link */
83 | .header-medium-link:hover {
84 | opacity: 0.6;
85 | }
86 |
87 | .header-medium-link:before {
88 | content: "\f23a";
89 | font-family: "Font Awesome 5 Brands";
90 | font-size: x-large;
91 | }
92 |
93 | div[class^="announcementBar_"] {
94 | --site-announcement-bar-stripe-color1: hsl(var(--site-primary-hue-saturation) 85%);
95 | --site-announcement-bar-stripe-color2: hsl(var(--site-primary-hue-saturation) 95%);
96 | background: repeating-linear-gradient(35deg,
97 | var(--site-announcement-bar-stripe-color1),
98 | var(--site-announcement-bar-stripe-color1) 20px,
99 | var(--site-announcement-bar-stripe-color2) 10px,
100 | var(--site-announcement-bar-stripe-color2) 40px);
101 | font-weight: bold;
102 | }
103 |
--------------------------------------------------------------------------------
/src/dinodoc/impl/quickdoc/api.cljc:
--------------------------------------------------------------------------------
1 | (ns dinodoc.impl.quickdoc.api
2 | {:no-doc true}
3 | (:require
4 | #?(:bb [babashka.pods :as pods]
5 | :clj [clj-kondo.core :as clj-kondo])
6 | [clojure.java.io :as io]
7 | [clojure.string :as str]
8 | [dinodoc.impl.quickdoc.impl :as impl]))
9 |
10 | #?(:bb
11 | (or (try (requiring-resolve 'pod.borkdude.clj-kondo/run!)
12 | (catch Exception _ nil)) ;; pod is loaded via bb.edn
13 | (pods/load-pod 'clj-kondo/clj-kondo "2022.11.02")))
14 |
15 | #?(:bb
16 | (require '[pod.borkdude.clj-kondo :as clj-kondo]))
17 |
18 | (defn quickdoc
19 | "Generate API docs. Options:
20 | * `:github/repo` - a link like `https://github.com/borkdude/quickdoc`
21 | * `:git/branch` - branch name for source links, default to `\"main\"`
22 | * `:source-uri` - source link template. Supports `{row}`, `{end-row}`, `{col}`, `{end-col}`, `{filename}`, `{branch}`, `{path}`, `{repo}`.
23 | * `:outfile` - file where API docs are written, or falsey if you don't need a file. Defaults to `\"API.md\"`
24 | * `:source-paths` - sources that are scanned for vars. Defaults to `[\"src\"]`.
25 | * `:toc` - generate table of contents. Defaults to `true`.
26 | * `:var-links` - generate links to vars within the same namespace. Defauls to `true`.
27 | * `:var-pattern` - detecting vars for linking, either `:backticks` (default) or `:wikilinks` (double brackets)
28 | * `:overrides` - overrides in the form `{namespace {:no-doc true var {:no-doc true :doc ...}}}`
29 | * `:filename-add-prefix` - add a prefix to the filename for source links.
30 | * `:filename-remove-prefix` - remove a prefix from the filename for source links.
31 | * `:filename-fn` - transformation of filename before it is rendered to markdown, e.g. for source links.
32 |
33 | Returns a map containing the generated markdown string under the key `:markdown`."
34 | {:org.babashka/cli
35 | {:coerce {:outfile (fn [s]
36 | (if (or (= "false" s)
37 | (= "nil" s))
38 | false
39 | s))
40 | :toc :boolean
41 | :var-links :boolean}
42 | :collect {:source-paths []}}}
43 | [opts]
44 | (let [{:as opts
45 | :keys [outdir
46 | source-paths
47 | analysis
48 | overrides]} (merge {:git/branch "main"
49 | :outfile "API.md"
50 | :source-paths ["src"]
51 | :toc true
52 | :var-links true
53 | :var-pattern :backticks-and-wikilinks}
54 | opts)
55 | opts (assoc opts :var-regex (case (:var-pattern opts)
56 | :backticks #"`(.*?)`"
57 | :wikilinks #"\[\[(.*?)\]\]"
58 | :backticks-and-wikilinks impl/backticks-and-wikilinks-pattern))
59 | ana (or analysis
60 | (-> (clj-kondo/run! {:lint source-paths
61 | :config {:skip-comments true
62 | :output {:analysis
63 | {:arglists true
64 | :var-definitions {:meta [:no-doc
65 | :skip-wiki
66 | :arglists]}
67 | :namespace-definitions {:meta [:no-doc
68 | :skip-wiki]}}}}})
69 | :analysis))
70 | var-defs (:var-definitions ana)
71 | ns-defs (:namespace-definitions ana)
72 | ns-defs (group-by :name ns-defs)
73 | nss (group-by :ns var-defs)
74 | ns->vars (update-vals nss (comp set (partial map :name)))]
75 | (run! (fn [[ns-name vars]]
76 | (let [docs (with-out-str
77 | (impl/print-namespace ns-defs ns->vars ns-name vars opts overrides))
78 | outfile (str outdir "/" (str/replace ns-name #"\." "/") "/index.md")]
79 | (when-not (str/blank? docs)
80 | (io/make-parents outfile)
81 | (spit outfile docs))))
82 | (sort-by first nss))))
83 |
--------------------------------------------------------------------------------