[New Package] 基于Emacs-Lisp中的HTML模版库

之前有个初步的想法:使用emacs-lisp的S表达式生成HTML,便写了pp-html,但功能单一,禁不起推敲。现在pp-html经过了大量的优化和完善,借鉴 Liquid 模版语言的设计思路,经过构建静态博客生成器package的考验,已经相当成熟啦!

新特性:

  • 支持CSS选择器语法糖
  • 支持无值属性
  • 新增:assign标签
  • 支持变量求值,函数求值,对象属性求值
  • 支持过滤器和自定义过滤器
  • 支持生成XML

应用场景:

  • 需要生成html片段
  • 用于构建静态博客生成器
  • 需要生成xml文件

点击查看示例:

例子
(setq assign-vars
      '(:assign name "geekinney blog"
                description "Emacs is a lifestyle :-) And happy hacking emacs!"
                menus ((:path "/" :name "Index")
                       (:path "/archive" :name "Archive")
                       (:path "/category" :name "Category")
                       (:path "/about" :name "About"))
                comment-p t
                comment-type "disqus"
                valine-block (p "this is valine block")
                disqus-block (p "this is disqus block")))

(setq header-block
      '(header @topheader
               (a @logo :href "/" $name)
               (p .description $description)))

(setq menu-block
      '(nav @topmenu
            (:for menu in $menus
                  (a :href $menu.path $menu.name))))

(setq article-block
      '(article
        (p ($ concat "Function: the site name is " ($ upcase $name)))
        (p (/ "Filter: the site name is " :concat (/ $name :capitalize)))
        (p (/ ("happy" "hacking" "emacs") :join " " :capitalize :concat "!"))))

(setq comment-block
      '(div @comment
            (:if comment-p
                 (:cond
                  ($ string= $comment-type "valine") (:include $valine-block)
                  ($ string= $comment-type "disqus") (:include $disqus-block)
                  t nil)
                 (p "The comment is closed!"))))

(setq side-block
      '(aside @sidebar
              (:block side-block
                      (p "this is base sidebar"))))

(setq footer-block
      '(:block footer-block
               (footer
                (p "this is base footer."))))

(setq base-block
      '((:include $assign-vars)
        (body
         (div .container
              (div .row
                   (div .col-12
                        (:include $header-block)))
              (div .row
                   (div .col-12
                        (:include $menu-block)))
              (div .row
                   (div .col-12 .col-sm-12 .col-md-8 .col-lg-8
                        (:include $article-block)
                        (:include $comment-block))
                   (div .col-md-4 .col-lg-4
                        (:include $side-block)))
              (div .row
                   (div .col-12
                        (:include $footer-block)))))))

(pp-html
 '(:extend $base-block
           (:block side-block
                   (p "this is extended sidebar"))
           (:block footer-block)))
结果
 <body>
  <div class="container">
    <div class="row">
      <div class="col-12">
        <header id="topheader">
          <a id="logo" href="/">geekinney blog</a>
          <p class="description">Emacs is a lifestyle :-) And happy hacking emacs!</p>
        </header>
      </div>
    </div>
    <div class="row">
      <div class="col-12">
        <nav id="topmenu">
          <a href="/">Index</a>
          <a href="/archive">Archive</a>
          <a href="/category">Category</a>
          <a href="/about">About</a>
        </nav>
      </div>
    </div>
    <div class="row">
      <div class="col-12 col-sm-12 col-md-8 col-lg-8">
        <article>
          <p>Function: the site name is GEEKINNEY BLOG</p>
          <p>Filter: the site name is Geekinney blog</p>
          <p>Happy hacking emacs!</p>
        </article>
        <div id="comment">
          <p>this is disqus block</p>
        </div>
      </div>
      <div class="col-md-4 col-lg-4">
        <aside id="sidebar">
          <p>this is extended sidebar</p>
        </aside>
      </div>
    </div>
    <div class="row">
      <div class="col-12">
        <footer>
          <p>this is base footer.</p>
        </footer>
      </div>
    </div>
  </div>
</body>

感兴趣的童鞋可以查看github的 README 或 这篇文章。这是我写的第一个正式的emacs package,希望各位大佬给我提些建议 :smile:

8赞

最近学了点前端的东西,走马观花看了下vuejs和reactjs,还是有点欣赏不了它们跟模板交互的方式,转了一圈觉得还是lisp这类看起来数据即是程序、程序即是数据处理方式比较舒服

哈哈 :smile:,那你可以试试 pp-html,语法超级容易理解,可以看下github的文档或我的博客。

最近入了clojurescript reagent的坑,所以看你这些感觉很亲切 :smile:

最受不了的就是这种 html 模板库了,完全就是一门方言了,散失了 HTML 最大的几个 好处:

1, HTML 语言标准

2, 各种 validator, lint 工具以及 IDE 支持

3, 所见即所得

浏览器打开 html 模板,直接可以看到展示效果,以前公司还有专门的重构师:

设计师出效果图(PhotoShop PNG)
  -> 重构师出 HTML 样板(HTML, CSS,img)
  -> 开发人员开发(TEMPLATE + JS)

还是比较容易接受 smarty、Handlebars、mustache 之类的,只是在 html 文件中添加一 些控制块。

Okay, just let it go.

我只是从个人从业经验角度对这种模板方案提出疑义哈,如果不用于生产而只是个人兴趣的 话还是有一定可玩性的,Just for fun!

类似的还有 node.js 的 http://jade-lang.com/

200617更新:

  • 新增三个逻辑标签。
  • 为for标签添加 else, break, continue 逻辑。
  • for标签可添加参数 :offset, :limit, :reversed 。
  • for标签中支持 (2…5 by 2) 形式的数字列表简写。
  • 新增29个过滤器。

详见 README

自己的看法,我觉得 Lisp 是很适合做这类的模板的,但是优势不是在于长什么样子,而是真的在用数据去表达 HTML,数据的关键在于可以操作变换。

所以我觉得应该是任意的语法自由变换得到的数据结构,通过一个 API 转成 HTML,而不是造一套 DSL。

1赞

我大概明白你的意思,但不知道怎么实现,毕竟新手。DSL的好处是将复杂的逻辑简单化,语法风格统一,方便新手使用。如果统一的api处理,想要写出复杂的逻辑势必要稍微精通elisp,对新手不是很友好。但是DSL想要进一步拓展只能不断堆叠代码,这是一个问题,我在写的过程中就深有体会。

只能说是有舍有得,况且在我的能力范围内也只能这样了,谢谢大佬指点!

1赞