orionsoft-react-scripts
Version:
Orionsoft Configuration and scripts for Create React App.
457 lines (386 loc) • 13.8 kB
text/coffeescript
UTIL = require 'util'
PATH = require 'path'
Color = require('./color').Color
Q = require('q')
#inspect = require('eyes').inspector { maxLength: 99999, stream: process.stderr }
###*
Command
Top level entity. Commands may have options and arguments.
Presents command
###
exports.Cmd = class Cmd
###*
{COA.Cmd} [cmd] parent command
###
constructor: (cmd) ->
if this not instanceof Cmd
return new Cmd cmd
cmd
= []
= {}
= []
= {}
= []
= false
(propertyName, func) ->
Object.defineProperty @::, propertyName,
configurable: true
enumerable: true
get: func
###*
Returns object containing all its subcommands as methods
to use from other programs.
{Object}
###
'api', () ->
if not
= => .apply @, arguments
for c of
do (c) =>
[c] = [c].api
_parent: (cmd) ->
= cmd or this
if cmd
cmd._cmds.push @
if then ._cmdsByName[ ] = @
@
###*
Set a canonical command identifier to be used anywhere in the API.
{String} _name command name
{COA.Cmd} this instance (for chainability)
###
name: ( ) ->
if isnt @ then ._cmdsByName[_name] = @
@
###*
Set a long description for command to be used anywhere in text messages.
{String} _title command title
{COA.Cmd} this instance (for chainability)
###
title: ( ) -> @
###*
Create new or add existing subcommand for current command.
{COA.Cmd} [cmd] existing command instance
{COA.Cmd} new subcommand instance
###
cmd: (cmd) ->
if cmd then cmd._parent @
else new Cmd @
###*
Create option for current command.
{COA.Opt} new option instance
###
opt: -> new (require('./opt').Opt) @
###*
Create argument for current command.
{COA.Opt} new argument instance
###
arg: -> new (require('./arg').Arg) @
###*
Add (or set) action for current command.
{Function} act action function,
invoked in the context of command instance
and has the parameters:
- {Object} opts parsed options
- {Array} args parsed arguments
- {Object} res actions result accumulator
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.
{Boolean} [force=false] flag for set action instead add to existings
{COA.Cmd} this instance (for chainability)
###
act: (act, force) ->
return @ unless act
if not force and
.push act
else
= [act]
@
###*
Set custom additional completion for current command.
{Function} completion generation function,
invoked in the context of command instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
{COA.Cmd} this instance (for chainability)
###
comp: ( ) -> @
###*
Apply function with arguments in context of command instance.
{Function} fn
{Array} args
{COA.Cmd} this instance (for chainability)
###
apply: (fn, args...) ->
fn.apply this, args
@
###*
Make command "helpful", i.e. add -h --help flags for print usage.
{COA.Cmd} this instance (for chainability)
###
helpful: ->
.name('help').title('Help')
.short('h').long('help')
.flag()
.only()
.act ->
return
.end()
###*
Adds shell completion to command, adds "completion" subcommand,
that makes all the magic.
Must be called only on root command.
{COA.Cmd} this instance (for chainability)
###
completable: ->
.name('completion')
.apply(require './completion')
.end()
###*
Allow command to be extendable by external node.js modules.
{String} [pattern] Pattern of node.js module to find subcommands at.
{COA.Cmd} this instance (for chainability)
###
extendable: (pattern) ->
= pattern or true
@
_exit: (msg, code) ->
process.once 'exit', ->
if msg then console.error msg
process.exit code or 0
###*
Build full usage text for current command instance.
{String} usage text
###
usage: ->
res = []
if then res.push
res.push('', 'Usage:')
if .length then res.push(['', '',
Color('lred', ),
Color('lblue', 'COMMAND'),
Color('lgreen', '[OPTIONS]'),
Color('lpurple', '[ARGS]')].join ' ')
if .length + .length then res.push(['', '',
Color('lred', ),
Color('lgreen', '[OPTIONS]'),
Color('lpurple', '[ARGS]')].join ' ')
res.push(
,
,
)
res.join '\n'
_usage: ->
Color('lblue', ) + ' : ' +
_usages: (os, title) ->
unless os.length then return
res = ['', title + ':']
for o in os
res.push ' ' + o._usage()
res.join '\n'
_fullTitle: ->
(if is this then '' else ._fullTitle() + '\n') +
_fullName: ->
(if this._cmd is this then '' else ._fullName() + ' ') + PATH.basename( )
_ejectOpt: (opts, opt) ->
if (pos = opts.indexOf(opt)) >= 0
if opts[pos]._arr
opts[pos]
else
opts.splice(pos, 1)[0]
_checkRequired: (opts, args) ->
if not ( .filter (o) -> o._only and o._name of opts).length
all = .concat
while i = all.shift()
if i._req and i._checkParsed opts, args
return i._requiredText()
_parseCmd: (argv, unparsed = []) ->
argv = argv.concat()
optSeen = false
while i = argv.shift()
if not i.indexOf '-'
optSeen = true
if not optSeen and /^\w[\w-_]*$/.test(i)
cmd = [i]
if not cmd and
# construct package name to require
if typeof is 'string'
if ~ .indexOf('%s')
# use formatted string
pkg = UTIL.format( , i)
else
# just append subcommand name to the prefix
pkg = + i
else if is true
# use default scheme: <command>-<subcommand>-<subcommand> and so on
pkg = i
c = @
loop
pkg = c._name + '-' + pkg
if c._cmd is c then break
c = c._cmd
try
cmdDesc = require(pkg)
catch e
if cmdDesc
if typeof cmdDesc == 'function'
# set create subcommand, set its name and apply imported function
.name(i)
.apply(cmdDesc)
.end()
else if typeof cmdDesc == 'object'
# register subcommand
# set command name
cmdDesc.name(i)
else
throw new Error 'Error: Unsupported command declaration type, ' +
'should be function or COA.Cmd() object'
cmd = [i]
if cmd
return cmd._parseCmd argv, unparsed
unparsed.push i
{ cmd: @, argv: unparsed }
_parseOptsAndArgs: (argv) ->
opts = {}
args = {}
nonParsedOpts = .concat()
nonParsedArgs = .concat()
while i = argv.shift()
# opt
if i isnt '--' and not i.indexOf '-'
if m = i.match /^(--\w[\w-_]*)=(.*)$/
i = m[1]
# suppress 'unknown argument' error for flag options with values
if not [i]._flag
argv.unshift m[2]
if opt = nonParsedOpts, [i]
if Q.isRejected(res = opt._parse argv, opts)
return res
else
return "Unknown option: #{ i }"
# arg
else
if i is '--'
i = argv.splice(0)
i = if Array.isArray(i) then i else [i]
while a = i.shift()
if arg = nonParsedArgs.shift()
if arg._arr then nonParsedArgs.unshift arg
if Q.isRejected(res = arg._parse a, args)
return res
else
return "Unknown argument: #{ a }"
# set defaults
{
opts: ,
args:
}
_setDefaults: (params, desc) ->
for i in desc
if i._name not of params and '_def' of i
i._saveVal params, i._def
params
_processParams: (params, desc) ->
notExists = []
for i in desc
n = i._name
if n not of params
notExists.push i
continue
vals = params[n]
delete params[n]
if not Array.isArray vals
vals = [vals]
for v in vals
if Q.isRejected(res = i._saveVal(params, v))
return res
# set defaults
params, notExists
_parseArr: (argv) ->
Q.when , (p) ->
Q.when p.cmd._parseOptsAndArgs(p.argv), (r) ->
{ cmd: p.cmd, opts: r.opts, args: r.args }
_do: (input) ->
Q.when input, (input) =>
cmd = input.cmd
[ ].concat(cmd._act or []).reduce(
(res, act) ->
Q.when res, (res) ->
act.call(
cmd
input.opts
input.args
res)
undefined
)
###*
Parse arguments from simple format like NodeJS process.argv
and run ahead current program, i.e. call process.exit when all actions done.
{Array} argv
{COA.Cmd} this instance (for chainability)
###
run: (argv = process.argv.slice(2)) ->
cb = (code) => (res) =>
if res
res.stack ? res.toString(), res.exitCode ? code
else
Q.when( , cb(0), cb(1)).done()
@
###*
Convenient function to run command from tests.
{Array} argv
{Q.Promise}
###
do: (argv) ->
###*
Invoke specified (or current) command using provided
options and arguments.
{String|Array} cmds subcommand to invoke (optional)
{Object} opts command options (optional)
{Object} args command arguments (optional)
{Q.Promise}
###
invoke: (cmds = [], opts = {}, args = {}) ->
if typeof cmds == 'string'
cmds = cmds.split(' ')
if arguments.length < 3
if not Array.isArray cmds
args = opts
opts = cmds
cmds = []
Q.when , (p) =>
if p.argv.length
return "Unknown command: " + cmds.join ' '
Q.all([ , ])
.spread (opts, args) =>
# catch fails from .only() options
.fail (res) =>
if res and res.exitCode is 0
res.toString()
else
###*
Return reject of actions results promise with error code.
Use in .act() for return with error.
{Object} reject reason
You can customize toString() method and exitCode property
of reason object.
{Q.promise} rejected promise
###
reject: (reason) -> Q.reject(reason)
###*
Finish chain for current subcommand and return parent command instance.
{COA.Cmd} parent command
###
end: ->