From 1e257ba0a1cf7bdc6d06a12c942a3532693b4635 Mon Sep 17 00:00:00 2001 From: fogus Date: Wed, 5 Jan 2011 09:06:54 -0500 Subject: [PATCH] Added official site --- .gitignore | 2 + site/README | 10 + site/Rakefile | 69 + site/config.yaml | 35 + site/ext/init.rb | 10 + site/src/browserfix.css | 7 + site/src/default.css | 16 + site/src/default.template | 114 ++ site/src/images/labyrinth.ico | Bin 0 -> 2870 bytes site/src/images/spacer.gif | Bin 0 -> 43 bytes site/src/index.page | 82 + site/src/uberdoc.html | 3263 +++++++++++++++++++++++++++++++++ 12 files changed, 3608 insertions(+) create mode 100644 site/README create mode 100644 site/Rakefile create mode 100644 site/config.yaml create mode 100644 site/ext/init.rb create mode 100644 site/src/browserfix.css create mode 100644 site/src/default.css create mode 100644 site/src/default.template create mode 100644 site/src/images/labyrinth.ico create mode 100644 site/src/images/spacer.gif create mode 100644 site/src/index.page create mode 100644 site/src/uberdoc.html diff --git a/.gitignore b/.gitignore index cb73c3c0..5dd4b22b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ pom.xml *jar lib classes +out +webgen.cache diff --git a/site/README b/site/README new file mode 100644 index 00000000..331d532a --- /dev/null +++ b/site/README @@ -0,0 +1,10 @@ +description: + This skeleton of a webgen website provides a set of default files for every + created webgen website. + + When using the standard settings, the sources are in the directory `src` and + the generated output goes into `out`. Extensions can be placed under `ext`. + + For configuration purposes, use the config.yaml file. +--- +note: This file can be deleted! diff --git a/site/Rakefile b/site/Rakefile new file mode 100644 index 00000000..0181af49 --- /dev/null +++ b/site/Rakefile @@ -0,0 +1,69 @@ +# -*- ruby -*- +# +# This is a sample Rakefile to which you can add tasks to manage your website. For example, users +# may use this file for specifying an upload task for their website (copying the output to a server +# via rsync, ftp, scp, ...). +# +# It also provides some tasks out of the box, for example, rendering the website, clobbering the +# generated files, an auto render task,... +# + +require 'webgen/webgentask' +require 'webgen/website' + +task :default => :webgen + +webgen_config = lambda do |config| + # you can set configuration options here +end + +Webgen::WebgenTask.new do |website| + website.clobber_outdir = true + website.config_block = webgen_config +end + +desc "Show outdated translations" +task :outdated do + puts "Listing outdated translations" + puts + puts "(Note: Information is taken from the last webgen run. To get the" + puts " useful information, run webgen once before this task!)" + puts + + website = Webgen::Website.new(Dir.pwd, Webgen::Logger.new($stdout), &webgen_config) + website.execute_in_env do + website.init + website.tree.node_access[:acn].each do |acn, versions| + main = versions.find {|v| v.lang == website.config['website.lang']} + next unless main + outdated = versions.select do |v| + main != v && main['modified_at'] > v['modified_at'] + end.map {|v| v.lang}.join(', ') + puts "ACN #{acn}: #{outdated}" if outdated.length > 0 + end + end +end + +desc "Render the website automatically on changes" +task :auto_webgen do + puts 'Starting auto-render mode' + time = Time.now + abort = false + old_paths = [] + Signal.trap('INT') {abort = true} + + while !abort + # you may need to adjust the glob so that all your sources are included + paths = Dir['src/**/*'].sort + if old_paths != paths || paths.any? {|p| File.mtime(p) > time} + begin + Rake::Task['webgen'].execute({}) + rescue Webgen::Error => e + puts e.message + end + end + time = Time.now + old_paths = paths + sleep 2 + end +end diff --git a/site/config.yaml b/site/config.yaml new file mode 100644 index 00000000..f5280259 --- /dev/null +++ b/site/config.yaml @@ -0,0 +1,35 @@ +##### +# This is the YAML configuration file for webgen used to set configuration options. +# +# The general syntax is: +# +# configuration.option.name: value +# +# For example, to set a different default language, you would do: +# +# website.lang: de +# +# Have a look at the documentation of the individual configuration options to see the allowed format +# of the values. Since this is a YAML file, you can easily set configuration options as strings, +# integers, dates, arrays, hashes and more. +# +# The available configuration options can be found on the homepage in the Configuration Option +# Reference at +# +# http://webgen.rubyforge.org/documentation/reference_configuration.html +# +# Some common use cases are shown below. +##### + +## The default processing pipeline for page files. If you use a different markup language you need to +## change 'markdown' to the short name of the content processor for the markup language. +default_processing_pipeline: + Page: erb,tags,markdown,blocks,fragments + +## Setting the default language. The argument must be an ISO-639-1/2 language code. +# website.lang: de + +## Adding some extensions to the copy source handler. +# patterns: +# Copy: +# add: [**/*.pdf, **/*.djvu] diff --git a/site/ext/init.rb b/site/ext/init.rb new file mode 100644 index 00000000..996ed619 --- /dev/null +++ b/site/ext/init.rb @@ -0,0 +1,10 @@ +# = webgen extensions directory +# +# All init.rb files anywhere under this directory get automatically loaded on a webgen run. This +# allows you to add your own extensions to webgen or to modify webgen's core! +# +# If you don't need this feature you can savely delete this file and the directory in which it is! +# +# The +config+ variable below can be used to access the Webgen::Configuration object for the current +# website. +config = Webgen::WebsiteAccess.website.config diff --git a/site/src/browserfix.css b/site/src/browserfix.css new file mode 100644 index 00000000..374470fa --- /dev/null +++ b/site/src/browserfix.css @@ -0,0 +1,7 @@ +/**************** IE fixes ****************/ + +html +{overflow:hidden;} + +body +{height:100%; width:100%; overflow:auto;} \ No newline at end of file diff --git a/site/src/default.css b/site/src/default.css new file mode 100644 index 00000000..a8c522e4 --- /dev/null +++ b/site/src/default.css @@ -0,0 +1,16 @@ +html,body{margin:0;padding:0;height:100%;} +body{background:#F5F5DC;font-family:Georgia, Times, serifs;} +a:link{color:#B88A00;text-decoration:none;} +a:visited{color:#6B1B00;font-weight:900;} +#floater{position:relative;float:left;height:50%;margin-bottom:-300px;width:1px;} +#centered{position:relative;clear:left;height:600px;width:80%;max-width:800px;min-width:400px;margin:0 auto;} +#bottom{position:absolute;bottom:0;right:0;} +#nav{position:absolute;left:0;top:0;bottom:0;right:70%;padding:20px;margin:10px;} +#content{position:absolute;left:30%;right:0;top:0;bottom:0;overflow:auto;height:340px;padding:20px;margin:10px;} +.mainbookpage{width:95%;line-height:110%;font-size:11px;color:#333;background:#F5F5DC;} +p.lawbook{text-indent:25px;text-align:justify;padding-left:10px;padding-right:9px;} +.showme{border:1px solid #F5F5DC;} +.leftcol{float:left;width:47%;background:#F5F5DC;padding-top:10px;padding-right:0;padding-bottom:10px;padding-left:10px;} +.rightcol{float:right;width:47%;background:#F5F5DC;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:0;} +.rightmiddle{float:right;width:50%;font-size:12px;text-align:right;padding-top:10px;padding-right:0;padding-bottom:10px;padding-left:5px;} +.leftmiddle{float:left;width:50%;font-size:12px;text-align:left;padding-top:10px;padding-right:5px;padding-bottom:10px;padding-left:0;} \ No newline at end of file diff --git a/site/src/default.template b/site/src/default.template new file mode 100644 index 00000000..9981576a --- /dev/null +++ b/site/src/default.template @@ -0,0 +1,114 @@ +--- pipeline:erb,tags,blocks,head + + + + + Marginalia Home + + + + + + + + + + + +
+
+ + +
+ + + +
+ + +

+ +

+ +
+

+ +

+
+ +

+ +

+
+ + + +
+ +

+ +

+ + +
+

+
+ +

+ +

+
+ + + You will not see this line but it is needed. Take it out to see the background color beige + disappear from the center. Surely there is a better way. Thinkity, thinkity +
+
+ +
+

+ Copyright © 2011 Fogus based on a design by Mandarin Designs (RIP). +

+
+ + + + + + diff --git a/site/src/images/labyrinth.ico b/site/src/images/labyrinth.ico new file mode 100644 index 0000000000000000000000000000000000000000..1d6da7e2ff6d4bfdff93cea031d204f1490505c6 GIT binary patch literal 2870 zcmd5+jZ;%s7Jq~fN$$Jvy(9<-iY)7Py13IBb)0Prse+vkS`DlLd#fmQ3_TH2!wp{kw6ll(5|g+$If(icJ|M319lzhx;wkmo!Q=*^X@tC z-gEBnoO{m?K_ZZ7gdmXcoQhOMAqa&ayKHy(A3@N<>k-IAcw)VWsDd~}~j1_j7mNg|iuE?az8f}?f zr#|LZs80L3+E1=pnj}kYN{P2wb|RoP)ol&*Fkg-VqoWYFrYU>r`c-oNeKpTref7=9 z`n#e{t&R+8+Qq(WSB`pG>IMD|9nYt2<3GOLTQ?-3!c}=bo{S8ohippjW?xUOy}mvp zpl_*o(lccEVPK^A&%K{0xAf);_jP&3ve{C*Vm0S1Xj^mVnstJ0ZwMI3y4hG99O{10 zZfia7(>3w}ik?$X2Znfm9sj)V>EHnG+rh3=E_nmj->nn{x<4*k)@q6aW+TO>MMm_W zAz7;`3$8U*1rO7Ouz&olRxAk1XB!e;b>U|K;{@@zYNR>x0(L+(orH zZ(Xi0@HZ(7mK(Z+o(~(TkhVSVhr!{K!4{=-(_*-=WoZ*`_G$T(%J;t0slWr&S9UwH^6B zTl+bey0yY%?5y$_)#pMAE$=I%QLtt)3m27gK|rk}L;d>9M*99e-YW{>YO9X)s&S9jOc(rJ>hk$Exp>WH5JyMS zb{?wN(2?oY8*=AN240{~cX_Q(NBQ(x!NZFmaNKhFiC~+WAL=zqmTZ>t`A!|rt-Z$e zTC`_>d_D+_1<2;$&8mQ{F}%-^?bB&$ogda`F3Aj(U)@$@SIRhkou+#2hAn&JhKBN) zb=4dg_q<7d3^H<4&)T}xb7{kB;;k8Vr1Sc<9Jfkc;%=0cx)lm;z@#Y*^mP{oO~#8e z7Sq0eoc;1V(cH{hw{_MoH>)`zm8!_ms*w8I+a)WC_9BP8`JD}`wPr?DoA9#FPxWY8 z6cT7@J-*VWDxdm;TH1 zS{38*b0rX;&WnxVy^?2W;r!_A;beTTQwo@#GJp>sgT%yX44*qm#5*Mn_M`;FkDY)i z({(uR+{H6A8XW;(EE|Vjl8%n?ATjVJdL$%**;^#|okNVL_>KZFD}Zq`JTzgFPrN=; z$AHZ*c!mi_(lMrw3(%=rz#PqB9(Ps1Gmi|Y;QM08eWb(>`zo;H$qYdMV=Xvf&w2G+^5tcSz2-(BA#6D*}e)c;#TJW?O<9=UH z9B|~qgsD8@pr-&!o-TlIxPlumM}}AY>x;Y z@QP5(UJ2u-C0ODtAFy5T0_Io+zSmI-@hOS{z98|$Sqf*l$!OLn7D1zh>;JGge7|DI zeo)F_-7Ns|6FDe*rUqw^a4`0*Oo%xvVWK+=?w`H@StI3;F($_FS#eCno}Ipd^aq&? z#&|9uZu8LOvC9nR1jRt@0tWj&2PNiCFxVbG#O_HTK3)#+dprhxf{W7cz2KRMs3-(W zo~nXzK0bzzW&_M#i=nfbth5CRJLE0`dmnNb*xhOrjAmnrehN*rS0^y@cl9im*3W|b z+?Q~chX?Qll8#U0VEA+i9yePGSR-73%@jiHi^6ao7h}_gU&%8V-NM3uNeeXudgtHrOiWBnxM%Ih!}TwR2@j;$z6asHos$5<&WWa_ r?!x@P=b3*B|C~s|-@+H#|5w?~i~kz$vuy(Y1&sd`?=JxA4uro0Vz=?8 literal 0 HcmV?d00001 diff --git a/site/src/images/spacer.gif b/site/src/images/spacer.gif new file mode 100644 index 0000000000000000000000000000000000000000..f55d454174c86cf2a8bf76baec8c872de08c8992 GIT binary patch literal 43 rcmZ?wbhEHbWMp7uXkcUjg8%>jEB<5wG8q|kKzxu40~3=EBZD;nzy${b literal 0 HcmV?d00001 diff --git a/site/src/index.page b/site/src/index.page new file mode 100644 index 00000000..e2c4023e --- /dev/null +++ b/site/src/index.page @@ -0,0 +1,82 @@ +--- +title: Marginalia +routed_title: The Marginalia Manifesto +--- + +[user guide](https://github.com/fogus/marginalia/wiki) + +[example output](uberdoc.html) + +[source code](https://github.com/fogus/marginalia) + +[bug reports](https://github.com/fogus/marginalia/issues) + +#### Features + +* HTML generation from Clojure source +* Markdown support within comments and docstrings +* Latex formatting support (via [MathJax](http://www.mathjax.org/)) +* Leiningen and Cake support + +--- name:center_right + +**the one true way** + +1. Start by running Marginalia against your code +2. Cringe at the sad state of your code commentary +3. Add docstrings and code comments as appropriate +4. Generate the documentation again +5. Read the resulting documentation +6. Make changes to code and documentation so that the "dialog" flows sensibly +7. Repeat from step #4 until complete + + +--- name:top_right + +
+
+
+ +### 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. + +--- name:bottom_right + +### 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 crystalize 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. + +--- name:top_left + +# Marginalia + +([example output](uberdoc.html)) + +In late 2010 I was motivated by posts by [Tom Preston-Werner][rdd], [Reginald Braithwaite][reg], and [Luigi Montanez][ddd] to create a Clojure clone of the excellent [Docco](http://jashkenas.github.com/docco/) documentation generator written in [CoffeeScript](http://jashkenas.github.com/coffee-script/). The byproduct of this motivation was [Marginalia](https://github.com/fogus/marginalia). The goal of Marginalia was to create a full-blown [literate programming][literate] system with Docco-esque functionality as a way-station along the way. + +[reg]: https://github.com/raganwald/homoiconic/blob/master/2010/11/docco.md + +[literate]: http://www.literateprogramming.com/ + +I have a very strong opinion regarding the importance of clear and complete documentation. My reaction to poorly documented code, products, and services is visceral to the point that I often refuse to release even the most humble library without code comments, examples, tests, invariant definitions, a logo, and an "official website". + +[ddd]: http://luigimontanez.com/2010/reading-code-is-good-writing-documentation-is-better/ + +[rdd]: http://tom.preston-werner.com/2010/08/23/readme-driven-development.html + +--- name:bottom_left + +However, time is not always on my side for personal projects, so I am constantly looking for ways to minimize the amount of work required to generate well-documented software without sacrificing quality. Marginalia is a step in that direction. However, I piddled around with a comment parser and created the code that builds a tree of code-lines associated with comment lines. Having scratched that particular itch I then took a detour into researching literate programming proper, leaving behind a half-baked mess. + +### Enter Zachary Kim + +You may already know [Zachary Kim](http://zacharykim.com/) as the creator of the excellent [ClojureDocs](http://clojuredocs.org/) website; an invaluable resource for finding Clojure API examples. Zachary resurrected my original source for Marginalia and single-handedly molded it into a tool worth using. Marginalia would be nothing without his vision and hard-work. I would also like to thank [Justin Balthrop](http://ninjudd.com/) and [Brenton Ashworth](http://formpluslogic.blogspot.com/) for their support and code contributions. + +*If you find Marginalia useful (or even just cool), then please do visit [ClojureDocs](http://clojuredocs.org/) and donate some of your time to enhancing its existing examples and/or fill in the holes as you find them.* diff --git a/site/src/uberdoc.html b/site/src/uberdoc.html new file mode 100644 index 00000000..e91a15f1 --- /dev/null +++ b/site/src/uberdoc.html @@ -0,0 +1,3263 @@ + +Marginalia Output

marginalia

0.2.3


lightweight literate programming for clojure -- inspired by docco

+

dependencies

org.clojure/clojure
1.2.0
org.clojars.nakkaya/markdownj
1.0.2b4
hiccup
0.3.0

dev dependencies

lein-clojars
0.5.0-SNAPSHOT
jline
0.9.94
swank-clojure
1.2.1
hiccup
0.3.0
org.clojars.nakkaya/markdownj
1.0.2b4
marginalia
0.2.3

cake plugin namespaces

  • marginalia.tasks



(this space intentionally left blank)
 

Leiningen plugin for running marginalia against your project.

+ +

Usage

+ +
    +
  1. Add [marginalia "0.2.3"] to your project.clj's :dev-dependencies section.
  2. +
  3. run lein marg from your project's root directory.
  4. +
+
(ns leiningen.marg
+  (:use [marginalia.core]))
+
+(defn marg [project & args]
+  (run-marginalia args))

You can pass a file, directory, multiple files and/or directories to marginalia like so:

+ +
$ lein marg  # runs marginalia on all the cljs found in your ./src dir.
+$ lein marg ./path/to/files  # runs marginalia on all cljs found in ./path/to/files
+$ lein marg ./path/to/file.clj  # runs marginalia on ./path/to/file.clj only.
+$ lein marg ./path/to/one.clj ./path/to/another.clj
+$ lein marg ./path/to/dir ./path/to/some/random.clj
+
+ +

This allows you to control the order in which sections appear in the generated +documentation. For example, in marginalia's docs, the leiningen.marg namespace +forced to the bottom of the namespace ordering by using this command:

+ +
$ lein marg ./src/marginalia ./src/leiningen
+
+
+
+
 

Core provides all of the functionality around parsing clojure source files +into an easily consumable format.

+
(ns marginalia.core
+  (:require [clojure.java.io :as io]
+            [clojure.string  :as str])
+  (:use [marginalia.html :only (uberdoc-html)]
+        [clojure.contrib.find-namespaces :only (read-file-ns-decl)])
+  (:gen-class))
+
+
+
(def *test* "./src/cljojo/core.clj")
+
(def *docs* "docs")
+
(def *comment* #"^\s*;;\s?")
+
(def *divider-text* "\n;;DIVIDER\n")
+(def *divider-html* #"\n*<span class=\"c[1]?\">;;DIVIDER</span>\n*")

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 (java.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)))
+
+(defn dir? [path]
+  (.isDirectory (java.io.File. path)))

Returns a seq of clojure file paths (strings) in alphabetical depth-first order (I think?).

+
+(defn find-clojure-file-paths
+  [dir]
+  (->> (java.io.File. dir)
+       (file-seq)
+       (filter #(re-find #"\.clj$" (.getAbsolutePath %)))
+       (map #(.getAbsolutePath %))))

Project Info Parsing

+ +

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

+ +

TODO add pom.xml support.

+
+
+
+

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

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

by reading the defproject form from your project.clj to obtain name and +version, then merges in the rest of the defproject forms (:dependencies, etc).

+
+(defn parse-project-file
+  ([] (parse-project-file "./project.clj"))
+  ([path]
+      (try
+        (let [rdr (clojure.lang.LineNumberingPushbackReader.
+                   (java.io.FileReader.
+                    (java.io.File. path)))
+              project-form (read rdr)]
+          (merge {:name (str (second project-form))
+                  :version (nth project-form 2)}
+                 (apply hash-map (drop 3 project-form))))
+        (catch Exception e
+          (throw (Exception.
+                  (str
+                   "There was a problem reading the project definition from "
+                   path)))))))
+

Source File Analysis

+

This line should be replaced

+

and this one too!

+
(defn parse [src]
+  (for [line (line-seq src)]
+    (if (re-find *comment* line)
+      {:docs-text (str (str/replace line *comment* ""))}
+      {:code-text (str line)})))
+
+
+(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)))))

Hacktastic, these ad-hoc checks should be replaced with something

+

more robust.

+
(defn docstring-line? [line sections]
+  (let [l (last sections)
+        last-code-text (get l :code-text "")]
+    (try

Last line contain defn && +last line not contain what looks like a param vector && +current line start with a quote

+
      (or
+       (and (re-find #"\(defn" last-code-text)
+            (not (re-find #"\[.*\]" last-code-text))

Is the last line's code-text a deftask, and does the +current line start with a quote?

+
            (re-find #"^\"" (str/trim (str line))))
+       (and (re-find #"^\(deftask" (str/trim last-code-text))

Is the last line's code-text the start of a ns +decl, and does the current line start with a quote?

+
            (re-find #"^\"" (str/trim (str line))))
+       (and (re-find #"^\(ns" last-code-text)

Is the prev line a docstring, prev line not end with a quote, +and the current line empty?

+
            (re-find #"^\"" (str/trim (str line))))
+       (and (:docstring-text l)

Is the prev line a docstring, the prev line not end with a quote, +and the current line not an empty string?

+
            (not (re-find #"\"$" (str/trim (:docstring-text l)))))
+       (and (:docstring-text l)
+            (not (re-find #"[^\\]\"$" (str/trim (:docstring-text l))))
+            (= "" (str/trim (str line)))))
+      (catch Exception e nil))))
+
+
+(defn parse [src]
+  (loop [[line & more] (line-seq src) cnum 1 dnum 0 sections []]
+    (if line
+      (if (re-find *comment* line)
+        (recur more
+               cnum
+               (inc dnum)
+               (conj sections {:docs-text (str (str/replace line *comment* "")) :line (+ cnum dnum)}))
+        (recur more
+               (inc cnum)
+               0
+               (if (docstring-line? (str line) sections)
+                 (conj sections {:docstring-text (str line) :line cnum})
+                 (conj sections {:code-text (str line) :line cnum}))))
+      sections)))
+

How is this handled? +I wonder?

+

No idea ne

+
(defn gen-doc! [path]
+  (println "Generating documentation for " path)
+  (with-open [src (io/reader (io/file path))]

and this?

+
    (doseq [section (parse src)]
+      (println section))))
+
+(defn gen-doc! [path]
+  (with-open [src (io/reader (io/file path))]
+    (parse src)))
+
+(re-find *comment* "  ;; this is a comment")
+
+(defn path-to-doc [fn]
+  (let [ns (-> (java.io.File. fn)
+               (read-file-ns-decl)
+               (second)
+               (str))
+        groups (->> fn
+                    (gen-doc!)
+                    (group-lines))]
+    {:ns ns
+     :groups groups}))
+

Ouput Generation

+

Generates an uberdoc html file from 3 pieces of information:

+ +
    +
  1. Results from processing source files (path-to-doc)
  2. +
  3. Project metadata obtained from parse-project-file.
  4. +
  5. The path to spit the result (output-file-name)
  6. +
+
+(defn uberdoc!
+  [output-file-name files-to-analyze]
+  (let [docs (map path-to-doc files-to-analyze)
+        source (uberdoc-html
+                output-file-name
+                (parse-project-file)
+                (map path-to-doc files-to-analyze))]
+    (spit output-file-name source)))

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

+
+
+(defn format-sources [sources]
+  (if (nil? sources)
+    (find-clojure-file-paths "./src")
+    (->> sources
+         (map #(if (dir? %)
+                 (find-clojure-file-paths %)
+                 [%]))
+         (flatten))))
+
+(defn usage []
+  (println "marginalia <src1> ... <src-n>"))
+
+(defn run-marginalia [sources]
+  (let [sources (format-sources sources)]
+    (if-not sources
+      (do
+        (println "Wrong number of arguments passed to marginalia.")
+        (println "Please present paths to source files as follows:")
+        (usage))
+      (do
+        (println "Generating uberdoc for the following source files:")
+        (doseq [s sources]
+          (println "  " s))
+        (println)
+        (ensure-directory! "./docs")
+        (uberdoc! "./docs/uberdoc.html" sources)
+        (println "Done generating your docs, please see ./docs/uberdoc.html")
+        (println)))))

main docstring +Multi line

+
+(defn -main
+  [& sources]
+  (run-marginalia sources))
+

Example Usage

+

Command line example

+
(comment
+  (-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} {}))
+
 

Utilities for converting parse results into html.

+ +

Plus a few other goodies.

+ +
Here's a random code block (println "hi!")
+
+ +

Like I said:

+ +
    +
  • utils for docs → html
  • +
  • other goodies
  • +
+ +

hello world

+
(ns marginalia.html
+  (:use [hiccup.core :only (html escape-html)]
+        [hiccup.page-helpers :only (doctype)])
+  (:require [clojure.string :as str])
+  (:import [com.petebevin.markdown MarkdownProcessor]))
+
+
+(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]
+  (-> (.getContextClassLoader (Thread/currentThread))
+      (.getResourceAsStream resource-name)
+      (java.io.InputStreamReader.)
+      (slurp)))
+
+(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!

+
+(defn md 
+   -> \"<h1>header!</h1>"
+  [s]
+  (.markdown mdp s))

Inserts super-fancy characters into the doc section.

+
+(defn replace-special-chars
+  [s]
+  (-> s
+      (str/replace #"-&gt;"  "&rarr;")
+      (str/replace #"&quot;" "\"")))

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.

+
+
+(defn prep-docs-text [s] s)
+
+(defn prep-docstring-text [s]
+  (-> s
+      (str/replace #"\\\"" "\"")
+      (str/replace #"^\s\s\"" "")
+      (str/replace #"^\s\s\s" "")

Don't escape code blocks

+
      (str/replace #"\"$" "")
+      ((fn [t]
+         (if (re-find #"^\s\s\s\s" t)
+           t
+           (escape-html t))))))
+

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
+       (map #(if (:docs-text %)
+               (prep-docs-text (:docs-text %))
+               (prep-docstring-text (:docstring-text %))))
+       (map replace-special-chars)
+       (interpose "\n")
+       (apply str)
+       (md)))
+
+
+(defn codes-to-html [codes]
+  (html [:pre {:class "brush: clojure"}
+         (->> codes
+              (map :code-text)
+              (map escape-html)
+              (interpose "\n")
+              (apply str))]))
+
+(defn group-to-html [group]
+  (html
+   [:tr
+    [:td {:class "docs"} (docs-to-html (:docs group))]
+    [:td {:class "codes"} (codes-to-html (:codes group))]]))
+
+(defn dependencies-html [deps & header-name]
+  (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)]])))
+
+(defn cake-plugins-html [tasks]
+  (when tasks
+    (html [:div {:class "plugins"}
+           [:h3 "cake plugin namespaces"]
+           [:ul
+            (map #(vector :li %) tasks)]])))

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.

+ +

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

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"} (: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")
+     (cake-plugins-html (:tasks project-info))]
+    [:td {:class "codes"
+          :style "text-align: center; vertical-align: middle;color: #666;padding-right:20px"}
+     [:br]
+     [:br]
+     [:br]
+     "(this space intentionally left blank)"]]))
+
+(defn toc-html [docs]
+  (html
+   [:tr
+    [:td {:class "docs"}
+     [:div {:class "toc"}
+      [:a {:name "toc"} [:h3 "namespaces"]]
+      [:ul
+       (map #(vector :li [:a {:href (str "#" (:ns %))} (:ns %)])
+            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 [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)]
+       [:a {:href "#toc" :class "toc-link"}
+        "toc"]]]]
+    [:td {:class "codes"}]]
+   (map group-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"}]
+   [:h1 {:font-size "20px"
+         :margin-top 0}]
+   [: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"}]
+   [: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 prabably change in the future.

+
+(defn page-template
+  [opt-resources header toc floating-toc content]
+  (html
+   (doctype :html5)
+   [:html
+    [:head
+     (inline-js "jquery-1.4.4.min.js")
+     (inline-js "xregexp-min.js")
+     (inline-js "shCore.js")
+     (inline-js "shBrushClojure.js")
+     (inline-js "app.js")
+     #_[:script {:type "text/javascript" :src "./../resources/app.js"}]
+     (inline-css "shCore.css")
+     (css
+      [:.syntaxhighlighter {:overflow "hidden !important"}])
+     (inline-css "shThemeEclipse.css")
+     reset-css
+     header-css
+     floating-toc-css
+     general-css
+     opt-resources
+     [:title "Marginalia Output"]]
+    [:body
+     [:table
+      header
+      toc
+      content]
+     [:div {:class "footer"}
+      "Generated by "
+      [:a {:href "https://github.com/fogus/marginalia"} "marginalia"]
+      ".&nbsp;&nbsp;"
+      "Syntax highlighting provided by Alex Gorbatchev's "
+      [:a {:href "http://alexgorbatchev.com/SyntaxHighlighter/"}
+       "SyntaxHighlighter"]
+      floating-toc]
+     [:script {:type "text/javascript"}
+      "SyntaxHighlighter.defaults['gutter'] = false;
+       SyntaxHighlighter.all()"]]]))
+

Syntax highlighting is done a bit differently than docco. Instead of embedding +the higlighting 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
+  [output-file-name project-metadata docs]
+  (page-template
+   (opt-resources-html project-metadata)
+   (header-html project-metadata)
+   (toc-html docs)
+   (floating-toc-html docs)
+   (map groups-html docs)))
+
 

Cake plugin for running marginalia against your project.

+ +

Usage

+ +
    +
  1. In your project.clj, add [marginalia "0.2.3"] to your:dev-dependenciesandmarginalia.tasksto:tasks`
  2. +
  3. Run cake marg from within your project directory.
  4. +
+
(ns marginalia.tasks
+  (:use marginalia.core
+        [cake.core :only [deftask]]))

Run marginalia against your project code. +Optionally, you can pass files or directories to control what documentation is generated and in what order.

+
+(deftask marg
+  {files :marg}
+  (run-marginalia files))
 

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

+
(ns problem-cases.general
+  )
+

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

+
+
+(defn parse-bool [v] (condp = (.trim (text v))
+                         "0" false
+                         "1" true
+                         "throw exception here"))
+
+(defn a-function "Here is a docstring. It should be to the left."
+  [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))
+
+(def ^{:doc "This is also a docstring via metadata. It should be on the left."}
+  a 42)
+
+(def ^{:doc
+       "This is also a docstring via metadata. It should be on the left."}
+  b 42)
+
+(def ^{:doc
+       "This is also a docstring via metadata. It should be on the left."}
+  c
+  "This is just a value.  It should be on the right.")

From fnparse

+
+
+; Define single-character indicator rules.
+; I use `clojure.template/do-template` to reduce repetition.
+(do-template [rule-name token]
+  (h/defrule rule-name
+    "Padded on the front with optional whitespace."
+    (h/lit token))
+  <escape-char-start> \\
+  <str-delimiter>   \"
+  <value-separator> \,
+  <name-separator>  \:
+  <array-start>     \[
+  <array-end>       \]
+  <object-start>    \{
+  <object-end>      \})
 
\ No newline at end of file