如何减少下面Clojure代码中的重复?

时间:2017-01-11 22:00:58

标签: clojure enlive

我有以下带有渲染功能的Clojure代码,该代码使用enlive-html呈现html页面。根据所选语言,使用不同的html模板。

正如您所看到的,有很多代码重复,我想删除它。

我正在考虑编写一些宏,但是,如果我理解正确的话,语言(即lang参数)在宏执行时是不可用的,因为它是在请求中提供的,而且是在执行时而不是在编译时。

我还尝试修改enlive以便稍后添加i18n支持,但我的Clojure技能还没有。

所以问题是:

如何删除下面代码中的代码重复?

是enlive-html的方式还是我应该使用另一个库? 是否有一个类似于enlive的库,支持i18n?

谢谢!

请参阅此处的代码:

(ns myapp.core
  (:require [net.cgrand.enlive-html :as e))

(deftemplate not-found-en "en/404.html"
  [{path :path}]
  [:#path] (e/content path))

(deftemplate not-found-fr "fr/404.html"
  [{path :path}]
  [:#path] (e/content path))


(defn getTemplate [page lang]
  (case lang
      :en (case page
                :page/not-found not-found-en)
      :fr (case page
                :page/not-found not-found-fr)))

(defn render [lang [page params]]
  (apply (getTemplate page lang) params))

2 个答案:

答案 0 :(得分:2)

一方面,编写一个宏来生成你在这里为任意语言设置的确切代码并不是一件难事。另一方面,可能有一种比使用deftemplate更好的方法 - def d是你期望在源代码中通过名称引用的东西,而你只是想要创建这个东西,自动使用。但是我不熟悉enlive API,所以我不能说你应该做什么。

如果您决定坚持使用宏,您可以编写如下内容:

(defmacro def-language-404s [languages]
  `(do
     ~@(for [lang languages]
         `(deftemplate ~(symbol (str "not-found-" lang)) ~(str lang "/404.html")
            [{path# :path}]
            [:#path] (e/content path#)))
     (defn get-template [page# lang#]
       (case page#
         :page/not-found (case lang#
                           ~@(for [lang languages
                                   clause [(keyword lang)
                                           (symbol (str "not-found-" lang))]]
                               clause))))))

user> (macroexpand-1 '(def-language-404s [en fr]))
(do
  (deftemplate not-found-en "en/404.html"
    [{path__2275__auto__ :path}]
    [:#path] (content path__2275__auto__))
  (deftemplate not-found-fr "fr/404.html"
    [{path__2275__auto__ :path}]
    [:#path] (content path__2275__auto__))
  (defn get-template [page__2276__auto__ lang__2277__auto__]
    (case page__2276__auto__
      :page/not-found (case lang__2277__auto__
                        :en not-found-en
                        :fr not-found-fr))))

答案 1 :(得分:1)

经过相当多的Macro-Fu后,我得到了一个我很满意的结果。在一些不错的stackoverflowers的帮助下,我在enlive上编写了以下宏:

(ns hello-enlive
  (:require [net.cgrand.enlive-html :refer [deftemplate]]))

(defn- template-name [lang page] (symbol (str "-template-" (name page) "-" (name lang) "__")))
(defn- html-file [lang page] (str (name lang) "/" (name page) ".html"))
(defn- page-fun-name [page] (symbol (str "-page" (name page))))

(defmacro def-page [app languages [page & forms]]
  `(do
     ~@(for [lang languages]
         `(deftemplate ~(template-name lang page) ~(html-file lang page)
            ~@forms))

      (defn ~(page-fun-name page) [lang#]
         (case lang#
           ~@(for [lang languages
                   clause [(keyword lang) (template-name lang page)]]
               clause)))

      (def ^:dynamic ~app
        (assoc ~app ~page ~(page-fun-name page)))
      ))

(defmacro def-app [app-name languages pages]
  (let [app (gensym "app__")]
    `(do
       (def ~(vary-meta app merge {:dynamic true}) {})

       ~@(for [page# pages]
           `(def-page ~app ~languages ~page#))

       (defn ~app-name [lang# [page# params#]]
         (apply (apply (get ~app page#) [lang#]) params#)))))

...然后按照这样使用:

html模板存储在这样的树

html/fr/not-found.html
html/fr/index.html
html/en/not-found.html
html/en/index.html
...

...并且渲染逻辑如下所示:

(def-app my-app [:en :it :fr :de]
  [ [:page/index [] ]

    ;... put your rendering here

    [:page/not-found [{path :path}]
      [:#path] (content path)]])

......用法如下:

...
(render lang [:page/index {}])
(render lang [:page/not-found {:path path}])
...

结果虽然可以改进,但我认为非常好,没有重复和样板代码。