diff 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
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/clojure/contrib/strint.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,72 @@
     1.4 +;;; strint.clj -- String interpolation for Clojure
     1.5 +;; originally proposed/published at http://muckandbrass.com/web/x/AgBP
     1.6 +
     1.7 +;; by Chas Emerick <cemerick@snowtide.com>
     1.8 +;; December 4, 2009
     1.9 +
    1.10 +;; Copyright (c) Chas Emerick, 2009. All rights reserved.  The use
    1.11 +;; and distribution terms for this software are covered by the Eclipse
    1.12 +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
    1.13 +;; which can be found in the file epl-v10.html at the root of this
    1.14 +;; distribution.  By using this software in any fashion, you are
    1.15 +;; agreeing to be bound by the terms of this license.  You must not
    1.16 +;; remove this notice, or any other, from this software.
    1.17 +
    1.18 +(ns
    1.19 +  ^{:author "Chas Emerick",
    1.20 +     :doc "String interpolation for Clojure."}
    1.21 +  clojure.contrib.strint)
    1.22 +
    1.23 +(defn- silent-read
    1.24 +  "Attempts to clojure.core/read a single form from the provided String, returning
    1.25 +   a vector containing the read form and a String containing the unread remainder
    1.26 +   of the provided String.  Returns nil if no valid form can be read from the
    1.27 +   head of the String."
    1.28 +  [s]
    1.29 +  (try
    1.30 +    (let [r (-> s java.io.StringReader. java.io.PushbackReader.)]
    1.31 +      [(read r) (slurp r)])
    1.32 +    (catch Exception e))) ; this indicates an invalid form -- the head of s is just string data
    1.33 +
    1.34 +(defn- interpolate
    1.35 +  "Yields a seq of Strings and read forms."
    1.36 +  ([s atom?]
    1.37 +    (lazy-seq
    1.38 +      (if-let [[form rest] (silent-read (subs s (if atom? 2 1)))]
    1.39 +        (cons form (interpolate (if atom? (subs rest 1) rest)))
    1.40 +        (cons (subs s 0 2) (interpolate (subs s 2))))))
    1.41 +  ([^String s]
    1.42 +    (if-let [start (->> ["~{" "~("]
    1.43 +                     (map #(.indexOf s %))
    1.44 +                     (remove #(== -1 %))
    1.45 +                     sort
    1.46 +                     first)]
    1.47 +      (lazy-seq (cons
    1.48 +                  (subs s 0 start)
    1.49 +                  (interpolate (subs s start) (= \{ (.charAt s (inc start))))))
    1.50 +      [s])))
    1.51 +
    1.52 +(defmacro <<
    1.53 +  "Takes a single string argument and emits a str invocation that concatenates
    1.54 +   the string data and evaluated expressions contained within that argument.
    1.55 +   Evaluation is controlled using ~{} and ~() forms.  The former is used for
    1.56 +   simple value replacement using clojure.core/str; the latter can be used to
    1.57 +   embed the results of arbitrary function invocation into the produced string.
    1.58 +
    1.59 +   Examples:
    1.60 +   user=> (def v 30.5)
    1.61 +   #'user/v
    1.62 +   user=> (<< \"This trial required ~{v}ml of solution.\")
    1.63 +   \"This trial required 30.5ml of solution.\"
    1.64 +   user=> (<< \"There are ~(int v) days in November.\")
    1.65 +   \"There are 30 days in November.\"
    1.66 +   user=> (def m {:a [1 2 3]})
    1.67 +   #'user/m
    1.68 +   user=> (<< \"The total for your order is $~(->> m :a (apply +)).\")
    1.69 +   \"The total for your order is $6.\"
    1.70 +
    1.71 +   Note that quotes surrounding string literals within ~() forms must be
    1.72 +   escaped."
    1.73 +  [string]
    1.74 +  `(str ~@(interpolate string)))
    1.75 +