用Python高亮org-mode代碼塊

文章同時可在個人github blog上閱讀:http://cheukyin.github.io/python/2014-08/pygments-highlight-src-export-html.html

本文完整代碼可見個人_pygment-html.pycss

1 前言

最近在研究利用org-mode寫博客,其餘一切都深得我心、甚合吾意,就是代碼染色發佈html這一點要給差評。org-mode利用htmlize 插件給 src block 中的代碼着色,讓文章中的代碼塊輸出html後的顏色於你在emacs上看到的相同。可問題在於,我emacs上背景是暗黑系的,而我博客上是淺色系,所以代碼高亮風格不相調和,況且高亮主題單一不可定製,輸出代碼行號醜陋不堪,固然這均可以用elisp解決,但是想必是繁雜晦色無比(要調色啊…)html

因而,我又再次投入萬能的Python的懷抱,直接利用它的pygments庫高亮代碼。python

2 實現框架

先介紹一下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代碼提取到數組A
  • 去標記:用 BeautifulSoup 解析數組A中的html代碼,把當中 html tags 去掉,等到原生代碼
  • 高亮:用 pygments 高亮原生代碼,並輸出新的 html
  • 替換:用新的html把舊的替換掉,並從新寫入文件
  • 樣式:爲代碼塊指定或設計 CSS

3 具體實現

完整代碼可見個人_pygment-html.py

3.1 虛擬環境

因爲在個人 Jekyll 中,須要寫多個python腳本處理,所以我先創建一個虛擬環境,而後所由腳本都在這個虛擬環境中開發。

3.1.1 Virtualenv

Virtualenv 用於建立獨立的Python環境,多個Python相互獨立,互不影響,它可以:

  1. 在沒有權限的狀況下安裝新套件
  2. 不一樣應用可使用不一樣的套件版本
  3. 套件升級不影響其餘應用

安裝: 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

3.1.2 VirtualenvWrapper

Virtualenv頗有用,可是操做較爲麻煩(想一想你須要來回切換多個ENV),所以可用 Virtualenvwrapper 簡化操做:

  1. 將全部虛擬環境整合在一個目錄下
  2. 管理(新增,刪除,複製)虛擬環境
  3. 切換虛擬環境

安裝: 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解釋器。

3.1.3 ENV安裝Shell腳本

因爲整個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部份就會出錯)

3.2 編碼問題

因爲我使用的是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') 

3.3 命令行交互

本腳本是經過命令行運行的,高亮的文件由用戶經過命令行參數指令,利用 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中的各個參數名。

3.4 Pygments_Html

Pygments_Html 是我寫的用於高亮代碼的類,僅包含兩個函數: __init__ 和 colorize 。

3.4.1 __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 上看到

3.4.2 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() 

3.5 CSS

上面的 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 禁止行號被選中,如此瀏覽代碼是能夠很方便地複製代碼。

4 用法

在Shell中運行腳本,shell命令後面跟html文件名

相關文章
相關標籤/搜索