diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn new file mode 100644 index 00000000..eb379bcf --- /dev/null +++ b/.clj-kondo/config.edn @@ -0,0 +1,50 @@ +{:linters + {:refer-all + {:exclude [clojure.test + marginalia.hiccup + marginalia.test.helpers]} + + :refer + {:level :warning + :exclude [clojure.test + marginalia.hiccup + marginalia.test.helpers]} + + :docstring-leading-trailing-whitespace {:level :warning} + :keyword-binding {:level :warning} + :misplaced-docstring {:level :warning} + :missing-body-in-when {:level :warning} + :missing-docstring {:level :warning} + :missing-else-branch {:level :warning} + :namespace-name-mismatch {:level :warning} + :non-arg-vec-return-type-hint {:level :warning} + :reduce-without-init {:level :warning} + :redundant-fn-wrapper {:level :warning} + :shadowed-var {:level :warning} + :single-key-in {:level :warning} + :unsorted-required-namespaces {:level :warning} + :use {:level :warning} + :used-underscored-binding {:level :warning} + :warn-on-reflection {:level :warning} + :unresolved-symbol + {:exclude + [number-of-generated-pages + project-name + doc-generator + tests]}} + + :lint-as + {} + + :config-in-comment + {:linters {:unresolved-symbol {:level :off}}} + + :ns-groups + ;; tests are anything that ends in `-test` + [{:pattern ".*-test$" + :name tests}] + + :config-in-ns + {tests + {:linters + {:missing-docstring {:level :off}}}}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6eabaadf..425cd50a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,3 +29,16 @@ jobs: key: "m2-${{ hashFiles('project.clj') }}" - name: Run tests run: lein test + kondo: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4.1.0 + - uses: DeLaGuardo/clojure-lint-action@master + with: + check-name: Run clj-kondo + clj-kondo-args: >- + --lint + src/marginalia + test + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/bin/kondo.sh b/bin/kondo.sh new file mode 100755 index 00000000..7099c258 --- /dev/null +++ b/bin/kondo.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash + +set -euo pipefail + +rm -rf .clj-kondo/.cache + +clj-kondo --parallel --lint src/marginalia/ test diff --git a/src/marginalia/core.clj b/src/marginalia/core.clj index 50e0687e..226b680a 100644 --- a/src/marginalia/core.clj +++ b/src/marginalia/core.clj @@ -35,9 +35,9 @@ (:require [clojure.java.io :as io] [clojure.string :as str] - [clojure.tools.cli :refer [cli]] - [marginalia.html :refer [uberdoc-html index-html single-page-html]] - [marginalia.parser :refer [parse-file parse-ns *lift-inline-comments* *delete-lifted-comments*]]) + [clojure.tools.cli :as cli] + [marginalia.html :as html] + [marginalia.parser :as parser]) (:import (java.io File FileReader))) @@ -45,7 +45,7 @@ ;; ## File System Utilities -(defn ls +(defn- ls "Performs roughly the same task as the UNIX `ls`. That is, returns a seq of the filenames at a given directory. If a path to a file is supplied, then the seq contains only the original path given." @@ -56,10 +56,10 @@ (when (.exists file) [path])))) -(defn mkdir [path] +(defn- mkdir [path] (.mkdirs (io/file path))) -(defn ensure-directory! +(defn- ensure-directory! "Ensure that the directory specified by `path` exists. If not then make it so. Here is a snowman ☃" [path] @@ -71,12 +71,12 @@ [path] (.isDirectory (io/file path))) -(defn find-file-extension +(defn- find-file-extension "Returns a string containing the files extension." [^File file] (second (re-find #"\.([^.]+)$" (.getName file)))) -(defn processable-file? +(defn- processable-file? "Predicate. Returns true for \"normal\" files with a file extension which passes the provided predicate." [pred ^File file] @@ -90,7 +90,7 @@ (->> (io/file dir) (file-seq) (filter (partial processable-file? pred)) - (sort-by parse-ns) + (sort-by parser/parse-ns) (map #(.getCanonicalPath ^File %)))) ;; ## Project Info Parsing @@ -127,66 +127,29 @@ (if found-project? (parse-project-form line) (recur (read rdr)))))) - (catch Exception e + (catch Exception _ (throw (Exception. (str "There was a problem reading the project definition from " path))))))) - -;; ## Source File Analysis - -;; TODO: why are these args unused? -(defn end-of-block? [_cur-group _groups lines] - (let [line (first lines) - next-line (second lines) - next-line-code (get next-line :code-text "")] - (when (or (and (:code-text line) - (:docs-text next-line)) - (re-find #"^\(def" (str/trim next-line-code))) - true))) - -(defn merge-line [line m] - (cond - (:docstring-text line) (assoc m - :docs - (conj (get m :docs []) line)) - (:code-text line) (assoc m - :codes - (conj (get m :codes []) line)) - (:docs-text line) (assoc m - :docs - (conj (get m :docs []) line)))) - -(defn group-lines [doc-lines] - (loop [cur-group {} - groups [] - lines doc-lines] - (cond - (empty? lines) (conj groups cur-group) - - (end-of-block? cur-group groups lines) - (recur (merge-line (first lines) {}) (conj groups cur-group) (rest lines)) - - :else (recur (merge-line (first lines) cur-group) groups (rest lines))))) - -(defn path-to-doc [filename] - {:ns (parse-ns (io/file filename)) - :groups (parse-file filename)}) - - ;; ## Output Generation -(defn filename-contents +(defn- path-to-doc [filename] + {:ns (parser/parse-ns (io/file filename)) + :groups (parser/parse-file filename)}) + +(defn- filename-contents [props output-dir all-files parsed-file] {:name (io/file output-dir (str (:ns parsed-file) ".html")) - :contents (single-page-html props parsed-file all-files)}) + :contents (html/single-page-html props parsed-file all-files)}) (defn multidoc! + "Generate documentation for the given `files-to-analyze`, write the doc files to disk in the `output-dir`" [output-dir files-to-analyze props] (let [parsed-files (map path-to-doc files-to-analyze) - index (index-html props parsed-files) - pages (map #(filename-contents props output-dir parsed-files %) parsed-files)] + index (html/index-html props parsed-files) + pages (map #(filename-contents props output-dir parsed-files %) parsed-files)] (doseq [f (conj pages {:name (io/file output-dir "toc.html") :contents index})] (spit (:name f) (:contents f))))) @@ -200,7 +163,7 @@ - :name - :version" [output-file-name files-to-analyze props] - (let [source (uberdoc-html + (let [source (html/uberdoc-html props (map path-to-doc files-to-analyze))] (spit output-file-name source))) @@ -224,7 +187,7 @@ (find-processable-file-paths % file-extensions) [(.getCanonicalPath (io/file %))]))))) -(defn split-deps [deps] +(defn- split-deps [deps] (when deps (for [d (str/split deps #";") :let [[group artifact version] (str/split d #":")]] @@ -253,41 +216,43 @@ If no source files are found, complain with a usage message." [args & [project]] - (let [[{:keys [dir file name version desc deps css js multi + (let [[{:keys [dir file version desc deps css js multi leiningen exclude - lift-inline-comments exclude-lifted-comments]} files help] - (cli args - ["-d" "--dir" - "Directory into which the documentation will be written" :default "./docs"] - ["-f" "--file" - "File into which the documentation will be written" :default "uberdoc.html"] - ["-n" "--name" - "Project name - if not given will be taken from project.clj"] - ["-v" "--version" - "Project version - if not given will be taken from project.clj"] - ["-D" "--desc" - "Project description - if not given will be taken from project.clj"] - ["-a" "--deps" - "Project dependencies in the form ::;... + lift-inline-comments exclude-lifted-comments] + project-name :name} + files help] + (cli/cli args + ["-d" "--dir" + "Directory into which the documentation will be written" :default "./docs"] + ["-f" "--file" + "File into which the documentation will be written" :default "uberdoc.html"] + ["-n" "--name" + "Project name - if not given will be taken from project.clj"] + ["-v" "--version" + "Project version - if not given will be taken from project.clj"] + ["-D" "--desc" + "Project description - if not given will be taken from project.clj"] + ["-a" "--deps" + "Project dependencies in the form ::;... If not given will be taken from project.clj"] - ["-c" "--css" - "Additional css resources ;;... + ["-c" "--css" + "Additional css resources ;;... If not given will be taken from project.clj."] - ["-j" "--js" - "Additional javascript resources ;;... + ["-j" "--js" + "Additional javascript resources ;;... If not given will be taken from project.clj"] - ["-m" "--multi" - "Generate each namespace documentation as a separate file" :flag true] - ["-l" "--leiningen" - "Generate the documentation for a Leiningen project file."] - ["-e" "--exclude" - "Exclude source file(s) from the document generation process ;;... + ["-m" "--multi" + "Generate each namespace documentation as a separate file" :flag true] + ["-l" "--leiningen" + "Generate the documentation for a Leiningen project file."] + ["-e" "--exclude" + "Exclude source file(s) from the document generation process ;;... If not given will be taken from project.clj"] - ["-L" "--lift-inline-comments" - "Lift ;; inline comments to the top of the enclosing form. + ["-L" "--lift-inline-comments" + "Lift ;; inline comments to the top of the enclosing form. They will be treated as if they preceded the enclosing form." :flag true] - ["-X" "--exclude-lifted-comments" - "If ;; inline comments are being lifted into documentation + ["-X" "--exclude-lifted-comments" + "If ;; inline comments are being lifted into documentation then also exclude them from the source code display." :flag true]) sources (distinct (format-sources (seq files))) sources (if leiningen (cons leiningen sources) sources)] @@ -295,8 +260,8 @@ (do (println "Wrong number of arguments passed to Marginalia.") (println help)) - (binding [*lift-inline-comments* lift-inline-comments - *delete-lifted-comments* exclude-lifted-comments] + (binding [parser/*lift-inline-comments* lift-inline-comments + parser/*delete-lifted-comments* exclude-lifted-comments] (let [project-clj (or project (when (.exists (io/file "project.clj")) (parse-project-file))) @@ -308,7 +273,7 @@ :leiningen leiningen} (:marginalia project-clj)) opts (merge-with choose - {:name name + {:name project-name :version version :description desc :dependencies (split-deps deps) diff --git a/src/marginalia/hiccup.clj b/src/marginalia/hiccup.clj index bab80954..6a1f533e 100644 --- a/src/marginalia/hiccup.clj +++ b/src/marginalia/hiccup.clj @@ -1,23 +1,39 @@ (ns marginalia.hiccup "Library for rendering a tree of vectors into a string of HTML. Pre-compiles where possible for performance." - (:import [clojure.lang IPersistentVector ISeq] - java.net.URI)) + (:import + [clojure.lang IPersistentVector ISeq] + [java.net URI])) + +(set! *warn-on-reflection* true) -;; Pulled from old-contrib to avoid dependency (defn as-str + "Very similar to `clojure.core/str`, but uses `name` instead of `str` when possible: + + ``` + (for [x [{:a 1 :b 2} :foo \"bar\"]] + {:as-str (as-str x) + :str (str x)}) + ;; => + ({:as-str \"{:a 1, :b 2}\" + :str \"{:a 1, :b 2}\"}, + {:as-str \"foo\" + :str \":foo\"}, + {:as-str \"bar\" + :str \"bar\"}) + ```" ([] "") ([x] (if (instance? clojure.lang.Named x) (name x) (str x))) ([x & ys] - ((fn [^StringBuilder sb more] - (if more - (recur (. sb (append (as-str (first more)))) (next more)) - (str sb))) - (new StringBuilder ^String (as-str x)) ys))) + ((fn [^StringBuilder sb more] + (if more + (recur (. sb (append (as-str (first more)))) (next more)) + (str sb))) + (new StringBuilder ^String (as-str x)) ys))) -(def ^:dynamic *html-mode* :xml) +(def ^:dynamic *html-mode* "TODO: where is this used? :O":xml) (defn escape-html "Change special characters into HTML character entities." @@ -28,27 +44,25 @@ (replace ">" ">") (replace "\"" """))) -(def h escape-html) ; alias for escape-html - (defn- xml-mode? [] (= *html-mode* :xml)) (defn- end-tag [] (if (xml-mode?) " />" ">")) -(defn- xml-attribute [name value] - (str " " (as-str name) "=\"" (escape-html value) "\"")) +(defn- xml-attribute [attr-name value] + (str " " (as-str attr-name) "=\"" (escape-html value) "\"")) -(defn- render-attribute [[name value]] +(defn- render-attribute [[attr-name value]] (cond (true? value) (if (xml-mode?) - (xml-attribute name name) - (str " " (as-str name))) + (xml-attribute attr-name attr-name) + (str " " (as-str attr-name))) (not value) "" :else - (xml-attribute name value))) + (xml-attribute attr-name value))) (defn- render-attr-map [attrs] (apply str @@ -60,17 +74,19 @@ (def ^{:doc "A list of tags that need an explicit ending tag when rendered." :private true} container-tags #{"a" "b" "body" "canvas" "dd" "div" "dl" "dt" "em" "fieldset" "form" "h1" "h2" "h3" - "h4" "h5" "h6" "head" "html" "i" "iframe" "label" "li" "ol" "option" "pre" + "h4" "h5" "h6" "head" "html" "i" "iframe" "label" "li" "ol" "option" "pre" "script" "span" "strong" "style" "table" "textarea" "ul"}) (defn- normalize-element "Ensure a tag vector is of the form [tag-name attrs content]." [[tag & content]] - (when (not (or (keyword? tag) (symbol? tag) (string? tag))) + (when (not (or (keyword? tag) + (symbol? tag) + (string? tag))) (throw (IllegalArgumentException. (str tag " is not a valid tag name.")))) - (let [[_ tag id class] (re-matches re-tag (as-str tag)) - tag-attrs {:id id - :class (if class (.replace ^String class "." " "))} + (let [[_ tag id klass] (re-matches re-tag (as-str tag)) + tag-attrs {:id id + :class (when klass (.replace ^String klass "." " "))} map-attrs (first content)] (if (map? map-attrs) [tag (merge tag-attrs map-attrs) (next content)] @@ -119,7 +135,8 @@ (defn- form-name "Get the name of the supplied form." [form] - (if (and (seq? form) (symbol? (first form))) + (when (and (seq? form) + (symbol? (first form))) (name (first form)))) (defmulti compile-form @@ -141,15 +158,15 @@ (defn- not-hint? "True if x is not hinted to be the supplied type." - [x type] - (if-let [hint (-> x meta :tag)] - (not (isa? (eval hint) type)))) + [x the-type] + (when-let [hint (-> x meta :tag)] + (not (isa? (eval hint) the-type)))) (defn- hint? "True if x is hinted to be the supplied type." - [x type] - (if-let [hint (-> x meta :tag)] - (isa? (eval hint) type))) + [x the-type] + (when-let [hint (-> x meta :tag)] + (isa? (eval hint) the-type))) (defn- literal? "True if x is a literal value that can be rendered as-is." @@ -167,7 +184,7 @@ (defn- element-compile-strategy "Returns the compilation strategy to use for a given element." - [[tag attrs & content :as element]] + [[tag attrs & _ :as element]] (cond (every? literal? element) ::all-literal ; e.g. [:span "foo"] @@ -272,10 +289,10 @@ (defmacro defhtml "Define a function, but wrap its output in an implicit html macro." - [name & fdecl] + [fn-name & fdecl] (let [[fhead fbody] (split-with #(not (or (list? %) (vector? %))) fdecl) wrap-html (fn [[args & body]] `(~args (html ~@body)))] - `(defn ~name + `(defn ~fn-name ~@fhead ~@(if (vector? (first fbody)) (wrap-html fbody) @@ -293,14 +310,13 @@ (apply func args)))) (defmacro defelem - "Defines a function that will return a tag vector. If the first argument - passed to the resulting function is a map, it merges it with the attribute - map of the returned tag value." - [name & fdecl] - `(do (defn ~name ~@fdecl) + "Defines a function that will return a tag vector. If the first argument passed to the resulting function is a map, it + merges it with the attribute map of the returned tag value." + [fn-name & fdecl] + `(do (defn ~fn-name ~@fdecl) (alter-var-root (var ~name) add-optional-attrs))) -(def ^:dynamic *base-url* nil) +(def ^:dynamic *base-url* "Base URL to prepend to URIs (if supplied)" nil) (defmacro with-base-url "Add a base-url that will be added to the output of the resolve-uri function." diff --git a/src/marginalia/html.clj b/src/marginalia/html.clj index abb7fd47..6e81b18b 100644 --- a/src/marginalia/html.clj +++ b/src/marginalia/html.clj @@ -1,12 +1,15 @@ (ns marginalia.html "Utilities for converting parse results into html." - (:use [marginalia.hiccup :only (html escape-html)]) - (:require [clojure.string :as str]) - (:import [com.petebevin.markdown MarkdownProcessor])) + (:require + [marginalia.hiccup :as hiccup :refer [html]]) + (:import + [com.petebevin.markdown MarkdownProcessor])) -(def ^{:dynamic true} *resources* "./vendor/") +(set! *warn-on-reflection* true) -(defn css-rule [rule] +(def ^{:dynamic true} *resources* "Directory in which to find resources (CSS, JS, etc.)" "./vendor/") + +(defn- css-rule [rule] (let [sels (reverse (rest (reverse rule))) props (last rule)] (str (apply str (interpose " " (map name sels))) @@ -30,7 +33,7 @@ (.getResourceAsStream resource-name) (java.io.InputStreamReader.) (slurp)) - (catch java.lang.NullPointerException npe + (catch java.lang.NullPointerException _ (println (str "Could not locate resources at " resource-name)) (println " ... attempting to fix.") (let [resource-name (str *resources* resource-name)] @@ -39,18 +42,16 @@ (.getResourceAsStream resource-name) (java.io.InputStreamReader.) (slurp)) - (catch java.lang.NullPointerException npe + (catch java.lang.NullPointerException _ (println (str " STILL could not locate resources at " resource-name ". Giving up!")))))))) -(defn inline-js [resource] - (let [src (slurp-resource resource)] - (html [:script {:type "text/javascript"} - src]))) +(defn- inline-js [resource] + (html [:script {:type "text/javascript"} + (slurp-resource resource)])) -(defn inline-css [resource] - (let [src (slurp-resource resource)] - (html [:style {:type "text/css"} - (slurp-resource resource)]))) +(defn- inline-css [resource] + (html [:style {:type "text/css"} + (slurp-resource resource)])) @@ -59,7 +60,7 @@ ;; based) for display through html & css. ;; Markdown processor. -(def mdp (com.petebevin.markdown.MarkdownProcessor.)) +(def ^:private ^MarkdownProcessor mdp (MarkdownProcessor.)) (defn md "Markdown string to html converter. Translates strings like: @@ -69,7 +70,7 @@ \"## header!\" -> `\"

header!

\"` ..." - [s] + [^String s] (.markdown mdp s)) ;; As a result of docifying then grouping, you'll end up with a seq like this one: @@ -87,18 +88,17 @@ ex. (docs-to-html [{:doc-text \"# hello world!\"} {:docstring-text \"I'm a docstring!}]) - -> `\"

hello world!


\"` - " + -> `\"

hello world!


\"`" [docs] (-> docs str (md))) -(defn codes-to-html [code-block] +(defn- codes-to-html [code-block] (html [:pre {:class "brush: clojure"} - (escape-html code-block)])) + (hiccup/escape-html code-block)])) -(defn section-to-html [section] +(defn- section-to-html [section] (html [:tr [:td {:class "docs"} (docs-to-html (if (= (:type section) :comment) @@ -108,7 +108,7 @@ (codes-to-html (:raw section)) "")]])) -(defn dependencies-html [deps & header-name] +(defn- dependencies-html [deps & header-name] (when-let [deps (seq deps)] (let [header-name (or header-name "dependencies")] (html [:div {:class "dependencies"} @@ -132,10 +132,8 @@ (defn metadata-html "Generate meta tags from project info." [project-info] - (let [options (:marginalia project-info) - meta (:meta options)] - (html (when meta - (map #(vector :meta {:name (name (key %)) :contents (val %)}) meta))))) + (html (when-let [m (get-in project-info [:marginalia :meta])] + (map #(vector :meta {:name (name (key %)) :contents (val %)}) m)))) ;; # Load Optional Resources ;; Use external Javascript and CSS in your documentation. For example: @@ -165,18 +163,16 @@ (defn opt-resources-html "Generate script and link tags for optional external javascript and css." [project-info] - (let [options (:marginalia project-info) - javascript (:javascript options) - css (:css options)] + (let [options (:marginalia project-info)] (html (concat - (when javascript - (map #(vector :script {:type "text/javascript" :src %}) javascript)) - (when css - (map #(vector :link {:tyle "text/css" :rel "stylesheet" :href %}) css)))))) + (when-let [js (:javascript options)] + (map #(vector :script {:type "text/javascript" :src %}) js)) + (when-let [the-css (:css options)] + (map #(vector :link {:tyle "text/css" :rel "stylesheet" :href %}) the-css)))))) ;; Is <h1/> overloaded? Maybe we should consider redistributing ;; header numbers instead of adding classes to all the h1 tags. -(defn header-html [project-info] +(defn- header-html [project-info] (html [:tr [:td {:class "docs"} @@ -213,7 +209,7 @@ [anchor?] (link-to-namespace "toc" anchor? {:class "toc-link"})) -(defn toc-html [props docs] +(defn- toc-html [props docs] (html [:tr [:td {:class "docs"} @@ -224,7 +220,7 @@ docs)]]] [:td {:class "codes"} " "]])) -(defn floating-toc-html [docs] +(defn- floating-toc-html [docs] [:div {:id "floating-toc"} [:ul (map #(vector :li {:class "floating-toc-li" @@ -232,7 +228,7 @@ (:ns %)) docs)]]) -(defn groups-html [props doc] +(defn- groups-html [props doc] (html [:tr [:td {:class "docs"} @@ -247,7 +243,7 @@ [:td {:class "spacer docs"} " "] [:td {:class "codes"}]])) -(def reset-css +(def ^:private reset-css (css [:html {:margin 0 :padding 0}] [:h1 {:margin 0 :padding 0}] [:h2 {:margin 0 :padding 0}] @@ -256,7 +252,7 @@ [:a {:color "#261A3B"}] [:a:visited {:color "#261A3B"}])) -(def header-css +(def ^:private header-css (css [:.header {:margin-top "30px"}] [:h1.project-name {:font-size "34px" :display "inline"}] @@ -309,7 +305,7 @@ :padding-left 0}] [:.header :p {:margin-left "20px"}])) -(def floating-toc-css +(def ^:private floating-toc-css (css [:#floating-toc {:position "fixed" :top "10px" :right "20px" @@ -320,7 +316,7 @@ :margin 0 :padding 0}])) -(def general-css +(def ^:private general-css (css [:body {:margin 0 :padding 0 @@ -373,9 +369,10 @@ [:.footer {:text-align "center"}])) (defn page-template - "Notice that we're inlining the css & javascript for [SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/) (`inline-js` - & `inline-css`) to be able to package the output as a single file (uberdoc if you will). It goes without - saying that all this is WIP and will probably change in the future." + "Notice that we're inlining the CSS and Javascript + for [SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/) (`inline-js` & `inline-css`) to be able to + package the output as a single file (uberdoc, if you will). It goes without saying that all this is WIP and will + probably change in the future." [project-metadata opt-resources header toc content floating-toc] (html "\n" @@ -414,9 +411,9 @@ (inline-js (str *resources* "app.js"))]])) -;; Syntax highlighting is done a bit differently than docco. Instead of embedding -;; the highlighting metadata on the parse / html gen phase, we use [SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/) -;; to do it in javascript. +;; Syntax highlighting is done a bit differently than docco. Instead of embedding the highlighting metadata on the +;; parse / html gen phase, we use [SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/) to do it in +;; Javascript. (defn uberdoc-html "This generates a stand alone html file (think `lein uberjar`). @@ -431,6 +428,7 @@ (floating-toc-html docs))) (defn index-html + "Generate the HTML for the index page (in multi-page mode)" [project-metadata docs] (page-template project-metadata @@ -441,7 +439,8 @@ "")) ;; no floating toc (defn single-page-html - [project-metadata doc all-docs] + "Generate a given page's HTML" + [project-metadata doc _all-docs] (page-template project-metadata (opt-resources-html project-metadata) diff --git a/src/marginalia/latex.clj b/src/marginalia/latex.clj index c9cf6465..6f978855 100644 --- a/src/marginalia/latex.clj +++ b/src/marginalia/latex.clj @@ -1,46 +1,47 @@ (ns marginalia.latex "Utilities for converting parse results into LaTeX." - (:require [clojure.string :as str] - [clostache.parser :as mustache] - [clojure.java.shell :as shell] - [clojure.string :as string]) - (:use [marginalia.html :only [slurp-resource]])) + (:require + [clojure.java.shell :as shell] + [clojure.string :as str] + [clostache.parser :as mustache] + [marginalia.html :as html])) +(set! *warn-on-reflection* true) -(def template (slurp-resource "latex.mustache")) +(def template "Mustache-format template to use for the document" (html/slurp-resource "latex.mustache")) ;; ## Namespace header calculation -(def headers ["subsubsection" "subsection" "section"]) +(def ^:private ^clojure.lang.APersistentVector headers ["subsubsection" "subsection" "section"]) -(def section-re #"\\((sub){0,2}section)\s*\{") +(def ^:private section-re #"\\((sub){0,2}section)\s*\{") (defn get-section-type "It calculates the suitable header for a namespace `ns`. For example, - if the generated output has subsections a section must be generated - for each namespace in the table of contents. If it has - subsubsections a subsection should be generated and so on." + if the generated output has subsections a section must be generated for each namespace in the table of contents. If + it has subsubsections a subsection should be generated and so on." [groups] - (let [docs (map :doc groups) - matches (->> docs (mapcat (partial re-seq section-re)) - (map second)) - matches (or (seq matches) ["none"]) + (let [matches (->> groups + (map :doc) + (mapcat (partial re-seq section-re)) + (map second)) + matches (or (seq matches) ["none"]) most-important (apply max (map #(.indexOf headers %) matches)) - position (min (-> most-important inc) - (-> (count headers) dec))] + position (min (-> most-important inc) + (-> (count headers) dec))] (nth headers (max position 0)))) -(defn get-header - [type ns] - (str "\\" type "{" ns "}")) +(defn- get-header + [the-type ns'] + (str "\\" the-type "{" ns' "}")) ;; ## Markdown to LaTeX conversion -(def mark (string/join (repeat 3 "marginalialatex"))) +(def ^:private mark (str/join (repeat 3 "marginalialatex"))) -(def separator-marker (str "\n\n" mark "\n\n")) +(def ^:private separator-marker (str "\n\n" mark "\n\n")) -(def separator-marker-re (re-pattern (str "\n?\n?" mark "\n?\n?"))) +(def ^:private separator-marker-re (re-pattern (str "\n?\n?" mark "\n?\n?"))) (defn invoke-pandoc "We use pandoc to convert `markdown-input` to LaTeX." @@ -57,25 +58,25 @@ "It applies pandoc in a string joined by `separator-marker`. The output is split using a regex created from `separator-marker`" [docs] - (let [joined (string/join separator-marker docs) + (let [joined (str/join separator-marker docs) output (invoke-pandoc joined) - result (string/split output separator-marker-re (count docs))] + result (str/split output separator-marker-re (count docs))] (assert (= (count docs) (count result)) "The converted docs must have the same number") result)) -(defn to-latex +(defn- to-latex [groups] - (map #(assoc-in %1 [:doc] %2) groups (md->latex (map :doc groups)))) + (map #(assoc %1 :doc %2) groups (md->latex (map :doc groups)))) ;; ## Code and doc extraction -(defn docs-from-group [group] +(defn- docs-from-group [group] (or (:docstring group) (and (= (:type group) :comment) (:raw group)) "")) -(defn code-from-group [group] +(defn- code-from-group [group] (if (= (:type group) :code) (:raw group) "")) @@ -89,17 +90,16 @@ ;; ## Uberdoc generation -(def convert-groups (comp to-latex extract-code-and-doc)) +(def ^:private convert-groups (comp to-latex extract-code-and-doc)) (defn as-data-for-template - " - 1. Converts each `groups` data to the format expected by the template. - 1. Calculates the correct header level for namespaces." + "1. Converts each `groups` data to the format expected by the template. + 2. Calculates the correct header level for namespaces." [project-metadata docs] - (let [namespaces (map #(update-in % [:groups] convert-groups) docs) + (let [namespaces (map #(update % :groups convert-groups) docs) section-type (get-section-type (mapcat :groups namespaces)) - namespaces (map #(assoc % :ns-header (get-header section-type (:ns %))) - namespaces)] + namespaces (map #(assoc % :ns-header (get-header section-type (:ns %))) + namespaces)] {:namespaces namespaces :project project-metadata})) (defn uberdoc-latex @@ -107,4 +107,4 @@ template. Before it needs to convert the data to the format expected for the template" [project-metadata docs] - (mustache/render template (as-data-for-template project-metadata docs))) \ No newline at end of file + (mustache/render template (as-data-for-template project-metadata docs))) diff --git a/src/marginalia/main.clj b/src/marginalia/main.clj index 77f848f4..02621413 100644 --- a/src/marginalia/main.clj +++ b/src/marginalia/main.clj @@ -1,24 +1,17 @@ (ns marginalia.main - (:use [marginalia.html :only [*resources*]] - [marginalia.core :only [run-marginalia]]) + (:require + [marginalia.core :as marginalia] + [marginalia.html :as html]) (:gen-class)) (defn -main "The main entry point into Marginalia." [& sources] - (binding [*resources* ""] - (run-marginalia sources) + (binding [html/*resources* ""] + (marginalia/run-marginalia sources) (shutdown-agents))) ;; # Example Usage (comment ;; Command line example - (-main "./src/marginalia/core.clj" "./src/marginalia/html.clj") - - ;; This will find all marginalia source files, and then generate an uberdoc. - (apply -main (find-clojure-file-paths "./src")) - -;; Move these to tests - (merge-line {:docstring-text "hello world" :line 3} {:docs ["stuff"]}) - (merge-line {:code-text "(defn asdf" :line 4} {:docs ["stuff"]}) - (merge-line {:docs-text "There's only one method in this module", :line 4} {})) + (-main "./src/marginalia/core.clj" "./src/marginalia/html.clj")) diff --git a/src/marginalia/parser.clj b/src/marginalia/parser.clj index 450dbcfb..fa812b73 100644 --- a/src/marginalia/parser.clj +++ b/src/marginalia/parser.clj @@ -3,19 +3,23 @@ ;; Clojure parsing solution. (ns marginalia.parser "Provides the parsing facilities for Marginalia." - (:refer-clojure :exclude [replace]) (:require - [cljs.tagged-literals :refer [*cljs-data-readers*]] - [clojure.string :refer [join replace lower-case]] - [clojure.tools.namespace :refer [read-file-ns-decl]])) + [cljs.tagged-literals :as cljs] + [clojure.string :as str] + [clojure.tools.namespace :as tools.ns]) + (:import + [clojure.lang LineNumberingPushbackReader LispReader] + [java.io File Reader Writer])) +(set! *warn-on-reflection* true) ;; Extracted from clojure.contrib.reflect (defn get-field "Access to private or protected field. field-name is a symbol or keyword." - [klass field-name obj] - (-> klass (.getDeclaredField (name field-name)) + [^Class klass field-name obj] + (-> klass + (.getDeclaredField (name field-name)) (doto (.setAccessible true)) (.get obj))) @@ -29,26 +33,27 @@ obj is nil for static methods, the instance object otherwise. The method-name is given a symbol or a keyword (something Named)." - [klass method-name params obj & args] - (-> klass (.getDeclaredMethod (name method-name) - (into-array Class params)) + [^Class klass method-name params obj & args] + (-> klass + (.getDeclaredMethod (name method-name) + (into-array Class params)) (doto (.setAccessible true)) (.invoke obj (into-array Object args)))) (defrecord Comment [content]) -(defmethod print-method Comment [comment ^String out] - (.write out (str \" (.content comment) \"))) +(defmethod print-method Comment [^Comment the-comment ^Writer out] + (.write out (str \" (.content the-comment) \"))) -(def top-level-comments (atom [])) -(def sub-level-comments (atom [])) +(def ^:private top-level-comments (atom [])) +(def ^:private sub-level-comments (atom [])) -(def ^{:dynamic true} *comments* nil) -(def ^{:dynamic true} *comments-enabled* nil) -(def ^{:dynamic true} *lift-inline-comments* nil) -(def ^{:dynamic true} *delete-lifted-comments* nil) +(def ^:dynamic *comments* "TODO: document" nil) +(def ^:dynamic *comments-enabled* "TODO: document" nil) +(def ^:dynamic *lift-inline-comments* "TODO: document" nil) +(def ^:dynamic *delete-lifted-comments*"TODO: document" nil) -(defn comments-enabled? +(defn- comments-enabled? [] @*comments-enabled*) @@ -77,8 +82,8 @@ (directive)) (not directive))) -(defn read-comment - ([reader semicolon] +(defn- read-comment + ([^LineNumberingPushbackReader reader semicolon] (let [sb (StringBuilder.)] (.append sb semicolon) (loop [c (.read reader)] @@ -97,17 +102,17 @@ (do (.append sb (Character/toString ch)) (recur (.read reader)))))))) - ([reader semicolon opts pending] + ([^Reader reader semicolon _opts _pending] ;; TODO: who uses this? (read-comment reader semicolon))) -(defn set-comment-reader [reader] - (aset (get-field clojure.lang.LispReader :macros nil) +(defn- set-comment-reader [^Reader reader] + (aset ^"[Lclojure.lang.IFn;" (get-field LispReader :macros nil) (int \;) reader)) (defrecord DoubleColonKeyword [content]) -(defmethod print-method DoubleColonKeyword [dck ^java.io.Writer out] +(defmethod print-method DoubleColonKeyword [^DoubleColonKeyword dck ^java.io.Writer out] (.write out (str \: (.content dck)))) (defmethod print-dup DoubleColonKeyword [dck ^java.io.Writer out] @@ -126,16 +131,16 @@ ;;; else we will crash and fail people's builds. (if-let [resolver-var (resolve '*reader-resolver*)] (defn ^:private match-symbol [s] - (call-method clojure.lang.LispReader :matchSymbol + (call-method LispReader :matchSymbol [String, (Class/forName "clojure.lang.LispReader$Resolver")] nil s (deref resolver-var))) (defn ^:private match-symbol [s] - (call-method clojure.lang.LispReader :matchSymbol + (call-method LispReader :matchSymbol [String] nil s))) -(defn read-keyword - ([reader colon] +(defn- read-keyword + ([^LineNumberingPushbackReader reader colon] (let [c (.read reader)] (if (= (int \:) c) (-> (read-token reader (char c)) @@ -144,28 +149,34 @@ (do (.unread reader c) (-> (read-token reader colon) match-symbol))))) - ([reader colon opts pending] + ([^LineNumberingPushbackReader reader colon _opts _pending] ;; TODO: who uses this? (read-keyword reader colon))) -(defn set-keyword-reader [reader] - (aset (get-field clojure.lang.LispReader :macros nil) +(defn- set-keyword-reader [reader] + (aset ^"[Lclojure.lang.IFn;" (get-field LispReader :macros nil) (int \:) reader)) -(defn skip-spaces-and-comments [rdr] +(defn- skip-spaces-and-comments [^LineNumberingPushbackReader rdr] (loop [c (.read rdr)] - (cond (= c -1) nil - (= (char c) \;) - (do (read-comment rdr \;) - (recur (.read rdr))) - (#{\space \tab \return \newline \,} (char c)) - (recur (.read rdr)) - :else (.unread rdr c)))) + (cond + (= c -1) + nil + + (= (char c) \;) + (do (read-comment rdr \;) + (recur (.read rdr))) + + (#{\space \tab \return \newline \,} (char c)) + (recur (.read rdr)) + + :else + (.unread rdr c)))) (declare adjacent?) (declare merge-comments) -(defn parse* [reader] +(defn- parse* [^LineNumberingPushbackReader reader] (take-while #(not= :_eof (:form %)) (flatten @@ -173,67 +184,67 @@ (fn [] (binding [*comments* top-level-comments] (skip-spaces-and-comments reader)) - (let [start (.getLineNumber reader) - form (binding [*comments* sub-level-comments] - (try (. clojure.lang.LispReader - (read reader {:read-cond :allow - :eof :_eof})) - (catch Exception ex - (let [msg (str "Problem parsing near line " start - " <" (.readLine reader) ">" - " original reported cause is " - (.getCause ex) " -- " - (.getMessage ex)) - e (RuntimeException. msg)] - (.setStackTrace e (.getStackTrace ex)) - (throw e))))) - end (.getLineNumber reader) - code {:form form :start start :end end} + (let [start (.getLineNumber reader) + form (binding [*comments* sub-level-comments] + (try (. LispReader + (read reader {:read-cond :allow + :eof :_eof})) + (catch Exception ex + (let [msg (str "Problem parsing near line " start + " <" (.readLine reader) ">" + " original reported cause is " + (.getCause ex) " -- " + (.getMessage ex)) + e (RuntimeException. msg)] + (.setStackTrace e (.getStackTrace ex)) + (throw e))))) + end (.getLineNumber reader) + code {:form form :start start :end end} ;; We optionally lift inline comments to the top of the form. ;; This monstrosity ensures that each consecutive group of inline ;; comments is treated as a mergable block, but with a fake ;; blank comment between non-adjacent inline comments. When merged ;; and converted to markdown, this will produce a paragraph for ;; each separate block of inline comments. - paragraph-comment {:form (Comment. ";;") - :text [";;"]} + paragraph-comment {:form (Comment. ";;") + :text [";;"]} merge-inline-comments (fn [cs c] (if (re-find #"^;(\s|$)" - (.content (:form c))) + (.content ^Comment (:form c))) cs (if-let [t (peek cs)] (if (adjacent? t c) (conj cs c) (conj cs paragraph-comment c)) (conj cs c)))) - inline-comments (when (and *lift-inline-comments* - (seq @sub-level-comments)) - (cond->> (reduce merge-inline-comments - [] - @sub-level-comments) - (seq @top-level-comments) - (into [paragraph-comment]) - true - (mapv #(assoc % :start start :end (dec start))))) - comments (concat @top-level-comments inline-comments)] + inline-comments (when (and *lift-inline-comments* + (seq @sub-level-comments)) + (cond->> (reduce merge-inline-comments + [] + @sub-level-comments) + (seq @top-level-comments) + (into [paragraph-comment]) + true + (mapv #(assoc % :start start :end (dec start))))) + comments (concat @top-level-comments inline-comments)] (swap! top-level-comments (constantly [])) (swap! sub-level-comments (constantly [])) (if (empty? comments) [code] (vec (concat comments [code]))))))))) -(defn strip-docstring [docstring raw] +(defn- strip-docstring [docstring raw] (-> raw - (replace (str \" (-> docstring - str - (replace "\"" "\\\"")) - \") - "") - (replace #"#?\^\{\s*:doc\s*\}" "") - (replace #"\n\s*\n" "\n") - (replace #"\n\s*\)" ")"))) - -(defn get-var-docstring [nspace-sym sym] + (str/replace (str \" (-> docstring + str + (str/replace "\"" "\\\"")) + \") + "") + (str/replace #"#?\^\{\s*:doc\s*\}" "") + (str/replace #"\n\s*\n" "\n") + (str/replace #"\n\s*\)" ")"))) + +(defn- get-var-docstring [nspace-sym sym] (let [s (if nspace-sym (symbol (str nspace-sym) (str sym)) (symbol (str sym)))] @@ -242,34 +253,40 @@ ;; HACK: to handle types (catch Exception _)))) -(defmulti dispatch-form (fn [form _ _] - (if (seq? form) (first form) form))) +(defmulti dispatch-form "TODO: document" + (fn [form _ _] + (if (seq? form) + (first form) + form))) (defn- extract-common-docstring [form raw nspace-sym] (let [sym (second form)] (if (symbol? sym) - (let [maybe-metadocstring (:doc (meta sym))] - (let [nspace (find-ns sym) - [maybe-ds remainder] (let [[_ _ ? & more?] form] [? more?]) - docstring (if (and (string? maybe-ds) remainder) - maybe-ds - (if (= (first form) 'ns) - (if (not maybe-metadocstring) - (when (string? maybe-ds) maybe-ds) - maybe-metadocstring) - (if-let [ds maybe-metadocstring] - ds - (when nspace - (-> nspace meta :doc) - (get-var-docstring nspace-sym sym)))))] - [(when docstring - ;; Exclude flush left docstrings from adjustment: - (if (re-find #"\n[^\s]" docstring) - docstring - (replace docstring #"\n " "\n"))) - (strip-docstring docstring raw) - (if (or (= 'ns (first form)) nspace) sym nspace-sym)])) + (let [maybe-metadocstring (:doc (meta sym)) + nspace (find-ns sym) + [maybe-ds remainder] (let [[_ _ ? & more?] form] [? more?]) + docstring (if (and (string? maybe-ds) remainder) + maybe-ds + (if (= (first form) 'ns) + (if (not maybe-metadocstring) + (when (string? maybe-ds) maybe-ds) + maybe-metadocstring) + (if-let [ds maybe-metadocstring] + ds + (when nspace + (-> nspace meta :doc) + (get-var-docstring nspace-sym sym)))))] + [#_form + (when docstring + ;; Exclude flush left docstrings from adjustment: + (if (re-find #"\n[^\s]" docstring) + docstring + (str/replace docstring #"\n " "\n"))) + #_raw + (strip-docstring docstring raw) + #_nspace-sym + (if (or (= 'ns (first form)) nspace) sym nspace-sym)]) [nil raw nspace-sym]))) (defn- extract-impl-docstring @@ -278,25 +295,25 @@ (defn- extract-internal-docstrings [body] - (mapcat #(extract-impl-docstring %) + (mapcat extract-impl-docstring body)) (defmethod dispatch-form 'defprotocol [form raw nspace-sym] - (let [[ds r s] (extract-common-docstring form raw nspace-sym) + (let [[ds r s] (extract-common-docstring form raw nspace-sym) ;; Clojure 1.10 added `:extend-via-metadata` to the `defprotocol` macro. ;; If the flag is present, `extract-internal-docstrings` needs to start ;; 2 forms later, to account for the presence of a keyword, ;; `:extend-via-metadata` and a boolean `true` in the macro body. - evm (contains? (set form) :extend-via-metadata)] - (let [internal-dses (cond - (and evm ds) (extract-internal-docstrings (nthnext form 5)) - evm (extract-internal-docstrings (nthnext form 4)) - ds (extract-internal-docstrings (nthnext form 3)) - :else (extract-internal-docstrings (nthnext form 2)))] - (with-meta - [ds r s] - {:internal-docstrings internal-dses})))) + evm (contains? (set form) :extend-via-metadata) + internal-dses (cond + (and evm ds) (extract-internal-docstrings (nthnext form 5)) + evm (extract-internal-docstrings (nthnext form 4)) + ds (extract-internal-docstrings (nthnext form 3)) + :else (extract-internal-docstrings (nthnext form 2)))] + (with-meta + [ds r s] + {:internal-docstrings internal-dses}))) (defmethod dispatch-form 'ns [form raw nspace-sym] @@ -319,10 +336,10 @@ (extract-common-docstring form raw nspace-sym)) (defmethod dispatch-form 'defmethod - [form raw nspace-sym] + [_form raw nspace-sym] [nil raw nspace-sym]) -(defn dispatch-inner-form +(defn- dispatch-inner-form [form raw nspace-sym] (conj (reduce (fn [[adoc araw] inner-form] @@ -337,7 +354,7 @@ nspace-sym)) (defn- dispatch-literal - [form raw nspace-sym] + [_form raw _nspace-sym] [nil raw]) (defn- literal-form? [form] @@ -355,25 +372,25 @@ :else (dispatch-inner-form form raw nspace-sym))) -(defn extract-docstring [{:keys [start end form]} raw-lines nspace-sym] - (let [new-lines (join "\n" (subvec raw-lines (dec start) (min end (count raw-lines))))] +(defn- extract-docstring [{:keys [start end form]} raw-lines nspace-sym] + (let [new-lines (str/join "\n" (subvec raw-lines (dec start) (min end (count raw-lines))))] (dispatch-form form new-lines nspace-sym))) -(defn- ->str [m] - (-> (-> m :form .content) - (replace #"^;+\s(\s*)" "$1") - (replace #"^;+" ""))) +(defn- ->str [{:keys [form]}] + (-> (.content ^Comment form) + (str/replace #"^;+\s(\s*)" "$1") + (str/replace #"^;+" ""))) -(defn merge-comments [f s] +(defn- merge-comments [f s] {:form (Comment. (str (->str f) "\n" (->str s))) :text (into (:text f) (:text s)) :start (:start f) :end (:end s)}) -(defn comment? [{:keys [form]}] +(defn- comment? [{:keys [form]}] (instance? Comment form)) -(defn code? [{:keys [form] :as o}] +(defn- code? [{:keys [form] :as o}] (and (not (nil? form)) (not (comment? o)))) @@ -382,7 +399,7 @@ [{:keys [end] :as _first} {:keys [start] :as _second}] (= end (dec start))) -(defn arrange-in-sections [parsed-code raw-code] +(defn- arrange-in-sections [parsed-code raw-code] (loop [sections [] f (first parsed-code) s (second parsed-code) @@ -391,7 +408,7 @@ (if f (cond ;; ignore comments with only one semicolon - (and (comment? f) (re-find #"^;(\s|$)" (-> f :form .content))) + (and (comment? f) (re-find #"^;(\s|$)" (.content ^Comment (:form f)))) (recur sections s (first nn) (next nn) nspace) ;; merging comments block (and (comment? f) (comment? s) (adjacent? f s)) @@ -416,13 +433,13 @@ ;; this is far from perfect but should work ;; for most cases: erase matching comments ;; and then remove lines that are blank - (-> (reduce (fn [raw comment] - (replace raw - (str comment "\n") - "\n")) + (-> (reduce (fn [raw the-comment] + (str/replace raw + (str the-comment "\n") + "\n")) code (:text f)) - (replace #"\n\s+\n" "\n")) + (str/replace #"\n\s+\n" "\n")) code) :docstring (str doc "\n\n" (->str f))) (first nn) (next nn) nspace)) @@ -443,12 +460,14 @@ s (first nn) (next nn) nspace))) sections))) -(defn parse [source-string] +(defn parse + "Return a parse tree for the given code (provided as a string)" + [source-string] (let [make-reader #(java.io.BufferedReader. (java.io.StringReader. (str source-string "\n"))) lines (vec (line-seq (make-reader))) reader (clojure.lang.LineNumberingPushbackReader. (make-reader)) - old-cmt-rdr (aget (get-field clojure.lang.LispReader :macros nil) (int \;))] + old-cmt-rdr (aget ^"[Lclojure.lang.IFn;" (get-field LispReader :macros nil) (int \;))] (try (set-comment-reader read-comment) (set-keyword-reader read-keyword) @@ -461,33 +480,37 @@ (set-keyword-reader nil) (throw e))))) -(defn cljs-file? [filepath] - (.endsWith (lower-case filepath) "cljs")) +(defn- cljs-file? [filepath] + (str/ends-with? (str/lower-case filepath) "cljs")) -(defn cljx-file? [filepath] - (.endsWith (lower-case filepath) "cljx")) +(defn- cljx-file? [filepath] + (str/ends-with? (str/lower-case filepath) "cljx")) -(def cljx-data-readers {'+clj identity +(def ^:private cljx-data-readers {'+clj identity '+cljs identity}) -(defmacro with-readers-for [file & body] +(defmacro ^:private with-readers-for [file & body] `(let [readers# (merge {} - (when (cljs-file? ~file) *cljs-data-readers*) + (when (cljs-file? ~file) cljs/*cljs-data-readers*) (when (cljx-file? ~file) cljx-data-readers) default-data-readers)] (binding [*data-readers* readers#] ~@body))) -(defn parse-file [fn] - (with-readers-for fn +(defn parse-file + "Return a parse tree for the given `filename`" + [filename] + (with-readers-for filename (binding [*comments-enabled* (atom true)] - (parse (slurp fn))))) + (parse (slurp filename))))) -(defn parse-ns [file] +(defn parse-ns + "Return a parse tree for all the files in the namespace" + [^File file] (let [filename (.getName file)] (with-readers-for filename - (or (not-empty (-> file - (read-file-ns-decl) - (second) - (str))) - filename)))) + (or (not-empty (-> file + (tools.ns/read-file-ns-decl) + (second) + (str))) + filename)))) diff --git a/test/marginalia/core_test.clj b/test/marginalia/core_test.clj index fb5c8bfa..1b6efad1 100644 --- a/test/marginalia/core_test.clj +++ b/test/marginalia/core_test.clj @@ -1,8 +1,8 @@ (ns marginalia.core-test (:require [clojure.test :refer :all] - [marginalia.core :as core])) + [marginalia.core :as marginalia])) (deftest parse-project-file-simple (is (= "project-name" - (:name (marginalia.core/parse-project-file "test/marginalia/resources/multi-def-project.clj.txt"))))) + (:name (marginalia/parse-project-file "test/marginalia/resources/multi-def-project.clj.txt"))))) diff --git a/test/marginalia/parse_test.clj b/test/marginalia/parse_test.clj index 2dd28797..3c97e027 100644 --- a/test/marginalia/parse_test.clj +++ b/test/marginalia/parse_test.clj @@ -22,8 +22,8 @@ (* x x))") (deftest test-parse-fn-docstring - (let [{:keys [type docstring]} (first (marginalia.parser/parse simple-fn))] - (is (= :code type)) + (let [{docstring :docstring the-type :type} (first (marginalia.parser/parse simple-fn))] + (is (= :code the-type)) (is (= "the docstring" docstring)))) (def reader-conditional-fn @@ -34,8 +34,8 @@ :cljs (js/Error. msg)))") (deftest test-reader-conditional - (let [{:keys [type docstring]} (first (marginalia.parser/parse reader-conditional-fn))] - (is (= :code type)) + (let [{docstring :docstring the-type :type} (first (marginalia.parser/parse reader-conditional-fn))] + (is (= :code the-type)) (is (= "Returns a language-appropriate error" docstring)))) (deftest inline-comments diff --git a/test/marginalia/test/helpers.clj b/test/marginalia/test/helpers.clj index 7e6f5072..f553f314 100644 --- a/test/marginalia/test/helpers.clj +++ b/test/marginalia/test/helpers.clj @@ -1,10 +1,11 @@ (ns marginalia.test.helpers (:require - [clojure.java.io :refer [file delete-file]] + [clojure.java.io :as io] [clojure.test :refer :all] - [marginalia.core :as core] + [marginalia.core :as marginalia] [marginalia.html :as html])) +(set! *warn-on-reflection* true) ;; copied from http://clojuredocs.org/clojure_contrib/clojure.contrib.io/delete-file-recursively ;; N.B. that Raynes's filesystem library could possibly replace this, @@ -12,17 +13,21 @@ (defn delete-file-recursively "Delete file f. If it's a directory, recursively delete all its contents. Raise an exception if any deletion fails unless silently is true." [f & [silently]] - (let [f (file f)] + (let [f (io/file f)] (when (.isDirectory f) (doseq [child (.listFiles f)] (delete-file-recursively child silently))) - (delete-file f silently))) + (io/delete-file f silently))) -(defn find-clojure-file-paths [source-dir] - (core/find-processable-file-paths source-dir #(re-find #"clj$" %))) +(defn find-clojure-file-paths + "Returns a seq of string paths for Clojure files in the given `source-dir`" + [source-dir] + (marginalia/find-processable-file-paths source-dir #(re-find #"clj$" %))) -(defn files-in [dir] - (seq (.listFiles (file dir)))) +(defn files-in + "A seq of `File`s in the `dir`" + [dir] + (seq (.listFiles (io/file dir)))) (defmacro with-project "Runs assertions in the context of a project set up for testing in the `test_projects` directory. @@ -38,20 +43,18 @@ output files and test project metadata * `tests` - assertions to be run after the output has been produced" [project-name doc-generator & tests] - (let [project (file "test_projects" project-name) - test-project-src (str (file project "src")) - test-project-target (str (file project "docs")) - test-metadata { - :dependencies [["some/dep" "0.0.1"]] - :description "Test project" - :name "test" - :dev-dependencies [] - :version "0.0.1" - }] + (let [project (io/file "test_projects" project-name) + test-project-src (str (io/file project "src")) + test-project-target (str (io/file project "docs")) + test-metadata {:dependencies [["some/dep" "0.0.1"]] + :description "Test project" + :name "test" + :dev-dependencies [] + :version "0.0.1"}] `(do (delete-file-recursively ~test-project-target true) - (.mkdirs (file ~test-project-target)) + (.mkdirs (io/file ~test-project-target)) (binding [html/*resources* ""] (~doc-generator ~test-project-src ~test-project-target ~test-metadata)) (let [~'number-of-generated-pages (count (files-in ~test-project-target))]