头图

——————————☆☆☆——————————

Corresponding address of Node series:

——————————☆☆☆——————————

commander.js -a complete Node.js command line solution.

This article explains how to walk around the Node.js command line commander.js

I. Introduction

When a Node.js program is running, there will be many global variables stored in memory.

Among them, process used as a process object, it has a argv attribute, and the instructions can be viewed.

Let's just build a index.js example, the terminal executes the command: node index.js --name jsliang

index.js
console.log(process.argv);
/*
  [
    'C:\\Program Files\\nodejs\\node.exe',
    'F:\\jsliang\\index.js',
    '--name',
    'jsliang'
  ]
*/

Look at the printed data:

  • Node Location: C:\\Program Files\\nodejs\\node.exe
  • Current code path: F:\\jsliang\\index.js
  • Parameter 1: --name
  • Parameter 2: jsliang

Therefore, when we write the command line program, we only need to process.argv the third element of the array 060cdb9238e991 and the following parameters.

If it is not too troublesome, you can write a lot of judgment branches to do it.

However, if there is a ready-made one, why do I have to write it myself? If I can be lazy, I’ll be lazy~

Two commander.js

commander.js is a tj written by 060cdb9238e9e3, which is used to make Node command line programs easier:

Understand you, Chinese README is here: https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md

Let's start the operation:

  • Initialize package.json : npm init --yes

    • ! ! [Note] This step can be omitted if it is learned in the order of the Node series
    • ! ! [Note] The following code can create an test to play around
  • Installation package: npm i commander
  • Write instruction file:
index.js
const program = require('commander');

program
  .version('0.0.1')
  .description('小工具指令清单')
  .option('-s, --sort <path>', '排序功能', '')

program.parse(process.argv);
package.json (automatically generated)
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "commander": "^7.2.0"
  }
}

The logic of this code is:

  1. Reference commander
  2. Description commander the version parameters
  3. With commander control process.argv

Focus on the parameters in paragraph 2 of this code:

The parameters seem very irritating, but I don’t understand and don’t know how to use it.
  • version : version

    • Usage: .version('x.y.z')
  • description : description

    • Usage: .description('Widget instruction list')
  • option : Option

    • Usage: .option('-n, --name <name>', 'your name', 'jsliang')
    • The first parameter is the option definition, which can be connected | , , and ' '
    • The second parameter is the option description
    • The third parameter is the default value of the option parameter (optional)

So below we can view some information.

  • Execute the command: node index.js -h

Get the following results:

Usage: index [options]

小工具指令清单

Options:
  -V, --version           output the version number
  -s, --sort <path>       排序功能 (default: "")
  -h, --help              display help for command

So we have completed some small instructions, so how do we do it?

  • Sort: node index.js -s "jsliang"

Of course, this feels too weird, can it be npm run xxx in the same way npm run dev , 060cdb9238edb2?

Of course it is possible!

Three practice: file rearrangement function

Having said so much, let's do a little practice!


For a document library written in Markdown, if you don't set the document order, it will read the directory according to the system's rules:

  • 1. Article 1
  • 10. Article 10
  • 2. Article 2
  • ……

When there is too much content in a folder, we want to let users read it in our own order, so we can't find the system, so we need to write a small tool with Node, and read it in the desired order when reading it. This is Our original intention to develop gadgets.

Of course, there is another very important function, that is, when we want to insert an article between the first and second articles, for example:

  • 1. Article 1
  • 1-1. Article 1-1
  • 2. Article 2
  • ……

We also need to reorder this directory structure so that new articles can be inserted into the specified location.

3.1 Practice list

At this time our directory is:

+ docs
  + 3.目录c
    - 1.目录c1.md
    - 1-1.目录c2.md
    - 2.目录c3.md
  - 1.文章a.md
  - 2.文章b.md
  - 10.文章d.md
+ src
  - config.ts
  - index.ts【已有】
  - sortCatalog.ts
- .eslintrc.js【已有】
- package.json【已有】
- tsconfig.json【已有】

.eslintrc.js , package-lock.json , 060cdb9238ef10 and tsconfig.json in this directory package.json previous article for the configuration of TypeScript, so I won’t repeat it here.

docs directory, just copy the file names.

In order to avoid misoperation by your friends, let’s take a screenshot.

commander-01.png

3.2 Writing commander

Adding commonder only needs to configure on package.json and index.ts

  • Initialize package.json : npm init --yes (previously configured)
  • Install commander : npm i commander
package.json
{
  "name": "jsliang",
  "version": "1.0.0",
  "description": "FE-util",
  "main": "index.js",
  "scripts": {
    "sort": "ts-node ./src/index.ts sort"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^15.0.2",
    "@typescript-eslint/eslint-plugin": "^4.23.0",
    "@typescript-eslint/parser": "^4.23.0",
    "eslint": "^7.26.0",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.4"
  },
  "dependencies": {
    "commander": "^7.2.0"
  }
}
Note that scripts changed, remember to copy it

Then simply write the contents of index.ts

src/index.ts
import program from 'commander';
import { sortCatalog } from './sortCatalog';

program
  .version('0.0.1')
  .description('工具库')

program
  .command('sort <path>')
  .description('文件排序功能。示例:npm run sort "docs" 或者 npm run sort " C:/code/jsliang/src/docs"')
  .action((path: string) => {
    sortCatalog(`../${path}`); // 为了更便捷,先退一层到外边
  });

program.parse(process.argv);

3.3 Write the sort function

After preparing the basic configuration, just add content sortCatalog.ts

src/sortCatalog.ts
/**
 * @name 文件排序功能
 * @time 2021-05-22 16:08:06
 * @description 规则
   1. 系统顺序 1/10/2/21/3,希望排序 1/2/3/10/21
   2. 插入文件 1/2/1-1,希望排序 1/2/3(将 1-1 变成 2,2 变成 3)
*/
import fs from 'fs';
import path from 'path';
import { IGNORE_PATH } from './config';

const recursion = (filePath: string, level = 0) => {
  const files = fs.readdirSync(filePath);

  files
    .filter((item => !IGNORE_PATH.includes(item))) // 过滤忽略文件/文件夹
    .sort((a, b) =>
      Number((a.split('.')[0]).replace('-', '.'))
      - Number((b.split('.')[0]).replace('-', '.'))
    ) // 排序文件夹
    .forEach((item, index) => { // 遍历文件夹
      // 设置旧文件名称和新文件名称
      const oldFileName = item;
      const newFileName = `${index + 1}.${oldFileName.slice(oldFileName.indexOf('.') + 1)}`;

      // 设置旧文件路径和新文件路径
      const oldPath = `${filePath}/${oldFileName}`;
      const newPath = `${filePath}/${newFileName}`;

      // 判断文件格式
      const stat = fs.statSync(oldPath);

      // 判断是文件夹还是文件
      if (stat.isFile()) {
        fs.renameSync(oldPath, newPath); // 重命名文件
      } else if (stat.isDirectory()) {
        fs.renameSync(oldPath, newPath); // 重命名文件夹
        recursion(newPath, level + 1); // 递归文件夹
      }
    });
};

export const sortCatalog = (filePath: string): boolean => {
  // 绝对路径
  if (path.isAbsolute(filePath)) {
    recursion(filePath);
  } else { // 相对路径
    recursion(path.join(__dirname, filePath));
  }

  return true;
};

Some friends will definitely be curious about config.ts is after reading it. In fact, it is just a global configuration:

/**
 * @name 默认的全局配置
 * @time 2021-05-22 16:12:21
 */
import path from 'path';

// 基础目录
export const BASE_PATH = path.join(__dirname, './docs');

// 忽略目录
export const IGNORE_PATH = [
  '.vscode',
  'node_modules',
];

When we have no configuration, we give the default configuration.

3.4 Operation content

OK, when you are ready, you can start playing.

The current directory structure under docs

- 1.文章a.md
- 10.文章d.md
- 2.文章b.md
- 3.目录c
  - 1-1.目录c2.md
  - 1.目录c1.md
  - 2.目录c3.md

And after we run npm run sort "docs" , the new directory list becomes:

- 1.文章a.md
- 2.文章b.md
- 3.目录c
  - 1.目录c1.md
  - 2.目录c2.md
  - 3.目录c3.md
- 4.文章d.md

commander-02.png

In this way, our simple case is ready! Is it very simple!

Four commonly used commander configuration

commander configuration that we do not want to see very much, but is common in normal use:

  • version : version. Used to set the version number of the command program

    • Usage: .version('x.y.z')
  • description : description. Used to set the description of the command

    • Usage: .description('Widget instruction list')
  • option : Options.

    • Usage: .option('-n, --name <name>', 'your name', 'jsliang')
    • The first parameter is the option definition, which can be | , , and ' ' . The parameter can be modified with <> (required) or [] (optional)
    • The second parameter is the option description
    • The third parameter is the default value of the option parameter (optional)
  • command : Command

    • Usage: .command('init <path>', 'description')
    • command slightly more complicated. In principle, it accepts 3 parameters, the first is the command definition, the second is the description of the command, and the third is the command auxiliary modification object
    • The first parameter can use <> or [] modify the command parameters
    • The second parameter is optional

      • When there is no second parameter, commander.js will return Command object
      • With the second parameter, the prototype object will be returned, and the subcommand mode will be used when action(fn)
      • Subcommand mode: ./pm , ./pm-install , ./pm-search etc. These subcommands are in a different file from the main command
    • The third parameter is generally different, he can set whether to display the sub-command mode used
  • action : Action. Used to set related callbacks for command execution

    • Usage: .action(fn)
    • fn can accept command parameters as function parameters, and the order is consistent with the order defined in command()
  • parse : Analysis of process.argv

    • Usage: program.parse(process.argv)
    • This API is generally called at the end to parse process.argv

OK, this is the end of the brief introduction of commander

Five references


jsliang's document library is by 160cdb9238f5b0 Liang Junrong using Creative Commons Attribution-Non-commercial Use-Same Way Sharing 4.0 International License Agreement <br/>Based on https://github.com/LiangJunrong/document-library . <br/>Use rights not authorized by this license agreement can be obtained from https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ .

jsliang
393 声望32 粉丝

一个充满探索欲,喜欢折腾,乐于扩展自己知识面的终身学习斜杠程序员