diff --git a/codox/resources/codox/theme/default/css/default.css b/codox/resources/codox/theme/default/css/default.css index 33f78fed..b48c5cbc 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 { @@ -92,16 +92,17 @@ h5.license { #header { background: #3f3f3f; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); + border-bottom: 1px solid #cccccc; z-index: 100; } #header h1 { + display: block; + float: left; margin: 0; padding: 0; font-size: 18px; font-weight: lighter; - text-shadow: -1px -1px 0px #333; } #header h1 .project-version { @@ -121,6 +122,27 @@ h5.license { color: #f5f5f5; } +#langs { + display: block; + float: left; + font-size: 16px; + margin: 0 10px; + font-weight: lighter; +} + +#langs .lang { + display: inline-block; + margin: 0 2px; + padding: 0 7px; + background-color: #bbb; + border-radius: 6px; + vertical-align: middle; +} + +#langs .lang.current { + background-color: #a33; +} + .sidebar a { color: #333; } @@ -325,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; @@ -336,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; @@ -344,7 +368,8 @@ h4.deprecated { .members h4.type, .members h4.added, -.members h4.deprecated { +.members h4.deprecated, +.members h4.lang { margin-top: 1px; } @@ -364,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 1a11ab30..9f3cb953 100644 --- a/codox/src/codox/main.clj +++ b/codox/src/codox/main.clj @@ -4,6 +4,7 @@ (:require [clojure.string :as str] [clojure.java.shell :as shell] [codox.reader.clojure :as clj] + [codox.reader.clojurescript :as cljs] [codox.reader.plaintext :as text])) (defn- writer [{:keys [writer]}] @@ -19,29 +20,9 @@ (throw (Exception. (str "Could not resolve codox writer " writer-sym)))))) -(defn- macro? [var] - (= (:type var) :macro)) - -(defn- read-macro-namespaces [paths read-opts] - (->> (clj/read-namespaces paths read-opts) - (map (fn [ns] (update-in ns [:publics] #(filter macro? %)))) - (remove (comp empty? :publics)))) - -(defn- merge-namespaces [namespaces] - (for [[name namespaces] (group-by :name namespaces)] - (assoc (first namespaces) :publics (mapcat :publics namespaces)))) - -(defn- cljs-read-namespaces [paths read-opts] - ;; require is here to allow Clojure 1.3 and 1.4 when not using ClojureScript - (require 'codox.reader.clojurescript) - (let [reader (find-var 'codox.reader.clojurescript/read-namespaces)] - (merge-namespaces - (concat (reader paths read-opts) - (read-macro-namespaces paths read-opts))))) - (def ^:private namespace-readers {:clojure clj/read-namespaces - :clojurescript cljs-read-namespaces}) + :clojurescript cljs/read-namespaces}) (defn- var-symbol [namespace var] (symbol (name (:name namespace)) (name (:name var)))) @@ -77,14 +58,49 @@ (filter #(some (partial ns-matches? %) ns-filters) namespaces) namespaces)) +(defn- cross-platform? + "Do given options indicate *both* Clojure and ClojureScript sources?" + [{:keys [language] :as opts}] + (= language :both)) + (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) + {:clojure (read-namespaces (assoc opts :language :clojure)) + :clojurescript (read-namespaces (assoc opts :language :clojurescript))} + (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- 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 {}) + (->> + (get-var-langs :clojure (:clojure namespaces) {}) + (get-var-langs :clojurescript (:clojurescript namespaces)))))) + +(comment (get-var-langs {:language :clojure} + '({: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 @@ -121,7 +137,10 @@ (let [options (merge defaults 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 + :cross-platform? (cross-platform? options)))))) diff --git a/codox/src/codox/reader/clojure.clj b/codox/src/codox/reader/clojure.clj index f37346c0..119f2e80 100644 --- a/codox/src/codox/reader/clojure.clj +++ b/codox/src/codox/reader/clojure.clj @@ -116,8 +116,8 @@ (defn- find-namespaces [file] (cond - (.isDirectory file) (ns/find-namespaces-in-dir file) - (jar-file? file) (ns/find-namespaces-in-jarfile (JarFile. file)))) + (.isDirectory file) (set (ns/find-namespaces-in-dir file)) + (jar-file? file) (set (ns/find-namespaces-in-jarfile (JarFile. file))))) (defn read-namespaces "Read Clojure namespaces from a set of source directories (defaults diff --git a/codox/src/codox/reader/clojurescript.clj b/codox/src/codox/reader/clojurescript.clj index dee1892c..1db1296f 100644 --- a/codox/src/codox/reader/clojurescript.clj +++ b/codox/src/codox/reader/clojurescript.clj @@ -4,6 +4,8 @@ (:require [clojure.java.io :as io] [cljs.analyzer :as an] [cljs.analyzer.api :as ana] + [cljs.closure] + [cljs.env] [clojure.string :as str])) (defn- cljs-filename? [filename] @@ -15,7 +17,7 @@ (-> file .getName cljs-filename?))) (defn- remove-quote [x] - (if (and (list? x) (= (first x) 'quote)) + (if (and (seq? x) (= (first x) 'quote)) (second x) x)) @@ -50,15 +52,16 @@ :else :var)) (defn- read-var [file vars var] - (-> var - (select-keys [:name :line :arglists :doc :dynamic :added :deprecated :doc/format]) - (update-some :name (comp symbol name)) - (update-some :arglists remove-quote) - (update-some :doc correct-indent) - (assoc-some :file (.getPath file) - :type (var-type var) - :members (map (partial read-var file vars) - (protocol-methods var vars))))) + (let [vt (var-type var)] + (-> var + (select-keys [:name :line :arglists :doc :dynamic :added :deprecated :doc/format]) + (update-some :name (comp symbol name)) + (update-some :arglists remove-quote) + (update-some :doc correct-indent) + (assoc-some :file (if (= vt :macro) (:file var) (.getPath file)) + :type vt + :members (map (partial read-var file vars) + (protocol-methods var vars)))))) (defn- read-publics [state namespace file] (let [vars (vals (ana/ns-publics state namespace))] @@ -70,10 +73,11 @@ (sort-by (comp str/lower-case :name))))) (defn- analyze-file [file] - (let [state (ana/empty-state)] - (binding [an/*analyze-deps* false] - (ana/no-warn - (ana/analyze-file state file {}))) + (let [opts (cljs.closure/add-implicit-options {}) + state (cljs.env/default-compiler-env opts)] + (ana/no-warn + (cljs.closure/validate-opts opts) + (ana/analyze-file state file opts)) state)) (defn- read-file [path file exception-handler] diff --git a/codox/src/codox/writer/html.clj b/codox/src/codox/writer/html.clj index 0a4f9c81..efad384d 100644 --- a/codox/src/codox/writer/html.clj +++ b/codox/src/codox/writer/html.clj @@ -146,8 +146,27 @@ (if-let [doc (:doc metadata)] (markdown-to-html doc project ns))]) +(defn- language-fileext + ([language] + (case language + :clojure ".clj" + :clojurescript ".cljs" + nil "")) + ([language base-language] + (when (not= language base-language) + (language-fileext language)))) + +(defn- index-filename [language] + (str "index" (language-fileext language) ".html")) + (defn- ns-filename [namespace] - (str (:name namespace) ".html")) + (str (:name namespace) + (language-fileext (:language namespace) (:base-language namespace)) + ".html")) + +(comment + (ns-filename {:name 'my-ns :language :clojure}) + (ns-filename {:name 'my-ns :language :clojure :base-language :clojure})) (defn- ns-filepath [output-dir namespace] (str output-dir "/" (ns-filename namespace))) @@ -235,11 +254,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 +295,26 @@ (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 + [:li.depth-1 (link-to (index-filename :clojure) [:div.inner "Clojure"])] + [:li.depth-1 (link-to (index-filename :clojure) [:div.inner "ClojureScript"])]]))) (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,23 @@ [:span.project-name (h (:name project))] " " [:span.project-version (h (:version project))]]) +(defn- header-platforms [project] + (when (:cross-platform? project) + (let [{:keys [language]} project] + [:div#langs + (if (= language :clojure) + [:div.lang.current "clj"] + [:div.lang (link-to (index-filename :clojure) "clj")]) + + (if (= language :clojurescript) + [:div.lang.current "cljs"] + [:div.lang (link-to (index-filename :clojurescript) "cljs")])]))) + (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 +403,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 both Clojure and ClojureScript code, please " + [:strong "choose a platform"] " to view its documentation:"] + [:ul + [:li (link-to (index-filename :clojure) "Clojure")] + [:li (link-to (index-filename :clojurescript) "ClojureScript")]])) + + (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." @@ -425,6 +480,24 @@ [:h4.type (name (:type var))]) (if (:dynamic var) [:h4.dynamic "dynamic"]) + + (if (:cross-platform? project) + (let [languages (get-in project [:var-langs (:name namespace) (:name var)]) + has-clj? (contains? languages :clojure) + has-cljs? (contains? languages :clojurescript) + {:keys [language]} project] + + (list + (when has-clj? + (if (= language :clojurescript) + [:h4.lang (link-to (var-uri (assoc namespace :language :clojure) var) "clj")] + [:h4.lang.current "clj"])) + + (when has-cljs? + (if (= language :clojure) + [:h4.lang (link-to (var-uri (assoc namespace :language :clojurescript) var) "cljs")] + [:h4.lang.current "cljs"]))))) + (added-and-deprecated-docs var) (if (:type-sig var) [:div.type-sig @@ -464,13 +537,62 @@ (doseq [dir dirs] (.mkdirs (io/file output-dir dir)))) +(defn- cross-platform-namespaces + [namespaces language base-language] + (map + #(assoc % + :language language + :base-language base-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 (keys namespaces)] + (let [namespaces (cross-platform-namespaces namespaces language + (:base-language project)) + 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 (keys namespaces)] + (let [namespaces (cross-platform-namespaces namespaces language + (:base-language project))] + (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)]