CLI(命令行工具,Command Line Interface)大家都非常熟悉了,比如 create-react-app 等。我们今天介绍一个 CLI 工具的开发框架,可以帮助我们快速构建 CLI 工具。

oclif(发音为 'oh-cliff') 是一个命令行工具开发框架,功能丰富,开发方便。同时 oclif 还支持通过 TypeScript 来开发,对于习惯使用 TypeScript 的同学来说非常友好。

基本用法

oclif 提供两种运行模式,一种是单一命令模式,类似于 curl,通过各种参数使用不同的功能。另一种是多命令模式,类似于 git,可以定义子命令来实现不同的功能。

下面的两个样例分别展示了单一命令模式和多命令模式的使用方法.

$ npx oclif single mynewcli
? npm package name (mynewcli): mynewcli
$ cd mynewcli
$ ./bin/run
hello world from ./src/index.js!

单命令模式下,会在 src 目录下生成一个 index.{ts,js} 文件,我们在这个文件里定义命令。

$ npx oclif multi mynewcli
? npm package name (mynewcli): mynewcli
$ cd mynewcli
$ ./bin/run --version
mynewcli/0.0.0 darwin-x64 node-v9.5.0
$ ./bin/run --help
USAGE$ mynewcli [COMMAND]COMMANDShellohelp   display help for mynewcli$ ./bin/run hello
hello world from ./src/hello.js!

多命令模式下,会在 src 目录下生成一个 commands 目录,这个目录下的每一个文件就是一个子命令。比如 ./src/commands/hello.ts./src/commands/goodbye.ts

注意,多命令模式下命令和文件之间一个隐式的对应关系,比如 src/commands 目录下的文件是子命令。如果 src/commands 下是一个目录,则目录下的多个文件会形成一个 Topic

加入有如下目录结构:

package.json
src/
└── commands/└── config/├── index.ts├── set.ts└── get.ts

那么,命令最终的执行形式为: mynewcli configmynewcli config:set 和 mynewcli config:get

定义命令

不管是单一命令模式还是多命令模式,开发者只需要定义一个 class 继承 Command 类即可。

import Command from '@oclif/command'export class MyCommand extends Command {static description = 'description of this example command'async run() {console.log('running my command')}
}

如上,在命令运行地时候,会自动执行 run 方法。

Command 还提供了很多工具方法,比如 this.logthis.warnthis.errorthis.exit 等,方便在运行过程中打印日志信息。

命令行工具通常都需要定义一些参数,oclif 支持两种参数定义形式,一种是 argument,用于定义有顺序要求的参数,一种是 flag,用于定义没有顺序要求的参数。

定义 argument

argument 的使用如下:

$ mycli firstArg secondArg # 参数顺序不能乱

我们可以这样定义 argument 参数:

import Command from '@oclif/command'export class MyCLI extends Command {static args = [{name: 'firstArg'},{name: 'secondArg'},]async run() {// 通过对象的形式获取参数const { args } = this.parse(MyCLI)console.log(`running my command with args: ${args.firstArg}, ${args.secondArg}`)// 也可以通过数组的形式获取参数const { argv } = this.parse(MyCLI)console.log(`running my command with args: ${argv[0]}, ${argv[1]}`)}
}

我们可以对 argument 参数进行属性定义:

static args = [{name: 'file',               // 参数名称,之后通过 argv[name] 的形式获取参数required: false,            // 是否必填description: 'output file', // 参数描述hidden: true,               // 是否从命令的 help 信息中隐藏parse: input => 'output',   // 参数处理函数,可以改变用户输入的值default: 'world',           // 参数默认值options: ['a', 'b'],        // 参数的可选范围}
]

定义 flag

flag 的使用形式如下:

$ mycli --force --file=./myfile

我们可以这样定义 flag 参数:

import Command, {flags} from '@oclif/command'export class MyCLI extends Command {static flags = {// 可以通过 --force 或 -f 来指定参数force: flags.boolean({char: 'f'}),file: flags.string(),}async run() {const {flags} = this.parse(MyCLI)if (flags.force) console.log('--force is set')if (flags.file) console.log(`--file is: ${flags.file}`)}
}

我们可以对 flag 参数进行属性定义:

static flags = {name: flags.string({char: 'n',                    // 参数短名称description: 'name to print', // 参数描述hidden: false,                // 是否从 help 信息中隐藏multiple: false,              // 是否支持对这个参数设置多个值env: 'MY_NAME',               // 默认值使用的环境变量的名称options: ['a', 'b'],          // 可选值列表parse: input => 'output',     // 对用户输入进行处理default: 'world',             // 默认值,也可以是一个返回字符串的函数required: false,              // 是否必填dependsOn: ['extra-flag'],    // 依赖的其他 flag 参数列表exclusive: ['extra-flag'],    // 不能一起使用的其他 flag 参数列表}),// 布尔值参数force: flags.boolean({char: 'f',default: true,                // 默认值,可以是一个返回布尔值的函数}),
}

使用生命周期钩子

oclif 提供了一些生命周期钩子,可以让开发者在工具运行的各个阶段进行一些额外操作。

我们可以这样定义一个钩子函数:

import { Hook } from '@oclif/config'export default const hook: Hook<'init'> = async function (options) {console.log(`example init hook running before ${options.id}`)
}

同时,还需要在 package.json 中注册这个钩子函数:

"oclif": {"commands": "./lib/commands","hooks": {"init": "./lib/hooks/init/example"}
}

oclif 还支持定义多个钩子函数,多个钩子函数会并行运行:

"oclif": {"commands": "./lib/commands","hooks": {"init": ["./lib/hooks/init/example","./lib/hooks/init/another_hook"]}
}

目前支持的生命周期钩子如下:

  • init - 在 CLI 完成初始化之后,找到对应命令之前。
  • prerun - 在 init 完成,并找到对应命令之后,但是在命令运行之前。
  • postrun - 在命令运行结束之后,并没有错误发生。
  • command_not_found - 没有找到对应命令,在展示错误信息之前。

使用插件

oclif 官方和社区提供了很多有用的插件可以供新开发的命令行工具使用,只需要在 package.json 中声明即可。

{"name": "mycli","version": "0.0.0",// ..."oclif": {"plugins": ["@oclif/plugin-help","@oclif/plugin-not-found"]}
}

可用的插件有:

  • @oclif/plugin-not-found 当未找到命令的时候提供一个友好的 "did you mean" 信息。
  • @oclif/plugin-plugins 允许用户给你的命令行工具添加插件。
  • @oclif/plugin-update 自动更新插件。
  • @oclif/plugin-help 帮助信息插件。
  • @oclif/plugin-warn-if-update-available 当有可用更新时,展示一个警告信息提示更新。
  • @oclif/plugin-autocomplete 提供 bash/zsh 的自动补全。

错误处理

命令行运行难免会出错,oclif 提供了两种错误处理的方法。

Command.catch

每个 Command 实例都有一个 catch 方法,开发者可以在这个方法中处理错误。

import {Command, flags} from '@oclif/command'export default class Hello extends Command {async catch(error) {// do something or// re-throw to be handled globallythrow error;}
}

bin/run 的 catch

bin/run 是每个 oclif 命令行工具的入口文件,我们可以通过 bin/run 的 catch 方法抓取错误,包括 Command 中重新抛出的错误。

.catch(require('@oclif/errors/handle'))//或.catch((error) => {const oclifHandler = require('@oclif/errors/handle');// do any extra work with errorreturn oclifHandler(error);
})

其他功能

cli-ux

oclif 官方维护的 cli-ux 库提供了许多使用的功能。

  • 通过 cliux.prompt() 函数可以实现简单的交互功能。如果有更复杂的交互需求,可以使用 inquirer。
  • 通过 cliux.action 可以实现旋转 loading 效果。
  • 通过 cliux.table 可以展示表格数据。

真香!原来 CLI 开发可以这么简单相关推荐

  1. 【真香】我开发了一款提醒各位 [ 大佬 ] 喝水的 [ chrome插件 ]

    theme: channing-cyan 海阔凭鱼跃,天高任鸟飞.Hey 你好!我是秦爱德.

  2. 船新 IDEA 2020.1真香体验!Java开发首席生产力担当

    就在前几天,Java软件开发神器 IDEA 2020.1 新版发布了: 我第一时间在机子上更新并体验了几天,感觉还是有点香的!怎么硕呢,体验完新特性之后,不由的感叹一句:IDEA现在真的是越来越智能, ...

  3. jpa query oracle 参数int为空_撸一个预言机(Oracle)服务,真香!—中篇

    本文作者:六天 一.文章结构 本文将通过上.中.下三篇文章带领大家一步步开发实现一个中心化的 Oracle 服务,并通过一个抽奖合约演示如何使用我们的 Oracle 服务.文章内容安排如下: 上篇:O ...

  4. excel appliacation 不能分配角色_用了5年Excel,换了这个神奇的报表工具,不禁感叹:国产真香...

    作为一个在数据行业摸爬滚打多年的老江湖,我初入行的时候和大部分人一样,当了很长一段时间的"表哥",才开始做真正的数据分析,辅助业务决策.不过无论是当纯粹的"表哥表姐&qu ...

  5. 趣谈程序员真香定律:源码即设计

    来源 | 码砖杂役 责编 | Carol 封图 | CSDN 付费下载自视觉中国 我们经常谈论架构,讨论设计,却甚少关注实现和代码本身,架构和设计固然重要,但要说代码本身不重要,我不同意,Robert ...

  6. 用了5年Excel,换了这个神奇的报表工具,不禁感叹:国产真香

    作为一个在数据行业摸爬滚打多年的老江湖,我初入行的时候和大部分人一样,当了很长一段时间的"表哥",才开始做真正的数据分析,辅助业务决策.不过无论是当纯粹的"表哥表姐&qu ...

  7. java sleep方法_6种快速统计代码执行时间的方法,真香!(史上最全)

    我们在日常开发中经常需要测试一些代码的执行时间,但又不想使用向 JMH(Java Microbenchmark Harness,Java 微基准测试套件)这么重的测试框架,所以本文就汇总了一些 Jav ...

  8. Python做一个Kindle电子书下载助手,真香!

    哈喽,大家好,我是菜鸟哥! 大家有没有想过把亚马逊网站上的Kindle电子书下载到自己的电脑上? 今天分享的项目可以帮大家实现这一目的.该项目用Python开发,简单.好用.开源. 下面分享下项目的使 ...

  9. 最强Python编程神器,真香!

    来源: 软件测评说 大家好,我是 菜鸟哥. 记得上学那会儿,就喜欢折腾,其中印象深刻的一个就是在手机上写Python代码并运行Python. 当时,初学Python,躺在床上,上课都在写代码,确实挺好 ...

最新文章

  1. 大数据教程之大数据处理流程
  2. JSON.parse()和eval()的区别
  3. R语言应用实战-基于R的因子分析(以上市公司数据为例)
  4. IDA+OD双剑合璧=逆向无敌
  5. [Swift]LeetCode1146. 快照数组 | Snapshot Array
  6. 3. 机器学习中为什么需要梯度下降_【干货】机器学习 | 为什么机器能“学习”?——感知器和梯度下降...
  7. shell中返回值是1为真还是假_肝!Shell 脚本编程最佳实践
  8. 【LeetCode】剑指 Offer 44. 数字序列中某一位的数字
  9. php对mysql解决乱码_PHP彻底解决mysql中文乱码-阿里云开发者社区
  10. 【jQuery笔记Part1】01-jQuery简介集成
  11. 深度学习——CNN、RNN、DNN汇总
  12. (附源码)计算机毕业设计ssm 航空订票系统
  13. 你技术这么好,总要改变点什么把!
  14. 数据结构与算法面试题80道
  15. 在linux下打开chm文件
  16. 华为HCIP RS题库221 121-130题
  17. Siemens配置许可证服务器,OPC 服务器 配置
  18. Java中 VO、PO、DO、DTO、BO、QO、DAO、POJO的概念
  19. js 获取汉字首拼和全拼和拼音
  20. mpv播放器 —— 一个免费的、开源的、跨平台的媒体播放器

热门文章

  1. matlab 行 读取文件 跳过_用Matlab处理LAS点云(1)——LAS文件概述
  2. 嵌入式笔录(3)-电容,频率与幅度基础
  3. 【深度学习】围观特斯拉总监把玩MNIST
  4. 【机器学习基础】一文归纳Python特征生成方法(全)
  5. 【机器学习基础】不会时间序列预测?不要紧,大神来教你
  6. 基于geopandas的空间数据分析——空间计算篇(下)
  7. Github标星8.3k+,Tensorflow 2.0的代码实现及教学材料(“龙书”)公布下载了!
  8. 特征锦囊:怎么批量把特征中的离群点给处理下?
  9. 经典算法笔记:异常检测和推荐系统
  10. 【必须收藏】那些酷炫的深度学习网络图怎么画出来的?