前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本

「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本

作者头像
Piper蛋窝
发布2020-12-15 10:52:24
1.2K0
发布2020-12-15 10:52:24
举报
文章被收录于专栏:Piper蛋窝Piper蛋窝

Photo by Maxwell Nelson, unsplash

简介:为了刷算法题,建了一个 GitHub仓库:PiperLiu / ACMOI_Journey[1],记录自己的刷题轨迹,并总结一下方法、心得。想到一个需求:能不能在我每新增一条题目的笔记后,利用程序自动地将其归类、创建索引?用 Python 实现一个入门级的小脚本,涉及到文件读写、命令行参数、数组操作应用等知识点,在此分享给朋友们。

需求实现

我有一个 Markdown 文档,长成下面这个样子:

代码语言:javascript
复制
# ACM/OI Journey在此留下刷题痕迹与刷题心得。不定期的方法论总结在这里[./notes/README.md](./notes/README.md)。学习资料:- OI Wiki: https://oi-wiki.org/- 力扣中国: https://leetcode-cn.com/## 归档## 日期归档

注意到,两个二级标题## 归档## 日期归档下空空如也。

我的需求是,我刷完一道题,就将其记录在## 日期归档下,格式为: - uu 日期 题目名称与概括 类别A 类别B 类别C... [程序文件1] [程序文件2] [程序文件3]...

假设我今天刷了 2 道题,那么我就将其记录在我的## 日期归档下面,如下所示。

代码语言:javascript
复制
## 日期归档- uu 2020.11.26 盛最多水的容器『因为两个边共同决定了上限,因此将较短边向内移动,抛弃搜索次优解』 双指针法 搜索 [py](./vsc_leetcode/11.盛最多水的容器.py) [cpp](./vsc_leetcode/11.盛最多水的容器.cpp)- uu 2020.11.27 整数转罗马数字『生活中从大的位数开始描述数字,因此从大的数与字符开始匹配』 匹配 字符串 [cpp](./vsc_leetcode/12.整数转罗马数字.cpp)

而我的## 归档下面还什么都没有,我希望我的脚本可以自动帮我在## 归档下创建三级目录:双指针法搜索匹配字符串,并且将对应的题目放到下面去。

最终的效果是:

代码语言:javascript
复制
## 归档- [匹配](#匹配)- [字符串](#字符串)- [双指针法](#双指针法)- [搜索](#搜索)### 匹配- 整数转罗马数字『生活中从大的位数开始描述数字,因此从大的数与字符开始匹配』 [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 引擎渲染后的效果如下图。

左边是脚本处理过的Markdown文件;右边是渲染后的效果

如上,我不但新增了三级标题### 匹配### 字符串等,还为三级标题创建了目录索引链接。

最终程序实现如下图。

Python 与脚本文件

这样就要派上我们的 Python 出场了。我觉得这才是 Python 的老本行:脚本文件。记得Python猫曾经有篇文章[2],讲过为什么 Python 中的注释符号是 # 而不是 //

原因很可能是:Python的老本行,就是写这一个个易用的脚本文件的,与shell类似。

想想 Python 的特点:解释型语言、动态型语言、在命令行里可以一条一条地输入、os.system()可以直接调用命令...所以,拿 Python 来执行一个个小任务(脚本文件)再合适不过了。

整体逻辑

逻辑是:

•先把文件读到内存中,以列表list的形式保存•列表list内,每一元素对应一句话•遍历列表,遇到元素## 归档则其之后的元素按照不同条件取出、分析•直到遇到元素## 日期归档,则把其之后的元素按条件取出、分析

细节在代码里(代码文件refresh.py),我使用汉语标明了。

代码语言:javascript
复制
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释 
* """ """import os.path as ospimport redef refreah():    """    我要处理的文件是 README.md    那么我获取其绝对路径    注意这里处理的文件和代码文件处于同一目录下    """    dirname = osp.dirname(__file__)    filepath = osp.join(dirname, "README.md")    """    打开这个文件,其变量名是 f    """    with open(filepath, 'r+', encoding='utf-8') as f:        """        将文件的内容读到内存 f.read()        """        content = f.read()        """        以“换行符”/“回车”进行字符串分割        这样,row_list 每个元素就是一行文字了        """        row_list = content.split('\n')        """        下面开始把不同的目录对应的条目取出        """        # found the un-packed row        un_packed_rows = []        dict_cata = {}        dict_row_flag = False        date_row_flag = False        dict_row_num  = 0        date_row_num  = 0        cur_cata = None        for idx, row in enumerate(row_list):            """            如果到了 ## 归档 下面            """            if dict_row_flag:                if "### " in row[:4]:                    cur_cata = row[4:]                    """                    data_cata 是我们的类别字典,最终效果为                    data_cata = {                        "匹配": [匹配的第1题, 匹配的第2题, ...],                        "字符串": [字符串的第1题, 字符串的第2题, ...],                        ...                    }                    """                    dict_cata.setdefault(cur_cata, [])                elif "- " in row[:2] and not re.match('\[.*\]\(.*\)', row[2:]):                    """                    这里用了一个正则                    因为索引格式为                        - [索引名称](#索引名称)                    而题目格式为                        - 题目 程序 日期                    因此如果仅凭是否以「- 」开头,则难以区分二者                    因此加了一个是否正则匹配 [*](*) 的判断                    """                    dict_cata[cur_cata] = [row] + dict_cata[cur_cata]            else:                """                判断是否到了 ## 归档 下面                """                if row == "## 归档":                    dict_row_flag = True                    dict_row_num  = idx + 1            """            如果到了 ## 日期归档 下面            """            if date_row_flag:                """                - uu 是我自己设的格式                如果题目有 uu ,那么这条就是我要用脚本加到归档里的题目                """                if '- uu ' in row[:5]:                    un_packed_rows = [row] + un_packed_rows                    row_list[idx] = "- " + row[5:]            else:                """                判断是否到了 ## 日期归档 下面                """                if row == "## 日期归档":                    date_row_flag = True                    dict_row_flag = False                    date_row_num  = idx + 1        # pack those rows to "## 日期归档"        """        下面是把新题目(uu)加到 data_cata 字典中        """        for row in un_packed_rows:            row = row.split(' ')            file_num = 0            file_name = ""            for ele in row:                if re.match('\[.*\]\(.*\)', ele):                    file_num += 1                    file_name += (ele + ' ')            catas = row[4:-file_num]            for c in catas:                dict_cata.setdefault(c, [])                row_ = '- ' + row[3] + ' ' + file_name + row[2]                dict_cata[c].append(row_)        # del file "## 归档"        """        下面是清空 ## 归档 的内容        根据 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 = []        for key in dict_cata:            row_list_b.append("\n### " + key)            for row in dict_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    """    把新处理好的文本,逐行写到文件中    (文件先清空,原文本被覆盖)    """    with open(filepath, 'w', encoding='utf-8') as f:        for row in row_list:            f.write(row + '\n')    """    提示用户,处理好了    """    print("\033[1;34mREADME.md refresh done\033[0m")    print("\033[1;36mhttps://github.com/PiperLiu/ACMOI_Journey\033[0m")    print("star"        + "\033[1;36m the above repo \033[0m"        + "and practise together!")def cata_index():    """    这是我用于生成索引的函数    索引就是:    ## 归档    - [匹配](#匹配)    - [字符串](#字符串)    - [双指针法](#双指针法)    - [搜索](#搜索)    思路很简单,还是取各个三级标题    然后规整到 ## 归档 下面    """    dirname = osp.dirname(__file__)    filepath = osp.join(dirname, "README.md")    with open(filepath, 'r+', encoding='utf-8') as f:        content = f.read()        row_list = content.split('\n')        cata_list = []        dict_row_flag = False        dict_row_num  = 0        cata_row_num  = 0        for idx, row in enumerate(row_list):            if dict_row_flag:                if cata_row_num == 0:                    cata_row_num = idx                if "### " in row[:4]:                    cata = row[4:]                    cata = "- [" + cata + "]" + "(#" + cata + ")"                    cata_list.append(cata)            elif row == "## 归档":                dict_row_flag = True                dict_row_num  = idx + 1            elif row == "## 日期归档":                cata_list.append("\n")                break        # add idx        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        with open(filepath, 'w', encoding='utf-8') as f:            for row in row_list:                f.write(row + '\n')refresh()cata_index()
*/

最终的运行效果是,我在命令行执行该脚本,则文档自动规整。

argparse应用

注意到上面我输入了一个参数 -r ,这个是为了让 refresh.py 这个文件有更多功能,并且在不同参数时做不同的事。参数仿佛不同的「按钮」。

我将各个功能封装在不同函数中,将应用解耦,即不同功能间不互相依赖,防止出现逻辑错误。

此外,我新建了一个函数,用于获取参数。

代码语言:javascript
复制
def get_args():    parser = argparse.ArgumentParser()    parser.add_argument(        '--refresh', '-r',        action='store_true',        help='refreah README.md'    )    args = parser.parse_known_args()[0]    return args

这样,我们就可以获取到 -r 这个参数,在主进程里,我们判断用户是否使用 r 这个功能,使用的话,则调用相应函数。

代码语言:javascript
复制
def main(args=get_args()):    if args.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 五连。

引用链接

[1] PiperLiu / ACMOI_Journey: https://github.com/PiperLiu/ACMOI_Journey [2] Python猫曾经有篇文章: Python猫:Python为什么用#号作注释符?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Piper蛋窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求实现
  • Python 与脚本文件
  • 整体逻辑
  • argparse应用
  • 注意事项:encoding
  • 值得改进的点:更好的正则
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档