From 3fafe8838743733f282a526ded746ad59738f909 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:12:37 +0100 Subject: [PATCH 1/8] Make project eslint standardjs compatible --- package.json | 3 +- src/OutputCtl.js | 58 +-- src/Parser.js | 260 ++++++------ src/assemblies-create.js | 513 ++++++++++++----------- src/assemblies.js | 128 +++--- src/bills.js | 32 +- src/cli.js | 875 +++++++++++++++++++-------------------- src/help.js | 108 ++--- src/helpers.js | 64 +-- src/index.js | 62 +-- src/notifications.js | 28 +- src/templates.js | 128 +++--- test/cli.js | 766 +++++++++++++++++----------------- 13 files changed, 1512 insertions(+), 1513 deletions(-) diff --git a/package.json b/package.json index 4b8e094..fc1f908 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "eslintConfig": { "extends": "standard", "env": { - "node": true + "node": true, + "mocha": true } }, "author": "Adrian Sinclair ", diff --git a/src/OutputCtl.js b/src/OutputCtl.js index a1a6670..dea3779 100644 --- a/src/OutputCtl.js +++ b/src/OutputCtl.js @@ -1,35 +1,35 @@ export default class OutputCtl { - constructor({ logLevel, jsonMode }) { - this.json = jsonMode; - this.logLevel = logLevel; - - process.stdout.on("error", err => { - if (err.code === "EPIPE") return process.exit(0); - }); - process.stderr.on("error", err => { - if (err.code === "EPIPE") return process.exit(0); - }); - } - - error(msg) { - console.error("ERROR ", msg); - } + constructor ({ logLevel, jsonMode }) { + this.json = jsonMode + this.logLevel = logLevel - warn(msg) { - if (this.logLevel > 0) console.error("WARNING", msg); - } + process.stdout.on('error', err => { + if (err.code === 'EPIPE') return process.exit(0) + }) + process.stderr.on('error', err => { + if (err.code === 'EPIPE') return process.exit(0) + }) + } - info(msg) { - if (this.logLevel > 0) console.error("INFO ", msg); - } + error (msg) { + console.error('ERROR ', msg) + } - debug(msg) { - if (this.logLevel > 1) console.error("DEBUG ", msg); - } + warn (msg) { + if (this.logLevel > 0) console.error('WARNING', msg) + } - print(simple, json) { - if (this.json) console.log(JSON.stringify(json)); - else if (typeof simple === "string") console.log(simple); - else console.dir(simple, { depth: null }); - } + info (msg) { + if (this.logLevel > 0) console.error('INFO ', msg) + } + + debug (msg) { + if (this.logLevel > 1) console.error('DEBUG ', msg) + } + + print (simple, json) { + if (this.json) console.log(JSON.stringify(json)) + else if (typeof simple === 'string') console.log(simple) + else console.dir(simple, { depth: null }) + } } diff --git a/src/Parser.js b/src/Parser.js index 41fd644..92ebdcf 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -1,157 +1,157 @@ export default class Parser { - constructor() { - this._opts = []; - this._longs = {}; - this._shorts = {}; - this._commands = {}; + constructor () { + this._opts = [] + this._longs = {} + this._shorts = {} + this._commands = {} + } + + register (long, short, hasArg) { + let record = { long, short, hasArg } + this._opts.push(record) + this._longs[long] = record + if (short) this._shorts[short] = record + } + + command (field, name, ...aliases) { + this._commands[field] || (this._commands[field] = []) + aliases.push(name) + this._commands[field].push({ name, aliases }) + } + + parse (args) { + if (args == null) args = Array.from(process.argv.slice(2)) + return this._parse(args, {}, [], []) + } + + _parse (args, cmds, opts, tgts) { + if (args.length === 0) return { commands: cmds, options: opts, targets: tgts } + + let arg = args.shift() + + if (arg === '--') { + while (args.length > 0) tgts.push(args.shift()) + return this._parse(args, cmds, opts, tgts) } - register(long, short, hasArg) { - let record = { long, short, hasArg }; - this._opts.push(record); - this._longs[long] = record; - if (short) this._shorts[short] = record; + if (arg.startsWith('--')) return this._parseLong(arg, args, cmds, opts, tgts) + if (arg !== '-' && arg.startsWith('-')) return this._parseShort(arg, args, cmds, opts, tgts) + if (this._isCommand(cmds, arg)) return this._parseCommand(arg, args, cmds, opts, tgts) + + tgts.push(arg) + + return this._parse(args, cmds, opts, tgts) + } + + _parseLong (arg, args, cmds, opts, tgts) { + let name, value + arg.replace(/^--([^=]*)(?:=([\s\S]*))?$/, ($0, $1, $2) => { + name = $1 + value = $2 + }) + if (name == null) throw new Error('failed parsing long argument') + + if (!this._longs.hasOwnProperty(name)) { + return { + error: 'INVALID_OPTION', + option: name, + message: `invalid option supplied: '${arg}'` + } } - command(field, name, ...aliases) { - this._commands[field] || (this._commands[field] = []); - aliases.push(name); - this._commands[field].push({ name, aliases }); + let { hasArg } = this._longs[name] + + if (!hasArg && value != null) { + return { + error: 'UNNECESSARY_ARGUMENT', + option: name, + message: `unnecessary argument supplied: '${arg}'` + } } - parse(args) { - if (args == null) args = Array.from(process.argv.slice(2)); - return this._parse(args, {}, [], []); + if (!hasArg) { + opts.push({ name }) + return this._parse(args, cmds, opts, tgts) } - _parse(args, cmds, opts, tgts) { - if (args.length === 0) return { commands: cmds, options: opts, targets: tgts }; + if (hasArg && value != null) { + opts.push({ name, value }) + return this._parse(args, cmds, opts, tgts) + } - let arg = args.shift(); + // hasArg && value == null + if (args.length === 0) { + return { + error: 'MISSING_ARGUMENT', + option: name, + message: `no argument supplied: '${arg}'` + } + } - if (arg === "--") { - while (args.length > 0) tgts.push(args.shift()); - return this._parse(args, cmds, opts, tgts); - } + opts.push({ name, value: args.shift() }) + return this._parse(args, cmds, opts, tgts) + } - if (arg.startsWith("--")) return this._parseLong(arg, args, cmds, opts, tgts); - if (arg !== "-" && arg.startsWith("-")) return this._parseShort(arg, args, cmds, opts, tgts); - if (this._isCommand(cmds, arg)) return this._parseCommand(arg, args, cmds, opts, tgts); - - tgts.push(arg); + _parseShort (arg, args, cmds, opts, tgts) { + let chars = Array.from(arg.slice(1)) - return this._parse(args, cmds, opts, tgts); - } + do { + let opt = chars.shift() - _parseLong(arg, args, cmds, opts, tgts) { - let name, value; - arg.replace(/^--([^=]*)(?:=([\s\S]*))?$/, ($0, $1, $2) => { - name = $1; - value = $2; - }); - if (name == null) throw new Error("failed parsing long argument"); - - if (!this._longs.hasOwnProperty(name)) { - return { - error: "INVALID_OPTION", - option: name, - message: `invalid option supplied: '${arg}'` - }; + if (!this._shorts.hasOwnProperty(opt)) { + return { + error: 'INVALID_OPTION', + option: opt, + message: `invalid option supplied: '${arg}' ('${opt}')` } + } - let { hasArg } = this._longs[name]; - - if (!hasArg && value != null) { + let { long: name, hasArg } = this._shorts[opt] + if (!hasArg) opts.push({ name }) + else { + if (chars.length === 0) { + if (args.length === 0) { return { - error: "UNNECESSARY_ARGUMENT", - option: name, - message: `unnecessary argument supplied: '${arg}'` - }; - } - - if (!hasArg) { - opts.push({ name }); - return this._parse(args, cmds, opts, tgts); - } + error: 'MISSING_ARGUMENT', + option: name, + message: `no argument supplied: '${arg}'` + } + } - if (hasArg && value != null) { - opts.push({ name, value }); - return this._parse(args, cmds, opts, tgts); + opts.push({ name, value: args.shift() }) + } else { + opts.push({ name, value: chars.join('') }) } - - // hasArg && value == null - if (args.length === 0) { - return { - error: "MISSING_ARGUMENT", - option: name, - message: `no argument supplied: '${arg}'` - }; + break + } + } while (chars.length > 0) + + return this._parse(args, cmds, opts, tgts) + } + + _isCommand (cmds, arg) { + for (let field in this._commands) { + for (let command of this._commands[field]) { + if (command.aliases.includes(arg)) { + if (field in cmds) return false + return true } - - opts.push({ name, value: args.shift() }); - return this._parse(args, cmds, opts, tgts); + } } - _parseShort(arg, args, cmds, opts, tgts) { - let chars = Array.from(arg.slice(1)); - - do { - let opt = chars.shift(); - - if (!this._shorts.hasOwnProperty(opt)) { - return { - error: "INVALID_OPTION", - option: opt, - message: `invalid option supplied: '${arg}' ('${opt}')` - }; - } - - let { long: name, hasArg } = this._shorts[opt]; - if (!hasArg) opts.push({ name }); - else { - if (chars.length === 0) { - if (args.length === 0) { - return { - error: "MISSING_ARGUMENT", - option: name, - message: `no argument supplied: '${arg}'` - }; - } - - opts.push({ name, value: args.shift() }); - } else { - opts.push({ name, value: chars.join("") }); - } - break; - } - } while (chars.length > 0); - - return this._parse(args, cmds, opts, tgts); - } + return false + } - _isCommand(cmds, arg) { - for (let field in this._commands) { - for (let command of this._commands[field]) { - if (command.aliases.includes(arg)) { - if (field in cmds) return false; - return true; - } - } + _parseCommand (arg, args, cmds, opts, tgts) { + for (let field in this._commands) { + for (let command of this._commands[field]) { + if (command.aliases.includes(arg)) { + cmds[field] = command.name + return this._parse(args, cmds, opts, tgts) } - - return false; + } } - _parseCommand(arg, args, cmds, opts, tgts) { - for (let field in this._commands) { - for (let command of this._commands[field]) { - if (command.aliases.includes(arg)) { - cmds[field] = command.name; - return this._parse(args, cmds, opts, tgts); - } - } - } - - throw new Error("unreachable"); - } + throw new Error('unreachable') + } } diff --git a/src/assemblies-create.js b/src/assemblies-create.js index b861e8f..b9b8708 100644 --- a/src/assemblies-create.js +++ b/src/assemblies-create.js @@ -1,328 +1,327 @@ -import { formatAPIError } from "./helpers"; -import fs from "fs"; -import watch from "node-watch"; -import http from "http"; -import path from "path"; -import EventEmitter from "events"; -import tty from "tty"; +import { formatAPIError } from './helpers' +import fs from 'fs' +import watch from 'node-watch' +import http from 'http' +import path from 'path' +import EventEmitter from 'events' +import tty from 'tty' // workaround for determining mime-type of stdin -process.stdin.path = "/dev/stdin"; +process.stdin.path = '/dev/stdin' -function myStatSync(stdioStream, path) { - if (path === "-") return fs.fstatSync(stdioStream.fd); - return fs.statSync(path); +function myStatSync (stdioStream, path) { + if (path === '-') return fs.fstatSync(stdioStream.fd) + return fs.statSync(path) } -function ensureDir(dir) { - try { fs.mkdirSync(dir); } - catch (e) { - if (e.code === "EEXIST") { - if (!fs.statSync(dir).isDirectory()) throw e; - return; - } - if (e.code !== "ENOENT") throw e; - - ensureDir(path.dirname(dir)); - fs.mkdirSync(dir); +function ensureDir (dir) { + try { fs.mkdirSync(dir) } catch (e) { + if (e.code === 'EEXIST') { + if (!fs.statSync(dir).isDirectory()) throw e + return } + if (e.code !== 'ENOENT') throw e + + ensureDir(path.dirname(dir)) + fs.mkdirSync(dir) + } } -function dirProvider(output) { - return (inpath, indir=process.cwd()) => { - let relpath = path.relative(indir, inpath) +function dirProvider (output) { + return (inpath, indir = process.cwd()) => { + let relpath = path.relative(indir, inpath) // if inpath is outside indir, ensure that outpath will still be inside // output - relpath = relpath.replace(/^(\.\.\/)+/, ""); - let outpath = path.join(output, relpath); - let outdir = path.dirname(outpath); + relpath = relpath.replace(/^(\.\.\/)+/, '') + let outpath = path.join(output, relpath) + let outdir = path.dirname(outpath) // TODO can this be moved elsewhere to avoid synchronous IO? - ensureDir(outdir); + ensureDir(outdir) - return fs.createWriteStream(outpath); - }; + return fs.createWriteStream(outpath) + } } -function fileProvider(output) { - ensureDir(path.dirname(output)); - return inpath => output === "-" ? process.stdout : fs.createWriteStream(output); +function fileProvider (output) { + ensureDir(path.dirname(output)) + return inpath => output === '-' ? process.stdout : fs.createWriteStream(output) } class MyEventEmitter extends EventEmitter { - constructor(...args) { - super(...args); - this.hasEnded = false; - } - - emit(event, ...args) { - if (this.hasEnded) return; - if (event === "end" || event === "error") { - this.hasEnded = true; - super.emit(event, ...args); - return; - } - super.emit(event, ...args); + constructor (...args) { + super(...args) + this.hasEnded = false + } + + emit (event, ...args) { + if (this.hasEnded) return + if (event === 'end' || event === 'error') { + this.hasEnded = true + super.emit(event, ...args) + return } + super.emit(event, ...args) + } } class ReaddirJobEmitter extends MyEventEmitter { - constructor({ dir, streamRegistry, recursive, outstreamProvider, topdir=dir }) { - super(); - - process.nextTick(() => { - let awaitCount = 0; - const complete = () => { - if (--awaitCount === 0) this.emit("end"); - }; - - fs.readdir(dir, (err, files) => { - if (err != null) return this.emit("error", err); - - awaitCount += files.length; - - for (let file of files) { - file = path.normalize(path.join(dir, file)); - fs.stat(file, (err, stats) => { - if (err != null) return this.emit("error", err); - - if (stats.isDirectory()) { - if (recursive) { - let subdirEmitter = new ReaddirJobEmitter( - { dir: file, streamRegistry, recursive, outstreamProvider, topdir }); - subdirEmitter.on("job", job => this.emit("job", job)); - subdirEmitter.on("error", error => this.emit("error", error)); - subdirEmitter.on("end", complete); - } else { - complete(); - } - } else { - if (streamRegistry[file]) streamRegistry[file].end(); - let outstream = streamRegistry[file] = outstreamProvider(file, topdir); - this.emit("job", { in: fs.createReadStream(file), out: outstream }); - complete(); - } - }); - } - }); - }); - } -} - -class SingleJobEmitter extends MyEventEmitter { - constructor({ file, streamRegistry, outstreamProvider }) { - super(); - - file = path.normalize(file); - if (streamRegistry[file]) streamRegistry[file].end(); - let outstream = streamRegistry[file] = outstreamProvider(file); - - let instream; - if (file === "-") { - if (tty.isatty(process.stdin.fd)) { - instream = null; // Don't read from stdin if it's input from the console + constructor ({ dir, streamRegistry, recursive, outstreamProvider, topdir = dir }) { + super() + + process.nextTick(() => { + let awaitCount = 0 + const complete = () => { + if (--awaitCount === 0) this.emit('end') + } + + fs.readdir(dir, (err, files) => { + if (err != null) return this.emit('error', err) + + awaitCount += files.length + + for (let file of files) { + file = path.normalize(path.join(dir, file)) + fs.stat(file, (err, stats) => { + if (err != null) return this.emit('error', err) + + if (stats.isDirectory()) { + if (recursive) { + let subdirEmitter = new ReaddirJobEmitter( + { dir: file, streamRegistry, recursive, outstreamProvider, topdir }) + subdirEmitter.on('job', job => this.emit('job', job)) + subdirEmitter.on('error', error => this.emit('error', error)) + subdirEmitter.on('end', complete) + } else { + complete() + } } else { - instream = process.stdin; + if (streamRegistry[file]) streamRegistry[file].end() + let outstream = streamRegistry[file] = outstreamProvider(file, topdir) + this.emit('job', { in: fs.createReadStream(file), out: outstream }) + complete() } - } else { - instream = fs.createReadStream(file); + }) } + }) + }) + } +} - process.nextTick(() => { - this.emit("job", { in: instream, out: outstream }); - this.emit("end"); - }); +class SingleJobEmitter extends MyEventEmitter { + constructor ({ file, streamRegistry, outstreamProvider }) { + super() + + file = path.normalize(file) + if (streamRegistry[file]) streamRegistry[file].end() + let outstream = streamRegistry[file] = outstreamProvider(file) + + let instream + if (file === '-') { + if (tty.isatty(process.stdin.fd)) { + instream = null // Don't read from stdin if it's input from the console + } else { + instream = process.stdin + } + } else { + instream = fs.createReadStream(file) } + + process.nextTick(() => { + this.emit('job', { in: instream, out: outstream }) + this.emit('end') + }) + } } class NullJobEmitter extends MyEventEmitter { - constructor() { - super(); + constructor () { + super() - process.nextTick(() => this.emit("end")); - } + process.nextTick(() => this.emit('end')) + } } class WatchJobEmitter extends MyEventEmitter { - constructor({ file, streamRegistry, recursive, outstreamProvider }) { - super(); + constructor ({ file, streamRegistry, recursive, outstreamProvider }) { + super() + + fs.stat(file, (err, stats) => { + if (err) return this.emit('error', err) + let topdir = stats.isDirectory() ? file : undefined + let watcher = watch(file, { recursive, followSymLinks: true }) + + watcher.on('error', err => this.emit('error', err)) + watcher.on('end', () => this.emit('end')) + watcher.on('change', file => { + file = path.normalize(file) fs.stat(file, (err, stats) => { - if (err) return this.emit("error", err); - let topdir = stats.isDirectory() ? file : undefined; - - let watcher = watch(file, { recursive, followSymLinks: true }); - - watcher.on("error", err => this.emit("error", err)); - watcher.on("end", () => this.emit("end")); - watcher.on("change", file => { - file = path.normalize(file); - fs.stat(file, (err, stats) => { - if (err) return this.emit("error", err); - if (stats.isDirectory()) return; - if (streamRegistry[file]) streamRegistry[file].end(); - let outstream = streamRegistry[file] = outstreamProvider(file, topdir); - let instream = fs.createReadStream(file); - this.emit("job", { in: instream, out: outstream }); - }); - }); - }); - } + if (err) return this.emit('error', err) + if (stats.isDirectory()) return + if (streamRegistry[file]) streamRegistry[file].end() + let outstream = streamRegistry[file] = outstreamProvider(file, topdir) + let instream = fs.createReadStream(file) + this.emit('job', { in: instream, out: outstream }) + }) + }) + }) + } } class MergedJobEmitter extends MyEventEmitter { - constructor(...jobEmitters) { - super(); + constructor (...jobEmitters) { + super() - let ncomplete = 0; + let ncomplete = 0 - for (let jobEmitter of jobEmitters) { - jobEmitter.on("error", err => this.emit("error", err)); - jobEmitter.on("job", job => this.emit("job", job)); - jobEmitter.on("end", () => { - if (++ncomplete === jobEmitters.length) this.emit("end"); - }); - } + for (let jobEmitter of jobEmitters) { + jobEmitter.on('error', err => this.emit('error', err)) + jobEmitter.on('job', job => this.emit('job', job)) + jobEmitter.on('end', () => { + if (++ncomplete === jobEmitters.length) this.emit('end') + }) } + } } class ConcattedJobEmitter extends MyEventEmitter { - constructor(emitterFn, ...emitterFns) { - super(); - - let emitter = emitterFn(); - - if (emitterFns.length === 0) { - emitter.on("error", err => this.emit("error", err)); - emitter.on("job", job => this.emit("job", job)); - emitter.on("end", () => this.emit("end")); - } else { - emitter.on("error", err => this.emit("error", err)); - emitter.on("job", job => this.emit("job", job)); - emitter.on("end", () => { - let restEmitter = new ConcattedJobEmitter(...emitterFns); - restEmitter.on("error", err => this.emit("error", err)); - restEmitter.on("job", job => this.emit("job", job)); - restEmitter.on("end", () => this.emit("end")); - }); - } + constructor (emitterFn, ...emitterFns) { + super() + + let emitter = emitterFn() + + if (emitterFns.length === 0) { + emitter.on('error', err => this.emit('error', err)) + emitter.on('job', job => this.emit('job', job)) + emitter.on('end', () => this.emit('end')) + } else { + emitter.on('error', err => this.emit('error', err)) + emitter.on('job', job => this.emit('job', job)) + emitter.on('end', () => { + let restEmitter = new ConcattedJobEmitter(...emitterFns) + restEmitter.on('error', err => this.emit('error', err)) + restEmitter.on('job', job => this.emit('job', job)) + restEmitter.on('end', () => this.emit('end')) + }) } + } } -function makeJobEmitter(inputs, { recursive, outstreamProvider, streamRegistry, watch }) { - let emitter = new EventEmitter(); - - let emitterFns = []; - let watcherFns = []; - - for (let input of inputs) { - if (input === "-") { - emitterFns.push( - () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry })); - watcherFns.push( - () => new NullJobEmitter()); - - startEmitting(); +function makeJobEmitter (inputs, { recursive, outstreamProvider, streamRegistry, watch }) { + let emitter = new EventEmitter() + + let emitterFns = [] + let watcherFns = [] + + for (let input of inputs) { + if (input === '-') { + emitterFns.push( + () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry })) + watcherFns.push( + () => new NullJobEmitter()) + + startEmitting() + } else { + fs.stat(input, (err, stats) => { + if (err != null) return emitter.emit('error', err) + + if (stats.isDirectory()) { + emitterFns.push( + () => new ReaddirJobEmitter({ dir: input, recursive, outstreamProvider, streamRegistry })) + watcherFns.push( + () => new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry })) } else { - fs.stat(input, (err, stats) => { - if (err != null) return emitter.emit("error", err); - - if (stats.isDirectory()) { - emitterFns.push( - () => new ReaddirJobEmitter({ dir: input, recursive, outstreamProvider, streamRegistry })); - watcherFns.push( - () => new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry })); - } else { - emitterFns.push( - () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry })); - watcherFns.push( - () => new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry })); - } - - startEmitting(); - }); + emitterFns.push( + () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry })) + watcherFns.push( + () => new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry })) } + + startEmitting() + }) } + } - function startEmitting() { - if (emitterFns.length !== inputs.length) return; - - let source = new MergedJobEmitter(...emitterFns.map(f => f())); + function startEmitting () { + if (emitterFns.length !== inputs.length) return - if (watch) { - source = new ConcattedJobEmitter(() => source, - () => new MergedJobEmitter(...watcherFns.map(f => f()))); - } + let source = new MergedJobEmitter(...emitterFns.map(f => f())) - source.on("job", job => emitter.emit("job", job)); - source.on("error", err => emitter.emit("error", err)); - source.on("end", () => emitter.emit("end")); + if (watch) { + source = new ConcattedJobEmitter(() => source, + () => new MergedJobEmitter(...watcherFns.map(f => f()))) } - return emitter; -} + source.on('job', job => emitter.emit('job', job)) + source.on('error', err => emitter.emit('error', err)) + source.on('end', () => emitter.emit('end')) + } -export default function run(outputctl, client, { steps, template, fields, watch, recursive, inputs, output }) { - if (inputs.length === 0) inputs = [ "-" ]; + return emitter +} - let params = steps ? { steps: JSON.parse(fs.readFileSync(steps)) } : { template_id: template }; - params.fields = fields; - - let outstat = myStatSync(process.stdout, output); - if (!outstat.isDirectory()) { - if (inputs.length > 1 || myStatSync(process.stdin, inputs[0]).isDirectory()) { - return outputctl.error("Output must be a directory when specifying multiple inputs"); - } - } +export default function run (outputctl, client, { steps, template, fields, watch, recursive, inputs, output }) { + if (inputs.length === 0) inputs = [ '-' ] - let outstreamProvider = outstat.isDirectory() ? dirProvider(output) : fileProvider(output); - let streamRegistry = {}; + let params = steps ? { steps: JSON.parse(fs.readFileSync(steps)) } : { template_id: template } + params.fields = fields - let emitter = makeJobEmitter(inputs, { recursive, watch, outstreamProvider, streamRegistry }); + let outstat = myStatSync(process.stdout, output) + if (!outstat.isDirectory()) { + if (inputs.length > 1 || myStatSync(process.stdin, inputs[0]).isDirectory()) { + return outputctl.error('Output must be a directory when specifying multiple inputs') + } + } - emitter.on("job", job => { - outputctl.debug(`GOT JOB ${job.in.path} ${job.out.path}`); - let superceded = false; - job.out.on("finish", () => superceded = true); + let outstreamProvider = outstat.isDirectory() ? dirProvider(output) : fileProvider(output) + let streamRegistry = {} - if (job.in != null) client.addStream("in", job.in); + let emitter = makeJobEmitter(inputs, { recursive, watch, outstreamProvider, streamRegistry }) - client.createAssembly({ params }, (err, result) => { - if (err != null) return outputctl.error(err); + emitter.on('job', job => { + outputctl.debug(`GOT JOB ${job.in.path} ${job.out.path}`) + let superceded = false + job.out.on('finish', () => superceded = true) - if (superceded) return; - - client.getAssembly(result.assembly_id, function callback(err, result) { - if (err != null) return outputctl.error(formatAPIError(err)); + if (job.in != null) client.addStream('in', job.in) - if (superceded) return; + client.createAssembly({ params }, (err, result) => { + if (err != null) return outputctl.error(err) - if (result.ok !== "ASSEMBLY_COMPLETED") { - client.getAssembly(result.assembly_id, callback); - return; - } + if (superceded) return - let resulturl = result.results[Object.keys(result.results)[0]][0].url; - - http.get(resulturl, res => { - if (res.statusCode !== 200) { - outputctl.error(`Server returned http status ${res.statusCode}`); - return; - } + client.getAssembly(result.assembly_id, function callback (err, result) { + if (err != null) return outputctl.error(formatAPIError(err)) - if (superceded) return; + if (superceded) return - res.pipe(job.out); - res.on("end", () => outputctl.debug(`COMPLETED ${job.in.path} ${job.out.path}`)); - job.out.on("finish", () => res.unpipe()); // TODO is this done automatically? - }).on("error", err => { - outputctl.error(err.message); - }); - }); - }); - }); + if (result.ok !== 'ASSEMBLY_COMPLETED') { + client.getAssembly(result.assembly_id, callback) + return + } - emitter.on("error", err => { - outputctl.error(err); - }); + let resulturl = result.results[Object.keys(result.results)[0]][0].url + + http.get(resulturl, res => { + if (res.statusCode !== 200) { + outputctl.error(`Server returned http status ${res.statusCode}`) + return + } + + if (superceded) return + + res.pipe(job.out) + res.on('end', () => outputctl.debug(`COMPLETED ${job.in.path} ${job.out.path}`)) + job.out.on('finish', () => res.unpipe()) // TODO is this done automatically? + }).on('error', err => { + outputctl.error(err.message) + }) + }) + }) + }) + + emitter.on('error', err => { + outputctl.error(err) + }) } diff --git a/src/assemblies.js b/src/assemblies.js index 2851147..9cb20a3 100644 --- a/src/assemblies.js +++ b/src/assemblies.js @@ -1,79 +1,79 @@ -import Q from "q"; -import { stream2buf, createReadStream, inSequence, formatAPIError } from "./helpers"; -import assembliesCreate from "./assemblies-create"; +import Q from 'q' +import { stream2buf, createReadStream, inSequence, formatAPIError } from './helpers' +import assembliesCreate from './assemblies-create' -export const create = assembliesCreate; +export const create = assembliesCreate -export function list(output, client, { before, after, fields, keywords }) { - let assemblies = client.streamAssemblies({ - fromdate: after, - todate: before, - fields, keywords - }); - - assemblies.on("readable", () => { - let assembly = assemblies.read(); - if (assembly == null) return; +export function list (output, client, { before, after, fields, keywords }) { + let assemblies = client.streamAssemblies({ + fromdate: after, + todate: before, + fields, keywords + }) - if (fields == null) { - output.print(assembly.id, assembly); - } else { - output.print(fields.map(field => assembly[field]).join(" "), assembly); - } - }); + assemblies.on('readable', () => { + let assembly = assemblies.read() + if (assembly == null) return - assemblies.on("error", err => { - output.error(formatAPIError(err)); - }); + if (fields == null) { + output.print(assembly.id, assembly) + } else { + output.print(fields.map(field => assembly[field]).join(' '), assembly) + } + }) + + assemblies.on('error', err => { + output.error(formatAPIError(err)) + }) } -export function get(output, client, { assemblies }) { - let requests = assemblies.map(assembly => { - let deferred = Q.defer(); - - client.getAssembly(assembly, (err, result) => { - if (err) deferred.reject(err); - else deferred.resolve(result); - }); +export function get (output, client, { assemblies }) { + let requests = assemblies.map(assembly => { + let deferred = Q.defer() - return deferred.promise; - }); + client.getAssembly(assembly, (err, result) => { + if (err) deferred.reject(err) + else deferred.resolve(result) + }) - inSequence(requests, result => { - output.print(result, result); - }, err => { - output.error(formatAPIError(err)); - }); + return deferred.promise + }) + + inSequence(requests, result => { + output.print(result, result) + }, err => { + output.error(formatAPIError(err)) + }) } -exports["delete"] = function _delete(output, client, { assemblies }) { - for (let assembly of assemblies) { - client.deleteAssembly(assembly, err => { - if (err) output.error(formatAPIError(err)); - }); - } -}; +exports['delete'] = function _delete (output, client, { assemblies }) { + for (let assembly of assemblies) { + client.deleteAssembly(assembly, err => { + if (err) output.error(formatAPIError(err)) + }) + } +} -export function replay(output, client, { fields, reparse, steps, notify_url, assemblies }) { - if (steps) { - stream2buf(createReadStream(steps), (err, buf) => { - if (err) return output.error(err.message); +export function replay (output, client, { fields, reparse, steps, notify_url, assemblies }) { + if (steps) { + stream2buf(createReadStream(steps), (err, buf) => { + if (err) return output.error(err.message) - apiCall(JSON.parse(buf.toString())); - }); - } else { - apiCall(); - } + apiCall(JSON.parse(buf.toString())) + }) + } else { + apiCall() + } - function apiCall(steps) { - for (let assembly of assemblies) { - client.replayAssembly({ - assembly_id: assembly, - reparse_template: reparse, - fields, steps, notify_url - }, (err, result) => { - if (err) return output.error(formatAPIError(err)); - }); - } + function apiCall (steps) { + for (let assembly of assemblies) { + client.replayAssembly({ + assembly_id: assembly, + reparse_template: reparse, + fields, steps, notify_url + }, (err, result) => { + if (err) return output.error(formatAPIError(err)) + }) } + } } diff --git a/src/bills.js b/src/bills.js index d91eade..4c5014e 100644 --- a/src/bills.js +++ b/src/bills.js @@ -1,21 +1,21 @@ -import { formatAPIError, inSequence } from "./helpers"; -import Q from "q"; +import { formatAPIError, inSequence } from './helpers' +import Q from 'q' -export function get(output, client, { months }) { - let requests = months.map(month => { - let deferred = Q.defer(); +export function get (output, client, { months }) { + let requests = months.map(month => { + let deferred = Q.defer() - client.getBill(month, (err, result) => { - if (err) return deferred.reject(err); - deferred.resolve(result); - }); + client.getBill(month, (err, result) => { + if (err) return deferred.reject(err) + deferred.resolve(result) + }) - return deferred.promise; - }); + return deferred.promise + }) - inSequence(requests, result => { - output.print(`$${result.total}`, result); - }, err => { - output.error(formatAPIError(err)); - }); + inSequence(requests, result => { + output.print(`$${result.total}`, result) + }, err => { + output.error(formatAPIError(err)) + }) } diff --git a/src/cli.js b/src/cli.js index c8fb23b..b4a512f 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,613 +1,612 @@ -import fs from "fs"; -import Parser from "./Parser"; - -const parser = new Parser(); - -parser.command("mode", "register"); -parser.command("mode", "assemblies", "assembly", "a"); -parser.command("mode", "templates", "template", "t"); -parser.command("mode", "assembly-notifications", "assembly-notification", - "notifications", "notification", "n"); -parser.command("mode", "bills", "bill", "b"); - -parser.command("action", "create", "new", "c"); -parser.command("action", "delete", "cancel", "d"); -parser.command("action", "modify", "edit", "alter", "m"); -parser.command("action", "replay", "r"); -parser.command("action", "list", "l"); -parser.command("action", "get", "info", "view", "display", "g"); - -parser.register("steps", null, true); -parser.register("template", "t", true); -parser.register("field", "f", true); -parser.register("watch", "w", false); -parser.register("recursive", "r", false); -parser.register("input", "i", true); -parser.register("output", "o", true); -parser.register("after", "a", true); -parser.register("before", "b", true); -parser.register("keywords", null, true); -parser.register("fields", null, true); -parser.register("reparse-template", null, false); -parser.register("notify-url", null, true); -parser.register("sort", null, true); -parser.register("order", null, true); -parser.register("name", "n", true); -parser.register("failed", null, false); -parser.register("successful", null, false); -parser.register("verbose", "v", false); -parser.register("quiet", "q", false); -parser.register("json", "j", false); -parser.register("version", null, false); -parser.register("help", "h", false); - -export default function cli(...args) { - let result = parser.parse(...args); - if (result.error != null) return result; - - let { commands, options, targets } = result; - - let err = generalValidation(options); - if (err != null) return err; - - return modeDispatch(commands, options, targets); +import fs from 'fs' +import Parser from './Parser' + +const parser = new Parser() + +parser.command('mode', 'register') +parser.command('mode', 'assemblies', 'assembly', 'a') +parser.command('mode', 'templates', 'template', 't') +parser.command('mode', 'assembly-notifications', 'assembly-notification', + 'notifications', 'notification', 'n') +parser.command('mode', 'bills', 'bill', 'b') + +parser.command('action', 'create', 'new', 'c') +parser.command('action', 'delete', 'cancel', 'd') +parser.command('action', 'modify', 'edit', 'alter', 'm') +parser.command('action', 'replay', 'r') +parser.command('action', 'list', 'l') +parser.command('action', 'get', 'info', 'view', 'display', 'g') + +parser.register('steps', null, true) +parser.register('template', 't', true) +parser.register('field', 'f', true) +parser.register('watch', 'w', false) +parser.register('recursive', 'r', false) +parser.register('input', 'i', true) +parser.register('output', 'o', true) +parser.register('after', 'a', true) +parser.register('before', 'b', true) +parser.register('keywords', null, true) +parser.register('fields', null, true) +parser.register('reparse-template', null, false) +parser.register('notify-url', null, true) +parser.register('sort', null, true) +parser.register('order', null, true) +parser.register('name', 'n', true) +parser.register('failed', null, false) +parser.register('successful', null, false) +parser.register('verbose', 'v', false) +parser.register('quiet', 'q', false) +parser.register('json', 'j', false) +parser.register('version', null, false) +parser.register('help', 'h', false) + +export default function cli (...args) { + let result = parser.parse(...args) + if (result.error != null) return result + + let { commands, options, targets } = result + + let err = generalValidation(options) + if (err != null) return err + + return modeDispatch(commands, options, targets) } -function generalValidation(options) { - let modesSpecified = []; - for (let option of options) { - if (option.name === "field" && !option.value.match(/^[^=]+=[\s\S]*$/)) { - return { - error: "INVALID_OPTION", - option: option.name, - message: `invalid argument for --field: '${option.value}'` - }; - } - - if (option.name === "after" || option.name === "before") { +function generalValidation (options) { + let modesSpecified = [] + for (let option of options) { + if (option.name === 'field' && !option.value.match(/^[^=]+=[\s\S]*$/)) { + return { + error: 'INVALID_OPTION', + option: option.name, + message: `invalid argument for --field: '${option.value}'` + } + } + + if (option.name === 'after' || option.name === 'before') { // TODO reject invalid dates - } - - if (option.name === "sort" && !["id", "name", "created", "modified"].includes(option.value)) { - return { - error: "INVALID_OPTION", - option: option.name, - message: `invalid argument for --sort` - }; - } - - if (option.name === "order" && !["asc", "desc"].includes(option.value)) { - return { - error: "INVALID_OPTION", - option: option.name, - message: `invalid argument for --order` - }; - } - - if (option.name === "verbosity" && !["0", "1", "2"].includes(option.value)) { - return { - error: "INVALID_OPTION", - option: option.name, - message: `invalid argument for --verbosity` - }; - } } -} -function modeDispatch({ mode, action }, opts, tgts) { - if (opts.filter(opt => opt.name === "help").length !== 0) { - return { - mode: "help", - logLevel: 1, - jsonMode: false, - helpMode: mode, - helpAction: action - }; + if (option.name === 'sort' && !['id', 'name', 'created', 'modified'].includes(option.value)) { + return { + error: 'INVALID_OPTION', + option: option.name, + message: `invalid argument for --sort` + } } - if (mode == null) { - if (action != null) mode = "assemblies"; - else if (opts.length === 0) mode = "register"; - else if (opts.filter(opt => opt.name === "version").length !== 0) mode = "version"; - else mode = "assemblies", action = "create"; + if (option.name === 'order' && !['asc', 'desc'].includes(option.value)) { + return { + error: 'INVALID_OPTION', + option: option.name, + message: `invalid argument for --order` + } } - let verbosity = getVerbosity(opts); - - let noJsonFlag = opts.filter(opt => opt.name !== "json"); - let jsonMode = opts.length != noJsonFlag.length; - opts = noJsonFlag; - - let handler = subcommands[mode]; - if (action != null) { - if (!(typeof handler === "object" || action in handler)) { - return { - error: "INVALID_COMMAND", - message: `mode '${mode}' does not support the action '${action}'` - }; - } - handler = handler[action]; + if (option.name === 'verbosity' && !['0', '1', '2'].includes(option.value)) { + return { + error: 'INVALID_OPTION', + option: option.name, + message: `invalid argument for --verbosity` + } } + } +} - if (typeof handler !== "function") { - return { - error: "INVALID_COMMAND", - message: `mode '${mode}' requires an action (one of ${Object.keys(handler)})` - }; +function modeDispatch ({ mode, action }, opts, tgts) { + if (opts.filter(opt => opt.name === 'help').length !== 0) { + return { + mode: 'help', + logLevel: 1, + jsonMode: false, + helpMode: mode, + helpAction: action + } + } + + if (mode == null) { + if (action != null) mode = 'assemblies' + else if (opts.length === 0) mode = 'register' + else if (opts.filter(opt => opt.name === 'version').length !== 0) mode = 'version' + else mode = 'assemblies', action = 'create' + } + + let verbosity = getVerbosity(opts) + + let noJsonFlag = opts.filter(opt => opt.name !== 'json') + let jsonMode = opts.length != noJsonFlag.length + opts = noJsonFlag + + let handler = subcommands[mode] + if (action != null) { + if (!(typeof handler === 'object' || action in handler)) { + return { + error: 'INVALID_COMMAND', + message: `mode '${mode}' does not support the action '${action}'` + } } + handler = handler[action] + } - let result = handler(opts, tgts); - - if (!result.error) { - result.logLevel = verbosity; - result.jsonMode = jsonMode; - result.mode = mode; - result.action = action; + if (typeof handler !== 'function') { + return { + error: 'INVALID_COMMAND', + message: `mode '${mode}' requires an action (one of ${Object.keys(handler)})` } + } + + let result = handler(opts, tgts) - return result; + if (!result.error) { + result.logLevel = verbosity + result.jsonMode = jsonMode + result.mode = mode + result.action = action + } + + return result } // determine the specified verbosity, and remove any verbosity-related options // so that we don't have to worry about them. -function getVerbosity(opts) { - let result = 1; - let writeAt = 0; - for (let readFrom = 0; readFrom < opts.length; readFrom++) { - if (opts[readFrom].name === "verbose") result = 2; - else if (opts[readFrom].name === "quiet") result = 0; - else opts[writeAt++] = opts[readFrom]; - } - opts.splice(writeAt); - return result; +function getVerbosity (opts) { + let result = 1 + let writeAt = 0 + for (let readFrom = 0; readFrom < opts.length; readFrom++) { + if (opts[readFrom].name === 'verbose') result = 2 + else if (opts[readFrom].name === 'quiet') result = 0 + else opts[writeAt++] = opts[readFrom] + } + opts.splice(writeAt) + return result } -function allowOptions(optClassFn, msgfn) { - return (opts, tgts) => { - let invalid = opts.filter(opt => !optClassFn(opt)); - if (invalid.length > 0) { - return { - error: "INVALID_OPTION", - message: msgfn(invalid[0]) - }; - } - }; +function allowOptions (optClassFn, msgfn) { + return (opts, tgts) => { + let invalid = opts.filter(opt => !optClassFn(opt)) + if (invalid.length > 0) { + return { + error: 'INVALID_OPTION', + message: msgfn(invalid[0]) + } + } + } } -function nOfOption(optClassFn, low, high, msgfn) { - return (opts, tgts) => { - let relevantOpts = opts.filter(optClassFn); - if (!(low <= relevantOpts.length && relevantOpts.length <= high)) { - return { - error: "INVALID_OPTION", - message: msgfn(relevantOpts[0]) - }; - } - }; +function nOfOption (optClassFn, low, high, msgfn) { + return (opts, tgts) => { + let relevantOpts = opts.filter(optClassFn) + if (!(low <= relevantOpts.length && relevantOpts.length <= high)) { + return { + error: 'INVALID_OPTION', + message: msgfn(relevantOpts[0]) + } + } + } } -function exactlyOneOfOption(optClassFn, msgfn) { - return nOfOption(optClassFn, 1, 1, msgfn); +function exactlyOneOfOption (optClassFn, msgfn) { + return nOfOption(optClassFn, 1, 1, msgfn) } -function atMostOneOfOption(optClassFn, msgfn) { - return nOfOption(optClassFn, 0, 1, msgfn); +function atMostOneOfOption (optClassFn, msgfn) { + return nOfOption(optClassFn, 0, 1, msgfn) } -function atLeastOneOfOption(optClassFn, msgfn) { - return nOfOption(optClassFn, 1, Infinity, msgfn); +function atLeastOneOfOption (optClassFn, msgfn) { + return nOfOption(optClassFn, 1, Infinity, msgfn) } -function noTargets(msg) { - return (opts, tgts) => { - if (tgts.length > 0) { - return { - error: "INVALID_ARGUMENT", - message: msg - }; - } - }; +function noTargets (msg) { + return (opts, tgts) => { + if (tgts.length > 0) { + return { + error: 'INVALID_ARGUMENT', + message: msg + } + } + } } -function requireTargets(msg) { - return (opts, tgts) => { - if (tgts.length === 0) { - return { - error: "MISSING_ARGUMENT", - message: msg - }; - } - }; +function requireTargets (msg) { + return (opts, tgts) => { + if (tgts.length === 0) { + return { + error: 'MISSING_ARGUMENT', + message: msg + } + } + } } -function nTargets(low, high, { few, many }) { - return (opts, tgts) => { - if (tgts.length < low) { - return { - error: "MISSING_ARGUMENT", - message: few - }; - } - if (tgts.length > high) { - return { - error: "INVALID_ARGUMENT", - message: many - }; - } - }; +function nTargets (low, high, { few, many }) { + return (opts, tgts) => { + if (tgts.length < low) { + return { + error: 'MISSING_ARGUMENT', + message: few + } + } + if (tgts.length > high) { + return { + error: 'INVALID_ARGUMENT', + message: many + } + } + } } -function validate(opts, tgts, ...constraints) { - for (let constraint of constraints) { - let err = constraint(opts, tgts); - if (err) return err; - } +function validate (opts, tgts, ...constraints) { + for (let constraint of constraints) { + let err = constraint(opts, tgts) + if (err) return err + } } -function anyOf(...args) { - return opt => args.includes(opt.name); +function anyOf (...args) { + return opt => args.includes(opt.name) } -function optget(opts, opt) { - let all = optgetall(opts, opt); - return all.length > 0 ? all[all.length - 1] : false; +function optget (opts, opt) { + let all = optgetall(opts, opt) + return all.length > 0 ? all[all.length - 1] : false } -function optgetall(opts, name) { - let result = []; - for (let opt of opts) { - if (opt.name === name) { - result.push(opt.value != null ? opt.value : true); - } +function optgetall (opts, name) { + let result = [] + for (let opt of opts) { + if (opt.name === name) { + result.push(opt.value != null ? opt.value : true) } - return result; + } + return result } -function getfields(opts) { - let fields = {}; - for (let field of optgetall(opts, "field")) { - let segments = field.split("="); - fields[segments[0]] = segments.slice(1).join("="); - } - return fields; +function getfields (opts) { + let fields = {} + for (let field of optgetall(opts, 'field')) { + let segments = field.split('=') + fields[segments[0]] = segments.slice(1).join('=') + } + return fields } const subcommands = { - register(opts, tgts) { - let err = validate(opts, tgts, + register (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("register"), + allowOptions(anyOf('register'), opt => `register doesn't accept any options`), - noTargets("too many arguments passed to register")); + noTargets('too many arguments passed to register')) - if (err) return err; + if (err) return err - return {}; - }, - - assemblies: { - create(opts, tgts) { - let err = validate(opts, tgts, + return {} + }, + + assemblies: { + create (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("steps", "template", "field", "watch", "recursive", "input", "output"), + allowOptions(anyOf('steps', 'template', 'field', 'watch', 'recursive', 'input', 'output'), opt => `assemblies create doesn't accept the option --${opt.name}`), - exactlyOneOfOption(anyOf("steps", "template"), + exactlyOneOfOption(anyOf('steps', 'template'), opt => `assemblies create requires exactly one of either --steps and --template`), - atMostOneOfOption(anyOf("output"), + atMostOneOfOption(anyOf('output'), opt => `assemblies create accepts at most one --output`), - - noTargets("too many arguments passed to assemblies create")); - if (err) return err; + noTargets('too many arguments passed to assemblies create')) + + if (err) return err - let inputs = optgetall(opts, "input"); - if (inputs.length === 0) inputs = ["-"]; + let inputs = optgetall(opts, 'input') + if (inputs.length === 0) inputs = ['-'] - return { - steps: optget(opts, "steps"), - template: optget(opts, "template"), - fields: getfields(opts), - watch: optget(opts, "watch"), - recursive: optget(opts, "recursive"), - output: optget(opts, "output") || "-", - inputs - }; - }, + return { + steps: optget(opts, 'steps'), + template: optget(opts, 'template'), + fields: getfields(opts), + watch: optget(opts, 'watch'), + recursive: optget(opts, 'recursive'), + output: optget(opts, 'output') || '-', + inputs + } + }, - list(opts, tgts) { - let err = validate(opts, tgts, + list (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("before", "after", "keywords", "fields"), + allowOptions(anyOf('before', 'after', 'keywords', 'fields'), opt => `assemblies list doesn't accept the option --${opt.name}`), - atMostOneOfOption(anyOf("before"), + atMostOneOfOption(anyOf('before'), opt => `assemblies list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("after"), + atMostOneOfOption(anyOf('after'), opt => `assemblies list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("fields"), + atMostOneOfOption(anyOf('fields'), opt => `assemblies list accepts at most one of --${opt.name}`), - noTargets("too many arguments passed to assemblies list")); + noTargets('too many arguments passed to assemblies list')) + + if (err) return err - if (err) return err; + let keywords = [] + for (let arg of optgetall(opts, 'keywords')) { + for (let kw of arg.split(',')) keywords.push(kw) + } - let keywords = []; - for (let arg of optgetall(opts, "keywords")) { - for (let kw of arg.split(",")) keywords.push(kw); - } + let fields = optget(opts, 'fields') + if (fields) fields = fields.split(',') + else fields = undefined - let fields = optget(opts, "fields"); - if (fields) fields = fields.split(","); - else fields = undefined; + return { + before: optget(opts, 'before') || undefined, + after: optget(opts, 'after') || undefined, + fields, + keywords + } + }, - return { - before: optget(opts, "before") || undefined, - after: optget(opts, "after") || undefined, - fields, - keywords - }; - }, + get (opts, tgts) { + let err = validate(opts, tgts, - get(opts, tgts) { - let err = validate(opts, tgts, - allowOptions(anyOf(), opt => `assemblies get doesn't accept the option --${opt.name}`), - requireTargets("no assemblies specified")); + requireTargets('no assemblies specified')) - if (err) return err; + if (err) return err - return { - assemblies: tgts - }; - }, + return { + assemblies: tgts + } + }, - delete(opts, tgts) { - let err = validate(opts, tgts, + delete (opts, tgts) { + let err = validate(opts, tgts, allowOptions(anyOf(), opt => `assemblies delete doesn't accept the option --${opt.name}`), - requireTargets("no assemblies specified")); + requireTargets('no assemblies specified')) - if (err) return err; + if (err) return err - return { - assemblies: tgts - }; - }, + return { + assemblies: tgts + } + }, - replay(opts, tgts) { - let err = validate(opts, tgts, + replay (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("reparse-template", "field", "steps", "notify-url"), + allowOptions(anyOf('reparse-template', 'field', 'steps', 'notify-url'), opt => `assemblies replay doesn't accept the option --${opt.name}`), - atMostOneOfOption(anyOf("steps"), + atMostOneOfOption(anyOf('steps'), opt => `too many --steps provided to assemblies replay`), - atMostOneOfOption(anyOf("notify-url"), + atMostOneOfOption(anyOf('notify-url'), opt => `too many --notify-urls provided to assemblies replay`), - requireTargets("no assemblies specified")); + requireTargets('no assemblies specified')) - if (err) return err; + if (err) return err - return { - fields: getfields(opts), - reparse: optget(opts, "reparse-template"), - steps: optget(opts, "steps"), - notiy_url: optget(opts, "notify-url") || undefined, - assemblies: tgts - }; - } - }, + return { + fields: getfields(opts), + reparse: optget(opts, 'reparse-template'), + steps: optget(opts, 'steps'), + notiy_url: optget(opts, 'notify-url') || undefined, + assemblies: tgts + } + } + }, - templates: { - create(opts, tgts) { - let err = validate(opts, tgts, + templates: { + create (opts, tgts) { + let err = validate(opts, tgts, allowOptions(anyOf(), opt => `templates create doesn't accept the option --${opt.name}`), nTargets(1, 2, - { few: "too few arguments passed to templates create", - many: "too many arguments passed to templates create" })); + { few: 'too few arguments passed to templates create', + many: 'too many arguments passed to templates create' })) - if (err) return err; + if (err) return err - return { - name: tgts[0], - file: tgts.length === 2 ? tgts[1] : "-" - }; - }, + return { + name: tgts[0], + file: tgts.length === 2 ? tgts[1] : '-' + } + }, - get(opts, tgts) { - let err = validate(opts, tgts, + get (opts, tgts) { + let err = validate(opts, tgts, allowOptions(anyOf(), opt => `templates create doesn't accept the option --${opt.name}`), - requireTargets("no template specified")); + requireTargets('no template specified')) - if (err) return err; + if (err) return err - return { - templates: tgts - }; - }, + return { + templates: tgts + } + }, - modify(opts, tgts) { - let err = validate(opts, tgts, + modify (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("name"), + allowOptions(anyOf('name'), opt => `templates modify doesn't accept the option --${opt.name}`), nTargets(1, 2, - { few: "too few arguments passed to templates modify", - many: "too many arguments passed to templates modify" })); + { few: 'too few arguments passed to templates modify', + many: 'too many arguments passed to templates modify' })) - if (err) return err; + if (err) return err - return { - template: tgts[0], - name: optget(opts, "name") || undefined, - file: tgts.length === 2 ? tgts[1] : "-" - }; - }, + return { + template: tgts[0], + name: optget(opts, 'name') || undefined, + file: tgts.length === 2 ? tgts[1] : '-' + } + }, - delete(opts, tgts) { - let err = validate(opts, tgts, + delete (opts, tgts) { + let err = validate(opts, tgts, allowOptions(anyOf(), opt => `templates delete doesn't accept the option --${opt.name}`), - requireTargets("no template specified")); + requireTargets('no template specified')) - if (err) return err; + if (err) return err - return { - templates: tgts - }; - }, + return { + templates: tgts + } + }, - list(opts, tgts) { - let err = validate(opts, tgts, + list (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("after", "before", "sort", "order", "fields"), + allowOptions(anyOf('after', 'before', 'sort', 'order', 'fields'), opt => `templates list doesn't accept the option --${opt.name}`), - atMostOneOfOption(anyOf("before"), + atMostOneOfOption(anyOf('before'), opt => `templates list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("after"), + atMostOneOfOption(anyOf('after'), opt => `templates list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("sort"), + atMostOneOfOption(anyOf('sort'), opt => `templates list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("order"), + atMostOneOfOption(anyOf('order'), opt => `templates list accepts at most one of --${opt.name}`), - atMostOneOfOption(anyOf("fields"), + atMostOneOfOption(anyOf('fields'), opt => `templates list accepts at most one of --${opt.name}`), - noTargets("too many arguments passed to templates list")); + noTargets('too many arguments passed to templates list')) - if (err) return err; + if (err) return err - let fields = optget(opts, "fields"); - if (fields) fields = fields.split(","); - else fields = undefined; + let fields = optget(opts, 'fields') + if (fields) fields = fields.split(',') + else fields = undefined - return { - before: optget(opts, "before") || undefined, - after: optget(opts, "after") || undefined, - sort: optget(opts, "sort") || "created", - order: optget(opts, "order") || "desc", - fields - }; - } - }, + return { + before: optget(opts, 'before') || undefined, + after: optget(opts, 'after') || undefined, + sort: optget(opts, 'sort') || 'created', + order: optget(opts, 'order') || 'desc', + fields + } + } + }, - "assembly-notifications": { - replay(opts, tgts) { - let err = validate(opts, tgts, + 'assembly-notifications': { + replay (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("notify-url"), + allowOptions(anyOf('notify-url'), opt => `assembly-notifications replay doesn't accept the option --${opt.name}`), - atMostOneOfOption(anyOf("notify-url"), + atMostOneOfOption(anyOf('notify-url'), opt => `assembly-notifications replay accepts at most one of --${opt.name}`), - requireTargets("no assemblies specified")); - - if (err) return err; + requireTargets('no assemblies specified')) - return { - notify_url: optget(opts, "notify-url") || undefined, - assemblies: tgts - }; + if (err) return err - }, + return { + notify_url: optget(opts, 'notify-url') || undefined, + assemblies: tgts + } + }, - list(opts, tgts) { - let err = validate(opts, tgts, + list (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("failed", "successful"), + allowOptions(anyOf('failed', 'successful'), opt => `assembly-notifications list doesn't accept the option --${opt.name}`), - atMostOneOfOption(anyOf("failed", "successful"), + atMostOneOfOption(anyOf('failed', 'successful'), opt => `assembly-notifications accepts at most one of --failed and --successful`), nTargets(0, 1, - { few: undefined, // can't have <0 targets - many: "too many assembly ids provided to assembly-notifications list" })); + { few: undefined, // can't have <0 targets + many: 'too many assembly ids provided to assembly-notifications list' })) - if (err) return err; + if (err) return err - return { - type: optget(opts, "failed") ? "failed" : optget(opts, "successful") ? "successful" : undefined, - assembly_id: tgts[0] - }; - } - }, + return { + type: optget(opts, 'failed') ? 'failed' : optget(opts, 'successful') ? 'successful' : undefined, + assembly_id: tgts[0] + } + } + }, - bills: { - get(opts, tgts) { - let err = validate(opts, tgts, + bills: { + get (opts, tgts) { + let err = validate(opts, tgts, allowOptions(anyOf(), - opt => `bills get doesn't accept any options`)); - - if (err) return err; - - let months = []; - for (let tgt of tgts) { - const pat = /^(\d{4})-(\d{1,2})$/; - if (!tgt.match(pat)) { - return { - error: "INVALID_ARGUMENT", - message: `invalid date format '${tgt}' (YYYY-MM)` - }; - } - months.push(tgt); - } - - if (months.length === 0) { - let d = new Date(); - months.push(`${d.getUTCFullYear()}-${d.getUTCMonth()+1}`); - } - - return { months }; + opt => `bills get doesn't accept any options`)) + + if (err) return err + + let months = [] + for (let tgt of tgts) { + const pat = /^(\d{4})-(\d{1,2})$/ + if (!tgt.match(pat)) { + return { + error: 'INVALID_ARGUMENT', + message: `invalid date format '${tgt}' (YYYY-MM)` + } } - }, + months.push(tgt) + } - help(opts, tgts) { - let err = validate(opts, tgts, + if (months.length === 0) { + let d = new Date() + months.push(`${d.getUTCFullYear()}-${d.getUTCMonth() + 1}`) + } - allowOptions(anyOf("help"), + return { months } + } + }, + + help (opts, tgts) { + let err = validate(opts, tgts, + + allowOptions(anyOf('help'), opt => `--help doesn't accept any options`), - noTargets("too many argument passed to --help")); + noTargets('too many argument passed to --help')) - if (err) return err; + if (err) return err - return {}; - }, + return {} + }, - version(opts, tgts) { - let err = validate(opts, tgts, + version (opts, tgts) { + let err = validate(opts, tgts, - allowOptions(anyOf("version"), + allowOptions(anyOf('version'), opt => `--version doesn't accept any options`), - noTargets("too many argument passed to --version")); + noTargets('too many argument passed to --version')) - if (err) return err; + if (err) return err - return {}; - } -}; + return {} + } +} diff --git a/src/help.js b/src/help.js index 7080e17..1a256da 100644 --- a/src/help.js +++ b/src/help.js @@ -1,16 +1,16 @@ -export default function help(output, client, { helpMode: mode, helpAction: action }) { - if (!mode && action) return output.print(messages.default); +export default function help (output, client, { helpMode: mode, helpAction: action }) { + if (!mode && action) return output.print(messages.default) - let msg = messages; - if (mode) msg = msg[mode]; - if (action) msg = msg[action]; - if (typeof msg === "object") msg = msg.default; + let msg = messages + if (mode) msg = msg[mode] + if (action) msg = msg[action] + if (typeof msg === 'object') msg = msg.default - output.print(msg.slice(1)); + output.print(msg.slice(1)) } const register = ` -Command: register`; +Command: register` const assemblies = ` Command: assemblies @@ -19,7 +19,7 @@ Subcommands: delete replay list - get`; + get` const templates = ` Command: templates @@ -28,18 +28,18 @@ Subcommands: delete modify list - get`; + get` const bills = ` Command: bills Subcommands: - get`; + get` const notifications = ` Command: assembly-notifications Subcommands: replay - list`; + list` const main = ` Transloadit client. @@ -60,7 +60,7 @@ ${register} ${assemblies} ${templates} ${notifications} -${bills}`; +${bills}` const assembliesCreate = ` Create assemblies to process media. @@ -75,7 +75,7 @@ Options: -o --output Specify an output file or directory; uses STDOUT if absent -f --field Set a template field -w --watch Watch inputs for changes - -r --recursive Enumerate input directories recursively`; + -r --recursive Enumerate input directories recursively` const assembliesList = ` List assemblies matching given criteria. @@ -87,17 +87,17 @@ Options: -b --before Return only assemblies created before specified date -a --after Return only assemblies created after specified date --kewords Specify a comma-separated list of keywords to match assemblies - --fields Specify a list of fields to return for each assembly`; + --fields Specify a list of fields to return for each assembly` const assembliesGet = ` Fetch assembly statuses. -Usage: transloadify assemblies get ID...`; +Usage: transloadify assemblies get ID...` const assembliesDelete = ` Cancel assemblies. -Usage: transloadify assemblies delete ID...`; +Usage: transloadify assemblies delete ID...` const assembliesReplay = ` Replay assemblies. @@ -109,19 +109,19 @@ Options: -f --field Set a template field -s --steps Override assembly instructions --notify-url Specify a new url for assembly notifications - --reparse-template Use the most up-to-date version of the template`; + --reparse-template Use the most up-to-date version of the template` const templatesCreate = ` Create a new template. Usage: transloadify templates create NAME [FILE] -If FILE is not specified, default to STDIN.`; +If FILE is not specified, default to STDIN.` const templatesGet = ` Retrieve the template content as JSON. -Usage: transloadify templates get ID...`; +Usage: transloadify templates get ID...` const templatesModify = ` Change the JSON content of a template. @@ -131,12 +131,12 @@ Usage: transloadify templates modify [--name NAME] ID [FILE] If FILE is not specified, default to STDIN. Options: - -n --name A new name for the template`; + -n --name A new name for the template` const templatesDelete = ` Delete templates. -Usage: transloadify templates delete ID...`; +Usage: transloadify templates delete ID...` const templatesList = ` List templates matching given criteria. @@ -149,7 +149,7 @@ Options: -b --before Return only templates created before specified date --sort Field to sort by (id, name, created, or modified) --order Sort ascending or descending (default: descending) - --fields A list of fields to return for each templates`; + --fields A list of fields to return for each templates` const notificationsReplay = ` Replay notifications for assemblies. @@ -157,7 +157,7 @@ Replay notifications for assemblies. Usage: transloadify assembly-notifications replay [--notify-url URL] ASSEMBLY... Options: - --notify-url Specify a new url to send the notifications to`; + --notify-url Specify a new url to send the notifications to` const notificationsList = ` List notifications matching given criteria. @@ -169,41 +169,41 @@ If ASSEMBLY is specified, return only notifications sent for that assembly. Options: --failed Return only failed notifications - --successful Return only successful notifications`; + --successful Return only successful notifications` const billsGet = ` Fetch billing information. Usage: transloadify bills get MONTH... -Months should be specified in YYYY-MM format.`; +Months should be specified in YYYY-MM format.` const messages = { - default: main, - register: register, - assemblies: { - default: assemblies, - create: assembliesCreate, - list: assembliesList, - get: assembliesGet, - delete: assembliesDelete, - replay: assembliesReplay - }, - templates: { - default: templates, - create: templatesCreate, - get: templatesGet, - modify: templatesModify, - delete: templatesDelete, - list: templatesList - }, - "assembly-notifications": { - default: notifications, - replay: notificationsReplay, - list: notificationsList - }, - bills: { - default: bills, - get: billsGet - } -}; + default: main, + register: register, + assemblies: { + default: assemblies, + create: assembliesCreate, + list: assembliesList, + get: assembliesGet, + delete: assembliesDelete, + replay: assembliesReplay + }, + templates: { + default: templates, + create: templatesCreate, + get: templatesGet, + modify: templatesModify, + delete: templatesDelete, + list: templatesList + }, + 'assembly-notifications': { + default: notifications, + replay: notificationsReplay, + list: notificationsList + }, + bills: { + default: bills, + get: billsGet + } +} diff --git a/src/helpers.js b/src/helpers.js index 55f0d82..8914b16 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,46 +1,46 @@ -import fs from "fs"; +import fs from 'fs' -export function createReadStream(file) { - if (file === "-") return process.stdin; - else return fs.createReadStream(file); +export function createReadStream (file) { + if (file === '-') return process.stdin + else return fs.createReadStream(file) } -export function stream2buf(stream, cb) { - let size = 0; - let bufs = []; +export function stream2buf (stream, cb) { + let size = 0 + let bufs = [] - stream.on("error", cb); + stream.on('error', cb) - stream.on("readable", () => { - let chunk = stream.read(); - if (chunk === null) return; + stream.on('readable', () => { + let chunk = stream.read() + if (chunk === null) return - size += chunk.length; - bufs.push(chunk); - }); + size += chunk.length + bufs.push(chunk) + }) - stream.on("end", () => { - let buf = new Buffer(size); - let offset = 0; + stream.on('end', () => { + let buf = new Buffer(size) + let offset = 0 - for (let b of bufs) { - b.copy(buf, offset); - offset += b.length; - } + for (let b of bufs) { + b.copy(buf, offset) + offset += b.length + } - cb(null, buf); - }); + cb(null, buf) + }) } -export function inSequence(promises, fulfilled, rejected) { - promises.reduce((a, b) => { - return a.then((...args) => { - fulfilled(...args); - return b; - }); - }).then(fulfilled).fail(rejected); +export function inSequence (promises, fulfilled, rejected) { + promises.reduce((a, b) => { + return a.then((...args) => { + fulfilled(...args) + return b + }) + }).then(fulfilled).fail(rejected) } -export function formatAPIError(err) { - return `${err.error}: ${err.message}`; +export function formatAPIError (err) { + return `${err.error}: ${err.message}` } diff --git a/src/index.js b/src/index.js index d52e7cd..0749a37 100644 --- a/src/index.js +++ b/src/index.js @@ -1,41 +1,41 @@ -if (process.env.NODE_ENV !== "production") require('source-map-support').install(); +if (process.env.NODE_ENV !== 'production') require('source-map-support').install() -import cli from "./cli"; -import TransloaditClient from "transloadit"; -import OutputCtl from "./OutputCtl"; -import help from "./help"; +import cli from './cli' +import TransloaditClient from 'transloadit' +import OutputCtl from './OutputCtl' +import help from './help' -let invocation = cli(); +let invocation = cli() -let output = new OutputCtl(invocation); +let output = new OutputCtl(invocation) if (invocation.error) { - output.error(invocation.message); - process.exit(1); + output.error(invocation.message) + process.exit(1) } const commands = { - assemblies: require("./assemblies"), - templates: require("./templates"), - "assembly-notifications": require("./notifications"), - bills: require("./bills"), - help -}; - -let command = commands[invocation.mode]; -if (invocation.action) command = command[invocation.action]; - -let client; -if (!["help", "version", "register"].includes(invocation.mode)) { - if (!process.env.TRANSLOADIT_KEY || !process.env.TRANSLOADIT_SECRET) { - output.error("Please provide API authentication in the environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET"); - process.exit(1); - } - - client = new TransloaditClient({ - authKey: process.env.TRANSLOADIT_KEY, - authSecret: process.env.TRANSLOADIT_SECRET - }); + assemblies: require('./assemblies'), + templates: require('./templates'), + 'assembly-notifications': require('./notifications'), + bills: require('./bills'), + help } -command(output, client, invocation); +let command = commands[invocation.mode] +if (invocation.action) command = command[invocation.action] + +let client +if (!['help', 'version', 'register'].includes(invocation.mode)) { + if (!process.env.TRANSLOADIT_KEY || !process.env.TRANSLOADIT_SECRET) { + output.error('Please provide API authentication in the environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET') + process.exit(1) + } + + client = new TransloaditClient({ + authKey: process.env.TRANSLOADIT_KEY, + authSecret: process.env.TRANSLOADIT_SECRET + }) +} + +command(output, client, invocation) diff --git a/src/notifications.js b/src/notifications.js index 7fbc6ce..c23e069 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -1,20 +1,20 @@ -export function replay(output, client, { notify_url, assemblies }) { - for (let assembly_id of assemblies) { - client.replayAssemblyNotification({ notify_url, assembly_id }, (err, result) => { - if (err) return output.error(err); - }); - } +export function replay (output, client, { notify_url, assemblies }) { + for (let assembly_id of assemblies) { + client.replayAssemblyNotification({ notify_url, assembly_id }, (err, result) => { + if (err) return output.error(err) + }) + } } -export function list(output, client, { type, assembly_id }) { - let notifications = client.streamAssemblyNotifications({ type, assembly_id }); +export function list (output, client, { type, assembly_id }) { + let notifications = client.streamAssemblyNotifications({ type, assembly_id }) - notifications.on("readable", () => { - let notification = notifications.read(); - if (!notification) return; + notifications.on('readable', () => { + let notification = notifications.read() + if (!notification) return - output.print(notification, notification); - }); + output.print(notification, notification) + }) - notifications.on("error", output.error.bind(output)); + notifications.on('error', output.error.bind(output)) } diff --git a/src/templates.js b/src/templates.js index dd9d60c..a5e3289 100644 --- a/src/templates.js +++ b/src/templates.js @@ -1,83 +1,83 @@ -import Q from "q"; -import { stream2buf, createReadStream, inSequence, formatAPIError } from "./helpers"; +import Q from 'q' +import { stream2buf, createReadStream, inSequence, formatAPIError } from './helpers' -export function create(output, client, { name, file }) { - stream2buf(createReadStream(file), (err, buf) => { - client.createTemplate({ name, template: buf.toString() }, (err, result) => { - if (err) return output.error(err.message); - output.print(result.id, result); - }); - }); +export function create (output, client, { name, file }) { + stream2buf(createReadStream(file), (err, buf) => { + client.createTemplate({ name, template: buf.toString() }, (err, result) => { + if (err) return output.error(err.message) + output.print(result.id, result) + }) + }) } -export function get(output, client, { templates }) { - let requests = templates.map(template => { - let deferred = Q.defer(); - - client.getTemplate(template, (err, result) => { - if (err) deferred.reject(err); - else deferred.resolve(result); - }); +export function get (output, client, { templates }) { + let requests = templates.map(template => { + let deferred = Q.defer() - return deferred.promise; - }); + client.getTemplate(template, (err, result) => { + if (err) deferred.reject(err) + else deferred.resolve(result) + }) - inSequence(requests, result => { - output.print(result, result); - }, err => { - output.error(formatAPIError(err)); - }); + return deferred.promise + }) + + inSequence(requests, result => { + output.print(result, result) + }, err => { + output.error(formatAPIError(err)) + }) } -export function modify(output, client, { template, name, file }) { - stream2buf(createReadStream(file), (err, buf) => { - if (err) return output.error(err.message); - - let promise = (name && buf.length !== 0) +export function modify (output, client, { template, name, file }) { + stream2buf(createReadStream(file), (err, buf) => { + if (err) return output.error(err.message) + + let promise = (name && buf.length !== 0) ? Q.fcall(() => ({ name, json: buf.toString() })) : Q.nfcall(client.getTemplate.bind(client), template) .then(template => ({ - name: name || template.name, - json: buf.length !== 0 ? buf.toString() : template.content - })); + name: name || template.name, + json: buf.length !== 0 ? buf.toString() : template.content + })) - promise + promise .then(({ name, json }) => { - client.editTemplate(template, { name, template: json }, (err, result) => { - if (err) return output.error(formatAPIError(err)); - }); + client.editTemplate(template, { name, template: json }, (err, result) => { + if (err) return output.error(formatAPIError(err)) + }) }) - .fail(err => output.error(formatAPIError(err))); - }); + .fail(err => output.error(formatAPIError(err))) + }) } -exports["delete"] = function _delete(output, client, { templates }) { - for (let template of templates) { - client.deleteTemplate(template, err => { - if (err) output.error(formatAPIError(err)); - }); - } -}; +exports['delete'] = function _delete (output, client, { templates }) { + for (let template of templates) { + client.deleteTemplate(template, err => { + if (err) output.error(formatAPIError(err)) + }) + } +} + +export function list (output, client, { before, after, order, sort, fields }) { + let stream = client.streamTemplates({ + todate: before, + fromdate: after, + order, sort, fields + }) -export function list(output, client, { before, after, order, sort, fields }) { - let stream = client.streamTemplates({ - todate: before, - fromdate: after, - order, sort, fields - }); - - stream.on("readable", () => { - let template = stream.read(); - if (template == null) return; + stream.on('readable', () => { + let template = stream.read() + if (template == null) return - if (fields == null) { - output.print(template.id, template); - } else { - output.print(fields.map(field => template[field]).join(" "), template); - } - }); + if (fields == null) { + output.print(template.id, template) + } else { + output.print(fields.map(field => template[field]).join(' '), template) + } + }) - stream.on("error", err => { - output.error(formatAPIError(err)); - }); + stream.on('error', err => { + output.error(formatAPIError(err)) + }) } diff --git a/test/cli.js b/test/cli.js index a7c41c3..02c183f 100644 --- a/test/cli.js +++ b/test/cli.js @@ -1,383 +1,383 @@ -import { assert } from "chai"; -import Parser from "../src/Parser"; -import cli from "../src/cli"; - -describe("Parser", function () { - describe("constructor", function () { - it("should set private fields", function () { - let parser = new Parser(); - assert.deepEqual(parser._opts, []); - assert.deepEqual(Object.keys(parser._longs), []); - assert.deepEqual(Object.keys(parser._shorts), []); - }); - }); - - describe("register", function () { - it("should add the declared option to its records", function () { - let tests = [{ long: "foo", short: "f", hasArg: false }, - { long: "bar", short: null, hasArg: true }]; - let parser = new Parser(); - - for (let test of tests) { - parser.register(test.long, test.short, test.hasArg); - let record = parser._opts[parser._opts.length - 1]; - assert.deepEqual(record, test); - assert.equal(record, parser._longs[test.long]); - if (test.short) assert.equal(record, parser._shorts[test.short]); - } - }); - }); - - describe("parse", function () { - it("should handle typical cli edge-cases", function () { - let parser = new Parser(); - parser.register("recursive", "r", false); - parser.register("file", "f", true); - parser.command("mode", "assemblies", "a"); - parser.command("mode", "templates"); - parser.command("action", "create"); - - let tests = [ - { args: "--recursive", - opts: [ {recursive: null} ], - tgts: [] }, - { args: "-r", - opts: [ {recursive: null} ], - tgts: [] }, - { args: "--recursive file.txt", - opts: [ {recursive: null} ], - tgts: [ "file.txt" ] }, - { args: "-r file.txt", - opts: [ {recursive: null} ], - tgts: [ "file.txt" ] }, - - { args: "--file file.txt", - opts: [ {file: "file.txt"} ], - tgts: [] }, - { args: "--file=file.txt", - opts: [ {file: "file.txt"} ], - tgts: [] }, - { args: "-f file.txt", - opts: [ {file: "file.txt"} ], - tgts: [] }, - { args: "-ffile.txt", - opts: [ {file: "file.txt"} ], - tgts: [] }, - - { args: "--recursive=invalid.txt", - fail: "UNNECESSARY_ARGUMENT" }, - { args: "-rinvalid.txt", - fail: "INVALID_OPTION" }, - - { args: "--invalid", - fail: "INVALID_OPTION" }, - { args: "-i", - fail: "INVALID_OPTION" }, - - { args: "-r file1.txt file2.txt", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt -r", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt -r file2.txt", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "--recursive file1.txt file2.txt", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt --recursive", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt --recursive file2.txt", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "--file file.txt file1.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt --file file.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt --file file.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "--file=file.txt file1.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt --file=file.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt --file=file.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "-f file.txt file1.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt -f file.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt -f file.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "-ffile.txt file1.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt -ffile.txt file2.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - { args: "file1.txt file2.txt -ffile.txt", - opts: [ {file: "file.txt"} ], - tgts: [ "file1.txt", "file2.txt" ] }, - - { args: "-r -- file1.txt -f file.txt file2.txt", - opts: [ {recursive: null} ], - tgts: [ "file1.txt", "-f", "file.txt", "file2.txt" ] }, - { args: "-r -", - opts: [ {recursive: null} ], - tgts: [ "-" ] }, - { args: "-f -", - opts: [ {file: "-"} ], - tgts: [] }, - { args: "-f -- -r", - opts: [ {file: "--"}, {recursive: null} ], - tgts: [] }, - - { args: "assemblies create", - cmds: { mode: "assemblies", action: "create" }, - opts: [], - tgts: [] }, - { args: "templates", - cmds: { mode: "templates" }, - opts: [], - tgts: [] }, - { args: "a create", - cmds: { mode: "assemblies", action: "create" }, - opts: [], - tgts: [] }, - { args: "assemblies create templates", - cmds: { mode: "assemblies", action: "create" }, - opts: [], - tgts: [ "templates" ] } - ]; - - for (let test of tests) { - let result = parser.parse(test.args.split(/\s+/)); - - if (typeof test.fail !== "undefined") { - assert.propertyVal(result, "error", test.fail); - continue; - } - - assert.notProperty(result, "error"); - - let opts = result.options.map(opt => ({ [opt.name]: opt.value || null })); - - if (test.cmds) assert.deepEqual(test.cmds, result.commands); - - assert.deepEqual(test.opts, opts); - - assert.deepEqual(test.tgts, result.targets); - } - }); - }); -}); - -describe("Cli", function () { - it("should validate and interpret arguments appropriately", function () { - let tests = [ - { args: "", - rslt: { mode: "register" } }, - { args: "register", - rslt: { mode: "register" } }, - { args: "register target", - rslt: { error: "INVALID_ARGUMENT" } }, - { args: "register --watch", - rslt: { error: "INVALID_OPTION" } }, - - { args: "assemblies create --steps steps.json", - rslt: { mode: "assemblies", - action: "create", - template: false, - steps: "steps.json", - recursive: false, - watch: false, - fields: {}, - inputs: ["-"], - output: "-" } }, - { args: "--steps steps.json", - rslt: { mode: "assemblies", - action: "create", - steps: "steps.json" } }, - { args: "--template 5", - rslt: { mode: "assemblies", - action: "create", - template: "5", - steps: false } }, - { args: "-t5 --watch", - rslt: { watch: true } }, - { args: "-t5 --recursive", - rslt: { recursive: true } }, - { args: "-t5 -ffoo=bar=baz -fa=b -fc=d", - rslt: { fields: { foo: "bar=baz", a: "b", c: "d" } } }, - { args: "-t5 -ia -ib -oc", - rslt: { inputs: ["a", "b"], - output: "c" } }, - { args: "assemblies create", - rslt: { error: "INVALID_OPTION" } }, - { args: "--template 5 --steps steps.json", - rslt: { error: "INVALID_OPTION" } }, - { args: "-t5 -oa -ob", - rslt: { error: "INVALID_OPTION" } }, - - { args: "assemblies list", - rslt: { mode: "assemblies", - action: "list", - before: undefined, - after: undefined, - keywords: [], - fields: undefined } }, - { args: "assemblies list -b 2016-09-11", - rslt: { before: "2016-09-11" } }, - { args: "assemblies list -a 2016-09-11", - rslt: { after: "2016-09-11" } }, - { args: "assemblies list --keywords=foo,bar --keywords=baz,qux", - rslt: { keywords: ["foo", "bar", "baz", "qux"] } }, - { args: "assemblies list --fields foo,bar", - reslt: { fields: ["foo", "bar"] } }, - { args: "assemblies list -a 2016-09-11 -a 2016-09-12", - rslt: { error: "INVALID_OPTION" } }, - { args: "assemblies list --fields foo,bar --fields baz,qux", - reslt: { error: "INVALID_OPTION" } }, - - { args: "assemblies get a b c", - rslt: { mode: "assemblies", - action: "get", - assemblies: ["a", "b", "c"] } }, - { args: "assemblies get", - rslt: { error: "MISSING_ARGUMENT" } }, - { args: "assemblies get --recursive", - rslt: { error: "INVALID_OPTION" } }, - - { args: "assemblies delete a b c", - rslt: { mode: "assemblies", - action: "delete", - assemblies: ["a", "b", "c"] } }, - { args: "assemblies delete", - rslt: { error: "MISSING_ARGUMENT" } }, - { args: "assemblies delete --recursive", - rslt: { error: "INVALID_OPTION" } }, - - { args: "assemblies replay a b c", - rslt: { mode: "assemblies", - action: "replay", - assemblies: ["a", "b", "c"], - reparse: false, - fields: {}, - steps: false } }, - { args: "assemblies replay --reparse-template a", - rslt: { reparse: true } }, - { args: "assemblies replay -fa=b a", - rslt: { fields: { a: "b" } } }, - { args: "assemblies replay", - rslt: { error: "MISSING_ARGUMENT" } }, - { args: "assemblies replay --recursive", - rslt: { error: "INVALID_OPTION" } }, - - { args: "templates create foo", - rslt: { mode: "templates", - action: "create", - name: "foo", - file: "-" } }, - { args: "templates create foo steps.json", - rslt: { name: "foo", - file: "steps.json" } }, - { args: "templates create", - rslt: { error: "MISSING_ARGUMENT" } }, - { args: "templates create a b c", - rslt: { error: "INVALID_ARGUMENT" } }, - - { args: "templates get a", - rslt: { mode: "templates", - action: "get", - templates: ["a"] } }, - { args: "templates get a b", - rslt: { templates: ["a", "b"] } }, - { args: "templates get", - rslt: { error: "MISSING_ARGUMENT" } }, - - { args: "templates modify foo", - rslt: { mode: "templates", - action: "modify", - template: "foo", - name: undefined, - file: "-" } }, - { args: "templates modify foo steps.json", - rslt: { template: "foo", - file: "steps.json" } }, - { args: "templates modify foo -n bar steps.json", - rslt: { template: "foo", - name: "bar", - file: "steps.json" } }, - { args: "templates modify", - rslt: { error: "MISSING_ARGUMENT" } }, - { args: "templates modify a b c", - rslt: { error: "INVALID_ARGUMENT" } }, - - { args: "templates delete a", - rslt: { mode: "templates", - action: "delete", - templates: ["a"] } }, - { args: "templates delete a b", - rslt: { templates: ["a", "b"] } }, - { args: "templates delete", - rslt: { error: "MISSING_ARGUMENT" } }, - - { args: "templates list", - rslt: { mode: "templates", - action: "list", - before: undefined, - after: undefined, - sort: "created", - order: "desc", - fields: undefined } }, - { args: "templates list --before 2016-09-13 --after 2015-09-13", - rslt: { before: "2016-09-13", - after: "2015-09-13" } }, - { args: "templates list --fields id,created", - rslt: { fields: ["id", "created"] } }, - { args: "templates list --order=asc", - rslt: { order: "asc" } }, - { args: "templates list --order=invalid", - rslt: { error: "INVALID_OPTION" } }, - { args: "templates list --sort=name", - rslt: { sort: "name" } }, - { args: "templates list --sort=invalid", - rslt: { error: "INVALID_OPTION" } }, - - { args: "bills get", - rslt: { mode: "bills", - action: "get", - months: [`${new Date().getUTCFullYear()}-${new Date().getUTCMonth()+1}`] } }, - { args: "bills get 2016-08 2016-07", - rslt: { months: ["2016-08", "2016-07"] } }, - { args: "bills get invalid", - rslt: { error: "INVALID_ARGUMENT" } } - ]; - - for (let test of tests) { - let args = test.args.split(/\s+/); - let result = cli(args == false ? [] : args); - if (args[0] === "--edit-template") console.log(result); - for (let key in test.rslt) { - if (!test.rslt.hasOwnProperty(key)) continue; - assert.deepEqual(test.rslt[key], result[key], - `expected result.${key} to be ${JSON.stringify(test.rslt[key])}; was ${result[key]}\nargs: ${test.args}\nresult: ${JSON.stringify(result)}\n`); - } - } - }); -}); +import { assert } from 'chai' +import Parser from '../src/Parser' +import cli from '../src/cli' + +describe('Parser', function () { + describe('constructor', function () { + it('should set private fields', function () { + let parser = new Parser() + assert.deepEqual(parser._opts, []) + assert.deepEqual(Object.keys(parser._longs), []) + assert.deepEqual(Object.keys(parser._shorts), []) + }) + }) + + describe('register', function () { + it('should add the declared option to its records', function () { + let tests = [{ long: 'foo', short: 'f', hasArg: false }, + { long: 'bar', short: null, hasArg: true }] + let parser = new Parser() + + for (let test of tests) { + parser.register(test.long, test.short, test.hasArg) + let record = parser._opts[parser._opts.length - 1] + assert.deepEqual(record, test) + assert.equal(record, parser._longs[test.long]) + if (test.short) assert.equal(record, parser._shorts[test.short]) + } + }) + }) + + describe('parse', function () { + it('should handle typical cli edge-cases', function () { + let parser = new Parser() + parser.register('recursive', 'r', false) + parser.register('file', 'f', true) + parser.command('mode', 'assemblies', 'a') + parser.command('mode', 'templates') + parser.command('action', 'create') + + let tests = [ + { args: '--recursive', + opts: [ {recursive: null} ], + tgts: [] }, + { args: '-r', + opts: [ {recursive: null} ], + tgts: [] }, + { args: '--recursive file.txt', + opts: [ {recursive: null} ], + tgts: [ 'file.txt' ] }, + { args: '-r file.txt', + opts: [ {recursive: null} ], + tgts: [ 'file.txt' ] }, + + { args: '--file file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [] }, + { args: '--file=file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [] }, + { args: '-f file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [] }, + { args: '-ffile.txt', + opts: [ {file: 'file.txt'} ], + tgts: [] }, + + { args: '--recursive=invalid.txt', + fail: 'UNNECESSARY_ARGUMENT' }, + { args: '-rinvalid.txt', + fail: 'INVALID_OPTION' }, + + { args: '--invalid', + fail: 'INVALID_OPTION' }, + { args: '-i', + fail: 'INVALID_OPTION' }, + + { args: '-r file1.txt file2.txt', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt -r', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt -r file2.txt', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '--recursive file1.txt file2.txt', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt --recursive', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt --recursive file2.txt', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '--file file.txt file1.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt --file file.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt --file file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '--file=file.txt file1.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt --file=file.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt --file=file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '-f file.txt file1.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt -f file.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt -f file.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '-ffile.txt file1.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt -ffile.txt file2.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + { args: 'file1.txt file2.txt -ffile.txt', + opts: [ {file: 'file.txt'} ], + tgts: [ 'file1.txt', 'file2.txt' ] }, + + { args: '-r -- file1.txt -f file.txt file2.txt', + opts: [ {recursive: null} ], + tgts: [ 'file1.txt', '-f', 'file.txt', 'file2.txt' ] }, + { args: '-r -', + opts: [ {recursive: null} ], + tgts: [ '-' ] }, + { args: '-f -', + opts: [ {file: '-'} ], + tgts: [] }, + { args: '-f -- -r', + opts: [ {file: '--'}, {recursive: null} ], + tgts: [] }, + + { args: 'assemblies create', + cmds: { mode: 'assemblies', action: 'create' }, + opts: [], + tgts: [] }, + { args: 'templates', + cmds: { mode: 'templates' }, + opts: [], + tgts: [] }, + { args: 'a create', + cmds: { mode: 'assemblies', action: 'create' }, + opts: [], + tgts: [] }, + { args: 'assemblies create templates', + cmds: { mode: 'assemblies', action: 'create' }, + opts: [], + tgts: [ 'templates' ] } + ] + + for (let test of tests) { + let result = parser.parse(test.args.split(/\s+/)) + + if (typeof test.fail !== 'undefined') { + assert.propertyVal(result, 'error', test.fail) + continue + } + + assert.notProperty(result, 'error') + + let opts = result.options.map(opt => ({ [opt.name]: opt.value || null })) + + if (test.cmds) assert.deepEqual(test.cmds, result.commands) + + assert.deepEqual(test.opts, opts) + + assert.deepEqual(test.tgts, result.targets) + } + }) + }) +}) + +describe('Cli', function () { + it('should validate and interpret arguments appropriately', function () { + let tests = [ + { args: '', + rslt: { mode: 'register' } }, + { args: 'register', + rslt: { mode: 'register' } }, + { args: 'register target', + rslt: { error: 'INVALID_ARGUMENT' } }, + { args: 'register --watch', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'assemblies create --steps steps.json', + rslt: { mode: 'assemblies', + action: 'create', + template: false, + steps: 'steps.json', + recursive: false, + watch: false, + fields: {}, + inputs: ['-'], + output: '-' } }, + { args: '--steps steps.json', + rslt: { mode: 'assemblies', + action: 'create', + steps: 'steps.json' } }, + { args: '--template 5', + rslt: { mode: 'assemblies', + action: 'create', + template: '5', + steps: false } }, + { args: '-t5 --watch', + rslt: { watch: true } }, + { args: '-t5 --recursive', + rslt: { recursive: true } }, + { args: '-t5 -ffoo=bar=baz -fa=b -fc=d', + rslt: { fields: { foo: 'bar=baz', a: 'b', c: 'd' } } }, + { args: '-t5 -ia -ib -oc', + rslt: { inputs: ['a', 'b'], + output: 'c' } }, + { args: 'assemblies create', + rslt: { error: 'INVALID_OPTION' } }, + { args: '--template 5 --steps steps.json', + rslt: { error: 'INVALID_OPTION' } }, + { args: '-t5 -oa -ob', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'assemblies list', + rslt: { mode: 'assemblies', + action: 'list', + before: undefined, + after: undefined, + keywords: [], + fields: undefined } }, + { args: 'assemblies list -b 2016-09-11', + rslt: { before: '2016-09-11' } }, + { args: 'assemblies list -a 2016-09-11', + rslt: { after: '2016-09-11' } }, + { args: 'assemblies list --keywords=foo,bar --keywords=baz,qux', + rslt: { keywords: ['foo', 'bar', 'baz', 'qux'] } }, + { args: 'assemblies list --fields foo,bar', + reslt: { fields: ['foo', 'bar'] } }, + { args: 'assemblies list -a 2016-09-11 -a 2016-09-12', + rslt: { error: 'INVALID_OPTION' } }, + { args: 'assemblies list --fields foo,bar --fields baz,qux', + reslt: { error: 'INVALID_OPTION' } }, + + { args: 'assemblies get a b c', + rslt: { mode: 'assemblies', + action: 'get', + assemblies: ['a', 'b', 'c'] } }, + { args: 'assemblies get', + rslt: { error: 'MISSING_ARGUMENT' } }, + { args: 'assemblies get --recursive', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'assemblies delete a b c', + rslt: { mode: 'assemblies', + action: 'delete', + assemblies: ['a', 'b', 'c'] } }, + { args: 'assemblies delete', + rslt: { error: 'MISSING_ARGUMENT' } }, + { args: 'assemblies delete --recursive', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'assemblies replay a b c', + rslt: { mode: 'assemblies', + action: 'replay', + assemblies: ['a', 'b', 'c'], + reparse: false, + fields: {}, + steps: false } }, + { args: 'assemblies replay --reparse-template a', + rslt: { reparse: true } }, + { args: 'assemblies replay -fa=b a', + rslt: { fields: { a: 'b' } } }, + { args: 'assemblies replay', + rslt: { error: 'MISSING_ARGUMENT' } }, + { args: 'assemblies replay --recursive', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'templates create foo', + rslt: { mode: 'templates', + action: 'create', + name: 'foo', + file: '-' } }, + { args: 'templates create foo steps.json', + rslt: { name: 'foo', + file: 'steps.json' } }, + { args: 'templates create', + rslt: { error: 'MISSING_ARGUMENT' } }, + { args: 'templates create a b c', + rslt: { error: 'INVALID_ARGUMENT' } }, + + { args: 'templates get a', + rslt: { mode: 'templates', + action: 'get', + templates: ['a'] } }, + { args: 'templates get a b', + rslt: { templates: ['a', 'b'] } }, + { args: 'templates get', + rslt: { error: 'MISSING_ARGUMENT' } }, + + { args: 'templates modify foo', + rslt: { mode: 'templates', + action: 'modify', + template: 'foo', + name: undefined, + file: '-' } }, + { args: 'templates modify foo steps.json', + rslt: { template: 'foo', + file: 'steps.json' } }, + { args: 'templates modify foo -n bar steps.json', + rslt: { template: 'foo', + name: 'bar', + file: 'steps.json' } }, + { args: 'templates modify', + rslt: { error: 'MISSING_ARGUMENT' } }, + { args: 'templates modify a b c', + rslt: { error: 'INVALID_ARGUMENT' } }, + + { args: 'templates delete a', + rslt: { mode: 'templates', + action: 'delete', + templates: ['a'] } }, + { args: 'templates delete a b', + rslt: { templates: ['a', 'b'] } }, + { args: 'templates delete', + rslt: { error: 'MISSING_ARGUMENT' } }, + + { args: 'templates list', + rslt: { mode: 'templates', + action: 'list', + before: undefined, + after: undefined, + sort: 'created', + order: 'desc', + fields: undefined } }, + { args: 'templates list --before 2016-09-13 --after 2015-09-13', + rslt: { before: '2016-09-13', + after: '2015-09-13' } }, + { args: 'templates list --fields id,created', + rslt: { fields: ['id', 'created'] } }, + { args: 'templates list --order=asc', + rslt: { order: 'asc' } }, + { args: 'templates list --order=invalid', + rslt: { error: 'INVALID_OPTION' } }, + { args: 'templates list --sort=name', + rslt: { sort: 'name' } }, + { args: 'templates list --sort=invalid', + rslt: { error: 'INVALID_OPTION' } }, + + { args: 'bills get', + rslt: { mode: 'bills', + action: 'get', + months: [`${new Date().getUTCFullYear()}-${new Date().getUTCMonth() + 1}`] } }, + { args: 'bills get 2016-08 2016-07', + rslt: { months: ['2016-08', '2016-07'] } }, + { args: 'bills get invalid', + rslt: { error: 'INVALID_ARGUMENT' } } + ] + + for (let test of tests) { + let args = test.args.split(/\s+/) + let result = cli(args == false ? [] : args) + if (args[0] === '--edit-template') console.log(result) + for (let key in test.rslt) { + if (!test.rslt.hasOwnProperty(key)) continue + assert.deepEqual(test.rslt[key], result[key], + `expected result.${key} to be ${JSON.stringify(test.rslt[key])}; was ${result[key]}\nargs: ${test.args}\nresult: ${JSON.stringify(result)}\n`) + } + } + }) +}) From 8483426df1bb5f8fe961aa92ce909b179528413b Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:16:43 +0100 Subject: [PATCH 2/8] Run linting on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 209cea9..a3c9120 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ cache: directories: - website/.lanyon install: npm install # <-- yarn still messes up nested bins: https://github.com/yarnpkg/yarn/issues/760 -script: npm run test +script: npm run lint && npm run test before_cache: - rm -f ./node_modules/.bin/which before_deploy: node_modules/.bin/lanyon postinstall From f2905718b25d2d77df9edd8afcbc3537b220af7b Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:20:58 +0100 Subject: [PATCH 3/8] Update CHANGELOG --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e270218..2ebdd3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,13 @@ Here's is a combined todo/done list. You can see what todos are planned for the ## v0.2.2 -Released: Not yet +Released: TBA. [Diff](https://github.com/transloadit/transloadify/compare/v0.2.0...master). -- [ ] Add linting via eslint with standardjs.com (Fix codebase via `eslint --fix`, make violations fatal on Travis CI tests) (@kvz) -- [ ] Make tests running & passing on Travis +- [ ] Fix remaining linting issues (@adrusi) +- [x] Add linting via eslint with standardjs.com (Fix codebase via `eslint --fix`, make violations fatal on Travis CI tests) (@kvz) +- [x] Make tests running & passing on Travis (@adrusi) ## v0.2.1 @@ -21,6 +22,6 @@ Released: 2016-12-01. [Diff](https://github.com/transloadit/transloadify/compare/v0.1.14...v0.1.0). -- [x] release management via npm scripts (Since Go was claiming up until `v0.1.14`, we start at `v0.2.1` - SemVer says you can break BC < 1) +- [x] release management via npm scripts (Since Go was claiming up until `v0.1.14`, we start at `v0.2.1` - SemVer says you can break BC < 1) (@kvz) - [x] Add a simple website, based on README.md, CHANGELOG.md, FAQ.md (@kvz) - [x] First Node.js based version of Transloadify (previously Go) (@adrusi) From 021d4b183c23edf265b52fa5a4e087d6160ca58d Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:22:52 +0100 Subject: [PATCH 4/8] Upgrade Lanyon to v0.0.24 --- package.json | 2 +- yarn.lock | 46 ++++++++++++++++++++-------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index fc1f908..31d984c 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "eslint-plugin-promise": "3.4.0", "eslint-plugin-standard": "2.0.1", "fakefile": "^0.0.8", - "lanyon": "0.0.23", + "lanyon": "0.0.24", "mocha": "^3.2.0", "nodemon": "1.11.0", "npm-run-all": "^3.1.2", diff --git a/yarn.lock b/yarn.lock index c647a3e..f827ede 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3366,9 +3366,9 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -lanyon@0.0.23: - version "0.0.23" - resolved "https://registry.yarnpkg.com/lanyon/-/lanyon-0.0.23.tgz#d6f30a741cb52470eaf97a8badb27f0a47f9504c" +lanyon@0.0.24: + version "0.0.24" + resolved "https://registry.yarnpkg.com/lanyon/-/lanyon-0.0.24.tgz#2aac1728cc98bdac38fc233c8ec453cb98919339" dependencies: autoprefixer "6.5.3" babel-core "6.18.2" @@ -4852,7 +4852,7 @@ readable-stream@^1.0.27-1, readable-stream@^1.1.13: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@~2.1.4: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: @@ -4864,7 +4864,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@~2.0.0, readable-stream@~2.0.5: +readable-stream@^2.0.2, readable-stream@~2.0.0, readable-stream@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -5002,18 +5002,17 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@2, request@2.74.0, request@^2.61.0: - version "2.74.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.74.0.tgz#7693ca768bbb0ea5c8ce08c084a45efa05b892ab" +request@2, request@^2.61.0, request@^2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" - bl "~1.1.2" caseless "~0.11.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" - form-data "~1.0.0-rc4" + form-data "~2.1.1" har-validator "~2.0.6" hawk "~3.1.3" http-signature "~1.1.0" @@ -5021,12 +5020,12 @@ request@2, request@2.74.0, request@^2.61.0: isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.7" - node-uuid "~1.4.7" oauth-sign "~0.8.1" - qs "~6.2.0" + qs "~6.3.0" stringstream "~0.0.4" tough-cookie "~2.3.0" tunnel-agent "~0.4.1" + uuid "^3.0.0" request@2.65.0: version "2.65.0" @@ -5052,17 +5051,18 @@ request@2.65.0: tough-cookie "~2.2.0" tunnel-agent "~0.4.1" -request@^2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" +request@2.74.0: + version "2.74.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.74.0.tgz#7693ca768bbb0ea5c8ce08c084a45efa05b892ab" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" + bl "~1.1.2" caseless "~0.11.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" - form-data "~2.1.1" + form-data "~1.0.0-rc4" har-validator "~2.0.6" hawk "~3.1.3" http-signature "~1.1.0" @@ -5070,12 +5070,12 @@ request@^2.79.0: isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.7" + node-uuid "~1.4.7" oauth-sign "~0.8.1" - qs "~6.3.0" + qs "~6.2.0" stringstream "~0.0.4" tough-cookie "~2.3.0" tunnel-agent "~0.4.1" - uuid "^3.0.0" require-directory@^2.1.1: version "2.1.1" @@ -5417,13 +5417,7 @@ source-map-resolve@^0.3.0: source-map-url "~0.3.0" urix "~0.1.0" -source-map-support@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.2.tgz#0710dc5315401e0bedbe0899a1bb938adbc02d5b" - dependencies: - source-map "0.1.32" - -source-map-support@^0.4.6: +source-map-support@^0.4.2, source-map-support@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.6.tgz#32552aa64b458392a85eab3b0b5ee61527167aeb" dependencies: @@ -5433,7 +5427,7 @@ source-map-url@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" -source-map@0.1.32, source-map@0.1.x: +source-map@0.1.x: version "0.1.32" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" dependencies: From 7844c3af9121e74dd3344dc9a8ead82acade4417 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:34:01 +0100 Subject: [PATCH 5/8] More todos --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ebdd3e..30e02bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Here's is a combined todo/done list. You can see what todos are planned for the ## Ideas -- [ ] Add a logo for display up on http://transloadify.io +- [ ] Add a logo for display up on http://transloadify.io (@kvz) +- [ ] A way to register at Transloadit straight from the CLI ## v0.2.2 From 57c621f082e01aa2b37d3bbb174e175d3c781f5a Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:35:06 +0100 Subject: [PATCH 6/8] More todos --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e02bf..95b20b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Released: TBA. [Diff](https://github.com/transloadit/transloadify/compare/v0.2.0...master). +- [ ] Document the Template on-disk feature in the README.md (@adrusi) - [ ] Fix remaining linting issues (@adrusi) - [x] Add linting via eslint with standardjs.com (Fix codebase via `eslint --fix`, make violations fatal on Travis CI tests) (@kvz) - [x] Make tests running & passing on Travis (@adrusi) From 0e9c24bea2ac49b2a08650513ae5aaa399334afe Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 1 Dec 2016 14:35:26 +0100 Subject: [PATCH 7/8] more todos --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b20b8..f5ea06e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Released: TBA. [Diff](https://github.com/transloadit/transloadify/compare/v0.2.0...master). - [ ] Document the Template on-disk feature in the README.md (@adrusi) -- [ ] Fix remaining linting issues (@adrusi) +- [ ] Fix remaining linting issues, then merge https://github.com/transloadit/transloadify/pull/7 (@adrusi) - [x] Add linting via eslint with standardjs.com (Fix codebase via `eslint --fix`, make violations fatal on Travis CI tests) (@kvz) - [x] Make tests running & passing on Travis (@adrusi) From 6af424f1f238596d4ef84cc99196d94ed2f3fd9b Mon Sep 17 00:00:00 2001 From: Adrian Sinclair Date: Sat, 3 Dec 2016 16:48:44 -0500 Subject: [PATCH 8/8] fix linter errors (#8) --- src/Parser.js | 3 ++- src/assemblies-create.js | 2 +- src/assemblies.js | 7 +++++-- src/cli.js | 9 ++------- src/notifications.js | 4 ++-- src/templates.js | 6 +++++- test/cli.js | 2 +- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index 92ebdcf..6bf9726 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -21,7 +21,8 @@ export default class Parser { parse (args) { if (args == null) args = Array.from(process.argv.slice(2)) - return this._parse(args, {}, [], []) + + return this._parse(args, {}, [], []) } _parse (args, cmds, opts, tgts) { diff --git a/src/assemblies-create.js b/src/assemblies-create.js index b9b8708..aa8a1a9 100644 --- a/src/assemblies-create.js +++ b/src/assemblies-create.js @@ -282,7 +282,7 @@ export default function run (outputctl, client, { steps, template, fields, watch emitter.on('job', job => { outputctl.debug(`GOT JOB ${job.in.path} ${job.out.path}`) let superceded = false - job.out.on('finish', () => superceded = true) + job.out.on('finish', () => { superceded = true }) if (job.in != null) client.addStream('in', job.in) diff --git a/src/assemblies.js b/src/assemblies.js index 9cb20a3..6b18b0b 100644 --- a/src/assemblies.js +++ b/src/assemblies.js @@ -8,7 +8,8 @@ export function list (output, client, { before, after, fields, keywords }) { let assemblies = client.streamAssemblies({ fromdate: after, todate: before, - fields, keywords + fields, + keywords }) assemblies.on('readable', () => { @@ -70,7 +71,9 @@ export function replay (output, client, { fields, reparse, steps, notify_url, as client.replayAssembly({ assembly_id: assembly, reparse_template: reparse, - fields, steps, notify_url + fields, + steps, + notify_url }, (err, result) => { if (err) return output.error(formatAPIError(err)) }) diff --git a/src/cli.js b/src/cli.js index b4a512f..57993e0 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,4 +1,3 @@ -import fs from 'fs' import Parser from './Parser' const parser = new Parser() @@ -54,7 +53,6 @@ export default function cli (...args) { } function generalValidation (options) { - let modesSpecified = [] for (let option of options) { if (option.name === 'field' && !option.value.match(/^[^=]+=[\s\S]*$/)) { return { @@ -109,13 +107,13 @@ function modeDispatch ({ mode, action }, opts, tgts) { if (action != null) mode = 'assemblies' else if (opts.length === 0) mode = 'register' else if (opts.filter(opt => opt.name === 'version').length !== 0) mode = 'version' - else mode = 'assemblies', action = 'create' + else [mode, action] = ['assemblies', 'create'] } let verbosity = getVerbosity(opts) let noJsonFlag = opts.filter(opt => opt.name !== 'json') - let jsonMode = opts.length != noJsonFlag.length + let jsonMode = opts.length !== noJsonFlag.length opts = noJsonFlag let handler = subcommands[mode] @@ -192,9 +190,6 @@ function exactlyOneOfOption (optClassFn, msgfn) { function atMostOneOfOption (optClassFn, msgfn) { return nOfOption(optClassFn, 0, 1, msgfn) } -function atLeastOneOfOption (optClassFn, msgfn) { - return nOfOption(optClassFn, 1, Infinity, msgfn) -} function noTargets (msg) { return (opts, tgts) => { diff --git a/src/notifications.js b/src/notifications.js index c23e069..b8b03f9 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -1,6 +1,6 @@ export function replay (output, client, { notify_url, assemblies }) { - for (let assembly_id of assemblies) { - client.replayAssemblyNotification({ notify_url, assembly_id }, (err, result) => { + for (let id of assemblies) { + client.replayAssemblyNotification({ notify_url, assembly_id: id }, (err, result) => { if (err) return output.error(err) }) } diff --git a/src/templates.js b/src/templates.js index a5e3289..b52eab2 100644 --- a/src/templates.js +++ b/src/templates.js @@ -3,6 +3,8 @@ import { stream2buf, createReadStream, inSequence, formatAPIError } from './help export function create (output, client, { name, file }) { stream2buf(createReadStream(file), (err, buf) => { + if (err) return output.error(err.message) + client.createTemplate({ name, template: buf.toString() }, (err, result) => { if (err) return output.error(err.message) output.print(result.id, result) @@ -63,7 +65,9 @@ export function list (output, client, { before, after, order, sort, fields }) { let stream = client.streamTemplates({ todate: before, fromdate: after, - order, sort, fields + order, + sort, + fields }) stream.on('readable', () => { diff --git a/test/cli.js b/test/cli.js index 02c183f..6492ab6 100644 --- a/test/cli.js +++ b/test/cli.js @@ -371,7 +371,7 @@ describe('Cli', function () { for (let test of tests) { let args = test.args.split(/\s+/) - let result = cli(args == false ? [] : args) + let result = cli(args.filter(arg => arg.trim() !== '').length === 0 ? [] : args) if (args[0] === '--edit-template') console.log(result) for (let key in test.rslt) { if (!test.rslt.hasOwnProperty(key)) continue