workshopper-adventure
Advanced tools
Comparing version 3.5.2 to 4.0.0
710
adventure.js
@@ -1,682 +0,84 @@ | ||
const minimist = require('minimist') | ||
, fs = require('fs') | ||
, path = require('path') | ||
, map = require('map-async') | ||
, msee = require('msee') | ||
, chalk = require('chalk') | ||
const path = require('path') | ||
, inherits = require('util').inherits | ||
, EventEmitter = require('events').EventEmitter | ||
/* jshint -W079 */ | ||
const createMenu = require('simple-terminal-menu') | ||
, print = require('./print-text') | ||
, util = require('./util') | ||
, i18n = require('./i18n') | ||
const Core = require('./index') | ||
, error = require('./lib/print').error | ||
/* jshint +W079 */ | ||
const defaultWidth = 65 | ||
function legacyCommands(item) { | ||
if (!item.aliases) | ||
item.aliases = [] | ||
if (item && item.name) | ||
item.aliases.unshift(item.name) | ||
return item | ||
} | ||
function Adventure (options) { | ||
if (!(this instanceof Adventure)) | ||
return new Adventure(options) | ||
function LegacyAdventure (options) { | ||
if (!(this instanceof LegacyAdventure)) | ||
return new LegacyAdventure(options) | ||
EventEmitter.call(this) | ||
if (typeof options === 'string') | ||
options = {name: options} | ||
if (!options) | ||
options = {} | ||
if (typeof options !== 'object') | ||
throw new TypeError('need to provide an options object') | ||
return error('You need to provide an options object') | ||
if (typeof options.name !== 'string') | ||
throw new TypeError('need to provide a `name` String option') | ||
if (!options.commands) | ||
options.commands = options.menuItems | ||
this.name = options.name | ||
this.appName = options.name | ||
if (options.commands) | ||
options.commands = options.commands.map(legacyCommands) | ||
this.appDir = util.getDir(options.appDir, '.') | ||
if (this.appDir) | ||
this.exerciseDir = util.getDir(options.exerciseDir, this.appDir) | ||
|| util.getDir('exercises', this.appDir) | ||
if (options.modifiers) | ||
options.modifiers = option.modifiers.map(legacyCommands) | ||
this.globalDataDir = util.userDir('.config', 'workshopper') | ||
this.dataDir = util.userDir('.config', this.appName) | ||
// Backwards compatibility with adventure | ||
this.datadir = this.dataDir | ||
if (!options.languages) { | ||
// In case a workshopper didn't define a any language | ||
options.languages = ['en'] | ||
} | ||
if (options.helpFile) | ||
options.help = {file: options.helpFile} | ||
if (!options.defaultLang) { | ||
options.defaultLang = options.languages[0] | ||
if (!options.footer) { | ||
if (options.footerFile) { | ||
options.footer = { file: options.footerFile } | ||
} else { | ||
options.footer = require('./default/footer') | ||
} | ||
} | ||
this.defaultLang = options.defaultLang | ||
this.menuOptions = options.menu || {} | ||
this.helpFile = options.helpFile | ||
this.footer = options.footer | ||
this.showHeader = options.showHeader || false | ||
this.footerFile = [options.footerFile, path.join(__dirname, './i18n/footer/{lang}.md')] | ||
if (typeof this.menuOptions.width !== 'number') | ||
this.menuOptions.width = typeof options.width == 'number' ? options.width : defaultWidth | ||
if (typeof this.menuOptions.x !== 'number') | ||
this.menuOptions.x = 3 | ||
if (typeof this.menuOptions.y !== 'number') | ||
this.menuOptions.y = 2 | ||
// an `onComplete` hook function *must* call the callback given to it when it's finished, async or not | ||
this.onComplete = typeof options.onComplete == 'function' && options.onComplete | ||
if (typeof options.onComplete == 'function') | ||
this.onComplete = options.onComplete | ||
this.exercises = [] | ||
this._meta = {} | ||
Core.call(this, options) | ||
var menuJson = util.getFile(options.menuJson || 'menu.json', this.exerciseDir) | ||
if (menuJson) { | ||
require(menuJson).forEach((function (entry) { | ||
this.add(entry) | ||
}).bind(this)) | ||
if (options.execute === 'now') { | ||
this.execute(process.argv.slice(2)) | ||
} else if (options.execute === 'immediatly') { | ||
setImmediate(this.execute.bind(this, process.argv.slice(2))) | ||
} | ||
if (options.menuItems && !options.commands) | ||
options.commands = options.menuItems | ||
this.commands = Array.isArray(options.commands) ? options.commands : [] | ||
this.options = options | ||
} | ||
inherits(Adventure, EventEmitter) | ||
Adventure.prototype.execute = function (args) { | ||
var mode = args[0] | ||
, handled = false | ||
, argv = minimist(args, { | ||
alias: { | ||
h: 'help', | ||
l: 'lang', | ||
v: 'version', | ||
select: 'print', | ||
selected: 'current' | ||
} | ||
}) | ||
try { | ||
this.lang = i18n.chooseLang( | ||
this.globalDataDir | ||
, this.dataDir | ||
, argv.lang | ||
, this.defaultLang | ||
, this.options.languages | ||
) | ||
} catch (e) { | ||
if (e instanceof TypeError) // In case the language couldn't be selected | ||
console.log(e.message) | ||
else | ||
console.error(e.stack) | ||
process.exit(1) | ||
} | ||
this.i18n = i18n.init(this.options, this.exercises, this.lang, this.globalDataDir) | ||
this.__ = this.i18n.__ | ||
this.__n = this.i18n.__n | ||
this.languages = this.i18n.languages | ||
// backwards compatibility for title and subtitle and width | ||
// backwards compatibility support | ||
this.__defineGetter__('title', this.__.bind(this, 'title')) | ||
this.__defineGetter__('subtitle', this.__.bind(this, 'subtitle')) | ||
this.__defineGetter__('width', function () { | ||
return this.menuOptions.width | ||
}.bind(this)) | ||
this.current = this.getData('current') | ||
if (this.commands) | ||
this.commands.forEach(function (item) { | ||
if (mode == item.name | ||
|| argv[item.name] | ||
|| (item.short && argv[item.short])) { | ||
handled = true | ||
return item.handler(this) | ||
} | ||
}.bind(this)) | ||
if (argv.version || mode == 'version') | ||
return console.log( | ||
this.appName | ||
+ '@' | ||
+ require(path.join(this.appDir, 'package.json')).version | ||
) | ||
if (handled) | ||
return | ||
if (argv.help || mode == 'help') | ||
return this._printHelp() | ||
if (mode == 'list') { | ||
return this.exercises.forEach(function (name) { | ||
console.log(this.__('exercise.' + name)) | ||
}.bind(this)) | ||
} | ||
if (mode == 'current') | ||
return console.log(this.__('exercise.' + this.current)) | ||
if (mode == 'print') { | ||
var selected = argv._.length > 1 ? argv._.slice(1).join(' ') : this.current | ||
if (/[0-9]+/.test(selected)) { | ||
selected = this.exercises[parseInt(selected-1, 10)] || selected | ||
} else { | ||
selected = this.exercises.filter(function (exercise) { | ||
return selected === this.__('exercise.' + exercise) | ||
}.bind(this))[0] || selected; | ||
} | ||
onselect.call(this, selected) | ||
return | ||
} | ||
if (mode == 'verify' || mode == 'run') { | ||
if (!this.current) | ||
return error(this.__('error.exercise.none_active')) | ||
exercise = this.loadExercise(this.current) | ||
if (!exercise) | ||
return error(this.__('error.exercise.missing', {name: name})) | ||
if (exercise.requireSubmission !== false && argv._.length == 1) | ||
return error(this.__('ui.usage', {appName: this.appName, mode: mode})) | ||
return this.runExercise(exercise, mode, argv._.slice(1)) | ||
} | ||
if (mode == 'next') { | ||
var remainingAfterCurrent = this.exercises.slice(this.exercises.indexOf(this.current)) | ||
var completed = this.getData('completed') | ||
if (!completed) | ||
return error(this.__('error.exercise.none_active') + '\n') | ||
var incompleteAfterCurrent = remainingAfterCurrent.filter(function (elem) { | ||
return completed.indexOf(elem) < 0 | ||
}) | ||
if (incompleteAfterCurrent.length === 0) | ||
return console.log(this.__('error.no_uncomplete_left') + '\n') | ||
return onselect.call(this, incompleteAfterCurrent[0]) | ||
} | ||
if (mode == 'reset') { | ||
this.reset() | ||
return console.log(this.__('progress.reset', {title: this.__('title')})) | ||
} | ||
this.printMenu() | ||
this.__defineGetter__('name', this.__.bind(this, 'name')) | ||
this.__defineGetter__('appName', this.__.bind(this, 'name')) | ||
this.__defineGetter__('appname', this.__.bind(this, 'name')) | ||
this.__defineGetter__('lang', this.i18n.lang.bind(this.i18n, 'lang')) | ||
this.__defineGetter__('width', function () { return this.menuFactory.options.width }.bind(this)) | ||
this.__defineGetter__('helpFile', function () { return this.options.helpFile }.bind(this)) | ||
this.__defineGetter__('footer', function () { return this.options.footer }.bind(this)) | ||
this.__defineGetter__('defaultLang', function () { return this.options.defaultLang }.bind(this)) | ||
this.__defineGetter__('languages', function () { return this.options.languages }.bind(this)) | ||
this.__defineGetter__('globalDataDir', function () { return this.globalStorage.dir }.bind(this)) | ||
this.__defineGetter__('dataDir', function () { return this.appStorage.dir }.bind(this)) | ||
this.__defineGetter__('datadir', function () { return this.appStorage.dir }.bind(this)) // adventure | ||
this.__defineGetter__('appDir', function () { return this.options.appDir }.bind(this)) | ||
this.__defineGetter__('exerciseDir', function () { return this.options.exerciseDir }.bind(this)) | ||
this.__defineGetter__('current', function () { return this.appStorage.get('current') }.bind(this)) | ||
} | ||
Adventure.prototype.add = function (name_or_object, fn_or_object, fn) { | ||
var meta | ||
, dir | ||
, stat | ||
inherits(LegacyAdventure, Core) | ||
meta = (typeof name_or_object === 'object') | ||
? name_or_object | ||
: (typeof fn_or_object === 'object') | ||
? fn_or_object | ||
: { name: name_or_object } | ||
if (typeof name_or_object === 'string') | ||
meta.name = name_or_object | ||
if (/^\/\//.test(meta.name)) | ||
return | ||
if (!meta.id) | ||
meta.id = util.idFromName(meta.name) | ||
if (!meta.dir) | ||
meta.dir = this.dirFromName(meta.name) | ||
if (meta.dir && !meta.exerciseFile) | ||
meta.exerciseFile = path.join(meta.dir, './exercise.js') | ||
if (typeof fn_or_object === 'function') | ||
meta.fn = fn_or_object | ||
if (typeof fn === 'function') | ||
meta.fn = fn | ||
if (!meta.fn && meta.exerciseFile) { | ||
try { | ||
stat = fs.statSync(meta.exerciseFile) | ||
} catch (err) { | ||
return error(this.__('error.exercise.missing_file', {exerciseFile: meta.exerciseFile})) | ||
} | ||
if (!stat || !stat.isFile()) | ||
return error(this.__('error.exercise.missing_file', {exerciseFile: meta.exerciseFile})) | ||
meta.fn = (function () { | ||
return require(meta.exerciseFile) | ||
}).bind(meta) | ||
} | ||
if (!meta.fn) | ||
return error(this.__('error.exercise.not_a_workshopper', {exerciseFile: meta.exerciseFile})) | ||
this.exercises.push(meta.name) | ||
this._meta[meta.id] = meta | ||
meta.number = this.exercises.length | ||
return this | ||
} | ||
Adventure.prototype.end = function (mode, pass, exercise, callback) { | ||
if (typeof exercise.end == 'function') | ||
exercise.end(mode, pass, function (err) { | ||
if (err) | ||
return error(this.__('error.cleanup', {err: err.message || err})) | ||
setImmediate(callback || function () { | ||
process.exit(pass ? 0 : -1) | ||
}) | ||
}.bind(this)) | ||
} | ||
// overall exercise fail | ||
Adventure.prototype.exerciseFail = function (mode, exercise) { | ||
if (exercise.fail) { | ||
print.text(this.appName, this.appDir, exercise.failType || 'txt', exercise.fail) | ||
console.log() | ||
} else { | ||
console.log('\n' + chalk.bold.red('# ' + this.__('solution.fail.title')) + '\n') | ||
console.log(this.__('solution.fail.message', {name: this.__('exercise.' + exercise.meta.name)})) | ||
} | ||
this.end(mode, false, exercise) | ||
} | ||
// overall exercise pass | ||
Adventure.prototype.exercisePass = function (mode, exercise) { | ||
var done = function done () { | ||
var completed = this.getData('completed') || [] | ||
, remaining | ||
this.updateData('completed', function (xs) { | ||
if (!xs) | ||
xs = [] | ||
return xs.indexOf(exercise.meta.name) >= 0 ? xs : xs.concat(exercise.meta.name) | ||
}) | ||
completed = this.getData('completed') || [] | ||
remaining = this.exercises.length - completed.length | ||
if (exercise.pass) { | ||
print.text(this.appName, this.appDir, exercise.passType || 'txt', exercise.pass) | ||
} else { | ||
console.log('\n' + chalk.bold.green('# ' + this.__('solution.pass.title')) + '\n') | ||
console.log(chalk.bold(this.__('solution.pass.message', {name: this.__('exercise.' + exercise.meta.name)})) + '\n') | ||
} | ||
if (exercise.solution) | ||
print.text(this.appName, this.appDir, exercise.solutionType || 'txt', exercise.solution) | ||
if (remaining === 0) { | ||
if (this.onComplete) | ||
return this.onComplete(this.end.bind(this, mode, true, exercise)) | ||
else | ||
console.log(this.__('progress.finished')) | ||
} else { | ||
console.log(this.__n('progress.remaining', remaining)) | ||
console.log(this.__('ui.return', {appName: this.appName})) | ||
} | ||
this.end(mode, true, exercise) | ||
}.bind(this) | ||
if (!exercise.hideSolutions && typeof exercise.getSolutionFiles === 'function') { | ||
exercise.getSolutionFiles(function (err, files) { | ||
if (err) | ||
return error(this.__('solution.notes.load_error', {err: err.message || err})) | ||
if (!files.length) | ||
return done() | ||
console.log(this.__('solution.notes.compare')) | ||
function processSolutionFile (file, callback) { | ||
fs.readFile(file, 'utf8', function (err, content) { | ||
if (err) | ||
return callback(err) | ||
var filename = path.basename(file) | ||
// code fencing is necessary for msee to render the solution as code | ||
content = msee.parse('```js\n' + content + '\n```') | ||
callback(null, { name: filename, content: content }) | ||
}) | ||
} | ||
function printSolutions (err, solutions) { | ||
if (err) | ||
return error(this.__('solution.notes.load_error', {err: err.message || err})) | ||
solutions.forEach(function (file, i) { | ||
console.log(chalk.yellow(util.repeat('\u2500', 80))) | ||
if (solutions.length > 1) | ||
console.log(chalk.bold.yellow(file.name + ':') + '\n') | ||
console.log(file.content.replace(/^\n/m, '').replace(/\n$/m, '')) | ||
if (i == solutions.length - 1) | ||
console.log(chalk.yellow(util.repeat('\u2500', 80)) + '\n') | ||
}.bind(this)) | ||
done() | ||
} | ||
map(files, processSolutionFile, printSolutions.bind(this)) | ||
}.bind(this)) | ||
} else { | ||
done() | ||
} | ||
} | ||
// single 'pass' event for a validation | ||
function onpass (msg) { | ||
console.log(chalk.green.bold('\u2713 ') + msg) | ||
} | ||
// single 'fail' event for validation | ||
function onfail (msg) { | ||
console.log(chalk.red.bold('\u2717 ') + msg) | ||
} | ||
Adventure.prototype.runExercise = function (exercise, mode, args) { | ||
// individual validation events | ||
if (typeof exercise.on === 'function') { | ||
exercise.on('pass', onpass) | ||
exercise.on('fail', onfail) | ||
exercise.on('pass', this.emit.bind(this, 'pass', exercise, mode)) | ||
exercise.on('fail', this.emit.bind(this, 'fail', exercise, mode)) | ||
} | ||
function done (err, pass) { | ||
if (pass === undefined && (err === true || err === false || err === undefined || err === null)) { | ||
pass = err | ||
err = null | ||
} | ||
if (err) { | ||
// if there was an error then we need to do this after cleanup | ||
return this.end(mode, true, exercise, function () { | ||
error(this.__('error.exercise.unexpected_error', {mode: mode, err: (err.message || err) })) | ||
}.bind(this)) | ||
} | ||
if (mode == 'run') | ||
return this.end(mode, true, exercise) // clean up | ||
if (!pass || exercise.fail) | ||
return this.exerciseFail(mode, exercise) | ||
this.exercisePass(mode, exercise) | ||
} | ||
var method = exercise[mode] | ||
, result; | ||
if (!method) | ||
return error('This problem doesn\'t have a .' + mode + ' function.') | ||
if (typeof method !== 'function') | ||
return error('This .' + mode + ' is a ' + typeof method + '. It should be a function instead.') | ||
result = (method.length > 1) | ||
? method.bind(exercise)(args, done.bind(this)) | ||
: method.bind(exercise)(args) | ||
if (result) | ||
print.text(this.appName, this.appDir, 'txt', result) | ||
} | ||
Adventure.prototype.selectLanguage = function (lang) { | ||
this.i18n.change(this.globalDataDir, this.dataDir, lang, this.defaultLang, this.i18n.languages) | ||
this.lang = lang | ||
this.printMenu() | ||
} | ||
Adventure.prototype.printLanguageMenu = function () { | ||
var __ = this.i18n.__ | ||
, menu = createMenu(this.menuOptions) | ||
, completed = this.getData('completed') || [] | ||
menu.writeLine(chalk.bold(__('title'))) | ||
if (this.i18n.has('subtitle')) | ||
menu.writeLine(chalk.italic(__('subtitle'))) | ||
menu.writeSeparator() | ||
this.i18n.languages.forEach(function (language) { | ||
var label = chalk.bold('»') + ' ' + __('language.' + language) | ||
, marker = (this.lang === language) ? '[' + __('language._current') + ']' : '' | ||
menu.add(label, marker, this.selectLanguage.bind(this, language)) | ||
}.bind(this)) | ||
menu.writeSeparator() | ||
menu.add(chalk.bold(__('menu.cancel')), this.printMenu.bind(this)) | ||
menu.add(chalk.bold(__('menu.exit')), this._exit.bind(this)) | ||
} | ||
Adventure.prototype._exit = function () { | ||
process.exit(0) | ||
} | ||
Adventure.prototype.printMenu = function () { | ||
var __ = this.i18n.__ | ||
, menu = createMenu(this.menuOptions) | ||
, completed = this.getData('completed') || [] | ||
menu.writeLine(chalk.bold(__('title'))) | ||
if (this.i18n.has('subtitle')) | ||
menu.writeLine(chalk.italic(__('subtitle'))) | ||
menu.writeSeparator() | ||
this.exercises.forEach(function (exercise) { | ||
var label = chalk.bold('»') + ' ' + __('exercise.' + exercise) | ||
, marker = (completed.indexOf(exercise) >= 0) ? '[' + __('menu.completed') + ']' : '' | ||
menu.add(label, marker, onselect.bind(this, exercise)) | ||
}.bind(this)) | ||
menu.writeSeparator() | ||
menu.add(chalk.bold(__('menu.help')), this._printHelp.bind(this)) | ||
if (this.i18n.languages && this.i18n.languages.length > 1) | ||
menu.add(chalk.bold(__('menu.language')), this.printLanguageMenu.bind(this)) | ||
this.commands.filter(function (extra) { | ||
return extra.menu !== false | ||
}).forEach(function (extra) { | ||
menu.add(chalk.bold(__('menu.' + extra.name)), extra.handler.bind(extra, this)) | ||
}.bind(this)) | ||
menu.add(chalk.bold(__('menu.exit')), this._exit.bind(this)) | ||
} | ||
Adventure.prototype.getData = function (name) { | ||
var file = path.resolve(this.dataDir, name + '.json') | ||
try { | ||
return JSON.parse(fs.readFileSync(file, 'utf8')) | ||
} catch (e) {} | ||
return null | ||
} | ||
Adventure.prototype.updateData = function (id, fn) { | ||
var json = {} | ||
, file | ||
try { | ||
json = this.getData(id) | ||
} catch (e) {} | ||
file = path.resolve(this.dataDir, id + '.json') | ||
fs.writeFileSync(file, JSON.stringify(fn(json))) | ||
} | ||
Adventure.prototype.reset = function () { | ||
fs.unlink(path.resolve(this.dataDir, 'completed.json'), function () {}) | ||
fs.unlink(path.resolve(this.dataDir, 'current.json'), function () {}) | ||
} | ||
Adventure.prototype.dirFromName = function (name) { | ||
return util.dirFromName(this.exerciseDir, name) | ||
} | ||
Adventure.prototype._printHelp = function () { | ||
var part | ||
, stream = require("combined-stream").create() | ||
if (this.helpFile) | ||
part = print.localisedFileStream(this.appName, this.appDir, this.helpFile, this.lang) | ||
if (part) | ||
stream.append(part) | ||
part = print.localisedFirstFileStream(this.appName, this.appDir, [ | ||
path.join(__dirname, './i18n/usage/{lang}.txt'), | ||
path.join(__dirname, './i18n/usage/en.txt') | ||
], this.lang) | ||
if (part) | ||
stream.append(part) | ||
stream.pipe(process.stdout) | ||
} | ||
Adventure.prototype.getExerciseMeta = function (name) { | ||
return this._meta[util.idFromName(name)] | ||
} | ||
Adventure.prototype.loadExercise = function (name) { | ||
var meta = this.getExerciseMeta(name) | ||
if (!meta) | ||
return null | ||
exercise = meta.fn() | ||
if (typeof exercise.init === 'function') | ||
exercise.init(this, meta.id, meta.name, meta.dir, meta.number) | ||
exercise.meta = meta | ||
return exercise | ||
} | ||
function error () { | ||
var pr = chalk.bold.red | ||
console.log(pr.apply(pr, arguments)) | ||
process.exit(-1) | ||
} | ||
function onselect (name) { | ||
var exercise = this.loadExercise(name) | ||
, afterPrepare | ||
if (!exercise) | ||
return error(this.__('error.exercise.missing', {name: name})) | ||
if (this.showHeader) | ||
console.log( | ||
'\n ' + chalk.green.bold(this.__('title')) | ||
+ '\n' + chalk.green.bold(util.repeat('\u2500', chalk.stripColor(this.__('title')).length + 2)) | ||
+ '\n ' + chalk.yellow.bold(this.__('exercise.' + exercise.meta.name)) | ||
+ '\n ' + chalk.yellow.italic(this.__('progress.state', {count: exercise.meta.number, amount: this.exercises.length})) | ||
+ '\n' | ||
) | ||
this.current = exercise.meta.name | ||
this.updateData('current', function () { | ||
return exercise.meta.name | ||
}) | ||
afterPreparation = function (err) { | ||
if (err) | ||
return error(this.__('error.exercise.preparing', {err: err.message || err})) | ||
afterProblem = function() { | ||
var stream = require('combined-stream').create() | ||
, part | ||
if (exercise.problem) | ||
print.text(this.appName, this.appDir, exercise.problemType || 'txt', exercise.problem) | ||
else { | ||
part = print.localisedFileStream(this.appName, this.appDir, path.resolve(__dirname, 'i18n/missing_problem/{lang}.md'), this.lang) | ||
if (part) | ||
stream.append(part) | ||
} | ||
if (exercise.footer || this.footer) | ||
print.text(this.appName, this.appDir, exercise.footer || this.footer, this.lang) | ||
else if (this.footerFile !== false) { | ||
part = print.localisedFirstFileStream(this.appName, this.appDir, this.footerFile || [], this.lang) | ||
if (part) | ||
stream.append(part) | ||
} | ||
stream.pipe(process.stdout) | ||
}.bind(this) | ||
if (!exercise.problem && typeof exercise.getExerciseText === 'function') { | ||
exercise.getExerciseText(function (err, type, exerciseText) { | ||
if (err) | ||
return error(this.__('error.exercise.loading', {err: err.message || err})) | ||
exercise.problem = exerciseText | ||
exercise.problemType = type | ||
afterProblem() | ||
}.bind(this)) | ||
} else { | ||
afterProblem() | ||
} | ||
}.bind(this) | ||
if (typeof exercise.prepare === 'function') { | ||
exercise.prepare(afterPreparation) | ||
} else { | ||
afterPreparation(null) | ||
} | ||
} | ||
Adventure.prototype.error = error | ||
Adventure.prototype.print = print | ||
module.exports = Adventure | ||
module.exports = LegacyAdventure |
168
i18n.js
const i18n = require('i18n-core') | ||
, i18nFs = require('i18n-core/lookup/fs') | ||
, i18nObject = require('i18n-core/lookup/object') | ||
, i18nChain = require('i18n-core/lookup/chain') | ||
, i18nExtend = require('i18n-core/lookup/extend') | ||
, path = require('path') | ||
, fs = require('fs') | ||
, UNDERLINE = 'Underline' | ||
, chalk = require('chalk') | ||
, util = require('./util') | ||
function i18nChain() { | ||
var linked = { | ||
handler: arguments[0] | ||
, next: null | ||
} | ||
, current = linked | ||
for (var i = 1; i<arguments.length; i++) { | ||
var next = { | ||
handler: arguments[i] | ||
} | ||
current.next = next | ||
current = next | ||
} | ||
return { | ||
get: function (key) { | ||
var current = linked | ||
, result | ||
while (!result && current) { | ||
result = current.handler.get(key) | ||
current = current.next | ||
} | ||
return result | ||
} | ||
} | ||
function commandify (s) { | ||
return String(s).toLowerCase().replace(/\s+/g, '-'); | ||
} | ||
function createDefaultLookup(options, exercises) { | ||
var result = {} | ||
result[options.defaultLang] = { | ||
title: options.title | ||
, subtitle: options.subtitle | ||
, exercise: {} | ||
} | ||
options.languages.forEach(function (language) { | ||
if (!result[language]) | ||
result[language] = {} | ||
if (!result[language].title) | ||
result[language].title = options.name.toUpperCase() | ||
}) | ||
exercises.forEach(function (exercise) { | ||
result[options.defaultLang].exercise[exercise] = exercise | ||
}) | ||
return result | ||
} | ||
function chooseLang (globalDataDir, appDataDir, lang, defaultLang, availableLangs) { | ||
var globalPath = path.resolve(globalDataDir, 'lang.json') | ||
, appPath = path.resolve(appDataDir, 'lang.json') | ||
, data | ||
try { | ||
// Lets see if we find some stored language in the app's config | ||
data = require(appPath) | ||
} catch (e) { | ||
// Without a file an error will occur here, but thats okay | ||
} | ||
if (!data) { | ||
// Lets see if some other workshopper stored language settings | ||
try { | ||
data = require(globalPath) | ||
} catch (e) { | ||
data = {} | ||
// Without a file an error will occur here, but thats okay | ||
} | ||
} | ||
function chooseLang (globalStorage, appStorage, defaultLang, availableLangs, lang) { | ||
if (!!lang && typeof lang != 'string') | ||
throw new TypeError('Please supply a language. Available languages are: ' + availableLangs.join(', ')) | ||
throw new Error('Please supply a language. Available languages are: ' + availableLangs.join(', ')) | ||
@@ -85,7 +24,9 @@ if (lang) | ||
if (availableLangs.indexOf(defaultLang) === -1) | ||
throw new TypeError('The default language "' + defaultLang + ' is not one of the available languages?! Available languages are: ' + availableLangs.join(', ')) | ||
throw new Error('The default language "' + defaultLang + ' is not one of the available languages?! Available languages are: ' + availableLangs.join(', ')) | ||
if (lang && availableLangs.indexOf(lang) === -1) | ||
throw new TypeError('The language "' + lang + '" is not available.\nAvailable languages are ' + availableLangs.join(', ') + '.\n\nNote: the language is not case-sensitive ("en", "EN", "eN", "En" will become "en") and you can use "_" instead of "-" for seperators.') | ||
throw new Error('The language "' + lang + '" is not available.\nAvailable languages are ' + availableLangs.join(', ') + '.\n\nNote: the language is not case-sensitive ("en", "EN", "eN", "En" will become "en") and you can use "_" instead of "-" for seperators.') | ||
var data = ((appStorage && appStorage.get('lang')) || globalStorage.get('lang') || {}) | ||
if (availableLangs.indexOf(data.selected) === -1) | ||
@@ -97,8 +38,6 @@ // The stored data is not available so lets use one of the other languages | ||
try { | ||
fs.writeFileSync(globalPath, JSON.stringify(data)) | ||
fs.writeFileSync(appPath, JSON.stringify(data)) | ||
} catch(e) { | ||
// It is not good if an error occurs but it shouldn't really matter | ||
} | ||
globalStorage.save('lang', data) | ||
if (appStorage) | ||
appStorage.save('lang', data) | ||
return data.selected | ||
@@ -108,27 +47,64 @@ } | ||
module.exports = { | ||
chooseLang: chooseLang, | ||
init: function(options, exercises, lang) { | ||
var generalTranslator = i18nChain( | ||
i18nFs(path.resolve(__dirname, './i18n')) | ||
, i18nObject(createDefaultLookup(options, exercises)) | ||
init: function(options, globalStorage, appStorage) { | ||
var lookup = i18nChain( | ||
options.appDir ? i18nFs(path.resolve(options.appDir, './i18n')) : null | ||
, i18nFs(path.resolve(__dirname, './i18n')) | ||
) | ||
, translator = i18n( | ||
options.appDir | ||
? i18nChain( i18nFs(path.resolve(options.appDir, './i18n')), generalTranslator) | ||
: generalTranslator | ||
) | ||
, result = translator.lang(lang, true) | ||
translator.fallback = function (key) { | ||
if (!key) | ||
return '(???)' | ||
, root = i18n(lookup) | ||
, choose = chooseLang.bind(null, globalStorage, appStorage, options.defaultLang, options.languages) | ||
, lang = choose(null) | ||
, translator = root.lang(lang, true) | ||
, result = i18n(i18nExtend(translator, { | ||
get: function (key) { | ||
if (options[key]) | ||
return options[key] | ||
// legacy -- start | ||
if (key === 'title') | ||
return options.name.toUpperCase() | ||
if (key === 'appName' || key === 'appname' || key === 'ADVENTURE_NAME') | ||
return options.name | ||
if (key === 'rootdir') | ||
return options.appDir | ||
if (key === 'COMMAND' || key === 'ADVENTURE_COMMAND') | ||
return commandify(options.name) | ||
// legacy -- end | ||
var exercisePrefix = 'exercise.' | ||
if (key.indexOf(exercisePrefix) === 0) | ||
return key.substr(exercisePrefix.length) | ||
if (key.length > UNDERLINE.length) { | ||
var end = key.length-UNDERLINE.length | ||
if (key.indexOf(UNDERLINE) === end) | ||
return util.repeat('\u2500', chalk.stripColor(result.__(key.substr(0, end))).length + 2) | ||
} | ||
} | ||
})) | ||
, _exercises = [] | ||
root.fallback = function (key) { | ||
return '?' + key + '?' | ||
} | ||
result.languages = options.languages || ['en'] | ||
result.change = function (globalDataDir, appDataDir, lang, defaultLang, availableLangs) { | ||
result.changeLang(lang) | ||
chooseLang(globalDataDir, appDataDir, lang, defaultLang, availableLangs) | ||
result.change = function (lng) { | ||
lang = choose(lng) | ||
translator.changeLang(lang) | ||
} | ||
result.extend = function (obj) { | ||
return i18n(i18nExtend(result, { | ||
get: function (key) { | ||
return obj[key] | ||
} | ||
})); | ||
} | ||
result.updateExercises = function(exercises) { | ||
_exercises = exercises; | ||
} | ||
result.lang = function () { | ||
return lang | ||
} | ||
return result | ||
} | ||
} |
@@ -43,7 +43,7 @@ { | ||
"title": "NICHT BESTANDEN", | ||
"message": "Deine Lösung für {{{name}}} hat den Test nicht bestanden. Versuche es erneut!\n" | ||
"message": "Deine Lösung für {{{currentExercise.name}}} hat den Test nicht bestanden. Versuche es erneut!\n" | ||
}, | ||
"pass": { | ||
"title": "BESTANDEN", | ||
"message": "Deine Lösung für {{{name}}} hat den Test bestanden!" | ||
"message": "Deine Lösung für {{{currentExercise.name}}} hat den Test bestanden!" | ||
}, | ||
@@ -50,0 +50,0 @@ "notes": { |
@@ -43,7 +43,7 @@ { | ||
"title": "FAIL", | ||
"message": "Your solution to {{{name}}} didn't pass. Try again!\n" | ||
"message": "Your solution to {{{currentExercise.name}}} didn't pass. Try again!\n" | ||
}, | ||
"pass": { | ||
"title": "PASS", | ||
"message": "Your solution to {{{name}}} passed!" | ||
"message": "Your solution to {{{currentExercise.name}}} passed!" | ||
}, | ||
@@ -50,0 +50,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "Ruso (Русский)", | ||
"uk": "Ucranio (Українська)", | ||
"ko": "Coreano (한국어)", | ||
@@ -43,7 +44,7 @@ "pl": "Polaco (Polski)", | ||
"title": "FALLO", | ||
"message": "Su solución a {{{name}}} no pasó. Intentalo de nuevo!\n" | ||
"message": "Su solución a {{{currentExercise.name}}} no pasó. Intentalo de nuevo!\n" | ||
}, | ||
"pass": { | ||
"title": "PASO", | ||
"message": "Su solución a {{{name}}} pasó sin problemas!" | ||
"message": "Su solución a {{{currentExercise.name}}} pasó sin problemas!" | ||
}, | ||
@@ -50,0 +51,0 @@ "notes": { |
@@ -1,4 +0,4 @@ | ||
__»__ Um diese Anweisungen erneut auszugeben: `{appname} print` | ||
__»__ Um dein Programm in der Testumgebung auszuführen: `{appname} run program.js` | ||
__»__ Um dein Programm zu verifizieren: `{appname} verify program.js` | ||
__»__ Für Hilfe: `{appname} help` | ||
- Um diese Anweisungen erneut auszugeben: `{appname} print` | ||
- Um dein Programm in der Testumgebung auszuführen: `{appname} run program.js` | ||
- Um dein Programm zu verifizieren: `{appname} verify program.js` | ||
- Für Hilfe: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ To print these instructions again, run: `{appname} print` | ||
__»__ To execute your program in a test environment, run: `{appname} run program.js` | ||
__»__ To verify your program, run: `{appname} verify program.js` | ||
__»__ For help run: `{appname} help` | ||
- To print these instructions again, run: `{appname} print` | ||
- To execute your program in a test environment, run: `{appname} run program.js` | ||
- To verify your program, run: `{appname} verify program.js` | ||
- For help run: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Para ver estas instrucciones de nuevo, ejecute: `{appname} print` | ||
__»__ Para ejecutar su programa en un entorno de pruebas, ejecute: `{appname} run program.js` | ||
__»__ Para verificar su programa, ejecute: `{appname} verify program.js` | ||
__»__ Para más información, ejecute: `{appname} help` | ||
- Para ver estas instrucciones de nuevo, ejecute: `{appname} print` | ||
- Para ejecutar su programa en un entorno de pruebas, ejecute: `{appname} run program.js` | ||
- Para verificar su programa, ejecute: `{appname} verify program.js` | ||
- Para más información, ejecute: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Pour ré-afficher ces instructions, faites : `{appname} print` | ||
__»__ Pour exécuter votre programme dans un environnement de test, faites : `{appname} run program.js` | ||
__»__ Pour vérifier que votre programme résoud l’exercice, faites : `{appname} verify program.js` | ||
__»__ Pour de l’aide sur les commandes, faites : `{appname} help` | ||
- Pour ré-afficher ces instructions, faites : `{appname} print` | ||
- Pour exécuter votre programme dans un environnement de test, faites : `{appname} run program.js` | ||
- Pour vérifier que votre programme résoud l’exercice, faites : `{appname} verify program.js` | ||
- Pour de l’aide sur les commandes, faites : `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Per mostrare nuovamente queste istruzioni, esegui: `{appname} print` | ||
__»__ Per eseguire il tuo programma in un ambiente di prova, esegui: `{appname} run program.js` | ||
__»__ Per validare il tuo programma, esegui: `{appname} verify program.js` | ||
__»__ Per ottenere aiuto esegui: `{appname} help` | ||
- Per mostrare nuovamente queste istruzioni, esegui: `{appname} print` | ||
- Per eseguire il tuo programma in un ambiente di prova, esegui: `{appname} run program.js` | ||
- Per validare il tuo programma, esegui: `{appname} verify program.js` | ||
- Per ottenere aiuto esegui: `{appname} help` |
@@ -1,5 +0,5 @@ | ||
__»__ この説明をもう一度表示する: `{appname} print` | ||
__»__ 作成したアプリをテスト環境で実行する: `{appname} run program.js` | ||
__»__ 作成したアプリが正しいか検証する: `{appname} verify program.js` | ||
__»__ 出力結果が見づらい場合には ``--no-color`` をつけてみてください: `{appname} verify program.js --no-color` | ||
__»__ ヘルプを表示する: `{appname} help` | ||
- この説明をもう一度表示する: `{appname} print` | ||
- 作成したアプリをテスト環境で実行する: `{appname} run program.js` | ||
- 作成したアプリが正しいか検証する: `{appname} verify program.js` | ||
- 出力結果が見づらい場合には ``--no-color`` をつけてみてください: `{appname} verify program.js --no-color` | ||
- ヘルプを表示する: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ 설명을 다시 출력하시려면, `{appname} print`를 실행하세요. | ||
__»__ 테스트 환경에서 프로그램을 실행하시려면, `{appname} run program.js`를 실행하세요. | ||
__»__ 프로그램을 검증하시려면, `{appname} verify program.js`를 실행하세요. | ||
__»__ 도움말을 보시려면 `{appname} help`를 실행하세요. | ||
- 설명을 다시 출력하시려면, `{appname} print`를 실행하세요. | ||
- 테스트 환경에서 프로그램을 실행하시려면, `{appname} run program.js`를 실행하세요. | ||
- 프로그램을 검증하시려면, `{appname} verify program.js`를 실행하세요. | ||
- 도움말을 보시려면 `{appname} help`를 실행하세요. |
@@ -1,4 +0,4 @@ | ||
__»__ For å skrive ut oppgaveteksten igjen: `{appname} print` | ||
__»__ For å kjøre programmet i et test miljø: `{appname} run program.js` | ||
__»__ For å verifisere programmet: `{appname} verify program.js` | ||
__»__ For hjelp: `{appname} help` | ||
- For å skrive ut oppgaveteksten igjen: `{appname} print` | ||
- For å kjøre programmet i et test miljø: `{appname} run program.js` | ||
- For å verifisere programmet: `{appname} verify program.js` | ||
- For hjelp: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Para imprimir essas instruções novamente execute: `{appname} print` | ||
__»__ Para executar seu programa em um ambiente de testes execute: `{appname} run program.js` | ||
__»__ Para que seu programa seja avaliado execute: `{appname} verify program.js` | ||
__»__ Para ajuda, execute: `{appname} help` | ||
- Para imprimir essas instruções novamente execute: `{appname} print` | ||
- Para executar seu programa em um ambiente de testes execute: `{appname} run program.js` | ||
- Para que seu programa seja avaliado execute: `{appname} verify program.js` | ||
- Para ajuda, execute: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Для вывода описания задачи, введите: `{appname} print` | ||
__»__ Для запуска решения в тестовом режиме, введите: `{appname} run program.js` | ||
__»__ Для проверки решения, введите: `{appname} verify program.js` | ||
__»__ Для вызова справки, введите: `{appname} help` | ||
- Для вывода описания задачи, введите: `{appname} print` | ||
- Для запуска решения в тестовом режиме, введите: `{appname} run program.js` | ||
- Для проверки решения, введите: `{appname} verify program.js` | ||
- Для вызова справки, введите: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Щоб вивести опис завдання, введіть: `{appname} print` | ||
__»__ Для запуску розв’язку в тестовому режимі, введіть: `{appname} run program.js` | ||
__»__ Для перевірки розв’язку, введіть: `{appname} verify program.js` | ||
__»__ Щоб викликати довідку, введіть: `{appname} help` | ||
- Щоб вивести опис завдання, введіть: `{appname} print` | ||
- Для запуску розв’язку в тестовому режимі, введіть: `{appname} run program.js` | ||
- Для перевірки розв’язку, введіть: `{appname} verify program.js` | ||
- Щоб викликати довідку, введіть: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ Xem lại hướng dẫn: `{appname} print` | ||
__»__ Chạy chương trình với môi trường giả lập: `{appname} run program.js` | ||
__»__ Xác nhận chương trình: `{appname} verify program.js` | ||
__»__ Xem trợ giúp: `{appname} help` | ||
- Xem lại hướng dẫn: `{appname} print` | ||
- Chạy chương trình với môi trường giả lập: `{appname} run program.js` | ||
- Xác nhận chương trình: `{appname} verify program.js` | ||
- Xem trợ giúp: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ 要再次显示此说明文本,请执行: `{appname} print` | ||
__»__ 要测试执行你的程序,请执行: `{appname} run program.js` | ||
__»__ 要验证你的程序,请执行: `{appname} verify program.js` | ||
__»__ 需要帮助?执行: `{appname} help` | ||
- 要再次显示此说明文本,请执行: `{appname} print` | ||
- 要测试执行你的程序,请执行: `{appname} run program.js` | ||
- 要验证你的程序,请执行: `{appname} verify program.js` | ||
- 需要帮助?执行: `{appname} help` |
@@ -1,4 +0,4 @@ | ||
__»__ To print these instructions again, run: `{appname} print` | ||
__»__ To execute your program in a test environment, run: `{appname} run program.js` | ||
__»__ To verify your program, run: `{appname} verify program.js` | ||
__»__ For help run: `{appname} help` | ||
- To print these instructions again, run: `{appname} print` | ||
- To execute your program in a test environment, run: `{appname} run program.js` | ||
- To verify your program, run: `{appname} verify program.js` | ||
- For help run: `{appname} help` |
@@ -42,7 +42,7 @@ { | ||
"title": "RATÉ", | ||
"message": "Votre solution pour {{{name}}} ne fonctionne pas. Réessayez !\n" | ||
"message": "Votre solution pour {{{currentExercise.name}}} ne fonctionne pas. Réessayez !\n" | ||
}, | ||
"pass": { | ||
"title": "RÉUSSI", | ||
"message": "Votre solution pour {{{name}}} fonctionne !" | ||
"message": "Votre solution pour {{{currentExercise.name}}} fonctionne !" | ||
}, | ||
@@ -49,0 +49,0 @@ "notes": { |
@@ -43,7 +43,7 @@ { | ||
"title": "FALLIMENTO", | ||
"message": "La tua soluzione a {{{name}}} non è corretta. Riprova!\n" | ||
"message": "La tua soluzione a {{{currentExercise.name}}} non è corretta. Riprova!\n" | ||
}, | ||
"pass": { | ||
"title": "SUCCESSO", | ||
"message": "La tua soluzione a {{{name}}} è corretta!" | ||
"message": "La tua soluzione a {{{currentExercise.name}}} è corretta!" | ||
}, | ||
@@ -50,0 +50,0 @@ "notes": { |
@@ -15,12 +15,12 @@ { | ||
"ja": "日本語", | ||
"zh-cn": "中国語 (中文)", | ||
"zh-tw": "国語 (國語)", | ||
"zh-cn": "中国語 (簡体)", | ||
"zh-tw": "中国語 (繁体)", | ||
"es": "スペイン語 (Español)", | ||
"pt-br": "ポルトガル語 (Português)", | ||
"vi": "ベトナム語 (Tiếng Việt)", | ||
"ru": "ロシア (Русский)", | ||
"ru": "ロシア語 (Русский)", | ||
"uk": "ウクライナ語 (Українська)", | ||
"ko": "韓国語 (한국어)", | ||
"pl": "ポーランド語 (Polski)", | ||
"nb-no": "ノルウェー語 (Norsk)", | ||
"it": "イタリア語 (Italiano)" | ||
"nb-no": "ノルウェー語 (Norsk)" | ||
}, | ||
@@ -44,7 +44,7 @@ "error": { | ||
"title": "残念!", | ||
"message": "「{{{name}}}」に対するあなたの回答は不合格でした。再度挑戦してみて下さい!\n" | ||
"message": "「{{{currentExercise.name}}}」に対するあなたの回答は不合格でした。再度挑戦してみて下さい!\n" | ||
}, | ||
"pass": { | ||
"title": "おめでとう!", | ||
"message": "「{{{name}}}」に対するあなたの回答は合格です!" | ||
"message": "「{{{currentExercise.name}}}」に対するあなたの回答は合格です!" | ||
}, | ||
@@ -65,6 +65,6 @@ "notes": { | ||
"remaining": { | ||
"one": "一つのチャンレジが残っています!", | ||
"other": "あと{{count}}個のチャンレンジが残っています。" | ||
"one": "一つのチャレンジが残っています!", | ||
"other": "あと{{count}}個のチャレンジが残っています。" | ||
} | ||
} | ||
} |
@@ -21,2 +21,3 @@ { | ||
"ru": "러시아어(Русский)", | ||
"uk": "우크라이나어 (Українська)", | ||
"ko": "한국어", | ||
@@ -44,7 +45,7 @@ "pl": "폴란드어 (Polski)", | ||
"title": "실패", | ||
"message": "{{{name}}}의 해결책은 통과하지 못했습니다. 다시 한번 해보세요!\n" | ||
"message": "{{{currentExercise.name}}}의 해결책은 통과하지 못했습니다. 다시 한번 해보세요!\n" | ||
}, | ||
"pass": { | ||
"title": "통과", | ||
"message": "{{{name}}}의 해결책은 통과했습니다!" | ||
"message": "{{{currentExercise.name}}}의 해결책은 통과했습니다!" | ||
}, | ||
@@ -51,0 +52,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "Russisk (Русский)", | ||
"uk": "Ukrainsk (Українська)", | ||
"ko": "Koreansk (한국어)", | ||
@@ -44,7 +45,7 @@ "pl": "Polsk (Polski)", | ||
"title": "FEIL", | ||
"message": "Din løsning på {{{name}}} er ikke godkjent. Prøv en gang til!\n" | ||
"message": "Din løsning på {{{currentExercise.name}}} er ikke godkjent. Prøv en gang til!\n" | ||
}, | ||
"pass": { | ||
"title": "RIKTIG", | ||
"message": "Løsningen din på {{{name}}} er riktig!" | ||
"message": "Løsningen din på {{{currentExercise.name}}} er riktig!" | ||
}, | ||
@@ -51,0 +52,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "Rosyjski (Русский)", | ||
"uk": "Ukraiński (Українська)", | ||
"ko": "Koreański (한국어)", | ||
@@ -44,7 +45,7 @@ "pl": "Polski", | ||
"title": "PORAŻKA", | ||
"message": "Twoje rozwiązanie dla {{{name}}} nie zdało egzaminu. Spróbuj ponownie!\n" | ||
"message": "Twoje rozwiązanie dla {{{currentExercise.name}}} nie zdało egzaminu. Spróbuj ponownie!\n" | ||
}, | ||
"pass": { | ||
"title": "SUKCES", | ||
"message": "Twoje rozwiązanie dla {{{name}}} jest prawidłowe!" | ||
"message": "Twoje rozwiązanie dla {{{currentExercise.name}}} jest prawidłowe!" | ||
}, | ||
@@ -51,0 +52,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "Russo (Русский)", | ||
"uk": "Ucraniano (Українська)", | ||
"ko": "Coreano (한국어)", | ||
@@ -43,7 +44,7 @@ "pl": "Polonês (Polski)", | ||
"title": "FALHA", | ||
"message": "Sua solução para {{{name}}} não foi aprovada. Tente novamente!\n" | ||
"message": "Sua solução para {{{currentExercise.name}}} não foi aprovada. Tente novamente!\n" | ||
}, | ||
"pass": { | ||
"title": "SUCESSO", | ||
"message": "Sua solução para {{{name}}} foi aprovada!" | ||
"message": "Sua solução para {{{currentExercise.name}}} foi aprovada!" | ||
}, | ||
@@ -50,0 +51,0 @@ "notes": { |
@@ -42,7 +42,7 @@ { | ||
"title": "НЕВЕРНО", | ||
"message": "Ваше решение {{{name}}} неверное. Попробуйте снова!\n" | ||
"message": "Ваше решение {{{currentExercise.name}}} неверное. Попробуйте снова!\n" | ||
}, | ||
"pass": { | ||
"title": "ВЕРНО", | ||
"message": "Ваше решение {{{name}}} верное!" | ||
"message": "Ваше решение {{{currentExercise.name}}} верное!" | ||
}, | ||
@@ -49,0 +49,0 @@ "notes": { |
@@ -44,7 +44,7 @@ { | ||
"title": "НЕ ВІРНО", | ||
"message": "Ваш роз’язок {{{name}}} не вірний. Спробуйте знову!\n" | ||
"message": "Ваш роз’язок {{{currentExercise.name}}} не вірний. Спробуйте знову!\n" | ||
}, | ||
"pass": { | ||
"title": "ВІРНО", | ||
"message": "Ваш розв’язок {{{name}}} вірний!" | ||
"message": "Ваш розв’язок {{{currentExercise.name}}} вірний!" | ||
}, | ||
@@ -51,0 +51,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "Tiếng Nga (Русский)", | ||
"uk": "Tiếng Ukraina (Українська)", | ||
"ko": "Hàn Quốc (한국어)", | ||
@@ -43,7 +44,7 @@ "pl": "Đánh Bóng (Polski)", | ||
"title": "SAI", | ||
"message": "Bài {{{name}}} của bạn chưa chuẩn. Thử lại xem sao!\n" | ||
"message": "Bài {{{currentExercise.name}}} của bạn chưa chuẩn. Thử lại xem sao!\n" | ||
}, | ||
"pass": { | ||
"title": "ĐÚNG", | ||
"message": "Bài {{{name}}} của bạn rất ngon!" | ||
"message": "Bài {{{currentExercise.name}}} của bạn rất ngon!" | ||
}, | ||
@@ -50,0 +51,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "俄 (Русский)", | ||
"uk": "乌克兰语 (Українська)", | ||
"ko": "韩国语 (한국어)", | ||
@@ -43,7 +44,7 @@ "pl": "波兰语 (Polski)", | ||
"title": "失败", | ||
"message": "你对 {{{name}}} 所作的答案未通过验证, 请再试一次!\n" | ||
"message": "你对 {{{currentExercise.name}}} 所作的答案未通过验证, 请再试一次!\n" | ||
}, | ||
"pass": { | ||
"title": "通过", | ||
"message": "你对 {{{name}}} 所作的答案已通过验证!" | ||
"message": "你对 {{{currentExercise.name}}} 所作的答案已通过验证!" | ||
}, | ||
@@ -50,0 +51,0 @@ "notes": { |
@@ -21,2 +21,3 @@ { | ||
"ru": "俄 (Русский)", | ||
"uk": "烏克蘭語 (Українська)", | ||
"ko": "韓國語 (한국어)", | ||
@@ -43,7 +44,7 @@ "pl": "波蘭語 (Polski)", | ||
"title": "FAIL", | ||
"message": "Your solution to {{{name}}} didn't pass. Try again!\n" | ||
"message": "Your solution to {{{currentExercise.name}}} didn't pass. Try again!\n" | ||
}, | ||
"pass": { | ||
"title": "PASS", | ||
"message": "Your solution to {{{name}}} passed!" | ||
"message": "Your solution to {{{currentExercise.name}}} passed!" | ||
}, | ||
@@ -50,0 +51,0 @@ "notes": { |
421
index.js
@@ -1,17 +0,416 @@ | ||
var Adventure = require("./adventure") | ||
, inherits = require('util').inherits | ||
const fs = require('fs') | ||
, path = require('path') | ||
, commandico = require('commandico') | ||
, inherits = require('util').inherits | ||
, EventEmitter = require('events').EventEmitter | ||
module.exports = LegacyWorkshopper | ||
/* jshint -W079 */ | ||
const util = require('./util') | ||
, PrintStream = require('./lib/print') | ||
, storage = require('./lib/storage') | ||
/* jshint +W079 */ | ||
function LegacyWorkshopper(options) { | ||
if (!(this instanceof LegacyWorkshopper)) | ||
return new LegacyWorkshopper(options) | ||
function WA (options) { | ||
if (!(this instanceof WA)) | ||
return new WA(options) | ||
if (options.showHeader === undefined) | ||
options.showHeader = true | ||
if (!options) | ||
options = {} | ||
Adventure.apply(this, [options]) | ||
this.execute(process.argv.slice(2)) | ||
if (options.appDir) { | ||
options.appDir = util.getDir(options.appDir, '.') | ||
if (!options.name) { | ||
try { | ||
options.name = require(path.join(options.appDir, 'package.json')).name | ||
} catch(e) {} | ||
} | ||
} | ||
if (!options.name) | ||
throw new Error('The workshopper needs a name to store the progress.'); | ||
if (!options.languages) | ||
options.languages = ['en'] | ||
if (!options.defaultLang) | ||
options.defaultLang = options.languages[0] | ||
if (!options.appRepo && options.appDir) | ||
try { | ||
options.appRepo = require(path.join(options.appDir, 'package.json')).repository.url | ||
} catch (e) {} | ||
if (options.appDir) | ||
options.exerciseDir = util.getDir(options.exerciseDir || 'exercises', options.appDir) | ||
if (!options.menu) | ||
options.menu = { | ||
width: 65 | ||
, x: 3 | ||
, y: 2 | ||
} | ||
if (!options.menuFactory) | ||
options.menuFactory = require('simple-terminal-menu/factory')(options.menu, {}) | ||
EventEmitter.call(this) | ||
this.options = options | ||
var globalStorage = storage(storage.userDir, '.config', 'workshopper') | ||
this.appStorage = storage(storage.userDir, '.config', options.name) | ||
this.exercises = [] | ||
this._meta = {} | ||
try { | ||
this.i18n = require('./i18n').init(options, globalStorage, this.appStorage) | ||
} catch(e) { | ||
console.log(e.message) | ||
process.exit(1) | ||
} | ||
this.__ = this.i18n.__ | ||
this.__n = this.i18n.__n | ||
this.cli = commandico(this, 'menu') | ||
.loadCommands(path.resolve(__dirname, './lib/commands')) | ||
.loadModifiers(path.resolve(__dirname, './lib/modifiers')) | ||
if (options.commands) | ||
this.cli.addCommands(options.commands) | ||
if (options.modifiers) | ||
this.cli.addModifiers(options.modifiers) | ||
} | ||
inherits(LegacyWorkshopper, Adventure) | ||
inherits(WA, EventEmitter) | ||
WA.prototype.execute = function (args) { | ||
return this.cli.execute(args) | ||
} | ||
WA.prototype.add = function (name_or_object, fn_or_object, fn) { | ||
var meta | ||
try { | ||
meta = require('./lib/createExerciseMeta')(this.options.exerciseDir, name_or_object, fn_or_object) | ||
} catch(e) { | ||
console.log(e) | ||
return error(this.__('error.exercise.' + e.id, e)) | ||
} | ||
return this.addExercise(meta) | ||
} | ||
WA.prototype.addAll = function (list) { | ||
return list.map(this.add.bind(this)) | ||
} | ||
WA.prototype.addExercise = function (meta) { | ||
this.exercises.push(meta.name) | ||
this.i18n.updateExercises(this.exercises) | ||
this._meta[meta.id] = meta | ||
meta.number = this.exercises.length | ||
return this | ||
} | ||
WA.prototype.countRemaining = function () { | ||
var completed = this.appStorage.get('completed') | ||
return this.exercises.length - (completed ? completed.length : 0) | ||
} | ||
WA.prototype.markCompleted = function (exerciseName, cb) { | ||
var completed = this.appStorage.get('completed') || [] | ||
if (completed.indexOf(exerciseName) === -1) | ||
completed.push(exerciseName) | ||
this.appStorage.save('completed', completed) | ||
if (this.onComplete.length === 0) { | ||
throw new Error('The workshoppers `.onComplete` method must have at least one callback argument') | ||
} | ||
return this.onComplete(cb) | ||
} | ||
WA.prototype.onComplete = function (cb) { | ||
setImmediate(cb) | ||
} | ||
// overall exercise fail | ||
WA.prototype.exerciseFail = function (mode, exercise, stream, cb) { | ||
stream.append(exercise.fail, exercise.failType) | ||
|| stream.append('\n' + | ||
'{bold}{red}# {solution.fail.title}{/red}{/bold}\n' + | ||
'{solution.fail.message}\n', 'txt') | ||
stream.append('\n') | ||
cb() | ||
} | ||
WA.prototype.getExerciseFiles = function (exercise, callback) { | ||
if (!exercise.hideSolutions && typeof exercise.getSolutionFiles === 'function') | ||
return exercise.getSolutionFiles(callback) | ||
setImmediate(callback.bind(null, null, exercise.solutionFiles || [])) | ||
} | ||
// overall exercise pass | ||
WA.prototype.exercisePass = function (mode, exercise, stream, cb) { | ||
this.getExerciseFiles(exercise, function (err, files) { | ||
if (err) | ||
return cb(this.__('solution.notes.load_error', {err: err.message || err})) | ||
this.markCompleted(exercise.meta.name, function (err, completeMessage) { | ||
if (err) | ||
return cb(err) | ||
stream.append(exercise.pass, exercise.passType) | ||
|| stream.append('\n' + | ||
'{bold}{green}# {solution.pass.title}{/green}{/bold}\n' + | ||
'{bold}{solution.pass.message}{/bold}\n', 'txt') | ||
if (!exercise.hideSolutions) { | ||
if ((files && files.length > 0) || exercise.solution) | ||
stream.append('{solution.notes.compare}') | ||
files && files.length > 0 | ||
? stream.append({ files: files }) | ||
: stream.append(exercise.solution, exercise.solutionType) | ||
} | ||
var remaining = this.countRemaining() | ||
remaining > 0 | ||
? stream.append( | ||
'{progress.remaining#' + remaining + '}\n' + | ||
'{ui.return}\n') | ||
: stream.append('{progress.finished}\n') | ||
stream.append(completeMessage) | ||
stream.append('\n') | ||
cb() | ||
}.bind(this)) | ||
}.bind(this)) | ||
} | ||
WA.prototype.verify = function (args, specifier, cb) { | ||
return this.process('verify', args, specifier, cb) | ||
} | ||
WA.prototype.run = function (args, specifier, cb) { | ||
return this.process('run', args, specifier, cb) | ||
} | ||
WA.prototype.process = function (mode, args, specifier, cb) { | ||
var exercise = this.loadExercise(specifier) | ||
if (!exercise) | ||
return cb(this.__('error.exercise.missing', {name: specifier})) | ||
if (exercise.requireSubmission !== false && args.length == 0) | ||
return cb(this.__('ui.usage', {appName: this.options.name, mode: mode})) | ||
var method = exercise[mode] | ||
if (!method) | ||
return cb('This problem doesn\'t have a `.' + mode + '` function.') | ||
if (typeof method !== 'function') | ||
return cb('The `.' + mode + '` object of the exercise `' + exercise.meta.id + ' is a `' + typeof method + '`. It should be a `function` instead.') | ||
var stream = this.executeExercise(exercise, mode, method, args, cb) | ||
if (typeof exercise.on === 'function') { | ||
exercise.on('pass', function (message) { | ||
stream.append(require('chalk').green.bold('\u2713 ') + message) | ||
}) | ||
exercise.on('fail', function (message) { | ||
stream.append(require('chalk').red.bold('\u2717 ') + message) | ||
}) | ||
exercise.on('pass', this.emit.bind(this, 'pass', exercise, mode)) | ||
exercise.on('fail', this.emit.bind(this, 'fail', exercise, mode)) | ||
} | ||
return stream | ||
} | ||
WA.prototype.executeExercise = function (exercise, mode, method, args, cb) { | ||
var result | ||
, finished = false | ||
, stream = this.createMarkdownStream(exercise) | ||
, cleanup = function cleanup(err, pass, message, messageType) { | ||
if (finished) | ||
return // TODO: make this easier to debug ... bad case of zalgo | ||
finished = true | ||
if (message) { | ||
exercise[pass ? 'pass': 'fail'] = { | ||
text: message, | ||
type: messageType | ||
} | ||
} | ||
if (err) | ||
return cb(this.__('error.exercise.unexpected_error', {mode: mode, err: (err.message || err) })) | ||
var end = function (err) { | ||
if (typeof exercise.end !== 'function') | ||
return cb(null, pass, stream) | ||
exercise.end(mode, pass, function (cleanupErr) { | ||
if (cleanupErr) | ||
return cb(this.__('error.cleanup', {err: cleanupErr.message || cleanupErr})) | ||
cb(err, pass, stream) | ||
}.bind(this)) | ||
}.bind(this) | ||
if (mode === 'run') | ||
return setImmediate(end) | ||
if (pass) | ||
this.exercisePass(mode, exercise, stream, end) | ||
else | ||
this.exerciseFail(mode, exercise, stream, end) | ||
}.bind(this) | ||
try { | ||
method.length <= 1 | ||
? cleanup(null, true, method.call(exercise, args)) | ||
: method.call(exercise, args, function callback (err, pass) { | ||
/* | ||
callback(true) -> err=null, pass=true | ||
callback(false) -> err=null, pass=false | ||
callback() -> err=null, pass=null | ||
callback(null) -> err=null, pass=null | ||
callback(true, true) -> err=true, pass="x" | ||
callback(false, "x") -> err=false, pass="x" | ||
callback(null, "x") -> err=null, pass="x" | ||
callback("x", false) -> err="x", pass=false | ||
callback("x", true) -> err="x", pass=true ... pass should be ignored | ||
*/ | ||
if (pass === undefined && (err === true || err === false || err === undefined || err === null)) { | ||
pass = err | ||
err = null | ||
} | ||
pass = (mode === 'run' || (pass && !exercise.fail)) | ||
err | ||
? cleanup(err) | ||
: cleanup(null, pass) | ||
}.bind(this)) | ||
} catch (e) { | ||
return cleanup(e) | ||
} | ||
return stream | ||
} | ||
WA.prototype.loadExercise = function (specifier) { | ||
var id | ||
if (specifier) | ||
id = this.specifierToId(specifier) | ||
else | ||
id = util.idFromName(this.appStorage.get('current')) | ||
if (!id) | ||
return null | ||
var meta = this._meta[id] | ||
if (!meta) | ||
return null | ||
exercise = meta.fn() | ||
exercise.meta = meta | ||
if (typeof exercise.init === 'function') | ||
exercise.init(this, meta.id, meta.name, meta.dir, meta.number) | ||
return exercise | ||
} | ||
WA.prototype.specifierToId = function (specifier) { | ||
if (!isNaN(specifier)) { | ||
var number = parseInt(specifier, 10) | ||
if (number >= 0 && number < this.exercises.length) { | ||
specifier = this.exercises[number] | ||
} else { | ||
specifier = '' | ||
} | ||
} | ||
return util.idFromName(specifier) | ||
} | ||
WA.prototype.selectExercise = function (specifier) { | ||
var id = this.specifierToId(specifier) | ||
if (!id) | ||
return error(this.__('error.exercise.missing', {name: specifier})) | ||
var meta = this._meta[id] | ||
if (!meta) | ||
return error(this.__('error.exercise.missing', {name: specifier})) | ||
this.appStorage.save('current', meta.name) | ||
return meta.id | ||
} | ||
WA.prototype.createMarkdownStream = function (exercise) { | ||
var context = exercise ? this.createExerciseContext(exercise) : this.i18n; | ||
return new PrintStream(context, this.i18n.lang()) | ||
} | ||
WA.prototype.createExerciseContext = function (exercise) { | ||
return this.i18n.extend({ | ||
"currentExercise.name" : exercise.meta.name | ||
, "progress.count" : exercise.meta.number | ||
, "progress.total" : this.exercises.length | ||
, "progress.state_resolved" : this.__('progress.state', {count: exercise.meta.number, amount: this.exercises.length}) | ||
}) | ||
} | ||
WA.prototype.getExerciseText = function printExercise (specifier, contentOnly, callback) { | ||
var exercise = this.loadExercise(specifier) | ||
, prepare | ||
if (arguments.length === 2) { | ||
callback = contentOnly | ||
contentOnly = false | ||
} | ||
if (!exercise) | ||
callback(this.__('error.exercise.none_active')) | ||
prepare = (typeof exercise.prepare === 'function') ? exercise.prepare.bind(exercise) : setImmediate; | ||
prepare(function(err) { | ||
if (err) | ||
return callback(this.__('error.exercise.preparing', {err: err.message || err})) | ||
var getExerciseText = (typeof exercise.getExerciseText === 'function') ? exercise.getExerciseText.bind(exercise) : setImmediate; | ||
getExerciseText(function (err, exerciseTextType, exerciseText) { | ||
if (err) | ||
return callback(this.__('error.exercise.loading', {err: err.message || err})) | ||
var stream = this.createMarkdownStream(exercise) | ||
, found = false | ||
if (!contentOnly) | ||
stream.append(exercise.header) | ||
|| stream.append(this.options.header) | ||
if (stream.append(exercise.problem, exercise.problemType)) | ||
found = true | ||
if (stream.append(exerciseText, exerciseTextType)) | ||
found = true | ||
if (!found) | ||
return callback('The exercise "' + exercise.meta.name + '" is missing a problem definition!') | ||
if (!contentOnly) | ||
stream.append(exercise.footer) | ||
|| stream.append(this.options.footer) | ||
&& stream.append('\n') | ||
callback(null, stream) | ||
}.bind(this)) | ||
}.bind(this)) | ||
} | ||
module.exports = WA |
{ | ||
"name": "workshopper-adventure", | ||
"version": "3.5.2", | ||
"version": "4.0.0", | ||
"description": "A terminal workshop runner framework (adventure compatible)", | ||
@@ -13,14 +13,16 @@ "main": "./index.js", | ||
"dependencies": { | ||
"@leichtgewicht/msee": "^0.2.3", | ||
"chalk": "~0.4.0", | ||
"colors-tmpl": "~0.1.0", | ||
"combined-stream": "0.0.7", | ||
"i18n-core": "^1.3.2", | ||
"map-async": "~0.1.1", | ||
"minimist": "^1.1.1", | ||
"commandico": "^1.1.1", | ||
"concat-stream": "^1.5.0", | ||
"i18n-core": "^2.1.1", | ||
"mkdirp": "~0.3.5", | ||
"msee": "~0.1.1", | ||
"simple-terminal-menu": "^1.0.0", | ||
"terminal-menu": "^2.1.1", | ||
"through": "^2.3.7", | ||
"visualwidth": "~0.0.1", | ||
"rimraf": "^2.4.3", | ||
"simple-terminal-menu": "^1.1.1", | ||
"split": "^1.0.0", | ||
"string-to-stream": "^1.0.1", | ||
"through2": "^2.0.0", | ||
"visualwidth": "^0.1.0", | ||
"xtend": "~3.0.0" | ||
@@ -27,0 +29,0 @@ }, |
23
util.js
const path = require('path') | ||
, fs = require('fs') | ||
, mkdirp = require('mkdirp') | ||
, vw = require('visualwidth') | ||
function repeat (ch, sz) { | ||
return new Array(sz + 1).join(ch) | ||
} | ||
function idFromName (id) { | ||
return id.toLowerCase() | ||
if (id === null || id === undefined) | ||
id = "" | ||
return id.toString().toLowerCase() | ||
.replace(/^\s+|\s+$/g, '') | ||
.replace(/\s/g, '_') | ||
@@ -26,9 +22,2 @@ .replace(/[^\w]/gi, '') | ||
function userDir () { | ||
var folders = [process.env.HOME || process.env.USERPROFILE].concat(Array.prototype.slice.apply(arguments)) | ||
var dir = path.join.apply(path, folders) | ||
mkdirp.sync(dir) | ||
return dir | ||
} | ||
function getFsObject(type, file, base) { | ||
@@ -54,6 +43,4 @@ var stat | ||
, dirFromName: dirFromName | ||
, repeat: repeat | ||
, getDir: getFsObject.bind(null, 'dir') | ||
, getFile: getFsObject.bind(null, 'file') | ||
, userDir: userDir | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
114871
95
2101
15
1
17
+ Added@leichtgewicht/msee@^0.2.3
+ Addedcommandico@^1.1.1
+ Addedconcat-stream@^1.5.0
+ Addedrimraf@^2.4.3
+ Addedsplit@^1.0.0
+ Addedstring-to-stream@^1.0.1
+ Addedthrough2@^2.0.0
+ Added@hapi/address@2.1.4(transitive)
+ Added@hapi/bourne@1.3.2(transitive)
+ Added@hapi/hoek@8.5.1(transitive)
+ Added@hapi/joi@15.1.1(transitive)
+ Added@hapi/topo@3.1.6(transitive)
+ Added@leichtgewicht/msee@0.2.5(transitive)
+ Added@leichtgewicht/visualwidth@0.1.0(transitive)
+ Addedamdefine@1.0.1(transitive)
+ Addedansi-regex@1.1.1(transitive)
+ Addedargparse@1.0.10(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbossy@1.0.3(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addedcircular-json@0.3.3(transitive)
+ Addedcli-width@1.1.1(transitive)
+ Addedcommandico@1.1.2(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedconcat-stream@1.6.2(transitive)
+ Addedd@1.0.2(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addeddeep-is@0.1.4(transitive)
+ Addeddiff@2.2.3(transitive)
+ Addeddoctrine@0.6.40.7.2(transitive)
+ Addedes5-ext@0.10.64(transitive)
+ Addedes6-iterator@2.0.3(transitive)
+ Addedes6-map@0.1.5(transitive)
+ Addedes6-set@0.1.6(transitive)
+ Addedes6-symbol@3.1.4(transitive)
+ Addedes6-weak-map@2.0.3(transitive)
+ Addedescope@3.6.0(transitive)
+ Addedeslint@0.24.11.5.1(transitive)
+ Addedeslint-config-hapi@2.0.1(transitive)
+ Addedeslint-plugin-hapi@1.2.2(transitive)
+ Addedesniff@2.0.1(transitive)
+ Addedespree@2.2.5(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedesrecurse@4.3.0(transitive)
+ Addedestraverse@4.3.05.3.0(transitive)
+ Addedestraverse-fb@1.3.2(transitive)
+ Addedesutils@1.1.6(transitive)
+ Addedevent-emitter@0.3.5(transitive)
+ Addedexit@0.1.2(transitive)
+ Addedexplicit@0.1.3(transitive)
+ Addedext@1.7.0(transitive)
+ Addedfast-levenshtein@1.0.7(transitive)
+ Addedfigures@1.7.0(transitive)
+ Addedfile-entry-cache@1.3.1(transitive)
+ Addedflat-cache@1.3.4(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedgenerate-function@2.3.1(transitive)
+ Addedgenerate-object-property@1.2.0(transitive)
+ Addedget-stdin@3.0.2(transitive)
+ Addedglob@4.5.35.0.157.2.3(transitive)
+ Addedglobals@8.18.0(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhandlebars@4.7.8(transitive)
+ Addedhapi-capitalize-modules@1.1.6(transitive)
+ Addedhapi-scope-start@1.1.4(transitive)
+ Addedhoek@2.16.3(transitive)
+ Addedi18n-core@2.1.1(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinquirer@0.8.50.9.0(transitive)
+ Addedis-my-ip-valid@1.0.1(transitive)
+ Addedis-my-json-valid@2.20.6(transitive)
+ Addedis-property@1.0.2(transitive)
+ Addedis-resolvable@1.1.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisemail@1.2.0(transitive)
+ Addeditems@1.1.1(transitive)
+ Addedjoi@6.8.1(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedjslint@0.9.8(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedjsonpointer@5.0.1(transitive)
+ Addedlab@5.18.1(transitive)
+ Addedlevn@0.2.5(transitive)
+ Addedlodash@3.10.1(transitive)
+ Addedlodash._arraycopy@3.0.0(transitive)
+ Addedlodash._arrayeach@3.0.0(transitive)
+ Addedlodash._arraymap@3.0.0(transitive)
+ Addedlodash._baseassign@3.2.0(transitive)
+ Addedlodash._baseclone@3.3.0(transitive)
+ Addedlodash._basecopy@3.0.1(transitive)
+ Addedlodash._basedifference@3.0.3(transitive)
+ Addedlodash._baseflatten@3.1.4(transitive)
+ Addedlodash._basefor@3.0.3(transitive)
+ Addedlodash._baseindexof@3.1.0(transitive)
+ Addedlodash._bindcallback@3.0.1(transitive)
+ Addedlodash._cacheindexof@3.0.2(transitive)
+ Addedlodash._createassigner@3.1.1(transitive)
+ Addedlodash._createcache@3.1.2(transitive)
+ Addedlodash._getnative@3.9.1(transitive)
+ Addedlodash._isiterateecall@3.0.9(transitive)
+ Addedlodash._pickbyarray@3.0.2(transitive)
+ Addedlodash._pickbycallback@3.0.0(transitive)
+ Addedlodash.clonedeep@3.0.2(transitive)
+ Addedlodash.isarguments@3.1.0(transitive)
+ Addedlodash.isarray@3.0.4(transitive)
+ Addedlodash.isplainobject@3.2.0(transitive)
+ Addedlodash.istypedarray@3.0.6(transitive)
+ Addedlodash.keys@3.1.2(transitive)
+ Addedlodash.keysin@3.0.8(transitive)
+ Addedlodash.merge@3.3.2(transitive)
+ Addedlodash.omit@3.1.0(transitive)
+ Addedlodash.restparam@3.6.1(transitive)
+ Addedlodash.toplainobject@3.0.0(transitive)
+ Addedminimatch@2.0.103.1.2(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedmoment@2.30.1(transitive)
+ Addedms@2.0.0(transitive)
+ Addedmute-stream@0.0.4(transitive)
+ Addedneo-async@2.6.2(transitive)
+ Addednext-tick@1.1.0(transitive)
+ Addedno-shadow-relaxed@1.0.1(transitive)
+ Addednopt@3.0.6(transitive)
+ Addedobject-assign@2.1.14.1.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedoptionator@0.5.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-is-inside@1.0.2(transitive)
+ Addedprelude-ls@1.1.2(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedreadline2@0.1.1(transitive)
+ Addedrimraf@2.6.32.7.1(transitive)
+ Addedrun-async@0.1.0(transitive)
+ Addedrx@2.5.3(transitive)
+ Addedrx-lite@2.5.2(transitive)
+ Addedsafe-buffer@5.1.2(transitive)
+ Addedshelljs@0.3.0(transitive)
+ Addedsource-map@0.1.320.6.1(transitive)
+ Addedsource-map-support@0.3.3(transitive)
+ Addedsplit@1.0.1(transitive)
+ Addedsprintf-js@1.0.3(transitive)
+ Addedstring-to-stream@1.1.1(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedstrip-ansi@2.0.1(transitive)
+ Addedstrip-json-comments@1.0.4(transitive)
+ Addedtext-table@0.2.0(transitive)
+ Addedthrough2@2.0.5(transitive)
+ Addedto-double-quotes@1.0.2(transitive)
+ Addedto-single-quotes@1.0.4(transitive)
+ Addedtopo@1.1.0(transitive)
+ Addedtype@2.7.3(transitive)
+ Addedtype-check@0.3.2(transitive)
+ Addedtypedarray@0.0.6(transitive)
+ Addeduglify-js@3.19.3(transitive)
+ Addeduser-home@1.1.1(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedvisualwidth@0.1.0(transitive)
+ Addedwordwrap@0.0.31.0.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedwrite@0.2.1(transitive)
+ Addedxml-escape@1.0.0(transitive)
- Removedmap-async@~0.1.1
- Removedminimist@^1.1.1
- Removedmsee@~0.1.1
- Removedterminal-menu@^2.1.1
- Removedthrough@^2.3.7
- Removedcharm@0.1.2(transitive)
- Removedi18n-core@1.3.3(transitive)
- Removedmap-async@0.1.1(transitive)
- Removedmsee@0.1.2(transitive)
- Removedterminal-menu@2.1.1(transitive)
- Removedvisualwidth@0.0.1(transitive)
Updatedi18n-core@^2.1.1
Updatedsimple-terminal-menu@^1.1.1
Updatedvisualwidth@^0.1.0