本文完整代碼可見個人_pygment-html.pycss
最近在研究利用org-mode寫博客,其餘一切都深得我心、甚合吾意,就是代碼染色發佈html這一點要給差評。org-mode利用htmlize
插件給 src block
中的代碼着色,讓文章中的代碼塊輸出html後的顏色於你在emacs上看到的相同。可問題在於,我emacs上背景是暗黑系的,而我博客上是淺色系,所以代碼高亮風格不相調和,況且高亮主題單一不可定製,輸出代碼行號醜陋不堪,固然這均可以用elisp解決,但是想必是繁雜晦色無比(要調色啊…)html
因而,我又再次投入萬能的Python的懷抱,直接利用它的pygments庫高亮代碼。python
先介紹一下pygments。 pygments
可以將一段原生代碼高亮並輸出爲 html、latex、png
等多種格式,並且還提供各類樣式控制。c++
因爲pygments庫是原生的python庫,所以經過寫elisp插件控制org-mode發佈並不現實,再三思慮之下,只能從org-mode發佈的html文件開始入手,把代碼塊的html改了。git
先來看看org-mode代碼塊輸出html的特徵:github
src block
:web
#+begin_src python import pygments print "aa" #+end_src
輸出 html
:正則表達式
<div class="org-src-container"> <pre class="src src-python"><span style="color: #66D9EF;">import</span> pygments <span style="color: #66D9EF;">print</span> <span style="color: #E6DB74;">"aa"</span> </pre> </div>
能夠看出,代碼塊輸出html後總會包含在 <div class="org-src-container">...</div>
內,至於代碼語言則由 <pre>
的class
屬性指明。shell
OK,目標很明顯,就是要將上面那段 html
代碼用 pygments
替換成咱們想要的高亮主題。數組
程序流程:
<div class="org-src-container">...</div>
內的html代碼提取到數組ABeautifulSoup
解析數組A中的html代碼,把當中 html tags
去掉,等到原生代碼pygments
高亮原生代碼,並輸出新的 html
CSS
完整代碼可見個人_pygment-html.py
因爲在個人 Jekyll
中,須要寫多個python腳本處理,所以我先創建一個虛擬環境,而後所由腳本都在這個虛擬環境中開發。
Virtualenv
用於建立獨立的Python環境,多個Python相互獨立,互不影響,它可以:
安裝: pip install virtualenv
建立: virtualenv /your/path/of/env
,默認狀況下,虛擬環境會依賴系統環境中的 site packages
,就是說系統中已經安裝好的第三方 package
也會安裝在虛擬環境中,若是不想依賴這些package,那麼能夠加上參數 --no-site-packages
創建虛擬環境
啓動虛擬環境: cd /your/path/of/env
, source ./bin/activate
,注意此時命令行會多一個 ENV
, ENV
爲虛擬環境名稱,接下來全部模塊都只會安裝到該目錄中去。
退出虛擬環境: deactivate
Virtualenv頗有用,可是操做較爲麻煩(想一想你須要來回切換多個ENV),所以可用 Virtualenvwrapper
簡化操做:
安裝: pip install virtualenvwrapper
把下面的代碼寫入 .bashrc/.zshrc
中:
if [ `id -u` != '0' ]; then export VIRTUALENV_USE_DISTRIBUTE=1 # <-- Always use pip/distribute export WORKON_HOME=$HOME/.virtualenvs # <-- Where all virtualenvs will be stored source /usr/local/bin/virtualenvwrapper.sh export PIP_VIRTUALENV_BASE=$WORKON_HOME export PIP_RESPECT_VIRTUALENV=true fi
建立 $HOME/.virtualenvs
目錄,之後可在裏面建立新的Virtualenv,若是你的Virtualenv不想放在裏面,也能夠只創建符號連接。
使用:
workon
或者 lsvirtualenv
mkvirtualenv [虛擬環境名稱]
workon [虛擬環境名稱]
rmvirtualenv [虛擬環境名稱]
deactivate
須要注意的是,當你進入ENV後,你所調用的python程序是在 ENV/bin
目錄下,所以腳本開頭的 #!/usr/bin/python
就沒有用了,運行腳本時須要顯式調用python解釋器。
因爲整個ENV目錄不適合上傳至 github page
的倉庫(上傳後出現各類 build page error
)。 因此我寫了個安裝ENV的Shell腳本:
mkdir _py_virtualenv
pip2 install virtualenv && virtualenv _py_virtualenv --no-site-packages && source _py_virtualenv/bin/activate && pip2 install pygments && pip2 install beautifulsoup4
切記此腳本只能用 source
運行,不能當成可執行文件運行。由於source是直接在當前shell環境中執行,而可執行文件方式只會在新的子shell下執行(執行到source部份就會出錯)
因爲我使用的是python2.7 ,而 python2.7
的編碼問題一直爲人所詬病。python2.7默認的是 ascii編碼
,當程序中出現非ascii編碼
時,python的處理經常會報這樣的錯:
UnicodeEncodeError: 'ascii' codec can't encode characters blalbla
對此有兩種辦法應對:
一種是涉及非ascii編碼的字串後添加 encode("utf8")
,不過這種方法彷佛時靈時不靈,並且一旦少寫一個地方,將會致使大量的錯誤報告,不推薦。
另外一種是在程序加載之初就將解釋器編碼改成 utf8
,這也是我所採用的:
import sys reload(sys) sys.setdefaultencoding('utf8')
本腳本是經過命令行運行的,高亮的文件由用戶經過命令行參數指令,利用 sys
模塊能夠很好地解析 cli參數
,所以用戶能夠方便地利用shell的一些特性輸入參數,具體代碼實現以下:
if len(sys.argv)==1: print 'No Arguments!' else: for file in sys.argv[1:]: if '.html' in file: hightlight_instance = Pygments_Html(file) hightlight_instance.colorize()
sys.argv
是一個 list
, sys.argv[0]
是程序名, sys.argv[1:]
纔是cli中的各個參數名。
Pygments_Html
是我寫的用於高亮代碼的類,僅包含兩個函數: __init__
和 colorize
。
__init__
初始化函數
def __init__(self,file): self.filename = file self.language_dict = {'sh':'sh','matlab':'matlab','C':'c','C++':'c++','css':'css', 'python':'python','scheme':'scheme','latex':'latex', 'ruby':'ruby','css':'css','html':'html','others':'text'}
filename
爲代處理的html文件,而 language_dict
則爲 org-mode
支持的語言名到 pygments
支持的語言名的映射(由於二者會有細微差別),若org-mode中的語言不爲pygments所支持,則映射至 text
,以純文本方式輸出。
注: org-mode
所支持的語言可用 ls /usr/share/emacs/site-lisp/org-mode/ob-*
看到,而 pygments
支持的可在pygments.org/docs/lexers 上看到
colorize
高亮函數,對 filename
文件所包含的代碼塊進行高亮。
Read file:
先讀入對應文件流至 file_read
:
try: # open the html file file = open( self.filename,'r' ) except IOError: print self.filename,'not exists' return file_read = file.read() print "Opening",self.filename file.close()
RE:
而後從 file_read
提取出包含在 <div class="org-src-container">...</div>
內的html代碼:
import re src_html_list = re.findall(r'<div class="org-src-container">.*?</div>',file_read,re.S)
提取是利用 re
模塊進行,正則表達式中 .*?
表明 惰性匹配
,之因此說是惰性,是由於它會匹配儘量少的字符,它從第一個字符開始找起,一旦符合條件,馬上保存到匹配集合中,而後繼續進行查找。與之相反的是不加 ?
的 貪婪匹配
。
re.S
是正則表達式的一個 flag
,由於須要尋找的文字跨越多行,若不加這個falg,python的re就只會一行一行地去匹配,若加了這個 flag
,表達式中的 .*
就會匹配包括 \n
在內的換行符。
BeautifulSoup:
接着便要開始對 src_html_list
裏的每一個元素作處理:
import BeautifulSoup4 for src_html in src_html_list: soup=BeautifulSoup(src_html) src_soup = soup.find("div",class_="org-src-container") language = (src_soup.pre['class'][1]).split('-')[1]
這裏利用 BeautifulSoup
對 src_html
包含的html進行解析,這裏 soup.find
使用了兩個參數,前一個是須要尋找的 tag
,後面的 class_
是 tag
中 class
屬性,返回符合這兩個條件的一個 soup
對象―― src_soup
。代碼塊的語言保存在<pre>
的 class
屬性中,把它提取出來存在 language
裏。
將 language
映射至 pygments
所支持的語言名:
if language in self.language_dict: language = self.language_dict[language] else: language = self.language_dict['others']
Pygments:
如今能夠用 pygments
高亮代碼了:
from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter lexer = get_lexer_by_name(language, stripall=True) formatter = HtmlFormatter(linespans='line', cssclass="highlight") src_colorized = highlight(src_soup.text, lexer, formatter)
Pygments
是 python
一個用於高亮代碼的模快
其中第7行中的 src_soup.text
能夠將 soup
對象中的 html tags
所有去掉,只剩下純文本的原生代碼。
highlight
函數有三個參數:第一個是用於高亮的代碼串;第二個是 lexer
,用於指定代碼語言;第三個是 formatter
,用於指定輸出樣式。
在這裏,指定 formatter
爲 HtmlFormatter
,即輸出爲 html
代碼,其中 cssclass
用於指定 div
的樣式名字,linespans
指定爲 line
,用於指定 <span>
的 id
前綴爲 line
,能夠用來輸出行號 ,輸出格式以下:
<div class="highlight"> <pre> <span id="line-1">...<span> <span id="line-2">...<span> <span id="line-3">...<span> <span id="line-4">...<span> </pre> </div>
待會我會爲 .hight
設計 CSS
,控制代碼及行號樣式。
Replace:
src_colorized
如今存儲了pygments高亮html代碼,須要替換掉原有的:
file_read = file_read.replace(src_html,src_colorized)
replace
有兩個參數,第一個是須要被替換的舊文本,第二個是新文本。
Rewrite:
for
循環完成後,意味着全部代碼已經高亮完畢,能夠將新的html重寫進去:
file = open( self.filename,'w' ) file.write(file_read) file.close()
上面的 pygments
只負責輸出html結構,而 CSS
倒是還沒有指定。
首先生成代碼顏色的樣式:
pygmentize -S default -f html > your/path/pygments.css
生成的樣式文件加到咱們的網頁中:
<link rel="stylesheet" href="/your/path/pygments.css">
因爲我使用 jekyll
,因此我將 css
文件發在 assets/themes/havee/css/
下
而後便須要指定行號樣式,上面說了行號由 .hightlight pre span
決定的:
.highlight pre { counter-reset: linenumbers; } .highlight pre > span:before { font-size: .9em; color: #aaa; content: counter(linenumbers); counter-increment: linenumbers; text-align: center; width: 2.5em; left: -0.5em; position: relative; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -webkit-user-select: none; /* Chrome all / Safari all */ -moz-user-select: none; /* Firefox all */ -ms-user-select: none; /* IE 10+ */ /* No support for these yet, use at own risk */ -o-user-select: none; user-select: none; }
行號是由 counter
自動生成,14行至21行的 *-user-select
禁止行號被選中,如此瀏覽代碼是能夠很方便地複製代碼。
在Shell中運行腳本,shell命令後面跟html文件名