annotate src/clojure/contrib/strint.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
rlm@10 1 ;;; strint.clj -- String interpolation for Clojure
rlm@10 2 ;; originally proposed/published at http://muckandbrass.com/web/x/AgBP
rlm@10 3
rlm@10 4 ;; by Chas Emerick <cemerick@snowtide.com>
rlm@10 5 ;; December 4, 2009
rlm@10 6
rlm@10 7 ;; Copyright (c) Chas Emerick, 2009. All rights reserved. The use
rlm@10 8 ;; and distribution terms for this software are covered by the Eclipse
rlm@10 9 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 10 ;; which can be found in the file epl-v10.html at the root of this
rlm@10 11 ;; distribution. By using this software in any fashion, you are
rlm@10 12 ;; agreeing to be bound by the terms of this license. You must not
rlm@10 13 ;; remove this notice, or any other, from this software.
rlm@10 14
rlm@10 15 (ns
rlm@10 16 ^{:author "Chas Emerick",
rlm@10 17 :doc "String interpolation for Clojure."}
rlm@10 18 clojure.contrib.strint)
rlm@10 19
rlm@10 20 (defn- silent-read
rlm@10 21 "Attempts to clojure.core/read a single form from the provided String, returning
rlm@10 22 a vector containing the read form and a String containing the unread remainder
rlm@10 23 of the provided String. Returns nil if no valid form can be read from the
rlm@10 24 head of the String."
rlm@10 25 [s]
rlm@10 26 (try
rlm@10 27 (let [r (-> s java.io.StringReader. java.io.PushbackReader.)]
rlm@10 28 [(read r) (slurp r)])
rlm@10 29 (catch Exception e))) ; this indicates an invalid form -- the head of s is just string data
rlm@10 30
rlm@10 31 (defn- interpolate
rlm@10 32 "Yields a seq of Strings and read forms."
rlm@10 33 ([s atom?]
rlm@10 34 (lazy-seq
rlm@10 35 (if-let [[form rest] (silent-read (subs s (if atom? 2 1)))]
rlm@10 36 (cons form (interpolate (if atom? (subs rest 1) rest)))
rlm@10 37 (cons (subs s 0 2) (interpolate (subs s 2))))))
rlm@10 38 ([^String s]
rlm@10 39 (if-let [start (->> ["~{" "~("]
rlm@10 40 (map #(.indexOf s %))
rlm@10 41 (remove #(== -1 %))
rlm@10 42 sort
rlm@10 43 first)]
rlm@10 44 (lazy-seq (cons
rlm@10 45 (subs s 0 start)
rlm@10 46 (interpolate (subs s start) (= \{ (.charAt s (inc start))))))
rlm@10 47 [s])))
rlm@10 48
rlm@10 49 (defmacro <<
rlm@10 50 "Takes a single string argument and emits a str invocation that concatenates
rlm@10 51 the string data and evaluated expressions contained within that argument.
rlm@10 52 Evaluation is controlled using ~{} and ~() forms. The former is used for
rlm@10 53 simple value replacement using clojure.core/str; the latter can be used to
rlm@10 54 embed the results of arbitrary function invocation into the produced string.
rlm@10 55
rlm@10 56 Examples:
rlm@10 57 user=> (def v 30.5)
rlm@10 58 #'user/v
rlm@10 59 user=> (<< \"This trial required ~{v}ml of solution.\")
rlm@10 60 \"This trial required 30.5ml of solution.\"
rlm@10 61 user=> (<< \"There are ~(int v) days in November.\")
rlm@10 62 \"There are 30 days in November.\"
rlm@10 63 user=> (def m {:a [1 2 3]})
rlm@10 64 #'user/m
rlm@10 65 user=> (<< \"The total for your order is $~(->> m :a (apply +)).\")
rlm@10 66 \"The total for your order is $6.\"
rlm@10 67
rlm@10 68 Note that quotes surrounding string literals within ~() forms must be
rlm@10 69 escaped."
rlm@10 70 [string]
rlm@10 71 `(str ~@(interpolate string)))
rlm@10 72