使用 GitHub, Jekyll 打造自己的独立博客

GitHub是一个代码托管网站,现在很多开源项目都放在GitHub上。 利用GitHub,可以让全球各地的程序员们一起协作开发。GitHub 提供了一种功能,叫 GitHub Pages, 利用这个功能,我 们可以为项目建立网站,当然,这也意味着我们可以通过 GitHub Pages 建立自己的网站。

Jekyll是一个简单的,针对博客设计的静态网站生成器。使用 GitHub 和 Jekyll,我们可以打造自己的独立博客,你可以自由地定制网站的风格,并且这 一切都是免费的。

这是我在GitHub上自己建立的博客源代码 (两个分支),在下文的讲解中,你可以随时查看博客的源代码,以便有直观的认识。

网站截图:

blog_snapshot

入门指引

GitHub Pages 的 主页 提供了一个简单的入门指引,阅读并 操作一下,会有一个直观简单的认识。

阮一峰的文章《[搭建一个免费的,无限流量的Blog—-github Pages和Jekyll入门] (http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html)》是使用 GitHub 和 Jekyll 搭建独立博客非常好的入门文章,强烈建议先阅读并操作一遍

建立自己的博客

在学习完阮一峰同学的文章后,你就已经有能力搭建自己的独立博客了,但是这个博客 只有最基本的功能,并且也不好看。这时候,你面临几个选择:

  1. 完全自己定制博客
  2. 找一份框架,修改后使用
  3. 从GitHub上fork别人的博客代码,在其中添加自己的文章

如果选择 2, 那么 jekyll-bootstrap是一个选择。 如果选择 3, 那么自己Google一下 github.io 博客 能找到不少博客,去fork,然后修改一下就好。 如果选择 1, 那么可以好好看看后文的内容。

GitHub + Jekyll 工作机制

简单地说,你在 GitHub 上有一个账号,名为username(任意), 有一个项目,名为 username.github.io(固定格式,username与账号名一致), 项目分支名为 master(固定),这个分支有着类似下面的 目录结构:

.
├── index.html
├── _config.yml
├── assets
│   ├── blog-images
│   ├── css
│   ├── fonts
│   ├── images
│   └── javascripts
├── _includes
├── _layouts
├── _plugins
├── _posts
└── _site

这样,当你访问 http://username.github.io/时,GitHub 会使用 Jekyll 解析 用户 username名下的username.github.io项目中,分支为master 的源代码,为你构建一个静态网站,并将生成的 index.html 展示给你。

关于这个目录更多的内容,我们还不需要关心,如果你好奇心比较重,可以先看 后文源代码一节。

看完上面的解释,你可能会有一些疑问,因为按照上面的说法,一个用户只能有一个 网站,那我有很多项目,每个项目都需要一个项目网站,该怎么办呢?另外,在阮一峰 同学的文章中,特别提到,分支名应该为 gh-pages,这又是怎么回事呢?

原来,GitHub认为,一个GitHub账号对应一个用户或者一个组织,GitHub会 给这个用户分配一个域名:username.github.io,当用户访问这个域名时, GitHub会去解析username用户下,username.github.io项目的master分支, 这与我们之前的描述一致。
另外,GitHub还为每个项目提供了域名,例如,你有一个项目名为blog, GitHub为这个项目提供的域名为username.github.io/blog, 当你访问这个域名时,GitHub会去解析username用户下,blog项目的gh-pages 分支。

所以,要搭建自己的博客,你可以选择建立名为 username.github.io的项目, 在master分支下存放网站源代码,也可以选择建立名为 blog 的项目,在 gh-pages分支下存放网站源代码。

GitHub 的 Help 文档中的 [User, Organization and Project Pages] (https://help.github.com/articles/user-organization-and-project-pages)对此有 详细的描述。

Jekyll 提供了插件功能,在网站源代码目录下,有一个名为 _plugins的目录, 你可以将一些插件放进去,这样,Jekyll在解析网站源代码时,就会运行你的插件, 这样插件是 Ruby 写成的。可以为Jekyll添加功能,例如,Jekyll默认是不提供分类 页面的,你可以写一个插件,根据文章内容生成分类页面。如果没有插件,你只能每 次写文章,添加分类时,为每个分类手动写 HTML 页面。

在本地运行 Jekyll 时,这些插件会自动被调用,但是GitHub在解析网站源代码时, 出于安全考虑,会开启安全模式,禁用这些插件。我们既想用这些插件,又想用 GitHub,怎么办呢怎么办呢?

GitHub还为我们提供了更一种解析网站的方式,那就是直接上传最终的静态网页, 这样,我们可以在本地使用 Jeklly 把网站解析出来,然后再上传到 GitHub上, 这就使得我们既使用了插件,又使用了 GitHub。在上文的目录结构中,有一个 名为 _site 的目录,这个就是Jeklly在本地解析时最终生成的静态网站,我们 把其中的内容上传到 GitHub 的项目中就可以了。例如,我在GitHub上的网站, 既解析后的 _site 目录,大概是这样的:

.

├── index.html
├── 2013
├── 2014
├── assets
├── categories
├── page2
├── page3
├── page4
├── 工具
├── 思想
├── 技术
└── 源代码阅读

其中的 categories2013, 2014 目录就是分类插件和归档插件帮助我生成的, 我只要把这个目录下的内容上传到 GitHub 相应的项目分支中就可以了。这样,你 访问 username.github.io时,GitHub就不解析了,直接把index.html返回给你了。

工作流

关于 git 和 jekyll 的安装与基本使用,这里就不多说了。

如果你不使用插件,那么只需要维护一个分支就好:

- username/username.github.io 的 master 分支
- username/blog 的 gh-pages 分支

其中 username 是你的 GitHub 帐号。

你需要在本地维护一份网站源代码,添加新文章后,使用 jekyll 在本地测试一下, 没有问题后,commit 到 GitHub 上的相应分支中就可以了。

如果你需要使用插件,那么需要维护两个分支,一个是网站的源代码分支,另一个 是 Jeklly 解析源代码后生成的静态网站。

例如,我的源代码分支名为 master,静态网站分支名为 gh-pages。平时写博客时, 首先在 master 分支下,添加新文章,然后本地使用 jekyll build 将添加文章后的网站 解析一次,这时 _site 目录下就有新网站的静态代码了。然后把这个目录下的所有内容 复制到 gh-pages 分支下。这个过程,可以写一个 Makefile,每次添加文章后 make 一下, 就自动将文章发布到 GitHub 上。

Makefile 内容如下:


deploy:
	git checkout source
	jekyll build
	git add -A
	git commit -m "update source"
	cp -r _site/ /tmp/
	git checkout master
	rm -r ./*
	cp -r /tmp/_site/* ./
	git add -A
	git commit -m "deploy blog"
	git push origin master
	git checkout source
	echo "deploy succeed"
	git push origin source
	echo "push source"

下面的内容涉及源代码,如果需要进一步学习,或者有问题,可以在 Jeklly 官网上找到更详细的解释,或者在评论中留言。

源代码

再来看一下这个目录结构:

.
├── _config.yml
├── index.html
├── assets
│   ├── blog-images
│   ├── css
│   ├── fonts
│   ├── images
│   └── javascripts
├── _includes
├── _layouts
├── _plugins
├── _posts
└── _site

这是针对 Jekyll 的配置文件, 决定了 Jekyll 如何解析网站的源代码,下面是一个示例:

baseurl: /StrayBirds
markdown: redcarpet
safe: false
pygments: true
excerpt_separator: "\n\n\n"
paginate: 5

我的网站建立在 StrayBirds 项目中,所以 baseurl 设置成 StrayBirds, 我的文章采用 Markdown 格式写成,可以指定 Markdown 的解析器 redcarpet。 另外,安全模式需要关闭,以便 Jekyll 解析时会运行插件。 pygments 可以使得Jekyll解析文章中源代码时加入特殊标记,例如指定代码类型, 这可以被很多 javascript 代码高度库使用。 excerpt_separator 指定了一个摘要分割符号,这样 Jekyll 可以在解析文章时, 将文章的提要提取出来。 paginate 指定了一页有几篇文章,页数太多时,我们可以将文章列表分页,我们在 后文还会提到。

这个目录存放着一些网页模板文件,为网站所有网页提供一个基本模板,这样 每个网页只需要关心自己的内容就好,其它的都由模板决定。例如,这个目录下的 default.html 文件:


<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <title>{{ page.title }}</title>
  </head>
  <body>
    <header>
    </header>

    <aside>
    </aside>

    <article>
{{ content }}
    </article>

    <footer>
    </footer>
  </body>
</html>


可以看出,这个文件就是所有页面共有的东西,每个页面的具体内容会被填充在 `<p>#WSGI 简介</p>

背景

Python Web 开发中,服务端程序可以分为两个部分,一是服务器程序,二是应用程序。前者负责把客户端请求接收,整理,后者负责具体的逻辑处理。为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,例如 Django, Flask, Tornado。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

Python Web开发中,这个标准就是 The Web Server Gateway Interface, 即 WSGI. 这个标准在PEP 333中描述,后来,为了支持 Python 3.x, 并且修正一些问题,新的版本在PEP 3333中描述。

WSGI 是什么

WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用。

WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。

另一方面,WSGI需要使得middleware(是中间件么?)易于实现。middleware处于服务器程序与应用程序之间,对服务器程序来说,它相当于应用程序,对应用程序来说,它相当于服务器程序。这样,对用户请求的处理,可以变成多个 middleware 叠加在一起,每个middleware实现不同的功能。请求从服务器来的时候,依次通过middleware,响应从应用程序返回的时候,反向通过层层middleware。我们可以方便地添加,替换middleware,以便对用户请求作出不同的处理。

WSGI 内容概要

WSGI主要是对应用程序与服务器端的一些规定,所以,它的主要内容就分为两个部分。

应用程序

WSGI规定:

1. 应用程序需要是一个可调用的对象

在Python中:

同时,WSGI规定:

2. 可调用对象接收两个参数

这样,如果这个对象是函数的话,它看起来要是这个样子:

# callable function
def application(environ, start_response):
    pass

如果这个对象是一个类的话,它看起来是这个样子:

# callable class
class Application:
    def __init__(self, environ, start_response):
        pass

如果这个对象是一个类的实例,那么,这个类看起来是这个样子:

# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        pass

最后,WSGI还规定:

3.可调用对象要返回一个值,这个值是可迭代的。

这样的话,前面的三个例子就变成:

HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
    return [HELLO_WORLD]


# callable class
class Application:
    def __init__(self, environ, start_response):
        pass

    def __iter__(self):
        yield HELLO_WORLD


# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        return [HELLO_WORLD]

你可能会说,不是啊,我们平时写的web程序不是这样啊。 比如如果使用web.py框架的话,一个典型的应用可能是这样的:

class hello:
    def GET(self):
        return 'Hello, world!'

这是由于框架已经把WSGI中规定的一些东西封装起来了,我们平时用框架时,看不到这些东西,只需要直接实现我们的逻辑,再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在,把常用的东西封装起来,让使用者只需要关注最重要的东西。

当然,WSGI关于应用程序的规定不只这些,但是现在,我们只需要知道这些就足够了。下面,再介绍服务器程序。

服务器程序

服务器程序会在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果返回给客户端。

WSGI规定:

4.服务器程序需要调用应用程序

服务器程序看起来大概是这个样子的:

def run(application):
    environ = {}

    def start_response(status, response_headers, exc_info=None):
        pass

    result = application(environ, start_response)

    def write(data):
        pass

    for data in result:
        write(data)

这里可以看出服务器程序是如何与应用程序配合完成用户请求的。

WSGI规定了应用程序需要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 程序中,针对这几个规定,做了以下几件事:

你可以从中发现,应用程序需要的两个参数,一个是一个dict对象,一个是函数。它们到底有什么用呢?这都不是我们现在应该关心的,现在只需要知道,服务器程序大概做了什么事情就好了,后面,我们会深入讨论这些细节。

middleware

另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。

middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。

其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。

下面,我们看看middleware大概是什么样子的。

# URL Routing middleware
def urlrouting(url_app_mapping):
    def midware_app(environ, start_response):
        url = environ['PATH_INFO']
        app = url_app_mapping[url]

        result = app(environ, start_response)

        return result

    return midware_app

函数 midware_app就是一个简单的middleware:对服务器而言,它是一个应用程序,是一个可调用对象, 有两个参数,返回一个可调用对象。对应用程序而言,它是一个服务器,为应用程序提供了参数,并且调用了应用程序。

另外,这里的urlrouting函数,相当于一个函数生成器,你给它不同的 url-app 映射关系,它会生成相应的具有 url routing功能的 middleware。

如果你仅仅想简单了解一下WSGI是什么,相信到这里,你差不多明白了,下面会介绍WSGI的细节,这些细节来自 PEP3333, 如果没有兴趣,到这里 可以停止了。

WSGI详解

注意:以 点 开始的解释是WSGI规定 必须满足 的。

应用程序

application(env, start_response)

而不是这样:

application(start_response=start_response, environ=env)

所以,参数名其实是可以随便起的,只不过为了表义清楚,我们起了environstart_response

response_headers参数是一个列表,列表项的形式为(header_name, header_value)。

exc_info参数在错误处理的时候使用。

status和response_headers的具体内容可以参考 HTTP 协议 Response部分

服务器程序

根据上述内容,我们的服务器程序看起来会是这个样子:

def run(application):
    environ = {}

    # set environ
    def write(data):
        pass

    def start_response(status, response_headers, exc_info=None):
        return write

    try:
        result = application(environ, start_response)
    finally:
        if hasattr(result, 'close'):
            result.close()

    if hasattr(result, '__len__'):
        # result must be accumulated
        pass


    for data in result:
        write(data)

应用程序看起来是这个样子:

HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)

    return [HELLO_WORLD]

下面我们再详细介绍之前提到的一些数据结构

environ 变量

environ 变量需要包含 CGI 环境变量,它们在The Common Gateway Interface Specification 中定义,下面列出的变量必须包含在 enciron变量中:

WSGI 有一个参考实现,叫 wsgiref,里面有一个示例,我们这里引用这个示例的结果,展现一下这些变量,以便有一个直观的体会,这个示例访问的 URL 为 http://localhost:8000/xyz?abc

上面提到的变量值为:

REQUEST_METHOD = 'GET'
SCRIPT_NAME = ''
PATH_INFO = '/xyz'
QUERY_STRING = 'abc'
CONTENT_TYPE = 'text/plain'
CONTENT_LENGTH = ''
SERVER_NAME = 'minix-ubuntu-desktop'
SERVER_PORT = '8000'
SERVER_PROTOCOL = 'HTTP/1.1'

HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_HOST = 'localhost:8000'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'

另外,服务器还应该(非必须)提供尽可能多的CGI变量,如果支持SSL的话,还应该提供Apache SSL 环境变量

服务器程序应该在文档中对它提供的变量进行说明,应用程序应该检查它需要的变量是否存在。

除了 CGI 定义的变量外,服务器程序还可以包含和操作系统相关的环境变量,但这并非必须。

但是,下面列出的这些 WSGI 相关的变量必须要包含:

这些值在 wsgiref示例中的值为:

wsgi.errors = <open file '<stderr>', mode 'w' at 0xb735f0d0>
wsgi.file_wrapper = <class wsgiref.util.FileWrapper at 0xb70525fc>
wsgi.input = <socket._fileobject object at 0xb7050e6c>
wsgi.multiprocess = False
wsgi.multithread = True
wsgi.run_once = False
wsgi.url_scheme = 'http'
wsgi.version = (1, 0)

另外,environ中还可以包含服务器自己定义的一些变量,这些变量应该只包含

例如,mod_python定义的变量名应该为mod_python.var_name的形式。

输入流及错误流(Input and Error Streams)

服务器程序提供的输入流及错误流必须包含以下方法:

应用程序使用输入流对象及错误流对象时,只能使用这些方法,禁止使用其它方法,特别是, 禁止应用程序关闭这些流。

start_response()

start_response是HTTP响应的开始,它的形式为:

start_response(status, response_headers, exc_info=None)

返回一个可调用对象,这个可调用对象形式为:

write(body_data)

status 表示 HTTP 状态码,例如 “200 OK”, “404 Not Found”,它们在 RFC 2616中定义,status禁止包含控制字符。

response_headers 是一个列表,列表项是一个二元组: (header_name, heaer_value) , 每个 header_name 都必须是 RFC 2616 4.2 节中定义的HTTP 头部名。header_value 禁止包含控制字符。

另外,服务器程序必须保证正确的headers 被返回给客户端,如果应用程序没有返回headers,服务器必须添加它。

应用程序和middleware禁止使用 HTTP/1.1 中的 “hop-by-hop”特性,以及其它可能影响客户端与服务器永久连接的特性。

start_response 被调用时,服务器应该检查 headers 中的错误,另外,禁止 start_response直接将 response_headers传递给客户端,它必须把它们存储起来,一直到应用程序第一次迭代返回一个非空数据后,才能将response_headers传递给客户端。这其实是在说,HTTP响应body部分必须有数据,不能只返回一个header。

start_response的第三个参数是一个可选参数,exc_info,它必须和Python的 sys.exc_info()返回的数据有相同类型。当处理请求的过程遇到错误时,这个参数会被设置,同时调用 start_response。如果提供了exc_info,但是HTTP headers 还没有输出,那么 start_response需要将当前存储的 HTTP response headers替换成一个新值。但是,如果提供了exc_info,同时 HTTP headers已经输出了,那么 start_response 必须 raise 一个 error。禁止应用程序处理 start_response raise出的 exceptions,应该交给服务器程序处理。

当且仅当提供 exc_info参数时,start_response才可以被调用多于一次。换句话说,要是没提供这个参数,start_response在当前应用程序中调用后,禁止再调用。

为了避免循环引用,start_response实现时需要保证 exc_info在函数调用后不再包含引用。 也就是说start_response用完 exc_info后,需要保证执行一句

exc_info = None

这可以通过 try/finally实现。

处理 Content-Length Header

如果应用程序支持 Content-Length,那么服务器程序传递的数据大小不应该超过 Content-Length,当发送了足够的数据后,应该停止迭代,或者 raise 一个 error。当然,如果应用程序返回的数据大小没有它指定的Content-Length那么多,那么服务器程序应该关闭连接,使用Log记录,或者报告错误。

如果应用程序不支持Content-Length,那么服务器程序应该选择一种方法处理这种情况。最简单的方法就是当响应完成后,关闭与客户端的连接。

缓冲与流(Buffering and Streaming)

一般情况下,应用程序会把需要返回的数据放在缓冲区里,然后一次性发送出去。之前说的应用程序会返回一个可迭代对象,多数情况下,这个可迭代对象,都只有一个元素,这个元素包含了HTML内容。但是在有些情况下,数据太大了,无法一次性在内存中存储这些数据,所以就需要做成一个可迭代对象,每次迭代只发送一块数据。

禁止服务器程序延迟任何一块数据的传送,要么把一块数据完全传递给客户端,要么保证在产生下一块数据时,继续传递这一块数据。

middleware 处理数据

如果 middleware调用的应用程序产生了数据,那么middleware至少要产生一个数据,即使它想等数据积累到一定程度再返回,它也需要产生一个空的bytestring。 注意,这也意味着只要middleware调用的应用程序产生了一个可迭代对象,middleware也必须返回一个可迭代对象。 同时,禁止middleware使用可调用对象write传递数据,write是middleware调用的应用程序使用的。

write 可调用对象

一些已经存在的应用程序框架使用了write函数或方法传递数据,并且没有使用缓冲区。不幸的是,根据WSGI中的要求,应用程序需要返回可迭代对象,这样就无法实现这些API,为了允许这些API 继续使用,WSGI要求 start_response 返回一个 write 可调用对象,这样应用程序就能使用这个 write 了。

但是,如果能避免使用这个 write,最好避免使用,这是为兼容以前的应用程序而设计的。这个write的参数是HTTP response body的一部分,这意味着在write()返回前,必须保证传给它的数据已经完全被传送了,或者已经放在缓冲区了。

应用程序必须返回一个可迭代对象,即使它使用write产生HTTP response body。

这里可以发现,有两中传递数据的方式,一种是直接使用write传递,一种是应用程序返回可迭代对象后,再将这个可迭代对象传递,如果同时使用这两种方式,前者的数据必须在后者之前传递。

Unicode

HTTP 不支持 Unicode, 所有编码/解码都必须由应用程序完成,所有传递给或者来自server的字符串都必须是 str 或者 bytes类型,而不是unicode

注意传递给start_response的数据,其编码都必须遵循 RFC 2616, 即使用 ISO-8859-1 或者 RFC 2047 MIME 编码。

WSGI 中据说的 bytestrings , 在Python3中指 bytes,在以前的Python版本中,指 str

错误处理(Error Handling)

应用程序应该捕获它们自己的错误,internal erros, 并且将相关错误信息返回给浏览器。 WSGI 提供了一种错误处理的方式,这就是之前提到的 exc_info参数。下面是 PEP 3333中提供的一段示例:

try:
    # regular application code here
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
    #     in a separate handler before this bare 'except:'...
    status = "500 Oops"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers, sys.exc_info())
    return ["error body goes here"]

当出现异常时,start_response的exc_info参数被设置成 sys.exc_info(),这个函数会返回当前的异常。

HTTP 1.1 Expect/Continue

如果服务器程序要实现 HTTP 1.1,那么它必须提供对 HTTP 1.1 expect/continue机制的支持。

其它内容

在 PEP 3333 中,还包含了其它内容,例如:

这里就不作过多介绍了。

扩展阅读

这篇文章主要是我阅读 PEP 3333 后的理解和记录,有些地方可能没有理解正确或者没有写全,下面提供一些资源供扩展阅读。

另外,还有一些文章介绍了一些基本概念和一些有用的实例,非常不错。

中,注意这个 content 两边的标记,这是一种叫 [liquid](https://github.com/Shopify/liquid) 的标记语言。 另外,还有那个 使用 GitHub, Jekyll, Markdown 打造自己的博客 ,其中 page 表示引用 default.html的 那个页面,这个页面的 title 值会在 page 相应页面中被设置,例如 下面的 index.html 文件,开头部分就设置了 title` 值。

这是网站的首页,访问 http://username.github.io 时,会指向 http://username.github.io/index.html,我们看一下基本内容:

---
layout: default
title: 首页
---


<ul class="post-list">
    {% for post in site.posts %}
        <a href="{{site.baseurl}}{{post.url}}"> {{ post.title }}  </a> <br>
        {{ post.date | date: "%F" }} <br>
        {{ post.category }} <br>
        {{ post.excerpt }} 
    {% endfor %}

</ul>

注意,文件开头的描述,我们称之为 front-matter, 是对当前文件的一种描述,这里 设置的变量可以在解析时被引用,例如这里的 layout就会告诉 Jekyll, 生成 index.html 文件时,去 _layouts 目录下找 default.html 文件,然后把当前文件解析后,添加到 default.htmlcontent 部分,组成最终的 index.html 文件。还有title 设置好的 值,会在 default.html 中通过 page.title 被引用。

文件的主体部分遍历了站点的所有文章,并将他们显示出来,这些语法都是 liquid 语法, 其中的变量,例如 site, 由 Jekyll 设置我们只需要引用就可以了。而 post 中的变量, 如 post.title, post.category 是由 post 文件中的 front-matter 决定,后面马上就会看到。

这个目录存放我们的所有博客文章,他们的名字有统一的格式:

YEAR-MONTH-DAY-title.MARKUP

例如,2014-02-15-github-jeklly.md,这个文件名会被解析,前面的 index.html 中, post.date 的值就由这里文件名中的日期而来。下面,我们看看一篇文章的内容示例:

---
layout: default
title: 使用 Markdown
category: 工具
comments: true
---

# 为什么使用 Markdown

* 看上去不错  
* 既然看上去不错,为什么不试试呢  


# 如何使用 Markdown

可以看出,文章的 front-matter 部分设置了多项值,以后可以通过类似 post.title, post.category 的方式引用这些些,另外,layout部分的值和之前解释的一样, 文件的内容会被填充到 _layouts/default.html 文件的 content 变量中。

另外,文章中 为什么不试试呢之后的有三个不可见的 \n,它决定了这三个 \n 之前的内容会被放在 post.excerpt 变量中,供其它文件使用。

这个文件中,存放着一些模块文件,例如 categories.ext,其它文件可以通过


{% include categories.ext %}

来引用这个文件的内容,方便代码模块化和重用。我的博客 主页上的 分类,归档,这些模块的代码都是通过这种方式引用的。

这个文件中存放一些Ruby插件, 例如 gen_categories.rb,这些文件会在 Jekyll 解析网站源代码时被执行。下一节讲述的就是插件。

Jekyll 解析整个网站源代码后,会将最终的静态网站源代码放在这里

插件

插件使用 Ruby 写成,放在 _plugins 目录下,有些 Jekyll 没有的功能,又不能 手动添加,因为页面的内容会随着文章日期类别的不同而不同,例如分类功能和归档功能, 这时,就需要使用插件自动生成一些页面和目录。

使用方法是,把 _plugins/category_archive_plugin.rb 放在 _plugins 目录下, 把 _layouts/category_archive.html 放在 _layouts 目录下, 这样,这个插件会在Jekyll解析网站时,生成相应categories目录,目录下是各个分类, 每个分类下都有一个 index.html 文件,这个文件是根据模板文件 category_archive.html 生成的,例如:

_site/categories/
├── 工具
│   └── index.html
├── 思想
│   └── index.html
├── 技术
│   └── index.html
└── 源代码阅读
    └── index.html

然后,你就可以通过 http://username.github.io/blog/categories/工具/ 访问 工具类下的 index.html 文件。

使用方法同上。注意,这个插件在 jekyll-1.4.2 中可能会出错,在 jekyll-1.2.0 中没有错误。

组件

当文章很多时,就需要使用分页功能,在 Jekyll 官网上提供了一种 实现,把相应代码放在 主页上,然后在 _config.yml 中设置 paginate 值就行了。

评论功能需要使用外挂,我使用的是 DISQUS, 注册 之后,将评论区的一段代码放在你需要使用评论功能的页面上, 然后,通过在页面的 front-matter 部分使用

comments: true

启用评论。

评论区截图:

blog_comments_snapshot

基本的内容就介绍到这里,任何问题,欢迎留言。