├── .gitignore ├── README.md ├── project.clj ├── src └── comb │ └── template.clj └── test └── comb └── test └── template.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Comb 2 | 3 | Comb is a simple templating system for Clojure. You can use Comb to embed 4 | fragments of Clojure code into a text file. 5 | 6 | ## Syntax 7 | 8 | The `<% %>` tags are used to embed a section of Clojure code with side-effects. 9 | This is commonly used for control structures like loops or conditionals. 10 | 11 | For example: 12 | 13 | (require '[comb.template :as template]) 14 | 15 | (template/eval "<% (dotimes [x 3] %>foo<% ) %>") 16 | => "foofoofoo" 17 | 18 | The `<%= %>` tags will be subsituted for the value of the expression within them. 19 | This is used for inserting values into a template. 20 | 21 | For example: 22 | 23 | (template/eval "Hello <%= name %>" {:name "Alice"}) 24 | => "Hello Alice" 25 | 26 | ## Installation 27 | 28 | To install, add the following dependency to your `project.clj` file: 29 | 30 | [comb "0.1.1"] 31 | 32 | ## API Documentation 33 | 34 | ### template/eval 35 | 36 | (template/eval source) 37 | (template/eval source bindings) 38 | 39 | Evaluate a template source using an optional map of bindings. The template 40 | source can be a string, or any I/O source understood by the standard `slurp` 41 | function. 42 | 43 | Example of use: 44 | 45 | (template/eval "Hello <%= name %>" {:name "Bob"}) 46 | 47 | ### template/fn 48 | 49 | (template/fn args source) 50 | 51 | Compile a template source into a anonymous function. This is a lot faster 52 | than `template/eval` for repeated calls, as the template source is only 53 | parsed when the function is created. 54 | 55 | Examples of use: 56 | 57 | (def hello 58 | (template/fn [name] "Hello <%= name %>")) 59 | 60 | (hello "Alice") 61 | 62 | ## License 63 | 64 | Copyright © 2015 James Reeves 65 | 66 | Distributed under the Eclipse Public License either version 1.0 or (at 67 | your option) any later version. 68 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject comb "0.1.1" 2 | :description "Clojure templating library similar to ERB" 3 | :url "https://github.com/weavejester/comb" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.2.1"]]) 7 | -------------------------------------------------------------------------------- /src/comb/template.clj: -------------------------------------------------------------------------------- 1 | (ns comb.template 2 | "Clojure templating library." 3 | (:refer-clojure :exclude [fn eval]) 4 | (:require [clojure.core :as core])) 5 | 6 | (defn- read-source [source] 7 | (if (string? source) 8 | source 9 | (slurp source))) 10 | 11 | (def delimiters ["<%" "%>"]) 12 | 13 | (def parser-regex 14 | (re-pattern 15 | (str "(?s)\\A" 16 | "(?:" "(.*?)" 17 | (first delimiters) "(.*?)" (last delimiters) 18 | ")?" 19 | "(.*)\\z"))) 20 | 21 | (defn emit-string [s] 22 | (print "(print " (pr-str s) ")")) 23 | 24 | (defn emit-expr [expr] 25 | (if (.startsWith expr "=") 26 | (print "(print " (subs expr 1) ")") 27 | (print expr))) 28 | 29 | (defn- parse-string [src] 30 | (with-out-str 31 | (print "(do ") 32 | (loop [src src] 33 | (let [[_ before expr after] (re-matches parser-regex src)] 34 | (if expr 35 | (do (emit-string before) 36 | (emit-expr expr) 37 | (recur after)) 38 | (do (emit-string after) 39 | (print ")"))))))) 40 | 41 | (defn compile-fn [args src] 42 | (core/eval 43 | `(core/fn ~args 44 | (with-out-str 45 | ~(-> src read-source parse-string read-string))))) 46 | 47 | (defmacro fn 48 | "Compile a template into a function that takes the supplied arguments. The 49 | template source may be a string, or an I/O source such as a File, Reader or 50 | InputStream." 51 | [args source] 52 | `(compile-fn '~args ~source)) 53 | 54 | (defn eval 55 | "Evaluate a template using the supplied bindings. The template source may 56 | be a string, or an I/O source such as a File, Reader or InputStream." 57 | ([source] 58 | (eval source {})) 59 | ([source bindings] 60 | (let [keys (map (comp symbol name) (keys bindings)) 61 | func (compile-fn [{:keys (vec keys)}] source)] 62 | (func bindings)))) 63 | -------------------------------------------------------------------------------- /test/comb/test/template.clj: -------------------------------------------------------------------------------- 1 | (ns comb.test.template 2 | (:use clojure.test) 3 | (:require [comb.template :as t] :reload)) 4 | 5 | (deftest eval-test 6 | (is (= (t/eval "foo") "foo")) 7 | (is (= (t/eval "<%= 10 %>") "10")) 8 | (is (= (t/eval "<%= x %>" {:x "foo"}) "foo")) 9 | (is (= (t/eval "<%=x%>" {:x "foo"}) "foo")) 10 | (is (= (t/eval "<% (doseq [x xs] %>foo<%= x %> <% ) %>" {:xs [1 2 3]}) 11 | "foo1 foo2 foo3 "))) 12 | 13 | (deftest fn-test 14 | (is (= ((t/fn [x] "foo<%= x %>") "bar") 15 | "foobar"))) 16 | --------------------------------------------------------------------------------