Mercurial > lasercutter
diff src/clojure/contrib/gen_html_docs.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/contrib/gen_html_docs.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,540 @@ 1.4 +;;; gen-html-docs.clj: Generate HTML documentation for Clojure libs 1.5 + 1.6 +;; by Craig Andera, http://pluralsight.com/craig, candera@wangdera.com 1.7 +;; February 13th, 2009 1.8 + 1.9 +;; Copyright (c) Craig Andera, 2009. All rights reserved. The use 1.10 +;; and distribution terms for this software are covered by the Eclipse 1.11 +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 1.12 +;; which can be found in the file epl-v10.html at the root of this 1.13 +;; distribution. By using this software in any fashion, you are 1.14 +;; agreeing to be bound by the terms of this license. You must not 1.15 +;; remove this notice, or any other, from this software. 1.16 + 1.17 +;; Generates a single HTML page that contains the documentation for 1.18 +;; one or more Clojure libraries. See the comments section at the end 1.19 +;; of this file for usage. 1.20 + 1.21 +;; TODO 1.22 +;; 1.23 +;; * Make symbols in the source hyperlinks to the appropriate section 1.24 +;; of the documentation. 1.25 +;; * Investigate issue with miglayout mentioned here: 1.26 +;; http://groups.google.com/group/clojure/browse_thread/thread/5a0c4395e44f5a79/3ae483100366bd3d?lnk=gst&q=documentation+browser#3ae483100366bd3d 1.27 +;; 1.28 +;; DONE 1.29 +;; 1.30 +;; * Move to clojure.contrib 1.31 +;; * Change namespace 1.32 +;; * Change license as appropriate 1.33 +;; * Double-check doc strings 1.34 +;; * Remove doc strings from source code 1.35 +;; * Add collapse/expand functionality for all namespaces 1.36 +;; * Add collapse/expand functionality for each namespace 1.37 +;; * See if converting to use clojure.contrib.prxml is possible 1.38 +;; * Figure out why the source doesn't show up for most things 1.39 +;; * Add collapsible source 1.40 +;; * Add links at the top to jump to each namespace 1.41 +;; * Add object type (var, function, whatever) 1.42 +;; * Add argument lists for functions 1.43 +;; * Add links at the top of each namespace to jump to members 1.44 +;; * Add license statement 1.45 +;; * Remove the whojure dependency 1.46 + 1.47 +(ns 1.48 + ^{:author "Craig Andera", 1.49 + :doc "Generates a single HTML page that contains the documentation for 1.50 +one or more Clojure libraries."} 1.51 + clojure.contrib.gen-html-docs 1.52 + (:require [clojure.contrib.io :as io] 1.53 + [clojure.contrib.string :as s]) 1.54 + (:use [clojure.contrib repl-utils def prxml]) 1.55 + (:import [java.lang Exception] 1.56 + [java.util.regex Pattern])) 1.57 + 1.58 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.59 +;; Doc generation constants 1.60 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.61 + 1.62 +(def *script* " // <![CDATA[ 1.63 + 1.64 +function getElem(id) 1.65 +{ 1.66 + if( document.getElementById ) 1.67 + { 1.68 + return document.getElementById( id ) 1.69 + } 1.70 + else if ( document.all ) 1.71 + { 1.72 + return eval( 'document.all.' + id ) 1.73 + } 1.74 + else 1.75 + return false; 1.76 +} 1.77 + 1.78 +function setDisplayStyle(id,displayStyle) 1.79 +{ 1.80 + var elem = getElem (id) 1.81 + if (elem) 1.82 + { 1.83 + elem.style.display = displayStyle 1.84 + } 1.85 + 1.86 +} 1.87 + 1.88 +function setLinkToggleText (id, text) 1.89 +{ 1.90 + var elem = getElem (id) 1.91 + if (elem) 1.92 + { 1.93 + elem.innerHTML = text 1.94 + } 1.95 +} 1.96 + 1.97 +function collapse(id) 1.98 +{ 1.99 + setDisplayStyle (id, 'none') 1.100 +} 1.101 + 1.102 +function expand (id) 1.103 +{ 1.104 + setDisplayStyle (id, 'block') 1.105 +} 1.106 + 1.107 +function toggleSource( id ) 1.108 +{ 1.109 + toggle(id, 'linkto-' + id, 'Hide Source', 'Show Source') 1.110 +} 1.111 + 1.112 +function toggle(targetid, linkid, textWhenOpen, textWhenClosed) 1.113 +{ 1.114 + var elem = getElem (targetid) 1.115 + var link = getElem (linkid) 1.116 + 1.117 + if (elem && link) 1.118 + { 1.119 + var isOpen = false 1.120 + if (elem.style.display == '') 1.121 + { 1.122 + isOpen = link.innerHTML == textWhenOpen 1.123 + } 1.124 + else if( elem.style.display == 'block' ) 1.125 + { 1.126 + isOpen = true 1.127 + } 1.128 + 1.129 + if (isOpen) 1.130 + { 1.131 + elem.style.display = 'none' 1.132 + link.innerHTML = textWhenClosed 1.133 + } 1.134 + else 1.135 + { 1.136 + elem.style.display = 'block' 1.137 + link.innerHTML = textWhenOpen 1.138 + } 1.139 + } 1.140 +} 1.141 + 1.142 + //]]> 1.143 +") 1.144 + 1.145 +(def *style* " 1.146 +.library 1.147 +{ 1.148 + padding: 0.5em 0 0 0 1.149 +} 1.150 +.all-libs-toggle,.library-contents-toggle 1.151 +{ 1.152 + font-size: small; 1.153 +} 1.154 +.all-libs-toggle a,.library-contents-toggle a 1.155 +{ 1.156 + color: white 1.157 +} 1.158 +.library-member-doc-whitespace 1.159 +{ 1.160 + white-space: pre 1.161 +} 1.162 +.library-member-source-toggle 1.163 +{ 1.164 + font-size: small; 1.165 + margin-top: 0.5em 1.166 +} 1.167 +.library-member-source 1.168 +{ 1.169 + display: none; 1.170 + border-left: solid lightblue 1.171 +} 1.172 +.library-member-docs 1.173 +{ 1.174 + font-family:monospace 1.175 +} 1.176 +.library-member-arglists 1.177 +{ 1.178 + font-family: monospace 1.179 +} 1.180 +.library-member-type 1.181 +{ 1.182 + font-weight: bold; 1.183 + font-size: small; 1.184 + font-style: italic; 1.185 + color: darkred 1.186 +} 1.187 +.lib-links 1.188 +{ 1.189 + margin: 0 0 1em 0 1.190 +} 1.191 + 1.192 +.lib-link-header 1.193 +{ 1.194 + color: white; 1.195 + background: darkgreen; 1.196 + width: 100% 1.197 +} 1.198 + 1.199 +.library-name 1.200 +{ 1.201 + color: white; 1.202 + background: darkblue; 1.203 + width: 100% 1.204 +} 1.205 + 1.206 +.missing-library 1.207 +{ 1.208 + color: darkred; 1.209 + margin: 0 0 1em 0 1.210 +} 1.211 + 1.212 +.library-members 1.213 +{ 1.214 + list-style: none 1.215 +} 1.216 + 1.217 +.library-member-name 1.218 +{ 1.219 + font-weight: bold; 1.220 + font-size: 105% 1.221 +}") 1.222 + 1.223 +(defn- extract-documentation 1.224 + "Pulls the documentation for a var v out and turns it into HTML" 1.225 + [v] 1.226 + (if-let [docs (:doc (meta v))] 1.227 + (map 1.228 + (fn [l] 1.229 + [:div {:class "library-member-doc-line"} 1.230 + (if (= 0 (count l)) 1.231 + [:span {:class "library-member-doc-whitespace"} " "] ; We need something here to make the blank line show up 1.232 + l)]) 1.233 + (s/split #"\n" docs)) 1.234 + "")) 1.235 + 1.236 +(defn- member-type 1.237 + "Figures out for a var x whether it's a macro, function, var or multifunction" 1.238 + [x] 1.239 + (try 1.240 + (let [dx (deref x)] 1.241 + (cond 1.242 + (:macro (meta x)) :macro 1.243 + (fn? dx) :fn 1.244 + (= clojure.lang.MultiFn (:tag (meta x))) :multi 1.245 + true :var)) 1.246 + (catch Exception e 1.247 + :unknown))) 1.248 + 1.249 +(defn- anchor-for-member 1.250 + "Returns a suitable HTML anchor name given a library id and a member 1.251 + id" 1.252 + [libid memberid] 1.253 + (str "member-" libid "-" memberid)) 1.254 + 1.255 +(defn- id-for-member-source 1.256 + "Returns a suitable HTML id for a source listing given a library and 1.257 + a member" 1.258 + [libid memberid] 1.259 + (str "membersource-" libid "-" memberid)) 1.260 + 1.261 +(defn- id-for-member-source-link 1.262 + "Returns a suitable HTML id for a link to a source listing given a 1.263 + library and a member" 1.264 + [libid memberid] 1.265 + (str "linkto-membersource-" libid "-" memberid)) 1.266 + 1.267 +(defn- symbol-for 1.268 + "Given a namespace object ns and a namespaceless symbol memberid 1.269 + naming a member of that namespace, returns a namespaced symbol that 1.270 + identifies that member." 1.271 + [ns memberid] 1.272 + (symbol (name (ns-name ns)) (name memberid))) 1.273 + 1.274 +(defn- elide-to-one-line 1.275 + "Elides a string down to one line." 1.276 + [s] 1.277 + (s/replace-re #"(\n.*)+" "..." s)) 1.278 + 1.279 +(defn- elide-string 1.280 + "Returns a string that is at most the first limit characters of s" 1.281 + [s limit] 1.282 + (if (< (- limit 3) (count s)) 1.283 + (str (subs s 0 (- limit 3)) "...") 1.284 + s)) 1.285 + 1.286 +(defn- doc-elided-src 1.287 + "Returns the src with the docs elided." 1.288 + [docs src] 1.289 + (s/replace-re (re-pattern (str "\"" (Pattern/quote docs) "\"")) 1.290 + (str "\"" 1.291 + (elide-to-one-line docs) 1.292 +;; (elide-string docs 10) 1.293 +;; "..." 1.294 + "\"") 1.295 + src)) 1.296 + 1.297 +(defn- format-source [libid memberid v] 1.298 + (try 1.299 + (let [docs (:doc (meta v)) 1.300 + src (if-let [ns (find-ns libid)] 1.301 + (get-source (symbol-for ns memberid)))] 1.302 + (if (and src docs) 1.303 + (doc-elided-src docs src) 1.304 + src)) 1.305 + (catch Exception ex 1.306 + nil))) 1.307 + 1.308 +(defn- generate-lib-member [libid [n v]] 1.309 + [:li {:class "library-member"} 1.310 + [:a {:name (anchor-for-member libid n)}] 1.311 + [:dl {:class "library-member-table"} 1.312 + [:dt {:class "library-member-name"} 1.313 + (str n)] 1.314 + [:dd 1.315 + [:div {:class "library-member-info"} 1.316 + [:span {:class "library-member-type"} (name (member-type v))] 1.317 + " " 1.318 + [:span {:class "library-member-arglists"} (str (:arglists (meta v)))]] 1.319 + (into [:div {:class "library-member-docs"}] (extract-documentation v)) 1.320 + (let [member-source-id (id-for-member-source libid n) 1.321 + member-source-link-id (id-for-member-source-link libid n)] 1.322 + (if-let [member-source (format-source libid n v)] 1.323 + [:div {:class "library-member-source-section"} 1.324 + [:div {:class "library-member-source-toggle"} 1.325 + "[ " 1.326 + [:a {:href (format "javascript:toggleSource('%s')" member-source-id) 1.327 + :id member-source-link-id} "Show Source"] 1.328 + " ]"] 1.329 + [:div {:class "library-member-source" :id member-source-id} 1.330 + [:pre member-source]]]))]]]) 1.331 + 1.332 +(defn- anchor-for-library 1.333 + "Given a symbol id identifying a namespace, returns an identifier 1.334 +suitable for use as the name attribute of an HTML anchor tag." 1.335 + [id] 1.336 + (str "library-" id)) 1.337 + 1.338 +(defn- generate-lib-member-link 1.339 + "Emits a hyperlink to a member of a namespace given libid (a symbol 1.340 +identifying the namespace) and the vector [n v], where n is the symbol 1.341 +naming the member in question and v is the var pointing to the 1.342 +member." 1.343 + [libid [n v]] 1.344 + [:a {:class "lib-member-link" 1.345 + :href (str "#" (anchor-for-member libid n))} (name n)]) 1.346 + 1.347 +(defn- anchor-for-library-contents 1.348 + "Returns an HTML ID that identifies the element that holds the 1.349 +documentation contents for the specified library." 1.350 + [lib] 1.351 + (str "library-contents-" lib)) 1.352 + 1.353 +(defn- anchor-for-library-contents-toggle 1.354 + "Returns an HTML ID that identifies the element that toggles the 1.355 +visibility of the library contents." 1.356 + [lib] 1.357 + (str "library-contents-toggle-" lib)) 1.358 + 1.359 +(defn- generate-lib-doc 1.360 + "Emits the HTML that documents the namespace identified by the 1.361 +symbol lib." 1.362 + [lib] 1.363 + [:div {:class "library"} 1.364 + [:a {:name (anchor-for-library lib)}] 1.365 + [:div {:class "library-name"} 1.366 + [:span {:class "library-contents-toggle"} 1.367 + "[ " 1.368 + [:a {:id (anchor-for-library-contents-toggle lib) 1.369 + :href (format "javascript:toggle('%s', '%s', '-', '+')" 1.370 + (anchor-for-library-contents lib) 1.371 + (anchor-for-library-contents-toggle lib))} 1.372 + "-"] 1.373 + " ] "] 1.374 + (name lib)] 1.375 + (let [ns (find-ns lib)] 1.376 + (if ns 1.377 + (let [lib-members (sort (ns-publics ns))] 1.378 + [:a {:name (anchor-for-library lib)}] 1.379 + [:div {:class "library-contents" :id (anchor-for-library-contents lib)} 1.380 + (into [:div {:class "library-member-links"}] 1.381 + (interpose " " (map #(generate-lib-member-link lib %) lib-members))) 1.382 + (into [:ol {:class "library-members"}] 1.383 + (map #(generate-lib-member lib %) lib-members))]) 1.384 + [:div {:class "missing-library library-contents" :id (anchor-for-library-contents lib)} "Could not load library"]))]) 1.385 + 1.386 +(defn- load-lib 1.387 + "Calls require on the library identified by lib, eating any 1.388 +exceptions." 1.389 + [lib] 1.390 + (try 1.391 + (require lib) 1.392 + (catch java.lang.Exception x 1.393 + nil))) 1.394 + 1.395 +(defn- generate-lib-link 1.396 + "Generates a hyperlink to the documentation for a namespace given 1.397 +lib, a symbol identifying that namespace." 1.398 + [lib] 1.399 + (let [ns (find-ns lib)] 1.400 + (if ns 1.401 + [:a {:class "lib-link" :href (str "#" (anchor-for-library lib))} (str (ns-name ns))]))) 1.402 + 1.403 +(defn- generate-lib-links 1.404 + "Generates the list of hyperlinks to each namespace, given libs, a 1.405 +vector of symbols naming namespaces." 1.406 + [libs] 1.407 + (into [:div {:class "lib-links"} 1.408 + [:div {:class "lib-link-header"} "Namespaces" 1.409 + [:span {:class "all-libs-toggle"} 1.410 + " [ " 1.411 + [:a {:href "javascript:expandAllNamespaces()"} 1.412 + "Expand All"] 1.413 + " ] [ " 1.414 + [:a {:href "javascript:collapseAllNamespaces()"} 1.415 + "Collapse All"] 1.416 + " ]"]]] 1.417 + (interpose " " (map generate-lib-link libs)))) 1.418 + 1.419 +(defn generate-toggle-namespace-script 1.420 + [action toggle-text lib] 1.421 + (str (format "%s('%s');\n" action (anchor-for-library-contents lib)) 1.422 + (format "setLinkToggleText('%s', '%s');\n" (anchor-for-library-contents-toggle lib) toggle-text))) 1.423 + 1.424 +(defn generate-all-namespaces-action-script 1.425 + [action toggle-text libs] 1.426 + (str (format "function %sAllNamespaces()" action) 1.427 + \newline 1.428 + "{" 1.429 + \newline 1.430 + (reduce str (map #(generate-toggle-namespace-script action toggle-text %) libs)) 1.431 + \newline 1.432 + "}")) 1.433 + 1.434 +(defn generate-documentation 1.435 + "Returns a string which is the HTML documentation for the libraries 1.436 +named by libs. Libs is a vector of symbols identifying Clojure 1.437 +libraries." 1.438 + [libs] 1.439 + (dorun (map load-lib libs)) 1.440 + (let [writer (new java.io.StringWriter)] 1.441 + (binding [*out* writer] 1.442 + (prxml 1.443 + [:html {:xmlns "http://www.w3.org/1999/xhtml"} 1.444 + [:head 1.445 + [:title "Clojure documentation browser"] 1.446 + [:style *style*] 1.447 + [:script {:language "JavaScript" :type "text/javascript"} [:raw! *script*]] 1.448 + 1.449 + [:script {:language "JavaScript" :type "text/javascript"} 1.450 + [:raw! "// <![CDATA[!" \newline] 1.451 + (generate-all-namespaces-action-script "expand" "-" libs) 1.452 + (generate-all-namespaces-action-script "collapse" "+" libs) 1.453 + [:raw! \newline "// ]]>"]]] 1.454 + (let [lib-vec (sort libs)] 1.455 + (into [:body (generate-lib-links lib-vec)] 1.456 + (map generate-lib-doc lib-vec)))])) 1.457 + (.toString writer))) 1.458 + 1.459 + 1.460 +(defn generate-documentation-to-file 1.461 + "Calls generate-documentation on the libraries named by libs and 1.462 +emits the generated HTML to the path named by path." 1.463 + [path libs] 1.464 + (io/spit path (generate-documentation libs))) 1.465 + 1.466 +(comment 1.467 + (generate-documentation-to-file 1.468 + "C:/TEMP/CLJ-DOCS.HTML" 1.469 + ['clojure.contrib.accumulators]) 1.470 + 1.471 + (defn gen-all-docs [] 1.472 + (generate-documentation-to-file 1.473 + "C:/temp/clj-libs.html" 1.474 + [ 1.475 + 'clojure.set 1.476 + 'clojure.main 1.477 + 'clojure.core 1.478 + 'clojure.zip 1.479 + 'clojure.xml 1.480 + 'clojure.contrib.accumulators 1.481 + 'clojure.contrib.apply-macro 1.482 + 'clojure.contrib.auto-agent 1.483 + 'clojure.contrib.combinatorics 1.484 + 'clojure.contrib.command-line 1.485 + 'clojure.contrib.complex-numbers 1.486 + 'clojure.contrib.cond 1.487 + 'clojure.contrib.def 1.488 + 'clojure.contrib.io 1.489 + 'clojure.contrib.enum 1.490 + 'clojure.contrib.error-kit 1.491 + 'clojure.contrib.except 1.492 + 'clojure.contrib.fcase 1.493 + 'clojure.contrib.generic 1.494 + 'clojure.contrib.generic.arithmetic 1.495 + 'clojure.contrib.generic.collection 1.496 + 'clojure.contrib.generic.comparison 1.497 + 'clojure.contrib.generic.functor 1.498 + 'clojure.contrib.generic.math-functions 1.499 + 'clojure.contrib.import-static 1.500 + 'clojure.contrib.javadoc 1.501 + 'clojure.contrib.javalog 1.502 + 'clojure.contrib.lazy-seqs 1.503 + 'clojure.contrib.lazy-xml 1.504 + 'clojure.contrib.macro-utils 1.505 + 'clojure.contrib.macros 1.506 + 'clojure.contrib.math 1.507 + 'clojure.contrib.miglayout 1.508 + 'clojure.contrib.mmap 1.509 + 'clojure.contrib.monads 1.510 + 'clojure.contrib.ns-utils 1.511 + 'clojure.contrib.prxml 1.512 + 'clojure.contrib.repl-ln 1.513 + 'clojure.contrib.repl-utils 1.514 + 'clojure.contrib.seq 1.515 + 'clojure.contrib.server-socket 1.516 + 'clojure.contrib.shell 1.517 + 'clojure.contrib.sql 1.518 + 'clojure.contrib.stream-utils 1.519 + 'clojure.contrib.string 1.520 + 'clojure.contrib.test-contrib 1.521 + 'clojure.contrib.trace 1.522 + 'clojure.contrib.types 1.523 + 'clojure.contrib.zip-filter 1.524 + 'clojure.contrib.javadoc.browse 1.525 + 'clojure.contrib.json.read 1.526 + 'clojure.contrib.json.write 1.527 + 'clojure.contrib.lazy-xml.with-pull 1.528 + 'clojure.contrib.miglayout.internal 1.529 + 'clojure.contrib.probabilities.finite-distributions 1.530 + 'clojure.contrib.probabilities.monte-carlo 1.531 + 'clojure.contrib.probabilities.random-numbers 1.532 + 'clojure.contrib.sql.internal 1.533 + 'clojure.contrib.test-clojure.evaluation 1.534 + 'clojure.contrib.test-clojure.for 1.535 + 'clojure.contrib.test-clojure.numbers 1.536 + 'clojure.contrib.test-clojure.printer 1.537 + 'clojure.contrib.test-clojure.reader 1.538 + 'clojure.contrib.test-clojure.sequences 1.539 + 'clojure.contrib.test-contrib.shell 1.540 + 'clojure.contrib.test-contrib.string 1.541 + 'clojure.contrib.zip-filter.xml 1.542 + ])) 1.543 + )