node命令行简单的脚本

225 阅读2分钟

node命令行脚本

本人因想养成把平时零散学到的知识记录下来,喜欢写一些文章来记录。所以就建立了一个git仓库。然后就遇到一个问题,因为写文章时创建的文件目录结构是一样的,每次手动创建感觉很痛苦。本着技术人的素养,就想着能写一个命令行的脚本来完成自动创建。


其实本次练习也是为了以后的创建自己的cli工具打下一定的基础。

用到的库:

"devDependencies": {
    "chalk": "^4.1.0",
    "commander": "^7.1.0",
    "inquirer": "^8.0.0",
    "lodash": "^4.17.21"
  }

先贴上代码,里面会有详细的注释,再来解释其中的用到的库。

const path = require("path");
const fs = require("fs");
const program = require("commander");
const _ = require("lodash");
const inquirer = require("inquirer");
const chalk = require("chalk");
const os = require("os");
const { dir } = require("console");

// 定义常量
const Technology = "technology";
const Diary = "diary";
const TypeSelection = "typeSelection";
const MaxLoop = 10;

const typeMap = {
  file: "file",
};

let count = 1;

function handleDirObj(dirObj = {}, abPath = "") {
  // 判断系统type,决定用/还是\,默认是windows, 因为用到的是递归,所以这部分可以写在外面
  let dilimiter = "\\";
  switch (os.type()) {
    case "Linux":
    case "Darwin":
      dilimiter = "/";
  }

  for (const key in dirObj) {
    const exact = dirObj[key];
    const exactPath = abPath + dilimiter + exact.name;
    const mustr = exactPath.slice(1);
    console.log(exact, exact.name, exactPath, mustr);
    if (!exact.type) {
      // 当目录处理
      if (!!fs.existsSync(mustr)) {
        console.log(`目录: ${mustr}已存在`);
      } else {
        if (!fs.mkdirSync(mustr)) {
          console.log(`目录: ${mustr}创建成功`);
        } else {
          console.log(`目录: ${mustr}创建失败`);
        }
      }
    } else {
      switch (exact.type) {
        case typeMap.file:
          if (!!fs.existsSync(mustr)) {
            return console.log("文件:" + mustr + "已存在");
          } else {
            if (fs.writeFileSync(mustr, "", "utf8")) {
              return console.log("文件:" + mustr + "创建失败");
            } else {
              return console.log("文件:" + mustr + "创建成功");
            }
          }
      }
    }
    count++;
    // 设定一个最大的层级,防止循环引用导致一直创建
    // 可以写一个栈的方式把每一个exact放进去,然后对后面的exact进行===来判断是否存在循环
    if (count > MaxLoop) {
      throw new Error("max loop");
    }
    // 用来判断路径的结构,必须是对象和含有name属性
    if (typeof exact !== 'object' || typeof exact.name !== "string") {
      return;
    }
    if (exact.children) {
      handleDirObj(exact.children, exactPath);
    }
  }
}

function getDiaryName() {
  return new Date().toLocaleDateString().split("/").join("-");
}

program
  .command("create")
  .alias("c")
  .description("创建新的文件夹")
  .option("--name [name]")
  .option("--technology", "创建技术文章")
  .option("--diary", "创建日记")
  .action((option) => {
    var config = _.assign(
      {
        name: null,
        description: "",
        technology: false,
        diary: false,
      },
      option
    );
    var promps = [];

    if (config.technology === false && config.diary === false) {
      promps.push({
        type: "checkbox",
        name: TypeSelection,
        message: "想创建什么类型的文件呢",
        choices: [
          {
            name: "technology/技术文章",
            value: Technology,
          },
          {
            name: "diary/日记",
            value: Diary,
          },
        ],
      });
    }

    if (!config.description) {
      promps.push({
        type: "input",
        name: "description",
        message: "请输入描述",
      });
    }

    if (!config.technology) {
      if (typeof config.name !== "string") {
        promps.push({
          type: "input",
          name: "name",
          message: "请输入名称",
          validate: function (input) {
            if (!input) {
              return "不能为空";
            }
            if (typeof input !== "string") {
              return "请输入字符串";
            }
            return true;
          },
        });
      }
    }
    inquirer.prompt(promps).then((answers) => {
      console.log(answers);
      // 处理逻辑

      // 对创建类型先进行规整
      let type = [];
      if (answers[TypeSelection]) {
        type = answers[TypeSelection];
      } else {
        if (answers.technology) {
          type.push(Technology);
        }
        if (answers.diary) {
          type.push(Diary);
        }
      }

      // 文件根路径
      const root = path.resolve(__dirname, "..");

      // 对每个的type进行相应的处理
      type.forEach((item) => {
        switch (item) {
          case Technology:
            // 对创建技术类文章进行处理
            const Tech = "tech";
            const techName = answers.name;

            // 用obj的表示创建的文件目录结构
            // type不写默认是文件夹
            const dirObj = {
              root: {
                name: root,
                children: {
                  Tech: {
                    name: Tech,
                    children: {
                      [techName]: {
                        name: techName,
                        children: {
                          img: {
                            name: "img",
                          },
                          indexMd: {
                            name: "index.md",
                            type: typeMap.file,
                          },
                        },
                      },
                    },
                  },
                },
              },
            };
            handleDirObj(dirObj);
            break;
          case Diary:
            // 对创建日记进行处理
            console.log("handle diary");
            const diaryDirObj = {
              root: {
                name: root,
                children: {
                  src: {
                    name: "src",
                    children: {
                      indexMd: {
                        name: `${getDiaryName()}.md`,
                        type: typeMap.file,
                      },
                    },
                  },
                },
              },
            };
            handleDirObj(diaryDirObj);
            break;
        }
      });
    });
  })
  .on("--help", function () {
    console.log("  Examples:");
    console.log("");
    console.log("$ app module moduleName");
    console.log("$ app m moduleName");
  });

program.parse(process.argv);

commander API

  • command -- 定义命令行指令,后面可跟上一个name,用空格隔开,如 .command( 'app [name] ')

  • alias -- 定义一个更短的命令行指令 ,如执行命令$ app m 与之是等价的

  • description -- 描述,它会在help里面展示 option -- 定义参数。它接受四个参数,在第一个参数中,它可输入短名字 -a和长名字--app ,使用 | 或者,分隔,在命令行里使用时,这两个是等价的,区别是后者可以在程序里通过回调获取到;第二个为描述, 会在 + + help 信息里展示出来;第三个参数为回调函数,他接收的参数为一个string,有时候我们需要一个命令行创建多个模块,就需要一个回调来处理;第四个参数为默认值

  • action -- 注册一个callback函数,这里需注意目前回调不支持let声明变量

  • parse -- 解析命令行

inquirer功能简介

  • input--输入
  • validate--验证
  • list--列表选项
  • confirm--提示
  • checkbox--复选框等等

chalk

最后我们引入 chalk 这个美化命令行的模块,它具有轻量级、高性能、学习成本低等特点。


代码结构写的不是很好,可以把具体的拆分。switch那段代码可以采用像pubsub里面的一样用对象存储handle,这样会有更好的扩展性。


参考网站:

jelly.jd.com/article/600…