1. 程式人生 > 實用技巧 >「懶惰的美德」我用 python 寫了個自動生成給文件生成索引的指令碼

「懶惰的美德」我用 python 寫了個自動生成給文件生成索引的指令碼

我用 python 寫了一個自動生成索引的指令碼

簡介:為了刷演算法題,建了一個 GitHub倉庫:PiperLiu / ACMOI_Journey,記錄自己的刷題軌跡,並總結一下方法、心得。想到一個需求:能不能在我每新增一條題目的筆記後,利用程式自動地將其歸類、建立索引?用 Python 實現一個入門級的小指令碼,涉及到檔案讀寫、命令列引數、陣列操作應用等知識點,在此分享給朋友們。

需求實現

我有一個 Markdown 文件,長成下面這個樣子:

#ACM/OIJourney
在此留下刷題痕跡與刷題心得。

不定期的方法論總結在這裡[./notes/README.md](./notes/README.md)。

學習資料:
-
OIWiki:https://oi-wiki.org/
-力扣中國:https://leetcode-cn.com/

##歸檔
##日期歸檔

注意到,兩個二級標題## 歸檔## 日期歸檔下空空如也。

我的需求是,我刷完一道題,就將其記錄在## 日期歸檔下,格式為: - uu 日期 題目名稱與概括 類別A 類別B 類別C... [程式檔案1] [程式檔案2] [程式檔案3]...

假設我今天刷了 2 道題,那麼我就將其記錄在我的## 日期歸檔下面,如下所示。

##日期歸檔
-uu2020.11.26盛最多水的容器『因為兩個邊共同決定了上限,因此將較短邊向內移動,拋棄搜尋次優解』雙指標法搜尋[py](./vsc_leetcode/11.盛最多水的容器.py
)[cpp](./vsc_leetcode/11.盛最多水的容器.cpp)
-uu2020.11.27整數轉羅馬數字『生活中從大的位數開始描述數字,因此從大的數與字元開始匹配』匹配字串[cpp](./vsc_leetcode/12.整數轉羅馬數字.cpp)

而我的## 歸檔下面還什麼都沒有,我希望我的指令碼可以自動幫我在## 歸檔下建立三級目錄:雙指標法搜尋匹配字串,並且將對應的題目放到下面去。

最終的效果是:

##歸檔
-[匹配](#匹配)
-[字串](#字串)
-[雙指標法](#雙指標法)
-[搜尋](#搜尋)
###匹配
-整數轉羅馬數字『生活中從大的位數開始描述數字,因此從大的數與字元開始匹配』[cpp](./vsc_leetcode/12.整數轉羅馬數字.cpp
)2020.11.27

###字串
-整數轉羅馬數字『生活中從大的位數開始描述數字,因此從大的數與字元開始匹配』[cpp](./vsc_leetcode/12.整數轉羅馬數字.cpp)2020.11.27

###雙指標法
-盛最多水的容器『因為兩個邊共同決定了上限,因此將較短邊向內移動,拋棄搜尋次優解』[py](./vsc_leetcode/11.盛最多水的容器.py)[cpp](./vsc_leetcode/11.盛最多水的容器.cpp)2020.11.26

###搜尋
-盛最多水的容器『因為兩個邊共同決定了上限,因此將較短邊向內移動,拋棄搜尋次優解』[py](./vsc_leetcode/11.盛最多水的容器.py)[cpp](./vsc_leetcode/11.盛最多水的容器.cpp)2020.11.26

##日期歸檔
-2020.11.26盛最多水的容器『因為兩個邊共同決定了上限,因此將較短邊向內移動,拋棄搜尋次優解』雙指標法搜尋[py](./vsc_leetcode/11.盛最多水的容器.py)[cpp](./vsc_leetcode/11.盛最多水的容器.cpp)
-2020.11.27整數轉羅馬數字『生活中從大的位數開始描述數字,因此從大的數與字元開始匹配』匹配字串[cpp](./vsc_leetcode/12.整數轉羅馬數字.cpp)

經過 Markdown 引擎渲染後的效果如下圖。

如上,我不但新增了三級標題### 匹配### 字串等,還為三級標題建立了目錄索引連結。

最終程式實現如下圖。

Python 與指令碼檔案

這樣就要派上我們的 Python 出場了。我覺得這才是 Python 的老本行:指令碼檔案。記得Python貓曾經有篇文章,講過為什麼 Python 中的註釋符號是 # 而不是 //

原因很可能是:Python的老本行,就是寫這一個個易用的指令碼檔案的,與shell類似。

想想 Python 的特點:解釋型語言、動態型語言、在命令列裡可以一條一條地輸入、os.system()可以直接呼叫命令...所以,拿 Python 來執行一個個小任務(指令碼檔案)再合適不過了。

整體邏輯

邏輯是:

  • 先把檔案讀到記憶體中,以列表list的形式儲存
  • 列表list內,每一元素對應一句話
  • 遍歷列表,遇到元素## 歸檔則其之後的元素按照不同條件取出、分析
  • 直到遇到元素## 日期歸檔,則把其之後的元素按條件取出、分析

細節在程式碼裡(程式碼檔案refresh.py),我使用漢語標明瞭。

""""""
importos.pathasosp
importre
defrefreah():
"""
我要處理的檔案是README.md
那麼我獲取其絕對路徑
注意這裡處理的檔案和程式碼檔案處於同一目錄下
"""

dirname=osp.dirname(__file__)
filepath=osp.join(dirname,"README.md")

"""
開啟這個檔案,其變數名是f
"""

withopen(filepath,'r+',encoding='utf-8')asf:
"""
將檔案的內容讀到記憶體f.read()
"""

content=f.read()
"""
以“換行符”/“回車”進行字串分割
這樣,row_list每個元素就是一行文字了
"""

row_list=content.split('\n')
"""
下面開始把不同的目錄對應的條目取出
"""

#foundtheun-packedrow
un_packed_rows=[]
dict_cata={}
dict_row_flag=False
date_row_flag=False
dict_row_num=0
date_row_num=0
cur_cata=None
foridx,rowinenumerate(row_list):
"""
如果到了##歸檔下面
"""

ifdict_row_flag:
if"###"inrow[:4]:
cur_cata=row[4:]
"""
data_cata是我們的類別字典,最終效果為
data_cata={
"匹配":[匹配的第1題,匹配的第2題,...],
"字串":[字串的第1題,字串的第2題,...],
...
}
"""

dict_cata.setdefault(cur_cata,[])
elif"-"inrow[:2]andnotre.match('\[.*\]\(.*\)',row[2:]):
"""
這裡用了一個正則
因為索引格式為
-[索引名稱](#索引名稱)
而題目格式為
-題目程式日期
因此如果僅憑是否以「-」開頭,則難以區分二者
因此加了一個是否正則匹配[*](*)的判斷
"""

dict_cata[cur_cata]=[row]+dict_cata[cur_cata]
else:
"""
判斷是否到了##歸檔下面
"""

ifrow=="##歸檔":
dict_row_flag=True
dict_row_num=idx+1
"""
如果到了##日期歸檔下面
"""

ifdate_row_flag:
"""
-uu是我自己設的格式
如果題目有uu,那麼這條就是我要用指令碼加到歸檔裡的題目
"""

if'-uu'inrow[:5]:
un_packed_rows=[row]+un_packed_rows
row_list[idx]="-"+row[5:]
else:
"""
判斷是否到了##日期歸檔下面
"""

ifrow=="##日期歸檔":
date_row_flag=True
dict_row_flag=False
date_row_num=idx+1
#packthoserowsto"##日期歸檔"
"""
下面是把新題目(uu)加到data_cata字典中
"""

forrowinun_packed_rows:
row=row.split('')
file_num=0
file_name=""
foreleinrow:
ifre.match('\[.*\]\(.*\)',ele):
file_num+=1
file_name+=(ele+'')
catas=row[4:-file_num]
forcincatas:
dict_cata.setdefault(c,[])
row_='-'+row[3]+''+file_name+row[2]
dict_cata[c].append(row_)
#delfile"##歸檔"
"""
下面是清空##歸檔的內容
根據dict_cata書寫新的全部內容
"""

row_list_a=row_list[:dict_row_num]
row_list_c=row_list[date_row_num-2:]
##row_list_b
row_list_b=[]
forkeyindict_cata:
row_list_b.append("\n###"+key)
forrowindict_cata[key]:
row_list_b.append(row)
row_list_b[0]=row_list_b[0][1:]
row_list=row_list_a+row_list_b+row_list_c

"""
把新處理好的文字,逐行寫到檔案中
(檔案先清空,原文字被覆蓋)
"""

withopen(filepath,'w',encoding='utf-8')asf:
forrowinrow_list:
f.write(row+'\n')

"""
提示使用者,處理好了
"""

print("\033[1;34mREADME.mdrefreshdone\033[0m")
print("\033[1;36mhttps://github.com/PiperLiu/ACMOI_Journey\033[0m")
print("star"
+"\033[1;36mtheaboverepo\033[0m"
+"andpractisetogether!")

defcata_index():
"""
這是我用於生成索引的函式
索引就是:
##歸檔
-[匹配](#匹配)
-[字串](#字串)
-[雙指標法](#雙指標法)
-[搜尋](#搜尋)

思路很簡單,還是取各個三級標題
然後規整到##歸檔下面
"""

dirname=osp.dirname(__file__)
filepath=osp.join(dirname,"README.md")

withopen(filepath,'r+',encoding='utf-8')asf:
content=f.read()
row_list=content.split('\n')
cata_list=[]
dict_row_flag=False
dict_row_num=0
cata_row_num=0
foridx,rowinenumerate(row_list):
ifdict_row_flag:
ifcata_row_num==0:
cata_row_num=idx
if"###"inrow[:4]:
cata=row[4:]
cata="-["+cata+"]"+"(#"+cata+")"
cata_list.append(cata)
elifrow=="##歸檔":
dict_row_flag=True
dict_row_num=idx+1
elifrow=="##日期歸檔":
cata_list.append("\n")
break
#addidx
row_list_a=row_list[:dict_row_num]
row_list_c=row_list[cata_row_num:]
row_list=row_list_a+cata_list+row_list_c
withopen(filepath,'w',encoding='utf-8')asf:
forrowinrow_list:
f.write(row+'\n')

refresh()
cata_index()

最終的執行效果是,我在命令列執行該指令碼,則文件自動規整。

argparse應用

注意到上面我輸入了一個引數 -r ,這個是為了讓 refresh.py 這個檔案有更多功能,並且在不同引數時做不同的事。引數彷彿不同的「按鈕」。

我將各個功能封裝在不同函式中,將應用解耦,即不同功能間不互相依賴,防止出現邏輯錯誤。

此外,我新建了一個函式,用於獲取引數。

defget_args():
parser=argparse.ArgumentParser()

parser.add_argument(
'--refresh','-r',
action='store_true',
help='refreahREADME.md'
)

args=parser.parse_known_args()[0]
returnargs

這樣,我們就可以獲取到 -r 這個引數,在主程序裡,我們判斷使用者是否使用 r 這個功能,使用的話,則呼叫相應函式。

defmain(args=get_args()):
ifargs.refresh:
refreah()
cata_index()

if__name__=="__main__":
main()

注意事項:encoding

此外,因為是中文,因此編碼規則值得注意。

比如,在檔案開頭加入 #-*- coding:UTF-8 -*-;在 open 檔案時,加入 encoding='uft-8' 引數。

值得改進的點:更好的正則

如果你讀我的程式碼,你會發現讀取、判斷行的邏輯上有些“粗暴”。

僅僅通過判斷 - [] 等是否是行的前四個字元是不妥的,並且我在判斷 - uu 日期 題目名稱與概括 類別A 類別B 類別C... [程式檔案1] [程式檔案2] [程式檔案3]... 時,也僅僅是通過 if else 判斷是否有方括號、括號來區分類別欄位程式檔案欄位。

這是不妥的,這樣,我就難以在題目裡自由書寫。一個可行的改進,是使用強大的正則表示式進階屬性。

尚無精力討論,未來可能會進一步修改討論,歡迎持續關注我。

專案地址:https://github.com/PiperLiu/ACMOI_Journey

歡迎 star watch fork pr issue 五連。

祝各位變得更強。歡迎關注公眾號:Piper蛋窩,回覆微信加我微信,邀請你進入高質量技術交流群 / 好文分享群。歡迎點贊、點選在看將好文分享出去。