使用 Node.js 构建 CLI
先决条件
项目设置
构建基本 CLI
处理命令行参数
添加实用函数
使用粉笔和粉笔进行美化
命令行实用程序是迄今为止最基本、最优秀的应用程序,也是这一切的起源。
我们每天都会用到命令行工具,无论是 git、grep、awk、npm 还是其他任何终端应用程序。命令行界面 (CLI) 非常实用,通常是完成任务最快的方式。你的工作流程中是否有需要反复执行的操作?很有可能这些操作都可以通过 CLI 实现自动化。
如果标题还不够清楚的话,我们将使用Node.js来构建我们的 CLI。为什么呢?因为 Node.js 生态系统拥有数千个极其有用的软件包,我们可以利用它们来实现我们的目标。无论你想做什么, npm上很可能都有相应的软件包。此外,Node.js 还内置了许多库,可以完成诸如文件处理、启动其他应用程序、断言测试等诸多任务。除此之外,基于 Node.js 构建的 CLI 具有高度可移植性,这意味着它们很容易在不同的操作系统上安装。
在本教程中,我们将构建一个简单的命令行界面(CLI)来实现语言间的翻译。我们将接收字符串类型的参数,将其解析成句子,然后发送到翻译 API 获取翻译结果,最后显示结果。完整的代码可以在GitHub 仓库中找到。让我们开始吧!
先决条件
以下是创建 CLI 所需的工具,请确保在开始之前已准备好这些工具:
- 已安装最新版本的Node.js。
- 文本编辑器。
大概就是这样了。
项目设置
我们将搭建一个基本的Node.js项目:
- 打开终端。
2. 为项目创建一个文件夹。
~$mkdir termTranslate
3. 导航到该位置。
~$cd termTranslate
4. 在该文件夹中初始化一个 Node.js 项目。
~$npm init
5. 填写提示。
您的项目已设置完成。
构建基本 CLI
现在我们的 Node 项目已经准备就绪,接下来我们将着手开发 CLI。以下是具体步骤:
bin在项目根目录下创建一个名为“文件夹”的文件夹。- 在内部创建一个名为“This is gonna be entry point of our CLI”的
bin文件。index.js - 现在打开
package.json文件,将“main”部分更改为bin/index.js. - 现在手动向
package.json名为 `<filename>` 的文件中添加另一个条目bin,并将其键设置为 `<key>`tran,值设置为 `<value>./bin/index.js`。添加的内容应该类似于这样:
"bin": {
"tran": "./bin/index.js"
}
关键在于 `<key> tran`,它是调用 CLI 的关键字。用户在终端中输入此关键字即可使用您的 CLI。您可以随意命名,但我建议使用简短
且语义化的名称,以便快速输入和轻松记忆。
这里定义的名称并非永久有效,您可以随时更改。
您的整个package.json文件应该类似于这样:
{
"name": "termtran",
"version": "1.0.0",
"description": "A CLI to translate between languages in the terminal",
"main": "bin/index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [
"cli"
],
"bin": {
"tran": "./bin/index.js"
},
"author": "Your Name",
"license": "ISC"
}
注意:在文件中添加新条目后,不要忘记添加额外的逗号
package.json。这是一个很容易犯的错误。
5. 打开文件夹index.js中的文件bin,并将以下代码放入其中:
#! /usr/bin/env node
console.log("Hello World!");
以 `/` 开头的第一行
#!称为 `/shebang` 行或 `/`bang行。`shebang` 行用于指定运行下方代码的解释器的绝对路径。此处使用的 `shebang` 行适用于 Linux 或 UNIX 系统,但 Node.js 在 Windows 和 macOS 上也需要它,才能正确安装和执行脚本。
现在让我们安装并测试我们的命令行界面。
用户可以从系统中的任何位置调用我们的 CLI,因此让我们将其全局安装。
导航到项目根目录,然后运行
~$npm install -g .
该-g标志告诉 npm 将软件包全局安装到系统中。
在终端中输入指定的关键字来测试 CLI。
~$tran
如果一切操作正确,您应该会看到我们用 console.log 输出到文件中的消息。 类似这样:in
dex.js
一切都好!
处理命令行参数
我们的基本命令行界面已经准备就绪,现在我们将着手添加更多功能。
任何命令行界面 (CLI) 最基本的任务都是处理命令行参数。在我们的 CLI 中,我们将接收语言名称和待翻译的句子作为参数,然后对其进行解析。
虽然 Node.js 提供了处理命令行参数的内置功能,但我们将使用一个名为yargs的 npm 包🏴☠,它是专门为构建 CLI 而设计的
。yargs 将简化我们解析参数的过程,并帮助我们组织命令行标志。
- 安装 yargs
~$npm i yargs
2. 安装完成后,将该模块包含在您的index.js:
~$const yargs = require("yargs");
3. 然后创建options包含所有命令行标志的对象:
const usage = "\nUsage: tran <lang_name> sentence to be translated";const options = yargs
.usage(usage)
.option("l", {alias:"languages", describe: "List all supported languages.", type: "boolean", demandOption
: false })
.help(true)
.argv;
在上面的代码中,我定义了一个选项-l,传递该选项后,它会打印出 API 支持的所有语言,我们稍后会实现这个功能。Yargs 默认提供了--help相应--version的标志。
如果你想让某个选项成为必填项,可以将其demandOption值设置为 `true` true,这样,如果未提供该标志,
YarGS 将抛出错误。 测试方法如下:Missing argument
好的!
所有通过命令传递的参数都会存储在列表中,
yargs.argv._除非参数以 `a`—或 `b`开头--;在这种情况下,它会被视为一个标志,默认值为布尔值。您可以使用 `console.log`yargs.argv来更清楚地了解参数的存储方式。
使用以下方式访问传递的标志的值yargs.argv.flagname。
添加实用函数
现在是时候添加实用功能了。
我计划以下列方式收集意见:
~$tran lang_name the sentence to be translated
所以我们需要解析这些参数。
我们可以把所有实用函数都写在同一个文件里index.js,但这看起来不够整洁,所以我将创建一个单独的文件utils.js来存放所有函数。以下是我们需要做的:
utils.js在文件夹中创建另一个名为的文件bin。- 将文件包含在您的
index.js:
const utils = require('./utils.js')
3. 创建一个用于解析句子的函数:
编写函数utils.js并导出它:
module.exports = { parseSentence: parseSentence };function parseSentence(words) {
var sentence = "";
for(var i = 1; i < words.length; i++) {
sentence = sentence + words[i] + " ";
}
打电话进来index.js:
var sentence = utils.parseSentence(yargs.argv._);
4. 创建一个函数,当没有传递参数时显示帮助信息:
在你的代码中创建一个函数utils.js:
module.exports = { showHelp: showHelp, parseSentence: parseSentence };const usage = "\nUsage: tran <lang_name
> sentence to be translated";
function showHelp() {
console.log(usage);
console.log('\nOptions:\r')
console.log('\t--version\t ' + 'Show version number.' + '\t\t' + '[boolean]\r')
console.log(' -l, --languages\t' + ' ' + 'List all languages.' + '\t\t' + '[boolean]\r')
console.log('\t--help\t\t ' + 'Show help.' + '\t\t\t' + '[boolean]\n')
}
打电话进来index.js:
if(yargs.argv._[0] == null){
utils.showHelp();
return;
}
5. 编写一个函数utils.js来显示所有支持的语言:
module.exports = { showAll: showAll, showHelp: showHelp, parseSentence: parseSentence};
function showAll(){
console.log(chalk.magenta.bold("\nLanguage Name\t\tISO-639-1 Code\n"))
for(let [key, value] of languages) {
console.log(key + "\\t\\t" + value + "\\n")
}
}
let languages = new Map();
languages.set('afrikaans', 'af')
languages.set('albanian', 'sq')
languages.set('amharic', 'am')
languages.set('arabic', 'ar')
languages.set('armenian', 'hy')
languages.set('azerbaijani', 'az')
languages.set('basque', 'eu')
languages.set('belarusian', 'be')
languages.set('bengali', 'bn')
languages.set('bosnian', 'bs')
languages.set('bulgarian', 'bg')
languages.set('catalan', 'ca')
languages.set('cebuano', 'ceb')
languages.set('chinese', 'zh')
languages.set('corsican', 'co')
languages.set('croatian', 'hr')
languages.set('czech', 'cs')
languages.set('danish', 'da')
languages.set('dutch', 'nl')
languages.set('english', 'en')
languages.set('esperanto', 'eo')
languages.set('estonian', 'et')
languages.set('finnish', 'fi')
languages.set('french', 'fr')
languages.set('frisian', 'fy')
languages.set('galician', 'gl')
languages.set('georgian', 'ka')
languages.set('german', 'de')
languages.set('greek', 'el')
languages.set('gujarati', 'gu')
languages.set('haitian creole', 'ht')
languages.set('hausa', 'ha')
languages.set('hawaiian', 'haw') // (iso-639-2)
languages.set('hebrew', 'he') //or iw
languages.set('hindi', 'hi')
languages.set('hmong', 'hmn') //(iso-639-2)
languages.set('hungarian', 'hu')
languages.set('icelandic', 'is')
languages.set('igbo', 'ig')
languages.set('indonesian', 'id')
languages.set('irish', 'ga')
languages.set('italian', 'it')
languages.set('japanese', 'ja')
languages.set('javanese', 'jv')
languages.set('kannada', 'kn')
languages.set('kazakh', 'kk')
languages.set('khmer', 'km')
languages.set('kinyarwanda', 'rw')
languages.set('korean', 'ko')
languages.set('kurdish', 'ku')
languages.set('kyrgyz', 'ky')
languages.set('lao', 'lo')
languages.set('latin', 'la')
languages.set('latvian', 'lv')
languages.set('lithuanian', 'lt')
languages.set('luxembourgish', 'lb')
languages.set('macedonian', 'mk')
languages.set('malagasy', 'mg')
languages.set('malay', 'ms')
languages.set('malayalam', 'ml')
languages.set('maltese', 'mt')
languages.set('maori', 'mi')
languages.set('marathi', 'mr')
languages.set('mongolian', 'mn')
languages.set('burmese', 'my')
languages.set('nepali', 'ne')
languages.set('norwegian', 'no')
languages.set('nyanja', 'ny')
languages.set('odia', 'or')
languages.set('pashto', 'ps')
languages.set('persian', 'fa')
languages.set('polish', 'pl')
languages.set('portuguese', 'pt')
languages.set('punjabi', 'pa')
languages.set('romanian', 'ro')
languages.set('russian', 'ru')
languages.set('samoan', 'sm')
languages.set('scots', 'gd')//gd gaelic
languages.set('serbian', 'sr')
languages.set('sesotho', 'st')
languages.set('shona', 'sn')
languages.set('sindhi', 'sd')
languages.set('sinhalese', 'si')
languages.set('slovak', 'sk')
languages.set('slovenian', 'sl')
languages.set('somali', 'so')
languages.set('spanish', 'es')
languages.set('sundanese', 'su')
languages.set('swahili', 'sw')
languages.set('swedish', 'sv')
languages.set('tagalog', 'tl')
languages.set('tajik', 'tg')
languages.set('tamil', 'ta')
languages.set('tatar', 'tt')
languages.set('telugu', 'te')
languages.set('thai', 'th')
languages.set('turkish', 'tr')
languages.set('turkmen', 'tk')
languages.set('ukrainian', 'uk')
languages.set('urdu', 'ur')
languages.set('uyghur', 'ug')
languages.set('uzbek', 'uz')
languages.set('vietnamese', 'vi')
languages.set('welsh', 'cy')
languages.set('xhosa', 'xh')
languages.set('yiddish', 'yi')
languages.set('yoruba', 'yo')
languages.set('zulu', 'zu')
这里我创建了一个哈希表,将所有语言名称映射到它们的 ISO-639-1 代码。这样做有两个目的:首先,它有助于在需要时显示所有语言;其次,API 只接受语言代码,所以即使用户输入的是语言名称,我们也可以在将其传递给 API 之前将其替换为语言代码。是不是很巧妙?🤫由于我们使用了哈希表,所以这种替换操作的时间复杂度为常数。
如果or标志为真,则调用该showAll()函数:index.js-l-languages
if(yargs.argv.l == true || yargs.argv.languages == true){
utils.showAll();
return;
}
6. 现在编写函数来执行我们在前面讨论过的肮脏勾当utils.js:
module.exports = { parseLanguage: parseLanguage, showAll: showAll, showHelp: showHelp, parseSentence: parseSent
ence };
function parseLanguage (language) {
if(language.length == 2){
return language;
}
if(languages.has(language)){
return languages.get(language)
}
else {
console.error("Language not supported!")
return; //returning null if the language is unsupported.
}
};
将语言转换为小写,然后调用该函数。index.js
if(yargs.argv._[0])
var language = yargs.argv._[0].toLowerCase(); // stores the language.
//parsing the language specified to the ISO-639-1 code.
language = utils.parseLanguage(language);
7. 现在检查句子是否为空,如果不是,则将其发送到 API:
在代码顶部包含 API index.js:
const translate = require('[@vitalets/google-translate-api](http://twitter.com/vitalets/google-translate-api)')
;if(sentence == ""){
console.error("\nThe entered sentence is like John Cena, I can't see it!\n")
console.log("Enter tran --help to get started.\n")
return;
}translate(sentence, {to: language}).then(res => {console.log("\n" + "\n" + res.text + "\n" + "\n";}).catch
(err => {
console.error(err);
});
你的命令行界面现在已经完成了!你还可以用方框和颜色来装饰输出和错误信息,我们可以使用boxen和chalk来实现这一点。
使用粉笔和粉笔进行美化
我们可以使用粉笔和方框为终端着色,并使用boxen来装饰我们的输出。
- 安装粉笔和方框。
npm install chalk boxen
2. 将它们包含在你的index.js和utils.js
const chalk = require('chalk');
const boxen = require('boxen');
3. 为使用常量添加颜色。
const usage = chalk.hex('#83aaff')("\\nUsage: tran <lang\_name> sentence to be translated");
4. 使用漂亮的方框显示输出结果。
translate(sentence, {to: language}).then(res => {console.log("\n" + boxen(chalk.green("\n" + res.text + "\n"
), {padding: 1, borderColor: 'green', dimBorder: true}) + "\n");}).catch(err => {
console.error(err);
});
欢迎浏览两种套餐,并根据您的喜好进行个性化定制。:)
啊,是的
希望你在学习如何构建自己的、完全可移植的命令行界面 (CLI) 的过程中玩得开心 :) 因为我玩得很开心。
祝您编程愉快!
完整的代码可以在以下网址找到:https://github.com/RushanKhan1/termTranslate
也许可以在Github上fork我:)
在领英上与我联系。
编辑:这篇文章现在也出现在Hackernoon上了。
文章来源:https://dev.to/rushankhan1/build-a-cli-with-node-js-4jbi


