Skip to content

Commit

Permalink
Added a data mapping feature that maps arguments to unrecognized flags.
Browse files Browse the repository at this point in the history
  • Loading branch information
coreybutler committed May 2, 2020
1 parent dedf9fd commit 66439d8
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@author.io/shell",
"version": "1.3.16",
"version": "1.4.0",
"description": "A micro-framework for creating CLI-like experiences. This supports Node.js and browsers.",
"main": "src/index.js",
"scripts": {
Expand Down
12 changes: 9 additions & 3 deletions src/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ export default class Base {
}

if (typeof cfg.arguments === 'string') {
cfg.arguments = cfg.arguments.split(/\s+\,/)
cfg.arguments = cfg.arguments.split(/\s+|\t+|\,+|\;+/).map(arg => arg.trim())
}

if (Array.isArray(cfg.arguments)) {
this.#arguments = new Set([...cfg.arguments])
this.#arguments = cfg.arguments
}

this.#name = (cfg.name || 'unknown').trim().split(/\s+/)[0]
this.#description = cfg.description || null

Expand Down Expand Up @@ -114,6 +114,12 @@ export default class Base {
this.#width = v || 80
}
},
arguments: {
enumerable: false,
get () {
return this.#arguments
}
},
initializeMiddleware: {
enumerable: false,
configurable: false,
Expand Down
64 changes: 56 additions & 8 deletions src/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Base from './base.js'
const SUBCOMMAND_PATTERN = /^([^"'][\S\b]+)[\s+]?([^-].*)$/i
const FLAG_PATTERN = /((?:"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|\/[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|$)|(?:\\\s|\S))+)(?=\s|$)/g
const METHOD_PATTERN = /^([\w]+\s?)\(.*\)\s?{/i
const STRIP_QUOTE_PATTERN = /"([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\'/ig

export default class Command extends Base {
#pattern
Expand Down Expand Up @@ -334,22 +335,21 @@ export default class Command extends Base {
}
}

// let source = input.replace(STRIP_EQUAL_SIGNS, '').trim() + ' '

const flags = Array.from(FLAG_PATTERN[Symbol.matchAll](input), x => x[0])
const parser = new Parser(flags, flagConfig)

let recognized = parser.data

const pdata = parser.data
const recognized = {}

parser.recognizedFlags.forEach(flag => recognized[flag] = pdata[flag])
parser.unrecognizedFlags.forEach(arg => delete recognized[arg])

data.flags = { recognized, unrecognized: parser.unrecognizedFlags }
data.valid = parser.valid
data.violations = parser.violations

data.parsed = {}
if (Object.keys(parser.data.flagSource).length > 0) {
for (const [key, src] of Object.entries(parser.data.flagSource)) {
if (Object.keys(pdata.flagSource).length > 0) {
for (const [key, src] of Object.entries(pdata.flagSource)) {
data.parsed[src.name] = src.inputName
}
}
Expand All @@ -362,6 +362,8 @@ export default class Command extends Base {
data.help.message = this.help
}

const args = this.arguments

Object.defineProperties(data, {
flag: {
enumerable: true,
Expand All @@ -372,7 +374,7 @@ export default class Command extends Base {
if (typeof name === 'number') {
return Array.from(parser.unrecognizedFlags)[name]
} else {
return parser.data.flagSource[name].value
return pdata.flagSource[name].value
}
} catch (e) {
return undefined
Expand All @@ -386,6 +388,52 @@ export default class Command extends Base {
shell: {
enumerable: true,
get: () => this.shell
},
data: {
enumerable: true,
get () {
let uf = parser.unrecognizedFlags
let result = Object.assign({}, recognized)
delete result.help

if (uf.length > 0) {
args.forEach((name, i) => {
let value = uf[i]
let normalizedValue = Object.keys(pdata).filter(key => key.toLowerCase() === value)
normalizedValue = (normalizedValue.length > 0 ? normalizedValue.pop() : value)

if (normalizedValue !== undefined) {
normalizedValue = normalizedValue.trim()

if (STRIP_QUOTE_PATTERN.test(normalizedValue)) {
normalizedValue = normalizedValue.substring(1, normalizedValue.length - 1)
}
}

if (result.hasOwnProperty(name)) {
result[name] = Array.isArray(result[name]) ? result[name]: [result[name]]
result[name].push(normalizedValue)
} else {
result[name] = normalizedValue
}
})

if (uf.length > args.length) {
uf.slice(args.length)
.forEach((flag, i) => {
let name = `unknown${i + 1}`
while (result.hasOwnProperty(name)) {
let number = name.substring(7)
name = 'unknown' + (parseInt(number) + 1)
}

result[name] = flag
})
}
}

return result
}
}
})

Expand Down
109 changes: 109 additions & 0 deletions test/unit/01-sanity/04-metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import 'source-map-support/register.js'
import test from 'tape'
import { Shell } from '../../.node/index.js'

test('Map unnamed argument', t => {
const shell = new Shell({
name: 'account',
commands: [{
name: 'create',
arguments: 'email',
handler (meta) {
t.ok(meta.data.hasOwnProperty('email'), 'Automapped data exists.')
t.ok(meta.data.email === '[email protected]', `Attribute named email expected a value of "[email protected]". Received "${meta.data.email}".`)
}
}]
})

shell.exec('create [email protected]')
.catch(e => t.fail(e.message))
.finally(() => t.end())
})

test('Map unnamed arguments', t => {
const shell = new Shell({
name: 'account',
commands: [{
name: 'create',
arguments: 'email displayName',
handler (meta) {
t.ok(meta.data.hasOwnProperty('email'), 'Automapped email data exists.')
t.ok(meta.data.email === '[email protected]', `Attribute named email expected a value of "[email protected]". Received "${meta.data.email}".`)
t.ok(meta.data.hasOwnProperty('displayName'), 'Automapped displayName data exists.')
t.ok(meta.data.displayName === 'John Doe', `Attribute named displayName expected a value of "John Doe". Received "${meta.data.displayName}".`)
}
}]
})

shell.exec('create [email protected] "John Doe"')
.catch(e => t.fail(e.message))
.finally(() => t.end())
})

test('Map extra unnamed arguments as unknown', t => {
const shell = new Shell({
name: 'account',
commands: [{
name: 'create',
arguments: 'email displayName',
handler (meta) {
t.ok(meta.data.hasOwnProperty('email'), 'Automapped email data exists.')
t.ok(meta.data.email === '[email protected]', `Attribute named email expected a value of "[email protected]". Received "${meta.data.email}".`)
t.ok(meta.data.hasOwnProperty('displayName'), 'Automapped displayName data exists.')
t.ok(meta.data.displayName === 'John Doe', `Attribute named displayName expected a value of "John Doe". Received "${meta.data.displayName}".`)
t.ok(meta.data.hasOwnProperty('unknown1'), 'Automapped unknown property to generic name.')
t.ok(meta.data.unknown1 === 'test1', `Unknown attribute expected a value of "test1". Received "${meta.data.unknown1}".`)
t.ok(meta.data.hasOwnProperty('unknown2'), 'Automapped extra unknown property to generic name.')
t.ok(meta.data.unknown2 === 'test2', `Extra unknown attribute expected a value of "test2". Received "${meta.data.unknown2}".`)
}
}]
})

shell.exec('create [email protected] "John Doe" test1 test2')
.catch(e => t.fail(e.message))
.finally(() => t.end())
})

test('Map unnamed/unsupplied arguments as undefined', t => {
const shell = new Shell({
name: 'account',
commands: [{
name: 'create',
arguments: 'email displayName',
handler (meta) {
t.ok(meta.data.hasOwnProperty('email'), 'Automapped email data exists.')
t.ok(meta.data.email === '[email protected]', `Attribute named email expected a value of "[email protected]". Received "${meta.data.email}".`)
t.ok(meta.data.hasOwnProperty('displayName'), 'Automapped displayName attribute exists.')
t.ok(meta.data.displayName === undefined, `Attribute named displayName expected a value of "undefined". Received "${meta.data.displayName}".`)
}
}]
})

shell.exec('create [email protected]')
.catch(e => t.fail(e.message))
.finally(() => t.end())
})

test('Map unnamed arguments when duplicate names are supplied', t => {
const shell = new Shell({
name: 'account',
commands: [{
name: 'create',
flags: {
email: {
alias: 'e'
}
},
arguments: 'email displayName',
handler (meta) {
t.ok(meta.data.hasOwnProperty('email'), 'Automapped email data exists.')
t.ok(Array.isArray(meta.data.email) && meta.data.email[1] === '[email protected]' && meta.data.email[0] === '[email protected]', `Attribute named email expected a value of "['[email protected]', '[email protected]']". Received "[${meta.data.email.map(i => '\'' + i + '\'').reverse().join(', ')}]".`)
t.ok(meta.data.displayName === undefined, `Attribute named displayName expected a value of "undefined". Received "${meta.data.displayName}".`)
}
}]
})

shell.exec('create [email protected] -e [email protected]')
.catch(e => t.fail(e.message))
.finally(() => t.end())
})

0 comments on commit 66439d8

Please sign in to comment.