diff --git a/.gitignore b/.gitignore index 5665fdc9d..34b981aa0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,17 @@ -uberdoc.html -.DS_Store .cake -pom.xml +.DS_Store +.lein-deps-sum +.lein-failures +.nrepl-port *jar -lib +/.calva/output-window/ +/.clj-kondo/.cache +/.lsp/.cache +/.portal/vs-code.edn +/site/uberdoc.html classes +lib out -webgen.cache -.lein-failures -.lein-deps-sum -docs/*.html +pom.xml target -.nrepl-port +webgen.cache diff --git a/docs/uberdoc.html b/docs/uberdoc.html new file mode 100644 index 000000000..b5747fcb6 --- /dev/null +++ b/docs/uberdoc.html @@ -0,0 +1,3749 @@ + +marginalia -- Marginalia

marginalia

0.9.2-SNAPSHOT


lightweight literate programming for clojure -- inspired by docco

+

dependencies

org.clojure/clojure
1.11.1
org.clojure/clojurescript
1.7.228
org.clojure/tools.namespace
0.2.10
org.clojure/tools.cli
0.3.3
org.markdownj/markdownj
0.3.0-1.0.2b4
de.ubercode.clostache/clostache
1.4.0



(this space intentionally left almost blank)
 

A new way to think about programs

+ +

What if your code and its documentation were one and the same?

+ +

Much of the philosophy guiding literate programming is the realization of the answer to this question. +However, if literate programming stands as a comprehensive programming methodology at one of end of the +spectrum and no documentation stands as its antithesis, then Marginalia falls somewhere between. That is, +you should always aim for comprehensive documentation, but the shortest path to a useful subset is the +commented source code itself.

+ +

The art of Marginalia

+ +

If you’re fervently writing code that is heavily documented, then using Marginalia for your Clojure projects +is as simple as running it on your codebase. However, if you’re unaccustomed to documenting your source, then +the guidelines herein will help you make the most out of Marginalia for true-power documentation.

+ +

Following the guidelines will work to make your code not only easier to follow: it will make it better. +The very process of using Marginalia will help to crystallize your understanding of problem and its solution(s).

+ +

The quality of the prose in your documentation will often reflect the quality of the code itself thus highlighting +problem areas. The elimination of problem areas will solidify your code and its accompanying prose. Marginalia +provides a virtuous circle spiraling inward toward maximal code quality.

+ +

The one true way

+ +
    +
  1. Start by running Marginalia against your code
  2. +
  3. Cringe at the sad state of your code commentary
  4. +
  5. Add docstrings and code comments as appropriate
  6. +
  7. Generate the documentation again
  8. +
  9. Read the resulting documentation
  10. +
  11. Make changes to code and documentation so that the “dialog” flows sensibly
  12. +
  13. Repeat from step #4 until complete
  14. +
+
(ns marginalia.core
+  (: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*]])
+  (:import
+   (java.io File FileReader)))
+
(set! *warn-on-reflection* true)

File System Utilities

+

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.

+
(defn ls
+  [path]
+  (let [file (io/file path)]
+    (if (.isDirectory file)
+      (seq (.list file))
+      (when (.exists file)
+        [path]))))
+
(defn mkdir [path]
+  (.mkdirs (io/file path)))

Ensure that the directory specified by path exists. If not then make it so. + Here is a snowman ☃

+
(defn ensure-directory!
+  [path]
+  (when-not (ls path)
+    (mkdir path)))

Many Marginalia fns use dir? to recursively search a filepath.

+
(defn dir?
+  [path]
+  (.isDirectory (io/file path)))

Returns a string containing the files extension.

+
(defn find-file-extension
+  [^File file]
+  (second (re-find #"\.([^.]+)$" (.getName file))))

Predicate. Returns true for "normal" files with a file extension which +passes the provided predicate.

+
(defn processable-file?
+  [pred ^File file]
+  (when (.isFile file)
+    (-> file find-file-extension pred)))

Returns a seq of processable file paths (strings) in alphabetical order by +namespace.

+
(defn find-processable-file-paths
+  [dir pred]
+  (->> (io/file dir)
+       (file-seq)
+       (filter (partial processable-file? pred))
+       (sort-by parse-ns)
+       (map #(.getCanonicalPath ^File %))))

Project Info Parsing

+ +

Marginalia will parse info out of your project.clj to display in +the generated html file's header.

+

Parses a project.clj file and returns a map in the following form

+ +
 {:name
+  :version
+  :dependencies
+  :dev-dependencies
+  etc...}
+
+ +

by merging into the name and version information the rest of the defproject +forms (:dependencies, etc)

+
(defn parse-project-form
+  [[_ project-name version-number & attributes]]
+  (merge {:name    (str project-name)
+	  :version version-number}
+	 (apply hash-map attributes)))

Parses a project file -- './project.clj' by default -- and returns a map + assembled according to the logic in parse-project-form.

+
(defn parse-project-file
+  ([] (parse-project-file "./project.clj"))
+  ([path]
+      (try
+        (let [rdr (clojure.lang.LineNumberingPushbackReader.
+                    (FileReader.
+                     (io/file path)))]
+          (loop [line (read rdr)]
+            (let [found-project? (= 'defproject (first line))]
+              (if found-project?
+                (parse-project-form line)
+                (recur (read rdr))))))
+	(catch Exception e
+          (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
+  [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)})
+
(defn multidoc!
+  [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)]
+    (doseq [f (conj pages {:name     (io/file output-dir "toc.html")
+                           :contents index})]
+           (spit (:name f) (:contents f)))))

Generates an uberdoc html file from 3 pieces of information:

+ +
    +
  1. The path to spit the result (output-file-name)
  2. +
  3. Results from processing source files (path-to-doc)
  4. +
  5. Project metadata as a map, containing at a minimum the following: +
    • :name
    • +
    • :version
  6. +
+
(defn uberdoc!
+  [output-file-name files-to-analyze props]
+  (let [source (uberdoc-html
+                props
+                (map path-to-doc files-to-analyze))]
+    (spit output-file-name source)))

External Interface (command-line, lein, cake, etc)

+

These functions support Marginalia's use by client software or command-line +users.

+
+
(def ^:private file-extensions #{"clj" "cljs" "cljx" "cljc"})

Given a collection of filepaths, returns a lazy sequence of filepaths to all + .clj, .cljs, .cljx, and .cljc files on those paths: directory paths will be searched + recursively for files.

+
(defn format-sources
+  [sources]
+  (if (nil? sources)
+    (find-processable-file-paths "./src" file-extensions)
+    (->> sources
+         (mapcat #(if (dir? %)
+                    (find-processable-file-paths % file-extensions)
+                    [(.getCanonicalPath (io/file %))])))))
+
(defn split-deps [deps]
+  (when deps
+    (for [d (str/split deps #";")
+          :let [[group artifact version] (str/split d #":")]]
+      [(if (= group artifact) artifact (str group "/" artifact))
+       version])))

Check if a source file is excluded from the generated documentation

+
(defn source-excluded?
+  [source opts]
+  (if-not (empty?
+           (filter #(if (re-find (re-pattern %) source)
+                      true
+                      false)
+                   (-> opts :marginalia :exclude)))
+    true
+    false))

Default generation: given a collection of filepaths in a project, find the .clj + files at these paths and, if Clojure source files are found:

+ +
    +
  1. Print out a message to std out letting a user know which files are to be processed;
  2. +
  3. Create the docs directory inside the project folder if it doesn't already exist;
  4. +
  5. Call the uberdoc! function to generate the output file at its default location, +using the found source files and a project file expected to be in its default location.

    + +

    If no source files are found, complain with a usage message.

  6. +
+
(defn run-marginalia
+  [args & [project]]
+  (let [[{:keys [dir file name 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 <group1>:<artifact1>:<version1>;<group2>...
+                 If not given will be taken from project.clj"]
+             ["-c" "--css"
+              "Additional css resources <resource1>;<resource2>;...
+                 If not given will be taken from project.clj."]
+             ["-j" "--js"
+              "Additional javascript resources <resource1>;<resource2>;...
+                 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 <file1>;<file2>;...
+                 If not given will be taken from project.clj"]
+             ["-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
+                 then also exclude them from the source code display." :flag true])
+        sources (distinct (format-sources (seq files)))
+        sources (if leiningen (cons leiningen sources) sources)]
+    (if-not sources
+      (do
+        (println "Wrong number of arguments passed to Marginalia.")
+        (println help))
+      (binding [*lift-inline-comments*   lift-inline-comments
+                *delete-lifted-comments* exclude-lifted-comments]
+        (let [project-clj (or project
+                              (when (.exists (io/file "project.clj"))
+                                (parse-project-file)))
+              choose #(or %1 %2)
+              marg-opts (merge-with choose
+                                    {:css        (when css (str/split css #";"))
+                                     :javascript (when js (str/split js #";"))
+                                     :exclude    (when exclude (str/split exclude #";"))
+                                     :leiningen  leiningen}
+                                    (:marginalia project-clj))
+              opts (merge-with choose
+                               {:name         name
+                                :version      version
+                                :description  desc
+                                :dependencies (split-deps deps)
+                                :multi        multi
+                                :marginalia   marg-opts}
+                               project-clj)
+              sources (->> sources
+                           (filter #(not (source-excluded? % opts)))
+                           (into []))]
+          (println "Generating Marginalia documentation for the following source files:")
+          (doseq [s sources]
+            (println "  " s))
+          (println)
+          (ensure-directory! dir)
+          (if multi
+            (multidoc! dir sources opts)
+            (uberdoc! (str dir "/" file) sources opts))
+          (println "Done generating your documentation in" dir)
+          (println ""))))))
 

Utilities for converting parse results into html.

+
(ns marginalia.html
+  (:use [marginalia.hiccup :only (html escape-html)])
+  (:require [clojure.string :as str])
+  (:import [com.petebevin.markdown MarkdownProcessor]))
+
(def ^{:dynamic true} *resources* "./vendor/")
+
(defn css-rule [rule]
+  (let [sels (reverse (rest (reverse rule)))
+        props (last rule)]
+    (str (apply str (interpose " " (map name sels)))
+         "{" (apply str (map #(str (name (key %)) ":" (val %) ";") props)) "}")))

Quick and dirty dsl for inline css rules, similar to hiccup.

+ +

ex. (css [:h1 {:color "blue"}] [:div.content p {:text-indent "1em"}])

+ +

-> h1 {color: blue;} div.content p {text-indent: 1em;}

+
(defn css
+  [& rules]
+  (html [:style {:type "text/css"}
+         (apply str (map css-rule rules))]))

Stolen from leiningen

+
(defn slurp-resource
+  [resource-name]
+  (try
+    (-> (.getContextClassLoader (Thread/currentThread))
+        (.getResourceAsStream resource-name)
+        (java.io.InputStreamReader.)
+        (slurp))
+    (catch java.lang.NullPointerException npe
+      (println (str "Could not locate resources at " resource-name))
+      (println "    ... attempting to fix.")
+      (let [resource-name (str *resources* resource-name)]
+        (try
+          (-> (.getContextClassLoader (Thread/currentThread))
+              (.getResourceAsStream resource-name)
+              (java.io.InputStreamReader.)
+              (slurp))
+          (catch java.lang.NullPointerException npe
+            (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-css [resource]
+  (let [src (slurp-resource resource)]
+    (html [:style {:type "text/css"}
+           (slurp-resource resource)])))

The following functions handle preparation of doc text (both comment and docstring +based) for display through html & css.

+

Markdown processor.

+
(def mdp (com.petebevin.markdown.MarkdownProcessor.))

Markdown string to html converter. Translates strings like:

+ +

"# header!" -> "<h1>header!</h1>"

+ +

"## header!" -> "<h2>header!</h2>"

+ +

...

+
(defn md
+  [s]
+  (.markdown mdp s))

As a result of docifying then grouping, you'll end up with a seq like this one:

+ +
[...
+{:docs [{:docs-text "Some doc text"}]
+ :codes [{:code-text "(def something \"hi\")"}]}
+...]
+ +

docs-to-html and codes-to-html convert their respective entries into html, +and group-to-html calls them on each seq item to do so.

+

Converts a docs section to html by threading each doc line through the forms + outlined above.

+ +

ex. (docs-to-html [{:doc-text "# hello world!"} {:docstring-text "I'm a docstring!}])

+ +

-> "<h1>hello world!</h1><br />"

+
(defn docs-to-html
+  [docs]
+  (-> docs
+      str
+      (md)))
+
(defn codes-to-html [code-block]
+  (html [:pre {:class "brush: clojure"}
+         (escape-html code-block)]))
+
(defn section-to-html [section]
+  (html [:tr
+         [:td {:class "docs"} (docs-to-html
+                               (if (= (:type section) :comment)
+                                 (:raw section)
+                                 (:docstring section)))]
+         [:td {:class "codes"} (if (= (:type section) :code)
+                                  (codes-to-html (:raw section)))]]))
+
(defn dependencies-html [deps & header-name]
+  (when-let [deps (seq deps)]
+    (let [header-name (or header-name "dependencies")]
+      (html [:div {:class "dependencies"}
+             [:h3 header-name]
+             [:table
+              (map #(html [:tr
+                           [:td {:class "dep-name"} (str (first %))]
+                           [:td {:class "dotted"} [:hr]]
+                           [:td {:class "dep-version"} (second %)]])
+                   deps)]]))))

Load Optional Resources

+ +

Use external Javascript and CSS in your documentation. For example: +To format Latex math equations, download the +MathJax Javascript library to the docs +directory and then add

+ +
:marginalia {:javascript ["mathjax/MathJax.js"]}
+
+ +

to project.clj. Below is a simple example of both inline and block +formatted equations.

+ +

Optionally, you can put the MathJax CDN URL directly as a value of :javascript +like this:

+ +
:marginalia {
+  :javascript 
+    ["http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"]}
+
+ +

That way you won't have to download and carry around the MathJax library.

+ +

When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

+

Generate script and link tags for optional external javascript and css.

+
(defn opt-resources-html
+  [project-info]
+  (let [options (:marginalia project-info)
+        javascript (:javascript options)
+        css (:css options)]
+    (html (concat
+           (when javascript
+             (map #(vector :script {:type "text/javascript" :src %}) javascript))
+           (when css
+             (map #(vector :link {:tyle "text/css" :rel "stylesheet" :href %}) 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]
+  (html
+   [:tr
+    [:td {:class "docs"}
+     [:div {:class "header"}
+      [:h1 {:class "project-name"} (if (seq (:url project-info))
+                                     [:a {:href (:url project-info)} (:name project-info)]
+                                     (:name project-info))]
+      [:h2 {:class "project-version"} (:version project-info)]
+      [:br]
+      (md (:description project-info))]
+     (dependencies-html (:dependencies project-info))
+     (dependencies-html (:dev-dependencies project-info) "dev dependencies")]
+    [:td {:class "codes"
+          :style "text-align: center; vertical-align: middle;color: #666;padding-right:20px"}
+     [:br]
+     [:br]
+     [:br]
+     "(this space intentionally left almost blank)"]]))

Creates an 'a' tag pointing to the namespace-name, either as an anchor (if +anchor? is true) or as a link to a separate $namespace-name.html file. +If attrs aren't empty, they are added to the resulting tag.

+
(defn link-to-namespace
+  [namespace-name anchor? & attrs]
+  [:a (into {:href (if anchor?
+                   (str "#" namespace-name)
+                   (str namespace-name ".html"))}
+            attrs)
+   namespace-name])

This is a hack, as in the case when anchor? is false, the link will contain +a reference to toc.html which might not even exist.

+
(defn link-to-toc
+  [anchor?]
+  (link-to-namespace "toc" anchor? {:class "toc-link"}))
+
(defn toc-html [props docs]
+  (html
+   [:tr
+    [:td {:class "docs"}
+     [:div {:class "toc"}
+      [:a {:name "toc"} [:h3 "namespaces"]]
+      [:ul
+       (map #(vector :li (link-to-namespace (:ns %) (:uberdoc? props)))
+            docs)]]]
+    [:td {:class "codes"} "&nbsp;"]]))
+
(defn floating-toc-html [docs]
+  [:div {:id "floating-toc"}
+   [:ul
+    (map #(vector :li {:class "floating-toc-li"
+                       :id (str "floating-toc_" (:ns %))}
+                  (:ns %))
+         docs)]])
+
(defn groups-html [props doc]
+  (html
+   [:tr
+    [:td {:class "docs"}
+     [:div {:class "docs-header"}
+      [:a {:class "anchor" :name (:ns doc) :href (str "#" (:ns doc))}
+       [:h1 {:class "project-name"}
+        (:ns doc)]
+       (link-to-toc (:uberdoc? props))]]]
+    [:td {:class "codes"}]]
+   (map section-to-html (:groups doc))
+   [:tr
+    [:td {:class "spacer docs"} "&nbsp;"]
+    [:td {:class "codes"}]]))
+
(def reset-css
+  (css [:html {:margin 0 :padding 0}]
+       [:h1 {:margin 0 :padding 0}]
+       [:h2 {:margin 0 :padding 0}]
+       [:h3 {:margin 0 :padding 0}]
+       [:h4 {:margin 0 :padding 0}]
+       [:a {:color "#261A3B"}]
+       [:a:visited {:color "#261A3B"}]))
+
(def header-css
+  (css [:.header {:margin-top "30px"}]
+       [:h1.project-name {:font-size "34px"
+                          :display "inline"}]
+       [:h2.project-version {:font-size "18px"
+                             :margin-top 0
+                             :display "inline"
+                             :margin-left "10px"}]
+       [:.toc-link {:font-size "12px"
+                    :margin-left "10px"
+                    :color "#252519"
+                    :text-decoration "none"}]
+       [:.toc-link:hover {:color "#5050A6"}]
+       [:.toc :h1 {:font-size "34px"
+                   :margin 0}]
+       [:.docs-header {:border-bottom "dotted #aaa 1px"
+                       :padding-bottom "10px"
+                       :margin-bottom "25px"}]
+       [:.toc :h1 {:font-size "24px"}]
+       [:.toc {:border-bottom "solid #bbb 1px"
+               :margin-bottom "40px"}]
+       [:.toc :ul {:margin-left "20px"
+                   :padding-left "0px"
+                   :padding-top 0
+                   :margin-top 0}]
+       [:.toc :li {:list-style-type "none"
+                   :padding-left 0}]
+       [:.dependencies {}]
+       [:.dependencies :table {:font-size "16px"
+                               :width "99.99%"
+                               :border "none"
+                               :margin-left "20px"}]
+       [:.dependencies :td {:padding-right "20px;"
+                            :white-space "nowrap"}]
+       [:.dependencies :.dotted {:width "99%"}]
+       [:.dependencies :.dotted :hr {:height 0
+                                     :noshade "noshade"
+                                     :color "transparent"
+                                     :background-color "transparent"
+                                     :border-bottom "dotted #bbb 1px"
+                                     :border-top "none"
+                                     :border-left "none"
+                                     :border-right "none"
+                                     :margin-bottom "-6px"}]
+       [:.dependencies :.dep-version {:text-align "right"}]
+       [:.plugins :ul {:margin-left "20px"
+                       :padding-left "0px"
+                       :padding-top 0
+                       :margin-top 0}]
+       [:.plugins :li {:list-style-type "none"
+                       :padding-left 0}]
+       [:.header :p {:margin-left "20px"}]))
+
(def floating-toc-css
+  (css [:#floating-toc {:position "fixed"
+                        :top "10px"
+                        :right "20px"
+                        :height "20px"
+                        :overflow "hidden"
+                        :text-align "right"}]
+       [:#floating-toc :li {:list-style-type "none"
+                            :margin 0
+                            :padding 0}]))
+
(def general-css
+  (css
+   [:body {:margin 0
+           :padding 0
+           :font-family "'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;"
+           :font-size "16px"
+           :color "#252519"
+           :background-color "#F5F5FF"}]
+   [:h1 {:font-size "20px"
+         :margin-top 0}]
+   [:h2 {:font-size "18px"}]
+   [:h3 {:font-size "16px"}]
+   [:a.anchor {:text-decoration "none"
+              :color "#252519"}]
+   [:a.anchor:hover {:color "#5050A6"}]
+   [:table {:border-spacing 0
+            :border-bottom "solid #ddd 1px;"
+            :margin-bottom "10px"}]
+   [:code {:display "inline"}]
+   [:p {:margin-top "8px"}]
+   [:tr {:margin "0px"
+         :padding "0px"}]
+   [:td.docs {:width "410px"
+              :max-width "410px"
+              :vertical-align "top"
+              :margin "0px"
+              :padding-left "55px"
+              :padding-right "20px"
+              :border "none"
+              :background-color "#FFF"}]
+   [:td.docs :pre {:font-size "12px"
+                   :overflow "hidden"}]
+   [:td.codes {:width "55%"
+               :background-color "#F5F5FF"
+               :vertical-align "top"
+               :margin "0px"
+               :padding-left "20px"
+               :border "none"
+               :overflow "hidden"
+               :font-size "10pt"
+               :border-left "solid #E5E5EE 1px"}]
+   [:td.spacer {:padding-bottom "40px"}]
+   [:pre :code {:display "block"
+                :padding "4px"}]
+   [:code {:background-color "ghostWhite"
+           :border "solid #DEDEDE 1px"
+           :padding-left "3px"
+           :padding-right "3px"
+           :font-size "14px"}]
+   [:.syntaxhighlighter :code {:font-size "13px"}]
+   [:.footer {:text-align "center"}]))

Notice that we're inlining the css & javascript for 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.

+
(defn page-template
+  [project-metadata opt-resources header toc content floating-toc]
+  (html
+   "<!DOCTYPE html>\n"
+   [:html
+    [:head
+     [:meta {:http-equiv "Content-Type" :content "text/html" :charset "utf-8"}]
+     [:meta {:name "description" :content (:description project-metadata)}]
+     (inline-css (str *resources* "shCore.css"))
+     (css
+      [:.syntaxhighlighter {:overflow "hidden !important"}])
+     (inline-css (str *resources* "shThemeMarginalia.css"))
+     reset-css
+     header-css
+     floating-toc-css
+     general-css
+     (inline-js (str *resources* "jquery-1.7.1.min.js"))
+     (inline-js (str *resources* "xregexp-min.js"))
+     (inline-js (str *resources* "shCore.js"))
+     (inline-js (str *resources* "shBrushClojure.js"))
+     opt-resources
+     [:title (:name project-metadata) " -- Marginalia"]]
+    [:body
+     [:table
+      header
+      toc
+      content]
+     [:div {:class "footer"}
+      "Generated by "
+      [:a {:href "https://github.com/gdeer81/marginalia"} "Marginalia"]
+      ".&nbsp;&nbsp;"
+      "Syntax highlighting provided by Alex Gorbatchev's "
+      [:a {:href "http://alexgorbatchev.com/SyntaxHighlighter/"}
+       "SyntaxHighlighter"]
+      floating-toc]
+     (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 +to do it in javascript.

+

This generates a stand alone html file (think lein uberjar). + It's probably the only var consumers will use.

+
(defn uberdoc-html
+  [project-metadata docs]
+  (page-template
+   project-metadata
+   (opt-resources-html project-metadata)
+   (header-html project-metadata)
+   (toc-html {:uberdoc? true} docs)
+   (map #(groups-html {:uberdoc? true} %) docs)
+   (floating-toc-html docs)))
+
(defn index-html
+  [project-metadata docs]
+  (page-template
+   project-metadata
+   (opt-resources-html project-metadata)
+   (header-html project-metadata)
+   (toc-html {:uberdoc? false} docs)
+      ;; no contents)) ;; no floating toc

no floating toc

+
+
(defn single-page-html
+  [project-metadata doc all-docs]
+  (page-template
+   project-metadata
+   (opt-resources-html project-metadata)
+    ;; no header
+    ;; no toc
+   (groups-html {:uberdoc? false} doc)
+    ;; no floating toc))
 

A place to examine poor parser behavior. These should go in tests when they get written.

+
(ns problem-cases.general)
+
[::foo]
+
{:foo 43}
+{::foo 42}

private docstring

+
(defn ^:private private-fn  [])

docstring

+
(defn public-fn  []
+  (let [x (private-fn)]
+        (count x)))

Should have only this comment in the left margin. +See https://github.com/gdeer81/marginalia/issues/4

+
+
(defn parse-bool [v] (condp = (.trim (str v))
+                         "0" false
+                         "1" true
+                         "throw exception here"))

Here is a docstring. It should be to the left.

+
(defn a-function 
+  [x]
+  (* x x))

Here is a docstring. It should be to the left.

+
(defn b-function
+  [x]
+  "Here is just a string.  It should be to the right."
+  (* x x))

Defines a relation... duh!

+
(defprotocol Relation
+  (select     [this predicate]
+    "Confines the query to rows for which the predicate is true
+     Ex. (select (table :users) (where (= :id 5)))")
+  (join       [this table2 join_on]
+    "Joins two tables on join_on
+     Ex. (join (table :one) (table :two) :id)
+         (join (table :one) (table :two)
+               (where (= :one.col :two.col)))"))

This is a defmulti docstring, it should also be on the left

+
(defmulti bazfoo
+  class)
+
(defmethod bazfoo String [s]
+  "This is a defmethod docstring.  It should be on the left."
+  (vec (seq s)))
+
(bazfoo "abc")

This is a protocol docstring. It should be on the left.

+
(defprotocol Foo
+  (lookup  [cache e])
+  (has?    [cache e] )
+  (hit     [cache e])
+  (miss    [cache e ret]))

This is also a docstring via metadata. It should be on the left.

+
(def 
+  a 42)

This is also a docstring via metadata. It should be on the left.

+
(def 
+  b 42)

This is also a docstring via metadata. It should be on the left.

+
(def 
+  c
+  "This is just a value.  It should be on the right.")

From fnparse

+

Padded on the front with optional whitespace.

+
(comment
+  (do-template [rule-name token]
+               (h/defrule rule-name
+                 (h/lit token))
+               <escape-char-start> \\
+               <str-delimiter>   \"
+               <value-separator> \,
+               <name-separator>  \:
+               <array-start>     \[
+               <array-end>       \]
+               <object-start>    \{
+               <object-end>      \}))

Issue #26: Angle-bracket in Function Name Breaks Layout

+
(defn <test [] nil)

+

(defn test-html-entities-in-doc
+  []
+  nil)
+
(defmulti kompile identity)
+
(defmethod kompile [:standard]
+  [_]
+  "GENERATED ALWAYS AS IDENTITY")

strict-eval-op-fn is used to define functions of the above pattern for functions such as +, *, etc. Cljs special forms defined this way are applyable, such as (apply + [1 2 3]).

+ +

Resulting expressions are wrapped in an anonymous function and, down the line, called, like so:

+ +
 (+ 1 2 3) -> (function(){...}.call(this, 1 2 3)
+
+
(defn strict-eval-op-fn
+  [op inc-ind-str ind-str op nl]
+  (ind-str
+   "(function() {" nl
+   (inc-ind-str
+    "var _out = arguments[0];" nl
+    "for(var _i=1; _i<arguments.length; _i++) {" nl
+    (inc-ind-str
+     "_out = _out " op " arguments[_i];")
+    nl
+    "}" nl
+    "return _out;")
+   nl
+   "})"))
+
'(defn special-forms []
+  {'def     handle-def
+   'fn      handle-fn
+   'fn*     handle-fn
+   'set!    handle-set
+   'let     handle-let
+   'defn    handle-defn
+   'aget    handle-aget
+   'aset    handle-aset
+   'if      handle-if
+   'while   handle-while
+   'when    handle-when
+   'doto    handle-doto
+   '->      handle-->
+   '->>     handle-->>
+   'not     handle-not
+   'do      handle-do
+   'cond    handle-cond
+   '=       (make-lazy-op '==)
+   '>       (make-lazy-op '>)
+   '<       (make-lazy-op '<)
+   '>=      (make-lazy-op '>=)
+   '<=      (make-lazy-op '<=)
+   'or      (make-lazy-op '||)
+   'and     (make-lazy-op '&&)
+   'doseq   handle-doseq
+   'instanceof handle-instanceof
+   'gensym handle-gensym
+   'gensym-str handle-gensym-str})
+
'(defn greater [a b]
+  (>= a b))
+
'(fact
+  (greater 2 1) => truthy)
+
'(file->tickets commits)
+
(defmulti ns-kw-mm identity)
+(defmethod ns-kw-mm ::foo [_] :problem-cases.general/foo)
+(defmethod ns-kw-mm :user/foo [_] :user/foo)
+(defmethod ns-kw-mm :foo [_] :foo)
 
\ No newline at end of file diff --git a/project.clj b/project.clj index dcb533ac1..719a49a83 100644 --- a/project.clj +++ b/project.clj @@ -13,4 +13,11 @@ ;;Needed for testing Latex equation formatting. You must download ;;and install MathJax in you doc directory. - :marginalia {:javascript ["mathjax/MathJax.js"]}) + :marginalia {:javascript ["mathjax/MathJax.js"]} + + :aliases {"docs" ["run" "-m" "marginalia.main" + ;; leiningen/marg.clj ?? + "src/marginalia/core.clj" + "src/marginalia/html.clj" + ;; "src/marginalia/tasks.clj" + "src/problem_cases/general.clj"]})