用`markdown`寫文檔很方便,但是有個困擾的地方,就是標題的編號問題。 寫文檔的時候,經常會在中間插入新的標題和內容,所以手動管理編號的話,如果新的標題插在前面,則要調整後面所有的編號。 如果在文檔完成後再手動加上編號的話,不僅容易忘記, 而且有時候我們是在其他編輯器里編輯文檔再導出`mark ...
用markdown
寫文檔很方便,但是有個困擾的地方,就是標題的編號問題。
寫文檔的時候,經常會在中間插入新的標題和內容,所以手動管理編號的話,如果新的標題插在前面,則要調整後面所有的編號。
如果在文檔完成後再手動加上編號的話,不僅容易忘記,
而且有時候我們是在其他編輯器里編輯文檔再導出markdown
的,比如用語雀編寫文檔再導出markdown
,這時每次修改文檔再導出就要重新給導出的文檔添加編號。
我用語雀比較多,常常因此而困擾。
所以,用golang
簡單寫了個命令行工具,用來給markdown
文檔的標題添加編號。
1. 處理流程
處理過程很簡單:
- 首先讀取
markdown
文件的所有行 - 然後依次解析每一行
- 如果是標題行,添加編號
- 非標題行,略過
- 將添加了編號的內容重新寫入
markdown
文件
2. 主要步驟
主要的步驟有三個:
2.1. 讀取markdown
文件
// 獲取文件所有的行
func getAllLines(fp string) ([]string, error) {
fi, err := os.Open(fp)
if err != nil {
return nil, err
}
defer fi.Close()
br := bufio.NewReader(fi)
lines := make([]string, 0)
for {
a, _, c := br.ReadLine()
if c == io.EOF {
break
}
lines = append(lines, string(a))
}
return lines, nil
}
返回的是文件所有行的數組。
2.2. 添加標題編號
// 添加標題編號,最多支持五級標題
func addTitle(lines []string) ([]string, []ChangedLine) {
cLines := make([]ChangedLine, 0)
titles := [5]int{0, 0, 0, 0, 0}
for index, line := range lines {
titleLevel := getTitleLevel(line)
switch titleLevel {
case 1:
titles[0]++
lines[index] = strings.Replace(line, "# ", fmt.Sprintf("# %d. ", titles[0]), 1)
titles = [5]int{titles[0], 0, 0, 0, 0}
case 2:
titles[1]++
lines[index] = strings.Replace(line, "## ", fmt.Sprintf("## %d.%d. ", titles[0], titles[1]), 1)
titles = [5]int{titles[0], titles[1], 0, 0, 0}
case 3:
titles[2]++
lines[index] = strings.Replace(line, "### ", fmt.Sprintf("### %d.%d.%d. ", titles[0], titles[1], titles[2]), 1)
titles = [5]int{titles[0], titles[1], titles[2], 0, 0}
case 4:
titles[3]++
lines[index] = strings.Replace(line, "#### ", fmt.Sprintf("#### %d.%d.%d.%d. ", titles[0], titles[1], titles[2], titles[3]), 1)
titles = [5]int{titles[0], titles[1], titles[2], titles[3], 0}
case 5:
titles[4]++
lines[index] = strings.Replace(line, "##### ", fmt.Sprintf("##### %d.%d.%d.%d.%d. ", titles[0], titles[1], titles[2], titles[3], titles[4]), 1)
titles = [5]int{titles[0], titles[1], titles[2], titles[3], titles[4]}
}
if titleLevel != -1 {
cLines = append(cLines, ChangedLine{LineNo: index + 1, Before: line, After: lines[index]})
}
}
return lines, cLines
}
這裡支持最多5級標題的編號,寫的略顯繁瑣,本身邏輯比較簡單,暫時沒有去優化。
獲取標題的等級寫了簡單的小函數:
(根據markdown
的語法,根據每行開頭 # 的個數來判斷是幾級的標題)
// 獲取標題的等級
func getTitleLevel(s string) int {
if strings.HasPrefix(s, "# ") {
return 1
}
if strings.HasPrefix(s, "## ") {
return 2
}
if strings.HasPrefix(s, "### ") {
return 3
}
if strings.HasPrefix(s, "#### ") {
return 4
}
if strings.HasPrefix(s, "##### ") {
return 5
}
return -1
}
2.3. 新內容寫入markdown文件
// 寫入多行數據
func writeLines(fp string, lines []string) error {
content := strings.Join(lines, "\n")
return ioutil.WriteFile(fp, []byte(content), 0644)
}
2.4. 步驟合併起來
此命令行工具使用了 cobra 框架,最後把修改的部分也列印出來了。
type ChangedLine struct {
LineNo int
Before string
After string
}
var rootCmd = &cobra.Command{
Use: "mt",
Short: "給mkdown標題添加編號",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("NO file input!")
}
fp := args[0]
lines, err := getAllLines(fp)
if err != nil {
return err
}
newLines, changedLines := addTitle(lines)
err = writeLines(fp, newLines)
if err != nil {
return err
}
fmt.Println("修改的內容:>>>")
for _, cl := range changedLines {
fmt.Println("===================================")
fmt.Printf("line【%d】:\n修改前:%s\n修改後:%s\n", cl.LineNo, cl.Before, cl.After)
fmt.Println("===================================")
}
return nil
},
}
3. 使用方法
go build # 編譯後生成 mdtitle 二進位文件
./mdtitle xxxx.md # 運行
本文最後附加的下載地址中有完整源碼。
其中也包含編譯好的二進位(linux和windows版本2個都有)。
4. 補充說明
今天一時起意寫的小工具,只是為了方便給自己從語雀導出的markdown
加標題編號。
一定還有很多不足之處有待完善,寫完之後我自己感覺至少還需要完善:
- 沒有判斷文檔是否已經有標題編號,已經有標題編號的情況,應當忽略繼續添加標題編號
- 標題的層級是硬編碼的,目前只支持5級標題(不過5級應該滿足大部分情況了)
代碼下載地址(其中包含編譯好的linux和windows下的二進位):
md-title.zip: https://url11.ctfile.com/f/45455611-859852761-9f5f8e?p=6872
(訪問密碼: 6872)