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,这样会有更好的扩展性。
参考网站: