本文完整代碼可見我的_pygment-html.py
最近在研究利用org-mode寫博客,其他一切都深得我心、甚合吾意,就是代碼染色發布html這一點要給差評。org-mode利用htmlize 插件給 src block 中的代碼著色,讓文章中的代碼塊輸出html后的顏色于你在emacs上看到的相同。可問題在于,我emacs上背景是暗黑系的,而我博客上是淺色系,因此代碼高亮風格不相調和,何況高亮主題單一不可定制,輸出代碼行號丑陋不堪,當然這都可以用elisp解決,可是想必是繁雜晦色無比(要調色啊…)
于是,我又再次投入萬能的Python的懷抱,直接利用它的pygments庫高亮代碼。
先介紹一下pygments。 pygments 能夠將一段原生代碼高亮并輸出為 html、latex、png 等多種格式,而且還提供各種樣式控制。
由于pygments庫是原生的python庫,因此通過寫elisp插件控制org-mode發布并不現實,再三思慮之下,只能從org-mode發布的html文件開始入手,把代碼塊的html改了。
先來看看org-mode代碼塊輸出html的特征:
src block :
#+begin_src pythonimport pygments輸出
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屬性指明。OK,目標很明顯,就是要將上面那段
html代碼用pygments替換成我們想要的高亮主題。程序流程:
- 提取:把包含在
<div class="org-src-container">...</div>內的html代碼提取到數組A- 去標記:用
BeautifulSoup解析數組A中的html代碼,把當中html tags去掉,等到原生代碼- 高亮:用
pygments高亮原生代碼,并輸出新的html- 替換:用新的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' ]; thenexport VIRTUALENV_USE_DISTRIBUTE=1 # <-- Always use pip/distributeexport WORKON_HOME=$HOME/.virtualenvs # <-- Where all virtualenvs will be storedsource /usr/local/bin/virtualenvwrapper.shexport PIP_VIRTUALENV_BASE=$WORKON_HOMEexport PIP_RESPECT_VIRTUALENV=truefi創建 $HOME/.virtualenvs 目錄,以后可在里面創建新的Virtualenv,如果你的Virtualenv不想放在里面,也可以只建立符號鏈接。
使用:
workon 或者 lsvirtualenvmkvirtualenv [虛擬環境名稱]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 sysreload(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' returnfile_read = file.read()print "Opening",self.filenamefile.close()RE:
然后從 file_read 提取出包含在 <div class="org-src-container">...</div> 內的html代碼:
import resrc_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 BeautifulSoup4for 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 highlightfrom pygments.lexers import get_lexer_by_namefrom pygments.formatters import HtmlFormatterlexer = 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文件名
新聞熱點
疑難解答