Skip to content
Browse files

added renderer-fn macro and changed all interpolation/templating code…

… in the i18n namespace to use it (the result is simpler and shorter code as optimized as before)
  • Loading branch information...
1 parent 001f087 commit babd1b38744b68f4dfcda12e4125e8254a817fb6 @xavi committed
Showing with 164 additions and 127 deletions.
  1. +91 −126 src/noir_auth_app/i18n.clj
  2. +73 −1 src/noir_auth_app/utilities.clj
View
217 src/noir_auth_app/i18n.clj
@@ -20,169 +20,134 @@
(def contact-link (str "<a href=\"mailto:" config/contact-email "\">"
config/contact-email "</a>"))
-; Instead of
-; (def translations {:greeting #(str "hello " (:username %) "!")})
-; another option would be to use format
-; (def translations {:greeting #(format "hello %s!" (:username %))})
-; but that's much slower, as measured by
-; (time ((:greeting translations) {:username "xavi"}))
-; 0.0768 ms vs 0.137 ms (average on 5 runs, excluding the 1st one to
-; ignore compile time)
-; So, str only takes ~56% of the time taken by format.
(def translations
{:account-activation-failed-page-title
- (fn [_]
- (str*
- "Account activation failed — " config/app-name))
+ (renderer-fn
+ "Account activation failed — " config/app-name)
:activation-code-not-found
- ; if using the anonymous function literal, and the function
- ; implementation doesn't reference any parameter like in this case,
- ; then the function doesn't expect any parameters, and calling it with
- ; one parameter would result in
- ; Wrong number of args (1) passed to: i18n$fn
- ; http://stackoverflow.com/q/7841643/974795
- ; To work around this, the fn macro is used...
- ; http://clojuredocs.org/clojure_core/clojure.core/fn
- ; The underscore (_) is a Clojure naming convention for parameters that
- ; are not used
- ; http://java.ociweb.com/mark/clojure/article.html#Syntax
- (fn [_]
- ; str vs str*
- ; 0.0802 ms vs 0.0572 ms (average on 5 runs, excluding the 1st one
- ; to ignore compile time), so str* only takes ~71% of the time
- ; taken by str. Moreover, the difference increases with the number
- ; of concatenated strings.
- (str*
- "Activation code not found. Please make sure that the link that "
- "you opened in your browser is the same as the one you received "
- "by email. If problems continue, please contact us at "
- contact-link " ."))
+ (renderer-fn
+ "Activation code not found. Please make sure that the link that "
+ "you opened in your browser is the same as the one you received "
+ "by email. If problems continue, please contact us at "
+ contact-link " .")
:activation-code-sent
- (fn [_]
- "Email sent with your activation code. Thanks for signing up!")
+ (renderer-fn
+ "Email sent with your activation code. Thanks for signing up!")
:activation-code-taken
- (fn [_]
- (str*
- "Generated activation code is already taken. "
- "Please try it again."))
+ (renderer-fn
+ "Generated activation code is already taken. Please try it again.")
:admin-page-title
- (fn [_]
- (str*
- "Admin — " config/app-name))
+ (renderer-fn
+ "Admin — " config/app-name)
:cancel-change
- (fn [_]
- "cancel change")
+ (renderer-fn
+ "cancel change")
:change-password-page-title
- (fn [_]
- (str*
- "Change password — " config/app-name))
+ (renderer-fn
+ "Change password — " config/app-name)
:email-change-code-not-found
- (fn [_]
- "Email change code not found.")
+ (renderer-fn
+ "Email change code not found.")
:email-change-confirmation-sent
- #(str "Email sent to " (:email %)
- " with a link to confirm the address change.")
+ (renderer-fn
+ "Email sent to " (:email %)
+ " with a link to confirm the address change.")
:email-change-confirmed
- (fn [_]
- "Email change confirmed.")
+ (renderer-fn
+ "Email change confirmed.")
:email-not-found
- (fn [_]
- "Email not found")
+ (renderer-fn
+ "Email not found")
:email-taken
- (fn [_]
- "Email already taken.")
+ (renderer-fn
+ "Email already taken.")
:expired-activation-code
- #(str "Expired activation code. <a data-method=\"post\" href=\""
- (url "/resend-activation" {:email (:email %)})
- "\">Get a new activation email with a new code</a>.")
+ (renderer-fn
+ "Expired activation code. <a data-method=\"post\" href=\""
+ (url "/resend-activation" {:email (:email %)})
+ "\">Get a new activation email with a new code</a>.")
:expired-password-reset-code
- (fn [_]
- "Expired reset code. You can request a new one below.")
+ (renderer-fn
+ "Expired reset code. You can request a new one below.")
:forgot-password-page-title
- (fn [_]
- (str*
- "Forgot password — " config/app-name))
+ (renderer-fn
+ "Forgot password — " config/app-name)
:home-page-title
- (fn [_]
- config/app-name)
+ (renderer-fn
+ config/app-name)
:insert-error
- (fn [_]
- (str*
- "There was an error, please try again. If problems continue, "
- "contact us at " contact-link " ."))
+ (renderer-fn
+ "There was an error, please try again. If problems continue, "
+ "contact us at " contact-link " .")
:invalid-email
- (fn [_]
- "Email not valid.")
+ (renderer-fn
+ "Email not valid.")
:invalid-username
- (fn [_]
- (str*
- "Username can contain only letters (no accents), numbers, "
- "dots (.), hyphens (-) and underscores (_)."))
+ (renderer-fn
+ "Username can contain only letters (no accents), numbers, "
+ "dots (.), hyphens (-) and underscores (_).")
:login-page-title
- (fn [_]
- (str*
- "Login — " config/app-name))
+ (renderer-fn
+ "Login — " config/app-name)
:new-requested-email-taken
- (fn [_]
- "Email already taken.")
+ (renderer-fn
+ "Email already taken.")
:new-requested-email-taken-by-not-yet-activated-account
- #(str "Email already taken but not confirmed yet. <a href=\""
- ; http://weavejester.github.com/hiccup/hiccup.util.html#var-url
- (url "/resend-activation" {:email (:new_requested_email %)})
- "\" data-method=\"post\">Resend confirmation email</a>.")
+ (renderer-fn
+ "Email already taken but not confirmed yet. <a href=\""
+ ; http://weavejester.github.com/hiccup/hiccup.util.html#var-url
+ (url "/resend-activation" {:email (:new_requested_email %)})
+ "\" data-method=\"post\">Resend confirmation email</a>.")
:not-yet-activated
- #(str "Account not yet activated. <a data-method=\"post\" href=\""
- (url "/resend-activation" {:email (:email %)})
- "\">Resend activation email</a>.")
+ (renderer-fn
+ "Account not yet activated. <a data-method=\"post\" href=\""
+ (url "/resend-activation" {:email (:email %)})
+ "\">Resend activation email</a>.")
:password-changed
- (fn [_]
- "Your password has been changed.")
+ (renderer-fn
+ "Your password has been changed.")
:password-reset-code-not-found
- (fn [_]
- (str*
- "Reset code not found. You can try asking for a new one below. "
- "If problems continue, please contact us at " contact-link " ."))
+ (renderer-fn
+ "Reset code not found. You can try asking for a new one below. "
+ "If problems continue, please contact us at " contact-link " .")
:password-reset-code-taken
- (fn [_]
- (str*
- "Generated password reset code is already taken. "
- "Please try it again."))
+ (renderer-fn
+ "Generated password reset code is already taken. "
+ "Please try it again.")
:password-too-short
- (fn [_]
- "Password must be at least 5 characters.")
+ (renderer-fn
+ "Password must be at least 5 characters.")
:resend-activation-page-title
- (fn [_]
- config/app-name)
+ (renderer-fn
+ config/app-name)
:resend-confirmation
- (fn [_]
- "resend confirmation")
+ (renderer-fn
+ "resend confirmation")
:settings-page-title
- (fn [_]
- (str*
- "Settings — " config/app-name))
+ (renderer-fn
+ "Settings — " config/app-name)
:signup-page-title
- (fn [_]
- (str*
- "Signup — " config/app-name))
+ (renderer-fn
+ "Signup — " config/app-name)
:taken-by-not-yet-activated-account
- #(str "Email already taken but not confirmed yet. <a href=\""
- ; http://weavejester.github.com/hiccup/hiccup.util.html#var-url
- (url "/resend-activation" {:email (:email %)})
- "\" data-method=\"post\">Resend confirmation email</a>.")
+ (renderer-fn
+ "Email already taken but not confirmed yet. <a href=\""
+ ; http://weavejester.github.com/hiccup/hiccup.util.html#var-url
+ (url "/resend-activation" {:email (:email %)})
+ "\" data-method=\"post\">Resend confirmation email</a>.")
:update-error
- (fn [_]
- (str*
- "There was an error, please try again. If problems continue, "
- "contact us at " contact-link " ."))
+ (renderer-fn
+ "There was an error, please try again. If problems continue, "
+ "contact us at " contact-link " .")
:username-taken
- (fn [_]
- "That username is already taken.")
+ (renderer-fn
+ "That username is already taken.")
:username-too-long
- (fn [_]
- "Username must be less than 30 characters.")
+ (renderer-fn
+ "Username must be less than 30 characters.")
:username-too-short
- (fn [_]
- "Username must be at least 2 characters.")})
+ (renderer-fn
+ "Username must be at least 2 characters.")})
; http://www.ibm.com/developerworks/java/library/j-clojure-protocols/
View
74 src/noir_auth_app/utilities.clj
@@ -1,4 +1,5 @@
-(ns noir-auth-app.utilities)
+(ns noir-auth-app.utilities
+ (:require [clojure.walk :as walk]))
; What's the convention for using an asterisk at the end of a function name in
; Clojure and other Lisp dialects?
@@ -10,3 +11,74 @@
[& ss]
(apply str (map eval ss)))
+
+(defn replace-symbol [form old-sym-name new-sym-name]
+ (walk/postwalk #(if (and (symbol? %) (= (name %) (name old-sym-name)))
+ (symbol new-sym-name)
+ %)
+ form))
+
+(defn compile-template
+ "Returns an optimized expression implementing the template specified in the
+ first parameter (a sequence), replacing the \"%\" in the interpolation
+ expressions with the symbol name specified in the second parameter.
+
+ (compile-template '(\"hello \" (:name %) \"!\") 'm)
+ =>
+ (str \"hello \" (:name m) \"!\")"
+ [ss sym-name]
+ (let [ss (->> ss
+ ; the (not= ...) allows the interpolation expression to be
+ ; simply % instead of something like (:name %)
+ (map #(if (and (symbol? %) (not= (name %) "%")) (eval %) %))
+ ; For all elements that are not strings, replaces any % with
+ ; the sym-name specified as a parameter. This is aimed at
+ ; things like (:name %) .
+ (map #(if (string? %)
+ %
+ (replace-symbol % "%" sym-name)))
+ ; http://clojuredocs.org/clojure_core/clojure.core/partition-by
+ (partition-by string?)
+ ; Concatenates the strings on each sublist:
+ ; (("hello " "Clojurian ") ((:name %) (:surname %)))
+ ; =>
+ ; (("hello Clojurian ") ((:name %) (:surname %)))
+ (map #(if (string? (first %)) (list (apply str %)) %))
+ ; (("hello Clojurian ") ((:name %) (:surname %)))
+ ; =>
+ ; ("hello Clojurian " (:name %) (:surname %))
+ ; http://clojuredocs.org/clojure_core/clojure.core/flatten
+ ; http://stackoverflow.com/questions/5232350/clojure-semi-flattening-a-nested-sequence
+ (apply concat))]
+ (if (= (count ss) 1) (first ss) (cons 'str ss))))
+
+(defmacro renderer-fn
+ "(def app-name \"Demo App\")
+ (def f (renderer-fn \"Hello \" (:name %) \", \"
+ \"welcome to \" app-name \"! \"
+ \"You can also break long lines for code readability \"
+ \"without sacrificing performance.\"))
+
+ f is now something like (use macroexpand on the renderer-fn macro to see it):
+
+ (fn* ([& p__102]
+ (clojure.core/let [[m101] p__102]
+ (str \"Hello \" (:name m101)
+ \", welcome to Demo App! You can also break long lines for readability without sacrificing performance.\"))))
+
+ and it can be used like this:
+
+ (f {:name \"Xavi\"})
+ =>
+ \"Hello Xavi, welcome to Demo App! You can also break long lines for readability without sacrificing performance.\"
+
+ and it's probably the fastest templating that you can get in Clojure
+
+ (time (f {:name \"Xavi\"}))
+ => \"Elapsed time: 0.068 msecs\"
+ in my MBP (2.26 GHz Intel Core 2 Duo)
+ "
+ [& ss]
+ (let [m (gensym "m")]
+ `(fn [& [~m]] ~(compile-template ss (name m)))))
+

0 comments on commit babd1b3

Please sign in to comment.
Something went wrong with that request. Please try again.