我用 python 写了一个自动生成索引的脚本

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

需求实现

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

# 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 道题,那么我就将其记录在我的## 日期归档下面,如下所示。

## 日期归档
- uu 2020.11.26 盛最多水的容器『因为两个边共同决定了上限,因此将较短边向内移动,抛弃搜索次优解』 双指针法 搜索 [py](./vsc_leetcode/11.盛最多水的容器.py) [cpp](./vsc_leetcode/11.盛最多水的容器.cpp)
- uu 2020.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),我使用汉语标明了。

""" """
import os.path as osp
import re
def 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 这个文件有更多功能,并且在不同参数时做不同的事。参数仿佛不同的「按钮」。

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

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

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 这个功能,使用的话,则调用相应函数。

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 五连。

祝各位变得更强。欢迎关注公众号:Piper蛋窝,回复微信加我微信,邀请你进入高质量技术交流群 / 好文分享群。欢迎点赞、点击在看将好文分享出去。

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

  1. 利用sphinx为python项目生成API文档

    sphinx可以根据python的注释生成可以查找的api文档,简单记录了下步骤 1:安装 pip install -U Sphinx 2:在需要生成文档的.py文件目录下执行sphinx-apido ...

  2. python快速生成注释文档的方法

    python快速生成注释文档的方法 今天将告诉大家一个简单平时只要注意的小细节,就可以轻松生成注释文档,也可以检查我们写的类方法引用名称是否重复有问题等.一看别人专业的大牛们写的文档多牛多羡慕,不用担 ...

  3. [原创博文] 用Python做统计分析 (Scipy.stats的文档)

    [转自] 用Python做统计分析 (Scipy.stats的文档) 对scipy.stats的详细介绍: 这个文档说了以下内容,对python如何做统计分析感兴趣的人可以看看,毕竟Python的库也 ...

  4. Python sphinx-build在Windows系统中生成Html文档

    看到前同事发布的“Markdown/reST 文档发布流水线”基于TFS.Docker.Azure等工具和平台进行文档发布的介绍说明,不得不在心中暗暗竖起大拇指.这套模式,实现了文档编写后版本管理.发 ...

  5. Python处理Excel生成CSV文档

    Python是一种解释型的.动态数据类型的.面向对象的高级程序设计语言.拥有丰富的处理数据和文本类库,并且得益于它是一种解释型的语言,在程序修改和功能扩展上,可以很容易做到大规模的调整.综合考虑Pyt ...

  6. 使用sphinx快速为你python注释生成API文档

    sphinx简介sphinx是一种基于Python的文档工具,它可以令人轻松的撰写出清晰且优美的文档,由Georg Brandl在BSD许可证下开发.新版的Python3文档就是由sphinx生成的, ...

  7. python文档生成工具:pydoc、sphinx;django如何使用sphinx?

    文档生成工具: 自带的pydoc,比较差 建议使用sphinx 安装: pip install sphinx 安装主题: 由各种主题,我选择常用的sphinx_rtd_theme pip instal ...

  8. 孤荷凌寒自学python第五十四天使用python来删除Firebase数据库中的文档

    孤荷凌寒自学python第五十四天使用python来删除Firebase数据库中的文档 (完整学习过程屏幕记录视频地址在文末) 今天继续研究Firebase数据库,利用google免费提供的这个数据库 ...

  9. Python之文件处理-批量修改md文档内容

    目录 Python之文件处理-批量修改md文档内容 Python之文件处理-批量修改md文档内容 #!/usr/bin/env python # -*- coding:utf-8 -*- import ...

随机推荐

  1. 文件流转blob并播放

    axios 这里是请求了个mp3做例子: this.$axios({ methods:"GET", url:"/api/music/soures/双笙.mp3" ...

  2. uniApp打卡日历

    功能 滑动切换时间,打点功能,支持月周切换日历组件    这是一款支持滑动切换以及周模式和月模式切换功能的日历组件,可以设置打卡信息,自定义样式. 组件样式使用了sass所有需要项目中先安装node- ...

  3. Maven魔法堂:安装Oracle JDBC Driver依赖的那些坑

    前言 由于Oracle并没有向公开Maven仓库提供任何Oracle JDBC Driver的Jar包,因此我们无法像MySQL.SQLite等那么轻松直接通过Maven加载依赖. 而手动下载Orac ...

  4. Lte Design Documentation之RRC

    RRC 特点 RRC模型在模拟器中提供以下功能 生成(在eNB中)和解释(在UE中)信息块(尤其是MIB和SIB1, SIB2) 初始化小区选择 RRC连接建立过程 RRC重新配置程序, 支持以下方式 ...

  5. learning to Estimate 3D Hand Pose from Single RGB Images论文理解

    持续更新...... 概括:以往很多论文借助深度信息将2D上升到3D,这篇论文则是想要用网络训练代替深度数据(设备成本比较高),提高他的泛性,诠释了只要合成数据集足够大和网络足够强,我就可以不用深度信 ...

  6. leetcode117search-in-rotated-sorted-array

    题目描述 给出一个转动过的有序数组,你事先不知道该数组转动了多少 (例如,0 1 2 4 5 6 7可能变为4 5 6 7 0 1 2). 在数组中搜索给出的目标值,如果能在数组中找到,返回它的索引, ...

  7. Go语言内存分配(简述 转)

    在Go语言里,从内存的分配到不再使用后内存的回收等等这些内存管理工作都是由Go在底层完成的.虽然开发者在写代码时不必过度关心内存从分配到回收这个过程,但是Go的内存分配策略里有不少有意思的设计,通过了 ...

  8. golang 简单工厂模式

    package kit //golang简单工厂模式 //go 语言没有构造函数一说,所以一般会定义NewXXX函数来初始化相关类. NewXXX 函数返回接口时就是简单工厂模式,也就是说Golang ...

  9. 目录方式扩展swap分区大小

    1.查看swap大小:free  -m  (-k|m|g) --以k|m|g为单位用去尾法显示大小  [root@lbg tmp]# free -m total        used        ...

  10. mysql学习——数据库基本操作

    查看当前数据库 创建数据库 查看数据库定义 删除数据库