Mercurial > lasercutter
view 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 source
1 ;;; gen-html-docs.clj: Generate HTML documentation for Clojure libs3 ;; by Craig Andera, http://pluralsight.com/craig, candera@wangdera.com4 ;; February 13th, 20096 ;; Copyright (c) Craig Andera, 2009. All rights reserved. The use7 ;; and distribution terms for this software are covered by the Eclipse8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)9 ;; which can be found in the file epl-v10.html at the root of this10 ;; distribution. By using this software in any fashion, you are11 ;; agreeing to be bound by the terms of this license. You must not12 ;; remove this notice, or any other, from this software.14 ;; Generates a single HTML page that contains the documentation for15 ;; one or more Clojure libraries. See the comments section at the end16 ;; of this file for usage.18 ;; TODO19 ;;20 ;; * Make symbols in the source hyperlinks to the appropriate section21 ;; of the documentation.22 ;; * Investigate issue with miglayout mentioned here:23 ;; http://groups.google.com/group/clojure/browse_thread/thread/5a0c4395e44f5a79/3ae483100366bd3d?lnk=gst&q=documentation+browser#3ae483100366bd3d24 ;;25 ;; DONE26 ;;27 ;; * Move to clojure.contrib28 ;; * Change namespace29 ;; * Change license as appropriate30 ;; * Double-check doc strings31 ;; * Remove doc strings from source code32 ;; * Add collapse/expand functionality for all namespaces33 ;; * Add collapse/expand functionality for each namespace34 ;; * See if converting to use clojure.contrib.prxml is possible35 ;; * Figure out why the source doesn't show up for most things36 ;; * Add collapsible source37 ;; * Add links at the top to jump to each namespace38 ;; * Add object type (var, function, whatever)39 ;; * Add argument lists for functions40 ;; * Add links at the top of each namespace to jump to members41 ;; * Add license statement42 ;; * Remove the whojure dependency44 (ns45 ^{:author "Craig Andera",46 :doc "Generates a single HTML page that contains the documentation for47 one or more Clojure libraries."}48 clojure.contrib.gen-html-docs49 (:require [clojure.contrib.io :as io]50 [clojure.contrib.string :as s])51 (:use [clojure.contrib repl-utils def prxml])52 (:import [java.lang Exception]53 [java.util.regex Pattern]))55 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;56 ;; Doc generation constants57 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;59 (def *script* " // <![CDATA[61 function getElem(id)62 {63 if( document.getElementById )64 {65 return document.getElementById( id )66 }67 else if ( document.all )68 {69 return eval( 'document.all.' + id )70 }71 else72 return false;73 }75 function setDisplayStyle(id,displayStyle)76 {77 var elem = getElem (id)78 if (elem)79 {80 elem.style.display = displayStyle81 }83 }85 function setLinkToggleText (id, text)86 {87 var elem = getElem (id)88 if (elem)89 {90 elem.innerHTML = text91 }92 }94 function collapse(id)95 {96 setDisplayStyle (id, 'none')97 }99 function expand (id)100 {101 setDisplayStyle (id, 'block')102 }104 function toggleSource( id )105 {106 toggle(id, 'linkto-' + id, 'Hide Source', 'Show Source')107 }109 function toggle(targetid, linkid, textWhenOpen, textWhenClosed)110 {111 var elem = getElem (targetid)112 var link = getElem (linkid)114 if (elem && link)115 {116 var isOpen = false117 if (elem.style.display == '')118 {119 isOpen = link.innerHTML == textWhenOpen120 }121 else if( elem.style.display == 'block' )122 {123 isOpen = true124 }126 if (isOpen)127 {128 elem.style.display = 'none'129 link.innerHTML = textWhenClosed130 }131 else132 {133 elem.style.display = 'block'134 link.innerHTML = textWhenOpen135 }136 }137 }139 //]]>140 ")142 (def *style* "143 .library144 {145 padding: 0.5em 0 0 0146 }147 .all-libs-toggle,.library-contents-toggle148 {149 font-size: small;150 }151 .all-libs-toggle a,.library-contents-toggle a152 {153 color: white154 }155 .library-member-doc-whitespace156 {157 white-space: pre158 }159 .library-member-source-toggle160 {161 font-size: small;162 margin-top: 0.5em163 }164 .library-member-source165 {166 display: none;167 border-left: solid lightblue168 }169 .library-member-docs170 {171 font-family:monospace172 }173 .library-member-arglists174 {175 font-family: monospace176 }177 .library-member-type178 {179 font-weight: bold;180 font-size: small;181 font-style: italic;182 color: darkred183 }184 .lib-links185 {186 margin: 0 0 1em 0187 }189 .lib-link-header190 {191 color: white;192 background: darkgreen;193 width: 100%194 }196 .library-name197 {198 color: white;199 background: darkblue;200 width: 100%201 }203 .missing-library204 {205 color: darkred;206 margin: 0 0 1em 0207 }209 .library-members210 {211 list-style: none212 }214 .library-member-name215 {216 font-weight: bold;217 font-size: 105%218 }")220 (defn- extract-documentation221 "Pulls the documentation for a var v out and turns it into HTML"222 [v]223 (if-let [docs (:doc (meta v))]224 (map225 (fn [l]226 [:div {:class "library-member-doc-line"}227 (if (= 0 (count l))228 [:span {:class "library-member-doc-whitespace"} " "] ; We need something here to make the blank line show up229 l)])230 (s/split #"\n" docs))231 ""))233 (defn- member-type234 "Figures out for a var x whether it's a macro, function, var or multifunction"235 [x]236 (try237 (let [dx (deref x)]238 (cond239 (:macro (meta x)) :macro240 (fn? dx) :fn241 (= clojure.lang.MultiFn (:tag (meta x))) :multi242 true :var))243 (catch Exception e244 :unknown)))246 (defn- anchor-for-member247 "Returns a suitable HTML anchor name given a library id and a member248 id"249 [libid memberid]250 (str "member-" libid "-" memberid))252 (defn- id-for-member-source253 "Returns a suitable HTML id for a source listing given a library and254 a member"255 [libid memberid]256 (str "membersource-" libid "-" memberid))258 (defn- id-for-member-source-link259 "Returns a suitable HTML id for a link to a source listing given a260 library and a member"261 [libid memberid]262 (str "linkto-membersource-" libid "-" memberid))264 (defn- symbol-for265 "Given a namespace object ns and a namespaceless symbol memberid266 naming a member of that namespace, returns a namespaced symbol that267 identifies that member."268 [ns memberid]269 (symbol (name (ns-name ns)) (name memberid)))271 (defn- elide-to-one-line272 "Elides a string down to one line."273 [s]274 (s/replace-re #"(\n.*)+" "..." s))276 (defn- elide-string277 "Returns a string that is at most the first limit characters of s"278 [s limit]279 (if (< (- limit 3) (count s))280 (str (subs s 0 (- limit 3)) "...")281 s))283 (defn- doc-elided-src284 "Returns the src with the docs elided."285 [docs src]286 (s/replace-re (re-pattern (str "\"" (Pattern/quote docs) "\""))287 (str "\""288 (elide-to-one-line docs)289 ;; (elide-string docs 10)290 ;; "..."291 "\"")292 src))294 (defn- format-source [libid memberid v]295 (try296 (let [docs (:doc (meta v))297 src (if-let [ns (find-ns libid)]298 (get-source (symbol-for ns memberid)))]299 (if (and src docs)300 (doc-elided-src docs src)301 src))302 (catch Exception ex303 nil)))305 (defn- generate-lib-member [libid [n v]]306 [:li {:class "library-member"}307 [:a {:name (anchor-for-member libid n)}]308 [:dl {:class "library-member-table"}309 [:dt {:class "library-member-name"}310 (str n)]311 [:dd312 [:div {:class "library-member-info"}313 [:span {:class "library-member-type"} (name (member-type v))]314 " "315 [:span {:class "library-member-arglists"} (str (:arglists (meta v)))]]316 (into [:div {:class "library-member-docs"}] (extract-documentation v))317 (let [member-source-id (id-for-member-source libid n)318 member-source-link-id (id-for-member-source-link libid n)]319 (if-let [member-source (format-source libid n v)]320 [:div {:class "library-member-source-section"}321 [:div {:class "library-member-source-toggle"}322 "[ "323 [:a {:href (format "javascript:toggleSource('%s')" member-source-id)324 :id member-source-link-id} "Show Source"]325 " ]"]326 [:div {:class "library-member-source" :id member-source-id}327 [:pre member-source]]]))]]])329 (defn- anchor-for-library330 "Given a symbol id identifying a namespace, returns an identifier331 suitable for use as the name attribute of an HTML anchor tag."332 [id]333 (str "library-" id))335 (defn- generate-lib-member-link336 "Emits a hyperlink to a member of a namespace given libid (a symbol337 identifying the namespace) and the vector [n v], where n is the symbol338 naming the member in question and v is the var pointing to the339 member."340 [libid [n v]]341 [:a {:class "lib-member-link"342 :href (str "#" (anchor-for-member libid n))} (name n)])344 (defn- anchor-for-library-contents345 "Returns an HTML ID that identifies the element that holds the346 documentation contents for the specified library."347 [lib]348 (str "library-contents-" lib))350 (defn- anchor-for-library-contents-toggle351 "Returns an HTML ID that identifies the element that toggles the352 visibility of the library contents."353 [lib]354 (str "library-contents-toggle-" lib))356 (defn- generate-lib-doc357 "Emits the HTML that documents the namespace identified by the358 symbol lib."359 [lib]360 [:div {:class "library"}361 [:a {:name (anchor-for-library lib)}]362 [:div {:class "library-name"}363 [:span {:class "library-contents-toggle"}364 "[ "365 [:a {:id (anchor-for-library-contents-toggle lib)366 :href (format "javascript:toggle('%s', '%s', '-', '+')"367 (anchor-for-library-contents lib)368 (anchor-for-library-contents-toggle lib))}369 "-"]370 " ] "]371 (name lib)]372 (let [ns (find-ns lib)]373 (if ns374 (let [lib-members (sort (ns-publics ns))]375 [:a {:name (anchor-for-library lib)}]376 [:div {:class "library-contents" :id (anchor-for-library-contents lib)}377 (into [:div {:class "library-member-links"}]378 (interpose " " (map #(generate-lib-member-link lib %) lib-members)))379 (into [:ol {:class "library-members"}]380 (map #(generate-lib-member lib %) lib-members))])381 [:div {:class "missing-library library-contents" :id (anchor-for-library-contents lib)} "Could not load library"]))])383 (defn- load-lib384 "Calls require on the library identified by lib, eating any385 exceptions."386 [lib]387 (try388 (require lib)389 (catch java.lang.Exception x390 nil)))392 (defn- generate-lib-link393 "Generates a hyperlink to the documentation for a namespace given394 lib, a symbol identifying that namespace."395 [lib]396 (let [ns (find-ns lib)]397 (if ns398 [:a {:class "lib-link" :href (str "#" (anchor-for-library lib))} (str (ns-name ns))])))400 (defn- generate-lib-links401 "Generates the list of hyperlinks to each namespace, given libs, a402 vector of symbols naming namespaces."403 [libs]404 (into [:div {:class "lib-links"}405 [:div {:class "lib-link-header"} "Namespaces"406 [:span {:class "all-libs-toggle"}407 " [ "408 [:a {:href "javascript:expandAllNamespaces()"}409 "Expand All"]410 " ] [ "411 [:a {:href "javascript:collapseAllNamespaces()"}412 "Collapse All"]413 " ]"]]]414 (interpose " " (map generate-lib-link libs))))416 (defn generate-toggle-namespace-script417 [action toggle-text lib]418 (str (format "%s('%s');\n" action (anchor-for-library-contents lib))419 (format "setLinkToggleText('%s', '%s');\n" (anchor-for-library-contents-toggle lib) toggle-text)))421 (defn generate-all-namespaces-action-script422 [action toggle-text libs]423 (str (format "function %sAllNamespaces()" action)424 \newline425 "{"426 \newline427 (reduce str (map #(generate-toggle-namespace-script action toggle-text %) libs))428 \newline429 "}"))431 (defn generate-documentation432 "Returns a string which is the HTML documentation for the libraries433 named by libs. Libs is a vector of symbols identifying Clojure434 libraries."435 [libs]436 (dorun (map load-lib libs))437 (let [writer (new java.io.StringWriter)]438 (binding [*out* writer]439 (prxml440 [:html {:xmlns "http://www.w3.org/1999/xhtml"}441 [:head442 [:title "Clojure documentation browser"]443 [:style *style*]444 [:script {:language "JavaScript" :type "text/javascript"} [:raw! *script*]]446 [:script {:language "JavaScript" :type "text/javascript"}447 [:raw! "// <![CDATA[!" \newline]448 (generate-all-namespaces-action-script "expand" "-" libs)449 (generate-all-namespaces-action-script "collapse" "+" libs)450 [:raw! \newline "// ]]>"]]]451 (let [lib-vec (sort libs)]452 (into [:body (generate-lib-links lib-vec)]453 (map generate-lib-doc lib-vec)))]))454 (.toString writer)))457 (defn generate-documentation-to-file458 "Calls generate-documentation on the libraries named by libs and459 emits the generated HTML to the path named by path."460 [path libs]461 (io/spit path (generate-documentation libs)))463 (comment464 (generate-documentation-to-file465 "C:/TEMP/CLJ-DOCS.HTML"466 ['clojure.contrib.accumulators])468 (defn gen-all-docs []469 (generate-documentation-to-file470 "C:/temp/clj-libs.html"471 [472 'clojure.set473 'clojure.main474 'clojure.core475 'clojure.zip476 'clojure.xml477 'clojure.contrib.accumulators478 'clojure.contrib.apply-macro479 'clojure.contrib.auto-agent480 'clojure.contrib.combinatorics481 'clojure.contrib.command-line482 'clojure.contrib.complex-numbers483 'clojure.contrib.cond484 'clojure.contrib.def485 'clojure.contrib.io486 'clojure.contrib.enum487 'clojure.contrib.error-kit488 'clojure.contrib.except489 'clojure.contrib.fcase490 'clojure.contrib.generic491 'clojure.contrib.generic.arithmetic492 'clojure.contrib.generic.collection493 'clojure.contrib.generic.comparison494 'clojure.contrib.generic.functor495 'clojure.contrib.generic.math-functions496 'clojure.contrib.import-static497 'clojure.contrib.javadoc498 'clojure.contrib.javalog499 'clojure.contrib.lazy-seqs500 'clojure.contrib.lazy-xml501 'clojure.contrib.macro-utils502 'clojure.contrib.macros503 'clojure.contrib.math504 'clojure.contrib.miglayout505 'clojure.contrib.mmap506 'clojure.contrib.monads507 'clojure.contrib.ns-utils508 'clojure.contrib.prxml509 'clojure.contrib.repl-ln510 'clojure.contrib.repl-utils511 'clojure.contrib.seq512 'clojure.contrib.server-socket513 'clojure.contrib.shell514 'clojure.contrib.sql515 'clojure.contrib.stream-utils516 'clojure.contrib.string517 'clojure.contrib.test-contrib518 'clojure.contrib.trace519 'clojure.contrib.types520 'clojure.contrib.zip-filter521 'clojure.contrib.javadoc.browse522 'clojure.contrib.json.read523 'clojure.contrib.json.write524 'clojure.contrib.lazy-xml.with-pull525 'clojure.contrib.miglayout.internal526 'clojure.contrib.probabilities.finite-distributions527 'clojure.contrib.probabilities.monte-carlo528 'clojure.contrib.probabilities.random-numbers529 'clojure.contrib.sql.internal530 'clojure.contrib.test-clojure.evaluation531 'clojure.contrib.test-clojure.for532 'clojure.contrib.test-clojure.numbers533 'clojure.contrib.test-clojure.printer534 'clojure.contrib.test-clojure.reader535 'clojure.contrib.test-clojure.sequences536 'clojure.contrib.test-contrib.shell537 'clojure.contrib.test-contrib.string538 'clojure.contrib.zip-filter.xml539 ]))540 )