├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── build
├── at-rule.js
├── cookie-store-api.js
├── es.array.for-each.js
├── es.date.to-string.js
├── filterLimit.js
├── gyroscope.js
├── jpeg2000.js
├── lib
│ ├── index.js
│ └── string-utils.js
├── lua.json
├── meterRole.js
├── parseHeaders.js
├── passive-event-listener.js
├── pointer-events.js
├── register.lua
├── string-padding.js
├── sxg.js
└── xhr2.js
├── index.mjs
├── lib
├── arg.js
├── cmd.js
├── coaobject.js
├── coaparam.js
├── completion.js
├── completion.sh
├── index.js
├── opt.js
└── shell.js
└── package.json
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [7.0.4](https://www.github.com/yargs/cliui/compare/v7.0.3...v7.0.4) (2020-11-08)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * **deno:** import UIOptions from definitions ([#97](https://www.github.com/yargs/cliui/issues/97)) ([f04f343](https://www.github.com/yargs/cliui/commit/f04f3439bc78114c7e90f82ff56f5acf16268ea8))
11 |
12 | ### [7.0.3](https://www.github.com/yargs/cliui/compare/v7.0.2...v7.0.3) (2020-10-16)
13 |
14 |
15 | ### Bug Fixes
16 |
17 | * **exports:** node 13.0 and 13.1 require the dotted object form _with_ a string fallback ([#93](https://www.github.com/yargs/cliui/issues/93)) ([eca16fc](https://www.github.com/yargs/cliui/commit/eca16fc05d26255df3280906c36d7f0e5b05c6e9))
18 |
19 | ### [7.0.2](https://www.github.com/yargs/cliui/compare/v7.0.1...v7.0.2) (2020-10-14)
20 |
21 |
22 | ### Bug Fixes
23 |
24 | * **exports:** node 13.0-13.6 require a string fallback ([#91](https://www.github.com/yargs/cliui/issues/91)) ([b529d7e](https://www.github.com/yargs/cliui/commit/b529d7e432901af1af7848b23ed6cf634497d961))
25 |
26 | ### [7.0.1](https://www.github.com/yargs/cliui/compare/v7.0.0...v7.0.1) (2020-08-16)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * **build:** main should be build/index.cjs ([dc29a3c](https://www.github.com/yargs/cliui/commit/dc29a3cc617a410aa850e06337b5954b04f2cb4d))
32 |
33 | ## [7.0.0](https://www.github.com/yargs/cliui/compare/v6.0.0...v7.0.0) (2020-08-16)
34 |
35 |
36 | ### ⚠ BREAKING CHANGES
37 |
38 | * tsc/ESM/Deno support (#82)
39 | * modernize deps and build (#80)
40 |
41 | ### Build System
42 |
43 | * modernize deps and build ([#80](https://www.github.com/yargs/cliui/issues/80)) ([339d08d](https://www.github.com/yargs/cliui/commit/339d08dc71b15a3928aeab09042af94db2f43743))
44 |
45 |
46 | ### Code Refactoring
47 |
48 | * tsc/ESM/Deno support ([#82](https://www.github.com/yargs/cliui/issues/82)) ([4b777a5](https://www.github.com/yargs/cliui/commit/4b777a5fe01c5d8958c6708695d6aab7dbe5706c))
49 |
50 | ## [6.0.0](https://www.github.com/yargs/cliui/compare/v5.0.0...v6.0.0) (2019-11-10)
51 |
52 |
53 | ### ⚠ BREAKING CHANGES
54 |
55 | * update deps, drop Node 6
56 |
57 | ### Code Refactoring
58 |
59 | * update deps, drop Node 6 ([62056df](https://www.github.com/yargs/cliui/commit/62056df))
60 |
61 | ## [5.0.0](https://github.com/yargs/cliui/compare/v4.1.0...v5.0.0) (2019-04-10)
62 |
63 |
64 | ### Bug Fixes
65 |
66 | * Update wrap-ansi to fix compatibility with latest versions of chalk. ([#60](https://github.com/yargs/cliui/issues/60)) ([7bf79ae](https://github.com/yargs/cliui/commit/7bf79ae))
67 |
68 |
69 | ### BREAKING CHANGES
70 |
71 | * Drop support for node < 6.
72 |
73 |
74 |
75 |
76 | ## [4.1.0](https://github.com/yargs/cliui/compare/v4.0.0...v4.1.0) (2018-04-23)
77 |
78 |
79 | ### Features
80 |
81 | * add resetOutput method ([#57](https://github.com/yargs/cliui/issues/57)) ([7246902](https://github.com/yargs/cliui/commit/7246902))
82 |
83 |
84 |
85 |
86 | ## [4.0.0](https://github.com/yargs/cliui/compare/v3.2.0...v4.0.0) (2017-12-18)
87 |
88 |
89 | ### Bug Fixes
90 |
91 | * downgrades strip-ansi to version 3.0.1 ([#54](https://github.com/yargs/cliui/issues/54)) ([5764c46](https://github.com/yargs/cliui/commit/5764c46))
92 | * set env variable FORCE_COLOR. ([#56](https://github.com/yargs/cliui/issues/56)) ([7350e36](https://github.com/yargs/cliui/commit/7350e36))
93 |
94 |
95 | ### Chores
96 |
97 | * drop support for node < 4 ([#53](https://github.com/yargs/cliui/issues/53)) ([b105376](https://github.com/yargs/cliui/commit/b105376))
98 |
99 |
100 | ### Features
101 |
102 | * add fallback for window width ([#45](https://github.com/yargs/cliui/issues/45)) ([d064922](https://github.com/yargs/cliui/commit/d064922))
103 |
104 |
105 | ### BREAKING CHANGES
106 |
107 | * officially drop support for Node < 4
108 |
109 |
110 |
111 |
112 | ## [3.2.0](https://github.com/yargs/cliui/compare/v3.1.2...v3.2.0) (2016-04-11)
113 |
114 |
115 | ### Bug Fixes
116 |
117 | * reduces tarball size ([acc6c33](https://github.com/yargs/cliui/commit/acc6c33))
118 |
119 | ### Features
120 |
121 | * adds standard-version for release management ([ff84e32](https://github.com/yargs/cliui/commit/ff84e32))
122 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Contributors
2 |
3 | Permission to use, copy, modify, and/or distribute this software
4 | for any purpose with or without fee is hereby granted, provided
5 | that the above copyright notice and this permission notice
6 | appear in all copies.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
10 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
11 | LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
12 | OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
13 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
14 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cliui
2 |
3 | 
4 | [](https://www.npmjs.com/package/cliui)
5 | [](https://conventionalcommits.org)
6 | 
7 |
8 | easily create complex multi-column command-line-interfaces.
9 |
10 | ## Example
11 |
12 | ```js
13 | const ui = require('cliui')()
14 |
15 | ui.div('Usage: $0 [command] [options]')
16 |
17 | ui.div({
18 | text: 'Options:',
19 | padding: [2, 0, 1, 0]
20 | })
21 |
22 | ui.div(
23 | {
24 | text: "-f, --file",
25 | width: 20,
26 | padding: [0, 4, 0, 4]
27 | },
28 | {
29 | text: "the file to load." +
30 | chalk.green("(if this description is long it wraps).")
31 | ,
32 | width: 20
33 | },
34 | {
35 | text: chalk.red("[required]"),
36 | align: 'right'
37 | }
38 | )
39 |
40 | console.log(ui.toString())
41 | ```
42 |
43 | ## Deno/ESM Support
44 |
45 | As of `v7` `cliui` supports [Deno](https://github.com/denoland/deno) and
46 | [ESM](https://nodejs.org/api/esm.html#esm_ecmascript_modules):
47 |
48 | ```typescript
49 | import cliui from "https://deno.land/x/cliui/deno.ts";
50 |
51 | const ui = cliui({})
52 |
53 | ui.div('Usage: $0 [command] [options]')
54 |
55 | ui.div({
56 | text: 'Options:',
57 | padding: [2, 0, 1, 0]
58 | })
59 |
60 | ui.div({
61 | text: "-f, --file",
62 | width: 20,
63 | padding: [0, 4, 0, 4]
64 | })
65 |
66 | console.log(ui.toString())
67 | ```
68 |
69 |
70 |
71 | ## Layout DSL
72 |
73 | cliui exposes a simple layout DSL:
74 |
75 | If you create a single `ui.div`, passing a string rather than an
76 | object:
77 |
78 | * `\n`: characters will be interpreted as new rows.
79 | * `\t`: characters will be interpreted as new columns.
80 | * `\s`: characters will be interpreted as padding.
81 |
82 | **as an example...**
83 |
84 | ```js
85 | var ui = require('./')({
86 | width: 60
87 | })
88 |
89 | ui.div(
90 | 'Usage: node ./bin/foo.js\n' +
91 | ' \t provide a regex\n' +
92 | ' \t provide a glob\t [required]'
93 | )
94 |
95 | console.log(ui.toString())
96 | ```
97 |
98 | **will output:**
99 |
100 | ```shell
101 | Usage: node ./bin/foo.js
102 | provide a regex
103 | provide a glob [required]
104 | ```
105 |
106 | ## Methods
107 |
108 | ```js
109 | cliui = require('cliui')
110 | ```
111 |
112 | ### cliui({width: integer})
113 |
114 | Specify the maximum width of the UI being generated.
115 | If no width is provided, cliui will try to get the current window's width and use it, and if that doesn't work, width will be set to `80`.
116 |
117 | ### cliui({wrap: boolean})
118 |
119 | Enable or disable the wrapping of text in a column.
120 |
121 | ### cliui.div(column, column, column)
122 |
123 | Create a row with any number of columns, a column
124 | can either be a string, or an object with the following
125 | options:
126 |
127 | * **text:** some text to place in the column.
128 | * **width:** the width of a column.
129 | * **align:** alignment, `right` or `center`.
130 | * **padding:** `[top, right, bottom, left]`.
131 | * **border:** should a border be placed around the div?
132 |
133 | ### cliui.span(column, column, column)
134 |
135 | Similar to `div`, except the next row will be appended without
136 | a new line being created.
137 |
138 | ### cliui.resetOutput()
139 |
140 | Resets the UI elements of the current cliui instance, maintaining the values
141 | set for `width` and `wrap`.
142 |
--------------------------------------------------------------------------------
/build/at-rule.js:
--------------------------------------------------------------------------------
1 | let Prefixer = require('./prefixer')
2 |
3 | class AtRule extends Prefixer {
4 | /**
5 | * Clone and add prefixes for at-rule
6 | */
7 | add(rule, prefix) {
8 | let prefixed = prefix + rule.name
9 |
10 | let already = rule.parent.some(
11 | i => i.name === prefixed && i.params === rule.params
12 | )
13 | if (already) {
14 | return undefined
15 | }
16 |
17 | let cloned = this.clone(rule, { name: prefixed })
18 | return rule.parent.insertBefore(rule, cloned)
19 | }
20 |
21 | /**
22 | * Clone node with prefixes
23 | */
24 | process(node) {
25 | let parent = this.parentPrefix(node)
26 |
27 | for (let prefix of this.prefixes) {
28 | if (!parent || parent === prefix) {
29 | this.add(node, prefix)
30 | }
31 | }
32 | }
33 | }
34 |
35 | module.exports = AtRule
36 |
--------------------------------------------------------------------------------
/build/cookie-store-api.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F A B uB"},B:{"1":"W Z a b c d e f g h i j k X H","2":"C K L G M N O","194":"P Q R S T U V"},C:{"2":"0 1 2 3 4 5 6 7 8 9 vB jB I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB wB xB"},D:{"1":"W Z a b c d e f g h i j k X H nB yB zB","2":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB","194":"Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V"},E:{"2":"I l J D E F A B C K L G 0B oB 1B 2B 3B 4B pB hB iB 5B 6B 7B qB rB 8B"},F:{"1":"cB dB eB fB gB P Q R mB S T U V W","2":"0 1 2 3 4 5 6 7 8 9 F B C G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB 9B AC BC CC hB sB DC iB","194":"IB JB KB LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB"},G:{"2":"E oB EC tB FC GC HC IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB"},H:{"2":"YC"},I:{"1":"H","2":"jB I ZC aC bC cC tB dC eC"},J:{"2":"D A"},K:{"1":"Y","2":"A B C hB sB iB"},L:{"1":"H"},M:{"2":"X"},N:{"2":"A B"},O:{"2":"fC"},P:{"1":"oC pC qC","2":"I gC hC iC jC kC pB lC mC nC"},Q:{"2":"rC"},R:{"2":"sC"},S:{"2":"tC"}},B:7,C:"Cookie Store API"};
2 |
--------------------------------------------------------------------------------
/build/es.array.for-each.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var $ = require('../internals/export');
3 | var forEach = require('../internals/array-for-each');
4 |
5 | // `Array.prototype.forEach` method
6 | // https://tc39.es/ecma262/#sec-array.prototype.foreach
7 | // eslint-disable-next-line es-x/no-array-prototype-foreach -- safe
8 | $({ target: 'Array', proto: true, forced: [].forEach != forEach }, {
9 | forEach: forEach
10 | });
11 |
--------------------------------------------------------------------------------
/build/es.date.to-string.js:
--------------------------------------------------------------------------------
1 | // empty
2 |
--------------------------------------------------------------------------------
/build/filterLimit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _filter2 = require('./internal/filter.js');
8 |
9 | var _filter3 = _interopRequireDefault(_filter2);
10 |
11 | var _eachOfLimit = require('./internal/eachOfLimit.js');
12 |
13 | var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
14 |
15 | var _awaitify = require('./internal/awaitify.js');
16 |
17 | var _awaitify2 = _interopRequireDefault(_awaitify);
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 |
21 | /**
22 | * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a
23 | * time.
24 | *
25 | * @name filterLimit
26 | * @static
27 | * @memberOf module:Collections
28 | * @method
29 | * @see [async.filter]{@link module:Collections.filter}
30 | * @alias selectLimit
31 | * @category Collection
32 | * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
33 | * @param {number} limit - The maximum number of async operations at a time.
34 | * @param {Function} iteratee - A truth test to apply to each item in `coll`.
35 | * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
36 | * with a boolean argument once it has completed. Invoked with (item, callback).
37 | * @param {Function} [callback] - A callback which is called after all the
38 | * `iteratee` functions have finished. Invoked with (err, results).
39 | * @returns {Promise} a promise, if no callback provided
40 | */
41 | function filterLimit(coll, limit, iteratee, callback) {
42 | return (0, _filter3.default)((0, _eachOfLimit2.default)(limit), coll, iteratee, callback);
43 | }
44 | exports.default = (0, _awaitify2.default)(filterLimit, 4);
45 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/build/gyroscope.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F A B uB"},B:{"1":"P Q R S T U V W Z a b c d e f g h i j k X H","2":"C K L G M N O"},C:{"2":"0 1 2 3 4 5 6 7 8 9 vB jB I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB wB xB"},D:{"1":"VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB","2":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB","194":"PB kB QB lB RB SB Y TB UB"},E:{"2":"I l J D E F A B C K L G 0B oB 1B 2B 3B 4B pB hB iB 5B 6B 7B qB rB 8B"},F:{"1":"LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W","2":"0 1 2 3 4 5 6 7 8 9 F B C G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB 9B AC BC CC hB sB DC iB"},G:{"2":"E oB EC tB FC GC HC IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB"},H:{"2":"YC"},I:{"1":"H","2":"jB I ZC aC bC cC tB dC eC"},J:{"2":"D A"},K:{"1":"Y","2":"A B C hB sB iB"},L:{"1":"H"},M:{"2":"X"},N:{"2":"A B"},O:{"2":"fC"},P:{"2":"I gC hC iC jC kC pB lC mC nC oC pC qC"},Q:{"2":"rC"},R:{"2":"sC"},S:{"2":"tC"}},B:4,C:"Gyroscope"};
2 |
--------------------------------------------------------------------------------
/build/jpeg2000.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F A B uB"},B:{"2":"C K L G M N O P Q R S T U V W Z a b c d e f g h i j k X H"},C:{"2":"0 1 2 3 4 5 6 7 8 9 vB jB I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB wB xB"},D:{"2":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB"},E:{"1":"J D E F A B C K L G 2B 3B 4B pB hB iB 5B 6B 7B qB rB 8B","2":"I 0B oB","129":"l 1B"},F:{"2":"0 1 2 3 4 5 6 7 8 9 F B C G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W 9B AC BC CC hB sB DC iB"},G:{"1":"E FC GC HC IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB","2":"oB EC tB"},H:{"2":"YC"},I:{"2":"jB I H ZC aC bC cC tB dC eC"},J:{"2":"D A"},K:{"2":"A B C Y hB sB iB"},L:{"2":"H"},M:{"2":"X"},N:{"2":"A B"},O:{"2":"fC"},P:{"2":"I gC hC iC jC kC pB lC mC nC oC pC qC"},Q:{"2":"rC"},R:{"2":"sC"},S:{"2":"tC"}},B:6,C:"JPEG 2000 image format"};
2 |
--------------------------------------------------------------------------------
/build/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const align = {
3 | right: alignRight,
4 | center: alignCenter
5 | };
6 | const top = 0;
7 | const right = 1;
8 | const bottom = 2;
9 | const left = 3;
10 | export class UI {
11 | constructor(opts) {
12 | var _a;
13 | this.width = opts.width;
14 | this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;
15 | this.rows = [];
16 | }
17 | span(...args) {
18 | const cols = this.div(...args);
19 | cols.span = true;
20 | }
21 | resetOutput() {
22 | this.rows = [];
23 | }
24 | div(...args) {
25 | if (args.length === 0) {
26 | this.div('');
27 | }
28 | if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
29 | return this.applyLayoutDSL(args[0]);
30 | }
31 | const cols = args.map(arg => {
32 | if (typeof arg === 'string') {
33 | return this.colFromString(arg);
34 | }
35 | return arg;
36 | });
37 | this.rows.push(cols);
38 | return cols;
39 | }
40 | shouldApplyLayoutDSL(...args) {
41 | return args.length === 1 && typeof args[0] === 'string' &&
42 | /[\t\n]/.test(args[0]);
43 | }
44 | applyLayoutDSL(str) {
45 | const rows = str.split('\n').map(row => row.split('\t'));
46 | let leftColumnWidth = 0;
47 | // simple heuristic for layout, make sure the
48 | // second column lines up along the left-hand.
49 | // don't allow the first column to take up more
50 | // than 50% of the screen.
51 | rows.forEach(columns => {
52 | if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
53 | leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));
54 | }
55 | });
56 | // generate a table:
57 | // replacing ' ' with padding calculations.
58 | // using the algorithmically generated width.
59 | rows.forEach(columns => {
60 | this.div(...columns.map((r, i) => {
61 | return {
62 | text: r.trim(),
63 | padding: this.measurePadding(r),
64 | width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
65 | };
66 | }));
67 | });
68 | return this.rows[this.rows.length - 1];
69 | }
70 | colFromString(text) {
71 | return {
72 | text,
73 | padding: this.measurePadding(text)
74 | };
75 | }
76 | measurePadding(str) {
77 | // measure padding without ansi escape codes
78 | const noAnsi = mixin.stripAnsi(str);
79 | return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
80 | }
81 | toString() {
82 | const lines = [];
83 | this.rows.forEach(row => {
84 | this.rowToString(row, lines);
85 | });
86 | // don't display any lines with the
87 | // hidden flag set.
88 | return lines
89 | .filter(line => !line.hidden)
90 | .map(line => line.text)
91 | .join('\n');
92 | }
93 | rowToString(row, lines) {
94 | this.rasterize(row).forEach((rrow, r) => {
95 | let str = '';
96 | rrow.forEach((col, c) => {
97 | const { width } = row[c]; // the width with padding.
98 | const wrapWidth = this.negatePadding(row[c]); // the width without padding.
99 | let ts = col; // temporary string used during alignment/padding.
100 | if (wrapWidth > mixin.stringWidth(col)) {
101 | ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));
102 | }
103 | // align the string within its column.
104 | if (row[c].align && row[c].align !== 'left' && this.wrap) {
105 | const fn = align[row[c].align];
106 | ts = fn(ts, wrapWidth);
107 | if (mixin.stringWidth(ts) < wrapWidth) {
108 | ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);
109 | }
110 | }
111 | // apply border and padding to string.
112 | const padding = row[c].padding || [0, 0, 0, 0];
113 | if (padding[left]) {
114 | str += ' '.repeat(padding[left]);
115 | }
116 | str += addBorder(row[c], ts, '| ');
117 | str += ts;
118 | str += addBorder(row[c], ts, ' |');
119 | if (padding[right]) {
120 | str += ' '.repeat(padding[right]);
121 | }
122 | // if prior row is span, try to render the
123 | // current row on the prior line.
124 | if (r === 0 && lines.length > 0) {
125 | str = this.renderInline(str, lines[lines.length - 1]);
126 | }
127 | });
128 | // remove trailing whitespace.
129 | lines.push({
130 | text: str.replace(/ +$/, ''),
131 | span: row.span
132 | });
133 | });
134 | return lines;
135 | }
136 | // if the full 'source' can render in
137 | // the target line, do so.
138 | renderInline(source, previousLine) {
139 | const match = source.match(/^ */);
140 | const leadingWhitespace = match ? match[0].length : 0;
141 | const target = previousLine.text;
142 | const targetTextWidth = mixin.stringWidth(target.trimRight());
143 | if (!previousLine.span) {
144 | return source;
145 | }
146 | // if we're not applying wrapping logic,
147 | // just always append to the span.
148 | if (!this.wrap) {
149 | previousLine.hidden = true;
150 | return target + source;
151 | }
152 | if (leadingWhitespace < targetTextWidth) {
153 | return source;
154 | }
155 | previousLine.hidden = true;
156 | return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();
157 | }
158 | rasterize(row) {
159 | const rrows = [];
160 | const widths = this.columnWidths(row);
161 | let wrapped;
162 | // word wrap all columns, and create
163 | // a data-structure that is easy to rasterize.
164 | row.forEach((col, c) => {
165 | // leave room for left and right padding.
166 | col.width = widths[c];
167 | if (this.wrap) {
168 | wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');
169 | }
170 | else {
171 | wrapped = col.text.split('\n');
172 | }
173 | if (col.border) {
174 | wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');
175 | wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");
176 | }
177 | // add top and bottom padding.
178 | if (col.padding) {
179 | wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));
180 | wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));
181 | }
182 | wrapped.forEach((str, r) => {
183 | if (!rrows[r]) {
184 | rrows.push([]);
185 | }
186 | const rrow = rrows[r];
187 | for (let i = 0; i < c; i++) {
188 | if (rrow[i] === undefined) {
189 | rrow.push('');
190 | }
191 | }
192 | rrow.push(str);
193 | });
194 | });
195 | return rrows;
196 | }
197 | negatePadding(col) {
198 | let wrapWidth = col.width || 0;
199 | if (col.padding) {
200 | wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);
201 | }
202 | if (col.border) {
203 | wrapWidth -= 4;
204 | }
205 | return wrapWidth;
206 | }
207 | columnWidths(row) {
208 | if (!this.wrap) {
209 | return row.map(col => {
210 | return col.width || mixin.stringWidth(col.text);
211 | });
212 | }
213 | let unset = row.length;
214 | let remainingWidth = this.width;
215 | // column widths can be set in config.
216 | const widths = row.map(col => {
217 | if (col.width) {
218 | unset--;
219 | remainingWidth -= col.width;
220 | return col.width;
221 | }
222 | return undefined;
223 | });
224 | // any unset widths should be calculated.
225 | const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;
226 | return widths.map((w, i) => {
227 | if (w === undefined) {
228 | return Math.max(unsetWidth, _minWidth(row[i]));
229 | }
230 | return w;
231 | });
232 | }
233 | }
234 | function addBorder(col, ts, style) {
235 | if (col.border) {
236 | if (/[.']-+[.']/.test(ts)) {
237 | return '';
238 | }
239 | if (ts.trim().length !== 0) {
240 | return style;
241 | }
242 | return ' ';
243 | }
244 | return '';
245 | }
246 | // calculates the minimum width of
247 | // a column, based on padding preferences.
248 | function _minWidth(col) {
249 | const padding = col.padding || [];
250 | const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);
251 | if (col.border) {
252 | return minWidth + 4;
253 | }
254 | return minWidth;
255 | }
256 | function getWindowWidth() {
257 | /* istanbul ignore next: depends on terminal */
258 | if (typeof process === 'object' && process.stdout && process.stdout.columns) {
259 | return process.stdout.columns;
260 | }
261 | return 80;
262 | }
263 | function alignRight(str, width) {
264 | str = str.trim();
265 | const strWidth = mixin.stringWidth(str);
266 | if (strWidth < width) {
267 | return ' '.repeat(width - strWidth) + str;
268 | }
269 | return str;
270 | }
271 | function alignCenter(str, width) {
272 | str = str.trim();
273 | const strWidth = mixin.stringWidth(str);
274 | /* istanbul ignore next */
275 | if (strWidth >= width) {
276 | return str;
277 | }
278 | return ' '.repeat((width - strWidth) >> 1) + str;
279 | }
280 | let mixin;
281 | export function cliui(opts, _mixin) {
282 | mixin = _mixin;
283 | return new UI({
284 | width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),
285 | wrap: opts === null || opts === void 0 ? void 0 : opts.wrap
286 | });
287 | }
288 |
--------------------------------------------------------------------------------
/build/lib/string-utils.js:
--------------------------------------------------------------------------------
1 | // Minimal replacement for ansi string helpers "wrap-ansi" and "strip-ansi".
2 | // to facilitate ESM and Deno modules.
3 | // TODO: look at porting https://www.npmjs.com/package/wrap-ansi to ESM.
4 | // The npm application
5 | // Copyright (c) npm, Inc. and Contributors
6 | // Licensed on the terms of The Artistic License 2.0
7 | // See: https://github.com/npm/cli/blob/4c65cd952bc8627811735bea76b9b110cc4fc80e/lib/utils/ansi-trim.js
8 | const ansi = new RegExp('\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|' +
9 | '\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)', 'g');
10 | export function stripAnsi(str) {
11 | return str.replace(ansi, '');
12 | }
13 | export function wrap(str, width) {
14 | const [start, end] = str.match(ansi) || ['', ''];
15 | str = stripAnsi(str);
16 | let wrapped = '';
17 | for (let i = 0; i < str.length; i++) {
18 | if (i !== 0 && (i % width) === 0) {
19 | wrapped += '\n';
20 | }
21 | wrapped += str.charAt(i);
22 | }
23 | if (start && end) {
24 | wrapped = `${start}${wrapped}${end}`;
25 | }
26 | return wrapped;
27 | }
28 |
--------------------------------------------------------------------------------
/build/lua.json:
--------------------------------------------------------------------------------
1 | {
2 | "blacklist_client.lua": "local blacklist = ARGV[num_static_argv + 1]\n\nif redis.call('zscore', client_last_seen_key, blacklist) then\n redis.call('zadd', client_last_seen_key, 0, blacklist)\nend\n\n\nreturn {}\n",
3 | "check.lua": "local weight = tonumber(ARGV[num_static_argv + 1])\n\nlocal capacity = process_tick(now, false)['capacity']\nlocal nextRequest = tonumber(redis.call('hget', settings_key, 'nextRequest'))\n\nreturn conditions_check(capacity, weight) and nextRequest - now <= 0\n",
4 | "conditions_check.lua": "local conditions_check = function (capacity, weight)\n return capacity == nil or weight <= capacity\nend\n",
5 | "current_reservoir.lua": "return process_tick(now, false)['reservoir']\n",
6 | "done.lua": "process_tick(now, false)\n\nreturn tonumber(redis.call('hget', settings_key, 'done'))\n",
7 | "free.lua": "local index = ARGV[num_static_argv + 1]\n\nredis.call('zadd', job_expirations_key, 0, index)\n\nreturn process_tick(now, false)['running']\n",
8 | "get_time.lua": "redis.replicate_commands()\n\nlocal get_time = function ()\n local time = redis.call('time')\n\n return tonumber(time[1]..string.sub(time[2], 1, 3))\nend\n",
9 | "group_check.lua": "return not (redis.call('exists', settings_key) == 1)\n",
10 | "heartbeat.lua": "process_tick(now, true)\n",
11 | "increment_reservoir.lua": "local incr = tonumber(ARGV[num_static_argv + 1])\n\nredis.call('hincrby', settings_key, 'reservoir', incr)\n\nlocal reservoir = process_tick(now, true)['reservoir']\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(0, 0, groupTimeout)\n\nreturn reservoir\n",
12 | "init.lua": "local clear = tonumber(ARGV[num_static_argv + 1])\nlocal limiter_version = ARGV[num_static_argv + 2]\nlocal num_local_argv = num_static_argv + 2\n\nif clear == 1 then\n redis.call('del', unpack(KEYS))\nend\n\nif redis.call('exists', settings_key) == 0 then\n -- Create\n local args = {'hmset', settings_key}\n\n for i = num_local_argv + 1, #ARGV do\n table.insert(args, ARGV[i])\n end\n\n redis.call(unpack(args))\n redis.call('hmset', settings_key,\n 'nextRequest', now,\n 'lastReservoirRefresh', now,\n 'lastReservoirIncrease', now,\n 'running', 0,\n 'done', 0,\n 'unblockTime', 0,\n 'capacityPriorityCounter', 0\n )\n\nelse\n -- Apply migrations\n local settings = redis.call('hmget', settings_key,\n 'id',\n 'version'\n )\n local id = settings[1]\n local current_version = settings[2]\n\n if current_version ~= limiter_version then\n local version_digits = {}\n for k, v in string.gmatch(current_version, \"([^.]+)\") do\n table.insert(version_digits, tonumber(k))\n end\n\n -- 2.10.0\n if version_digits[2] < 10 then\n redis.call('hsetnx', settings_key, 'reservoirRefreshInterval', '')\n redis.call('hsetnx', settings_key, 'reservoirRefreshAmount', '')\n redis.call('hsetnx', settings_key, 'lastReservoirRefresh', '')\n redis.call('hsetnx', settings_key, 'done', 0)\n redis.call('hset', settings_key, 'version', '2.10.0')\n end\n\n -- 2.11.1\n if version_digits[2] < 11 or (version_digits[2] == 11 and version_digits[3] < 1) then\n if redis.call('hstrlen', settings_key, 'lastReservoirRefresh') == 0 then\n redis.call('hmset', settings_key,\n 'lastReservoirRefresh', now,\n 'version', '2.11.1'\n )\n end\n end\n\n -- 2.14.0\n if version_digits[2] < 14 then\n local old_running_key = 'b_'..id..'_running'\n local old_executing_key = 'b_'..id..'_executing'\n\n if redis.call('exists', old_running_key) == 1 then\n redis.call('rename', old_running_key, job_weights_key)\n end\n if redis.call('exists', old_executing_key) == 1 then\n redis.call('rename', old_executing_key, job_expirations_key)\n end\n redis.call('hset', settings_key, 'version', '2.14.0')\n end\n\n -- 2.15.2\n if version_digits[2] < 15 or (version_digits[2] == 15 and version_digits[3] < 2) then\n redis.call('hsetnx', settings_key, 'capacityPriorityCounter', 0)\n redis.call('hset', settings_key, 'version', '2.15.2')\n end\n\n -- 2.17.0\n if version_digits[2] < 17 then\n redis.call('hsetnx', settings_key, 'clientTimeout', 10000)\n redis.call('hset', settings_key, 'version', '2.17.0')\n end\n\n -- 2.18.0\n if version_digits[2] < 18 then\n redis.call('hsetnx', settings_key, 'reservoirIncreaseInterval', '')\n redis.call('hsetnx', settings_key, 'reservoirIncreaseAmount', '')\n redis.call('hsetnx', settings_key, 'reservoirIncreaseMaximum', '')\n redis.call('hsetnx', settings_key, 'lastReservoirIncrease', now)\n redis.call('hset', settings_key, 'version', '2.18.0')\n end\n\n end\n\n process_tick(now, false)\nend\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(0, 0, groupTimeout)\n\nreturn {}\n",
13 | "process_tick.lua": "local process_tick = function (now, always_publish)\n\n local compute_capacity = function (maxConcurrent, running, reservoir)\n if maxConcurrent ~= nil and reservoir ~= nil then\n return math.min((maxConcurrent - running), reservoir)\n elseif maxConcurrent ~= nil then\n return maxConcurrent - running\n elseif reservoir ~= nil then\n return reservoir\n else\n return nil\n end\n end\n\n local settings = redis.call('hmget', settings_key,\n 'id',\n 'maxConcurrent',\n 'running',\n 'reservoir',\n 'reservoirRefreshInterval',\n 'reservoirRefreshAmount',\n 'lastReservoirRefresh',\n 'reservoirIncreaseInterval',\n 'reservoirIncreaseAmount',\n 'reservoirIncreaseMaximum',\n 'lastReservoirIncrease',\n 'capacityPriorityCounter',\n 'clientTimeout'\n )\n local id = settings[1]\n local maxConcurrent = tonumber(settings[2])\n local running = tonumber(settings[3])\n local reservoir = tonumber(settings[4])\n local reservoirRefreshInterval = tonumber(settings[5])\n local reservoirRefreshAmount = tonumber(settings[6])\n local lastReservoirRefresh = tonumber(settings[7])\n local reservoirIncreaseInterval = tonumber(settings[8])\n local reservoirIncreaseAmount = tonumber(settings[9])\n local reservoirIncreaseMaximum = tonumber(settings[10])\n local lastReservoirIncrease = tonumber(settings[11])\n local capacityPriorityCounter = tonumber(settings[12])\n local clientTimeout = tonumber(settings[13])\n\n local initial_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n --\n -- Process 'running' changes\n --\n local expired = redis.call('zrangebyscore', job_expirations_key, '-inf', '('..now)\n\n if #expired > 0 then\n redis.call('zremrangebyscore', job_expirations_key, '-inf', '('..now)\n\n local flush_batch = function (batch, acc)\n local weights = redis.call('hmget', job_weights_key, unpack(batch))\n redis.call('hdel', job_weights_key, unpack(batch))\n local clients = redis.call('hmget', job_clients_key, unpack(batch))\n redis.call('hdel', job_clients_key, unpack(batch))\n\n -- Calculate sum of removed weights\n for i = 1, #weights do\n acc['total'] = acc['total'] + (tonumber(weights[i]) or 0)\n end\n\n -- Calculate sum of removed weights by client\n local client_weights = {}\n for i = 1, #clients do\n local removed = tonumber(weights[i]) or 0\n if removed > 0 then\n acc['client_weights'][clients[i]] = (acc['client_weights'][clients[i]] or 0) + removed\n end\n end\n end\n\n local acc = {\n ['total'] = 0,\n ['client_weights'] = {}\n }\n local batch_size = 1000\n\n -- Compute changes to Zsets and apply changes to Hashes\n for i = 1, #expired, batch_size do\n local batch = {}\n for j = i, math.min(i + batch_size - 1, #expired) do\n table.insert(batch, expired[j])\n end\n\n flush_batch(batch, acc)\n end\n\n -- Apply changes to Zsets\n if acc['total'] > 0 then\n redis.call('hincrby', settings_key, 'done', acc['total'])\n running = tonumber(redis.call('hincrby', settings_key, 'running', -acc['total']))\n end\n\n for client, weight in pairs(acc['client_weights']) do\n redis.call('zincrby', client_running_key, -weight, client)\n end\n end\n\n --\n -- Process 'reservoir' changes\n --\n local reservoirRefreshActive = reservoirRefreshInterval ~= nil and reservoirRefreshAmount ~= nil\n if reservoirRefreshActive and now >= lastReservoirRefresh + reservoirRefreshInterval then\n reservoir = reservoirRefreshAmount\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'lastReservoirRefresh', now\n )\n end\n\n local reservoirIncreaseActive = reservoirIncreaseInterval ~= nil and reservoirIncreaseAmount ~= nil\n if reservoirIncreaseActive and now >= lastReservoirIncrease + reservoirIncreaseInterval then\n local num_intervals = math.floor((now - lastReservoirIncrease) / reservoirIncreaseInterval)\n local incr = reservoirIncreaseAmount * num_intervals\n if reservoirIncreaseMaximum ~= nil then\n incr = math.min(incr, reservoirIncreaseMaximum - (reservoir or 0))\n end\n if incr > 0 then\n reservoir = (reservoir or 0) + incr\n end\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'lastReservoirIncrease', lastReservoirIncrease + (num_intervals * reservoirIncreaseInterval)\n )\n end\n\n --\n -- Clear unresponsive clients\n --\n local unresponsive = redis.call('zrangebyscore', client_last_seen_key, '-inf', (now - clientTimeout))\n local unresponsive_lookup = {}\n local terminated_clients = {}\n for i = 1, #unresponsive do\n unresponsive_lookup[unresponsive[i]] = true\n if tonumber(redis.call('zscore', client_running_key, unresponsive[i])) == 0 then\n table.insert(terminated_clients, unresponsive[i])\n end\n end\n if #terminated_clients > 0 then\n redis.call('zrem', client_running_key, unpack(terminated_clients))\n redis.call('hdel', client_num_queued_key, unpack(terminated_clients))\n redis.call('zrem', client_last_registered_key, unpack(terminated_clients))\n redis.call('zrem', client_last_seen_key, unpack(terminated_clients))\n end\n\n --\n -- Broadcast capacity changes\n --\n local final_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n if always_publish or (initial_capacity ~= nil and final_capacity == nil) then\n -- always_publish or was not unlimited, now unlimited\n redis.call('publish', 'b_'..id, 'capacity:'..(final_capacity or ''))\n\n elseif initial_capacity ~= nil and final_capacity ~= nil and final_capacity > initial_capacity then\n -- capacity was increased\n -- send the capacity message to the limiter having the lowest number of running jobs\n -- the tiebreaker is the limiter having not registered a job in the longest time\n\n local lowest_concurrency_value = nil\n local lowest_concurrency_clients = {}\n local lowest_concurrency_last_registered = {}\n local client_concurrencies = redis.call('zrange', client_running_key, 0, -1, 'withscores')\n\n for i = 1, #client_concurrencies, 2 do\n local client = client_concurrencies[i]\n local concurrency = tonumber(client_concurrencies[i+1])\n\n if (\n lowest_concurrency_value == nil or lowest_concurrency_value == concurrency\n ) and (\n not unresponsive_lookup[client]\n ) and (\n tonumber(redis.call('hget', client_num_queued_key, client)) > 0\n ) then\n lowest_concurrency_value = concurrency\n table.insert(lowest_concurrency_clients, client)\n local last_registered = tonumber(redis.call('zscore', client_last_registered_key, client))\n table.insert(lowest_concurrency_last_registered, last_registered)\n end\n end\n\n if #lowest_concurrency_clients > 0 then\n local position = 1\n local earliest = lowest_concurrency_last_registered[1]\n\n for i,v in ipairs(lowest_concurrency_last_registered) do\n if v < earliest then\n position = i\n earliest = v\n end\n end\n\n local next_client = lowest_concurrency_clients[position]\n redis.call('publish', 'b_'..id,\n 'capacity-priority:'..(final_capacity or '')..\n ':'..next_client..\n ':'..capacityPriorityCounter\n )\n redis.call('hincrby', settings_key, 'capacityPriorityCounter', '1')\n else\n redis.call('publish', 'b_'..id, 'capacity:'..(final_capacity or ''))\n end\n end\n\n return {\n ['capacity'] = final_capacity,\n ['running'] = running,\n ['reservoir'] = reservoir\n }\nend\n",
14 | "queued.lua": "local clientTimeout = tonumber(redis.call('hget', settings_key, 'clientTimeout'))\nlocal valid_clients = redis.call('zrangebyscore', client_last_seen_key, (now - clientTimeout), 'inf')\nlocal client_queued = redis.call('hmget', client_num_queued_key, unpack(valid_clients))\n\nlocal sum = 0\nfor i = 1, #client_queued do\n sum = sum + tonumber(client_queued[i])\nend\n\nreturn sum\n",
15 | "refresh_expiration.lua": "local refresh_expiration = function (now, nextRequest, groupTimeout)\n\n if groupTimeout ~= nil then\n local ttl = (nextRequest + groupTimeout) - now\n\n for i = 1, #KEYS do\n redis.call('pexpire', KEYS[i], ttl)\n end\n end\n\nend\n",
16 | "refs.lua": "local settings_key = KEYS[1]\nlocal job_weights_key = KEYS[2]\nlocal job_expirations_key = KEYS[3]\nlocal job_clients_key = KEYS[4]\nlocal client_running_key = KEYS[5]\nlocal client_num_queued_key = KEYS[6]\nlocal client_last_registered_key = KEYS[7]\nlocal client_last_seen_key = KEYS[8]\n\nlocal now = tonumber(ARGV[1])\nlocal client = ARGV[2]\n\nlocal num_static_argv = 2\n",
17 | "register.lua": "local index = ARGV[num_static_argv + 1]\nlocal weight = tonumber(ARGV[num_static_argv + 2])\nlocal expiration = tonumber(ARGV[num_static_argv + 3])\n\nlocal state = process_tick(now, false)\nlocal capacity = state['capacity']\nlocal reservoir = state['reservoir']\n\nlocal settings = redis.call('hmget', settings_key,\n 'nextRequest',\n 'minTime',\n 'groupTimeout'\n)\nlocal nextRequest = tonumber(settings[1])\nlocal minTime = tonumber(settings[2])\nlocal groupTimeout = tonumber(settings[3])\n\nif conditions_check(capacity, weight) then\n\n redis.call('hincrby', settings_key, 'running', weight)\n redis.call('hset', job_weights_key, index, weight)\n if expiration ~= nil then\n redis.call('zadd', job_expirations_key, now + expiration, index)\n end\n redis.call('hset', job_clients_key, index, client)\n redis.call('zincrby', client_running_key, weight, client)\n redis.call('hincrby', client_num_queued_key, client, -1)\n redis.call('zadd', client_last_registered_key, now, client)\n\n local wait = math.max(nextRequest - now, 0)\n local newNextRequest = now + wait + minTime\n\n if reservoir == nil then\n redis.call('hset', settings_key,\n 'nextRequest', newNextRequest\n )\n else\n reservoir = reservoir - weight\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'nextRequest', newNextRequest\n )\n end\n\n refresh_expiration(now, newNextRequest, groupTimeout)\n\n return {true, wait, reservoir}\n\nelse\n return {false}\nend\n",
18 | "register_client.lua": "local queued = tonumber(ARGV[num_static_argv + 1])\n\n-- Could have been re-registered concurrently\nif not redis.call('zscore', client_last_seen_key, client) then\n redis.call('zadd', client_running_key, 0, client)\n redis.call('hset', client_num_queued_key, client, queued)\n redis.call('zadd', client_last_registered_key, 0, client)\nend\n\nredis.call('zadd', client_last_seen_key, now, client)\n\nreturn {}\n",
19 | "running.lua": "return process_tick(now, false)['running']\n",
20 | "submit.lua": "local queueLength = tonumber(ARGV[num_static_argv + 1])\nlocal weight = tonumber(ARGV[num_static_argv + 2])\n\nlocal capacity = process_tick(now, false)['capacity']\n\nlocal settings = redis.call('hmget', settings_key,\n 'id',\n 'maxConcurrent',\n 'highWater',\n 'nextRequest',\n 'strategy',\n 'unblockTime',\n 'penalty',\n 'minTime',\n 'groupTimeout'\n)\nlocal id = settings[1]\nlocal maxConcurrent = tonumber(settings[2])\nlocal highWater = tonumber(settings[3])\nlocal nextRequest = tonumber(settings[4])\nlocal strategy = tonumber(settings[5])\nlocal unblockTime = tonumber(settings[6])\nlocal penalty = tonumber(settings[7])\nlocal minTime = tonumber(settings[8])\nlocal groupTimeout = tonumber(settings[9])\n\nif maxConcurrent ~= nil and weight > maxConcurrent then\n return redis.error_reply('OVERWEIGHT:'..weight..':'..maxConcurrent)\nend\n\nlocal reachedHWM = (highWater ~= nil and queueLength == highWater\n and not (\n conditions_check(capacity, weight)\n and nextRequest - now <= 0\n )\n)\n\nlocal blocked = strategy == 3 and (reachedHWM or unblockTime >= now)\n\nif blocked then\n local computedPenalty = penalty\n if computedPenalty == nil then\n if minTime == 0 then\n computedPenalty = 5000\n else\n computedPenalty = 15 * minTime\n end\n end\n\n local newNextRequest = now + computedPenalty + minTime\n\n redis.call('hmset', settings_key,\n 'unblockTime', now + computedPenalty,\n 'nextRequest', newNextRequest\n )\n\n local clients_queued_reset = redis.call('hkeys', client_num_queued_key)\n local queued_reset = {}\n for i = 1, #clients_queued_reset do\n table.insert(queued_reset, clients_queued_reset[i])\n table.insert(queued_reset, 0)\n end\n redis.call('hmset', client_num_queued_key, unpack(queued_reset))\n\n redis.call('publish', 'b_'..id, 'blocked:')\n\n refresh_expiration(now, newNextRequest, groupTimeout)\nend\n\nif not blocked and not reachedHWM then\n redis.call('hincrby', client_num_queued_key, client, 1)\nend\n\nreturn {reachedHWM, blocked, strategy}\n",
21 | "update_settings.lua": "local args = {'hmset', settings_key}\n\nfor i = num_static_argv + 1, #ARGV do\n table.insert(args, ARGV[i])\nend\n\nredis.call(unpack(args))\n\nprocess_tick(now, true)\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(0, 0, groupTimeout)\n\nreturn {}\n",
22 | "validate_client.lua": "if not redis.call('zscore', client_last_seen_key, client) then\n return redis.error_reply('UNKNOWN_CLIENT')\nend\n\nredis.call('zadd', client_last_seen_key, now, client)\n",
23 | "validate_keys.lua": "if not (redis.call('exists', settings_key) == 1) then\n return redis.error_reply('SETTINGS_KEY_NOT_FOUND')\nend\n"
24 | }
25 |
--------------------------------------------------------------------------------
/build/meterRole.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
4 |
5 | _Object$defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 |
9 | exports.default = void 0;
10 | var meterRole = {
11 | abstract: false,
12 | accessibleNameRequired: true,
13 | baseConcepts: [],
14 | childrenPresentational: true,
15 | nameFrom: ['author'],
16 | prohibitedProps: [],
17 | props: {},
18 | relatedConcepts: [],
19 | requireContextRole: [],
20 | requiredContextRole: [],
21 | requiredOwnedElements: [],
22 | requiredProps: {
23 | 'aria-valuemax': null,
24 | 'aria-valuemin': null,
25 | 'aria-valuenow': null
26 | },
27 | superClass: [['roletype', 'structure', 'range']]
28 | };
29 | var _default = meterRole;
30 | exports.default = _default;
--------------------------------------------------------------------------------
/build/parseHeaders.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import utils from './../utils.js';
4 |
5 | // RawAxiosHeaders whose duplicates are ignored by node
6 | // c.f. https://nodejs.org/api/http.html#http_message_headers
7 | const ignoreDuplicateOf = utils.toObjectSet([
8 | 'age', 'authorization', 'content-length', 'content-type', 'etag',
9 | 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',
10 | 'last-modified', 'location', 'max-forwards', 'proxy-authorization',
11 | 'referer', 'retry-after', 'user-agent'
12 | ]);
13 |
14 | /**
15 | * Parse headers into an object
16 | *
17 | * ```
18 | * Date: Wed, 27 Aug 2014 08:58:49 GMT
19 | * Content-Type: application/json
20 | * Connection: keep-alive
21 | * Transfer-Encoding: chunked
22 | * ```
23 | *
24 | * @param {String} rawHeaders Headers needing to be parsed
25 | *
26 | * @returns {Object} Headers parsed into an object
27 | */
28 | export default rawHeaders => {
29 | const parsed = {};
30 | let key;
31 | let val;
32 | let i;
33 |
34 | rawHeaders && rawHeaders.split('\n').forEach(function parser(line) {
35 | i = line.indexOf(':');
36 | key = line.substring(0, i).trim().toLowerCase();
37 | val = line.substring(i + 1).trim();
38 |
39 | if (!key || (parsed[key] && ignoreDuplicateOf[key])) {
40 | return;
41 | }
42 |
43 | if (key === 'set-cookie') {
44 | if (parsed[key]) {
45 | parsed[key].push(val);
46 | } else {
47 | parsed[key] = [val];
48 | }
49 | } else {
50 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
51 | }
52 | });
53 |
54 | return parsed;
55 | };
56 |
--------------------------------------------------------------------------------
/build/passive-event-listener.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F A B uB"},B:{"1":"M N O P Q R S T U V W Z a b c d e f g h i j k X H","2":"C K L G"},C:{"1":"GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB","2":"0 1 2 3 4 5 6 7 8 9 vB jB I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB wB xB"},D:{"1":"IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB","2":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB"},E:{"1":"A B C K L G pB hB iB 5B 6B 7B qB rB 8B","2":"I l J D E F 0B oB 1B 2B 3B 4B"},F:{"1":"5 6 7 8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W","2":"0 1 2 3 4 F B C G M N O m n o p q r s t u v w x y z 9B AC BC CC hB sB DC iB"},G:{"1":"LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB","2":"E oB EC tB FC GC HC IC JC KC"},H:{"2":"YC"},I:{"1":"H","2":"jB I ZC aC bC cC tB dC eC"},J:{"2":"D A"},K:{"1":"Y","2":"A B C hB sB iB"},L:{"1":"H"},M:{"1":"X"},N:{"2":"A B"},O:{"1":"fC"},P:{"1":"gC hC iC jC kC pB lC mC nC oC pC qC","2":"I"},Q:{"2":"rC"},R:{"2":"sC"},S:{"2":"tC"}},B:1,C:"Passive event listeners"};
2 |
--------------------------------------------------------------------------------
/build/pointer-events.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"1":"B","2":"J D E F A uB"},B:{"1":"C K L G M N O P Q R S T U V W Z a b c d e f g h i j k X H"},C:{"1":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB xB","2":"vB jB wB"},D:{"1":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB"},E:{"1":"I l J D E F A B C K L G 1B 2B 3B 4B pB hB iB 5B 6B 7B qB rB 8B","2":"0B oB"},F:{"1":"0 1 2 3 4 5 6 7 8 9 G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W","2":"F B C 9B AC BC CC hB sB DC iB"},G:{"1":"E oB EC tB FC GC HC IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB"},H:{"2":"YC"},I:{"1":"jB I H ZC aC bC cC tB dC eC"},J:{"1":"D A"},K:{"1":"Y","2":"A B C hB sB iB"},L:{"1":"H"},M:{"1":"X"},N:{"1":"B","2":"A"},O:{"1":"fC"},P:{"1":"I gC hC iC jC kC pB lC mC nC oC pC qC"},Q:{"1":"rC"},R:{"1":"sC"},S:{"1":"tC"}},B:7,C:"CSS pointer-events (for HTML)"};
2 |
--------------------------------------------------------------------------------
/build/register.lua:
--------------------------------------------------------------------------------
1 | local index = ARGV[num_static_argv + 1]
2 | local weight = tonumber(ARGV[num_static_argv + 2])
3 | local expiration = tonumber(ARGV[num_static_argv + 3])
4 |
5 | local state = process_tick(now, false)
6 | local capacity = state['capacity']
7 | local reservoir = state['reservoir']
8 |
9 | local settings = redis.call('hmget', settings_key,
10 | 'nextRequest',
11 | 'minTime',
12 | 'groupTimeout'
13 | )
14 | local nextRequest = tonumber(settings[1])
15 | local minTime = tonumber(settings[2])
16 | local groupTimeout = tonumber(settings[3])
17 |
18 | if conditions_check(capacity, weight) then
19 |
20 | redis.call('hincrby', settings_key, 'running', weight)
21 | redis.call('hset', job_weights_key, index, weight)
22 | if expiration ~= nil then
23 | redis.call('zadd', job_expirations_key, now + expiration, index)
24 | end
25 | redis.call('hset', job_clients_key, index, client)
26 | redis.call('zincrby', client_running_key, weight, client)
27 | redis.call('hincrby', client_num_queued_key, client, -1)
28 | redis.call('zadd', client_last_registered_key, now, client)
29 |
30 | local wait = math.max(nextRequest - now, 0)
31 | local newNextRequest = now + wait + minTime
32 |
33 | if reservoir == nil then
34 | redis.call('hset', settings_key,
35 | 'nextRequest', newNextRequest
36 | )
37 | else
38 | reservoir = reservoir - weight
39 | redis.call('hmset', settings_key,
40 | 'reservoir', reservoir,
41 | 'nextRequest', newNextRequest
42 | )
43 | end
44 |
45 | refresh_expiration(now, newNextRequest, groupTimeout)
46 |
47 | return {true, wait, reservoir}
48 |
49 | else
50 | return {false}
51 | end
52 |
--------------------------------------------------------------------------------
/build/string-padding.js:
--------------------------------------------------------------------------------
1 | // https://github.com/tc39/proposal-string-pad-start-end
2 | require('../modules/es.string.pad-end');
3 | require('../modules/es.string.pad-start');
4 |
--------------------------------------------------------------------------------
/build/sxg.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F A B uB"},B:{"1":"P Q R S T U V W Z a b c d e f g h i j k X H","2":"C K L G M N O"},C:{"2":"0 1 2 3 4 5 6 7 8 9 vB jB I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB wB xB"},D:{"1":"bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB","2":"0 1 2 3 4 5 6 7 8 9 I l J D E F A B C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB","132":"ZB aB"},E:{"2":"I l J D E F A B C K L G 0B oB 1B 2B 3B 4B pB hB iB 5B 6B 7B qB rB 8B"},F:{"1":"Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W","2":"0 1 2 3 4 5 6 7 8 9 F B C G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB 9B AC BC CC hB sB DC iB"},G:{"2":"E oB EC tB FC GC HC IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB"},H:{"2":"YC"},I:{"1":"H","2":"jB I ZC aC bC cC tB dC eC"},J:{"2":"D A"},K:{"1":"Y","2":"A B C hB sB iB"},L:{"1":"H"},M:{"2":"X"},N:{"2":"A B"},O:{"16":"fC"},P:{"1":"lC mC nC oC pC qC","2":"I gC hC iC jC kC pB"},Q:{"16":"rC"},R:{"16":"sC"},S:{"2":"tC"}},B:6,C:"Signed HTTP Exchanges (SXG)"};
2 |
--------------------------------------------------------------------------------
/build/xhr2.js:
--------------------------------------------------------------------------------
1 | module.exports={A:{A:{"2":"J D E F uB","132":"A B"},B:{"1":"C K L G M N O P Q R S T U V W Z a b c d e f g h i j k X H"},C:{"1":"0 1 2 3 4 5 6 7 8 9 C K L G M N O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W Z a b c d e f g h i j k X H nB","2":"vB jB","260":"A B","388":"J D E F","900":"I l wB xB"},D:{"1":"0 1 2 3 4 5 6 7 8 9 y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB kB QB lB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R S T U V W Z a b c d e f g h i j k X H nB yB zB","16":"I l J","132":"w x","388":"D E F A B C K L G M N O m n o p q r s t u v"},E:{"1":"E F A B C K L G 3B 4B pB hB iB 5B 6B 7B qB rB 8B","2":"I 0B oB","132":"D 2B","388":"l J 1B"},F:{"1":"0 1 2 3 4 5 6 7 8 9 C O m n o p q r s t u v w x y z AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB Y TB UB VB WB XB YB ZB aB bB cB dB eB fB gB P Q R mB S T U V W iB","2":"F B 9B AC BC CC hB sB DC","132":"G M N"},G:{"1":"E IC JC KC LC MC NC OC PC QC RC SC TC UC VC WC XC qB rB","2":"oB EC tB","132":"HC","388":"FC GC"},H:{"2":"YC"},I:{"1":"H eC","2":"ZC aC bC","388":"dC","900":"jB I cC tB"},J:{"132":"A","388":"D"},K:{"1":"C Y iB","2":"A B hB sB"},L:{"1":"H"},M:{"1":"X"},N:{"132":"A B"},O:{"1":"fC"},P:{"1":"I gC hC iC jC kC pB lC mC nC oC pC qC"},Q:{"1":"rC"},R:{"1":"sC"},S:{"1":"tC"}},B:1,C:"XMLHttpRequest advanced features"};
2 |
--------------------------------------------------------------------------------
/index.mjs:
--------------------------------------------------------------------------------
1 | // Bootstrap cliui with CommonJS dependencies:
2 | import { cliui } from './build/lib/index.js'
3 | import { wrap, stripAnsi } from './build/lib/string-utils.js'
4 |
5 | export default function ui (opts) {
6 | return cliui(opts, {
7 | stringWidth: (str) => {
8 | return [...str].length
9 | },
10 | stripAnsi,
11 | wrap
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/lib/arg.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const
4 | CoaParam = require('./coaparam'),
5 | chalk = require('chalk');
6 |
7 | /**
8 | * Argument
9 | *
10 | * Unnamed entity. From command line arguments passed as list of unnamed values.
11 | *
12 | * @class Arg
13 | * @extends CoaParam
14 | */
15 | module.exports = class Arg extends CoaParam {
16 | /**
17 | * @constructs
18 | * @param {COA.Cmd} cmd - parent command
19 | */
20 | constructor(cmd) {
21 | super(cmd);
22 |
23 | this._cmd._args.push(this);
24 | }
25 |
26 | _saveVal(args, val) {
27 | this._val && (val = this._val(val));
28 |
29 | const name = this._name;
30 | this._arr
31 | ? (args[name] || (args[name] = [])).push(val)
32 | : (args[name] = val);
33 |
34 | return val;
35 | }
36 |
37 | _parse(arg, args) {
38 | return this._saveVal(args, arg);
39 | }
40 |
41 | _checkParsed(opts, args) {
42 | return !args.hasOwnProperty(this._name);
43 | }
44 |
45 | _usage() {
46 | const res = [];
47 |
48 | res.push(chalk.magentaBright(this._name.toUpperCase()), ' : ', this._title);
49 |
50 | this._req && res.push(' ', chalk.redBright('(required)'));
51 |
52 | return res.join('');
53 | }
54 |
55 | _requiredText() {
56 | return `Missing required argument:\n ${this._usage()}`;
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/lib/cmd.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | 'use strict';
3 |
4 | const
5 | UTIL = require('util'),
6 | PATH = require('path'),
7 | EOL = require('os').EOL,
8 |
9 | Q = require('q'),
10 | chalk = require('chalk'),
11 |
12 | CoaObject = require('./coaobject'),
13 | Opt = require('./opt'),
14 | Arg = require('./arg'),
15 | completion = require('./completion');
16 |
17 | /**
18 | * Command
19 | *
20 | * Top level entity. Commands may have options and arguments.
21 | *
22 | * @namespace
23 | * @class Cmd
24 | * @extends CoaObject
25 | */
26 | class Cmd extends CoaObject {
27 | /**
28 | * @constructs
29 | * @param {COA.Cmd} [cmd] parent command
30 | */
31 | constructor(cmd) {
32 | super(cmd);
33 |
34 | this._parent(cmd);
35 | this._cmds = [];
36 | this._cmdsByName = {};
37 | this._opts = [];
38 | this._optsByKey = {};
39 | this._args = [];
40 | this._api = null;
41 | this._ext = false;
42 | }
43 |
44 | static create(cmd) {
45 | return new Cmd(cmd);
46 | }
47 |
48 | /**
49 | * Returns object containing all its subcommands as methods
50 | * to use from other programs.
51 | *
52 | * @returns {Object}
53 | */
54 | get api() {
55 | // Need _this here because of passed arguments into _api
56 | const _this = this;
57 | this._api || (this._api = function () {
58 | return _this.invoke.apply(_this, arguments);
59 | });
60 |
61 | const cmds = this._cmdsByName;
62 | Object.keys(cmds).forEach(cmd => { this._api[cmd] = cmds[cmd].api; });
63 |
64 | return this._api;
65 | }
66 |
67 | _parent(cmd) {
68 | this._cmd = cmd || this;
69 |
70 | this.isRootCmd ||
71 | cmd._cmds.push(this) &&
72 | this._name &&
73 | (this._cmd._cmdsByName[this._name] = this);
74 |
75 | return this;
76 | }
77 |
78 | get isRootCmd() {
79 | return this._cmd === this;
80 | }
81 |
82 | /**
83 | * Set a canonical command identifier to be used anywhere in the API.
84 | *
85 | * @param {String} name - command name
86 | * @returns {COA.Cmd} - this instance (for chainability)
87 | */
88 | name(name) {
89 | super.name(name);
90 |
91 | this.isRootCmd ||
92 | (this._cmd._cmdsByName[name] = this);
93 |
94 | return this;
95 | }
96 |
97 | /**
98 | * Create new or add existing subcommand for current command.
99 | *
100 | * @param {COA.Cmd} [cmd] existing command instance
101 | * @returns {COA.Cmd} new subcommand instance
102 | */
103 | cmd(cmd) {
104 | return cmd?
105 | cmd._parent(this)
106 | : new Cmd(this);
107 | }
108 |
109 | /**
110 | * Create option for current command.
111 | *
112 | * @returns {COA.Opt} new option instance
113 | */
114 | opt() {
115 | return new Opt(this);
116 | }
117 |
118 | /**
119 | * Create argument for current command.
120 | *
121 | * @returns {COA.Opt} new argument instance
122 | */
123 | arg() {
124 | return new Arg(this);
125 | }
126 |
127 | /**
128 | * Add (or set) action for current command.
129 | *
130 | * @param {Function} act - action function,
131 | * invoked in the context of command instance
132 | * and has the parameters:
133 | * - {Object} opts - parsed options
134 | * - {String[]} args - parsed arguments
135 | * - {Object} res - actions result accumulator
136 | * It can return rejected promise by Cmd.reject (in case of error)
137 | * or any other value treated as result.
138 | * @param {Boolean} [force=false] flag for set action instead add to existings
139 | * @returns {COA.Cmd} - this instance (for chainability)
140 | */
141 | act(act, force) {
142 | if(!act) return this;
143 |
144 | (!this._act || force) && (this._act = []);
145 | this._act.push(act);
146 |
147 | return this;
148 | }
149 |
150 | /**
151 | * Make command "helpful", i.e. add -h --help flags for print usage.
152 | *
153 | * @returns {COA.Cmd} - this instance (for chainability)
154 | */
155 | helpful() {
156 | return this.opt()
157 | .name('help')
158 | .title('Help')
159 | .short('h')
160 | .long('help')
161 | .flag()
162 | .only()
163 | .act(function() {
164 | return this.usage();
165 | })
166 | .end();
167 | }
168 |
169 | /**
170 | * Adds shell completion to command, adds "completion" subcommand,
171 | * that makes all the magic.
172 | * Must be called only on root command.
173 | *
174 | * @returns {COA.Cmd} - this instance (for chainability)
175 | */
176 | completable() {
177 | return this.cmd()
178 | .name('completion')
179 | .apply(completion)
180 | .end();
181 | }
182 |
183 | /**
184 | * Allow command to be extendable by external node.js modules.
185 | *
186 | * @param {String} [pattern] Pattern of node.js module to find subcommands at.
187 | * @returns {COA.Cmd} - this instance (for chainability)
188 | */
189 | extendable(pattern) {
190 | this._ext = pattern || true;
191 | return this;
192 | }
193 |
194 | _exit(msg, code) {
195 | return process.once('exit', function(exitCode) {
196 | msg && console[code === 0 ? 'log' : 'error'](msg);
197 | process.exit(code || exitCode || 0);
198 | });
199 | }
200 |
201 | /**
202 | * Build full usage text for current command instance.
203 | *
204 | * @returns {String} usage text
205 | */
206 | usage() {
207 | const res = [];
208 |
209 | this._title && res.push(this._fullTitle());
210 |
211 | res.push('', 'Usage:');
212 |
213 | this._cmds.length
214 | && res.push([
215 | '', '', chalk.redBright(this._fullName()), chalk.blueBright('COMMAND'),
216 | chalk.greenBright('[OPTIONS]'), chalk.magentaBright('[ARGS]')
217 | ].join(' '));
218 |
219 | (this._opts.length + this._args.length)
220 | && res.push([
221 | '', '', chalk.redBright(this._fullName()),
222 | chalk.greenBright('[OPTIONS]'), chalk.magentaBright('[ARGS]')
223 | ].join(' '));
224 |
225 | res.push(
226 | this._usages(this._cmds, 'Commands'),
227 | this._usages(this._opts, 'Options'),
228 | this._usages(this._args, 'Arguments')
229 | );
230 |
231 | return res.join(EOL);
232 | }
233 |
234 | _usage() {
235 | return chalk.blueBright(this._name) + ' : ' + this._title;
236 | }
237 |
238 | _usages(os, title) {
239 | if(!os.length) return;
240 |
241 | return ['', title + ':']
242 | .concat(os.map(o => ` ${o._usage()}`))
243 | .join(EOL);
244 | }
245 |
246 | _fullTitle() {
247 | return `${this.isRootCmd? '' : this._cmd._fullTitle() + EOL}${this._title}`;
248 | }
249 |
250 | _fullName() {
251 | return `${this.isRootCmd? '' : this._cmd._fullName() + ' '}${PATH.basename(this._name)}`;
252 | }
253 |
254 | _ejectOpt(opts, opt) {
255 | const pos = opts.indexOf(opt);
256 | if(pos === -1) return;
257 |
258 | return opts[pos]._arr?
259 | opts[pos] :
260 | opts.splice(pos, 1)[0];
261 | }
262 |
263 | _checkRequired(opts, args) {
264 | if(this._opts.some(opt => opt._only && opts.hasOwnProperty(opt._name))) return;
265 |
266 | const all = this._opts.concat(this._args);
267 | let i;
268 | while(i = all.shift())
269 | if(i._req && i._checkParsed(opts, args))
270 | return this.reject(i._requiredText());
271 | }
272 |
273 | _parseCmd(argv, unparsed) {
274 | unparsed || (unparsed = []);
275 |
276 | let i,
277 | optSeen = false;
278 | while(i = argv.shift()) {
279 | i.indexOf('-') || (optSeen = true);
280 |
281 | if(optSeen || !/^\w[\w-_]*$/.test(i)) {
282 | unparsed.push(i);
283 | continue;
284 | }
285 |
286 | let pkg, cmd = this._cmdsByName[i];
287 | if(!cmd && this._ext) {
288 | if(this._ext === true) {
289 | pkg = i;
290 | let c = this;
291 | while(true) { // eslint-disable-line
292 | pkg = c._name + '-' + pkg;
293 | if(c.isRootCmd) break;
294 | c = c._cmd;
295 | }
296 | } else if(typeof this._ext === 'string')
297 | pkg = ~this._ext.indexOf('%s')?
298 | UTIL.format(this._ext, i) :
299 | this._ext + i;
300 |
301 | let cmdDesc;
302 | try {
303 | cmdDesc = require(pkg);
304 | } catch(e) {
305 | // Dummy
306 | }
307 |
308 | if(cmdDesc) {
309 | if(typeof cmdDesc === 'function') {
310 | this.cmd().name(i).apply(cmdDesc).end();
311 | } else if(typeof cmdDesc === 'object') {
312 | this.cmd(cmdDesc);
313 | cmdDesc.name(i);
314 | } else throw new Error('Error: Unsupported command declaration type, '
315 | + 'should be a function or COA.Cmd() object');
316 |
317 | cmd = this._cmdsByName[i];
318 | }
319 | }
320 |
321 | if(cmd) return cmd._parseCmd(argv, unparsed);
322 |
323 | unparsed.push(i);
324 | }
325 |
326 | return { cmd : this, argv : unparsed };
327 | }
328 |
329 | _parseOptsAndArgs(argv) {
330 | const opts = {},
331 | args = {},
332 | nonParsedOpts = this._opts.concat(),
333 | nonParsedArgs = this._args.concat();
334 |
335 | let res, i;
336 | while(i = argv.shift()) {
337 | if(i !== '--' && i[0] === '-') {
338 | const m = i.match(/^(--\w[\w-_]*)=(.*)$/);
339 | if(m) {
340 | i = m[1];
341 | this._optsByKey[i]._flag || argv.unshift(m[2]);
342 | }
343 |
344 | const opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i]);
345 | if(!opt) return this.reject(`Unknown option: ${i}`);
346 |
347 | if(Q.isRejected(res = opt._parse(argv, opts))) return res;
348 |
349 | continue;
350 | }
351 |
352 | i === '--' && (i = argv.splice(0));
353 | Array.isArray(i) || (i = [i]);
354 |
355 | let a;
356 | while(a = i.shift()) {
357 | let arg = nonParsedArgs.shift();
358 | if(!arg) return this.reject(`Unknown argument: ${a}`);
359 |
360 | arg._arr && nonParsedArgs.unshift(arg);
361 | if(Q.isRejected(res = arg._parse(a, args))) return res;
362 | }
363 | }
364 |
365 | return {
366 | opts : this._setDefaults(opts, nonParsedOpts),
367 | args : this._setDefaults(args, nonParsedArgs)
368 | };
369 | }
370 |
371 | _setDefaults(params, desc) {
372 | for(const item of desc)
373 | item._def !== undefined &&
374 | !params.hasOwnProperty(item._name) &&
375 | item._saveVal(params, item._def);
376 |
377 | return params;
378 | }
379 |
380 | _processParams(params, desc) {
381 | const notExists = [];
382 |
383 | for(const item of desc) {
384 | const n = item._name;
385 |
386 | if(!params.hasOwnProperty(n)) {
387 | notExists.push(item);
388 | continue;
389 | }
390 |
391 | const vals = Array.isArray(params[n])? params[n] : [params[n]];
392 | delete params[n];
393 |
394 | let res;
395 | for(const v of vals)
396 | if(Q.isRejected(res = item._saveVal(params, v)))
397 | return res;
398 | }
399 |
400 | return this._setDefaults(params, notExists);
401 | }
402 |
403 | _parseArr(argv) {
404 | return Q.when(this._parseCmd(argv), p =>
405 | Q.when(p.cmd._parseOptsAndArgs(p.argv), r => ({
406 | cmd : p.cmd,
407 | opts : r.opts,
408 | args : r.args
409 | })));
410 | }
411 |
412 | _do(inputPromise) {
413 | return Q.when(inputPromise, input => {
414 | return [this._checkRequired]
415 | .concat(input.cmd._act || [])
416 | .reduce((res, act) =>
417 | Q.when(res, prev => act.call(input.cmd, input.opts, input.args, prev)),
418 | undefined);
419 | });
420 | }
421 |
422 | /**
423 | * Parse arguments from simple format like NodeJS process.argv
424 | * and run ahead current program, i.e. call process.exit when all actions done.
425 | *
426 | * @param {String[]} argv - arguments
427 | * @returns {COA.Cmd} - this instance (for chainability)
428 | */
429 | run(argv) {
430 | argv || (argv = process.argv.slice(2));
431 |
432 | const cb = code =>
433 | res => res?
434 | this._exit(res.stack || res.toString(), (res.hasOwnProperty('exitCode')? res.exitCode : code) || 0) :
435 | this._exit();
436 |
437 | Q.when(this.do(argv), cb(0), cb(1)).done();
438 |
439 | return this;
440 | }
441 |
442 | /**
443 | * Invoke specified (or current) command using provided
444 | * options and arguments.
445 | *
446 | * @param {String|String[]} [cmds] - subcommand to invoke (optional)
447 | * @param {Object} [opts] - command options (optional)
448 | * @param {Object} [args] - command arguments (optional)
449 | * @returns {Q.Promise}
450 | */
451 | invoke(cmds, opts, args) {
452 | cmds || (cmds = []);
453 | opts || (opts = {});
454 | args || (args = {});
455 | typeof cmds === 'string' && (cmds = cmds.split(' '));
456 |
457 | if(arguments.length < 3 && !Array.isArray(cmds)) {
458 | args = opts;
459 | opts = cmds;
460 | cmds = [];
461 | }
462 |
463 | return Q.when(this._parseCmd(cmds), p => {
464 | if(p.argv.length)
465 | return this.reject(`Unknown command: ${cmds.join(' ')}`);
466 |
467 | return Q.all([
468 | this._processParams(opts, this._opts),
469 | this._processParams(args, this._args)
470 | ]).spread((_opts, _args) =>
471 | this._do({
472 | cmd : p.cmd,
473 | opts : _opts,
474 | args : _args
475 | })
476 | .fail(res => (res && res.exitCode === 0)?
477 | res.toString() :
478 | this.reject(res)));
479 | });
480 | }
481 | }
482 |
483 | /**
484 | * Convenient function to run command from tests.
485 | *
486 | * @param {String[]} argv - arguments
487 | * @returns {Q.Promise}
488 | */
489 | Cmd.prototype.do = function(argv) {
490 | return this._do(this._parseArr(argv || []));
491 | };
492 |
493 | module.exports = Cmd;
494 |
--------------------------------------------------------------------------------
/lib/coaobject.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | 'use strict';
3 |
4 | const Q = require('q');
5 |
6 | /**
7 | * COA Object
8 | *
9 | * Base class for all COA-related objects
10 | *
11 | * --------|-----|-----|-----
12 | * | Cmd | Opt | Arg
13 | * --------|-----|-----|-----
14 | * name | ✓ | ✓ | ✓
15 | * title | ✓ | ✓ | ✓
16 | * comp | ✓ | ✓ | ✓
17 | * reject | ✓ | ✓ | ✓
18 | * end | ✓ | ✓ | ✓
19 | * apply | ✓ | ✓ | ✓
20 | *
21 | * @class CoaObject
22 | */
23 | module.exports = class CoaObject {
24 | constructor(cmd) {
25 | this._cmd = cmd;
26 | this._name = null;
27 | this._title = null;
28 | this._comp = null;
29 | }
30 |
31 | /**
32 | * Set a canonical identifier to be used anywhere in the API.
33 | *
34 | * @param {String} name - command, option or argument name
35 | * @returns {COA.CoaObject} - this instance (for chainability)
36 | */
37 | name(name) {
38 | this._name = name;
39 | return this;
40 | }
41 |
42 | /**
43 | * Set a long description to be used anywhere in text messages.
44 | * @param {String} title - human readable entity title
45 | * @returns {COA.CoaObject} - this instance (for chainability)
46 | */
47 | title(title) {
48 | this._title = title;
49 | return this;
50 | }
51 |
52 | /**
53 | * Set custom additional completion for current object.
54 | *
55 | * @param {Function} comp - completion generation function,
56 | * invoked in the context of object instance.
57 | * Accepts parameters:
58 | * - {Object} opts - completion options
59 | * It can return promise or any other value threated as a result.
60 | * @returns {COA.CoaObject} - this instance (for chainability)
61 | */
62 | comp(comp) {
63 | this._comp = comp;
64 | return this;
65 | }
66 |
67 | /**
68 | * Apply function with arguments in a context of object instance.
69 | *
70 | * @param {Function} fn - body
71 | * @param {Array.<*>} args... - arguments
72 | * @returns {COA.CoaObject} - this instance (for chainability)
73 | */
74 | apply(fn) {
75 | arguments.length > 1?
76 | fn.apply(this, [].slice.call(arguments, 1))
77 | : fn.call(this);
78 |
79 | return this;
80 | }
81 |
82 | /**
83 | * Return reject of actions results promise with error code.
84 | * Use in .act() for return with error.
85 | * @param {Object} reason - reject reason
86 | * You can customize toString() method and exitCode property
87 | * of reason object.
88 | * @returns {Q.promise} rejected promise
89 | */
90 | reject(reason) {
91 | return Q.reject(reason);
92 | }
93 |
94 | /**
95 | * Finish chain for current subcommand and return parent command instance.
96 | * @returns {COA.Cmd} parent command
97 | */
98 | end() {
99 | return this._cmd;
100 | }
101 | };
102 |
--------------------------------------------------------------------------------
/lib/coaparam.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | const CoaObject = require('./coaobject');
6 |
7 | /**
8 | * COA Parameter
9 | *
10 | * Base class for options and arguments
11 | *
12 | * --------|-----|-----|-----
13 | * | Cmd | Opt | Arg
14 | * --------|-----|-----|-----
15 | * arr | | ✓ | ✓
16 | * req | | ✓ | ✓
17 | * val | | ✓ | ✓
18 | * def | | ✓ | ✓
19 | * input | | ✓ | ✓
20 | * output | | ✓ | ✓
21 | *
22 | * @class CoaParam
23 | * @extends CoaObject
24 | */
25 | module.exports = class CoaParam extends CoaObject {
26 | constructor(cmd) {
27 | super(cmd);
28 |
29 | this._arr = false;
30 | this._req = false;
31 | this._val = undefined;
32 | this._def = undefined;
33 | }
34 |
35 | /**
36 | * Makes a param accepts multiple values.
37 | * Otherwise, the value will be used by the latter passed.
38 | *
39 | * @returns {COA.CoaParam} - this instance (for chainability)
40 | */
41 | arr() {
42 | this._arr = true;
43 | return this;
44 | }
45 |
46 | /**
47 | * Makes a param required.
48 | *
49 | * @returns {COA.CoaParam} - this instance (for chainability)
50 | */
51 | req() {
52 | this._req = true;
53 | return this;
54 | }
55 |
56 | /**
57 | * Set a validation (or value) function for param.
58 | * Value from command line passes through before becoming available from API.
59 | * Using for validation and convertion simple types to any values.
60 | *
61 | * @param {Function} val - validating function,
62 | * invoked in the context of option instance
63 | * and has one parameter with value from command line.
64 | * @returns {COA.CoaParam} - this instance (for chainability)
65 | */
66 | val(val) {
67 | this._val = val;
68 | return this;
69 | }
70 |
71 | /**
72 | * Set a default value for param.
73 | * Default value passed through validation function as ordinary value.
74 | *
75 | * @param {*} def - default value of function generator
76 | * @returns {COA.CoaParam} - this instance (for chainability)
77 | */
78 | def(def) {
79 | this._def = def;
80 | return this;
81 | }
82 |
83 | /**
84 | * Make option value inputting stream.
85 | * It's add useful validation and shortcut for STDIN.
86 | *
87 | * @returns {COA.CoaParam} - this instance (for chainability)
88 | */
89 | input() {
90 | process.stdin.pause();
91 | return this
92 | .def(process.stdin)
93 | .val(function(v) {
94 | if(typeof v !== 'string')
95 | return v;
96 |
97 | if(v === '-')
98 | return process.stdin;
99 |
100 | const s = fs.createReadStream(v, { encoding : 'utf8' });
101 | s.pause();
102 | return s;
103 | });
104 | }
105 |
106 | /**
107 | * Make option value outputing stream.
108 | * It's add useful validation and shortcut for STDOUT.
109 | *
110 | * @returns {COA.CoaParam} - this instance (for chainability)
111 | */
112 | output() {
113 | return this
114 | .def(process.stdout)
115 | .val(function(v) {
116 | if(typeof v !== 'string')
117 | return v;
118 |
119 | if(v === '-')
120 | return process.stdout;
121 |
122 | return fs.createWriteStream(v, { encoding : 'utf8' });
123 | });
124 | }
125 | };
126 |
--------------------------------------------------------------------------------
/lib/completion.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const constants = require('constants');
4 | const fs = require('fs');
5 | const path = require('path');
6 |
7 | const Q = require('q');
8 |
9 | const shell = require('./shell');
10 | const escape = shell.escape;
11 | const unescape = shell.unescape;
12 |
13 | /**
14 | * Most of the code adopted from the npm package shell completion code.
15 | * See https://github.com/isaacs/npm/blob/master/lib/completion.js
16 | *
17 | * @returns {COA.CoaObject}
18 | */
19 | module.exports = function completion() {
20 | return this
21 | .title('Shell completion')
22 | .helpful()
23 | .arg()
24 | .name('raw')
25 | .title('Completion words')
26 | .arr()
27 | .end()
28 | .act((opts, args) => {
29 | if(process.platform === 'win32') {
30 | const e = new Error('shell completion not supported on windows');
31 | e.code = 'ENOTSUP';
32 | e.errno = constants.ENOTSUP;
33 | return this.reject(e);
34 | }
35 |
36 | // if the COMP_* isn't in the env, then just dump the script
37 | if((process.env.COMP_CWORD == null)
38 | || (process.env.COMP_LINE == null)
39 | || (process.env.COMP_POINT == null)) {
40 | return dumpScript(this._cmd._name);
41 | }
42 |
43 | console.error('COMP_LINE: %s', process.env.COMP_LINE);
44 | console.error('COMP_CWORD: %s', process.env.COMP_CWORD);
45 | console.error('COMP_POINT: %s', process.env.COMP_POINT);
46 | console.error('args: %j', args.raw);
47 |
48 | // completion opts
49 | opts = getOpts(args.raw);
50 |
51 | // cmd
52 | const parsed = this._cmd._parseCmd(opts.partialWords);
53 | return Q.when(complete(parsed.cmd, parsed.opts), compls => {
54 | console.error('filtered: %j', compls);
55 | return console.log(compls.map(escape).join('\n'));
56 | });
57 | });
58 | };
59 |
60 | function dumpScript(name) {
61 | const defer = Q.defer();
62 |
63 | fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) {
64 | if(err) return defer.reject(err);
65 | d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^#!.*?\n/, '');
66 |
67 | process.stdout.on('error', onError);
68 | process.stdout.write(d, () => defer.resolve());
69 | });
70 |
71 | return defer.promise;
72 |
73 | function onError(err) {
74 | // Darwin is a real dick sometimes.
75 | //
76 | // This is necessary because the "source" or "." program in
77 | // bash on OS X closes its file argument before reading
78 | // from it, meaning that you get exactly 1 write, which will
79 | // work most of the time, and will always raise an EPIPE.
80 | //
81 | // Really, one should not be tossing away EPIPE errors, or any
82 | // errors, so casually. But, without this, `. <(cmd completion)`
83 | // can never ever work on OS X.
84 | if(err.errno !== constants.EPIPE) return defer.reject(err);
85 | process.stdout.removeListener('error', onError);
86 | return defer.resolve();
87 | }
88 | }
89 |
90 | function getOpts(argv) {
91 | // get the partial line and partial word, if the point isn't at the end
92 | // ie, tabbing at: cmd foo b|ar
93 | const line = process.env.COMP_LINE;
94 | const w = +process.env.COMP_CWORD;
95 | const point = +process.env.COMP_POINT;
96 | const words = argv.map(unescape);
97 | const word = words[w];
98 | const partialLine = line.substr(0, point);
99 | const partialWords = words.slice(0, w);
100 |
101 | // figure out where in that last word the point is
102 | let partialWord = argv[w] || '';
103 | let i = partialWord.length;
104 | while(partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) i--;
105 |
106 | partialWord = unescape(partialWord.substr(0, i));
107 | partialWord && partialWords.push(partialWord);
108 |
109 | return {
110 | line,
111 | w,
112 | point,
113 | words,
114 | word,
115 | partialLine,
116 | partialWords,
117 | partialWord
118 | };
119 | }
120 |
121 | function complete(cmd, opts) {
122 | let optWord, optPrefix,
123 | compls = [];
124 |
125 | // Complete on cmds
126 | if(opts.partialWord.indexOf('-'))
127 | compls = Object.keys(cmd._cmdsByName);
128 | // Complete on required opts without '-' in last partial word
129 | // (if required not already specified)
130 | //
131 | // Commented out because of uselessness:
132 | // -b, --block suggest results in '-' on cmd line;
133 | // next completion suggest all options, because of '-'
134 | //.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req
135 | else {
136 | // complete on opt values: --opt=| case
137 | const m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/);
138 | if(m) {
139 | optWord = m[1];
140 | optPrefix = optWord + '=';
141 | } else
142 | // complete on opts
143 | // don't complete on opts in case of --opt=val completion
144 | // TODO: don't complete on opts in case of unknown arg after commands
145 | // TODO: complete only on opts with arr() or not already used
146 | // TODO: complete only on full opts?
147 | compls = Object.keys(cmd._optsByKey);
148 | }
149 |
150 | // complete on opt values: next arg case
151 | opts.partialWords[opts.w - 1].indexOf('-') || (optWord = opts.partialWords[opts.w - 1]);
152 |
153 | // complete on opt values: completion
154 | let opt;
155 | optWord
156 | && (opt = cmd._optsByKey[optWord])
157 | && !opt._flag
158 | && opt._comp
159 | && (compls = Q.join(compls,
160 | Q.when(opt._comp(opts),
161 | (c, o) => c.concat(o.map(v => (optPrefix || '') + v)))));
162 |
163 | // TODO: complete on args values (context aware, custom completion?)
164 |
165 | // custom completion on cmds
166 | cmd._comp && (compls = Q.join(compls, Q.when(cmd._comp(opts)), (c, o) => c.concat(o)));
167 |
168 | // TODO: context aware custom completion on cmds, opts and args
169 | // (can depend on already entered values, especially options)
170 |
171 | return Q.when(compls, complitions => {
172 | console.error('partialWord: %s', opts.partialWord);
173 | console.error('compls: %j', complitions);
174 | return compls.filter(c => c.indexOf(opts.partialWord) === 0);
175 | });
176 | }
177 |
--------------------------------------------------------------------------------
/lib/completion.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ###-begin-{{cmd}}-completion-###
3 | #
4 | # {{cmd}} command completion script
5 | #
6 | # Installation: {{cmd}} completion >> ~/.bashrc (or ~/.zshrc)
7 | # Or, maybe: {{cmd}} completion > /usr/local/etc/bash_completion.d/{{cmd}}
8 | #
9 |
10 | COMP_WORDBREAKS=${COMP_WORDBREAKS/=/}
11 | COMP_WORDBREAKS=${COMP_WORDBREAKS/@/}
12 | export COMP_WORDBREAKS
13 |
14 | if complete &>/dev/null; then
15 | _{{cmd}}_completion () {
16 | local si="$IFS"
17 | IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
18 | COMP_LINE="$COMP_LINE" \
19 | COMP_POINT="$COMP_POINT" \
20 | {{cmd}} completion -- "${COMP_WORDS[@]}" \
21 | 2>/dev/null)) || return $?
22 | IFS="$si"
23 | }
24 | complete -F _{{cmd}}_completion {{cmd}}
25 | elif compctl &>/dev/null; then
26 | _{{cmd}}_completion () {
27 | local cword line point words si
28 | read -Ac words
29 | read -cn cword
30 | let cword-=1
31 | read -l line
32 | read -ln point
33 | si="$IFS"
34 | IFS=$'\n' reply=($(COMP_CWORD="$cword" \
35 | COMP_LINE="$line" \
36 | COMP_POINT="$point" \
37 | {{cmd}} completion -- "${words[@]}" \
38 | 2>/dev/null)) || return $?
39 | IFS="$si"
40 | }
41 | compctl -K _{{cmd}}_completion {{cmd}}
42 | fi
43 | ###-end-{{cmd}}-completion-###
44 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const
2 | Cmd = require('./cmd'),
3 | Opt = require('./opt'),
4 | Arg = require('./arg'),
5 | shell = require('./shell');
6 |
7 | module.exports = {
8 | Cmd : Cmd.create,
9 | Opt : Opt.create,
10 | Arg : Arg.create,
11 | classes : { Cmd, Opt, Arg },
12 | shell,
13 | require
14 | };
15 |
--------------------------------------------------------------------------------
/lib/opt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const
4 | Q = require('q'),
5 |
6 | CoaParam = require('./coaparam'),
7 | chalk = require('chalk');
8 |
9 | /**
10 | * Option
11 | *
12 | * Named entity. Options may have short and long keys for use from command line.
13 | *
14 | * @namespace
15 | * @class Opt
16 | * @extends CoaParam
17 | */
18 | module.exports = class Opt extends CoaParam {
19 | /**
20 | * @constructs
21 | * @param {COA.Cmd} cmd - parent command
22 | */
23 | constructor(cmd) {
24 | super(cmd);
25 |
26 | this._short = null;
27 | this._long = null;
28 | this._flag = false;
29 | this._only = false;
30 | this._cmd._opts.push(this);
31 | }
32 |
33 | /**
34 | * Set a short key for option to be used with one hyphen from command line.
35 | *
36 | * @param {String} short - short name
37 | * @returns {COA.Opt} - this instance (for chainability)
38 | */
39 | short(short) {
40 | this._short = short;
41 | this._cmd._optsByKey[`-${short}`] = this;
42 | return this;
43 | }
44 |
45 | /**
46 | * Set a short key for option to be used with double hyphens from command line.
47 | *
48 | * @param {String} long - long name
49 | * @returns {COA.Opt} - this instance (for chainability)
50 | */
51 | long(long) {
52 | this._long = long;
53 | this._cmd._optsByKey[`--${long}`] = this;
54 | return this;
55 | }
56 |
57 | /**
58 | * Make an option boolean, i.e. option without value.
59 | *
60 | * @returns {COA.Opt} - this instance (for chainability)
61 | */
62 | flag() {
63 | this._flag = true;
64 | return this;
65 | }
66 |
67 | /**
68 | * Makes an option to act as a command,
69 | * i.e. program will exit just after option action.
70 | *
71 | * @returns {COA.Opt} - this instance (for chainability)
72 | */
73 | only() {
74 | this._only = true;
75 | return this;
76 | }
77 |
78 | /**
79 | * Add action for current option command.
80 | * This action is performed if the current option
81 | * is present in parsed options (with any value).
82 | *
83 | * @param {Function} act - action function,
84 | * invoked in the context of command instance
85 | * and has the parameters:
86 | * - {Object} opts - parsed options
87 | * - {Array} args - parsed arguments
88 | * - {Object} res - actions result accumulator
89 | * It can return rejected promise by Cmd.reject (in case of error)
90 | * or any other value treated as result.
91 | * @returns {COA.Opt} - this instance (for chainability)
92 | */
93 | act(act) {
94 | // Need function here for arguments
95 | const opt = this;
96 | this._cmd.act(function(opts) {
97 | if(!opts.hasOwnProperty(opt._name)) return;
98 |
99 | const res = act.apply(this, arguments);
100 | if(!opt._only) return res;
101 |
102 | return Q.when(res, out => this.reject({
103 | toString : () => out.toString(),
104 | exitCode : 0
105 | }));
106 | });
107 |
108 | return this;
109 | }
110 |
111 | _saveVal(opts, val) {
112 | this._val && (val = this._val(val));
113 |
114 | const name = this._name;
115 | this._arr
116 | ? (opts[name] || (opts[name] = [])).push(val)
117 | : (opts[name] = val);
118 |
119 | return val;
120 | }
121 |
122 | _parse(argv, opts) {
123 | return this._saveVal(opts, this._flag ? true : argv.shift());
124 | }
125 |
126 | _checkParsed(opts) {
127 | return !opts.hasOwnProperty(this._name);
128 | }
129 |
130 | _usage() {
131 | const res = [],
132 | nameStr = this._name.toUpperCase();
133 |
134 | if(this._short) {
135 | res.push('-', chalk.greenBright(this._short));
136 | this._flag || res.push(' ' + nameStr);
137 | res.push(', ');
138 | }
139 |
140 | if(this._long) {
141 | res.push('--', chalk.green(this._long));
142 | this._flag || res.push('=' + nameStr);
143 | }
144 |
145 | res.push(' : ', this._title);
146 |
147 | this._req && res.push(' ', chalk.redBright('(required)'));
148 |
149 | return res.join('');
150 | }
151 |
152 | _requiredText() {
153 | return `Missing required option:\n ${this._usage()}`;
154 | }
155 | };
156 |
--------------------------------------------------------------------------------
/lib/shell.js:
--------------------------------------------------------------------------------
1 | module.exports = { escape, unescape };
2 |
3 | function unescape(w) {
4 | w = w.charAt(0) === '"'
5 | ? w.replace(/^"|([^\\])"$/g, '$1')
6 | : w.replace(/\\ /g, ' ');
7 |
8 | return w.replace(/\\("|'|\$|`|\\)/g, '$1');
9 | }
10 |
11 | function escape(w) {
12 | w = w.replace(/(["'$`\\])/g,'\\$1');
13 | return w.match(/\s+/) ? `"${w}"` : w;
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cliui",
3 | "version": "7.0.4",
4 | "description": "easily create complex multi-column command-line-interfaces",
5 | "main": "build/index.cjs",
6 | "exports": {
7 | ".": [
8 | {
9 | "import": "./index.mjs",
10 | "require": "./build/index.cjs"
11 | },
12 | "./build/index.cjs"
13 | ]
14 | },
15 | "type": "module",
16 | "module": "./index.mjs",
17 | "scripts": {
18 | "check": "standardx '**/*.ts' && standardx '**/*.js' && standardx '**/*.cjs'",
19 | "fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'",
20 | "pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs",
21 | "test": "c8 mocha ./test/*.cjs",
22 | "test:esm": "c8 mocha ./test/esm/cliui-test.mjs",
23 | "postest": "check",
24 | "coverage": "c8 report --check-coverage",
25 | "precompile": "rimraf build",
26 | "compile": "tsc",
27 | "postcompile": "npm run build:cjs",
28 | "build:cjs": "rollup -c",
29 | "prepare": "npm run compile"
30 | },
31 | "repository": "yargs/cliui",
32 | "standard": {
33 | "ignore": [
34 | "**/example/**"
35 | ],
36 | "globals": [
37 | "it"
38 | ]
39 | },
40 | "keywords": [
41 | "cli",
42 | "command-line",
43 | "layout",
44 | "design",
45 | "console",
46 | "wrap",
47 | "table"
48 | ],
49 | "author": "Ben Coe ",
50 | "license": "ISC",
51 | "dependencies": {
52 | "string-width": "^4.2.0",
53 | "strip-ansi": "^6.0.0",
54 | "wrap-ansi": "^7.0.0"
55 | },
56 | "devDependencies": {
57 | "@types/node": "^14.0.27",
58 | "@typescript-eslint/eslint-plugin": "^4.0.0",
59 | "@typescript-eslint/parser": "^4.0.0",
60 | "@wessberg/rollup-plugin-ts": "^1.3.2",
61 | "c8": "^7.3.0",
62 | "chai": "^4.2.0",
63 | "chalk": "^4.1.0",
64 | "cross-env": "^7.0.2",
65 | "eslint": "^7.6.0",
66 | "eslint-plugin-import": "^2.22.0",
67 | "eslint-plugin-node": "^11.1.0",
68 | "gts": "^3.0.0",
69 | "mocha": "^8.1.1",
70 | "rimraf": "^3.0.2",
71 | "rollup": "^2.23.1",
72 | "standardx": "^7.0.0",
73 | "typescript": "^4.0.0"
74 | },
75 | "files": [
76 | "build",
77 | "index.mjs",
78 | "!*.d.ts"
79 | ],
80 | "engine": {
81 | "node": ">=10"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------