Emacs publish
什么是 Muse
如果你知道 Wiki,那一定很快就能熟悉 Muse。因为 Muse 就是一个本地的Wiki 发布系统,它的前身就是 <literal>EmacsWiki</literal>。Muse 把 <literal>EmacsWiki</literal> 的代码重写一遍,强调 了它的编辑(authoring) 和发布 (publishing) 功能。目标是能产生好看并且多 种格式兼容的文档。
特性
Muse 是由 <literal>EmacsWiki</literal> 衍生的,自然 <literal>EmacsWiki</literal> 大部分的特性都保留下 来,EmacsWikiMode 上列举了一堆,我列一些吸引我使用 Muse 的原因:
- 标记简单,实际上也没有用到那么多标记
- 部分所见即所得,在 Emacs 中看到的文档很清爽
- 支持多种格式,你把 Muse 文档发布成 html,latex,docbook 等等多种格
式,如果还有需要生成其它格式文档,可以自己写一个转换的插件
- 可定制,有 elisp 支持,所以很容易写出自己需要的功能或扩展
- 兼容 org 表格
- 高亮源代码
安装
闲话少说,让我们直接进入实战吧。首先下载最新版本的 muse。如果是老手使 用哪个版本没有关系,但是新手最好使用这时我使用的版本 muse-3.12,以防止 muse 接口改变,使用一些配置不能用(当然,自己有能力研究也可以无视版本)。
和一般的 emacs 包一样,你可以选择按作者提供的方式安装,也可以直接把所 有 el 文件放到 load-path 中的某个目录中就行了。如果你在 linux 下或 windows 上安装了一般的 unix 开发环境,比如 msys 或 cygwin,还是推荐用 make 来安装。可以根据自己的情况修改 Makefile.defs1 文件中相关参数,比较重要的安装目录参数,比如 PREFIX,ELISPDIR。修改完后运行 #+BEGIN_SRCmake#+END_SRC 和 #+BEGIN_SRCmake install#+END_SRC 两个命令。
基本配置
Muse 文档中说在 .emacs 加上这样的基本配置:
(add-to-list 'load-path "<path to Muse>") (require 'muse-mode) ; load authoring mode (require 'muse-html) ; load publishing styles I use (require 'muse-latex) (require 'muse-texinfo) (require 'muse-docbook) (require 'muse-project) ; publish files in projects
但是我推荐如果你不是每天都用 muse 写东西,还是在使用时再导入需要的库。 方法是把你所有有关 muse 的配置写到一个 muse-init.el 文件中,放到 load-path 下的某个目录中,然后在 .emacs 中写:
(add-to-list 'load-path "<path to Muse>") (add-to-list 'auto-mode-alist '("\\.muse$" . muse-mode)) ;; Tricks to load feature when needed (defun muse-mode () (require 'muse-init) (muse-mode-choose-mode))
不要担心你这里定义了 muse-mode,因为当你导入 muse-init 时会同时导入 muse-mode,这样 muse-mode 就重定义成真正起作用的函数了。
在 muse-init.el 文件中除了加上前面一堆的 require,还有一些需要配置的东 西,你可以选择用 M-x customize-group RET muse 一个一个设定,也可以直接 写到 muse-init.el 文件中。我推荐的一些基本设置如下:
(setq ;; 使用 ido 来补全 muse-completing-read-function 'ido-completing-read ;; 使用 lisp 标签时不在打开 muse 文件时求值 muse-colors-evaluate-lisp-tags nil ;; 设置 (muse-publishing-directive "date") 的格式 muse-publish-date-format "%Y 年 %m 月 %d 日" ;; 单独的项目名字不会成为 wiki 链接 muse-wiki-ignore-bare-project-names t)
创建第一个 muse 文件
创建项目目录
如果你的文件比较多,应该使用项目来管理 muse 文件。项目可以让你的 muse 有统一的发布目录和风格,可以在各个项目中创建内部的 wiki 链接。项目由三 部分组成:
- 项目名字,虽然理论上中文也可以,但是我想还是老实有 E 文好
- 项目源文件设置,包括源文件目录及其它
- 项目输出文件设置,包括输入格式,保存目录等
在创建项目之前必须先考虑好项目之间的关系。虽然 muse 支持子目录发布,也就是 说在一个目录下包括子目录都使用相同的设置,但是我不推荐这样,因为这会让 内部的 wiki 链接很难写。使用子目录的形式唯一的好处是配置简单,可以同时 发布所有源文件。而一个目录一个项目的好处是可以精细调整参数,wiki 链接 写起来简单。另外在有命令行发布的程序后,一次发布多个项目也很容易就能实 现。
这里列一些我的项目设置:
(setq muse-project-alist `(("Emacs" ("~/Muse/Emacs" :default "index" :force-publish (,ywb-muse-recentchanges-page "WikiIndex")) (:base "html" :path "~/public_html/emacs")) ("ElispIntro" ("~/Muse/ElispIntro" :default "index" :force-publish (,ywb-muse-recentchanges-page "WikiIndex")) (:base "html" :path "~/public_html/elispintro") (:base "latexbook" :path "~/Muse/latex/elispintro" :exclude ,(regexp-opt '("index" "RecentChanges" "WikiIndex"))))))
一些使用经验
模板文件中的链接
如果发布到多层目录有一个问题是在共同的模板文件,比如 muse-html-header 文件中如何写链接,比如使用的相同的 css 文件链接或导航链接等。使用发布 的绝对路径当然没有问题,有没有使用相对路径的解决方案呢?
我写了一个 ywb-muse-relative-path 函数用于把发布文件所在的目录转换成与 发布根目录的相对路径。这样在你的头文件中可以用:
<lisp>(ywb-muse-relative-path "css/style.css")</lisp>
来引用在发布根目录下的 css/style.css 文件。这样发布的 html 文件中,根 目录下 index.html 这个链接为 "./css/style.css",而在 emacs/index.html 文件中这个链接为 "../css/style.css"。
#command-line
使用命令行发布项目
如果一次要发布的页面太多,确实这个还是很有必要的。当然这要借助命令行来 完成。在发布的源文件 examples 目录里有一个 publish-project 命令。没有 找到没有关系,就这几行:
#! /bin/bash emacs -q -batch -l muse-init.el -f muse-project-batch-publish "$@"
我稍微修改了一下 muse-project-batch-publish,可以用 <literal>--all</literal> 选项来发布所有定义的 project。
你可以以下面的方式使用这个命令:
publish-project [--force] [--all | ProjectName1 ProjectName2 ...]
<literal>--force</literal> 参数不考虑已发布文件的修改时间与源文件的关系,强制发布全部页面。
如何使用不同的模板
如果想让一个项目使用不同的模板文件,最简单的办法是重新定义一种发布格式。 看上去好像很复杂,实际上非常简单:
(muse-derive-style "my-html" "html" :header 'my-muse-html-header :footer 'my-muse-html-footer :style-sheet 'my-muse-html-style-sheet :maintainer "Ye Wenbin")
这里我定义一种新的发布格式 "my-html",其中 my-muse-html-header 和 my-muse-html-footer 同 muse-html-header 和 muse-html-footer 一样既可以 是一个字符串,也可以是一个文件名。其它没有修改参数就和发布格式 html 完 全一样。
如何安排其它目录
在 muse 文件中如果引用了图片或其它外部文件,muse 中的文件链接和发布文件 中的链接的有效性是一个问题。在 linux 上很简单,创建目录的符号链接就行了。 Windows 上我曾经研究过,相当野蛮,要修改 muse 的代码。不知道现在还能 不能用了,所以就不贴上来了。
高亮源代码的问题
muse 实现在 html 中高亮源代码。这个功能需要 htmlize 1.34 版的。
但是调用的 htmlize-region-for-paste 函数强制使用 inline-css,可以考虑
修改这个函数,把 (htmlize-output-type 'inline-css)
这一部分
注释了。
如果你也和我一样使用 executable 来自动加入shebang 行和设置文件模式,可 能会遇到一个问题是这些代码中可能也会自己加上 shebang 行。我的解决办法是 在使用 executable-set-magic 之前先检查当前 buffer 是否关联文件,比如:
(add-hook 'cperl-mode-hook (lambda () (when buffer-file-name (executable-set-magic "perl" "-w" t t))))
方便的插入标签
如果你用 html-mode,应该用到一个按键 C-c C-t,作用是成对的插入 html 标 签。在 muse-mode 也可以用这个命令:
(autoload 'sgml-tag "sgml-mode" "" t) (defvar muse-tag-alist '(("example") ("literal") ("lisp" n) ("#+BEGIN_SRC )) "Tag list for `sgml-tag'.") (add-hook 'muse-mode-hook (lambda () (set (make-local-variable 'sgml-tag-alist) muse-tag-alist) (modify-syntax-entry ?> ")" muse-mode-syntax-table) (modify-syntax-entry ?< "(" muse-mode-syntax-table) (define-key muse-mode-map (kbd "C-c /") 'sgml-close-tag) (define-key muse-mode-map (kbd "C-c t") 'sgml-tag)))
我写的一些扩展
增加预览的方式
Muse 只支持一种浏览方式,可以通过设置这个 style 的 :browser 属性实现。 对于 html 默认是用 browser-url 打开 html 文件,我增加两种浏览方式,一种 是直接打开 html 文件,绑定到 C-c C-c,另一种是用 w3m 打开,绑定到 C-c C-m。
在 muse 源文件中高亮源代码
muse 提供很方便修改 muse-mode 中高亮的接口。我写了一个函数用于在 muse-mode 中高亮源代码,增加的一个功能是可以在 #+BEGIN_SRC 码的行号。
在目录索引中显示标题
在文件中使用 #+BEGIN_SRC<lisp>(muse-index-as-string t t t)</lisp>#+END_SRC 可 以产生项目的索引。但是这样产生的链接文字是文件名,我觉得不是很有用,所 以写了一个兼容的函数 ywb-muse-index-as-string,可以使链接文字为实际的标 题。
产生最近更新页面
这个功能我觉得还是比较实用,但是 muse 没有实现,我自己写了一个,效果还 好。用法与生成目录类似,在文件中加入:
<lisp>(ywb-muse-generate-recentchanges)</lisp>
但是需要注意的是这个文件中最好只有这一句,因为这个函数是会修改这个文件 的。如果有其它文字,很有可能有影响。
显示项目文件的树状图
这个扩展能直观显示所有项目的树图。在光标在节点上时,可以用鼠标或按键发 布项目或文件。我想实现的另一个更重要的功能是能够自动提示你什么文件需要 重新输出发布文件,然后可以很容易就能发布。暂时这个功能还没有实现。
生成文件的 rss
虽然 Muse 有一个 muse-journal 的扩展,但是我一般不写 Journal,而且 它的那个生成 rss 的函数比较简单,不太容易扩展。 我写了一个扩展专门用于创建和修改 rss 文件,目前只支持 rss version 2.0。 还没有想好如何结合 muse 使用。目前只写了一个简单的命令用于生成我的网站 的 rss 文件。
Customize xml file
We can change the value of muse-xml-header, muse-html-footer and muse-xml-markup-regexps to generate developworks xml file.
(defcustom muse-xml-header "<?xml version=\"1.0\" encoding=\"<lisp> (muse-xml-encoding)</lisp>\"?> <MUSE> <pageinfo> <title><lisp>(muse-publishing-directive \"title\")</lisp></title> <author><lisp>(muse-publishing-directive \"author\")</lisp></author> <maintainer><lisp>(muse-style-element :maintainer)</lisp></maintainer> <pubdate><lisp>(muse-publishing-directive \"date\")</lisp></pubdate> </pageinfo> <!-- Page published by Emacs Muse begins here -->\n" "Header used for publishing XML files. This may be text or a filename." :type 'string :group 'muse-xml) (defcustom muse-xml-footer " <!-- Page published by Emacs Muse ends here --> </MUSE>\n" "Footer used for publishing XML files. This may be text or a filename." :type 'string :group 'muse-xml) (defcustom muse-xml-markup-regexps `(;; Beginning of doc, end of doc, or plain paragraph separator (10000 ,(concat "\\(\\(\n\\(?:[" muse-regexp-blank "]*\n\\)*" "\\([" muse-regexp-blank "]*\n\\)\\)" "\\|\\`\\s-*\\|\\s-*\\'\\)") ;; this is somewhat repetitive because we only require the ;; line just before the paragraph beginning to be not ;; read-only 3 muse-xml-markup-paragraph)) "List of markup rules for publishing a Muse page to XML. For more on the structure of this list, see `muse-publish-markup-regexps'." :type '(repeat (choice (list :tag "Markup rule" integer (choice regexp symbol) integer (choice string function symbol)) function)) :group 'muse-xml) (defcustom muse-xml-markup-functions '((anchor . muse-xml-markup-anchor) (table . muse-xml-markup-table)) "An alist of style types to custom functions for that kind of text. For more on the structure of this list, see `muse-publish-markup-functions'." :type '(alist :key-type symbol :value-type function) :group 'muse-xml) (defcustom muse-xml-markup-strings '((image-with-desc . "<image href=\"%s.%s\">%s</image>") (image . "<image href=\"%s.%s\"></image>") (image-link . "<link type=\"image\" href=\"%s\">%s.%s</link>") (anchor-ref . "<link type=\"url\" href=\"#%s\">%s</link>") (url . "<link type=\"url\" href=\"%s\">%s</link>") (link . "<link type=\"url\" href=\"%s\">%s</link>") (link-and-anchor . "<link type=\"url\" href=\"%s#%s\">%s</link>") (email-addr . "<link type=\"email\" href=\"%s\">%s</link>") (anchor . "<anchor id=\"%s\" />\n") (emdash . "%s--%s") (comment-begin . "<!-- ") (comment-end . " -->") (rule . "<hr />") (fn-sep . "<hr />\n") (no-break-space . " ") (line-break . "<br>") (enddots . "....") (dots . "...") (section . "<section level=\"1\"><title>") (section-end . "</title>") (subsection . "<section level=\"2\"><title>") (subsection-end . "</title>") (subsubsection . "<section level=\"3\"><title>") (subsubsection-end . "</title>") (section-other . "<section level=\"%s\"><title>") (section-other-end . "</title>") (section-close . "</section>") (footnote . "<footnote>") (footnote-end . "</footnote>") (begin-underline . "<format type=\"underline\">") (end-underline . "</format>") (begin-literal . "#+BEGIN_SRC") (end-literal . "#+END_SRC") (begin-emph . "<format type=\"emphasis\" level=\"1\">") (end-emph . "</format>") (begin-more-emph . "<format type=\"emphasis\" level=\"2\">") (end-more-emph . "</format>") (begin-most-emph . "<format type=\"emphasis\" level=\"3\">") (end-most-emph . "</format>") (begin-verse . "<verse>\n") (begin-verse-line . "<line>") (end-verse-line . "</line>") (empty-verse-line . "<line />") (begin-last-stanza-line . "<line>") (end-last-stanza-line . "</line>") (end-verse . "</verse>") (begin-example . "#+BEGIN_SRC") (end-example . "#+END_SRC") (begin-center . "<p><format type=\"center\">\n") (end-center . "\n</format></p>") (begin-quote . "<blockquote>\n") (end-quote . "\n</blockquote>") (begin-cite . "<cite>") (begin-cite-author . "<cite type=\"author\">") (begin-cite-year . "<cite type=\"year\">") (end-cite . "</cite>") (begin-quote-item . "<p>") (end-quote-item . "</p>") (begin-uli . "<list type=\"unordered\">\n") (end-uli . "\n</list>") (begin-uli-item . "<item>") (end-uli-item . "</item>") (begin-oli . "<list type=\"ordered\">\n") (end-oli . "\n</list>") (begin-oli-item . "<item>") (end-oli-item . "</item>") (begin-dl . "<list type=\"definition\">\n") (end-dl . "\n</list>") (begin-dl-item . "<item>\n") (end-dl-item . "\n</item>") (begin-ddt . "<term>") (end-ddt . "</term>") (begin-dde . "<definition>") (end-dde . "</definition>") (begin-table . "<table%s>\n") (end-table . "</table>") (begin-table-row . " <tr>\n") (end-table-row . " </tr>\n") (begin-table-entry . " <%s>") (end-table-entry . "</%s>\n")) ;;{{{ 自定义图片 html 标记代码 (setcdr (assoc 'image-with-desc muse-html-markup-strings) "<div class=\"figure\"> <div class=\"photo\"> <img #+BEGIN_SRC > <p>%3%</p> </div>") ;;}}}
Footnotes:
Footnotes:
Makefile.defs.default 两个文件都可以,你可以拷贝 Makefile.defs.default 成 Makefile.defs,这样修改后者可以防止错误修改文 件中的参数
可能 latex 或 pdf 有点问题,因为文件中的日期是 中文的,如果使用 latexcjk 或 pdfcjk 的话,这个文件中没有汉字,所以无法判定文件编 码,将使用默认编码 gb2312,如果你使用 utf-8 保存文件的话,会因为文件编 码内文件中指定的编码不一致而出错,可以通过设置变量 muse-latexcjk-encoding-default 为 #+BEGIN_SRC"{UTF8}{song}"#+END_SRC 来顺利产生 latex 文件或 pdf 文件。