Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
ptaoussanis committed Jan 4, 2013
2 parents 8044800 + 4177f58 commit 6b14ad2
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 37 deletions.
8 changes: 4 additions & 4 deletions README.md
@@ -1,7 +1,7 @@
Current [semantic](http://semver.org/) version:

```clojure
[com.taoensso/tower "1.0.0"]
[com.taoensso/tower "1.1.0"]
```

# Tower, a Clojure internationalization & translation library
Expand All @@ -27,7 +27,7 @@ Tower is an attempt to present a **simple, idiomatic internationalization and lo
Depend on Tower in your `project.clj`:

```clojure
[com.taoensso/tower "1.0.0"]
[com.taoensso/tower "1.1.0"]
```

and `require` the library:
Expand Down Expand Up @@ -199,8 +199,8 @@ CDS (Clojure Documentation Site) is a contributor-friendly community project aim
## Contact & Contribution
Reach me (Peter Taoussanis) at *ptaoussanis at gmail.com* for questions/comments/suggestions/whatever. I'm very open to ideas if you have any! I'm also on Twitter: [@ptaoussanis](https://twitter.com/#!/ptaoussanis).
Reach me (Peter Taoussanis) at [taoensso.com](https://www.taoensso.com) for questions/comments/suggestions/whatever. I'm very open to ideas if you have any! I'm also on Twitter: [@ptaoussanis](https://twitter.com/#!/ptaoussanis).
## License
Copyright © 2012 Peter Taoussanis. Distributed under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html), the same as Clojure.
Copyright © 2012 Peter Taoussanis. Distributed under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html), the same as Clojure.
7 changes: 4 additions & 3 deletions project.clj
@@ -1,9 +1,10 @@
(defproject com.taoensso/tower "1.0.0"
(defproject com.taoensso/tower "1.1.0"
:description "Clojure internationalization & translation library"
:url "https://github.com/ptaoussanis/tower"
:license {:name "Eclipse Public License"}
:dependencies [[org.clojure/clojure "1.3.0"]
[com.taoensso/timbre "1.0.0"]]
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/tools.macro "0.1.1"]
[com.taoensso/timbre "1.2.0"]]
:profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
:1.5 {:dependencies [[org.clojure/clojure "1.5.0-alpha3"]]}
Expand Down
32 changes: 19 additions & 13 deletions src/taoensso/tower.clj
Expand Up @@ -13,18 +13,19 @@

(declare compiled-dictionary)

(defonce config
^{:doc
"This map atom controls everything about the way Tower operates.
(utils/defonce* config
"This map atom controls everything about the way Tower operates.
To enable translations, :dictionary should be a map of form
{:locale {:ns1 ... {:nsN {:key<decorator> text}}}}}.
To enable translations, :dictionary should be a map of form
{:locale {:ns1 ... {:nsN {:key<decorator> text}}}}}.
:default-locale controls fallback locale for `with-locale` and `t`.
:dev-mode? controls `t` automatic dictionary reloading and default
`log-missing-translation!-fn` behaviour.
:default-locale controls fallback locale for `with-locale` and `t`.
:dev-mode? controls `t` automatic dictionary reloading and default
`log-missing-translation!-fn` behaviour.
See source code for details."}
See source code for details.
See `set-config!`, `merge-config!` for convenient config/dictionary
editing."
(atom {:dev-mode? true
:default-locale :en

Expand All @@ -48,7 +49,8 @@
(timbre/warn "Missing translation" k-or-ks "for" locale)
(timbre/debug "Missing translation" k-or-ks "for" locale)))}))

(defn set-config! [[k & ks] val] (swap! config assoc-in (cons k ks) val))
(defn set-config! [[k & ks] val] (swap! config assoc-in (cons k ks) val))
(defn merge-config! [& maps] (apply swap! config utils/deep-merge maps))

;;;; Locales (big L for the Java object)

Expand Down Expand Up @@ -306,13 +308,17 @@
read-string
(set-config! [:dictionary]))
;; For automatic dictionary reloading:
(set-config! [:dict-res-name] resource-name))))
(set-config! [:dict-res-name] resource-name)
(catch Exception _
(throw (Exception. (str "Failed to load dictionary from resource: "
resource-name)))))))

(defn- compile-map-path
"[:locale :ns1 ... :nsN unscoped-key<decorator> translation] =>
{:locale {:ns1.<...>.nsN/unscoped-key (f translation decorator)}}"
[path]
{:pre [(seq path) (>= (count path) 3)]}
(when-not (and (seq path) (>= (count path) 3))
(throw (Exception. "Failed to compile dictionary: malformed")))
(let [path (vec path)
locale-name (first path)
translation (peek path)
Expand Down Expand Up @@ -420,7 +426,7 @@

;; Automatic dictionary reloading
(when (and dev-mode? dict-res-name
(utils/file-resource-modified? dict-res-name))
(utils/file-resources-modified? dict-res-name))
(load-dictionary-from-map-resource! dict-res-name))

(let [;; :ns1.<...>.nsM.nsA.<...>/nsN = :ns1.<...>.nsN/key
Expand Down
66 changes: 49 additions & 17 deletions src/taoensso/tower/utils.clj
@@ -1,9 +1,18 @@
(ns taoensso.tower.utils
{:author "Peter Taoussanis"}
(:require [clojure.string :as str]
[clojure.java.io :as io])
(:require [clojure.string :as str]
[clojure.java.io :as io]
[clojure.tools.macro :as macro])
(:import [java.io File]))

(defmacro defonce*
"Like `clojure.core/defonce` but supports optional docstring and attributes
map for name symbol."
{:arglists '([name expr])}
[name & sigs]
(let [[name [expr]] (macro/name-with-attributes name sigs)]
`(clojure.core/defonce ~name ~expr)))

(defn leaf-paths
"Takes a nested map and squashes it into a sequence of paths to leaf nodes.
Based on 'flatten-tree' by James Reaves on Google Groups.
Expand Down Expand Up @@ -40,7 +49,7 @@
(defn inline-markdown->html
"Uses regex to parse given markdown string into HTML. Doesn't do any escaping.
**x** => <strong>x</strong>
*x* => <emph>x</emph>
*x* => <em>x</em>
__x__ => <b>x</b>
_x_ => <i>x</i>
~~x~~ => <span class=\"alt1\">x</span>
Expand All @@ -49,11 +58,11 @@
(-> (apply str strs)
;; Unescaped X is (?<!\\)X
(str/replace #"(?<!\\)\*\*(.+?)(?<!\\)\*\*" "<strong>$1</strong>")
(str/replace #"(?<!\\)\*(.+?)(?<!\\)\*" "<emph>$1</emph>")
(str/replace #"(?<!\\)\*(.+?)(?<!\\)\*" "<em>$1</em>")
(str/replace #"\\\*" "*") ; Unescape \*s

(str/replace #"(?<!\\)__(.+?)(?<!\\)__" "<b>$1</b>")
(str/replace #"(?<!\\)_(.+?)(?<!\\)__" "<i>$1</i>")
(str/replace #"(?<!\\)_(.+?)(?<!\\)_" "<i>$1</i>")
(str/replace #"\\\_" "_") ; Unescape \_s

(str/replace #"(?<!\\)~~(.+?)(?<!\\)~~" "<span class=\"alt1\">$1</span>")
Expand Down Expand Up @@ -100,19 +109,22 @@
file doesn't exist."
[resource-name]
(when-let [^File file (try (->> resource-name io/resource io/file)
(catch Exception _ nil))]
(catch Exception _))]
(.lastModified file)))

(def file-resource-modified?
"Returns true iff the file backing given named resource has changed since this
function was last called."
(let [;; {file1 time1, file2 time2, ...}
previous-times (atom {})]
(fn [resource-name]
(let [time (file-resource-last-modified resource-name)]
(if-not (= time (get @previous-times resource-name))
(do (swap! previous-times assoc resource-name time) true)
false)))))
(def file-resources-modified?
"Returns true iff any files backing the given group of named resources
have changed since this function was last called."
(let [;; {#{file1A file1B ...#} (time1A time1A ...),
;; #{file2A file2B ...#} (time2A time2B ...), ...}
group-times (atom {})]
(fn [& resource-names]
(let [file-group (into (sorted-set) resource-names)
file-times (map file-resource-last-modified file-group)
last-file-times (get @group-times file-group)]
(when-not (= file-times last-file-times)
(swap! group-times assoc file-group file-times)
(boolean last-file-times))))))

(defn parse-http-accept-header
"Parses HTTP Accept header and returns sequence of [choice weight] pairs
Expand All @@ -130,4 +142,24 @@
(parse-http-accept-header "en-GB")
(parse-http-accept-header "en-GB,en;q=0.8,en-US;q=0.6")
(parse-http-accept-header "en-GB , en; q=0.8, en-US; q=0.6")
(parse-http-accept-header "a,"))
(parse-http-accept-header "a,"))

(defn deep-merge-with ; From clojure.contrib.map-utils
"Like `merge-with` but merges maps recursively, applying the given fn
only when there's a non-map at a particular level.
(deepmerge-with + {:a {:b {:c 1 :d {:x 1 :y 2}} :e 3} :f 4}
{:a {:b {:c 2 :d {:z 9} :z 3} :e 100}})
=> {:a {:b {:z 3, :c 3, :d {:z 9, :x 1, :y 2}}, :e 103}, :f 4}"
[f & maps]
(apply
(fn m [& maps]
(if (every? map? maps)
(apply merge-with m maps)
(apply f maps)))
maps))

(def deep-merge (partial deep-merge-with (fn [x y] y)))

(comment (deep-merge {:a {:b {:c {:d :D :e :E}}}}
{:a {:b {:g :G :c {:c {:f :F}}}}}))

0 comments on commit 6b14ad2

Please sign in to comment.