根据文件层级生成README目录

488 阅读3分钟

时间: 2022.第22周 晚 20:00

需求背景

我模仿大佬的学习记录 Today I Learned,自建了一个仓库,希望将我日常学到的小知识点记录下来,并按一定的规则进行分类。

暂时想到的类别就是 编程语言技术栈 等,每个类别拥有一个自己的文件夹,然后可以根据文件夹内的文件名,生成对应的markdown链接,并汇总到README.MD中,就像是一个目录。

任务点拆分

  • 扫描项目目录,根据文件夹名称生成类别名
  • 生成类别名后,写入README.MD,生成二级菜单文本内容
  • 扫描类别文件夹下的所有文件,根据文件名称生成文章名
  • 生成文章名后,写入README.MD,生成三级菜单文本内容
  • 将上述功能点做成 npm script,甚至是 git hook,每次commit完毕后,自动更新README.MD

单元测试

由于涉及到文件夹、文件,所以或许需要mock ?

技术点

  • 遍历目标下的所有文件夹(无需递归至子文件夹)
  • 遍历目标文件夹下所有的md文件
  • 写入(完全覆盖)文件内容

实现过程

好吧,我们先想办法获取项目根目录的所有文件夹,因为我想将categories文件夹直接平铺开(因为大佬就是这么做的哈哈哈哈),这样显地我学了那么那么多种类的东西~

理所当然写出了如下代码

 const result = await fs.readdir("./");

readdir,怎么也应该是读取文件夹嘛,我这样猜想,但是输出结果打脸了:

result

(12) ['.git', '.gitignore', '.pnpm-debug.log', '.vscode', 'genREADMEcontent.ts', 'js', 'LICENSE', 'node_modules', 'package.json', 'pnpm-lock.yaml', 'README.md', 'tsconfig.json']

读读文档,修改如下:

async function readCategoriesFolder() {
  const result = await fs.readdir("./", {
    encoding: "utf-8",
    withFileTypes: true,
  });

  const directories = result.filter((file) => {
    return file.isDirectory();
  });
}

// 输出
['.git', '.vscode', 'js', 'node_modules']

这次对了。但是如.git等文件夹似乎只能写死过滤规则了

export function filterUselessDirectories(dir: Dirent) {

  const target = ["node_modules"];

  const reg = /^\..+/; // 过滤掉以 "." 开头的文件夹

  return !target.includes(dir.name) && !reg.test(dir.name);

}

后面的事情就容易了。按照想要的排版内容生成string即可。 实现的方案可以是自己拼接markdown字符串,也可以是类似mjs的形式,后者没用过,回头有机会可以试试。

这里要注意,forEach中await也是没用的,依旧会往下继续执行,所以使用for of替代

husky实现自动更新README

先在 package.json里增加我们的 script

"updateDoc": "node ./gen.js"

然后在pre-commit的git hook里实现:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

#npm test
npm run updateDoc
git add README.md

优化点

  1. 蠢了,其实用 path实现会更优雅。自己操作file好麻烦哩 重写后:
const path = require("path");
const glob = require("glob");
const fs = require("node:fs/promises");

async function pathBootstrap() {
  const dirs = glob.sync("*/").filter(filterUselessDirectories);
  // 生成文件夹目录
  let str = dirs.reduce((pre, cur) => {
    const dirName = path.basename(cur);
    pre += `* [${dirName}](#${dirName}) \n`;
    return pre;
  }, "## Categories\n\n");
  str += "\n---\n\n";
  dirs.forEach((dir) => {
    const dirName = path.basename(dir);
    str += `### ${dirName}\n\n`;

    const files = glob.sync(dir + "*.md");
    files.forEach((file) => {
      const extension = path.extname(file); //  获取后缀名
      const fileName = path.basename(file, extension); // 获取没有后缀的文件名
      str += `* [${fileName}](${file}) \n`;
    });
  });
  await writeREADME(str);
}

function filterUselessDirectories(dirName) {
  const target = ["node_modules/"];
  const reg = /^\..+/; // 过滤掉以 "." 开头的文件夹
  return !target.includes(dirName) && !reg.test(dirName);
}

async function writeREADME(content) {
  await fs.writeFile("README.md", content, {
    encoding: "utf-8",
  });
}

pathBootstrap();

  1. 自己折腾这些其实意义不大。因为都有现成的包啦...