5分钟入门到精通 cac

前言

cac 是一个命令行参数输入的辅助工具,这篇文章就借用这个工具,来做一个文件复制工具

文件复制工具有三种需求

  1. 通过--s ,--source 设置源文件,--d --destination 设置目标文件
  2. 没有参数显示说明哪个文件,可以用先后顺序来表示源文件和目标文件
  3. 也可以只用一个参数表示文件,另一个自然是其他文件

实现这个功能之前,先来了解 cac 这个工具,如何处理命令行的参数

先准备一个空文件夹,然后安装 cac

shell 复制代码
mkdir cac
cd cac
npm init -y
npm i cac

创建一个文件 index.js,填入下面这个代码

javascript 复制代码
/**@type {import('cac')} */
const cac = require("cac");

/**@type {import('cac').CAC} */
const cli = cac();

cli.option("--type <type>", "choose a type", {
	default: "node",
});

const parsed = cli.parse();

console.log(JSON.stringify(parsed, null, " "));

执行:

javascript 复制代码
node index.js --type test

控制台输出内容:

好了,现在我们一个完成了非常简单的命令行参数解析的功能,在命令行中设置 type 为'test',然后在打印出来的 options 对象中,就可以看到 type 的值确实为 test

下面我们来看看 cac 有哪些 api

option

command 外的 option 配置

option 作用是设置变量,来接收命令行中的参数值。optino 有两种,一种跟在 command 之后的,一种不跟在 command 之后的。先看不跟在 command 之后,就像上面的 type

对于不跟在 command 之后的 option 变量,cac 对其要求相当得宽松。

基本用法:命令行中,--变量名,后面紧跟变量值,匹配好的变量,就会被放在 options 对象里面。 如果没有紧跟变量名的变量值,那就会被放在 args 中

javascript 复制代码
cli.option("--test <test>", "test option");
powershell 复制代码
node index.js --test test1 test2 test3


test1test2放到了 args 中

功能简单,有几个需要注意的地方:

注意的几个地方


  1. 命令行中输入的变量可以不与 option 中定义的变量名称匹配,可以为任意名称,甚至任意数量
shell 复制代码
node index.js --test test1 --test2 test2 --test3 test3

option 中只定义了 test 变量,但是命令行中输入了多个变量,也全都被 cac 捕获到放在 options 对象里面


  1. 相同变量可以出现多次,表示该变量有多个值
shell 复制代码
node index.js --test test1 --test test2 --test3 test3

命令行中有两个 test,值分别是test1test2,所以在输出的 options 中,可以看到 test 的值变成了数组,数组的内容是test1test2


  1. 如果变量后面没有设值,那么在 options 中,该变量就会被设置成 true,即布尔类型
shell 复制代码
node index.js --test test1 --test2 test2 --test3

test3 的值变成了 true


  1. 如果删掉变量名之后的括号,该变量就变成了 boolean 值
javascript 复制代码
cli.option("--test", "test option");
shell 复制代码
node index.js --test test1 --test test2 --test3 test3

因为 option 中 test 后面的括号删掉了,所以 test 被识别为 boolean 类型。那紧跟着 --test的值就自然放在了 args 中了

command 内的 option 配置

command 内的 option 就要严格很多了。

先看 command 基本用法。command 表示需要执行的命令,像 npm 中的 npm init,npm install中的 initinstall

shell 复制代码
cli.command("rm <file>", "input remove file")
	.option("--d", "is directory")
	.action((file,  options) => {
		console.log('file: ',file);
		console.log('options: ',options);
	});

上面代码定义一个 rm的命令,表示要移除某个文件。其中的,表示用户需要在 rm 命令之后输入文件名,文件名用 file 变量接收

然后 action 函数中的回调函数的第一个参数就是 file 变量了。第二个参数 options 就是上文中普通的 options 对象

command 也会有自己的 option,option 的用法和上文的用法一致。下面来看看实际效果

shell 复制代码
node index.js rm test.txt --d false

输出分成两块来看,第一块是 action 中回调函数的输出。第二块就是原本的 parsed 对象的输出。着重看第一块。

可以看到 file 变量成功被匹配了,作为了 action 回调函数的第一个参数。 并且-d 变量的匹配结果放在了 options 对象中

在第二块可以看到 args 中有 test.txt,这是因为 test.txt 没有 options 中的变量接收

看似很简单,但还是有很多需要注意的地方

注意的几个地方

  1. command 中定义的命令变量,使用尖括号后表示必填,即必须提供命令变量的值,并且可以在 option 回调函数中单独获取。如果没有使用尖括号,表示选填,而且不能在 option 回调函数中单独获取

还是用上面的 command 配置做测试

shell 复制代码
node index.js rm

没有提供 rm 命令的值,就会报错。将改成 [file]

shell 复制代码
cli.command("rm [file]", "input remove file")
	.option("--d", "is directory")
	.action((file,  options) => {
		console.log('file: ',file);
		console.log('options: ',options);
	});

现在没有报错了,但出现有意思的事情。action 中回调函数的第一个参数编程了 options,第二个参数变成了 undefined。由此可以猜到回调函数的入参设置逻辑了

这逻辑大家可以想想为什么

注意⚠️, 如果使用了选填模式,那么就不能用 action 函数单独接收 file 的值了,只能去 args 中拿

shell 复制代码
node index.js rm test.txt

命令行中就算输入了 test.txt,option 回调函数中还是只有一个参数


  1. 不能使用 option 中没有定义的变量名
shell 复制代码
node index.js rm test.txt --d --f

使用了没有定义的 f 变量,就报错了

这里和 command 外的 option 配置很不一样,command 外的 option 可以接收任意的变量名,无论有没有定义过。


  1. options 的变量名如果使用了尖括号,就必须提供变量名的值,否则就报错。如果使用方括号或者没有括号,就是可选的
shell 复制代码
cli.command("rm [file]", "input remove file")
	.option("--d <d>", "is directory")
	.action((file,  options) => {
		console.log('file: ',file);
		console.log('options: ',options);
	});
shell 复制代码
node index.js rm test.txt --d
shell 复制代码
node index.js rm test.txt --d true

注意这里是不提供 d的值,而不是不设置 d。如果直接不设置 d,则不会报错。

这里和 command 外的 option 配置也不一样,对于 command 外的 option,无论变量名是否用了尖括号,还是方括号,都不用提供变量的值。如果不提供变量的值,会将变量默认置为 true


  1. 变量名可以使用简写,类似 --h,--help,或者 npm inpm install 表示同一个意思
javascript 复制代码
cli.command("rm [file]", "input remove file")
	.option("--d, --is-directory <d>", "is directory")
	.action((file,  options) => {
		console.log('file: ',file);
		console.log('options: ',options);
	});
shell 复制代码
node index.js rm test.txt --d true

可以看到 options 对象里面多了两个 key,一个是 d,还有一个是 isDirectory

cac 会将短横线命令法转成小驼峰

用命令行实现文件 copy

好了 cac 的 API 讲解得差不多了,可以上手实现文章开头提出的需求了

  1. 通过--s ,--source 设置源文件,--d --destination 设置目标文件
  2. 如果没有参数显示说明哪个文件,可以用先后顺序来表示源文件和目标文件
  3. 也可以只用一个参数表示其中一种文件,另一个文件名不需要参数

实现 copy 功能

javascript 复制代码
// copy.js
const fs = require("node:fs");
const path = require("node:path");
const copy = async (source, address) => {
	const currentDir = process.cwd();
	const addressPath = path.join(currentDir, address);
	const sourcePath = path.join(currentDir, source);

	try {
		await fs.promises.copyFile(sourcePath, addressPath, fs.constants.COPYFILE_EXCL);
	} catch (error) {
		console.error(error);
	}
};

exports.copy = copy;

这里准备了一段代码,实现了 copy 函数,函数接收源文件,和目标文件名。然后使用fs.promises.copyFile实现文件 copy

最后使用 exports将 copy 导出

代码准备好了。讲讲我使用 cac 工具和 copy 代码结合的思路。

  1. 首先使用 cac 读取命令行中的源文件名,和目标文件名
  2. 将文件名传入 copy 函数中,实现文件的拷贝

修改 index.js 代码

javascript 复制代码
const cac = require("cac");
const { copy } = require("./copy");
/**@typedef {import('cac').CAC} CAC*/

/**@type {CAC} */
const cli = cac();

cli.option("--r, --resource [resource]", "set resource file")
	 .option(
	"--d, --destination [destination]",
	"set destination file"
);

const parsed = cli.parse();

/**@typedef {typeof cli.parse}  parse*/
/**
 * @param {ReturnType<parse>} parsed
 * @returns
 */
const getFiles = (parsed) => {
	const options = parsed.options;
	let resource = options.r,
		destination = options.d;
	if (!resource && !destination) {
		return parsed.args.slice(0, 2);
	}

	if (!resource) {
		return [parsed.args[0], destination];
	}
	if (!destination) {
		return [resource, parsed.args[0]];
	}

	return [resource, destination];
};

copy(...getFiles(parsed));

先使用 option 配置读取命令行输入的文件名,配置中resourcedestination都使用了 command 外的 option,表示可填可不填。

重点的参数变量处理逻辑,是放在了函数getFiles 中。

首先判断两个文件名都没有使用变量名来接收,即!resource && !destination为真。然后读取 args 中的前两项。可能用户会输出超过两个文件名,但我们只读取前两项。

如果其中一个没有使用变量名来接收,那就分开来判断。

代码很简单吧😁。下面来测试测试效果

测试代码

先准备一个 test.txt 文件

然后使用命令,将这个 test.txt 复制,复制后的文件名是 test2.txt

shell 复制代码
node index.js test.txt test2.txt

copy 成功。这里没有使用任何变量来接受文件名。

shell 复制代码
node index.js -r test.txt test3.txt
shell 复制代码
node index.js test.txt -d test4.txt
shell 复制代码
node index.js -r test.txt -d test5.txt

有个小设计:如果复制后的文件名已经存在的话,就会报错

完美

总结

这篇文章分享了 cac 的 API 使用,并且做了实用场景小 demo--实现文件的 copy

有问题可以评论区留言哦。你觉得这篇文章怎么样呢,喜欢就点赞+关注吧

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui