Permalink
Browse files

migrate clojure.contrib.strint => clojure.core.strint, per http://dev…

  • Loading branch information...
1 parent e8324a6 commit e7627ef0586735271d478b1fe97c2d3c4933e788 @cemerick cemerick committed Sep 12, 2011
Showing with 111 additions and 0 deletions.
  1. +71 −0 src/main/clojure/clojure/core/strint.clj
  2. +40 −0 src/test/clojure/clojure/core/strint_test.clj
@@ -0,0 +1,71 @@
+;;; strint.clj -- String interpolation for Clojure
+;; originally proposed/published at http://cemerick.com/2009/12/04/string-interpolation-in-clojure/
+
+;; by Chas Emerick <cemerick@snowtide.com>
+;; December 4, 2009
+
+;; Copyright (c) Chas Emerick, 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.
+
+(ns
+ ^{:author "Chas Emerick",
+ :doc "Compile-time string interpolation for Clojure."}
+ clojure.core.strint)
+
+(defn- silent-read
+ "Attempts to clojure.core/read a single form from the provided String, returning
+ a vector containing the read form and a String containing the unread remainder
+ of the provided String. Returns nil if no valid form can be read from the
+ head of the String."
+ [s]
+ (try
+ (let [r (-> s java.io.StringReader. java.io.PushbackReader.)]
+ [(read r) (slurp r)])
+ (catch Exception e))) ; this indicates an invalid form -- the head of s is just string data
+
+(defn- interpolate
+ "Yields a seq of Strings and read forms."
+ ([s atom?]
+ (lazy-seq
+ (if-let [[form rest] (silent-read (subs s (if atom? 2 1)))]
+ (cons form (interpolate (if atom? (subs rest 1) rest)))
+ (cons (subs s 0 2) (interpolate (subs s 2))))))
+ ([^String s]
+ (if-let [start (->> ["~{" "~("]
+ (map #(.indexOf s %))
+ (remove #(== -1 %))
+ sort
+ first)]
+ (lazy-seq (cons
+ (subs s 0 start)
+ (interpolate (subs s start) (= \{ (.charAt s (inc start))))))
+ [s])))
+
+(defmacro <<
+ "Takes a single string argument and emits a str invocation that concatenates
+ the string data and evaluated expressions contained within that argument.
+ Evaluation is controlled using ~{} and ~() forms. The former is used for
+ simple value replacement using clojure.core/str; the latter can be used to
+ embed the results of arbitrary function invocation into the produced string.
+
+ Examples:
+ user=> (def v 30.5)
+ #'user/v
+ user=> (<< \"This trial required ~{v}ml of solution.\")
+ \"This trial required 30.5ml of solution.\"
+ user=> (<< \"There are ~(int v) days in November.\")
+ \"There are 30 days in November.\"
+ user=> (def m {:a [1 2 3]})
+ #'user/m
+ user=> (<< \"The total for your order is $~(->> m :a (apply +)).\")
+ \"The total for your order is $6.\"
+
+ Note that quotes surrounding string literals within ~() forms must be
+ escaped."
+ [string]
+ `(str ~@(interpolate string)))
@@ -0,0 +1,40 @@
+; Copyright (c) Chas Emerick 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.
+
+(ns clojure.core.strint-test
+ (:use clojure.test
+ clojure.core.strint))
+
+(deftest test-silent-read
+ (let [silent-read @#'clojure.core.strint/silent-read]
+ (testing "reading a valid form returns [read form, rest of string]"
+ (is (= [[1] "[2]"] (silent-read "[1][2]"))))
+ (testing "reading an invalid form returns nil"
+ (is (= nil (silent-read "["))))))
+
+(deftest test-interpolate
+ (let [interpolate @#'clojure.core.strint/interpolate]
+ (testing "a plain old string"
+ (is (= ["a plain old string"] (interpolate "a plain old string"))))
+ (testing "some value replacement forms"
+ (is (= '["" foo " and " bar ""] (interpolate "~{foo} and ~{bar}"))))
+ (testing "some fn-calling forms"
+ (is (= '["" (+ 1 2) " and " (vector 3) ""] (interpolate "~(+ 1 2) and ~(vector 3)"))))))
+
+(deftest test-<<
+ (testing "docstring examples"
+ (let [v 30.5
+ m {:a [1 2 3]}]
+ (is (= "This trial required 30.5ml of solution."
+ (<< "This trial required ~{v}ml of solution.")))
+ (is (= "There are 30 days in November."
+ (<< "There are ~(int v) days in November.")))
+ (is (= "The total for your order is $6."
+ (<< "The total for your order is $~(->> m :a (apply +))."))))))

0 comments on commit e7627ef

Please sign in to comment.