用Python高亮org-mode代码块
文章同时可在我的github blog上阅读:http://cheukyin.github.io/python/2014-08/pygments-highlight-src-export-html.html
本文完整代码可见我的_pygment-html.py
1 前言
最近在研究利用org-mode写博客,其他一切都深得我心、甚合吾意,就是代码染色发布html这一点要给差评。org-mode利用htmlize
插件给 src block
中的代码着色,让文章中的代码块输出html后的颜色于你在emacs上看到的相同。可问题在于,我emacs上背景是暗黑系的,而我博客上是浅色系,因此代码高亮风格不相调和,何况高亮主题单一不可定制,输出代码行号丑陋不堪,当然这都可以用elisp解决,可是想必是繁杂晦色无比(要调色啊…)
于是,我又再次投入万能的Python的怀抱,直接利用它的pygments库高亮代码。
2 实现框架
先介绍一下pygments。 pygments
能够将一段原生代码高亮并输出为 html、latex、png
等多种格式,而且还提供各种样式控制。
由于pygments库是原生的python库,因此通过写elisp插件控制org-mode发布并不现实,再三思虑之下,只能从org-mode发布的html文件开始入手,把代码块的html改了。
先来看看org-mode代码块输出html的特征:
src block
:
#+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
属性指明。
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相互独立,互不影响,它能够:
- 在没有权限的情况下安装新套件
- 不同应用可以使用不同的套件版本
- 套件升级不影响其他应用
安装: 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
简化操作:
- 将所有虚拟环境整合在一个目录下
- 管理(新增,删除,复制)虚拟环境
- 切换虚拟环境
- …
安装: 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文件名
用Python高亮org-mode代码块的更多相关文章
- python重要的函数代码块
注意:现在python3.3.5中print打印语句有了新写法: 1. python指定生成随机数 >>> import random >>> >>&g ...
- python如何计算程序(代码块)的运行时间?
1.引入time模块 2.调用time模块的time()函数 :用来获取当前的时间,返回的单位是秒 # 引入一个time模块, * 表示time模块的所有功能, # 作用: 可以统计程序运行的时间 f ...
- python下载图片的代码块
import urllib.requestimgurl="https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/si ...
- 代码块: 以冒号作为开始,用缩进来划分作用域,这个整体叫做代码块,python的代码块可以提升整体的整齐度,提高开发效率
# ### 代码块: 以冒号作为开始,用缩进来划分作用域,这个整体叫做代码块 if 5 == 5: print(1) print(2) if True: print(3) print(4) if Fa ...
- python代码块,小数据池,驻留机制深入剖析
一,什么是代码块. 根据官网提示我们可以获知: 根据提示我们从官方文档找到了这样的说法: A Python program is constructed from code blocks. A blo ...
- python代码块和小数据池
id和is 在介绍代码块之前,先介绍两个方法:id和is,来看一段代码 # name = "Rose" # name1 = "Rose" # print(id( ...
- Python小数据池,代码块
今日内容一些小的干货 一. id is == 二. 代码块 三. 小数据池 四. 总结 python小数据池,代码块的最详细.深入剖析 一. id is == 二. 代码块 三. 小数据池 四. ...
- python小数据池,代码块知识
一.什么是代码块? 根据官网提示我们可以获知: A Python program is constructed from code blocks. A block is a piece of Pyth ...
- Python基础学习Day6 is id == 区别,代码块,小数据池 ---->>编码
一.代码块 Python程序是由代码块构造的.块是一个python程序的文本,他是作为一个单元执行的. 代码块:一个模块,一个函数,一个类,一个文件等都是一个代码块. 而作为交互方式输入的每个命令都是 ...
- 《Python》 代码块、小数据池和编码转换
一.代码块 Python程序是由代码块构造的.块是一个python程序的文本,他是作为一个单元执行的. 什么是代码块:一个py文件,一个函数,一个文件,一个类都是一个代码块. 作为交互方式输入的每一行 ...
随机推荐
- 【转】如何使用Unity创造动态的2D水体效果
原文:http://gamerboom.com/archives/83080 作者:Alex Rose 在本篇教程中,我们将使用简单的物理机制模拟一个动态的2D水体.我们将使用一个线性渲染器.网格渲染 ...
- UVA10054 The Necklace
UVA10054 The Necklace 链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18806 [思路] 欧拉回路 ...
- 一个cocos2d程序的完整人生(从环境到代码全过程)
今天我的打砖块小游戏Beta0.1终于完成了,比较开心,写一下这个程序从出生到长大的全过程把. 这是个博客集合帖,具体的操作细节我都在其它博文中有详细说明,下面会给出链接 首先,我想我还是要介绍一 ...
- [转]让程序在崩溃时体面的退出之SEH+Dump文件
原文地址:http://blog.csdn.net/starlee/article/details/6649605 在我上篇文章<让程序在崩溃时体面的退出之SEH>中讲解了SEH中try/ ...
- Nunit中文文档
NUnit中文文档:http://www.36sign.com/nunit 关于特性的使用:http://www.36sign.com/nunit/attributes.html
- 吐槽一下CSDN的封停审查机制
今天和一同学用私信交流,我回答中用了"春季zhaopin"这几个字(大家知道是哪两个字),结果提示我内容非法无法发送,我立即改动用了谐音发了过去.结果我出来一看,显示我的个人主页 ...
- Delphi十进制和十六进制互转
Delphi 自带函数 IntToHex 功能说明:该函数用于将“十进制”转换成“十六进制”.该函数有二个参数.第一个参数为要转换的十进制数据,第二个参数是指定使用多少位来显示十六进制数据. 参考实例 ...
- 深入N皇后问题的两个最高效算法的详解 分类: C/C++ 2014-11-08 17:22 117人阅读 评论(0) 收藏
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行.同一列.同一斜线上的皇后都会自动攻击). 一. 求解N皇后问题是算法中回溯法应用的一个经典案例 回溯算 ...
- [io PWA] Great libraries and tools for great Progressive Web Apps
sw-toolbox: Github It provides a cononical implementation of all the runtime caching strategies that ...
- CH BR8(小学生在上课-逆元和互质数一一对应关系)
小学生在上课 总时限 11s 内存限制 256MB 出题人 jzc 提交情况 66/277 初始分值 600 锁定情况 背景 小学生在学校上数学课…… 描述 数学课上,小学生刚学会了乘除法.老师问了他 ...