從 Pixnet 轉移到 Pelican

By Shaform, Sat 17 May 2014, in category Notes

Pelican, Pixnet, Python

「翼之都」這個技術部落格其實早在 2006 年就成立了,一直放在 Blogger 上,但很少新增文章。最近好不容易開始打算多寫一些技術文,卻突然發現在 Blogger 上實在是有點麻煩:它對於一些程式碼的的支援有點不直覺,而文章的寫作方式也有許多不符我的需求。最後索性就把這個網誌改用 Pelican 建立了。而因為文章只有一點點,所以是用手動搬的。

搬完以後覺得這種靜態網頁在管理和備份文章上實在是很方便。想到「浮光」這個文章集散處,其實完全是由很少 HTML 的靜態文章所組成,實在是很適合放到 Pelican 上,再者 Pixnet 的後台界面自己一直不是很喜歡,所以就乾脆一起搬了。

以下記載完成這件事的流程,使用的環境是 Ubuntu 14.04 64-bit。

安裝 Pelican

詳細安裝方法可見 Getting started 文件,我自己選的方法是用 virtualenvwrapper,並從 source code 直接安裝最新版的 Pelican(其實這是因為我想要讓文章的發布時間可以支援用不同時區的表示格式,舊版的 Pelican 似乎尚未支援)。

1. 安裝 virtualenvwrapper

首先輸入以下指令:

sudo apt-get install python-pip virtualenvwrapper

然後新增 pelican 環境

mkvirtualenv pelican

這樣以後如果要進入 pelican 環境的話只要下:

workon pelican

而安裝的套件也不會跟系統混在一起。

2. 安裝 pelican

在 pelican 環境下隨意找個位置,下載 pelican 原始碼,並安裝相關套件:

git clone https://github.com/getpelican/pelican.git
cd pelican
python setup.py install
pip install Markdown

最後選個資料夾放置網誌檔案,並下以下指令,就可以建立簡單的設定擋了:

pelican-quickstart

匯出

Pixnet 的匯出格式是 Movable Type,恰巧 pelican-import 尚未支援。同時,浮光上的文章常有大量空行,我希望對轉換的格式有更多控制,所以乾脆自己寫了一個 Python script 來進行轉換:

# -*- coding: utf-8 -*-
# python convert.py < blog-export.txt
import fileinput
import os
import os.path
import re
import sys

import pelican.utils

def p_transform(lines):
    new_lines = []
    empty = 0
    for l in lines:
        if l == '':
            empty += 1
        else:
            if empty > 0:
                if empty == 1:
                    new_lines.append('')
                else:
                    num = ((empty-1) / 3) + 1
                    new_lines.extend([''] + [u'&nbsp;', ''] * num)
                empty = 0
            new_lines.append(l)
    return '\n'.join(new_lines)


rData = re.compile(r'(\d{2})/(\d{2})/(\d{4}) (\d{2}):(\d{2}):(\d{2}) (PM|AM)')
while True:
    article = {}
    for l in sys.stdin:
        l = l.decode('utf-8')
        if l.startswith(u'TITLE: '):
            article['title'] = l[7:-1]
        elif l.startswith(u'DATE: '):
            m = rData.search(l)
            if m is None:
                raise Exception(u'wrong with {}'.format(l))
            else:
                gs = m.groups()
                t = int(gs[3])
                if gs[6] == u'PM' and t != 12:
                    t += 12
                article['date'] = u'{}-{}-{} {}:{}:{}+0800'.format(
                        gs[2], gs[0], gs[1],
                        t, gs[4], gs[5])
        elif l.startswith(u'PRIMARY CATEGORY: '):
            cat = l[18:l.find(u'STATUS')]
            if cat in [u'雪山', u'海岸', u'城市', u'??']:
                article['category'] = u'天予'
                article['tags'] = {u'天予:' + cat, u'系列', u'奇幻'}
            elif cat in [u'我的世界', u'記憶']:
                article['category'] = cat
                article['tags'] = {u'系列'}
            else:
                article['category'] = cat
        elif l == u'BODY:\n':
            lines = []
            for l in sys.stdin:
                l = l.decode('utf-8')
                if l != '-----\n':
                    if l.find(u'nbsp') != -1:
                        lines.extend([u'',u''])
                    else:
                        space = False
                        if l.find(u'</p>') != -1:
                            space = True
                        l = l.strip().replace(u'<p>', u'').replace(u'</p>', '')
                        l = l.replace(u'<!-- more -->', '')
                        lines.append(l)
                        if space:
                            lines.append(u'')
                else:
                    break
            article['body'] = p_transform(lines)

            for l in sys.stdin:
                l = l.decode('utf-8')
                if l == u'--------\n':
                    break
        if 'body' in article:
            break

    if len(article) == 0:
        break
    else:
        path = 'content/' + article['category']
        if not os.path.exists(path):
            os.makedirs(path)
        with open(path + '/' + pelican.utils.slugify(article['title']) + '.md', 'w') as f:
            f.write(u'Title: {}\n'.format(article['title']).encode('utf-8'))
            f.write(u'Date: {}\n'.format(article['date']).encode('utf-8'))
            f.write(u'Category: {}\n'.format(article['category']).encode('utf-8'))
            if 'tags' in article:
                f.write(u'Tags: {}\n'.format(', '.join(article['tags'])).encode('utf-8'))
            f.write(u'Authors: Shaform\n')
            f.write(u'\n'.encode('utf-8'))
            f.write(article['body'].encode('utf-8') + '\n')

這個 script 會將結果輸出到 content/{category}/{title}.md 檔案,自動轉換適當的空行、分類、日期格式等等。分類的處理是依據浮光的架構,所以只有浮光能用。

發布到 Google App Engine

在這過程中,我短暫的嘗試用 Google App Engine 作為發布平台,不過因為太麻煩,所以最後還是採用 GitHub Pages。

實際的作法是先申請個帳號,然後安裝 SDK:

curl sdk.cloud.google.com | bash
# restart the Terminal, then
gcloud auth login

緊接著在網誌目錄中新增 app.yaml 檔案,設定讓伺服器顯示靜態網頁:

application: APP-NAME
version: 1
runtime: python27
api_version: 1
threadsafe: yes
module: default

handlers:
- url: /
    static_files: output/index.html
      upload: output/index.html

      - url: /
          static_dir: output

然後修改 Makefile 新增如下指令,自動將檔案複製到指定位置:

gcloud: publish
    mkdir -p gcloud/output
    cp -r $(OUTPUTDIR)/* gcloud/output
    mv gcloud $(OUTPUTDIR)
    cp app.yaml $(OUTPUTDIR)/gcloud
    appcfg.py update $(OUTPUTDIR)/gcloud

這樣的話只要在網誌的目錄下執行以下指令,就可以把網頁發布到 Google App Engine 上了:

make gcloud

發布到 GitHub Pages

雖然 Pelican 原本就有支援 GitHub Pages 的發布,不過我比較希望在 local 端的 master branch 紀錄網誌的原始 Markdown 檔案,在 gh-pages branch 上紀錄產生的網頁,因此我在 Makefile 做了以下修改:

GITHUB_PAGES_BRANCH=gh-pages
github: publish
        echo "DOMAIN-NAME" > $(OUTPUTDIR)/CNAME
        ghp-import -r gh-pages -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR) -m "$(MSG)"
        git push gh-pages $(GITHUB_PAGES_BRANCH):master

其中 DOMAIN-NAME 那行是自訂 domain 用的,若沒有可以刪除。而 -m $(MSG) 則是為了讓我可以用 MSG="new update" make github 來自訂 commit 訊息。最後一行的 master,如果是用 username.github.io 的話就保留 master,如果是一般 projects 的話就改成 gh-pages

緊接著安裝 ghp-import

pip install ghp-import

然後按照 GitHub Pages 的說明,建立一個 repository,並把整個網誌的目錄當成這個 repository。(我其實是先 git clone 然後把 .git 資料夾直接搬到網誌目錄中。)

結果

最後再調整一些佈景和版面的設定,就成了最後的結果:浮光。初步感覺還滿令我滿意的。以後再慢慢調整浮光以及翼之都的版面吧。