Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

commandos

Package Overview
Dependencies
Maintainers
2
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

commandos - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

test/parse.basic-usage.js

18

CHANGELOG.md

@@ -5,2 +5,20 @@ # commandos Change Log

## [0.1.4] - Jan 3, 2018
### New
Allow taking non-option argument(s) as option value by setting __nonOption__ property in option definition.
## [0.1.3] - Jan 2, 2018
### Fixed
Fixed the bug taht Error *None of the option groups matched* throwed if no named options found even if there is at least one option group requiring no named options.
## [0.1.2] - Dec 30, 2017
### New
Global setting `ignoreInvalidArgument` added.
## [0.1.1] - Dec 30, 2017

@@ -7,0 +25,0 @@

2

package.json

@@ -15,3 +15,3 @@ {

"name": "commandos",
"version": "0.1.2",
"version": "0.1.3",
"main": "index.js",

@@ -18,0 +18,0 @@ "keywords": [

@@ -39,3 +39,3 @@ 'use strict';

function parseColumn(desc, catcher) {
function parseColumn(desc) {
let column = {

@@ -49,2 +49,3 @@ name: null,

overwrite: undefined,
nonOption: undefined,
};

@@ -55,2 +56,77 @@

desc = desc.replace(/(^|\s)\[(.+)\](\s|$)/, (content) => {
let nonOption = RegExp.$2.trim();
if (!/^([^:]+)(:(.*))?$/.test(nonOption)) {
throw new Error(`invalid nonOption definition: ${nonOption}`);
}
let positions = RegExp.$1.trim().split(/[,\s]+/);
let valueDef = RegExp.$3.trim();
// ---------------------------
// 生成位置匹配函数。
positions = positions.map(position => {
let fn;
if (position == '*') {
return fn = () => true;
}
if (/^\d+$/.test(position)) {
position = parseInt(position);
return fn = (index) => index == position;
}
if (/^(>|>=|<|<=|=)(\d+)$/.test(position)) {
position = parseInt(RegExp.$2);
switch (RegExp.$1) {
case '>' : return fn = (index) => index > position;
case '>=' : return fn = (index) => index >= position;
case '<' : return fn = (index) => index < position;
case '<=' : return fn = (index) => index <= position;
case '=' : return fn = (index) => index == position;
}
}
throw new Error(`invalid nonOption definition: ${nonOption}`);
});
// 最终的位置匹配函数。
let indexValidator = (index) => {
let valid = true;
for (let i = 0; valid && i < positions.length; i++) {
valid = valid && positions[i](index);
}
return valid;
};
// ---------------------------
// 生成值匹配函数。
let valueValidator = null;
if (valueDef == '') {
valueValidator = () => true;
}
else if (/^=\*(.+)$/.test(valueDef)) {
let v = RegExp.$1.trim().toLowerCase();
valueValidator = (value) => v == value.toLowerCase();
}
else if (/^=(.+)$/.test(valueDef)) {
let v = RegExp.$1.trim();
valueValidator = (value) => v == value;
}
else if (/^~\*(.+)$/.test(valueDef)) {
let re = new RegExp(RegExp.$1.trim(), 'i');
valueValidator = (value) => re.test(value);
}
else if (/^~(.+)$/.test(valueDef)) {
let re = new RegExp(RegExp.$1.trim());
valueValidator = (value) => re.test(value);
}
else {
throw new Error(`invalid nonOption definition: ${nonOption}`);
}
// ---------------------------
// 生成完整的非选项参数匹配函数。
column.nonOption = (value, index) => indexValidator(index) && valueValidator(value);
// 位置替补定义语句已完成其使命。
// 注意须用一个空格替换,以免将可能的前后片断粘连在一起。
return ' ';
});
desc = desc.replace(/\s*\([^)]+\)/g, (content) => {

@@ -156,2 +232,20 @@ let index = inParentheses.length;

if (typeof column.nonOption != 'undefined') {
if (typeof column.nonOption == 'number') {
let pos = column.nonOption;
column.nonOption = (index, value) => index === pos;
}
else if (column.nonOption == 'string') {
let text = column.nonOption;
column.nonOption = (index, value) => value == text;
}
else if (column.nonOption instanceof RegExp) {
let re = column.nonOption;
column.nonOption = (index, value) => re.text(value);
}
else if (typeof column.nonOption != 'function') {
throw new Error(`invalid option's nonOption property: $column.nonOption`);
}
}
column.assignable = ifUndefined(column.assignable, true);

@@ -181,3 +275,3 @@ column.nullable = ifUndefined(column.nullable, true);

let parsedOptions = {};
let names_notation_cache = {};
for (let I = 0; I < def.options.length; I++) {

@@ -193,2 +287,3 @@ const column = def.options[I];

const names_notation = names.map(name => (name.length > 1 ? '--' : '-') + name).join(', ');
names_notation_cache[column.name] = names_notation;

@@ -222,3 +317,53 @@ let found = false;

}
}
if (raw.options.length) {
if (def.explicit) {
let names_notation = raw.options.map(option => (option.name.length > 1 ? '--' : '-') + option.name);
throw new Error(`unknown options: ${names_notation}`);
} else {
while (raw.options.length) {
let option = raw.options[0];
parsedOptions[option.name] = consumeOption(0);
}
}
}
// 在依据选项定义的 nonOption 属性消费余项之前,需要先删除已被其他选项显式占用的余项。
parsedOptions.$ = raw.$.filter(v => v !== null);
for (let I = 0; I < def.options.length; I++) {
const column = def.options[I];
const names_notation = names_notation_cache[column.name];
let found = parsedOptions.hasOwnProperty(column.name);
let value = parsedOptions[column.name];
// 消费余项。
if (!found && column.nonOption) {
value = column.multiple ? [] : null;
let matchedIndexes = [];
for (let i = 0, $i; i < parsedOptions.$.length; i++) {
$i = parsedOptions.$[i];
if ($i === null) continue;
if (column.nonOption($i, i)) {
// 将匹配项中余项数组中剥离。
parsedOptions.$[i] = null;
found = true;
// 如果选项支持重复项,则继续尝试匹配,否则终止。
if (column.multiple) {
value.push($i);
}
else {
value = column.assignable ? $i : true;
break;
}
}
}
if (found) {
parsedOptions[column.name] = value;
}
}
if (found && !column.nullable && typeof value == 'boolean') {

@@ -241,16 +386,5 @@ throw new Error(`option need to be valued: ${names_notation}`);

if (raw.options.length) {
if (def.explicit) {
let names_notation = raw.options.map(option => (option.name.length > 1 ? '--' : '-') + option.name);
throw new Error(`unknown options: ${names_notation}`);
} else {
while (raw.options.length) {
let option = raw.options[0];
parsedOptions[option.name] = consumeOption(0);
}
}
}
// 注意:因为选项可能依据 nonOption 属性又消费了一轮余项,因此这里有必要再筛选一次。
parsedOptions.$ = raw.$.filter(v => v !== null);
parsedOptions.$ = raw.$.filter(v => v != null);
return parsedOptions;

@@ -353,4 +487,4 @@ }

caseSensitive: true,
catcher: null,
explicit: false,
catcher: null,
groups: null,

@@ -365,49 +499,43 @@ options: [],

let throws = (fn) => {
try {
return fn();
} catch (ex) {
if (def.catcher) def.catcher(ex);
else throw ex;
}
};
let parsedOptions = null;
try {
let raw = parseRaw(args, def);
if (def.groups && def.groups.length) {
let reasons = [];
let maxMatching = -1;
for (let i = 0; i < def.groups.length; i++) {
let rawcopy = safeClone(raw);
let parsed = null;
let matching = 0;
def.options = def.groups[i].map(parseColumn);
try {
parsed = parseOptions(rawcopy, def);
let raw = throws(() => parseRaw(args, def));
let parsedOptions = null;
if (def.groups && def.groups.length) {
let reasons = [];
let maxMatching = 0;
for (let i = 0; i < def.groups.length; i++) {
let rawcopy = safeClone(raw);
let parsed = null;
let matching = 0;
def.options = throws(() => def.groups[i].map(parseColumn));
try {
parsed = parseOptions(rawcopy, def);
// 取匹配度最高的选项组。
def.options.forEach(option => {
if (parsed.hasOwnProperty(option.name)) matching++;
});
if (matching > maxMatching) {
maxMatching = matching;
parsedOptions = parsed;
// 取匹配度最高的选项组。
def.options.forEach(option => {
if (parsed.hasOwnProperty(option.name)) matching++;
});
if (matching > maxMatching) {
maxMatching = matching;
parsedOptions = parsed;
}
} catch (ex) {
reasons.push(ex);
}
} catch (ex) {
reasons.push(ex);
}
}
if (!parsedOptions) {
let error = new Error('None of the option groups matched');
error.reasons = reasons;
if (def.catcher) def.catcher(error);
else throw error;
if (!parsedOptions) {
let error = new Error('None of the option groups matched');
error.reasons = reasons;
throw error;
}
}
else {
def.options = def.options.map(parseColumn);
parsedOptions = parseOptions(raw, def);
}
} catch(ex) {
if (def.catcher) def.catcher(ex);
else throw ex;
}
else {
def.options = throws(() => def.options.map(parseColumn));
parsedOptions = throws(() => parseOptions(raw, def));
}
return parsedOptions;

@@ -414,0 +542,0 @@ }

@@ -21,2 +21,5 @@ # commandos

* [API](#api)
* [Go Advanced](#go-advanced)
* [ODL, Option Definition Language](#odl-option-definition-language)
* [Take Non-option Argument As Option Value](#take-non-option-argument-as-option-value)
* [Examples](#examples)

@@ -164,2 +167,5 @@ * [Why commandos](#why-commandos)

* __nonOption__ *number | string | RegExp | Function* OPTIONAL
If named option not found, the matching non-option argument(s) will be taken as the value of the option. See [Take Non-option Argument As Option Value](#take-non-option-argument-as-option-value) for details.
* __nullable__ *boolean* DEFAULT `true` OPTIONAL

@@ -177,4 +183,10 @@ When we say some option is NOT __nullable__, we mean it SHOULD NOT appear in the command line without being followed by some value.

It can also be a string according to private syntax looks like [column definition in MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-table.html):
It can also be a string according to private syntax looks like [column definition in MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-table.html). For convenience, it is hereinafter referred to as [__ODL__(Option Definition Language)](#odl-option-definition-language).
## Go Advanced
### ODL, Option Definition Language
ODL is a tiny language used to define option. It is an easy alternative for option define object. E.g.
```javascript

@@ -188,2 +200,5 @@ // * The option is named "version", or "v" in short. The first name is formal.

// * If named option not offered, the first non-argument will be used as value of option "action".
'--action [0:~* (start|stop|restart)]'
// * The first word is regarded as formal name of the option.

@@ -194,2 +209,37 @@ // * Alias "v" and "edition" are also acceptable.

Keywords in ODL is case-insensitive:
* []
* ALIAS
* ASSIGNABLE
* CASE_SENSITIVE
* CASE_INSENSITIVE
* COMMENT
* DEFAULT
* MULTIPLE
* NULLALBE
* OVERWRITE
* REQUIRED
### Take Non-option Argument As Option Value
To make command line more flexiable, __commandos.parse__ allows, by setting __nonOption__ in *definition of an option*, to take non-option argument(s) as option value while named option not found. Property __nonOption__ is overloaded with following types:
* __nonOption__ *number*
* __nonOption__ *string*
* __nonOption__ *RegExp*
* __nonOption__ *Function*(value, index)
In ODL, delimiters `[]` is used to define the nonOption property:
```javascript
// * Fixed position of non-option argument.
// * Fixed value.
'--help [0:=* help] NOT ASSIGNABLE'
// * Any position.
// * Use regular expression (case-insensitive) to validate the arguments.
'--action [*:~* (start|stop|restart)]'
// * Position range.
'--name [>1]'
```
## Examples

@@ -199,3 +249,7 @@

* [commandos.parse](./test/parse.js)
* [commandos.parse: basic usage](./test/parse.basic-usage.js)
* [commandos.parse: global settings](./test/parse.global-settings.js)
* [commandos.parse: option settings](./test/parse.option-settings.js)
* [commandos.parse: take non-option argument as option value](./test/parse.option-nonoption.js)
* [commandos.parse: option groups](./test/parse.option-groups.js)

@@ -202,0 +256,0 @@ ## Why *commandos*

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc