我用 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. Activit的心路历程:获取当前节点的上一节点【可能存在多个】的nodeId

    在我的开发任务中,突然给我提出了一个待办任务需要获取当前任务节点上以任务节点的表单信息,刚开始搞得我有点措手不及,后来仔细是靠后,灵感一下,直接操作流程的bpmn信息就可以获取到节点信息嘛,顺着这个思 ...

  2. 要求用户输入用户名和密码,只要不是admin、888888就

    要求用户输入用户名和密码,只要不是admin.888888就一直提示用户名或密码错误,请重新输入 Console.WriteLine("输入账号和密码"); string a = ...

  3. 痞子衡嵌入式:超级下载算法(RT-UFL)开发笔记(2) - 识别当前i.MXRT型号

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是超级下载算法开发笔记(2)之识别当前i.MXRT型号. 文接上篇 <超级下载算法(RT-UFL)开发笔记(1) - 执行在不同CM ...

  4. JavaScript探秘:可执行的上下文堆栈

    这一系列的文章,挺不错的,值得收藏细读 JavaScript探秘:可执行的上下文堆栈

  5. 解决 Vmware 服务拒绝访问的问题

    背景 在服务页面想将 VMware NAT Service 设置为自动开启的,但是保存的时候显示拒绝访问,如下图 解决方案 想到在本机的火绒启动项管理里面将 VMware NAT Service 设置 ...

  6. 查询Ceph的OSD占用内存

    前言 之前写过一篇关于查询OSD的运行的CPU的情况的分享,本篇是讲的获取内存占用的,代码包括两种输出,一种是直接的表格,一种是可以方便解析的json 代码 直接上代码,python才用不久,所以可能 ...

  7. Linux——CentOS 7 systemctl和防火墙firewalld命令

    一.防火墙的开启.关闭.禁用命令 (1)设置开机启用防火墙:systemctl enable firewalld.service (2)设置开机禁用防火墙:systemctl disable fire ...

  8. HotSpot类模型之InstanceKlass

    上一篇 HotSpot源码分析之类模型 介绍了类模型的基础类Klass的重要属性及方法,这一篇介绍一下InstanceKlass及InstanceKlass的子类. 1.InstanceKlass类 ...

  9. 打乱Map key - value的对应顺序

    应用场景:对于考试试卷选择题选项的乱序对应问题,防止考生作弊,每个人的题目都是不一样的选项顺序. package com.muyuan.platform.elearning.util; import ...

  10. Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)复现

    Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)复现 环境搭建 docker pull medicean/vulapps:s_shiro_1 docker run -d ...