设为首页 收藏本站
查看: 674|回复: 0

[经验分享] 用Python高亮org-mode代码块

[复制链接]
累计签到:5 天
连续签到:1 天
发表于 2015-11-30 07:05:39 | 显示全部楼层 |阅读模式
文章同时可在我的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文件名

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-145069-1-1.html 上篇帖子: Golang&Python测试thrift 下篇帖子: 2015/8/18 Python基本使用(2)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表