Python - codes2html 軟著代碼收集工具

Pixabay License

寫軟著是一個神奇的工作,最后還要將一些實(shí)際代碼粘貼到 word 文檔中硫戈,這種繁瑣的工作怎么能手動搞呢锰什,python 腳本走起來。

需求

一股腦粘貼上所有代碼即可丁逝,不用解釋不用組織汁胆,字號小點(diǎn)一頁50行以上即可。當(dāng)然像我介么優(yōu)秀的程序猿要考慮得更通用一點(diǎn):

  • 代碼加個語法高亮果港,支持大多數(shù)語言沦泌。
  • 可以自定義需要收集的文件的擴(kuò)展名
  • 可以設(shè)置哪些文件或目錄需要忽略,有一些帶密碼或者私有 key 的源文件不能放辛掠。
  • 可以限制行數(shù)

技術(shù)方案

既然要生成 word 文檔谢谦,先搜了一下 python 生成 docx 格式的庫,果然有萝衩,就叫 python-docx回挽。簡單試了一下,直接生成沒有語法高亮的文檔很簡單猩谊,如果要語法高亮就要調(diào)用各種 API 來添加格式化的文本千劈。

再搜了一下語法高亮的庫,果然也有牌捷,Pygments墙牌,支持幾乎所有語言,看示例代碼也很簡單暗甥。簡單說一下原理:Pygments 支持多種語言喜滨,需要先判斷代碼文本的語言,然后根據(jù)語言選擇詞法分析器 lexer撤防,文本通過語法分析器的處理得到結(jié)構(gòu)化的 token 流虽风,再選擇一種 formatter 來輸出語法高亮的代碼。lexer 跟源代碼的語言相關(guān),Pygments 支持幾乎所有語言辜膝,只用考慮怎么判斷語言就行无牵。formatter 的支持種類有限,有 html厂抖、pdf茎毁、各種圖片等……就是沒有 docx。

理論上可以通過 python-docx 庫來自定義 formatter 自行實(shí)現(xiàn)一個 docx 的版本验游,Pygments 對自定義 formatter 支持得很好……但工作量比較大充岛,不是一兩天能搞定的保檐。最后選擇生成 html耕蝉,最后一步 html 轉(zhuǎn)到 docx 通過手動進(jìn)行——看起來比較 low,實(shí)際上我還調(diào)研了一些自動轉(zhuǎn)換方法:

  • 調(diào)用 web 服務(wù)接口轉(zhuǎn)換:一些免費(fèi)的在線工具可以在網(wǎng)頁上免費(fèi)用夜只,調(diào)用接口就需要購買了垒在,免費(fèi)額度比較少,還必須聯(lián)網(wǎng)扔亥。
  • 還有一個叫 pandoc 的庫可以轉(zhuǎn)換场躯,但是不支持 style 的轉(zhuǎn)換,語法高亮沒了旅挤。

Word 能直接打開 html 文件踢关,只需要「另存為」一下就可以轉(zhuǎn)換成 docx,不需要額外工具粘茄。因此签舞,最后決定還是轉(zhuǎn)換為 html,再手工轉(zhuǎn)換為 word柒瓣。

代碼

腳本已放到 Github 上了:codes2html儒搭,直接可用。下文解析一下關(guān)鍵代碼芙贫。

argparse 自定義參數(shù)

有一些參數(shù)需要配置搂鲫,因此使用了 argparse 庫來定義和解析參數(shù),這個庫非常強(qiáng)大磺平,只需定義好參數(shù)魂仍,help 信息能自動生成〖鹋玻看下面的例子:

import argparse
parser = argparse.ArgumentParser(description='A tool to collect codes and highlight syntax in a single html document.')
parser.add_argument('sources', # 無前綴參數(shù)
    metavar='source', # 顯示在 help 中的名字
    nargs='+', # 指定該參數(shù)數(shù)量擦酌,"+" 表示至少一個,還可以設(shè)置具體數(shù)量
    help='source code directory or file') # help 信息
parser.add_argument('-o', '--out',  # 可以指定多個名字媒吗,哪個都可以
    help='output file path. default is output.html', 
    default='output.html', # 設(shè)置默認(rèn)值
    dest='output') # 代碼中的標(biāo)識符仑氛,如果不寫就用前面參數(shù)名稱 ”--out“ 指定的 "out"
parser.add_argument('--insert-file-name', help='insert file name as header of a file', 
    action='store_true', 
    # 這個 store_true 表示出現(xiàn)這個 '--insert-file-name' 參數(shù)
    # 就將 .insert_file_name 設(shè)置為 True
    dest='insert_file_name')
args = parser.parse_args()
args.source # list of str
args.output # str
args.insert_file_name # bool

使用 add_argument() 方法來定義參數(shù),這個方法參數(shù)比較多,一般有幾種類型的參數(shù):

  1. 不需要 -x--xxx 這類前綴的參數(shù)锯岖,一般當(dāng)做主要參數(shù)介袜。
  2. 指定 -x--xxx 前綴的參數(shù),一般當(dāng)做可選參數(shù)出吹,并提供默認(rèn)值遇伞。
  3. 使用 -x--xxx 作為開關(guān)。

這個模塊功能比較多捶牢,具體用法可參考以下鏈接:
name-or-flags
nargs
action

遍歷目錄鸠珠、extensions 參數(shù)、ignore 文件

遍歷目錄有幾種方法:glob.glob秋麸,os.walk渐排,os.listdir。前面兩個都是自動遞歸遍歷灸蟆。os.listdir 需要自己遞歸調(diào)用驯耻,由于需要遍歷到 ignore 的目錄時能終止其子目錄的遍歷,使用 os.listdir 看起來比較清晰一點(diǎn)炒考。

extensions 參數(shù)指定哪些擴(kuò)展名可以作為源文件可缚。

ignore 文件使用類似 .gitignore 語法:按行分割成一個匹配字符串列表,作為 ignore 規(guī)則斋枢。遍歷過程中如果一個文件匹配了 ignore 規(guī)則帘靡,直接將這個文件忽略;如果一個目錄匹配了 ignore 規(guī)則瓤帚,忽略它并且不進(jìn)入其中遍歷描姚,也就是忽略所有的子目錄和文件。

class Codes2HtmlTool:
    def _collect_files(self, path):
        subfiles = os.listdir(path)
        subfiles.sort() # 按字母順序排個序
        for subfile in subfiles:
            if self.written_lines >= self.args.lines: # 行數(shù)限制判斷
                break
            if subfile.startswith('.'): # 隱藏目錄直接忽略
                continue
            full_path = os.path.join(path, subfile)
            # 調(diào)用 _should_ignore_file 方法判斷是否需要忽略
            if self._should_ignore_file(subfile):
                print('ignore "', full_path, '"', sep='')
                continue
            if os.path.isdir(full_path): # 如果是目錄缘滥,遞歸調(diào)用
                self._collect_files(full_path)
            elif self._accept_extension(subfile): # 如果是文件轰胁,還要檢查擴(kuò)展名
                # 如果擴(kuò)展名符合,調(diào)用文件處理方法
                self._highlight_and_write_file(full_path) 
    def _should_ignore_file(self, name):
        return _match_any_pattern(name, self.args.ignore_patterns)
    def _accept_extension(self, name):
        patterns = self.args.extension_patterns
        # 沒有 patterns 表示對擴(kuò)展名沒有限制
        return len(patterns) == 0 or _match_any_pattern(name, patterns)
def _match_any_pattern(name, patterns):
    for pattern in patterns:
        if fnmatch.fnmatch(name, pattern):
            return True
    return False

語法高亮

Pygments 其實(shí)還可以生成 rtf 格式的文檔朝扼,它比 html 更接近 docx赃阀,因?yàn)?word 軟件會自動關(guān)聯(lián) rtf 擴(kuò)展名。但經(jīng)過調(diào)研發(fā)現(xiàn) Pygments 對 rtf 格式的處理沒有 html 靈活擎颖,rtf 文件頭中的樣式定義沒有剝離開榛斯,多個文件格式化拼接到一起比較麻煩。Pygments 的 html formatter 將 css 定義單獨(dú)抽象出來搂捧,并提供了只返回引用 css 的 html 片段驮俗,適合多個文件使用同一配色方案拼接成一個大文件的場景。

from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_for_filename
# hf - HtmlFormatter
class Codes2HtmlTool:
    def _highlight_and_write_file(self, full_path):
        write_fd = self.write_fd
        hf = self.hf
        footer = self.args.file_footer
        try:
            # 先根據(jù)文件名獲取 lexer允跑,如果無法識別為源代碼會直接拋異常
            lexer = get_lexer_for_filename(full_path)
            with open(full_path) as fd:
                lines = fd.readlines()
                self.written_lines += len(lines)
                content = ''.join(lines)
                if full_path.endswith('.h'):
                    # 如果是 ".h" 文件王凑,根據(jù)內(nèi)容再次判斷一下
                    lexer = get_lexer_for_filename(full_path, code=content)
                formatted = highlight(content, lexer, hf) # 高亮代碼返回格式化代碼
                write_fd.write(formatted) # 寫入格式化的代碼
                write_fd.write(footer) # 寫入?yún)?shù)中定義的 footer
                print('highlighted with ', _short_class_name(lexer), ': "', full_path, '"', sep='')
        except:
            # 如果正確設(shè)置了 extensions 參數(shù)搪柑,異常情況應(yīng)該很少出現(xiàn)
            print('not source code: "', full_path, '"', sep='')

猜測代碼語言,可以通過文件擴(kuò)展名索烹,也可以通過文件內(nèi)容工碾。比較有趣的是 .h 文件只通過擴(kuò)展名,會判定為 C 語言百姓。但 Objective-C 也使用 .h 文件渊额,而且 Objective-CC 的超集,有一些 C 中沒有的語法垒拢,如果只用文件名旬迹,就會導(dǎo)致一些語法解析錯誤,不能正確高亮求类。同時使用文件名和文件內(nèi)容判斷才可以正確判定使用的是 Objective-C 還是 C奔垦。

用法簡介

  • 需要 python3
  • 需要安裝 Pygments pip install Pygments
  • 最簡單用法:python codes2html.py [目錄],會遍歷指定的目錄仑嗅,收集 3500 行代碼到一個單獨(dú)的 html 文件中宴倍。
  • 參數(shù) -e, --extensions:限定源代碼文件擴(kuò)展名张症,用逗號分割的字符串仓技,如果不限制可以傳 "*" 或者不寫。默認(rèn)值 "*"俗他。
  • 參數(shù) -l, --lines:限制讀入的源代碼行數(shù)脖捻,但不會截斷一個完整的文件,所以最終行數(shù)可能會大于這個值兆衅。0 表示不限制行數(shù)地沮。默認(rèn)值 3500
  • 參數(shù) -o, --out:指定輸出的 html 文件名羡亩。默認(rèn)值 output.html
  • 參數(shù) -i, --ignore:指定 ignore 文件摩疑。默認(rèn)值 ignore.txt
  • 參數(shù) -f, --footer:指定每個文件末尾插入的內(nèi)容,HTML 格式字符串畏铆。默認(rèn)值 </br>雷袋。

舉個例子

python codes2html.py ~/texthere/ ~/next/ -e h,c,cpp,m,mm,swift -l 5000 -i xcode_ignore.txt -o all_ios_projects.html

ignore 文件示例,iOS 項(xiàng)目

Pods
Assets.xcassets
*.framework
AppDelegate.*

(ole)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辞居,一起剝皮案震驚了整個濱河市楷怒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓦灶,老刑警劉巖鸠删,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贼陶,居然都是意外死亡刃泡,警方通過查閱死者的電腦和手機(jī)巧娱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烘贴,“玉大人家卖,你說我怎么就攤上這事∶沓” “怎么了上荡?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馒闷。 經(jīng)常有香客問我酪捡,道長,這世上最難降的妖魔是什么纳账? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任逛薇,我火速辦了婚禮,結(jié)果婚禮上疏虫,老公的妹妹穿的比我還像新娘永罚。我一直安慰自己,他們只是感情好卧秘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布呢袱。 她就那樣靜靜地躺著,像睡著了一般翅敌。 火紅的嫁衣襯著肌膚如雪羞福。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天蚯涮,我揣著相機(jī)與錄音治专,去河邊找鬼。 笑死遭顶,一個胖子當(dāng)著我的面吹牛张峰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棒旗,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喘批,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗦哆?” 一聲冷哼從身側(cè)響起谤祖,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎老速,沒想到半個月后粥喜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橘券,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年额湘,在試婚紗的時候發(fā)現(xiàn)自己被綠了卿吐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡锋华,死狀恐怖嗡官,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毯焕,我是刑警寧澤衍腥,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站纳猫,受9級特大地震影響婆咸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芜辕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一尚骄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侵续,春花似錦倔丈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诗舰,卻和暖如春警儒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背眶根。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留边琉,地道東北人属百。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像变姨,于是被迫代替她去往敵國和親族扰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容