就像上一篇文章许诺的,我费了三天时间,把魔力女管家歌词库给搭出来了!
先上地址: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包含在你生成的网站呢?这里有好几个办法:
- 在
conf.py
中加上def setup(app): app.add_stylesheet('custom.css') # may also be an URL
(你的
custom.css
应该在_static/
目录下) - 在
conf.py
中加上(注意这里又变成从根目录起了…下同)html_context = { 'css_files': ['_static/custom.css'], }
- 先把你模板里的
layout.html
拷贝到目录下的_templates
里(当然,保证你没删掉conf.py
里的templates_path = ['_templates']
),然后找地方加一行{% set css_files = ['_static/custom.css'] %}
(在SO看到的是
{% set css_files = css_files + ['_static/custom.css'] %}
但似乎使用上并没有区别。)
- 还是上面的说的
layout.html
,直接强行加{%- block extrahead %} <link rel="stylesheet" href="{{ pathto('_static/custom.css', 1) }}" type="text/css" /> {% endblock %}
- 最暴力的方法,找到你模板的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),重现方法如下:
- 点一个带锚点的地址
- 滚动一些(即,你已经不再在锚点的原始位置)
- 点一个(非本页内的)链接,跳转到了其他页面
- 点“后退键”
正常来讲,点了后退之后,会后退到之前页面的之前位置。但是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一个用户好像就支持一个站),所以一开始迷惑了一阵为什么不好使。