diff options
Diffstat (limited to 'modules/gen-html-docs')
-rw-r--r-- | modules/gen-html-docs/pom.xml | 26 | ||||
-rw-r--r-- | modules/gen-html-docs/src/main/clojure/clojure/contrib/gen_html_docs.clj | 540 |
2 files changed, 566 insertions, 0 deletions
diff --git a/modules/gen-html-docs/pom.xml b/modules/gen-html-docs/pom.xml new file mode 100644 index 00000000..587c7e60 --- /dev/null +++ b/modules/gen-html-docs/pom.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http//www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.clojure.contrib</groupId> + <artifactId>parent</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../parent</relativePath> + </parent> + <artifactId>gen-html-docs</artifactId> + <dependencies> + <dependency> + <groupId>org.clojure.contrib</groupId> + <artifactId>io</artifactId> + <version>1.3.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.clojure.contrib</groupId> + <artifactId>string</artifactId> + <version>1.3.0-SNAPSHOT</version> + </dependency> + </dependencies> +</project>
\ No newline at end of file diff --git a/modules/gen-html-docs/src/main/clojure/clojure/contrib/gen_html_docs.clj b/modules/gen-html-docs/src/main/clojure/clojure/contrib/gen_html_docs.clj new file mode 100644 index 00000000..73166510 --- /dev/null +++ b/modules/gen-html-docs/src/main/clojure/clojure/contrib/gen_html_docs.clj @@ -0,0 +1,540 @@ +;;; gen-html-docs.clj: Generate HTML documentation for Clojure libs + +;; by Craig Andera, http://pluralsight.com/craig, candera@wangdera.com +;; February 13th, 2009 + +;; Copyright (c) Craig Andera, 2009. All rights reserved. The use +;; and distribution terms for this software are covered by the Eclipse +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this +;; distribution. By using this software in any fashion, you are +;; agreeing to be bound by the terms of this license. You must not +;; remove this notice, or any other, from this software. + +;; Generates a single HTML page that contains the documentation for +;; one or more Clojure libraries. See the comments section at the end +;; of this file for usage. + +;; TODO +;; +;; * Make symbols in the source hyperlinks to the appropriate section +;; of the documentation. +;; * Investigate issue with miglayout mentioned here: +;; http://groups.google.com/group/clojure/browse_thread/thread/5a0c4395e44f5a79/3ae483100366bd3d?lnk=gst&q=documentation+browser#3ae483100366bd3d +;; +;; DONE +;; +;; * Move to clojure.contrib +;; * Change namespace +;; * Change license as appropriate +;; * Double-check doc strings +;; * Remove doc strings from source code +;; * Add collapse/expand functionality for all namespaces +;; * Add collapse/expand functionality for each namespace +;; * See if converting to use clojure.contrib.prxml is possible +;; * Figure out why the source doesn't show up for most things +;; * Add collapsible source +;; * Add links at the top to jump to each namespace +;; * Add object type (var, function, whatever) +;; * Add argument lists for functions +;; * Add links at the top of each namespace to jump to members +;; * Add license statement +;; * Remove the whojure dependency + +(ns + ^{:author "Craig Andera", + :doc "Generates a single HTML page that contains the documentation for +one or more Clojure libraries."} + clojure.contrib.gen-html-docs + (:require [clojure.contrib.io :as io] + [clojure.contrib.string :as s]) + (:use [clojure.contrib repl-utils def prxml]) + (:import [java.lang Exception] + [java.util.regex Pattern])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Doc generation constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def *script* " // <![CDATA[ + +function getElem(id) +{ + if( document.getElementById ) + { + return document.getElementById( id ) + } + else if ( document.all ) + { + return eval( 'document.all.' + id ) + } + else + return false; +} + +function setDisplayStyle(id,displayStyle) +{ + var elem = getElem (id) + if (elem) + { + elem.style.display = displayStyle + } + +} + +function setLinkToggleText (id, text) +{ + var elem = getElem (id) + if (elem) + { + elem.innerHTML = text + } +} + +function collapse(id) +{ + setDisplayStyle (id, 'none') +} + +function expand (id) +{ + setDisplayStyle (id, 'block') +} + +function toggleSource( id ) +{ + toggle(id, 'linkto-' + id, 'Hide Source', 'Show Source') +} + +function toggle(targetid, linkid, textWhenOpen, textWhenClosed) +{ + var elem = getElem (targetid) + var link = getElem (linkid) + + if (elem && link) + { + var isOpen = false + if (elem.style.display == '') + { + isOpen = link.innerHTML == textWhenOpen + } + else if( elem.style.display == 'block' ) + { + isOpen = true + } + + if (isOpen) + { + elem.style.display = 'none' + link.innerHTML = textWhenClosed + } + else + { + elem.style.display = 'block' + link.innerHTML = textWhenOpen + } + } +} + + //]]> +") + +(def *style* " +.library +{ + padding: 0.5em 0 0 0 +} +.all-libs-toggle,.library-contents-toggle +{ + font-size: small; +} +.all-libs-toggle a,.library-contents-toggle a +{ + color: white +} +.library-member-doc-whitespace +{ + white-space: pre +} +.library-member-source-toggle +{ + font-size: small; + margin-top: 0.5em +} +.library-member-source +{ + display: none; + border-left: solid lightblue +} +.library-member-docs +{ + font-family:monospace +} +.library-member-arglists +{ + font-family: monospace +} +.library-member-type +{ + font-weight: bold; + font-size: small; + font-style: italic; + color: darkred +} +.lib-links +{ + margin: 0 0 1em 0 +} + +.lib-link-header +{ + color: white; + background: darkgreen; + width: 100% +} + +.library-name +{ + color: white; + background: darkblue; + width: 100% +} + +.missing-library +{ + color: darkred; + margin: 0 0 1em 0 +} + +.library-members +{ + list-style: none +} + +.library-member-name +{ + font-weight: bold; + font-size: 105% +}") + +(defn- extract-documentation + "Pulls the documentation for a var v out and turns it into HTML" + [v] + (if-let [docs (:doc (meta v))] + (map + (fn [l] + [:div {:class "library-member-doc-line"} + (if (= 0 (count l)) + [:span {:class "library-member-doc-whitespace"} " "] ; We need something here to make the blank line show up + l)]) + (s/split #"\n" docs)) + "")) + +(defn- member-type + "Figures out for a var x whether it's a macro, function, var or multifunction" + [x] + (try + (let [dx (deref x)] + (cond + (:macro (meta x)) :macro + (fn? dx) :fn + (= clojure.lang.MultiFn (:tag (meta x))) :multi + true :var)) + (catch Exception e + :unknown))) + +(defn- anchor-for-member + "Returns a suitable HTML anchor name given a library id and a member + id" + [libid memberid] + (str "member-" libid "-" memberid)) + +(defn- id-for-member-source + "Returns a suitable HTML id for a source listing given a library and + a member" + [libid memberid] + (str "membersource-" libid "-" memberid)) + +(defn- id-for-member-source-link + "Returns a suitable HTML id for a link to a source listing given a + library and a member" + [libid memberid] + (str "linkto-membersource-" libid "-" memberid)) + +(defn- symbol-for + "Given a namespace object ns and a namespaceless symbol memberid + naming a member of that namespace, returns a namespaced symbol that + identifies that member." + [ns memberid] + (symbol (name (ns-name ns)) (name memberid))) + +(defn- elide-to-one-line + "Elides a string down to one line." + [s] + (s/replace-re #"(\n.*)+" "..." s)) + +(defn- elide-string + "Returns a string that is at most the first limit characters of s" + [s limit] + (if (< (- limit 3) (count s)) + (str (subs s 0 (- limit 3)) "...") + s)) + +(defn- doc-elided-src + "Returns the src with the docs elided." + [docs src] + (s/replace-re (re-pattern (str "\"" (Pattern/quote docs) "\"")) + (str "\"" + (elide-to-one-line docs) +;; (elide-string docs 10) +;; "..." + "\"") + src)) + +(defn- format-source [libid memberid v] + (try + (let [docs (:doc (meta v)) + src (if-let [ns (find-ns libid)] + (get-source (symbol-for ns memberid)))] + (if (and src docs) + (doc-elided-src docs src) + src)) + (catch Exception ex + nil))) + +(defn- generate-lib-member [libid [n v]] + [:li {:class "library-member"} + [:a {:name (anchor-for-member libid n)}] + [:dl {:class "library-member-table"} + [:dt {:class "library-member-name"} + (str n)] + [:dd + [:div {:class "library-member-info"} + [:span {:class "library-member-type"} (name (member-type v))] + " " + [:span {:class "library-member-arglists"} (str (:arglists (meta v)))]] + (into [:div {:class "library-member-docs"}] (extract-documentation v)) + (let [member-source-id (id-for-member-source libid n) + member-source-link-id (id-for-member-source-link libid n)] + (if-let [member-source (format-source libid n v)] + [:div {:class "library-member-source-section"} + [:div {:class "library-member-source-toggle"} + "[ " + [:a {:href (format "javascript:toggleSource('%s')" member-source-id) + :id member-source-link-id} "Show Source"] + " ]"] + [:div {:class "library-member-source" :id member-source-id} + [:pre member-source]]]))]]]) + +(defn- anchor-for-library + "Given a symbol id identifying a namespace, returns an identifier +suitable for use as the name attribute of an HTML anchor tag." + [id] + (str "library-" id)) + +(defn- generate-lib-member-link + "Emits a hyperlink to a member of a namespace given libid (a symbol +identifying the namespace) and the vector [n v], where n is the symbol +naming the member in question and v is the var pointing to the +member." + [libid [n v]] + [:a {:class "lib-member-link" + :href (str "#" (anchor-for-member libid n))} (name n)]) + +(defn- anchor-for-library-contents + "Returns an HTML ID that identifies the element that holds the +documentation contents for the specified library." + [lib] + (str "library-contents-" lib)) + +(defn- anchor-for-library-contents-toggle + "Returns an HTML ID that identifies the element that toggles the +visibility of the library contents." + [lib] + (str "library-contents-toggle-" lib)) + +(defn- generate-lib-doc + "Emits the HTML that documents the namespace identified by the +symbol lib." + [lib] + [:div {:class "library"} + [:a {:name (anchor-for-library lib)}] + [:div {:class "library-name"} + [:span {:class "library-contents-toggle"} + "[ " + [:a {:id (anchor-for-library-contents-toggle lib) + :href (format "javascript:toggle('%s', '%s', '-', '+')" + (anchor-for-library-contents lib) + (anchor-for-library-contents-toggle lib))} + "-"] + " ] "] + (name lib)] + (let [ns (find-ns lib)] + (if ns + (let [lib-members (sort (ns-publics ns))] + [:a {:name (anchor-for-library lib)}] + [:div {:class "library-contents" :id (anchor-for-library-contents lib)} + (into [:div {:class "library-member-links"}] + (interpose " " (map #(generate-lib-member-link lib %) lib-members))) + (into [:ol {:class "library-members"}] + (map #(generate-lib-member lib %) lib-members))]) + [:div {:class "missing-library library-contents" :id (anchor-for-library-contents lib)} "Could not load library"]))]) + +(defn- load-lib + "Calls require on the library identified by lib, eating any +exceptions." + [lib] + (try + (require lib) + (catch java.lang.Exception x + nil))) + +(defn- generate-lib-link + "Generates a hyperlink to the documentation for a namespace given +lib, a symbol identifying that namespace." + [lib] + (let [ns (find-ns lib)] + (if ns + [:a {:class "lib-link" :href (str "#" (anchor-for-library lib))} (str (ns-name ns))]))) + +(defn- generate-lib-links + "Generates the list of hyperlinks to each namespace, given libs, a +vector of symbols naming namespaces." + [libs] + (into [:div {:class "lib-links"} + [:div {:class "lib-link-header"} "Namespaces" + [:span {:class "all-libs-toggle"} + " [ " + [:a {:href "javascript:expandAllNamespaces()"} + "Expand All"] + " ] [ " + [:a {:href "javascript:collapseAllNamespaces()"} + "Collapse All"] + " ]"]]] + (interpose " " (map generate-lib-link libs)))) + +(defn generate-toggle-namespace-script + [action toggle-text lib] + (str (format "%s('%s');\n" action (anchor-for-library-contents lib)) + (format "setLinkToggleText('%s', '%s');\n" (anchor-for-library-contents-toggle lib) toggle-text))) + +(defn generate-all-namespaces-action-script + [action toggle-text libs] + (str (format "function %sAllNamespaces()" action) + \newline + "{" + \newline + (reduce str (map #(generate-toggle-namespace-script action toggle-text %) libs)) + \newline + "}")) + +(defn generate-documentation + "Returns a string which is the HTML documentation for the libraries +named by libs. Libs is a vector of symbols identifying Clojure +libraries." + [libs] + (dorun (map load-lib libs)) + (let [writer (new java.io.StringWriter)] + (binding [*out* writer] + (prxml + [:html {:xmlns "http://www.w3.org/1999/xhtml"} + [:head + [:title "Clojure documentation browser"] + [:style *style*] + [:script {:language "JavaScript" :type "text/javascript"} [:raw! *script*]] + + [:script {:language "JavaScript" :type "text/javascript"} + [:raw! "// <![CDATA[!" \newline] + (generate-all-namespaces-action-script "expand" "-" libs) + (generate-all-namespaces-action-script "collapse" "+" libs) + [:raw! \newline "// ]]>"]]] + (let [lib-vec (sort libs)] + (into [:body (generate-lib-links lib-vec)] + (map generate-lib-doc lib-vec)))])) + (.toString writer))) + + +(defn generate-documentation-to-file + "Calls generate-documentation on the libraries named by libs and +emits the generated HTML to the path named by path." + [path libs] + (io/spit path (generate-documentation libs))) + +(comment + (generate-documentation-to-file + "C:/TEMP/CLJ-DOCS.HTML" + ['clojure.contrib.accumulators]) + + (defn gen-all-docs [] + (generate-documentation-to-file + "C:/temp/clj-libs.html" + [ + 'clojure.set + 'clojure.main + 'clojure.core + 'clojure.zip + 'clojure.xml + 'clojure.contrib.accumulators + 'clojure.contrib.apply-macro + 'clojure.contrib.auto-agent + 'clojure.contrib.combinatorics + 'clojure.contrib.command-line + 'clojure.contrib.complex-numbers + 'clojure.contrib.cond + 'clojure.contrib.def + 'clojure.contrib.io + 'clojure.contrib.enum + 'clojure.contrib.error-kit + 'clojure.contrib.except + 'clojure.contrib.fcase + 'clojure.contrib.generic + 'clojure.contrib.generic.arithmetic + 'clojure.contrib.generic.collection + 'clojure.contrib.generic.comparison + 'clojure.contrib.generic.functor + 'clojure.contrib.generic.math-functions + 'clojure.contrib.import-static + 'clojure.contrib.javadoc + 'clojure.contrib.javalog + 'clojure.contrib.lazy-seqs + 'clojure.contrib.lazy-xml + 'clojure.contrib.macro-utils + 'clojure.contrib.macros + 'clojure.contrib.math + 'clojure.contrib.miglayout + 'clojure.contrib.mmap + 'clojure.contrib.monads + 'clojure.contrib.ns-utils + 'clojure.contrib.prxml + 'clojure.contrib.repl-ln + 'clojure.contrib.repl-utils + 'clojure.contrib.seq + 'clojure.contrib.server-socket + 'clojure.contrib.shell + 'clojure.contrib.sql + 'clojure.contrib.stream-utils + 'clojure.contrib.string + 'clojure.contrib.test-contrib + 'clojure.contrib.trace + 'clojure.contrib.types + 'clojure.contrib.zip-filter + 'clojure.contrib.javadoc.browse + 'clojure.contrib.json.read + 'clojure.contrib.json.write + 'clojure.contrib.lazy-xml.with-pull + 'clojure.contrib.miglayout.internal + 'clojure.contrib.probabilities.finite-distributions + 'clojure.contrib.probabilities.monte-carlo + 'clojure.contrib.probabilities.random-numbers + 'clojure.contrib.sql.internal + 'clojure.contrib.test-clojure.evaluation + 'clojure.contrib.test-clojure.for + 'clojure.contrib.test-clojure.numbers + 'clojure.contrib.test-clojure.printer + 'clojure.contrib.test-clojure.reader + 'clojure.contrib.test-clojure.sequences + 'clojure.contrib.test-contrib.shell + 'clojure.contrib.test-contrib.string + 'clojure.contrib.zip-filter.xml + ])) + ) |