From fdfb3a881caaee3375d87b19aca88dc245f7e5ba Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 31 Jan 2024 22:23:53 -0800 Subject: [PATCH] fix #186; prep for 0.9.2 Signed-off-by: Sean Corfield --- .github/workflows/test.yml | 10 +- CHANGELOG.md | 14 + README.md | 10 +- docs/uberdoc.html | 3811 ------------------------------------ project.clj | 10 +- src/marginalia/html.clj | 24 +- 6 files changed, 51 insertions(+), 3828 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 docs/uberdoc.html diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 457c59ec..6eabaadf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,18 +12,18 @@ jobs: build-clj: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: "Setup Java 17" - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' - name: Setup Clojure - uses: DeLaGuardo/setup-clojure@12.1 + uses: DeLaGuardo/setup-clojure@12.3 with: - lein: 2.10.0 + lein: 2.11.1 - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: "m2-${{ hashFiles('project.clj') }}" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..08dbd496 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# CHANGES + +0.9.2 -- 01-31-2024 +* Add `:meta` support to address [#186](https://github.com/clj-commons/marginalia/issues/186). +* Update all dependencies. + +0.9.1 -- 11-08-2017 +* Update to Clojure 1.9.0. + +0.9.0 -- 03-16-2016 +* Bug fixes & documentation updates. + +0.8.0 -- 08-28-2014 +* Improve ClojureScript support, bug fixes, and documentation updates. diff --git a/README.md b/README.md index ecd6404b..81c0eed3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Marginalia 0.9.1 +Marginalia 0.9.2 ================ [![Clojars Project](https://img.shields.io/clojars/v/marginalia.svg)](https://clojars.org/marginalia) @@ -13,7 +13,7 @@ Marginalia is a source code documentation tool that parses Clojure and ClojureSc To get a quick look at what the Marginalia output looks like, [visit the official site](https://clj-commons.org/marginalia/). -**[View the release notes for this version of Marginalia](https://github.com/clj-commons/marginalia/blob/master/docs/release-notes/marginalia-v0.9.1-release-notes.markdown)** +**[View the release notes for this version of Marginalia](https://github.com/clj-commons/marginalia/releases/tag/v0.9.2)** Usage ----- @@ -26,8 +26,8 @@ Currently Marginalia can be used in a number of ways as described below. To use Marginalia with Leiningen add the following code to the project's `project.clj` file: -With Leiningen 1.x, add `[lein-marginalia "0.9.1"]` to your project.clj's `:dev-dependencies` argument of the `defproject` function, then run `lein deps`. -With Leiningen 2.x, add `[[lein-marginalia "0.9.1"]]` to the `:plugins` entry in either your project.clj file or your `:user` profile. +With Leiningen 1.x, add `[lein-marginalia "0.9.2"]` to your project.clj's `:dev-dependencies` argument of the `defproject` function, then run `lein deps`. +With Leiningen 2.x, add `[[lein-marginalia "0.9.2"]]` to the `:plugins` entry in either your project.clj file or your `:user` profile. See the [lein-marginalia](https://github.com/clj-commons/lein-marginalia) page for more details. Once installed, you can generate your complete source documentation with the command: @@ -132,6 +132,6 @@ If I've missed your name then please ping me. License ------- -Copyright (C) 2010-2017 Gary, Fogus and contributors. +Copyright (C) 2010-2024 Sean Corfield, Gary Deer, Fogus and contributors. Distributed under the Eclipse Public License, the same as Clojure. diff --git a/docs/uberdoc.html b/docs/uberdoc.html deleted file mode 100644 index abe11e21..00000000 --- a/docs/uberdoc.html +++ /dev/null @@ -1,3811 +0,0 @@ - -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)
 
-
(ns leiningen.marg)

Support eval-in-project in both Leiningen 1.x and 2.x.

-
(defn eval-in-project
-  [project form init]
-  (let [[eip two?] (or (try (require 'leiningen.core.eval)
-                            [(resolve 'leiningen.core.eval/eval-in-project)
-                             true]
-                            (catch java.io.FileNotFoundException _))
-                       (try (require 'leiningen.compile)
-                            [(resolve 'leiningen.compile/eval-in-project)]
-                            (catch java.io.FileNotFoundException _)))]
-    (if two?
-      (eip project form init)
-      (eip project form nil nil init))))
-
(def dep ['marginalia "0.9.1"])
-
(defn- add-marg-dep [project]
-  ;; Leiningen 2 is a bit smarter about only conjing it in if it
-  ;; doesn't already exist and warning the user.
-  (if-let [conj-dependency (resolve 'leiningen.core.project/conj-dependency)]
-    (conj-dependency project dep)
-    (update-in project [:dependencies] conj dep)))

Note:

- -

The docstring for the marg function is used by Leiningen when a -user types lein help marg. Because of this, and because, for -instance, an escaped asterisk in a docstring will cause errors when -you attempt to run lein marg, some extra care (read: hacking) is -required to get this docstring to look reasonable both in the lein -help output and in a marginalia uberdoc.

-

Run Marginalia against your project source files.

- -

Usage:

- -
lein marg <options> <files>
-
- -

Marginalia accepts options as described below:

- -

-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 (Taken from project.clj by default.)

- -

-v --version Project version (Taken from project.clj by default.)

- -

-D --desc Project description (Taken from project.clj by default.)

- -

-a --deps Project dependencies in the form <group1>:<artifact1>:<version1>;<group2>... - (Taken from project.clj by default.)

- -

-c --css Additional css resources <resource1>;<resource2>;... - (Taken from project.clj by default.)

- -

-j --js Additional javascript resources <jsfile1>;<jsfile2>;... - (Taken from project.clj by default.)

- -

-m --multi Generate each namespace documentation as a separate file

-
(defn marg
-  [project & args]
-  (eval-in-project (add-marg-dep project)
-                   `(binding [marginalia.html/*resources* ""]
-                      (marginalia.core/run-marginalia (list ~@args)))
-                   '(require 'marginalia.core)))
 

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/clj-commons/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/clj-commons/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 b4a46a2a..5568a6ee 100644 --- a/project.clj +++ b/project.clj @@ -1,12 +1,12 @@ -(defproject marginalia "0.9.2-SNAPSHOT" +(defproject marginalia "0.9.2" :description "lightweight literate programming for clojure -- inspired by [docco](http://jashkenas.github.com/docco/)" ;; :main marginalia.main :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"] + [org.clojure/clojurescript "1.11.132"] + [org.clojure/tools.namespace "1.4.5"] + [org.clojure/tools.cli "1.0.219"] + [org.markdownj/markdownj-core "0.4"] [de.ubercode.clostache/clostache "1.4.0"]] :resource-paths ["vendor"] diff --git a/src/marginalia/html.clj b/src/marginalia/html.clj index 54926d26..abb7fd47 100644 --- a/src/marginalia/html.clj +++ b/src/marginalia/html.clj @@ -120,16 +120,35 @@ [:td {:class "dep-version"} (second %)]]) deps)]])))) + +;; # Generate Optional Metadata +;; Add metadata to your documentation. +;; +;; To add to the head of the +;; docs, specify a hash map for the :meta key :marginalia in project.clj: +;; +;; :marginalia {:meta {:robots "noindex"}} + +(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))))) + ;; # Load Optional Resources ;; Use external Javascript and CSS in your documentation. For example: +;; ;; To format Latex math equations, download the ;; [MathJax](http://www.mathjax.org/) 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. +;; to project.clj. :javascript and :css accept a vector of paths or URLs +;; +;; 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: @@ -364,6 +383,7 @@ [:head [:meta {:http-equiv "Content-Type" :content "text/html" :charset "utf-8"}] [:meta {:name "description" :content (:description project-metadata)}] + (metadata-html project-metadata) (inline-css (str *resources* "shCore.css")) (css [:.syntaxhighlighter {:overflow "hidden !important"}])