devdocs添加新文档
本篇是“dash/zeal添加新文档”的姐妹篇,讲讲如何在另一个类似的文档应用——devdocs——中添加文档。
如果你之前没有用过类似的文档应用,建议现在就用起来。它们可以显著地提高文档查找的效率,而查文档又是日常编程中常做的事情之一。
devdocs 介绍
devdocs是一个开源的文档浏览应用。跟dash一样,它把HTML源文档转换成带索引的docset规格,以供用户查看。不过devdocs是一个用rails写的web应用而非本地应用,文档作为静态资源由服务器提供,交互则是通过浏览器页面完成。你可以在devdocs.io使用该应用,也可以在自己的电脑上部署它。注意devdocs提供了offline的选项,可以把数据写入到浏览器的indexeddb中,避免了每次从服务器获取数据带来的延迟。
在制作docset方面,devdocs跟dash最为显著的不同,在于devdocs把制作docset的功能集成到了应用当中。跟dash不同,devdocs把docset规格留作黑箱,取代它的是开发出一组制作docset的接口,通过这些接口制作的docset才能被识别。这么做的好处在于,官方会提供了制作docset的通用的辅助函数,减少了自己的开发量。坏处呢,就是限制了制作docset的发挥空间。另外,由于docset的静态资源代码是放在应用代码里面的,导致分发docset时也要将对应的静态资源代码一并分发。考虑到devdocs主要是一个web应用而非本地应用,这么做也不难理解。
还是跟上一篇讲dash的一样,本篇依旧采用官方文档解读加示例的形式。由于devdocs假定docset都位于同一个入口地址下(比如xxx/docs/),而上一篇的OpenResty文档不符合这一假设,所以换个示例。这次我们选择用devdocs里面的PHP作为示例。注意devdocs是一个Rails应用,所以要用它提供的接口自然只能用Ruby。不过据我观察,要制作docset并不需要什么Ruby编程知识。即使不懂Ruby,你也可以拿起现有的代码改改看,然后搜一下相关的语法知识,就能完事。
devdocs添加新文档(PHP示例)
官方文档见:https://github.com/Thibaut/devdocs/wiki/Adding-documentations-to-DevDocs
创建一个文档需要以下几步(假设需要生成的文档名为docset_name,下面的路径都是相对于devdocs项目代码根目录的相对路径,再强调一次,给devdocs添加文档需要在项目中写拓展代码):
- 在
./lib/docs/scrapers/下创建一个scraper类,起名为docsetName(注意这里用的是驼峰命名法)。让它继承自Docs::UrlScraper(用于下载在线文档)或Docs::FileScraper(用于处理本地文档,仅当文档页面过多时推荐)。 - 填写该类的以下属性。
# https://github.com/Thibaut/devdocs/blob/master/lib/docs/scrapers/php.rb
module Docs
class Php < FileScraper
include FixInternalUrlsBehavior
self.name = 'PHP' # 文档名
self.type = 'php' # 样式/语法高亮js类的的名字
self.release = 'up to 7.0.3' # 版本号
# 源文档入口地址。
self.base_url = 'https://secure.php.net/manual/en/'
self.root_path = 'index.html' # 根目录页
# 各子部分文档入口。下面的语法糖相当于`initial_paths = ['funcref.html', ...]`
self.initial_paths = %w(
funcref.html
langref.html
refs.database.html
set.mysqlinfo.html
language.control-structures.html
reference.pcre.pattern.syntax.html
reserved.exceptions.html
reserved.interfaces.html
reserved.variables.html)
self.links = {
home: 'https://secure.php.net/',
code: 'https://github.com/php/php-src'
}
# 由于该类继承自filescraper,所以从该路径下查找源文档。
self.dir = '/Users/Thibaut/DevDocs/Docs/PHP'
# 每个scraper有两个filter栈,第一个是html,另一个是text
# html filter把文档当做Nokogiri节点处理。
html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'
# text filter把文档当做字符串处理。
text_filters.push 'php/fix_urls'
# 关于Filter的更多内容,下面再一一道来。
# options用于控制不同类型的filter的行为。具体见
# https://github.com/Thibaut/devdocs/wiki/Scraper-Reference#filter-options
options[:title] = false
# 以下略
end
end
-
在
./lib/docs/filters/下创建一个./lib/docs/filters/<docset_name>/文件夹,将会在里面放置你的filter类。 -
devdocs在下载页面的时候,除了默认的filter(如AttributionFilter)外,还会调用你的filter类来处理数据。所以你需要准备下面两个filter。
- CleanHtmlFilter 该filter用于清理掉跟文档内容无关的页面元素。 相应的代码位于 https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/php/clean_html.rb 思路是使用nokogiri来删除/插入/修改文档的页面元素,去掉无用的,部分位置进行微调。
- EntriesFilter 该filter用于提取文档内容,得到条目,要求实现
get_name/get_type/include_default_entry?/additional_entries四个方法。每个条目有三个属性:name,type,path。name会用于索引,要求在整个文档中不能重复。type则是该条目所在的归类,如Function等等。path则是该条目所在的源文档路径。devdocs约定每个页面代表一个条目,get_name/get_type分别返回它的名字和类型,而path则默认为该页面的路径。如果一个页面有多个文档怎么办?那么需要剩下两个方法配置下。include_default_entry?默认返回true,表示当前页面就是单独一个条目(所谓的默认条目);如果当前页面只是若干条目的集合,覆盖该方法返回false。这时需要覆盖additional_entries方法,返回一个[[name,fragment_id, type], ...]数组,表示该页面中“额外的”(非默认)条目,其中fragment_id取该条目在页面中的相对锚点。举个例子,如果文档中有一个类似这样的条目:
<a href="#method-foo"></a> <h3>foo</h3>那么返回的数组应包括
['foo', 'method-foo', 'method']。相应的代码位于 https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/php/entries.rb 思路简单粗暴,就是用nokogiri的接口获取该页面的列出的条目的名字和目录,然后通过一个硬编码的哈希表查出其所属的类型。由于PHP文档每个条目单独成页,所以只需覆盖前两个方法。
另外,我们也可以看到示例代码里面自定义了其他几个filter,用于重写文档中的url。 关于Filter的更多内容,见文档。
-
还需要给文档准备一份样式。在
assets/stylesheets/pages/下创建_[type].scss,并在application.css.scss中import它。这里type指scraper类中的type属性的值。这个文件中的样式需要位于名为_[type]的类。
# https://github.com/Thibaut/devdocs/blob/master/assets/stylesheets/pages/_php.scss
# 样式什么的不需要跟源文档的一致,只要看起来舒服就好
._php {
...
}
- 运行
thor docs:page <docset_name> <path>下载文档到./public/docs/<docset_name>/。确保能够成功下载文档。 - 运行
thor docs:generate <docset_name> --force --verbose --debug生成文档。 - 现在你可以刷新下应用,看看生成的文档。第一次尝试总是会有些问题的。调整下样式、scraper代码、filter代码,重复上面两步,直到输出满意的结果为止。
- 如果你的docset需要语法高亮支持,在
./assets/javascripts/views/pages/创建一个coffeescript脚本。直接拿现有的改就好:
# https://github.com/Thibaut/devdocs/blob/master/assets/javascripts/views/pages/php.coffee
#= require views/pages/base
# 注意类的名字为app.views.[type]Page
class app.views.PhpPage extends app.views.BasePage
prepare: ->
# 用php语法,高亮.phpcode里面的内容
@highlightCode @findAllByClass('phpcode'), 'php'
return
- 最后,不忘给你的docset准备一个16x16的图标(16.png),和一个32x32的图标([email protected]),放到
./public/icons/docs/<docset_name>/下。 - 如果你乐意,可以把生成的docset提交给devdocs官方。详细的步骤和要求见:https://github.com/Thibaut/devdocs/blob/master/CONTRIBUTING.md