From f88c2d9ec8f221f805d81dd9eea17b3e785d84b7 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Mon, 3 Jul 2023 16:23:28 +0200 Subject: [PATCH 1/4] Add support for simultaneous output of >1 language Author: @ptaoussanis - This implementation attempts to be minimally invasive. Most of the logical changes are within `html/write-index` and `html/write-namespaces`, where a special path is introduced for cross-platform projects. - NO behavioural changes are intended for traditional (non-cross-platform) projects. - See #216 for detailed feature discussion. --- .../codox/theme/default/css/default.css | 24 ++- codox/src/codox/main.clj | 38 ++++- codox/src/codox/writer/html.clj | 148 +++++++++++++++--- 3 files changed, 176 insertions(+), 34 deletions(-) diff --git a/codox/resources/codox/theme/default/css/default.css b/codox/resources/codox/theme/default/css/default.css index 33f78fed..4b5175c2 100644 --- a/codox/resources/codox/theme/default/css/default.css +++ b/codox/resources/codox/theme/default/css/default.css @@ -44,7 +44,7 @@ h5.license { right: 0; height: 22px; color: #f5f5f5; - padding: 5px 7px; + padding: 7px 10px; } #content { @@ -97,6 +97,8 @@ h5.license { } #header h1 { + display: block; + float: left; margin: 0; padding: 0; font-size: 18px; @@ -121,6 +123,26 @@ h5.license { color: #f5f5f5; } +#langs { + display: block; + float: left; + font-size: 16px; + margin: 0 10px; +} + +#langs .lang { + display: inline-block; + margin: 0 2px; + padding: 0 7px; + background-color: #717171; + border-radius: 6px; + vertical-align: middle; +} + +#langs .lang.current { + background-color: #a33; +} + .sidebar a { color: #333; } diff --git a/codox/src/codox/main.clj b/codox/src/codox/main.clj index 1a11ab30..4bde4565 100644 --- a/codox/src/codox/main.clj +++ b/codox/src/codox/main.clj @@ -78,13 +78,25 @@ namespaces)) (defn- read-namespaces + "Returns { } for cross-platform opts, + or otherwise." [{:keys [language root-path source-paths namespaces metadata exclude-vars] :as opts}] - (let [reader (namespace-readers language)] - (-> (reader source-paths (select-keys opts [:exception-handler])) - (filter-namespaces namespaces) - (remove-excluded-vars exclude-vars) - (add-source-paths root-path source-paths) - (add-ns-defaults metadata)))) + (if (:cross-platform? opts) + (reduce + (fn [m language] + (assoc m language + (read-namespaces + (assoc opts + :language language + :cross-platform? false)))) + {} + (:languages opts)) + (let [reader (namespace-readers language)] + (-> (reader source-paths (select-keys opts [:exception-handler])) + (filter-namespaces namespaces) + (remove-excluded-vars exclude-vars) + (add-source-paths root-path source-paths) + (add-ns-defaults metadata))))) (defn- read-documents [{:keys [doc-paths doc-files] :or {doc-files :all}}] (cond @@ -113,12 +125,24 @@ :themes [:default] :git-commit (delay (git-commit root-path))})) +(defn- cross-platform-options [{:keys [language] :as opts}] + (if-not (set? language) + opts ; {:language } + (if (= (count language) 1) + (assoc opts :language (first language)) ; {:language } + + ;; Cross-platform case: {:language nil, :languages } + (assoc opts + :language nil + :languages language + :cross-platform? true)))) + (defn generate-docs "Generate documentation from source files." ([] (generate-docs {})) ([options] - (let [options (merge defaults options) + (let [options (-> (merge defaults options) cross-platform-options) write-fn (writer options) namespaces (read-namespaces options) documents (read-documents options)] diff --git a/codox/src/codox/writer/html.clj b/codox/src/codox/writer/html.clj index 0a4f9c81..ab080dbf 100644 --- a/codox/src/codox/writer/html.clj +++ b/codox/src/codox/writer/html.clj @@ -146,8 +146,25 @@ (if-let [doc (:doc metadata)] (markdown-to-html doc project ns))]) +(defn- language-info + [language] + (when language + (case language + :clojure {:ext "clj", :filename-suffix ".clj", :name "Clojure"} + :clojurescript {:ext "cljs", :filename-suffix ".cljs", :name "ClojureScript"} + (ex-info (str "Unexpected language: `" language "`") + {:language language})))) + +(defn- index-filename [language] + (str "index" (:filename-suffix (language-info language)) ".html")) + (defn- ns-filename [namespace] - (str (:name namespace) ".html")) + (str (:name namespace) + (:filename-suffix (language-info (:language namespace))) + ".html")) + +(comment + (ns-filename {:name 'my-ns :language :clojure})) (defn- ns-filepath [output-dir namespace] (str output-dir "/" (ns-filename namespace))) @@ -235,11 +252,12 @@ [:span.bottom]]))) (defn- index-link [project on-index?] - (list - [:h3.no-link [:span.inner "Project"]] - [:ul.index-link - [:li.depth-1 {:class (if on-index? "current")} - (link-to "index.html" [:div.inner "Index"])]])) + (when-not (:cross-platform? project) + (list + [:h3.no-link [:span.inner "Project"]] + [:ul.index-link + [:li.depth-1 {:class (if on-index? "current")} + (link-to "index.html" [:div.inner "Index"])]]))) (defn- topics-menu [project current-doc] (if-let [docs (seq (:documents project))] @@ -275,16 +293,28 @@ (get-in project [:html :namespace-list] default))) (defn- namespaces-menu [project current-ns] - (let [namespaces (:namespaces project)] + (when (:show-namespaces? project) + (let [namespaces (:namespaces project)] + (list + [:h3.no-link [:span.inner "Namespaces"]] + (case (namespace-list-type project) + :flat (flat-namespaces namespaces current-ns) + :nested (nested-namespaces namespaces current-ns)))))) + +(defn- platforms-menu [project] + (when (:show-platforms? project) (list - [:h3.no-link [:span.inner "Namespaces"]] - (case (namespace-list-type project) - :flat (flat-namespaces namespaces current-ns) - :nested (nested-namespaces namespaces current-ns))))) + [:h3.no-link [:span.inner "Platforms"]] + [:ul.index-link + (for [language (:languages project)] + [:li.depth-1 + (link-to (index-filename language) + [:div.inner (:name (language-info language))])])]))) (defn- primary-sidebar [project & [current]] [:div.sidebar.primary (index-link project (nil? current)) + (platforms-menu project) (topics-menu project current) (namespaces-menu project current)]) @@ -314,10 +344,20 @@ [:span.project-name (h (:name project))] " " [:span.project-version (h (:version project))]]) +(defn- header-platforms [project] + (when (:cross-platform? project) + (let [{:keys [language languages]} project] + [:div#langs + (for [language* languages] + (if (= language language*) + [:div.lang.current (:ext (language-info language*))] + [:div.lang (link-to (index-filename language*) (:ext (language-info language*)))]))]))) + (defn- header [project] [:div#header [:h2 "Generated by " (link-to "https://github.com/weavejester/codox" "Codox")] - [:h1 (link-to "index.html" (project-title project))]]) + [:h1 (link-to "index.html" (project-title project))] + (header-platforms project)]) (defn- package [project] (if-let [p (:package project)] @@ -360,16 +400,28 @@ [:ul.topics (for [doc docs] [:li (link-to (doc-filename doc) (h (:title doc)))])])) - [:h2 "Namespaces"] - (for [namespace (sort-by :name (:namespaces project))] - [:div.namespace - [:h3 (link-to (ns-filename namespace) (h (:name namespace)))] - [:div.doc (format-docstring project nil (update-in namespace [:doc] util/summary))] - [:div.index - [:p "Public variables and functions:"] - (unordered-list - (for [var (sorted-public-vars namespace)] - (list " " (link-to (var-uri namespace var) (h (:name var))) " ")))]])]])) + + (when (:show-platforms? project) + (list + [:h2 "Platforms"] + [:p "This project includes code for multiple platforms, please " + [:strong "choose a platform"] " to view its documentation:"] + [:ul + (for [language (:languages project)] + [:li (link-to (index-filename language) (:name (language-info language)))])])) + + (when (:show-namespaces? project) + (list + [:h2 "Namespaces"] + (for [namespace (sort-by :name (:namespaces project))] + [:div.namespace + [:h3 (link-to (ns-filename namespace) (h (:name namespace)))] + [:div.doc (format-docstring project nil (update-in namespace [:doc] util/summary))] + [:div.index + [:p "Public variables and functions:"] + (unordered-list + (for [var (sorted-public-vars namespace)] + (list " " (link-to (var-uri namespace var) (h (:name var))) " ")))]])))]])) (defmulti format-document "Format a document into HTML." @@ -464,13 +516,57 @@ (doseq [dir dirs] (.mkdirs (io/file output-dir dir)))) +(defn- cross-platform-namespaces + [namespaces language] + (map #(assoc % :language language) + (get namespaces language))) + (defn- write-index [output-dir project] - (spit (io/file output-dir "index.html") (transform-html project (index-page project)))) + (let [{:keys [namespaces cross-platform?]} project] + + (when cross-platform? + ;; Write an index file for each language + (doseq [language (:languages project)] + (let [namespaces (cross-platform-namespaces namespaces language) + project + (assoc project + :namespaces namespaces + :language language + :show-platforms? false + :show-namespaces? true)] + (spit (io/file output-dir (index-filename language)) + (transform-html project (index-page project)))))) + + ;; Always write a main index file + (let [project (assoc project + :show-platforms? cross-platform? + :show-namespaces? (not cross-platform?))] + (spit (io/file output-dir (index-filename nil)) + (transform-html project (index-page project)))))) (defn- write-namespaces [output-dir project] - (doseq [namespace (:namespaces project)] - (spit (ns-filepath output-dir namespace) - (transform-html project (namespace-page project namespace))))) + (let [{:keys [namespaces cross-platform?]} project] + + (if cross-platform? + ;; Write namespace files for each language + (doseq [language (:languages project)] + (let [namespaces (cross-platform-namespaces namespaces language)] + (doseq [namespace namespaces] + (let [project (assoc project + :namespaces namespaces + :language language + :show-platforms? false + :show-namespaces? true)] + (spit (ns-filepath output-dir namespace) + (transform-html project (namespace-page project namespace))))))) + + ;; Write namespace files for only language + (doseq [namespace namespaces] + (let [project (assoc project + :show-platforms? false + :show-namespaces? true)] + (spit (ns-filepath output-dir namespace) + (transform-html project (namespace-page project namespace)))))))) (defn- write-documents [output-dir project] (doseq [document (:documents project)] From f91fdf41a67fab855db2f73c6c27f40379b70b55 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Tue, 4 Jul 2023 18:45:56 +0200 Subject: [PATCH 2/4] Add support for advanced `:base-language` option Author: @ptaoussanis This is an advanced option to help prevent any broken doc links when upgrading a project's docs from single language to dual language. In this case, :base-language can be set to the previous (single) language for which doc links may already exist in the wild. In detail, if cross-platform project then: {:base-language nil} => ".clj", ".cljs" file extensions ; Default {:base-language :clojure} => nil, ".cljs" file extensions {:base-language :clojurescript} => ".clj", nil file extensions For example: Library Foo previously used {:language :clojure} (either because it was Clojure only, or because of limitations in Codox). Various links to Foo's Codox documentation now exist in the wild. Foo's authors want to change to cross-platform, but don't want to break pre-existing links in the wild. In this case, Foo's authors can use the following opts: {:language #{:clojure :clojurescript} :base-language :clojure} This will produce files like the following: com.foolib.html ; For Clojure platform com.foolib.cljs.html ; For ClojureScript platform Any pre-existing links will successfully point to the same (Clojure) docs they did previously. --- codox/src/codox/writer/html.clj | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/codox/src/codox/writer/html.clj b/codox/src/codox/writer/html.clj index ab080dbf..3486d4f2 100644 --- a/codox/src/codox/writer/html.clj +++ b/codox/src/codox/writer/html.clj @@ -147,20 +147,26 @@ (markdown-to-html doc project ns))]) (defn- language-info - [language] - (when language - (case language - :clojure {:ext "clj", :filename-suffix ".clj", :name "Clojure"} - :clojurescript {:ext "cljs", :filename-suffix ".cljs", :name "ClojureScript"} - (ex-info (str "Unexpected language: `" language "`") - {:language language})))) + ([language] + (when language + (case language + :clojure {:ext "clj", :filename-suffix ".clj", :name "Clojure"} + :clojurescript {:ext "cljs", :filename-suffix ".cljs", :name "ClojureScript"} + (ex-info (str "Unexpected language: `" language "`") + {:language language})))) + + ([language base-language] + (when-let [info (language-info language)] + (if (= language base-language) + (assoc info :filename-suffix "") + info)))) (defn- index-filename [language] (str "index" (:filename-suffix (language-info language)) ".html")) (defn- ns-filename [namespace] (str (:name namespace) - (:filename-suffix (language-info (:language namespace))) + (:filename-suffix (language-info (:language namespace) (:base-language namespace))) ".html")) (comment @@ -517,8 +523,11 @@ (.mkdirs (io/file output-dir dir)))) (defn- cross-platform-namespaces - [namespaces language] - (map #(assoc % :language language) + [namespaces language base-language] + (map + #(assoc % + :language language + :base-language base-language) (get namespaces language))) (defn- write-index [output-dir project] @@ -527,7 +536,7 @@ (when cross-platform? ;; Write an index file for each language (doseq [language (:languages project)] - (let [namespaces (cross-platform-namespaces namespaces language) + (let [namespaces (cross-platform-namespaces namespaces language (:base-language project)) project (assoc project :namespaces namespaces @@ -550,7 +559,7 @@ (if cross-platform? ;; Write namespace files for each language (doseq [language (:languages project)] - (let [namespaces (cross-platform-namespaces namespaces language)] + (let [namespaces (cross-platform-namespaces namespaces language (:base-language project))] (doseq [namespace namespaces] (let [project (assoc project :namespaces namespaces From cf166eb9db36a4c36950b2eadbfec4b2280c51dc Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Tue, 4 Jul 2023 13:56:17 +0200 Subject: [PATCH 3/4] Add linked platform labels ("clj", "cljs") to var docs Author: @ptaoussanis Note that this commit introduces new classes to `default.css` used (only) by "cross-platform" projects. This means: - NO behavioural changes are intended for non-cross-platform projects. - Cross-platform projects WILL require a theme that includes the new CSS classes. --- .../codox/theme/default/css/default.css | 19 +++++++++-- codox/src/codox/main.clj | 33 +++++++++++++++++-- codox/src/codox/writer/html.clj | 12 +++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/codox/resources/codox/theme/default/css/default.css b/codox/resources/codox/theme/default/css/default.css index 4b5175c2..e16171bc 100644 --- a/codox/resources/codox/theme/default/css/default.css +++ b/codox/resources/codox/theme/default/css/default.css @@ -347,7 +347,8 @@ h5.license { h4.type, h4.dynamic, h4.added, -h4.deprecated { +h4.deprecated, +h4.lang { float: left; margin: 3px 10px 15px 0; font-size: 15px; @@ -358,7 +359,8 @@ h4.deprecated { .public h4.type, .public h4.dynamic, .public h4.added, -.public h4.deprecated { +.public h4.deprecated, +.public h4.lang { font-size: 13px; font-weight: bold; margin: 3px 0 0 10px; @@ -366,7 +368,8 @@ h4.deprecated { .members h4.type, .members h4.added, -.members h4.deprecated { +.members h4.deprecated, +.members h4.lang { margin-top: 1px; } @@ -386,6 +389,16 @@ h4.deprecated { color: #880000; } +.public h4.lang, .public h4.lang a { + font-weight: normal; + font-variant: normal; + color: #717171; +} + +.public h4.lang.current { + color: #a33; +} + .namespace { margin-bottom: 30px; } diff --git a/codox/src/codox/main.clj b/codox/src/codox/main.clj index 4bde4565..4327a0b4 100644 --- a/codox/src/codox/main.clj +++ b/codox/src/codox/main.clj @@ -98,6 +98,33 @@ (add-source-paths root-path source-paths) (add-ns-defaults metadata))))) +(defn- get-var-langs + "Returns { { }} for given namespaces." + ([language namespaces var-langs] + (reduce + (fn [var-langs ns] + (reduce + (fn [var-langs public-var] + (update-in var-langs [(:name ns) (:name public-var)] + #(conj (or % #{}) language))) + var-langs + (:publics ns))) + var-langs + namespaces)) + + ([options namespaces] + (if-not (:cross-platform? options) + (get-var-langs (:language options) namespaces {}) + (reduce + (fn [var-langs language] + (get-var-langs language (get namespaces language) var-langs)) + {} + (:languages options))))) + +(comment (get-var-langs {:languages #{:clojure :clojurescript}} + '({:name codox.main :publics ({:name defaults} {:name bar})} + {:name codox.foo :publics ({:name bar})}))) + (defn- read-documents [{:keys [doc-paths doc-files] :or {doc-files :all}}] (cond (not= doc-files :all) (map text/read-file doc-files) @@ -145,7 +172,9 @@ (let [options (-> (merge defaults options) cross-platform-options) write-fn (writer options) namespaces (read-namespaces options) - documents (read-documents options)] + documents (read-documents options) + var-langs (get-var-langs options namespaces)] (write-fn (assoc options :namespaces namespaces - :documents documents))))) + :documents documents + :var-langs var-langs))))) diff --git a/codox/src/codox/writer/html.clj b/codox/src/codox/writer/html.clj index 3486d4f2..553115ea 100644 --- a/codox/src/codox/writer/html.clj +++ b/codox/src/codox/writer/html.clj @@ -483,6 +483,18 @@ [:h4.type (name (:type var))]) (if (:dynamic var) [:h4.dynamic "dynamic"]) + + (if (:cross-platform? project) + (let [var-langs (get-in project [:var-langs (:name namespace) (:name var)]) + {:keys [language languages]} project] + + (for [language* languages] + (when (contains? var-langs language*) + (if (= language language*) + [:h4.lang.current (:ext (language-info language*))] + [:h4.lang (link-to (var-uri (assoc namespace :language language*) var) + (:ext (language-info language*)))]))))) + (added-and-deprecated-docs var) (if (:type-sig var) [:div.type-sig From cde3ed9b2ecb9b9f1607950acbc830cb877af35d Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Thu, 6 Jul 2023 11:38:36 +0200 Subject: [PATCH 4/4] Control language sorting --- codox/src/codox/writer/html.clj | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/codox/src/codox/writer/html.clj b/codox/src/codox/writer/html.clj index 553115ea..102e218a 100644 --- a/codox/src/codox/writer/html.clj +++ b/codox/src/codox/writer/html.clj @@ -150,8 +150,8 @@ ([language] (when language (case language - :clojure {:ext "clj", :filename-suffix ".clj", :name "Clojure"} - :clojurescript {:ext "cljs", :filename-suffix ".cljs", :name "ClojureScript"} + :clojure {:sort 1, :ext "clj", :filename-suffix ".clj", :name "Clojure"} + :clojurescript {:sort 2, :ext "cljs", :filename-suffix ".cljs", :name "ClojureScript"} (ex-info (str "Unexpected language: `" language "`") {:language language})))) @@ -161,6 +161,11 @@ (assoc info :filename-suffix "") info)))) +(defn- sorted-languages [languages] + (sort-by #(:sort (language-info %)) languages)) + +(comment (sorted-languages #{:clojurescript :clojure})) + (defn- index-filename [language] (str "index" (:filename-suffix (language-info language)) ".html")) @@ -312,7 +317,7 @@ (list [:h3.no-link [:span.inner "Platforms"]] [:ul.index-link - (for [language (:languages project)] + (for [language (sorted-languages (:languages project))] [:li.depth-1 (link-to (index-filename language) [:div.inner (:name (language-info language))])])]))) @@ -354,7 +359,7 @@ (when (:cross-platform? project) (let [{:keys [language languages]} project] [:div#langs - (for [language* languages] + (for [language* (sorted-languages languages)] (if (= language language*) [:div.lang.current (:ext (language-info language*))] [:div.lang (link-to (index-filename language*) (:ext (language-info language*)))]))]))) @@ -413,7 +418,7 @@ [:p "This project includes code for multiple platforms, please " [:strong "choose a platform"] " to view its documentation:"] [:ul - (for [language (:languages project)] + (for [language (sorted-languages (:languages project))] [:li (link-to (index-filename language) (:name (language-info language)))])])) (when (:show-namespaces? project) @@ -488,7 +493,7 @@ (let [var-langs (get-in project [:var-langs (:name namespace) (:name var)]) {:keys [language languages]} project] - (for [language* languages] + (for [language* (sorted-languages languages)] (when (contains? var-langs language*) (if (= language language*) [:h4.lang.current (:ext (language-info language*))]