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

workshopper-adventure

Package Overview
Dependencies
Maintainers
3
Versions
62
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

workshopper-adventure - npm Package Compare versions

Comparing version 3.5.2 to 4.0.0

default/footer.js

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
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": {

@@ -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 @@ },

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
}
SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc