前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >如何用 Node.js 实现一个微型 CLI

如何用 Node.js 实现一个微型 CLI

原创
作者头像
林小帅
修改于 2020-04-08 01:42:33
修改于 2020-04-08 01:42:33
1K00
代码可运行
举报
文章被收录于专栏:林小帅的专栏林小帅的专栏
运行总次数:0
代码可运行

什么是 CLI

命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。

实现一个微型 CLI Demo

Node.js 官方示例:微型 CLI

readline.createInterface

首先创建一个接口的实例,用于处理流信息,例:输入、输出、提示字符串、自动补全、历史记录等。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const rli = readline.createInterface({
  // 要监听的可读流。此选项是必需的。
  input: process.stdin,
  // 将逐行读取数据写入的可写流。
  output: process.stdout
  // prompt // 要使用的提示字符串。默认值: '> '。
  // historySize  //保留的最大历史记录行数。 要禁用历史记录,请将此值设置为 0。
  // completer // 用于 Tab 自动补全的可选函数。
});

创建完成后一个基本的 CLI 就已经有了。但是,仅仅是拥有了能够处理输入输出等流信息的能力而已。但是此时只能够输入,不能够输出,如果需要输出能力则需要进一步进行完善。

on line

如果需要根据输入流的信息来反馈一些信息显示(输出流),则需要使用返回的实例来监听输入流的内容,然后进行相应的处理,再返回流信息用于输出显示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// on 函数是为需要监听的指令
// line 是能接受到当前命令行中的输入流信息,通过函数回调的方式返回处理过的字符串。
rli.on('line', line => {
  const line2str = line.trim()

  if (line2str === '嗨') { // 在命令行中输入 “嗨” 并回车,CLI 则会输出一个 “Hi!”
    console.log('Hi!')
  }
  if (line2str === '你好吗') { // 没错是个英语老方了
    console.log('I\'m fine, thank you, and you?')
  }
})

通过监听输入的行信息加以处理的逻辑,最后返回一个输出信息就实现了简单的输入输出互动效果。

至此,一个大概的互动式的 CLI 核心部分就已经完成了。

启动 CLI

如需使用 npm 命令的话则需要在 package.json 中 scripts 里加入你的命令名称和脚本位置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"scripts": {
  // 其他命令……
  "cli": "node build/index.js" // 新增的 npm 命令,通过 npm 命令可以启动 cli 脚本。
},
// 这时候就可以通过 npm run cli 命令运行 CLI 了。

脚本位置的话不能直接使用 ./filePath 或 /filePath 这样的路径会无法识别。需要使用 node filePath/xxx.js,这样 node 就会将脚本位置定位至当前项目开始寻找。

退出 CLI

当所有输入完成后或者达到特定条件就可以退出 CLI 模式了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (line2str === '再见') {
  console.log('Bye!')
  process.exit(0); // 退出 CLI 模式
}

通过 process.exit 就可以实现退出当前的 CLI 模式返回到命令行中。

process 在接下来的内容中还会使用到,但是可以先看以下 NodeJs 对他的定义:

process 对象是一个全局变量,它提供有关当前 Node.js 进程的信息并对其进行控制。作为一个全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()。 它也可以使用 require() 显式地访问

实现一个简单的问答式 CLI

什么情况会需要用到 CLI 功能呢?我们可以假设一个这样的场景:你在写 Vue 的时候是不是会重复的新建 xxx.vue 文件呢?这时候就可以使用 CLI 生成了。当然你会说:“我可以 copy & paste 啊!”。你当然可以,但是每次 copy & paste 完了你又要把里面的代码手动删除掉,不觉得很麻烦吗?这时候一条命令加上简单的输入就可以生成干净的 xxx.vue 模板,甚至附带的 xxx.js、xxx.css 也可以一并生成岂不是更有效率?

“我就喜欢 copy & paste!”。好了兄弟,你坐下,当我没说。

下面我们继续来分析一下实现这样的一个 CLI 需要考虑哪些因素

问题

“一个问答式的 CLI 当然需要问题啦,这不是废话嘛。”

话是没错,但是问题如何问当然也有一点点的讲究。那就是问题一定会是封闭式的问题,封闭式问题因为提得比较具体且圈定的范围固定,也就要求了回答者必须在这个范围内给予明确的回答。

如果是开放式问题的话,那么就会导致回答者(使用者)就有很大的自我发挥空间。因为问题过于开放笼统的话,那么答案就没有固定范围了,这时候你的问题也就是无效提问了。

答案

这个不必过多解释,既然是封闭式问题那就只有一些固定的选项,以及再照顾一下默认选项即可。

例如:代码文件类型?【JS/ts/vue/css

其中大写/加粗一般为默认类型,即回车即选择。

生成路径

所有问题都回答、选择完成后文件的生成路径,一般来说必须默认一个生成路径以及提供自定义填写符合文件夹规则的路径。当然也可以将其做成半开闭形式,即有固定几种选择也可以自定义填写符合文件夹规则的路径。

例如:指定文件路径?【SRC/components/assets/yourpath】

生成的模板

通过答案得知需要生成的是哪种类型的文件或者是某一类文件或某一种文件组合生成多个文件。

反馈结果

当所有回答都完成时,需要及时反馈、显示一些重要的步骤或信息,让使用者直观的知道进程如何,以及最终结果。

上面将一些所考虑的因素都说完了,这里就开始进入代码的实际编码和设计部分了。这部分开始会以我自己的项目为例来说明。

问题 & 答案的设计

首先需要一个问题列表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 构建问题列表
const buildQuestion = () => {
  // 问题文字内容、提示、类型
  const questionText = [
    {question: '组件名称?', tips: '', type: '[template]'},
    {question: '指定文件夹路径?', tips: '(最大深度:4)', type: '[./views/]'},
    {question: '代码文件类型?', tips: '', type: '[JS/ts]'},
    {question: '样式表类型?', tips: '', type: '[CSS/less/sass/scss]'},
    // {question: '是否创建单独的Api文件?', tips: '', type: '[y/N]'} // 暂时未想好该如何处理 API 文件的构建和写入
  ];

  return questionText.map(item => {
    return { text: item.question, question: `\x1B[32m?\x1B[97m ${item.question}${item.tips}\x1B[32m${item.type}` }
  });
}

你可以设计你自己的问题列表来决定需要生成的是那些内容/代码。

以及一个无效回答的默认值和一个记录回答对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 无有效输入时使用的默认内容
const defAnswer = {
  fileName: 'template',
  filePath: './views/',
  codeType: 'js',
  cssType: 'css',
  fileApi: false,
};

// 记录问题的回答内容
const answer = {
  fileName: '',
  filePath: '',
  codeType: '',
  cssType: '',
  fileApi: false,
};

当用户输入了答案后我们就需要去检查这个答案是否符合规则或者有效:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 检查是否符合规则,并处理答案默认选项
const checkAnswer = (step, content) => {
  // if (step > 1) { content = content.toLowerCase() }
  switch (step) {
    case 0:
      return answer.fileName = /^[a-zA-Z]{1,20}$/g.test(content)
        ? content : defAnswer.fileName;
    case 1:
      return answer.filePath = path.join(
        findChatIndex(
          path.join(defAnswer.filePath, content), '\\', 3),
        answer.fileName);
    case 2:
      content = content.toLowerCase()
      return answer.codeType = /^js|ts$/ig.test(content)
        ? content : 'js';
    case 3:
      content = content.toLowerCase()
      return answer.cssType = /^css|less|sass|scss$/ig.test(content)
        ? content : 'css';
    case 4:
      if (/^y|Y|n|N$/ig.test(content)) {
        const tempYN = content.toLowerCase()
        answer.fileApi = tempYN === 'y' ? true : false
        return content
      } else {
        answer.fileApi = false
        return 'N'
      }
  };
};

处理路径

针对用户自定输入路径时的处理,以及还要考虑不同操作系统路径分割符不一致的情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 拼接路径
let findChatIndex = (str, chat, num) => {
  if (str.match(/\\/g).length <= num) return str;

  let chatIndex = str.indexOf(chat);
  for (let index = 0; index < num; index++) {
    let tempIndex = str.indexOf(chat, chatIndex + 1);
    if (tempIndex !== -1) {
      chatIndex = tempIndex
    }
  }
  return str.substr(0, chatIndex);
};

处理模板

这里我就不贴代码了,因为我是使用了字符串模板来作为模板的输出内容,因为方便且字符串模板可以保存格式(缩进和换行)

参考这里:template.js

到这就完了?不,到这只是完成了考虑因素的代码实现部分,还有一些是需要我们继续完善的,例如输入输出的处理,显示、反馈处理等

输入输出的设计

一般来说在进入一个独立的 CLI 模式之前会对控制台之前的内容进行一个简单的清理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
readline.cursorTo(process.stdout, 0, 0); // 光标位置 0,0 即第一行第一位
readline.clearScreenDown(process.stdout); // 清理屏幕内容

这一步,简单来说就有很好,清理之后没有其他无关信息。当然没有的话也无伤大雅,属于锦上添花的部分,看需要来吧。

然后就是开始初始化第一个问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 初始化第一个问题。
console.log(questionList[stepQuestion].question); // 问题一
// 设定输入内容样式
console.log('\x1B[36m'); // 控制台字符样式

当然你也可以写多一点东西,比如输出一段简介或者输出一些其他自己喜欢的内容。

接下来就是比较重要的问题和答案处理部分了,监听 line 输入自然是不用多说。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// on 函数是为需要监听的指令
// line 是能接受到当前命令行中的输入流信息,通过函数回调的方式返回处理过的字符串。
  const line2str = line.trim()

  // 将检查处理后的答案信息存储用于后续命令行内容输出
  let tempAnswer = checkAnswer(stepQuestion, line2str);

  // 将光标移入上一次步骤的位置,可以造成用户已经选择完成的效果。
  readline.cursorTo(process.stdout, 0, stepQuestion);
  // 清理之前的输入内容。
  readline.clearScreenDown(process.stdout);
  // 选择完成后输出选择后的结果信息。
  console.log(`\x1B[32m?\x1B[97m ${questionList[stepQuestion].text}\x1B[36m%s`, tempAnswer);

  // 重置控制台样式。
  console.log('\x1B[0m');

// 如果当问题的步骤小于问题的长度时,则问题步长 + 1。
  if (stepQuestion < lenQuestion) {
    stepQuestion++;
    // 输出下一个问题内容
    console.log(questionList[stepQuestion].question);
  } else { …… }

这里可以看出我使用的是记录步长的方式来处理什么时候开始下一个问题的提问与答案的记录。

之前也考虑过使用递归,但是最终实现起来处理提问与答案的记录稍微麻烦,当然你也可以尝试。

else 部分呢就是处理所有答案都回答完成的情况了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  else {
    tpl.bulidTpl(answer)
    .then(() => {
      // 否则可以认为已经选择完成
      // console.log('再见! %o', answer);
      console.log('再见!');
      console.log('\x1B[0m');
      process.exit(0);
    })
    .catch(err => {
      console.log(`\x1B[31m${err.message}`);
      console.log(`\x1B[31m${err.error}`);
      console.log('\x1B[0m');
      process.exit(0);
    });
  }

最后最后还是需要额外考虑一下意外触发 CLI 任务中断的情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rli.on('line', line => {})
.on('close', () => {
  console.log('\x1B[0m');
  console.log('【信息】您已中断模板创建任务,感谢您的使用再见!');
  process.exit(0);
});

能看到到这里呢也就说明了,这个 微型的问答式的 CLI 也就完成了。

最终效果
最终效果

最后

当然这个只是一个简单的 CLI 实现而已,关于这个 CLI 我自己也还有一些想法,因为这里面还是有一些可以改进和优化的地方,例如现在是只能生成 Vue 这一套单一的文件模板,哪能不能生成其他框架的文件模板呢?或者是可以通过配置文件的方式生成的是一整套项目结构呢?又或者是代码模板能不能使用代码的方式而不是字符串模板生成代码模板呢?

这些也都是我自己需要考虑和更深入学习了解的地方。

各位小伙伴可能也会有自己的想法可以创造很多有趣、好玩的 CLI。当然也祝各位小伙伴能够学到有用的知识,然后把这些知识转变成代码然后创造生产力工具为自己和公司、企业、社区增砖添瓦。

GitHubTemplate Build

版权声明:

本文版权属于作者 林小帅,未经授权不得转载及二次修改。

转载或合作请在下方留言及联系方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux 下从头再走 GTK+-3.0 (二)
  仅仅创建一个空白窗口是不够的,下面我们为创建的窗口添加一个按钮。 以 Hello,World!为例。 首先创建一个源文件:example2.c 内容如下。 #include <gtk/gtk.h> //点击按钮后被调用的函数,用于输出hello,world static void print_msg (GtkWidget *button , gpointer data) { printf("Hello , world!\n"); } static void activate (GtkApp
RainMark
2019/09/10
1.4K0
Linux 下从头再走 GTK+-3.0 (二)
Linux 下从头再走 GTK+-3.0 (五)
  实践中表明,纯粹利用 gtk 函数来创建 UI 是很繁琐的事,需要编写很多代码。怎样才能快速统一的建立 UI 布局呢?
RainMark
2019/09/10
1.1K0
Linux 下从头再走 GTK+-3.0 (五)
[linux][c/c++]代码片段02
 gcc `pkg-config --cflags gtk+-3.0` -o example-1 example-1.c `pkg-config --libs gtk+-3.0`
landv
2019/11/11
4970
[linux][c/c++]代码片段02
gtk3示例--buttonbox
#include <gtk/gtk.h> static void print_hello (GtkWidget *widget, gpointer data) {     g_print("你按的是确定键\n"); } static void print_cancel (GtkWidget *widget, gpointer data) {     g_print("你按的是取消键\n"); } static void print_help (GtkWidget *wid
py3study
2020/01/14
1K0
Linux 下从头再走 GTK+-3.0 (三)
  之前我们为窗口添加了一个按钮,接下来让这个按钮丰富一点。并给窗口加上图标。 首先创建 example3,c 的源文件。 #include <gtk/gtk.h> static void activate(GtkApplication *app , gpointer data); //从文件中加载一张图片 GdkPixbuf *create_pixbuf(const char *image_name); int main(int argc , char **argv) {
RainMark
2019/09/10
1.2K0
Linux 下从头再走 GTK+-3.0 (三)
Linux 下从头再走 GTK+-3.0 (一)
  原本由于项目需求在 Linux 下学习过一段时间的 GTK+2.0 图形开发,时隔一段时间,想真正深入学习一下 GTK 。
RainMark
2019/09/10
2.3K0
Linux 下从头再走 GTK+-3.0 (一)
GTK 菜单的创建详解
============================================================================== 创建菜单条
RainMark
2019/09/10
1.5K0
C语言写一个GTK的二维码图形界面生成器
其实这个程序并不能自己生成二维码,它只是调用qrencode来生成二维码,并显示出来。
geek2pm
2020/02/15
1.5K0
C语言写一个GTK的二维码图形界面生成器
如何在C语言中进行图形界面编程
在C语言中进行图形界面编程是一项非常有挑战性和有趣的任务。虽然C语言主要用于系统级编程和算法开发,但我们仍然可以使用一些库来实现简单的图形界面。在本文中,我将介绍一种在C语言中进行图形界面编程的方法。
用户10354340
2023/08/13
9810
Gtk 窗口,控件,设置(添加图片等)
让窗体尺寸不可调整,但是好像与全屏使用时有些问题,会自动地调整窗口的大小
RainMark
2019/09/10
2.3K0
在Ubuntu上搭建kindle gtk开发环境
某个角度上说,kindle很类似android,同样的Linux内核,同样的Java用户层。不过kindle更注重简单、节能、稳定。Amazon一向认为,功能过多会分散人们阅读时候的注意力。 Kindle底层的Linux比Android保持了更多的linux兼容性,可以使用GTK或者QT编写程序。QT适合编写大的、独占界面性的应用,比如多看就曾经发布过一个Kindle之上的版本,现在还有很多人用,可惜因为公司战略调整的原因,这个产品被废弃了。GTK及最基本的Linux应用更适合开发一些补丁性的小程序,来补
俺踏月色而来
2018/06/20
1.4K0
Linux桌面程序开发 | Study Python For Gtk3
背景: ​ 使用Linux系统已经有一段时间了,在管理系统是几乎都是使用命令行与内核交流的,使用虽多的就是Shell,其次就是python。这两天突然心血来潮,想到了Linux PC端桌面程序,在我个人的熟悉语言中呢,python比较适合,不过、开发Linux桌面我只是玩玩的。对于开发Linux桌面程序掌握Python的推荐使用Python Gtk3。 ​ 想玩出一个Linux基本桌面程序( 几乎没有业务逻辑 ),看完下面的( 重点是图片 | UI组件 ),大概就有一个底了! ---- 1、入门He
AlicFeng
2018/06/08
5.1K0
Linux 下从头再走 GTK+-3.0 (六)
  在 GTK3 中增加了一个 GtkApplicaton 类,便于我们处理多窗口程序,同时有了 GtkApplication 我们也更容易创建灵活,易用,界面美观的应用程序。
RainMark
2019/09/10
1.8K0
Linux 下从头再走 GTK+-3.0 (六)
Windows 7/Visual Studio2012下使用GTK
其实并没有去刻意的要使用这个库,并且所谓的跨平台的东西配置起来都不是那么的容易的。之所以要用这个东西是因为要编译libgpod的代码,网上的说明文件不少,但是代码迁移到windows还是不少的问题的。
obaby
2023/02/23
7390
PyQt 编程入门(三)
本例讲解计算器的编程。涉及到的内容有:单行文本框、多行文本浏览框和按钮的应用,布局(含垂向箱型布局以及栅格布局)和 事件(含按钮单击,Enter建按下事件)。程序的显示效果如下图。计算的历史信息会保存在文本浏览框中。代码如下:
用户6021899
2019/08/14
8100
PySide——Python图形化界面入门教程(三)
PySide——Python图形化界面入门教程(三)          ——使用内建新号和槽               ——Using Built-In Signals and Slots 上一个教程中,我们学习了如何创建和建立交互widgets,以及将他们布局的两种不同的方法。今天我们继续讨论Python/Qt应用响应用户触发的事件:信号和槽。 当用户执行一个动作——点击按钮,选择组合框的值,在文本框中打字——这个widget就会发出一个信号。这个信号自己什么都不做,它必须和槽连接起来才行。槽是一个接
ascii0x03
2018/04/12
1.9K0
gtk还有人用吗_iperf使用方法
GTK+(GIMP Toolkit)是一套源码以LGPL许可协议分发、跨平台的图形工具包。最初是为GIMP写的,已成为一个功能强大、设计灵活的一个通用图形库,是GNU/Linux下开发图形界面的应用程序的主流开发工具之一。并且,GTK+也有Windows版本和Mac OS X版。 GTK+ 是一种图形用户界面(GUI)工具包。也就是说,它是一个库(或者,实际上是若干个密切相关的库的集合),它支持创建基于 GUI 的应用程序。可以把 GTK+ 想像成一个工具包,从这个工具包中可以找到用来创建 GUI 的许多已经准备好的构造块。差不多已经 10 年过去了。今天,在 GTK+ 的最新稳定版本 —— 2.8 版上(3.0测试中),仍然在进行许多活动,同时,GIMP 无疑仍然是使用 GTK+ 的最著名的程序之一,不过它已经不是惟一的使用 GTK+ 的程序了。已经为 GTK+ 编写了成百上千的应用程序,而且至少有两个主要的桌面环境(Xfce 和 GNOME)用 GTK+ 为用户提供完整的工作环境。 GTK+虽然是用C语言写的,但是您可以使用你熟悉的语言来使用GTK+,因为GTK+已经被绑定到几乎所有流行的语言上,如:C++,PHP, Guile,Perl, Python, TOM, Ada95, Objective C, Free Pascal, and Eiffel。
全栈程序员站长
2022/11/01
1.3K0
gtk还有人用吗_iperf使用方法
Linux C编程——为eog image viewer增加坐标和像素颜色显示功能
好用的看图工具对做图片相关的算法验证很有帮助。但常常工具并没有我们需要的功能。今天我就分享一个工作中遇到的例子。
ExASIC
2020/07/15
1.5K0
Linux C编程——为eog image viewer增加坐标和像素颜色显示功能
全栈之前端 | 6.CSS3基础知识之网页几种布局方法学习(1)
描述: 前面相信大家已经跟着【WeiyiGeek】作者一起学习了CSS基础知识了,并且了解了盒子模型、以及元素的定位浮动方面的知识。现在我们在此基础上继续深入学习CSS布局,它是学习CSS之路上一个重点,是在进行前端开发时常常使用到的,所以说我们需要认真学习,若有不懂的地方可以在文章末尾,以及作者交流群【在公众号回复微信交流群】进行留言交流。
全栈工程师修炼指南
2023/10/31
6470
全栈之前端 | 6.CSS3基础知识之网页几种布局方法学习(1)
事件与信号
所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的,当然也有其他的事件产生方式,比如网络的连接,窗口管理器或者定时器等。调用应用的exec_()方法时,应用会进入主循环,主循环会监听和分发事件。
小飞侠xp
2018/12/24
1.2K0
相关推荐
Linux 下从头再走 GTK+-3.0 (二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文