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

How can I reduce the duplication in the Clojure code below?

我有以下带有 render 函数的 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))

一方面,编写一个宏可以为任意一组语言生成与此处所用代码完全相同的宏并不太难。另一方面,可能有比使用 deftemplate 更好的方法 - defd 的东西是您希望在源代码中按名称引用的东西,而您只是想创建这个东西并自动使用。但是我不熟悉 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))))

经过一段时间的 Macro-Fu 我得到了一个令我满意的结果。在一些不错的 Whosebugers 的帮助下,我在 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}])
...

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