Emacs Muse 是一个可以在 Emacs 中写 Wiki 文档的插件,通过 Emacs Muse,我们可以很容易地编写 Wiki 文档,并生成各种格式的文件。本文介绍了如何扩展使用 Emacs Muse —— 一个 Emacs 编辑器插件来生成精美的测试结果报告。
前言
Emacs 是一个开放源代码的编辑器。由于它使用效率高,可扩展性强,自20世纪70年代诞生以来就一直经久不衰,受到广大开发人员的热烈追捧。关于 Emacs 的各种介绍和使用技巧教程屡见不鲜。而本文所介绍的 Emacs Muse 是 Emacs 的一个扩展插件,它的前身就是 Emacs Wiki。由于 Emacs Wiki Mode 原作者 Michael Olson 需要重新架构代码,才另外创立了一个新的 Emacs Muse 项目。通过该插件,我们可以在 Emacs 中写 Wiki 文档,生成各种格式,包括网页,pdf ,DocBook ,LaTex 等等,并可以直接发布到网络中。
我们在工作过程中,往往需要制作一些结果报告,例如测试结果报告。在一份测试报告中,需要统计测试结果,并对结果进行一定的分析和总结。如果我们想制作一份精美的报告,包括能对不同的结果着色,自动统计结果,对结果分析能有按照不同的格式来突出显示,这样的工作完全可以通过 Emacs Muse 来实现。而且 Emacs Muse 可能按照 Wiki 网页的要求来生成 HTML 格式的网页,发布到网络中供人浏览。
Emacs Muse 的安装与配置
从 Emacs Muse 的官方网站上(参考资源)可以下载最新的 Muse 安装包,解压后修改Makefile.defs 文件来设置 Emacs 的安装路径,如清单1所示,该路径是 Mac OS X 平台下 Emacs 的设置,其他平台也可以类似修改。
清单 1 Makefile.defs示例
#设置EMACS的路径 EMACS = emacs SITEFLAG = --no-site-file #设置Muse安装路径 DESTDIR = PREFIX = /Applications/Emacs.app/Contents/Resources ELISPDIR = $(DESTDIR)$(PREFIX)/site-lisp/muse INFODIR = $(DESTDIR)$(PREFIX)/info |
安装路径设置完成后,用 GNU Make 编译安装 Muse ,只需要运行 make install 命令即可。
安装完成后,我们需要修改 Emacs 的配置文件 .emacs 来加载 Muse 包,添加如清单2所示的代码到 .Emacs 文件中。
清单2 .emacs文件配置
(require 'muse-mode) (require 'muse-html) ;添加html格式的支持 (require 'muse-latex) ; 添加latex格式的支持 (require 'muse-texinfo) ; 添加texinfo格式的支持 (require 'muse-docbook) ; 添加docbook格式的支持 (require 'muse-wiki nil t) (require 'muse-project) ; 添加wiki project的支持 ;设置编码方式为utf-8 (setq muse-html-meta-content-type (concat "text/html; charset=utf-8")) ;新建一个wiki工程 (setq muse-project-alist '(("MyWiki" ("~/Documents/wiki" :default "index") (:base "html" :path "~/Document/wiki/publish")))) |
这样就完成了配置,在这个配置文件里创建了我们第一个 wiki 工程 MyWiki。
Emacs Muse的基本操作
配置完成以后重新启动 Emacs,就已经加载了 Muse。这时所有的以 muse 为扩展名的文件都是 Emacs Muse 的源文件,我们只需要用一些简单的标记来编写 muse 文件,然后用 Emacs 来生成各种格式的输出,下面我们就以生成一个简单的 Wiki 网页为例介绍下 Emacs Muse 的基本操作。
在 Emacs 中按快捷键 Ctrl+x Ctrl+f ,创建文件 ~/Documents/wiki/FirstPage.muse ,输入如清单3所示的内容:
清单3 FirstPage.muse
#title 第一个Wiki页面 * 一级标题需要一个星号开头 ** 二级标题需要两个星号开头 *** 三级标题需要三个星号开头 段落需要两个以上空行 居中一行文字需要以6个以上的空白开头 ---- 示一个横线只需要输入4个以上的“-”标记 这是 *着重* 的文字,这是 **进一步着重** 的文字,这是 ***更进一步的着重*** 的文字 这是 _下划线_ 文字,这是 =等宽verbatim and monospace= 的文字 *** 列表: - 无序列表需要以空格和“-”开头 1. 有序列表需要以空格和数字序号开头 字典 :: 名词定义需要以“::”分隔名词和所定义文字 - 列表也可以嵌套 1. 列表嵌套深度按照开头空格的多少来控制 2. 可以继续嵌套不同类型的列表 - 比如这样 *** 表格: 表格标题 || 用“||”分割 表格内容 | 用“|”分割 表格结尾 ||| 用“|||”分割 |
在 FirstPage.muse 文件输入过程中,Emacs 的 Muse 模式会根据用户的输入,生成不同的显示预览,如图1所示,这样大大方便了我们写 wiki 的效率,在 Emacs 中实现了所见即所得的用户体验。
图 1. Emacs 的 Muse 模式
输入完成后,按快捷键 Ctrl+c Ctrl+p ,该快捷键用来在当前工程目录下生成 wiki 网页。生成成功后, wiki 网页位于 ~/Documents/wiki/publish/FirstPage.html 。按快捷键 Ctrl+c Ctrl+v 来预览该网页,如图2所示:
图 2. 第一份 wiki 网页
这样我们就生成了一份 wiki 网页。在 firstPage.muse 文件中,我们用各种标记符号(*, -, =)来表示网页中的各种元素(标题,加粗,列表),这样就非常简单地生成了一张 wiki 网页,它可以发布到网站中直接供人阅读,非常适用于记录技术文档和笔记。
更为重要的是,Emacs Muse 不仅可以由 wiki 记号生成 HTML 格式,还可以生成 Latex, texinfo, docbook 等等多种格式,这也意味着我们完全可以用 Emacs Muse 来制作各种格式的文档。Emacs Muse 对多种文档格式的支持非常丰富,这在它的官方网站上可以找到完整的支持文档列表。
自定义单元测试报告的样式和自动分析结果
上面内容简单介绍了 Emacs Muse 的基本用法和基本快捷键。由于 Emacs 天生的易于扩展的特点,Emacs Muse 也提供了大量的函数接口来扩展它的功能。扩展 Emacs 所用的 Elisp 语言简洁优美,非常容易学习和编程。
Muse 默认生成的 HTML 格式是非常普通的网页样式,我们可以通过自定义其样式来美化其输出。我们可以根据项目,工作和学习的具体需要,来定义不同需求的格式,使其成为一个多样式的网页生成工具。下面本文就介绍如何自定义一种简单的单元测试报告的格式,该格式基于 HTML 的输出样式,但自定义了表单样式,同时根据测试结果来生成统计数据。
将 Muse 扩展代码放在 emacs-muse.el 文件中,在 .emacs 文件最后一样加入代码来包括这个文件,如清单4所示,把加载 muse 包的代码以及自定义样式的代码放入到 emacs-muse.el 文件,这样可以方便编写调试,而且还能方便在命令行调用。
清单4 在.emacs文件中包括Muse扩展包加载代码
;; Emacs Muse (load-file "~/emacs-muse.el") |
自定义单元测试样式
Muse 提供了函数 muse-define-style 来自定义格式,还有函数 muse-derive-style 来继承已定义的格式。本文所需输出的测试结果是一种自定义的 HTML 格式,所以只需要函数 muse-derive-style 来继承自 HTML 格式即可,代码如清单5所示,这里我们定义了一个名为 UT 的新格式,它继承自 HTML 格式。
清单5 定义UT样式
(muse-derive-style "ut" "html" :header 'ut-html-header :style-sheet 'ut-style-sheet) |
在该格式中,HTML 文件头由函数 ut-html-header 定义,该函数代码如清单6所示:
清单6 ut-html-header函数定义HTML文件头
(setq ut-html-header "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"> <html> <head> <title><lisp> (concat (muse-publishing-directive \"title\") (let ((author (muse-publishing-directive \"author\"))) (if (not (string= author (user-full-name))) (concat \" (by \" author \")\")))) </lisp></title> <meta name=\"generator\" content=\"muse.el\"> <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\" content=\"<lisp>muse-html-meta-content-type</lisp>\"> <lisp> (let ((maintainer (muse-style-element :maintainer))) (when maintainer (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\">\"))) </lisp> <lisp>(muse-style-element :style-sheet muse-publishing-current-style) </lisp> </head> <body> <h1><lisp>(muse-publishing-directive \"title\")</lisp></h1> <p><big><em><lisp>(let ((author (muse-publishing-directive \"author\"))) (if (not (string= author (user-full-name))) (concat \"by \" author )))</lisp></em></big></p> <p><big><em><lisp>(let ((date (muse-publishing-directive \"date\"))) (concat \"Last Modified: \" date ))</lisp></em></big></p> <h2><lisp>(let ((package (muse-publishing-directive \"package\"))) (concat \"Test script Package &mdash\; \" package )) </lisp> </h2> <table class=\"muse-table\" border=\"2\" cellpadding=\"5\"> <tbody><tr><td><span class=\"pass\">PASS</span></td> <td>Passed all test</td></tr><tr><td><span class=\"fail\">FAIL</span></td> <td>Failed for new issue</td></tr><tr><td><span class=\"attempt\">ATTEMPTED</span></td> <td>Tried but there is known issue</td></tr> <tr><td><span class=\"nattempt\">NOT ATTEMPTED</span></td> <td>Not tried</td></tr></tbody></table> <!-- Page published by Emacs Muse begins here -->\n") |
清单6中,定义了 UT 样式中所输出的网页文件的开头部分代码。作为一个简单的单元测试结果报告样式,在该网页的开始部分,说明了报告的标题,测试人员名称,单元测试的代码包。定义了四种测试结果: PASS, FAIL, ATTEMPTED, NOT ATTEMPTED ,分别表示通过,失败,有已知错误但已运行了单元测试,未测试。这段报告的开头部分所对应的输出网页位置如图4 所示。
为了表示不同样式元素,我们需要有个自定义的样式表嵌入到输出网页中,清单5 的代码中,样式表格式由函数 ut-style-sheet 定义,该函数定义如清单7所示。
清单7 ut-style-sheet函数定义样式表
(setq ut-style-sheet "<style type=\"text/css\"> body { FONT: 14px/1.4 'Trebuchet MS',Verdana, Arial, Helvetica, sans-serif; background:#fff; width: 60em; margin: 0 auto 0; padding: 2em 0 6em 0; text-align: left; } a { font-family: Verdana; text-decoration:none; font-weight:bold; color:#c00; } a:hover { background:#000000; color:#FFFFFF; } h1 a { color:#666;} h2 a { color:#666;} h3 a { color:#666;} h4 a { color:#666;} h1 { font-size: 40px; color:#666; border-bottom: 5px solid #000; padding: 2px; margin: 0px; margin-bottom: 8px; } h2 { color:#666; font-size: 22px; padding: 2px; margin-top: 15px; border-bottom: 2px solid #000000; } h3 { color:#666; font-size: 18px; padding: 2px; margin-top: 5px; } h4 { color:#666; font-size: 18px; padding: 2px; margin-top: 5px; } img { float: right; margin: 10px; border-style:solid; border-width:2px; } #im { clear: right;} pre { border: #777777 1px solid; padding: 0.5em; margin-left: 1em; margin-right: 2em; white-space: pre; background-color: #e6e6e6; color: black; } .pass { color:Green; } .fail { color:Red; font-weight: bold; } .attempt { color:Maroon; font-weight: bold; } .nattempt { color:Silver; } .verse { white-space: pre; margin-left: 1em; } dt { font-weight: bold;} li { margin-bottom: 0.9ex;} blockquote { margin-left: 2em; color: #4444ff; } td { font-size: 13px;} th { background: #d6d6d6; font-size: 14px; } </style>") |
在清单7所示的样式表中,我们定义了网页报告中的各种元素(标题,章节,表格等)的格式。而且还专门针对于单元测试结果报告,定义了四种样式: .pass, .fail, .attempt, .nattempt 。定义了它们的颜色和字体,读者可以根据自己的需要,修改它们的样式。
这样我们做好了一份测试报告的准备工作,定义了不同测试结果的 html 样式,定义了报告开始部分的格式。下面将介绍如何生成报告的主体部分。
自定义单元测试报告内容格式
在 Muse 中我们可以使用一种格式类似于 XML 的标签来表示特定的内容,例如如果想输入一段包括了 Muse 的关键字字符的文本,而又不想被 Muse 所解释这些关键字字符,则可以在 Muse 文件中输入如清单8所示的文本:
清单8 标签示例
<verbatim> 这是 *着重* 的文字,这是 **进一步着重** 的文字,这是 ***更进一步的着重*** 的文字 </verbatim> |
输出则如图3所示。
图 3. verbatim 标签输出示例
Muse 提供了多种标签可以在 Muse 文件中使用,包括 lisp(动态嵌入 lisp 代码的运行结果),python (动态嵌入 python 代码的运算结果), src (对标签内的代码文本进行着色)等等。通过 Muse 函数 muse-publish-markup-tags ,我们可以自定义标签。本测试报告自定义了 result 标签,通过在该标签范围内输入测试结果, 那么Muse 在生成 HTML 文件时,会根据我们的扩展代码解释 result 标签内的内容,生成我们所期望的格式。result 标签的定义代码如清单9所示。
清单9 result标签定义代码
(defvar muse-pass-tag '("pass" t nil nil muse-ut-pass-tag)) (defun muse-ut-pass-tag (beg end) (delete-region beg end) (goto-char beg) (muse-insert-markup "<span class=\"pass\">PASS</span>")) (defvar muse-fail-tag '("fail" t nil nil muse-ut-fail-tag)) (defun muse-ut-fail-tag (beg end) (delete-region beg end) (goto-char beg) (muse-insert-markup "<span class=\"fail\">FAIL</span>")) (defvar muse-attempt-tag '("attempt" t nil nil muse-ut-attempt-tag)) (defun muse-ut-attempt-tag (beg end) (delete-region beg end) (goto-char beg) (muse-insert-markup "<span class=\"attempt\">ATTEMPTED</span>")) (defvar muse-nattempt-tag '("nattempt" t nil nil muse-ut-nattempt-tag)) (defun muse-ut-nattempt-tag (beg end) (delete-region beg end) (goto-char beg) (muse-insert-markup "<span class=\"nattempt\">NOT ATTEMPTED</span>")) (defvar muse-result-tag '("result" t t nil muse-ut-result-tag)) (defun muse-ut-result-tag (beg end attrs) "Insert unit test result <result> ... </result>." (muse-publish-markup-attribute beg end attrs nil (save-excursion (save-restriction (setq passed 0) (setq failed 0) (setq attempted 0) (setq nattempted 0) (goto-char (point-min)) (while (re-search-forward "<pass/>" nil t) (setq passed (1+ passed))) (goto-char (point-min)) (while (re-search-forward "<fail/>" nil t) (setq failed (1+ failed))) (goto-char (point-min)) (while (re-search-forward "<attempt/>" nil t) (setq attempted (1+ attempted))) (goto-char (point-min)) (while (re-search-forward "<nattempt/>" nil t) (setq nattempted (1+ nattempted))) (goto-char (point-min)) (setq count (+ passed failed attempted nattempted)) (goto-char (point-min)) (muse-insert-markup (concat "<h3>Result:</h3><p><strong><em>" "Totally " (number-to-string count) " cases," (number-to-string passed) " passed, " (number-to-string failed) " failed, " (number-to-string attempted) " attempted, " (number-to-string nattempted) " not attempted." "</em></strong></p>")) )))) (add-to-list 'muse-publish-markup-tags muse-result-tag) (add-to-list 'muse-publish-markup-tags muse-pass-tag) (add-to-list 'muse-publish-markup-tags muse-fail-tag) (add-to-list 'muse-publish-markup-tags muse-attempt-tag) (add-to-list 'muse-publish-markup-tags muse-nattempt-tag) |
在清单9中,首先我们定义了四个单元测试结果标签,这四个标签分别代表了四种单元测试结果,并对每种结果应用对用的样式表样式,进行着色和设置字体。然后定义了 result 标签,该标签统计在该标签内容中所包含的每种测试结果标签的个数。最后一句代码将 result 标签和四种单元测试结果标签发布到 muse 的标签列表中。
解释 result 标签的代码位于 muse-ut-result-tag 函数中,它首先定义了4个变量用于存储每种测试结果的次数,然后不断遍历标签中的文本,统计测试结果标签的个数来获知测试结果。最后,该函数输出统计结果,将其列在报告中的 Result 小节的开始处,如图4 所示。
生成第一份单元测试结果报告
上面做好了所有的准备工作,保存好 emacs-muse.el 文件。重启 Emacs 或者运行命令 M-x eval-buffer ,我们自定义的单元测试报告格式就生效了。
现在新建一个 Muse 工程,如清单10所示:
清单10 第一个单元测试报告工程
;添加一个wiki工程 (setq muse-project-alist '(("MyWiki" ("~/Documents/wiki" :default "index") (:base "html" :path "~/Document/wiki/publish")) ("MyUTReport" ("~/Documents/wiki2" :default "index") (:base "ut" :path "~/Document/wiki2/publish")))) |
新建 muse 源文件 ~/Documents/wiki2/firstReport.muse ,输入如清单11所示的文本。
清单11 第一份单元测试报告的Muse文件
#author sky #package org.example <result> Script Name || Result || Description Script_a || <fail/> || resaon...reason...reason...reason... Scrpit_b | <pass/> | Script_c | <nattempt/> | why...why...why...why...why...why...why... Script_d | <attempt/> | result...result...result...result </result> * Analysis 1. Below methods need refactor: <src lang="c"> 1. code ... 2. code2... </src> |
按快捷键 Ctrl+c Ctrl+p 发布工程,发布成功后,按快捷键 Ctrl+c Ctrl+v 来预览结果,结果图4 所示。
图 4. 第一份测试报告输出结果
同其他工具的结合
通过 Emacs Muse ,我们可以通过简单的标记来生成精美的测试结果报告。生成报告所需要的muse文件既可以是手动输入,也可以是由自动化测试工具来自动生成。通过 Emacs 的 batch 模式,我们可以从命令行运行 Emacs ,让它以命令行的形式执行 emacs-muse.el 文件中的 Elisp 代码,生成 HTML 格式的网页报告。这一切只需要执行如下的命令:
skys-imac:~ sky$ emacs –q -batch -l ~/emacs-muse.el –f muse-project-batch-publish MyWiki |
最后的 MyWiki 是 muse 工程的名字。这样,通过自动化的测试工具来生成 muse 文件,再从命令行运行生成 HTML 报告。这样就将 Emacs Muse 同其他工具完美结合在一起。而且可以根据自己需求来自定义样式,将艺术和技术完美地结合到了一起,达到了很好的效果。
结束语
Emacs Muse 扩展性强,并在发布 Wiki 方面有着独到的强大功能。它支持格式多,支持源代码标记,可嵌套列表等等。有兴趣的读者可以直接访问附录中 Muse 的官方网站 http://mwolson.org/ ,这个网站完全使用 Emacs Muse 生成并发布到网络中的,并且所有的 Muse 文件都是开放代码,可以直接下载学习的。正是由于它的强大扩展性,也使得我们可以自定义样式来生成符合我们要求的文档,这也正是 Emacs 编辑器的迷人之处。
(责任编辑:A6)