I wrote a simple text-templating/processing system which resembles PHP, except that it works with Clojure. The main goal was to provide an easy way to use Clojure PHP-style for small files.
This is a usage example, the following file
<?clj
(def theName "Soonog")
(defn replaceAs
[input]
(clojure.string/replace input "a" "X")
)
?>
Hello <% theName %>, this is a simple template which should allow
you to <% (replaceAs "see the basics how this might work some day") %>.
should output (here are two newlines at the start missing)
Hello Soonog, this is a simple template which should allow
you to see the bXsics how this might work some dXy.
A short info on how it works:
- Accepts files as arguments
- If no arguments are provided or the argument is "-" it will read from stdin
- Outputs to stdout
- Uses regular expressions to split/process the text
- Everything in
<?clj ... ?>
is evaluated withload-string
- Everything in
<% ... %>
is wrapped in(echo ... )
and then treated like above
A few things I'm unsure about:
- The treatment of static variables (f.e.
tag-start
) - The style itself
- Using regular expressions to parse files
load-strings
will load it into the current context, so all files passed in share the same context...this is by design (for now) so that you can "include" files. Though, having a function that uses a separate context might be interesting if this is used as library.
And here's the code:
(ns closeddoor.core
(:gen-class :main true))
(require 'clojure.string)
(def tag-start
"The long/normal start tag."
"<\\?clj")
(def tag-end
"The long/normal end tag."
"(\\?>|\\Z)")
(def echo-start
"The short/echo start tag."
"<%")
(def echo-end
"The short/echo end tag."
"%>")
(def tag-pattern
"The compiled pattern for the long/normal tags."
(re-pattern (str tag-start "((?s:.+?))" tag-end)))
(def echo-pattern
"The compiled pattern for the short/echo tags."
(re-pattern (str echo-start "((?s:.+?))" echo-end)))
(defn- process-match
"Processes the given match, and returns the output of
the given match."
[[match group]]
; Create a StringBuilder which we'll use for our content.
(def buffer (StringBuilder.))
(defn echo
"A small helper function to allow to echo things."
[string]
(.append buffer string))
; Now load what the regex delivered to use.
(load-string (str "(use 'closeddoor.core)" group))
; Return the buffer.
; We're replacing the $ with an escaped version because this output
; is directly passed into string/replace.
(clojure.string/replace (.toString buffer) "$" "\\$"))
(defn- process-match-echo-wrapped
"Process the given match, but wraps it first in the echo function."
[[match group]]
(process-match [match (str "(echo " group ")")]))
(defn parse
"Parses the given input, processes the matches and returns the result.
The long/normal tags are processed first, after that the short/echo tags.
Order of appearance does not matter, all normal tags are processed first."
[input]
(clojure.string/replace
(clojure.string/replace
input
tag-pattern
process-match)
echo-pattern
process-match-echo-wrapped))
(defn process
"Processes the given source, which means that it reads everything from
the source with slurp, runs it through parse and spits it out into out.
So (process input *out*) is basically shorthand for (spit *out* (parse input))."
[source out]
(spit out (parse (slurp source))))
(defn -main
"The main function which does everything."
[& args]
(if (empty? args)
(process *in* *out*)
(doseq [arg args]
(if (= arg "-")
(process *in* *out*)
(process arg *out*)))))