zoukankan      html  css  js  c++  java
  • 「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本

    我用 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 引擎渲染后的效果如下图。 左边是脚本处理过的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(' ')
            """
            下面开始把不同的目录对应的条目取出
            """

            # 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[:2and 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(" ### " + 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 + ' ')
        
        """
        提示用户,处理好了
        """

        print("33[1;34mREADME.md refresh done33[0m")
        print("33[1;36mhttps://github.com/PiperLiu/ACMOI_Journey33[0m")
        print("star"
            + "33[1;36m the above repo 33[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(' ')
            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(" ")
                    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 + ' ')

    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蛋窝,回复微信加我微信,邀请你进入高质量技术交流群 / 好文分享群。欢迎点赞、点击在看将好文分享出去。

  • 相关阅读:
    tcp示例
    udp示例
    str 和 bytes
    Xamarin UIProgressView自定义
    个人用Canvas开发HTML5小游戏
    canvas画一个h5小游戏
    用canvas写一个h5小游戏
    react js踩坑之路(一)
    捣腾一下 webpack+gulp 使用姿势~
    关于移动端的适配
  • 原文地址:https://www.cnblogs.com/piperliu/p/14088703.html
Copyright © 2011-2022 走看看