From a2d5e4a1eac093f4c57cf8c7c2e04441f7f84898 Mon Sep 17 00:00:00 2001 From: John Andrew Fingerhut Date: Mon, 7 Mar 2011 14:37:40 -0800 Subject: [PATCH] Add ability to query a local snapshot of clojuredocs API results without using the Internet at all. The file test/partial-snapshot.clj can be used to test this capability on a partial snapshot of clojuredocs contents. See comments in that file for how to use it. --- src/cd_client/core.clj | 131 +++++++++- test/partial-snapshot.clj | 513 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 636 insertions(+), 8 deletions(-) create mode 100644 test/partial-snapshot.clj diff --git a/src/cd_client/core.clj b/src/cd_client/core.clj index 417e8ba..0eb5bc0 100644 --- a/src/cd_client/core.clj +++ b/src/cd_client/core.clj @@ -2,7 +2,8 @@ (:require [org.danlarkin.json :as json] [clj-http.client :as http] [clojure.string :as string]) - (:use [clojure.java.browse :only [browse-url]])) + (:use [clojure.java.browse :only [browse-url]] + [clojure.pprint :only [pprint]])) ; For testing purposes use localhost:8080 @@ -15,6 +16,41 @@ (def *seealso-api* (str *clojuredocs-root* "/see-also/")) +;; Use one of the functions set-local-mode! or set-web-mode! below to +;; change the mode, and show-mode to show the current mode. +(def *cd-client-mode* (ref {:source :web})) + + +(defn set-local-mode! [fname] + ;; TBD: Handle errors in attempting to open the file, or as returned + ;; from read. + (let [data (with-open [s (java.io.PushbackReader. + (java.io.InputStreamReader. + (java.io.FileInputStream. + (java.io.File. fname))))] + (read s))] + (dosync (alter *cd-client-mode* + (fn [cur-val] + {:source :local-file, :filename fname, :data data}))) + (println "Read info on" (count data) "names from local file") + (println fname))) + + +(defn set-web-mode! [] + (dosync (alter *cd-client-mode* (fn [cur-val] {:source :web}))) + (println "Now retrieving clojuredocs data from web site clojuredocs.org")) + + +(defn show-mode [] + (let [mode @*cd-client-mode*] + (if (= :web (:source mode)) + (println "Web mode. Data is retrieved from clojuredocs.org") + (do + (println "Local mode. Data for" (count (:data mode)) + "names was retrieved from the file") + (println (:filename mode)))))) + + (defn- fixup-name-url "Replace some special characters in symbol names in order to construct a URL that works on clojuredocs.org" [name] @@ -77,8 +113,18 @@ (defn examples-core "Return examples from clojuredocs for a given namespace and name (as strings)" [ns name] - (json/decode-from-str (:body (http/get (str *examples-api* ns "/" - (fixup-name-url name)))))) + (let [mode @*cd-client-mode*] + (if (= :web (:source mode)) + (json/decode-from-str (:body (http/get (str *examples-api* ns "/" + (fixup-name-url name))))) + ;; Make examples-core return the value that I wish the + ;; json/decode-from-str call above did when there are no + ;; examples, i.e. the URL and an empty vector of examples. Then + ;; I can test browse-to to see if it will work unmodified for + ;; names that have no examples. + (let [name-info (get (:data mode) (str ns "/" name))] + {:examples (:examples name-info), + :url (:url name-info)})))) (defmacro examples @@ -116,6 +162,8 @@ `(pr-examples-core ~ns ~name))) +;; TBD: Think about how to implement search when in local mode. + (defn search "Search for a method name within an (optional) namespace" ([name] @@ -127,8 +175,12 @@ (defn comments-core "Return comments from clojuredocs for a given namespace and name (as strings)" [ns name] - (json/decode-from-str (:body (http/get (str *comments-api* ns "/" - (fixup-name-url name)))))) + (let [mode @*cd-client-mode*] + (if (= :web (:source mode)) + (json/decode-from-str (:body (http/get (str *comments-api* ns "/" + (fixup-name-url name))))) + (let [name-info (get (:data mode) (str ns "/" name))] + (:comments name-info))))) (defmacro comments @@ -170,9 +222,13 @@ (defn see-also-core "Return 'see also' info from clojuredocs for a given namespace and name (as strings)" - ([ns name] + [ns name] + (let [mode @*cd-client-mode*] + (if (= :web (:source mode)) (json/decode-from-str (:body (http/get (str *seealso-api* ns "/" - (fixup-name-url name))))))) + (fixup-name-url name))))) + (let [name-info (get (:data mode) (str ns "/" name))] + (:see-alsos name-info))))) (defmacro see-also @@ -186,7 +242,7 @@ (defn browse-to-core "Open a browser to the clojuredocs page for a given namespace and name (as strings)" ([ns name] - (when-let [url (:url (examples ns name))] + (when-let [url (:url (examples-core ns name))] (browse-url url)))) @@ -196,3 +252,62 @@ `(handle-fns-etc ~name browse-to-core)) ([ns name] `(browse-to-core ~ns ~name))) + + +;; Collect lots of info about each name: +;; + examples, see also list, and comments +;; + DON'T get the Clojure documentation string. Assume we have that +;; locally already. +;; +;; Use search-str "let" to get a partial snapshot, with only those +;; names that contain "let". This currently returns 39 results, so it +;; is a nice smaller test case for development and debugging. +;; +;; Use search-str "" to get a full snapshot. As of Mar 3, 2011 that +;; is information on a little over 4000 names, requiring 3 API calls +;; per name. Best to ask permission before hitting the server with +;; this kind of use. + +(defn make-snapshot [search-str fname & quiet] + (let [verbose (not quiet) + all-names-urls (search search-str) + junk (when verbose + (println "Retrieved basic information for" (count all-names-urls) + "names. Getting full details...")) + all-info (doall + (map (fn [{ns :ns, name :name, :as m}] + ;; Make each of ex, sa, and com always a + ;; vector, never nil. If examples returns + ;; non-nil, it includes both a vector of + ;; examples and a URL. We discard the URL + ;; here, since it is already available in + ;; all-names-urls. + (let [junk (when verbose + (print (str ns "/" name) " examples:") + (flush)) + ex (if-let [x (examples ns name)] + (:examples x) + []) + junk (when verbose + (print (count ex) " see-alsos:") + (flush)) + sa (if-let [x (see-also ns name)] x []) + junk (when verbose + (print (count sa) " comments:") + (flush)) + com (if-let [x (comments ns name)] x []) + junk (when verbose + (println (count com)))] + (assoc m :examples ex :see-alsos sa :comments com))) + all-names-urls)) + all-info-map (reduce (fn [big-map one-name-info] + (assoc big-map + (str (:ns one-name-info) "/" + (:name one-name-info)) + one-name-info)) + {} all-info)] + (with-open [f (java.io.OutputStreamWriter. + (java.io.BufferedOutputStream. + (java.io.FileOutputStream. fname)))] + (binding [*out* f] + (pprint all-info-map))))) diff --git a/test/partial-snapshot.clj b/test/partial-snapshot.clj new file mode 100644 index 0000000..ac454bd --- /dev/null +++ b/test/partial-snapshot.clj @@ -0,0 +1,513 @@ +;; Created on March 7, 2011 by evaluating these expressions in a REPL: +;; +;; (require '[cd-client.core :as c]) +;; (c/make-snapshot "let" "partial-snapshot.clj") +;; +;; You can use it for testing local mode in cd-client as follows: +;; +;; (c/set-local-mode! "test/partial-snapshot.clj") +;; (c/pr-examples let) + +{"lancet/delete" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/3419", + :ns "lancet", + :name "delete", + :id 3419}, + "clojure.contrib.singleton/global-singleton" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/1224", + :ns "clojure.contrib.singleton", + :name "global-singleton", + :id 1224}, + "clojure.contrib.macro-utils/symbol-macrolet" + {:comments [], + :see-alsos [], + :examples + [{:namespace_id 52, + :ns "clojure.contrib.macro-utils", + :updated_at "2011-01-05 20:55:06.0", + :function "symbol-macrolet", + :version 1, + :created_at "2011-01-05 20:55:06.0", + :library "Clojure Contrib", + :lib_version "1.2.0", + :library_id 1, + :body + "user> (symbol-macrolet [hi (do (println \"Howdy\") 1)] (+ hi 2))\n \nHowdy\n3"}], + :url "http://clojuredocs.org/v/679", + :ns "clojure.contrib.macro-utils", + :name "symbol-macrolet", + :id 679}, + "clojure.contrib.io/delete-file-recursively" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/519", + :ns "clojure.contrib.io", + :name "delete-file-recursively", + :id 519}, + "clojure.contrib.java-utils/delete-file-recursively" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/538", + :ns "clojure.contrib.java-utils", + :name "delete-file-recursively", + :id 538}, + "swank.commands.completion/potential-completions" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4148", + :ns "swank.commands.completion", + :name "potential-completions", + :id 4148}, + "incanter.core/incomplete-beta" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2871", + :ns "incanter.core", + :name "incomplete-beta", + :id 2871}, + "ring.util.servlet/ring.util.servlet.proxy$javax.servlet.http.HttpServlet$0" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2619", + :ns "ring.util.servlet", + :name "ring.util.servlet.proxy$javax.servlet.http.HttpServlet$0", + :id 2619}, + "ring.util.servlet/merge-servlet-keys" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2620", + :ns "ring.util.servlet", + :name "merge-servlet-keys", + :id 2620}, + "swank.commands.contrib.swank-fuzzy/fuzzy-completion-selected" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4171", + :ns "swank.commands.contrib.swank-fuzzy", + :name "fuzzy-completion-selected", + :id 4171}, + "swank.commands.contrib.swank-c-p-c/completions" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4161", + :ns "swank.commands.contrib.swank-c-p-c", + :name "completions", + :id 4161}, + "ring.util.servlet/servlet" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2622", + :ns "ring.util.servlet", + :name "servlet", + :id 2622}, + "clojure.contrib.macro-utils/macrolet" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/658", + :ns "clojure.contrib.macro-utils", + :name "macrolet", + :id 658}, + "net.cgrand.enlive-html/let-select" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/3918", + :ns "net.cgrand.enlive-html", + :name "let-select", + :id 3918}, + "clojure.core/when-let" + {:comments + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2011-03-02 00:24:28.0", + :user_id 41, + :function "when-let", + :version "1.2.0", + :created_at "2011-03-02 00:24:28.0", + :library "Clojure Core", + :library_id 3, + :body + "The difference between when-let and if-let is that when-let doesn't have an else clause and and also accepts multiple forms so you don't need to use a (do...)."}], + :see-alsos + [{:namespace_id 99, + :weight 2, + :name "if-let", + :updated_at "2010-10-20 04:42:12.0", + :version "1.2.0", + :created_at "2010-07-14 20:24:37.0", + :added nil, + :url "http://clojuredocs.org/v/1953", + :line "1403", + :arglists_comp "[bindings then]|[bindings then else & oldform]", + :url_friendly_name "if-let", + :file "clojure/core.clj"}], + :examples + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-09-26 02:53:01.0", + :function "when-let", + :version 3, + :created_at "2010-08-11 12:12:56.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + ";; Very useful when working with sequences. Capturing the retun value \n;; of `seq` brings a performance gain in subsequent `first`/`rest`/`next`\n;; calls. Also the block is guarded by `nil` punning.\n\n(defn drop-one\n [coll]\n (when-let [s (seq coll)]\n (rest s)))\n\nuser=> (drop-one [1 2 3])\n(2 3)\nuser=> (drop-one [])\nnil\n"}], + :url "http://clojuredocs.org/v/1849", + :ns "clojure.core", + :name "when-let", + :id 1849}, + "incanter.processing/DELETE" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/3067", + :ns "incanter.processing", + :name "DELETE", + :id 3067}, + "clojure.core/if-let" + {:comments + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2011-03-02 00:24:45.0", + :user_id 41, + :function "if-let", + :version "1.2.0", + :created_at "2011-03-02 00:24:45.0", + :library "Clojure Core", + :library_id 3, + :body + "The difference between when-let and if-let is that when-let doesn't have an else clause and and also accepts multiple forms so you don't need to use a (do...)."}], + :see-alsos + [{:namespace_id 99, + :weight 1, + :name "when-let", + :updated_at "2010-10-20 04:42:09.0", + :version "1.2.0", + :created_at "2010-07-14 20:23:25.0", + :added nil, + :url "http://clojuredocs.org/v/1849", + :line "1422", + :arglists_comp "[bindings & body]", + :url_friendly_name "when-let", + :file "clojure/core.clj"}], + :examples + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-09-26 04:19:23.0", + :function "if-let", + :version 3, + :created_at "2010-07-17 08:16:25.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + "user=> (defn sum-even-numbers [nums]\n (if-let [nums (seq (filter even? nums))]\n (reduce + nums)\n \"No even numbers found.\"))\n#'user/sum-even-numbers\n\nuser=> (sum-even-numbers [1 3 5 7 9])\n\"No even numbers found.\"\n\nuser=> (sum-even-numbers [1 3 5 7 9 10 12])\n22\n"} + {:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-09-26 04:19:45.0", + :function "if-let", + :version 4, + :created_at "2010-07-18 14:39:00.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + "user=> (if-let [x false y true]\n \"then\"\n \"else\")\njava.lang.IllegalArgumentException: if-let requires exactly 2 forms in binding vector (NO_SOURCE_FILE:1)\n\nuser=> (defn if-let-demo [arg]\n (if-let [x arg]\n \"then\"\n \"else\"))\n\nuser=> (if-let-demo 1) ; anything except nil/false\n\"then\"\nuser=> (if-let-demo nil)\n\"else\"\nuser=> (if-let-demo false)\n\"else\"\n"}], + :url "http://clojuredocs.org/v/1953", + :ns "clojure.core", + :name "if-let", + :id 1953}, + "clojure.contrib.macros/letfn-" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/688", + :ns "clojure.contrib.macros", + :name "letfn-", + :id 688}, + "clojure.contrib.io/delete-file" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/506", + :ns "clojure.contrib.io", + :name "delete-file", + :id 506}, + "clojure.contrib.java-utils/delete-file" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/530", + :ns "clojure.contrib.java-utils", + :name "delete-file", + :id 530}, + "ring.util.servlet/update-servlet-response" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2617", + :ns "ring.util.servlet", + :name "update-servlet-response", + :id 2617}, + "swank.commands.contrib.swank-fuzzy/fuzzy-completions" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4183", + :ns "swank.commands.contrib.swank-fuzzy", + :name "fuzzy-completions", + :id 4183}, + "clojure.core/let" + {:comments + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2011-02-07 01:52:53.0", + :user_id 61, + :function "let", + :version "1.2.0", + :created_at "2011-02-07 01:52:53.0", + :library "Clojure Core", + :library_id 3, + :body + "Nota Bene: `let` in Clojure is like `let*` in Scheme -- each init-expr has access to the preceding binding forms. (There is also a `let*`, but it is more or less `let` without destructuring, and in fact is the underlying implementation.)"}], + :see-alsos + [{:namespace_id 99, + :weight 1, + :name "letfn", + :updated_at "2010-10-20 04:42:03.0", + :version "1.2.0", + :created_at "2010-07-14 20:19:51.0", + :added nil, + :url "http://clojuredocs.org/v/1546", + :line "5246", + :arglists_comp "[fnspecs & body]", + :url_friendly_name "letfn", + :file "clojure/core.clj"}], + :examples + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-11-09 21:04:11.0", + :function "let", + :version 7, + :created_at "2010-07-09 17:10:29.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + ";; let is a Clojure special form, a fundamental building block of the language.\n;;\n;; In addition to parameters passed to functions, let provides a way to create\n;; lexical bindings of data structures to symbols. The binding, and therefore \n;; the ability to resolve the binding, is available only within the lexical \n;; context of the let. \n;; \n;; let uses pairs in a vector for each binding you'd like to make and the value \n;; of the let is the value of the last expression to be evaluated. let also \n;; allows for destructuring which is a way to bind symbols to only part of a \n;; collection.\n\n;; A basic use for a let:\nuser=> (let \n [x 1] \n x)\n1\n\n\n;; Note that the binding for the symbol y won't exist outside of the let:\nuser=> (let \n [y 1] \n y)\n1\nuser=> (prn y)\njava.lang.Exception: Unable to resolve symbol: y in this context (NO_SOURCE_FILE:7)\n\n\n;; Another valid use of let:\nuser=> (let \n [a 1 b 2] \n (+ a b))\n3\n\n\n;; The forms in the vector can be more complex (this example also uses\n;; the thread macro):\nuser=> (let \n [c (+ 1 2) [d e] [5 6]] \n (-> (+ d e) (- c)))\n8\n\n\n\n;; The bindings for let need not match up (note the result is a numeric\n;; type called a ratio):\nuser=> (let \n [[g h] [1 2 3]] \n (/ g h))\n1/2\n\n\n;; From http://clojure-examples.appspot.com/clojure.core/let with permission."} + {:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-09-26 04:28:54.0", + :function "let", + :version 3, + :created_at "2010-07-14 20:01:09.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + "user=> (let [a (take 5 (range))\n {:keys [b c d] :or {d 10 b 20 c 30}} {:c 50 :d 100}\n [e f g & h] [\"a\" \"b\" \"c\" \"d\" \"e\"]\n _ (println \"I was here!\")\n foo 12\n bar (+ foo 100)]\n [a b c d e f g h foo bar])\nI was here!\n[(0 1 2 3 4) 20 50 100 \"a\" \"b\" \"c\" (\"d\" \"e\") 12 112]\n"}], + :url "http://clojuredocs.org/v/1585", + :ns "clojure.core", + :name "let", + :id 1585}, + "pallet.crate.tomcat/pallet-type" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4752", + :ns "pallet.crate.tomcat", + :name "pallet-type", + :id 4752}, + "clojure.java.io/delete-file" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2149", + :ns "clojure.java.io", + :name "delete-file", + :id 2149}, + "pallet.enlive/transform-if-let" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4791", + :ns "pallet.enlive", + :name "transform-if-let", + :id 4791}, + "swank.commands.contrib.swank-fuzzy/*fuzzy-completion-symbol-prefixes*" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4170", + :ns "swank.commands.contrib.swank-fuzzy", + :name "*fuzzy-completion-symbol-prefixes*", + :id 4170}, + "pallet.repl/use-pallet" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4851", + :ns "pallet.repl", + :name "use-pallet", + :id 4851}, + "swank.commands.contrib.swank-fuzzy/*fuzzy-completion-symbol-suffixes*" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4175", + :ns "swank.commands.contrib.swank-fuzzy", + :name "*fuzzy-completion-symbol-suffixes*", + :id 4175}, + "clojure.contrib.singleton/per-thread-singleton" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/1225", + :ns "clojure.contrib.singleton", + :name "per-thread-singleton", + :id 1225}, + "swank.loader/delete-file-recursive" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4296", + :ns "swank.loader", + :name "delete-file-recursive", + :id 4296}, + "clojure.contrib.sql/delete-rows" + {:comments [], + :see-alsos [], + :examples + [{:namespace_id 83, + :ns "clojure.contrib.sql", + :updated_at "2010-09-26 04:33:15.0", + :function "delete-rows", + :version 3, + :created_at "2010-07-14 19:33:16.0", + :library "Clojure Contrib", + :lib_version "1.2.0", + :library_id 1, + :body + ";;\n;; the first line allows us to say sql/with-connection instead of\n;; clojure.contrib.sql/with-connection\n;;\n\n(require '[clojure.contrib.sql :as sql])\n\n(defn delete-blog\n \"Deletes a blog entry given the id\"\n [id]\n (sql/with-connection db\n (sql/delete-rows :blogs [\"id=?\" id])))\n\n\n\n;; From http://en.wikibooks.org/wiki/Clojure_Programming/Examples/JDBC_Examples#DELETE"}], + :url "http://clojuredocs.org/v/1253", + :ns "clojure.contrib.sql", + :name "delete-rows", + :id 1253}, + "pallet.maven/has-pallet-properties" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4824", + :ns "pallet.maven", + :name "has-pallet-properties", + :id 4824}, + "swank.commands.contrib.swank-fuzzy/*fuzzy-completion-word-separators*" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4185", + :ns "swank.commands.contrib.swank-fuzzy", + :name "*fuzzy-completion-word-separators*", + :id 4185}, + "swank.commands.completion/simple-completions" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/4124", + :ns "swank.commands.completion", + :name "simple-completions", + :id 4124}, + "clojure.core/letfn" + {:comments [], + :see-alsos + [{:namespace_id 99, + :weight 2, + :name "let", + :updated_at "2010-10-20 04:42:04.0", + :version "1.2.0", + :created_at "2010-07-14 20:20:23.0", + :added nil, + :url "http://clojuredocs.org/v/1585", + :line "3461", + :arglists_comp "[bindings & body]", + :url_friendly_name "let", + :file "clojure/core.clj"}], + :examples + [{:namespace_id 99, + :ns "clojure.core", + :updated_at "2010-09-26 04:25:33.0", + :function "letfn", + :version 3, + :created_at "2010-07-14 20:06:27.0", + :library "Clojure Core", + :lib_version "1.2.0", + :library_id 3, + :body + "user=> (letfn [(twice [x] \n (* x 2))\n (six-times [y]\n (* (twice y) 3))]\n (six-times 100))\n600\n\n;; Unable to resolve symbol: twice in this context\nuser=> (twice 4)\n; Evaluation aborted.\n\n;; Unable to resolve symbol: six-times in this context\nuser=> (six-times 100)\n; Evaluation aborted.\n"}], + :url "http://clojuredocs.org/v/1546", + :ns "clojure.core", + :name "letfn", + :id 1546}, + "clojure.contrib.pprint/pprint-let" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/930", + :ns "clojure.contrib.pprint", + :name "pprint-let", + :id 930}, + "clojure.contrib.cond/cond-let" + {:comments [], + :see-alsos + [{:namespace_id 99, + :weight 2, + :name "cond", + :updated_at "2010-10-20 04:42:03.0", + :version "1.2.0", + :created_at "2010-07-14 20:19:57.0", + :added nil, + :url "http://clojuredocs.org/v/1553", + :line "491", + :arglists_comp "[& clauses]", + :url_friendly_name "cond", + :file "clojure/core.clj"}], + :examples + [{:namespace_id 8, + :ns "clojure.contrib.cond", + :updated_at "2010-11-05 19:30:07.0", + :function "cond-let", + :version 1, + :created_at "2010-11-05 19:30:07.0", + :library "Clojure Contrib", + :lib_version "1.2.0", + :library_id 1, + :body "(cond-let\n [[a b c]] [5 2 3]\n (< a b c))"}], + :url "http://clojuredocs.org/v/55", + :ns "clojure.contrib.cond", + :name "cond-let", + :id 55}, + "incanter.stats/sample-dirichlet" + {:comments [], + :see-alsos [], + :examples [], + :url "http://clojuredocs.org/v/2736", + :ns "incanter.stats", + :name "sample-dirichlet", + :id 2736}}