欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nodejs cli_使用NodeJS构建CLI应用程序

程序员文章站 2022-07-14 09:26:33
...

nodejs cli

As a developer, chances are you spend most of your time in your terminal, typing in commands to help you get around some tasks.

作为开发人员,您很可能会花费大部分时间在终端上,输入命​​令来帮助您解决一些任务。

Some of these commands come built into your Operating System, while some of them you install through some third party helper such as npm, or brew, or even downloading a binary and adding it to your $PATH.

其中一些命令内置在您的操作系统中,而某些命令则是通过第三方帮助程序(例如npm或brew)安装的,甚至下载了二进制文件并将其添加到$PATH

A good example of commonly used applications include npm, eslint, typescript, and project generators, such as Angular CLI, Vue CLI or Create React App.

常用应用程序的一个很好的例子包括npm,eslint,打字稿和项目生成器,例如Angular CLIVue CLICreate React App

舍邦 ( Shebang )

This is the wikipedia definition:

这是*的定义

In computing, a shebang is the character sequence consisting of the characters number sign and exclamation mark (#!) at the beginning of a script.

在计算中,shebang是由脚本开头的字符数字符号和感叹号(#!)组成的字符序列。

Whenever you look at any scripting file, you'll see characters such as the ones below in the beginning of the file.

每当您查看任何脚本文件时,都将在文件开头看到以下字符,例如。

#!/usr/bin/env sh

#!/usr/bin/env python -c

They serve as a way for your operating system program loader will help parse the correct interpreter for your executable file. This only works in Unix Systems though.

它们用作您的操作系统程序加载器的一种方式,有助于您为可执行文件解析正确的解释器。 不过,这仅适用于Unix系统。

NodeJS too has it's own supported shebang characters. Make sure you have NodeJS installed, and create a file called logger with the following content.

NodeJS也有它自己支持的shebang字符。 确保已安装NodeJS,并创建一个包含以下内容的名为logger的文件。

#!/usr/bin/env node

console.log("I am a logger")

The first line tells the program loader to parse this file with NodeJS. The rest of the file is just normal JavaScript.

第一行告诉程序加载器使用NodeJS解析此文件。 文件的其余部分只是普通JavaScript。

You can try and run the file by typing this in your terminal. You'll get a permission denied for execution.

您可以通过在终端中键入此文件来尝试运行该文件。 您将获得拒绝执行的权限。

./logger
zsh: permission denied: ./logger

You need to give the file execution permissions. You can do that with.

您需要授予文件执行权限。 您可以这样做。

chmod +x logger
./logger
I am a logger

You'll see the file log correctly.

您会正确看到文件日志。

This could also easily be achieved if we did node logger. But we want to have our own command, and not need to use node ... to run it.

如果我们执行node logger这也很容易实现。 但是我们希望拥有自己的命令,而无需使用node ...来运行它。

我们将建立什么 ( What we'll build )

Now that you have a teaser, we're going to look at two example applications that we can build to learn more about CLI apps.

现在您已经有了一个预告片,我们将看两个可以构建的示例应用程序,以了解有关CLI应用程序的更多信息。

  1. Quote Of The Day (qod) cli tool, that retrieves quotes of the day from https://quotes.rest/qod.

    每日报价(qod)cli工具,可从https://quotes.rest/qod检索当天的报价。
  2. A Tiny To Do List that uses JSON to save data.

    使用JSON保存数据的微型任务清单。

每日行情 ( Quote of the Day )

Let's create a directory and call it qod. And inside, instantiate a NodeJs app.

让我们创建一个目录并将其命名为qod。 在内部,实例化NodeJs应用程序。

mkdir qod
cd qod
touch qod
npm init -y

Next, we know we need to make requests to the quotes server, so we could use existing libraries to do just this. We'll use axios

接下来,我们知道我们需要向报价服务器发出请求,因此我们可以使用现有的库来做到这一点。 我们将使用axios

npm install --save axios

We'll also add a chalk, a library to help us print color in the terminal.

我们还将添加一个粉笔 ,一个库来帮助我们在终端上打印颜色。

npm install --save chalk

We then write the logic needed to retrieve these quotes. Edit the qod file.

然后,我们编写检索这些引号所需的逻辑。 编辑qod文件。

#!/usr/bin/env node

const axios = require('axios')
const chalk = require('chalk');

const url = "https://quotes.rest/qod"

// make a get request to the url
axios({
  method: 'get',
  url: url,
  headers: { 'Accept': 'application/json' }, // this api needs this header set for the request
}).then(res => {
  const quote = res.data.contents.quotes[0].quote
  const author = res.data.contents.quotes[0].author
  const log = chalk.green(`${quote} - ${author}`) // we use chalk to set the color green on successful response
  console.log(log)
}).catch(err => {
  const log = chalk.red(err) // we set the color red here for errors.
  console.log(log)
})

The above is normal NodeJS code, and all we are doing is calling an API endpoint. Change the file permissions with

上面是普通的NodeJS代码,我们要做的只是调用API端点。 使用更改文件权限

chmod +x qod

Then run the application.

然后运行该应用程序。

./qod

The best way to not feel hopeless is to get up and do something. Don’t wait for good things to happen to you. If you go out and make some good things happen, you will fill the world with hope, you will fill yourself with hope. - Barack Obama

It should appear green, as we stated with the color.

nodejs cli_使用NodeJS构建CLI应用程序

如我们用颜色所述,它应该显示为绿色。

This is roughly the same as our first logger, but the point was to show that we can use existing libraries, and still run our apps the same way.

这与我们的第一个记录器大致相同,但是重点是表明我们可以使用现有的库,并且仍然以相同的方式运行我们的应用程序。

微小的待办事项清单 ( A Tiny To Do List )

This will be a bit more complex, as it will involve data storage, and retrieval. Here's what we're trying to achieve.

这将更加复杂,因为它将涉及数据存储和检索。 这是我们正在努力实现的目标。

  1. We need to have a command called todo

    我们需要有一个名为todo的命令
  2. The command will take in four arguments. new, get, complete, and help. So the available commands will be

    该命令将接受四个参数。 newgetcompletehelp 。 所以可用的命令将是
./todo new // create a new todo
./todo get // get a list of all your todos
./todo complete // complete a todo item.
./todohelp // print the help text

Seems straight forward.

似乎直截了当。

Let's create a directory called todo, and instantiate a nodejs app

让我们创建一个名为todo的目录,并实例化一个nodejs应用

mkdir todo
cd todo
touch todo
npm install -y

TIP - If you do a lot of creating directories and getting into the directories, you can save yourself some time by adding a function like this to your bash or zsh file.

提示 -如果您要做很​​多创建目录并进入目录的操作,则可以通过在bash或zsh文件中添加类似的功能来节省一些时间。

# Create a new directory and enter it
function mkd() {
    mkdir -p "aaa@qq.com" && cd "$_";
}

Then, all you need to do is type in mkd directory_name, and you'll be in there.

然后,您需要做的就是输入mkd directory_name ,您将在那里。

We'll also install chalk again, so that we can log with colors.

我们还将再次安装粉笔 ,以便我们可以记录颜色。

npm install --save chalk

The first thing we're going to do is make sure we have these commands available. To get the commands working, we'll use NodeJs' process/argv which returns a string array of command line arguments

我们要做的第一件事是确保我们有这些命令可用。 为了使命令生效,我们将使用NodeJs的process / argv ,它返回命令行参数的字符串数组

The process.argv property returns an array containing the command line arguments passed when the Node.js process was launched

process.argv属性返回一个数组,其中包含启动Node.js进程时传递的命令行参数

Add this to the todo file.

将此添加到待办事项文件。

#!/usr/bin/env node

console.log(process.argv)

Give the file executable permissions, and then run it with a new command.

授予文件可执行权限,然后使用新命令运行它。

chmod +x ./todo
./todo new

You're going to get this output.

您将获得此输出。

[ '/Users/ganga/.nvm/versions/node/v8.11.2/bin/node',
  '/Users/ganga/Dev/scotch/todo/todo',
  'new' ]

Notice that the first two strings in the array are the interpreter and the file full path. In this case, these are my absolute path names. Then, the rest of the array contains whatever we passed, in this case it's new.

请注意,数组中的前两个字符串是解释器和文件完整路径。 在这种情况下,这些是我的绝对路径名。 然后,数组的其余部分包含我们传递的所有内容,在这种情况下,它是新的。

To be safe, let's restrict these, so that we can only accept the correct number of arguments, which is one, and they can only be new, get and complete.

为了安全起见,让我们限制它们,以便我们只能接受正确数量的参数,即一个,并且它们只能是newgetcomplete

Modify the todo file like shown below.

修改待办事项文件,如下所示。

#!/usr/bin/env node

const chalk = require('chalk')
const args = process.argv

// usage represents the help guide
const usage = function() {
  const usageText = `
  todo helps you manage you todo tasks.

  usage:
    todo <command>

    commands can be:

    new:      used to create a new todo
    get:      used to retrieve your todos
    complete: used to mark a todo as complete
    help:     used to print the usage guide
  `

  console.log(usageText)
}

// used to log errors to the console in red color
function errorLog(error) {
  const eLog = chalk.red(error)
  console.log(eLog)
}

// we make sure the length of the arguments is exactly three
if (args.length > 3) {
  errorLog(`only one argument can be accepted`)
  usage()
}

We've first assigned the command line arguments to a variable, and then we check at the bottom that the length is not greater than three.

我们首先将命令行参数分配给了一个变量,然后在底部检查该长度不大于3。

We've also added a usage string, that will print what the command line app expects. Run the app with wrong parameters like below.

我们还添加了一个usage字符串,该字符串将打印命令行应用程序期望的内容。 使用错误的参数运行应用,如下所示。

./todo new app
only one argument can be accepted

todo helps you manage you todo tasks.

usage:
  todo <command>

  commands can be:

  new:      used to create a new todo
  get:      used to retrieve your todos
  complete: used to mark a todo as complete
  help:     used to print the usage guide

If you run it with one parameter, it will not print anything, which means the code passes.

如果使用一个参数运行它,它将不会打印任何内容,这意味着代码可以通过。

Next, we need to make sure only the four commands are expected, and everything else will be printed as invalid.

接下来,我们需要确保仅期望使用这四个命令,其他所有命令都将被打印为无效命令。

Add a list of the commands at the top of the code.

在代码顶部添加命令列表。

//....
const commands = ['new', 'get', 'complete', 'help']
//....

And then check with the passed in command after we've checked the length.

检查完长度后,再使用传递的命令进行检查。

//...
if (commands.indexOf(args[2]) == -1) {
  errorLog('invalid command passed')
  usage()
}
//...

Now, if we run the app with an invalid command, we get this.

现在,如果使用无效命令运行该应用程序,则会得到此信息。

./todo ne
invalid command passed

  todo helps you manage you todo tasks.

  usage:
    todo <command>

    commands can be:

    new:      used to create a new todo
    get:      used to retrieve your todos
    complete: used to mark a todo as complete
    help:     used to print the usage guide

We'll cover the rest of the logic in separate topics. For now, you can combine the argument checks into a single function as an exercises.

我们将在单独的主题中介绍其余的逻辑。 现在,您可以将参数检查合并为一个函数作为练习。

Todo App:帮助命令 ( Todo App: Help Command )

This is the most straight forward part of the app. All we need to do is call the usage function. Let's add this to the todo file.

这是应用程序中最直接的部分。 我们需要做的就是调用usage函数。 让我们将其添加到待办事项文件中。

//...
switch(args[2]) {
  case 'help':
    usage()
    break
  case 'new':
    break
  case 'get':
    break
  case 'complete':
    break
  default:
    errorLog('invalid command passed')
    usage()
}
//...

We have a switch statement which will call functions based on what command has been called. If you look closely, you'll notice the help case just calls the usage function.

我们有一个switch语句,它将根据调用的命令来调用函数。 如果仔细观察,您会发现help案例只是调用了用法功能。

While writing this I also noticed that the default case can serve as an error handler for invalid commands, so I deleted the check we did earlier, and the commands array. Thought I'd leave it there because refactoring is always fun.

在撰写本文时,我还注意到default情况可以用作无效命令的错误处理程序,因此我删除了我们之前所做的检查以及命令数组。 以为我会留在那里,因为重构总是很有趣。

Todo App:新命令 ( Todo App: New Command )

The new command will create a new todo item, and save it in a json file. We will use a file db, to help us with the storage files.

新命令将创建一个新的待办事项,并将其保存在json文件中。 我们将使用文件db来帮助我们存储文件。

The library we will use is lowdb. We could easily write functions to read and write to a json file, if we wanted to.

我们将使用的库是lowdb 。 如果愿意,我们可以轻松地编写函数以读取和写入json文件。

Install lowdb

安装lowdb

npm install --save lowdb

Let's add [readline](https://nodejs.org/api/readline.html) and lowdb dependencies, to help us with storing data. The lowdb code is standard from their github page.

让我们添加[readline](https://nodejs.org/api/readline.html)lowdb依赖项,以帮助我们存储数据。 lowdb代码是其github页面上的标准代码。

//...
const rl = require('readline');

const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

const adapter = new FileSync('db.json')
const db = low(adapter)

// Set some defaults (required if your JSON file is empty)
db.defaults({ todos: []}).write()
//...

Next, we'll add a function to prompt the user to input data.

接下来,我们将添加一个函数来提示用户输入数据。

//...
function prompt(question) {
  const r = rl.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
  });
  return new Promise((resolve, error) => {
    r.question(question, answer => {
      r.close()
      resolve(answer)
    });
  })
}
//...

Here we are using the readline library to create an interface that will help us prompt a user to and then read the output.

在这里,我们使用readline库创建一个界面,该界面将帮助我们提示用户输入然后读取输出。

Next, we need to add a function that will be called when a user types in the new command

接下来,我们需要添加一个在用户键入new命令时将调用的函数

//...
function newTodo() {
  const q = chalk.blue('Type in your todo\n')
  prompt(q).then(todo => {
    console.log(todo)
  })
}
//...

We're using chalk to get the blue color for the prompt. And then we will log the result.

我们正在使用粉笔来获取提示的蓝色。 然后我们将记录结果。

Lastly, call the function in the new case.

最后,在new情况下调用该函数。

// ...
switch(args[2]) {
  //...
  case 'new':
    newTodo()
    break
    // ...
}
// ...

When you run the app now with the new command, you will be prompted to add in a todo. Type and press enter.

现在,使用新命令运行应用程序时,系统将提示您添加待办事项。 输入并按Enter。

./todo new
Type in your todo
This my todo  aaaaaaw yeah
This my todo  aaaaaaw yeah

You should see something similar to this.

nodejs cli_使用NodeJS构建CLI应用程序

您应该会看到类似的内容。

Notice also, that a db.json file has been created in your file system, and it has a todos property.

还要注意,已经在文件系统中创建了一个db.json文件,它具有todos属性。

Next, let's add in the logic for adding a todo. Modify the newTodo function.

接下来,让我们添加添加待办事项的逻辑。 修改newTodo函数。

//...
function newTodo() {
  const q = chalk.blue('Type in your todo\n')
  prompt(q).then(todo => {
    // add todo
    db.get('todos')
      .push({
      title: todo,
      complete: false
      })
      .write()
  })
}
//...

Run the code again.

再次运行代码。

./todo new
Type in your todo
Take a Scotch course

If you look at your db.json, you'll see the todo added. Add two more, so that we can retrieve them in the next get command. Here's mine.

如果查看db.json ,则会看到已添加的待办事项。 再添加两个,以便我们可以在下一个get命令中检索它们。 这是我的。

{
  "todos": [
    {
      "title": "Take a Scotch course",
      "complete": false
    },
    {
      "title": "Travel the world",
      "complete": false
    },
    {
      "title": "Rewatch Avengers",
      "complete": false
    }
  ]
}

Todo App:获取命令 ( Todo App: Get Command )

With knowledge from the new command, the get command should be simple to write.

有了新命令的知识,get命令应该易于编写。

Create a function that will retrieve the todos.

创建一个将检索待办事项的函数。

//...
function getTodos() {
  const todos = db.get('todos').value()
  let index = 1;
  todos.forEach(todo => {
    const todoText = `${index++}. ${todo.title}`
    console.log(todoText)
  })
}
//...

// switch statements
switch(args[2]) {
    //...
    case 'get':
        getTodos()
        break
    //...
}
//....

Running the app now, should give us this

现在运行应用程序,应该给我们这个

./todo get
1. Take a Scotch course
2. Travel the world
3. Rewatch Avengers

You can make the color fancy/green by using chalk.green

您可以使用chalk.green将颜色设置为花哨/绿色。

TodoApp:完整命令 ( TodoApp: Complete Command )

The complete command is a little bit complicated.

完整的命令有点复杂。

We can do it in two ways.

我们可以通过两种方式做到这一点。

  1. Whenever a user types in ./todo complete, we could list all the todos, and the ask them to type in the number/key for the todo to mark as complete.

    每当用户键入./todo complete ,我们都可以列出所有待办事项,并要求他们键入数字/**以将待办事项标记为完成。
  2. We can add in another parameter, so that a user can type in ./todo get, and then choose the task to mark as complete with a parameter, such as ./todo complete 1.

    我们可以添加另一个参数,以便用户可以输入./todo get ,然后选择带有参数标记为完成的任务,例如./todo complete 1

Since we covered how option 1 is done in the new command, we'll look at option 2.

由于我们在new命令中介绍了选项1的完成方式,因此我们将介绍选项2。

A good problem introduced here is the ./todo complete 1, will surely surpass our validity check for the number of commands given. We therefore first need to handle this. Change the function that checks the length of the arguments with to this.

这里介绍的一个好问题是./todo complete 1 ,它肯定会超过我们对给定命令数的有效性检查。 因此,我们首先需要处理这个问题。 更改用于检查参数长度的函数。

//...
// we make sure the length of the arguments is exactly three
if (args.length > 3 && args[2] != 'complete') {
  errorLog('only one argument can be accepted')
  usage()
  return
}
///...

Above approach is using the truth tables, where TRUE && FALSE will equal FALSE, and the code will be skipped when complete is passed.

上面的方法使用的是真值表,其中TRUE && FALSE将等于FALSE ,并且在通过complete时将跳过代码。

We'll then grab the value of the new argument and make the value of todo as completed like so

然后,我们将获取新参数的值,并使todo的值像这样完成

//...
function completeTodo() {
  // check that length
  if (args.length != 4) {
    errorLog("invalid number of arguments passed for complete command")
    return
  }

  let n = Number(args[3])
  // check if the value is a number
  if (isNaN(n)) {
    errorLog("please provide a valid number for complete command")
    return
  }

  // check if correct length of values has been passed
  let todosLength = db.get('todos').value().length
  if (n > todosLength) {
    errorLog("invalid number passed for complete command.")
    return
  }

  // update the todo item marked as complete
  db.set(`todos[${n-1}].complete`, true).write()
}
//...

I've made comments on each part of the code above. Please read through.

我已经对上面代码的每个部分进行了注释。 请通读。

Make sure to update the switch statements.

确保更新switch语句。

//...
case 'complete':
    completeTodo()
    break
//...

When you run this with ./todo complete 2, you'll notice your db.json has changed to this, marking the second task as complete.

当使用./todo complete 2运行此./todo complete 2 ,您会注意到db.json已更改为此,将第二项任务标记为完成。

{
  "todos": [
    {
      "title": "Take a Scotch course",
      "complete": false
    },
    {
      "title": "Travel the world",
      "complete": true
    },
    {
      "title": "Rewatch Avengers",
      "complete": false
    }
  ]
}

The last thing we need to do, is change ./todo get to only show tasks that are done. We'll use emojis for this.

我们要做的最后一件事是更改./todo get仅显示已完成的任务。 我们将为此使用表情符号。

//...
function getTodos() {
  const todos = db.get('todos').value()
  let index = 1;
  todos.forEach(todo => {
    let todoText = `${index++}. ${todo.title}`
    if (todo.complete) {
      todoText += ' ✔ ️' // add a check mark
    }
    console.log(chalk.strikethrough(todoText))
  })
  return
}
//...

When you now type in the ./todo get you'll see this.

现在,输入./todo get您将看到此内容。

./todo get
1. Take a Scotch course
2. Travel the world ✔ ️
3. Rewatch Avengers

I know, it could be improved, but you get the idea.

我知道,可以改进,但是您知道了。

That's it for now.

现在就这样。

图书馆 ( Libraries )

This is JavaScript, and of course some brilliant person out there has thought of all these and written helper libraries. The focus for this article was to look at how CLI applications are built with vanilla nodejs, but when working in the real world, it would be more productive to use libraries.

这是JavaScript,当然还有一些才华横溢的人想到了所有这些,并编写了帮助程序库。 本文的重点是研究如何使用普通的nodejs构建CLI应用程序,但是在现实世界中工作时,使用库会更有效率。

Here's a list of helpful libraries to help you write awesome CLI applications, which you can publish to npm.

以下是有用的库列表,可帮助您编写出色的CLI应用程序,并将其发布到npm。

  1. vopral - fully featured interactive CLI framework

    vopral-功能齐全的交互式CLI框架
  2. meow - CLI helper library

    -CLI帮助程序库
  3. commanderjs - CLI library

    commanderjs -CLI库
  4. minimist - for arguments parsing

    极简主义者 -用于参数解析
  5. yargs - arguments parsing

    yargs-参数解析

And not to mention libraries like chalk that helped us with colors.

更不用说像粉笔这样的库了,可以帮助我们获得色彩。

回顾 ( Recap )

I hope you got a thing or two about building CLI applications with NodeJS.

希望您对使用NodeJS构建CLI应用程序了解一两件事。

A common practice I did not mention is that, once your app is working fine, most libraries put the file into a bin folder, and this way, npm knows how to work with executables. This is not a requirement, but regardless of where you place the executable, you should update package.json bin property.

我没有提到的一种常见做法是,一旦您的应用正常运行,大多数库会将文件放入bin文件夹,这样,npm便知道如何使用可执行文件。 这不是必需的,但是无论您将可执行文件放置在何处,都应更新package.json bin属性。

As an excercise, try to add a Delete command to the CLI.

作为练习,请尝试将Delete命令添加到CLI。

Happy Coding.

编码愉快。

翻译自: https://scotch.io/tutorials/building-cli-applications-with-nodejs

nodejs cli