魔力女管家音乐歌词数据库搭建完成,顺便说说Sphinx

就像上一篇文章许诺的,我费了三天时间,把魔力女管家歌词库给搭出来了!

先上地址:http://fireattack.github.io/mahoromaticdb/ 在Github因为侵权把我的网站日掉之前,暂时就挂在这里了。

包括了所有的CD的简要信息,以及所有歌曲信息歌词(大部分含翻译)。所有的CD我都用当年收藏的Booklet精心制作了1000 px的封面(除了少数几个找不到BK的),欢迎使用。哦里面在最后还随意地包含了两张同人CD(C60、C61发售)的信息,其中第一张网上应该是能找到的,意外地非常好听,强烈推荐。

搭建过程中才发现我之前的文档写的有多烂。有好多后来才找到歌词的歌没包括就算了,格式也是一团糟,错字、标点符号不统一的问题比比皆是。看来我当年的强迫症要轻得多啊!我尽量把标点符号统一为:跟日文用全角,跟英文用半角,但是连用符号(例如:!?)用半角,波浪号的副标题前空一格(但是魔力女管家第二季的标题则不空),括号统一用全角。艺术家名义统一用角色(声优)的格式,但是少数早期CD直接单用声优名字。

呃好吧我承认这并不是特别统一…因为我有个更严重的强迫症,叫做“名从主人”…我一般会尽量遵照BK上的写法,因此牺牲一些统一度。

接下来的地方讲讲搭建网站过程中的一些值得记录的东西吧。

这次用的技术是Sphinx——一个用Python写成的Doc建站软件。之所以没有用Github Pages支持的Jekyll,主要是那个是用Ruby写的,不想去接触。不过后来才发现使用过程中99%都只是在和reStructuredText这玩意打交道,和后端的语言一点关系都没有,早知道就用Jekyll了——毕竟那个是用更简单的Markdown的来生成HTML的。

果然还是先说reStructuredText这种标记语言吧,毕竟大部分时间都耗在和他打交道了。一言以蔽之,这玩意的语法非常的反直觉和不灵活。不过用了三天之后,也算是慢慢熟悉了。reStructuredText的设计思想就是非常注重可读性:基本上而言,源代码就已经在ascii的层面上“格式化”了。例如其最反人类的设定——表格,正常来讲你需要手动用各种线把框框画出来(见下)!

+------------------------+------------+----------+----------+
| Header row, column 1   | Header 2   | Header 3 | Header 4 |
| (header rows optional) |            |          |          |
+========================+============+==========+==========+
| body row 1, column 1   | column 2   | column 3 | column 4 |
+------------------------+------------+----------+----------+
| body row 2             | Cells may span columns.          |
+------------------------+------------+---------------------+
| body row 3             | Cells may  | - Table cells       |
+------------------------+ span rows. | - contain           |
| body row 4             |            | - body elements.    |
+------------------------+------------+---------------------+

还好,还有csv table可以用,否则这真的要死人的(不过这里有个table ganerator可以用)。

除了最基本的一些标记(例如,粗体、斜体啥的,不过注意和Markdown不同),reStructuredText核心元素是directive和role这俩东西。前者是一种特定格式/结构的元素,一般“成块”出现;一般格式是:

.. directivename:: argument ...
   :option: value

   Content of the directive.

在第一行调用directive的名字,然后在第二行起带缩进写设置,然后空一行带缩进(缩进必须和前面保持一致)写被格式化的内容(有些时候则没有内容,比如图片啥的)。这个东西可以用来实现插入目录、图片、目标(锚点)、给内容加class等功能。role和directive类似,但是一般是用于行内(inline)mark一些内容,例如上面提到的粗体、斜体啥的,本质上也是一种预定义好的role。你也可以自定义role。

那么就大致按照我写站的时间顺序来讲吧,没啥逻辑关系。

在建立了你的Sphinx网站之后(推荐使用官方带的Sphinx-quickstart),第一件事在index(链接是到rst源代码,下同)页面中把站点的目录放在上面。这个倒是蛮简单,用Sphinx自带的toctree这个directive就行。在目录里你可以输入想包括的页面名称,以及目录深度。我设想的结构是所有CD信息在一个页面(cdlist.rst),而歌曲因为歌词较长,则每首歌分割为一个单独的页面放到里子目录songs/里。

这里就遇到了第一个问题:如果你用songs/kaerimichi的方式添加页面到toctree中,他会和cdlist都是平级——而不是处于一个Songs的一级目录之下。稍微研究了一下发现要这么做:在songs/子目录下先建立一个index.rst,然后在该rst中再建立一个toctree(下称toctree2),包含所有songs/下的页面。因为toctree总是从当前目录开始查询的,所以对于toctree2,你只需要罗列所有歌曲页面的名字就行,无需加songs/前缀。当然因为歌曲太多了,我们利用:glob:这个参数,就可以用通配符*来匹配所有页面了。回到根目录index的toctree,我们也只需要包含songs就可以了,会自动把toctree2里面的项显示为二级项(结果)。

正式写页面,对于CD list,我需要在最上面附一个(本页面内的)目录。这个用带的contents directive就能轻松做到。但是默认会加一个很多余的根目录节点,用:local:参数去掉它。搞定后,这个目录就会列出所有的section了。加一个section很简单,只要在一行文字下面加一堆“-”、“=”之类的就行。如果要多级section则需要分别套用不同的符号,不过我这里只有一层所以无所谓。一般而言,每个section会自动生成锚点,上面加的目录就能跳转。

因为页面上某些文字需要是小字,而且这些文字混在正文中(inline),我们需要一个role。当你给内容指定了role之后,生成html会自动指定对应的class名,因此配合CSS就可以实现想要的样式。要定义一个role,要先在文档某处(一般是最开头,我不确定在别的地方可不可以)写:

.. role:: smallfont

之后(注意:每个用到:smallfont:的rst都必须重新写一遍这个…),你就能用:smallfont:`your content`来标记你的内容了。不过这里有个限制:这一段代码的前后必须是非“word”的东西,也就是说中间一般得有一个空格。如果你不想要你的普通内容和小字内容之间有空格?需要加反斜杠来消掉空格。即:

your normal content\ :smallfont:`your small font content without space inbetween`

OK,那现在在CSS里写:

.smallfont{
 font-size: 80%;
 color: grey;
}

就行了。不过,怎么把自定义的CSS包含在你生成的网站呢?这里有好几个办法

  1. conf.py中加上
    def setup(app):
        app.add_stylesheet('custom.css')  # may also be an URL

    (你的custom.css应该在_static/目录下)

  2. conf.py中加上(注意这里又变成从根目录起了…下同)
    html_context = {
     'css_files': ['_static/custom.css'],
    }
  3. 先把你模板里的layout.html拷贝到目录下的_templates里(当然,保证你没删掉conf.py里的templates_path = ['_templates']),然后找地方加一行
    {% set css_files = ['_static/custom.css'] %}

    (在SO看到的是

    {% set css_files = css_files + ['_static/custom.css'] %}

    但似乎使用上并没有区别。)

  4. 还是上面的说的layout.html,直接强行加
    {%- block extrahead %}
      		<link rel="stylesheet" href="{{ pathto('_static/custom.css', 1) }}" type="text/css" />
    {% endblock %}
    
  5. 最暴力的方法,找到你模板的CSS文件,然后修改之;或者修改之后放在_static/里(原因见下面)。

对于我用的alabaster模板,默认的layout.html已经包含了上面的选择4,所以我只需要把custom.css放在_static/下即可。哦这里顺便说句,你放在_static/下的文件默认全部都会复制到你build出来的html里(不管用不用得到),你可以利用这个来覆盖模板里的东西(如上面的5所述)——不过这里注意是覆盖,不是添加。另外,图片之类的resource就别往里放了,否则会复制两遍(因为所有引用过的图片会自动被Sphinx复制在build目录的_images/目录里),占地方。

搞定了“小字体”这个样式之后,在每个歌曲的页面,我还需要引入两种新的样式:日文和中文,分别用来标记不同语种的歌词,从而实现更好的字体显示效果。

这里因为是成块的内容,我们就不用role了,用一个directive:class。如名字所示,其功能和role类似,也是给一块内容标记class。原始的reStructuredText直接用class就行,但是Sphinx是为Python文档开发的,默认把class给定义成一个role了,所以需要改用rst-class

.. rst-class:: ja

	| まなざし そっと ひとつ
	| 誰にもみつからぬように
	| ふんわり時間だけが
	| 流れては消えてく

这里可以看到,argument(双冒号+空格后面的)自然是想要的class名称。因为没有选项(options),所以内容就从第三行(空一行)开始。至于pipe符号(|)这里的目的是强制换行。另外注意,所有的内容必须有一致缩进(具体多少无所谓,反正不会显示成缩进)——缩进结束就退出了这个rst-class了。而相对地,在普通正文中的缩进就是正常的缩进,而且你加几个空格都会如实反映。当然,你还得去你的CSS文件里定义.ja,这里就不赘述。

在写歌曲页面的时候,我遇到一个非常蛋疼的锚点问题。一般而言,锚点在加section/heading时是自动生成的,但是如果你不想开新的section呢?方法是在文档中加入这么一行:

.. _targetname:

至于引用(指向)目标时,Sphinx推荐的用法是用:ref:这个role:即形如:ref:`targetname`这样来引用。但是注意!一般而言target是配合section来用的,所以会自动成section的名字。但是现在我们是在正文中随便添加的,我们必须显式指定他的名字::ref:`Display Name <targetname>`才行。

但是这里有个问题——在Sphinx里,所有的target和ref都是全局的,跨文件的。所以,我每个文件里的target还不能一样,比如如果我每个文件里都有个.. _ja:,用:ref:`Display Name <targetname>`会不知道飞到哪个文件的_ja锚点里去。

研究了半天,发现只能用reStructuredText自带的引用方式——`targetname`_因为这个只适用于本文件,这样即使我每个文件都有个重名的锚点,也只会正确跳转到本文件内的。不过这个有个缺点,不支持Display name和targetname不一样(就是上面带尖括号的用法),所以我的targetname必须就是我想显示的文字。还好,似乎支持中文字符和符号,我用了“[中文]”当锚点名称也没事儿。最终效果

那么最后需要的功能就是给CD曲目列表里的对应歌曲添加链接了。因为是跨文档引用,所以推荐的方法是给每个文档的标题加上个target,然后用:ref:;但是我嫌麻烦,直接用另外一个role,:doc:做。方法基本一样,直接输入:doc:`filename`就行了——显示的文字自动从对应文档的标题提取。不过这里我也并没有直接这么做,因为Sphinx带了个非常好用的role,叫:any::用它可以智能地自动寻找最接近的reference,可以是:ref:,可以是:doc:。在用:any:之前,我们更可以把它指定为“default role”——在conf.py里添加:

default_role = 'any'

这样,当你使用单个撇括起来时(例如:`songs/kaerimichi`),会自动调用:any:这个role。这里,因为这是文档名,又会进而自动调用:doc:

到这里,基本在reStructuredText里遇到的困难都说完了。在Sphinx这边,我对默认的alabaster模板也没怎么改,但是有一点要注意:如果要用这个模板的完整功能,要修改sidebar为

html_sidebars = {
 '**': [
 'about.html',
 'navigation.html',
 'relations.html',
 'searchbox.html',
 'donate.html',
 ]
}

才行,因为模板自带了一些sidebar并没有包括在默认的设置中。模板带的选择在conf.py里修改html_theme_options,基本很好理解,我就改了logo和字体。

Sphinx有一个自带的“basic”模板,还有一些JS和CSS,基本所有的模板都有inherit。但是那个JS(doctools.js)有个问题(Chrome only,我已经汇报到Sphinx dev team),重现方法如下:

  1. 点一个带锚点的地址
  2. 滚动一些(即,你已经不再在锚点的原始位置)
  3. 点一个(非本页内的)链接,跳转到了其他页面
  4. 点“后退键”

正常来讲,点了后退之后,会后退到之前页面的之前位置。但是bug就是,后退之后会强制再读取一次锚点位置,然后跳转过去,而非你之前的阅读位置。你可以在Python 3的官方文档页面重现此bug,因为他用的就是Sphinx。相反Sphinx自己的文档页面则不会,因为他用的是旧版的doctools.js文件。Firefox下无此bug。

我研究了半天,发现是该JS文件中的以下函数:

/**
* workaround a firefox stupidity
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},

导致的。讽刺地是,该函数的目的是为了workaround Firefox的一个bug——Firefox对于HTML5新增的<section>锚点tag支持不好——才加的。本来函数有一个判断是只对Firefox有效,但是由于JQuery移除了$.browser,这里被改成了对所有浏览器有效,从而导致了上面所述的副作用。因为那个Firefox的bug对一般应用并没有什么影响(一般应用的锚点都是靠id="xxx"来搞的,并不会用到<section>),所以我们这里直接删掉就好。删除的方式则是复制一份这个JS文件修改,然后放到_static/文件夹内。这样,每次build,会自动覆盖。注意复制的时候,别复制成了原始模板文件夹里那个doctools.js_t了——那个是个JS“模板”,中间有些参数还没生成的。正确的方法是先build一遍,然后从html的目录里复制个原始版的JS。

还有一点要注意,用sphinx-build来build HTML文件的时候,有的时候并不会刷新改刷新的文件(尤其是sidebar之类的),所以隔一段时间最好把_build/目录全删了然后重新build。当然你也可以用-E选项,不过一般还是别强制了,因为要慢许多。

往Github Host的时候,需要加个.nojekyll文件来禁用GitHub Pages自带的翻译引擎,否则会有问题。但是这个文件只有加在根目录才有效,而我是把整个网站放到mahoromaticdb/子目录下的(因为GitHub一个用户好像就支持一个站),所以一开始迷惑了一阵为什么不好使。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s