Skip to content

一个很易用的shell交互库,可以开发自己的node shell小工具

Notifications You must be signed in to change notification settings

antilmid/best-shell-tool

Repository files navigation

Best Shell tool

dev以来需要用--legacy-peer-deps来解决冲突

npm version npm version npm bundle size github activity languages gitHub watchers gitHub stars


1. 关于BST


BST实现的是一个shell & command的操作库,分别提供了shell控制字符串操作、shell终端操作、command parse操作和基础shell功能组件四大能力。

BST期望做到简化命令行编程能力,更快、更简单地开发命令行工具和cli。在后续的介绍过程中,会带大家实现几个命令行小功能,来了解BST开发命令行工具的过程。



2. shell控制字符模块


该模块提供了生成控制字符的能力。所谓控制字符其实就是改变终端或文件显示的一些行为。一个控制符是由 CONTRL + key 组成的(同时按下)。控制字符同样可以通过转义以八进制或十六进制的方式显示。


2.1 getFontStyle


获取带字体样式的shell消息,我们可以通过这个能力,获取带字体颜色和背景颜色的shell消息字符串。直接用console或stdout输出这个消息,就可以看到带颜色的文字。

getFontStyle(fontColor:Color, backColor:Color, msg:string):string

关于Color的定义如下TS所示:

type Color = 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'purple' | 'celeste' | 'white';

示例:

const bst = require('best-shell-tool')

console.log(bst.getFontStyle('blue', '', '我是蓝色字'))
console.log(bst.getFontStyle('red', '', '我是红色字'))
console.log(bst.getFontStyle('yellow', 'blue', '我是黄色字蓝色背景'))

输出:

图一


2.2 clearAllProps


获取清除所有属性的shell消息。通过这个可以清除前文所设置的所有属性样式。

clearAllProps(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.getFontStyle('blue')+'我是蓝色')
console.log(bst.getFontStyle('blue')+bst.clearAllProps('我的蓝色属性没有被继承过来'))

输出:

图二


2.3 getHighlightString


获取高亮的shell消息,其实个人感觉就是稍微加粗了一下。

getHighlightString(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.clearAllProps('我是普通字'))
console.log(bst.getHighlightString('我是高亮字'))
console.log(bst.getFontStyle('red') + bst.getHighlightString('我是红色高亮字'))

输出:

图三


2.4 getUnderLineString


获取下划线的shell消息

getUnderLineString(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.clearAllProps('我是普通字'))
console.log(bst.getUnderLineString('我是下划线字'))
console.log(bst.getFontStyle('red') + bst.getUnderLineString('我是红色下划线字'))

输出:

图四


2.5 getBlinkString


获取闪烁字体的shell消息,故名思议,该功能实现了shell文字闪烁,一般在shell交互能力中,用于表示已经选中的选项。

getUnderLineString(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.clearAllProps('我是普通字'))
console.log(bst.getBlinkString('我是闪烁字'))
console.log(bst.getFontStyle('red') + bst.getBlinkString('我是红色闪烁字'))

2.6 getRDisplayString


获取反显的shell消息,所谓反显,就是模拟文字被选中的状态,一般呈现为 背景=字体颜色, 字体颜色=背景。

getRDisplayString(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.clearAllProps('我是普通字'))
console.log(bst.getRDisplayString('我是反显状态'))
console.log(bst.getFontStyle('red') + bst.getRDisplayString('我是红色字反显状态'))

输出:

图五


2.7 getCancelHideString


获取消隐的shell消息,消隐的消息在控制台是看不见的,但是占位符是真实存在的,并且文字也是可以真实复制的。

getCancelHideString(msg:string):string

示例:

const bst = require('best-shell-tool')

console.log(bst.clearAllProps('我是普通字'))
console.log(bst.getCancelHideString('我是消隐状态'))

2.8 controlArrowMove


控制shell光标移动的shell消息,通过方向指令和移动数量来控制光标的移动,可以实现在不同位置做输出的功能。

controlArrowMove(direct:Direct, lines:number, msg:string):string

关于Direct的定义如下TS所示:

type Direct = 'up' | 'down' | 'right' | 'left' | '上' | '下' | '左' | '右';

示例:

const bst = require('best-shell-tool')

console.log('00 01 02 03')
console.log('10 11 12 13')
console.log('20 21 22 23')
console.log('30 31 32 33')
console.log(bst.controlArrowMove('上', 2, '我是移动后的'))

输出:

图六


2.9 setArrowPosition


设置shell光标位置的shell消息,和controlArrowMove相比,这个是直接通过指定坐标点来移动光标。

setArrowPosition(x:number | '', y:number | '', msg:string):string


2.10 clearScreen


清屏,顾名思义,清除之前屏幕所有的内容。

clearScreen(msg:string):string


2.11 saveArrowPosition


保存当前光标位置

saveArrowPosition(msg:string):string


2.12 readArrowPosition


取出之前保存的光标位置

saveArrowPosition(msg:string):string


2.13 hideArrow


隐藏光标,就是把shell的那个小黑点隐藏。

hideArrow(msg:string):string


2.14 showArrow


显示光标

showArrow(msg:string):string


2.15 clearPositionAfter


清除光标之后这一行的消息。在制作进度条的时候可以用它时时清除一行后的消息,保留之前输出的消息。

clearPositionAfter(msg:string):string


2.16 getFmtString


获取格式化字符串。和前面的不同,这个是链式获取一串格式化消息,通过end结束链式调用,拿到格式化消息。其中每次返回的StandOutOperate操作对象,里面的所有操作链都能和前面的函数一一对应。

function getFmtString(_msg:string):StandOutOperate

关于StandOutOperate的定义如下TS所示:

interface StandOutOperate {
  /**
   * @description: 附加消息
   * @param {string} msg 要附加的消息
   * @return {StandOutOperate}
   */
  msg?: (msg?:string) => StandOutOperate,

  /**
   * @description: 结束并获得格式化后的字符
   * @return {string}
   */
  end?: () => string,

  /**
   * @description: 设置字体样式
   * @param {Color | ''} fontColor 字体颜色
   * @param {Color | ''} background 背景色
   * @return {StandOutOperate}
   */
  setFont?: (fontColor?: Color | '', background?: Color | '', msg?:string) => StandOutOperate,

  /**
   * @description: 清除所有控制属性
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clearProps?: (msg?:string) => StandOutOperate,

  /**
   * @description: 高亮文本
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  highlight?: (msg?:string) => StandOutOperate,

  /**
   * @description: 下划线
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  underline?: (msg?:string) => StandOutOperate,

  /**
   * @description: 闪烁
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  blink?: (msg?:string) => StandOutOperate,

  /**
   * @description: 反显
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  rdisplay?: (msg?:string) => StandOutOperate,

  /**
   * @description: 消隐
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  cancelHide?: (msg?:string) => StandOutOperate,

  /**
   * @description: 控制光标移动
   * @param {Direct} direct 移动方向
   * @param {number} lines 移动行数
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  arrowMove?: (direct?:Direct, lines?:number, msg?:string) => StandOutOperate,

  /**
   * @description: 设置鼠标位置
   * @param {number | ''} x 横坐标移动距离
   * @param {number | ''} y 纵坐标移动距离
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  setArrow?: (x?:number | '', y?:number | '', msg?:string) => StandOutOperate,

  /**
   * @description: 清屏
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clear?: (msg?:string) => StandOutOperate,

  /**
   * @description: 保存光标位置
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  saveArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 读取恢复光标位置
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  readArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 隐藏光标
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  hideArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 显示光标
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  showArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 清除光标所在位置之后这一行的所有内容
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clearAfter?: (msg?:string) => StandOutOperate,
}

示例:

const bst = require('best-shell-tool')

console.log(
  bst.getFmtString('我是普通字体')
  .setFont('red', '', '我是红色字体')
  .clearProps()
  .underline('没想到我带下划线了')
  .setFont('blue', '', '我蓝了')
  .end()
)

输出:

图七


2.17 基于控制字符实现一个 进度条 功能


这里是一个简单的实战教学,基于前文提供的api制作一个简单的进度条功能。当然,因为这个进度条工具很常用,BST自带的组件库里面已经封装了进度条。这里的实现只是为了大家更好的掌握和熟悉BST-控制字符模块的功能。

示例:

function process (current, total, len = 10) {
  const back = bst.getFontStyle('', 'white', ' ')
  const active = bst.getFontStyle('', 'green', ' ')
  const activeNum = parseInt((current / total) * len, 10)
  let processStr = ''
  for(let i = 0; i < len; i++) {
    if(i < activeNum) processStr += active
    else processStr += back
  }
  console.log(
    bst.getFmtString()
    .hideArrow()
    .arrowMove('上', 1)
    .clearAfter(processStr)
    .clearProps()
    .msg(current)
    .msg('/')
    .msg(total)
    .end()
  )
}

let count = 0
let total = 21
console.log('准备加载进度\n')
setTimeout(()=>{
  const timer = setInterval(()=>{
    if(count === total) clearInterval(timer)
    process(count, total, 25)
    count++
  }, 1000)
}, 1000)


3. CommandX语法和语法解析器


CommandX是作者定义的一种简单命令交互语法,它是一种简化、弱化后的shell命令模式,设计之初的目的是为了解决node开发命令行工具时,希望对用户开放命令交互的愿景,CommandX语法通过cmParse可以将对应的命令解析成命令对象的形式。形式定义如下:

interface ParseStruct {
  command?: string,
  defaultArgs?: string,
  args?: {
    [argsName:string]:any
  }
}

3.1 CommandX语法


这一节将简单介绍一些CommandX语法编写,和对应转换成命令对象形式的样例。

  • 无参数直接命令
    commandX语法
    command

    转换成js对象后

    { args: {}, command: 'command' }

  • 带参数命令
    commandX语法
    command -arg1 你好世界 -arg2 我是参数2

    转换成js对象后

    { args: { arg1: '你好世界', arg2: '我是参数2' }, command: 'command' }

  • 布尔值参数使用
    commandX语法
    command -arg1 -arg2 arg1是布尔值

    转换成js对象后

    { args: { arg1: true, arg2: 'arg1是布尔值' }, command: 'command' }

  • 默认参数值语法
    commandX语法
    command 我是默认值 -other 我不是默认值

    转换成js对象后

    { args: { other: '我不是默认值' }, command: 'command', defaultArgs: '我是默认值' }

    commandX语法
    command -other 我不是默认值 我是默认值

    转换成js对象后

    { args: { other: '我不是默认值' }, command: 'command', defaultArgs: '我是默认值' }

  • 双引号限定字符串
    commandX中,带上双引号的字符串叫作双引号限定字符串,该类型字符串中,反斜杠(\)和双引号(")属于特殊字符,需要用转义字符才能使他正确转换。 commandX语法
    command "她说:\"我爱你\""

    转换成js对象后

    { args: {}, command: 'command', defaultArgs: '她说:"我爱你"' }

  • 自由非限定字符
    commandX中,不被双引号(")包裹的字符串被称作自由非限定字符,对于这类字符是不能使用双引号(")和减号(-)开头。所以在自由非限定字符模式下,提供了unicode直接编码转换。用\U;的模式可以指定任意一个Unicode对应的字符。比如\65;就会被转换为字符A。对于一些特殊字符,可以直接使用\S标识转换的模式,如常用的空格可以用\space;转换。标识的对应关系如下:

    • \space; ===
    • \backslash; === \
    • \slash; === /
    • \semicolon; === ;

    commandX语法
    command \65;\66;\67;

    转换成js对象后

    { args: {}, command: 'command', defaultArgs: 'ABC' }

3.2 parser函数


parser函数是BST CommandParse模块提供的CommandX语法解析函数,用它可以将CommandX语法编译成js对象。参数str是要解析commandX语法,参数mode是指定报错模式,如果为normal,使用console.log进行错误提示。如果为strict,错误直接抛出。参数isDebugger是用来调试编译的语法分析,如果为true,则在每一次意外的语法分析,输出当前语法分析状态机的状态
function parser(str:string, mode:Mode = 'normal', isDebugger:boolean = false):ParseStruct

关于ParseStruct的定义如下TS所示:

interface ParseStruct {
  command?: string,
  defaultArgs?: string,
  args?: {
    [argsName:string]:any
  }
}

示例:

const bst = require('best-shell-tool')

const res = bst.cmParser.parser('command -arg hello,world')
console.log(res)

输出:

{ args: { arg: 'hello,world' }, command: 'command' }

3.3 data2Commandx函数


data2Commandx函数是parser的一个逆向过程,它能将命令对象转换成CommandX语法。
function data2Commandx(data:ParseStruct):string

关于ParseStruct的定义如下TS所示:

interface ParseStruct {
  command?: string,
  defaultArgs?: string,
  args?: {
    [argsName:string]:any
  }
}

示例:

const bst = require('best-shell-tool')

const data = {
  args: { isOpen: true, x: '10', y: '20' },
  command: 'command',
  defaultArgs: 'hello,world'
}
const res = bst.cmParser.data2Commandx(data)
console.log(res)

输出:
command "hello,world" -isOpen -x "10" -y "20"


3.4 formatFree函数


formatFree函数是一个用来将自由非限定字符串转化为js字符串,CommandX的parser对于自由非限定字符串就是使用该函数实现。
function formatFree(str:string):string



4. IOStand标准输入输出库


BST提供了一个标准输入输出管理,用它可以轻松管理shell控制台的输入输出。

4.1 write


IOStand的write函数是输出一条消息到控制台。
write(data:string = ''):boolean

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand()
iostand.write('hello world\n')

此时你会发现控制台输出了hello world,和console.log效果类似,不同的是他不会自动在语句后换行。


4.2 writeChain


IOStand的writeChain函数是链式输出一条消息到控制台。里面会返回一条操作链。
writeChain(_msg:string = ''):StandOutOperate
其中StandOutOperate定义如下:

interface StandOutOperate {
  /**
   * @description: 附加消息
   * @param {string} msg 要附加的消息
   * @return {StandOutOperate}
   */
  msg?: (msg?:string) => StandOutOperate,

  /**
   * @description: 设置字体样式
   * @param {Color | ''} fontColor 字体颜色
   * @param {Color | ''} background 背景色
   * @return {StandOutOperate}
   */
  setFont?: (fontColor?: Color | '', background?: Color | '', msg?:string) => StandOutOperate,

  /**
   * @description: 清除所有控制属性
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clearProps?: (msg?:string) => StandOutOperate,

  /**
   * @description: 高亮文本
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  highlight?: (msg?:string) => StandOutOperate,

  /**
   * @description: 下划线
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  underline?: (msg?:string) => StandOutOperate,

  /**
   * @description: 闪烁
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  blink?: (msg?:string) => StandOutOperate,

  /**
   * @description: 反显
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  rdisplay?: (msg?:string) => StandOutOperate,

  /**
   * @description: 消隐
   * @param {string} msg 消息
   * @return {StandOutOperate}
   */
  cancelHide?: (msg?:string) => StandOutOperate,

  /**
   * @description: 控制光标移动
   * @param {Direct} direct 移动方向
   * @param {number} lines 移动行数
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  arrowMove?: (direct?:Direct, lines?:number, msg?:string) => StandOutOperate,

  /**
   * @description: 设置鼠标位置
   * @param {number | ''} x 横坐标移动距离
   * @param {number | ''} y 纵坐标移动距离
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  setArrow?: (x?:number | '', y?:number | '', msg?:string) => StandOutOperate,

  /**
   * @description: 清屏
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clear?: (msg?:string) => StandOutOperate,

  /**
   * @description: 保存光标位置
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  saveArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 读取恢复光标位置
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  readArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 隐藏光标
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  hideArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 显示光标
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  showArrow?: (msg?:string) => StandOutOperate,

  /**
   * @description: 清除光标所在位置之后这一行的所有内容
   * @param {string} msg 附加消息
   * @return {StandOutOperate}
   */
  clearAfter?: (msg?:string) => StandOutOperate,
}

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand()
iostand.writeChain('我是普通字体')
  .setFont('red', '', '我是红色字体')
  .setFont('yellow', '', '我是黄色字体\n')

输出:
图八


4.3 start


start函数是开启命令交互模式,你可以使用oninput事件来监听输入,注意,当你注册了oninput事件,那么start就不会启用CommandX命令交互模式。

如下示例,我们注册了oninput事件来监听输入。

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand()
iostand.oninput = (data) => {
  console.log('你输入了:', data)
}
iostand.start()

如果我们直接使用start,相当于是一个CommandX交互模式,你需要通过addCommand函数来注册命令。如下示例所示。

示例:

const iostand = new bst.IOStand()

iostand.addCommand('hello')
  .action(()=>{
    console.log('hello world')
  })
iostand.start()

输出:
图九
图十
图十一

4.4 addCommand


addCommand是添加一条CommandX命令,这样在开启start交互后,就会根据CommandX寻找已经注册过的action去执行。
addCommand(cmd:string, notes:string = ''):AddCommandOperate

其中AddCommandOperate如下:

interface AddCommandOperate {
  /**
   * @description: 声明一个参数
   * @param {string} argName 参数名称
   * @param {string} notes 参数注释
   * @return {AddCommandOperate} 返回操作链
   */
  arg: (argName:string, notes:string) => AddCommandOperate;

  /**
   * @description: 声明一个默认参数
   * @param {string} notes 参数注释
   * @return {AddCommandOperate} 返回操作链
   */
  defaultArg: (notes:string) => AddCommandOperate;

  /**
   * @description: 注册操作函数
   * @param {(command:ParseStruct)=>Promise<number>} fn 操作函数
   * @return {AddCommandOperate} 返回操作链
   */
  action: (fn:(command:ParseStruct)=>Promise<number>)=>AddCommandOperate;
}

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand()
iostand.addCommand('say', '输出一句话到控制台')
  .defaultArg('要说的话')
  .arg('prefix', '前缀')
  .arg('suffyx', '后缀')
  .action((cmd)=>{
    const prefix = cmd.args.prefix || '';
    const suffix = cmd.args.suffix || '';
    const content = cmd.defaultArgs || '';
    console.log(prefix,content,suffix)
  })
iostand.start()

输出:
图十二

值得注意的是,IOStand里面其实默认注册了一个help命令,通过它可以在控制台查询已经注册过的命令使用方式。
其次,action注册的函数如果返回的是Promise,相当于你是一个异步函数,它会等你执行完成再监听输入。
如果已经存在某个命令,则不能再注册该命令。

4.5 listAllCommand


listAllCommand函数是列出已经注册过的CommandX命令。
listAllCommand():void

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand()
iostand.addCommand('say', '输出一句话到控制台')
  .defaultArg('要说的话')
  .arg('prefix', '前缀')
  .arg('suffyx', '后缀')
  .action((cmd)=>{
    const prefix = cmd.args.prefix || '';
    const suffix = cmd.args.suffix || '';
    const content = cmd.defaultArgs || '';
    console.log(prefix,content,suffix)
  })
iostand.listAllCommand()

输出:

help    
    -[默认参数] 要查看帮助的命令(可以不填写)

say    输出一句话到控制台
    -[默认参数] 要说的话
    -prefix 前缀
    -suffyx 后缀

4.6 awaitInput


awaitInput是等待一次输入,等待的这次输入不会受到CommandX交互的影响。
awaitInput():Promise<any>

示例:

const bst = require('best-shell-tool')

const iostand = new bst.IOStand();
(async () => {
  const inp = await iostand.awaitInput()
  console.log('你输入了:', inp)
})();

4.7 pause


pause同process.stdin.pause,暂停控制台。
pause():NodeJS.ReadStream & {fd:0;}


4.8 resume


resume同process.stdin.resume,恢复控制台输入。
resume():NodeJS.ReadStream & {fd: 0;}


4.9 exit


exit同process.exit,退出控制台。
exit():never


4.10 release


release是释放IOStand对象,当不再用到IOStand时候,请一定要使用该函数释放
release():void




5. Tool工具


Tool提供了一些集成好的小工具,但是目前只提供了一个进度条功能,后续会根据大家的需求进行增加迭代。


5.1 process进度条


process提供的是显示一个进度条能力。
function process(current:number, total:number = 100, len:number = 24):string

示例:

const bst = require('best-shell-tool')

const process = bst.tool.process;
const iostand = new bst.IOStand();

iostand.addCommand('wait', '等待')
  .defaultArg('要等待的时间')
  .action((cmd)=>{
    console.log('')
    return new Promise((res) => {
      const waitTime = parseInt(cmd.defaultArgs, 10) || 0
      let current = 0
      const timer = setInterval(()=>{
        console.log(process(current, waitTime))
        if(current === waitTime) {
          clearInterval(timer)
          console.log('已经结束等待')
          res()
        }
        current += 1
      }, 1000)
    })
  })

iostand.start()


About

一个很易用的shell交互库,可以开发自己的node shell小工具

Resources

Stars

Watchers

Forks

Packages

No packages published