├── .gitignore ├── .travis.yml ├── lib ├── langFilter.js ├── modifiers │ ├── version.js │ ├── Readme.md │ └── language.js ├── commands │ ├── repo.js │ ├── list.js │ ├── reset.js │ ├── version.js │ ├── next.js │ ├── language-list.js │ ├── select.js │ ├── completed.js │ ├── current.js │ ├── help.js │ ├── print.js │ ├── run.js │ ├── verify.js │ ├── credits.js │ ├── update.js │ ├── language.js │ └── menu.js ├── msee.js ├── mseePipe.js ├── createExerciseMeta.js └── print.js ├── default ├── fail.js ├── pass.js ├── footer.js ├── header.js └── help.js ├── i18n ├── footer │ ├── zh-cn.md │ ├── zh-tw.md │ ├── ko.md │ ├── vi.md │ ├── sr.md │ ├── nb-no.md │ ├── de.md │ ├── en.md │ ├── ru.md │ ├── ja.md │ ├── uk.md │ ├── fi.md │ ├── it.md │ ├── es.md │ ├── pt-br.md │ ├── fr.md │ └── tr.md ├── usage │ ├── zh-tw.md │ ├── zh-cn.md │ ├── ja.md │ ├── ko.md │ ├── ru.md │ ├── sr.md │ ├── uk.md │ ├── vi.md │ ├── nb-no.md │ ├── en.md │ ├── pl.md │ ├── es.md │ ├── tr.md │ ├── fi.md │ ├── de.md │ ├── it.md │ ├── pt-br.md │ └── fr.md ├── zh-tw.json ├── zh-cn.json ├── ja.json ├── ko.json ├── sr.json ├── ru.json ├── vi.json ├── it.json ├── pt-br.json ├── nb-no.json ├── en.json ├── uk.json ├── es.json ├── fr.json ├── de.json ├── pl.json ├── fi.json └── tr.json ├── test ├── util.test.js ├── adventure.test.js └── core.test.js ├── util.js ├── workshopper.js ├── .jshintrc ├── package.json ├── LICENSE ├── CHANGELOG.md ├── README.md ├── i18n.js ├── adventure.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/*~ 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | sudo: false 5 | -------------------------------------------------------------------------------- /lib/langFilter.js: -------------------------------------------------------------------------------- 1 | module.exports = function langFilter (shop) { 2 | return shop.options.languages.length > 1 3 | } 4 | -------------------------------------------------------------------------------- /lib/modifiers/version.js: -------------------------------------------------------------------------------- 1 | exports.aliases = ['v'] 2 | exports.handler = function (shop, lang) { 3 | shop.execute(['version']) 4 | } 5 | -------------------------------------------------------------------------------- /default/fail.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | text: '\n{bold}{red}# {solution.fail.title}{/red}{/bold}\n' + 3 | '{solution.fail.message}\n' 4 | } 5 | -------------------------------------------------------------------------------- /default/pass.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | text: '\n{bold}{green}# {solution.pass.title}{/green}{/bold}\n' + 3 | '{bold}{solution.pass.message}{/bold}\n' 4 | } 5 | -------------------------------------------------------------------------------- /lib/commands/repo.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function printVersion (shop) { 3 | console.log(shop.options.appRepo) 4 | process.exit() 5 | } 6 | -------------------------------------------------------------------------------- /default/footer.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = [ 4 | '---', 5 | { 6 | file: path.join(__dirname, '../i18n/footer/{lang}.md') 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /default/header.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | text: '# {title}' + 3 | '\n## __{currentExercise.name}__ (_{progress.state_resolved}_)' + 4 | '\n\n', 5 | type: 'md' 6 | } 7 | -------------------------------------------------------------------------------- /i18n/footer/zh-cn.md: -------------------------------------------------------------------------------- 1 | - 要再次显示此说明文本,请执行: `{appname} print` 2 | - 要测试执行你的程序,请执行: `{appname} run program.js` 3 | - 要验证你的程序,请执行: `{appname} verify program.js` 4 | - 需要帮助?执行: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/zh-tw.md: -------------------------------------------------------------------------------- 1 | - 若要再次顯示這些說明,請執行: `{appname} print` 2 | - 要在測試環境執行你的程式,請執行: `{appname} run program.js` 3 | - 要驗證你的答案,請執行: `{appname} verify program.js` 4 | - 若需要幫助,請執行: `{appname} help` 5 | -------------------------------------------------------------------------------- /lib/commands/list.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function (shop) { 3 | shop.exercises.forEach(function (name) { 4 | console.log(shop.__('exercise.' + name)) 5 | }) 6 | } 7 | -------------------------------------------------------------------------------- /lib/commands/reset.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function (shop) { 3 | shop.appStorage.reset() 4 | return console.log(shop.__('progress.reset', { title: shop.__('title') })) 5 | } 6 | -------------------------------------------------------------------------------- /lib/commands/version.js: -------------------------------------------------------------------------------- 1 | exports.aliases = ['v'] 2 | exports.menu = false 3 | exports.handler = function printVersion (shop) { 4 | console.log(shop.getVersionString()) 5 | process.exit() 6 | } 7 | -------------------------------------------------------------------------------- /lib/modifiers/Readme.md: -------------------------------------------------------------------------------- 1 | # Modifiers 2 | Modifiers are code hooks that are executed when a modifier is called in the command line. 3 | 4 | Example: `--lang` will call the lang modifier, not the `lang` command. -------------------------------------------------------------------------------- /default/help.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | file: [ 5 | path.join(__dirname, '../i18n/usage/{lang}.md'), 6 | path.join(__dirname, '../i18n/usage/en.md') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /i18n/footer/ko.md: -------------------------------------------------------------------------------- 1 | - 설명을 다시 출력하시려면, `{appname} print`를 실행하세요. 2 | - 테스트 환경에서 프로그램을 실행하시려면, `{appname} run program.js`를 실행하세요. 3 | - 프로그램을 검증하시려면, `{appname} verify program.js`를 실행하세요. 4 | - 도움말을 보시려면 `{appname} help`를 실행하세요. 5 | -------------------------------------------------------------------------------- /i18n/footer/vi.md: -------------------------------------------------------------------------------- 1 | - Xem lại hướng dẫn: `{appname} print` 2 | - Chạy chương trình với môi trường giả lập: `{appname} run program.js` 3 | - Xác nhận chương trình: `{appname} verify program.js` 4 | - Xem trợ giúp: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/sr.md: -------------------------------------------------------------------------------- 1 | - За испис описа задатка унесите: `{appname} print` 2 | - Да покренете решење: `{appname} run program.js` 3 | - Да тестирате решење: `{appname} verify program.js` 4 | - За испис помоћних упутстава: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/nb-no.md: -------------------------------------------------------------------------------- 1 | - For å skrive ut oppgaveteksten igjen: `{appname} print` 2 | - For å kjøre programmet i et test miljø: `{appname} run program.js` 3 | - For å verifisere programmet: `{appname} verify program.js` 4 | - For hjelp: `{appname} help` 5 | -------------------------------------------------------------------------------- /lib/commands/next.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function next (shop) { 3 | var next = shop.getNext() 4 | if (next instanceof Error) { 5 | return console.log(shop.__(next.message) + '\n') 6 | } 7 | shop.execute(['print', next]) 8 | } 9 | -------------------------------------------------------------------------------- /i18n/footer/de.md: -------------------------------------------------------------------------------- 1 | - Um diese Anweisungen erneut auszugeben: `{appname} print` 2 | - Um dein Programm in der Testumgebung auszuführen: `{appname} run program.js` 3 | - Um dein Programm zu verifizieren: `{appname} verify program.js` 4 | - Für Hilfe: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/en.md: -------------------------------------------------------------------------------- 1 | - To print these instructions again, run: `{appname} print` 2 | - To execute your program in a test environment, run: `{appname} run program.js` 3 | - To verify your program, run: `{appname} verify program.js` 4 | - For help run: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/ru.md: -------------------------------------------------------------------------------- 1 | - Для вывода описания задачи введите: `{appname} print` 2 | - Для запуска решения в тестовом режиме введите: `{appname} run program.js` 3 | - Для проверки решения введите: `{appname} verify program.js` 4 | - Для вызова справки введите: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/ja.md: -------------------------------------------------------------------------------- 1 | - この説明をもう一度表示する: `{appname} print` 2 | - 作成したアプリをテスト環境で実行する: `{appname} run program.js` 3 | - 作成したアプリが正しいか検証する: `{appname} verify program.js` 4 | - 出力結果が見づらい場合には ``--no-color`` をつけてみてください: `{appname} verify program.js --no-color` 5 | - ヘルプを表示する: `{appname} help` 6 | -------------------------------------------------------------------------------- /i18n/footer/uk.md: -------------------------------------------------------------------------------- 1 | - Щоб вивести опис завдання, введіть: `{appname} print` 2 | - Для запуску розв’язку в тестовому режимі, введіть: `{appname} run program.js` 3 | - Для перевірки розв’язку, введіть: `{appname} verify program.js` 4 | - Щоб викликати довідку, введіть: `{appname} help` 5 | -------------------------------------------------------------------------------- /lib/modifiers/language.js: -------------------------------------------------------------------------------- 1 | exports.aliases = ['l', 'lang'] 2 | exports.filter = require('../langFilter') 3 | exports.handler = function (shop, lang) { 4 | try { 5 | shop.i18n.change(lang) 6 | } catch (e) { 7 | console.log(e.message) 8 | process.exit(1) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /i18n/footer/fi.md: -------------------------------------------------------------------------------- 1 | - Tulostaaksesi nämä ohjeet uudelleen, kirjoita: `{appname} print` 2 | - Ajaaksesi ohjelmasi testiympäristössä, kirjoita: `{appname} run program.js` 3 | - Antaaksesi ohjelmasi tarkastettavaksi, kirjoita: `{appname} verify program.js` 4 | - Saadaksesi apua: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/it.md: -------------------------------------------------------------------------------- 1 | - Per mostrare nuovamente queste istruzioni, esegui: `{appname} print` 2 | - Per eseguire il tuo programma in un ambiente di prova, esegui: `{appname} run program.js` 3 | - Per validare il tuo programma, esegui: `{appname} verify program.js` 4 | - Per ottenere aiuto esegui: `{appname} help` 5 | -------------------------------------------------------------------------------- /lib/commands/language-list.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.filter = require('../langFilter') 3 | exports.aliases = ['lang-list', 'langlist'] 4 | exports.handler = function printLanguageMenu (shop) { 5 | shop.options.languages.forEach(function (language) { 6 | console.log(language) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /lib/commands/select.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function (shop, args) { 3 | var specifier = args.join(' ') 4 | try { 5 | shop.selectExercise(specifier) 6 | } catch (e) { 7 | console.log(e.message) 8 | process.exit(1) 9 | } 10 | shop.execute(['print']) 11 | } 12 | -------------------------------------------------------------------------------- /i18n/footer/es.md: -------------------------------------------------------------------------------- 1 | - Para ver estas instrucciones de nuevo, ejecute: `{appname} print` 2 | - Para ejecutar su programa en un entorno de pruebas, ejecute: `{appname} run program.js` 3 | - Para verificar su programa, ejecute: `{appname} verify program.js` 4 | - Para más información, ejecute: `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/pt-br.md: -------------------------------------------------------------------------------- 1 | - Para imprimir essas instruções novamente execute: `{appname} print` 2 | - Para executar seu programa em um ambiente de testes execute: `{appname} run program.js` 3 | - Para que seu programa seja avaliado execute: `{appname} verify program.js` 4 | - Para ajuda, execute: `{appname} help` 5 | -------------------------------------------------------------------------------- /lib/commands/completed.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function current (shop) { 3 | var completed = shop.appStorage.get('completed') 4 | if (completed) { 5 | completed.forEach(function (completed) { 6 | console.log(shop.__('exercise.' + completed)) 7 | }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/commands/current.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function current (shop) { 3 | var current = shop.appStorage.get('current') 4 | if (current) { 5 | console.log(shop.__('exercise.' + current)) 6 | } else { 7 | console.log(shop.__('error.exercise.none_active')) 8 | process.exit(1) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /i18n/footer/fr.md: -------------------------------------------------------------------------------- 1 | - Pour ré-afficher ces instructions, faites : `{appname} print` 2 | - Pour exécuter votre programme dans un environnement de test, faites : `{appname} run program.js` 3 | - Pour vérifier que votre programme résoud l’exercice, faites : `{appname} verify program.js` 4 | - Pour de l’aide sur les commandes, faites : `{appname} help` 5 | -------------------------------------------------------------------------------- /i18n/footer/tr.md: -------------------------------------------------------------------------------- 1 | - Bu talimatları tekrar yazdırmak için `{appname} print` komutunu çalıştırınız 2 | - Programınızı test ortamında çalıştırmak için `{appname} run program.js` komutunu çalıştırınız 3 | - Programınızın doğru şekilde çalışıp çalışmadığını görmek için `{appname} verify program.js` komutunu çalıştırınız 4 | - Yardım için `{appname} help` komutunu çalıştırınız. 5 | -------------------------------------------------------------------------------- /lib/commands/help.js: -------------------------------------------------------------------------------- 1 | exports.order = 2 2 | exports.handler = function printHelp (shop) { 3 | var stream = shop.createMarkdownStream() 4 | stream.append(Object.hasOwnProperty.call(shop.options, 'help') ? shop.options.help : require('../../default/help')) || 5 | stream.append('No help available.') 6 | 7 | stream 8 | .appendChain('\n') 9 | .pipe(require('../mseePipe')()) 10 | .pipe(process.stdout) 11 | } 12 | -------------------------------------------------------------------------------- /test/util.test.js: -------------------------------------------------------------------------------- 1 | const util = require('../util') 2 | const test = require('tape') 3 | 4 | // idFromName 5 | const name = 'TEST CHALLENGE' 6 | test('remove white spaces with underscore', (t) => { 7 | t.plan(1) 8 | t.equal(util.idFromName(name), 'test_challenge') 9 | }) 10 | 11 | test('allow customize character to replace white space', (t) => { 12 | t.plan(1) 13 | t.equal(util.idFromName(name, '-'), 'test-challenge') 14 | }) 15 | -------------------------------------------------------------------------------- /lib/commands/print.js: -------------------------------------------------------------------------------- 1 | exports.menu = false 2 | exports.handler = function print (shop, args) { 3 | var specifier = args.join(' ') 4 | shop.getExerciseText(specifier, function (err, stream) { 5 | if (err) { 6 | console.log(err) 7 | process.exit(1) 8 | } 9 | stream 10 | .pipe(require('../mseePipe')()) 11 | .pipe(process.stdout) 12 | .on('end', function () { 13 | process.exit() 14 | }) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /lib/msee.js: -------------------------------------------------------------------------------- 1 | const msee = require('msee') 2 | const mseeOptions = { 3 | paragraphStart: '', 4 | paragraphEnd: '\n\n', 5 | hrChar: '\u2500', 6 | listItemPad: { 7 | right: ' ', 8 | first: ' » ', 9 | regular: ' ' 10 | }, 11 | defaultCodePad: ' ', 12 | paragraphPad: { 13 | left: ' ', 14 | right: ' ' 15 | }, 16 | maxWidth: 78 17 | } 18 | 19 | module.exports = function (content) { 20 | return msee.parse(content, mseeOptions) 21 | } 22 | -------------------------------------------------------------------------------- /lib/mseePipe.js: -------------------------------------------------------------------------------- 1 | var msee = require('./msee') 2 | const through = require('through2') 3 | 4 | module.exports = function () { 5 | var buffer = [] 6 | return through(function (contents, encoding, done) { 7 | buffer.push(contents.toString()) 8 | done() 9 | }, function (done) { 10 | var contents = buffer.join('\n') 11 | 12 | var str = msee(contents).replace(/^/gm, ' ').replace(/$/gm, ' ') 13 | str = str.substr(0, str.length - 3) 14 | this.push(str) 15 | done() 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /lib/commands/run.js: -------------------------------------------------------------------------------- 1 | var after = require('after') 2 | exports.menu = false 3 | exports.handler = function (shop, args) { 4 | var error 5 | var passed 6 | var exit = after(2, function () { 7 | if (error) { 8 | console.log(error.stack || error) 9 | } 10 | process.exit(passed && !error ? 0 : 1) 11 | }) 12 | var stream = shop.run(args, null, function (err, pass) { 13 | error = err 14 | passed = pass 15 | exit() 16 | }) 17 | stream = stream.pipe(require('../mseePipe')()) 18 | 19 | stream.on('end', exit) 20 | stream.pipe(process.stdout, { end: false }) 21 | } 22 | -------------------------------------------------------------------------------- /lib/commands/verify.js: -------------------------------------------------------------------------------- 1 | var after = require('after') 2 | exports.menu = false 3 | exports.handler = exports.handler = function (shop, args) { 4 | var passed 5 | var error 6 | var exit = after(2, function () { 7 | if (error) { 8 | console.log(error.stack || error) 9 | } 10 | process.exit(passed && !error ? 0 : 1) 11 | }) 12 | var stream = shop.verify(args, null, function (err, pass) { 13 | error = err 14 | passed = pass 15 | exit() 16 | }) 17 | var msee = require('../msee') 18 | stream 19 | .on('data', function (data) { 20 | process.stdout.write(msee('\n' + data)) 21 | }) 22 | .on('end', function () { 23 | exit() 24 | }) 25 | 26 | stream.resume() 27 | } 28 | -------------------------------------------------------------------------------- /test/adventure.test.js: -------------------------------------------------------------------------------- 1 | const LegacyAdventure = require('../adventure') 2 | const test = require('tape') 3 | 4 | test('options a string', (t) => { 5 | const workshopper = LegacyAdventure('test workshopper') 6 | 7 | t.plan(2) 8 | t.ok(workshopper) 9 | t.equal(workshopper.title, 'TEST WORKSHOPPER') 10 | }) 11 | 12 | test('options an object', (t) => { 13 | const options = { 14 | name: 'TEST SAMPLE', 15 | languages: ['en', 'es'] 16 | } 17 | const workshopper = LegacyAdventure(options) 18 | 19 | t.plan(6) 20 | t.ok(workshopper) 21 | t.equal(workshopper.title, options.name) 22 | t.equal(workshopper.name, options.name) 23 | t.equal(workshopper.appName, options.name) 24 | t.equal(workshopper.appname, options.name) 25 | t.deepEqual(workshopper.languages, options.languages) 26 | }) 27 | -------------------------------------------------------------------------------- /i18n/usage/zh-tw.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## 在 {appname} 的練習中遇到困難了嗎? 4 | 5 | 一群專家級的小精靈助手正等着協助您掌握 Node.js 的基礎,請前往 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | 然後 __新建一個 Issue__,讓我們知道你在哪裡遇到了問題。沒有甚麼問題是 _愚蠢的_。 10 | 11 | 關於 Node.js 如果你正在尋找幫助,在 Freenode IRC 上的 #Node.js 頻道是一個很不錯的地方,可以找到很多人來協助你。同樣地,這裡還有一個 Node.js 的 Google 群組: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## 發現了{appname} 的 bug 或者想要貢獻程式碼? 16 | 17 | {appname} 的官方 git 倉庫是: 18 | 19 | {appRepo} 20 | 21 | 請隨意指出一個 bug (或者更進一步) 提出一個 Pull Request。 22 | 23 | ## Usage 24 | 25 | __`{appname}`__ ..................... 顯示一個互動式的選單來選擇練習題。 26 | 27 | __`{appname} list`__ ................ 顯示出一個所有練習題的清單。 28 | 29 | __`{appname} select NAME`__ ......... 選擇一個練習題。 30 | 31 | __`{appname} current`__ ............. 顯示出目前選擇的練習題。 32 | 33 | __`{appname} print`__ ............... 印出目前練習題的說明指南。 34 | 35 | __`{appname} next`__ ................ 印出下一道未完成練習題的說明指南。 36 | 37 | __`{appname} reset`__ ............... 重設所有已完成練習題的進度。 38 | 39 | __`{appname} run program.js`__ ...... 執行你的程式來檢查輸出。 40 | 41 | __`{appname} verify program.js`__ ... 驗證你的程式來和正確解答比較。 42 | 43 | __`{appname} -l `__ ....... 更換系統至指定語言。 44 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | function idFromName (id, spaceChar = '_') { 5 | if (id === null || id === undefined) { 6 | id = '' 7 | } 8 | const regex = new RegExp(`[^\\w${spaceChar}]`, 'gi') 9 | 10 | return id.toString().toLowerCase() 11 | .replace(/^\s+|\s+$/g, '') 12 | .replace(/\s/g, spaceChar) 13 | .replace(regex, '') 14 | } 15 | 16 | function dirFromName (exerciseDir, name) { 17 | if (typeof exerciseDir !== 'string') { 18 | return null 19 | } 20 | return path.join(exerciseDir, idFromName(name)) 21 | } 22 | 23 | function getFsObject (type, file, base) { 24 | var stat 25 | 26 | if (typeof base !== 'string' || typeof file !== 'string') { 27 | return null 28 | } 29 | 30 | file = path.resolve(base, file) 31 | try { 32 | stat = fs.statSync(file) 33 | } catch (e) {} 34 | 35 | if (!stat || !(type === 'file' ? stat.isFile() : stat.isDirectory())) { 36 | return null 37 | } 38 | 39 | return file 40 | } 41 | 42 | module.exports = { 43 | idFromName: idFromName, 44 | dirFromName: dirFromName, 45 | getDir: getFsObject.bind(null, 'dir'), 46 | getFile: getFsObject.bind(null, 'file') 47 | } 48 | -------------------------------------------------------------------------------- /i18n/usage/zh-cn.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## 在 {appname} 的练习中遇到困难了吗? 4 | 5 | 一群专家级别的小精灵助手正急切等待着帮你掌握 Node.js 的基础,前往 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | 然后 __新建一个 Issue__,让我们知道你在哪里遇到了难题。 没什么有什么问题是 _愚蠢的_。 10 | 11 | 如果你正在寻找一些关于 Node.js 的帮助,在 Freenode 上的 #Node.js IRC 频道是一个很不错的地方,可以找到很多人来帮助你。同样,这里还有一个关于 Node.js 的 Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## 发现了一个 {appname} 的 bug 或者想要贡献代码? 16 | 17 | {appname} 的官方 git 仓库是: 18 | 19 | {appRepo} 20 | 21 | 请随意提交一个 bug (或者更好的) 提交一个 Pull Request。 22 | 23 | ## Usage 24 | 25 | __`{appname}`__ ..................... 显示一个互动菜单来选择习题。 26 | 27 | __`{appname} list`__ ................ 将所有习题逐行列举显示出来。 28 | 29 | __`{appname} select NAME`__ ......... 选择所指定的习题。 30 | 31 | __`{appname} current`__ ............. 显示当前选择的习题。 32 | 33 | __`{appname} print`__ ............... 打印出当前习题的说明文本。 34 | 35 | __`{appname} next`__ ................ 打印出当前所选习题的下一个未完成习题的说明。 36 | 37 | __`{appname} reset`__ ............... 重置已完成的习题。 38 | 39 | __`{appname} run program.js`__ ...... 运行你的程序来比较输入输出。 40 | 41 | __`{appname} verify program.js`__ ... 验证你的程序是否符合期望的结果。 42 | 43 | __`{appname} -l `__ ....... 将本程序语言切换至所选的语言。 44 | -------------------------------------------------------------------------------- /i18n/usage/ja.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## {appname} のワークショップに対して質問がありますか? 4 | 5 | エキスパートチームはあなたの質問を待っています。あなたもNode.jsのマスターになれるようにここに質問({bold}New Issue{/bold})をしてみてください: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | どんな質問でもどうぞ! 10 | 11 | 一般的なNode.jsのヘルプが必要であればNode.jsの日本のGoogleグループがあります: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs_jp 14 | 15 | ## {appname}のバグが見つかった場合または貢献したい場合: 16 | 17 | {appname}のリポジトリはここにあります: 18 | 19 | {appRepo} 20 | 21 | バグの報告やPullリクエストは日本語でいつでもどうぞ! 22 | 23 | ## 使い方 24 | 25 | __`{appname}`__ ..................... ワークショップを選択する対話的メニューを表示します。 26 | 27 | __`{appname} list`__ ................ 登録されているすべてのワークショップの名称一覧を表示します。 28 | 29 | __`{appname} select NAME`__ ......... ワークショップを選択します。 30 | 31 | __`{appname} current`__ ............. 現在選択されているワークショップの名称を表示します。 32 | 33 | __`{appname} print`__ ............... 現在選択されているワークショップのガイドを表示します。 34 | 35 | __`{appname} next`__ ................ 現在選択されているワークショップの次の未修了のワークショップのガイドを表示します。 36 | 37 | __`{appname} reset`__ ............... ワークショップの進捗状況をリセットします。 38 | 39 | __`{appname} run program.js`__ ...... あなたが作成したプログラムを実行します。 40 | 41 | __`{appname} verify program.js`__ ... あなたが作成したプログラムが正しいかを検証します。 42 | 43 | __`{appname} -l `__ ....... システムで使用する言語を指定されたものに変更します。 44 | -------------------------------------------------------------------------------- /workshopper.js: -------------------------------------------------------------------------------- 1 | var Adventure = require('./adventure') 2 | var util = require('./util') 3 | var inherits = require('util').inherits 4 | 5 | module.exports = LegacyWorkshopper 6 | 7 | function LegacyWorkshopper (options) { 8 | if (!(this instanceof LegacyWorkshopper)) { 9 | return new LegacyWorkshopper(options) 10 | } 11 | 12 | if (!options.header) { 13 | if (options.showHeader === undefined || options.showHeader) { 14 | options.header = require('./default/header') 15 | } 16 | } 17 | if (options.hideRemaining === undefined) { 18 | options.hideRemaining = false 19 | } 20 | 21 | if (options.requireSubmission === undefined) { 22 | options.requireSubmission = true 23 | } 24 | 25 | if (options.pass === undefined) { 26 | options.pass = require('./default/pass') 27 | } 28 | 29 | if (options.fail === undefined) { 30 | options.fail = require('./default/fail') 31 | } 32 | 33 | if (options.execute === undefined) { 34 | options.execute = 'immediatly' 35 | } 36 | 37 | Adventure.apply(this, [options]) 38 | 39 | var menuJson = util.getFile(options.menuJson || 'menu.json', this.options.exerciseDir) 40 | if (menuJson) { 41 | this.addAll(require(menuJson)) 42 | } 43 | } 44 | 45 | inherits(LegacyWorkshopper, Adventure) 46 | -------------------------------------------------------------------------------- /i18n/usage/ko.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## {appname} 연습문제에 문제가 있나요 4 | 5 | 전문적인 도우미 요정 팀이 열심히 당신이 Node.js의 기본에 정통하도록 돕기 위해 기다리는 중입니다. 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | 에서 __새로운 이슈__를 추가해 어디에 문제가 있는지 저희에게 알려주세요. _멍청한_ 질문은 없습니다! 10 | 11 | Node.js의 일반적인 도움이 필요하면 Freenode IRC의 #Node.js 채널이 도움을 받을 일반적인 장소입니다. Node.js 구글 그룹도 있습니다. 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## {appname}에 버그를 찾았거나 기여하고 싶으신가요? 16 | 17 | {appname}의 공식 저장소는 여기입니다. 18 | 19 | {appRepo} 20 | 21 | 자유롭게 버그를 제보하거나 (되도록이면) 풀 리퀘스트를 보내세요. 22 | 23 | ## 사용법 24 | 25 | __`{appname}`__ ..................... 워크숍을 선택하는 대화형 메뉴를 표시합니다. 26 | 27 | __`{appname} list`__ ................ 모든 워크숍의 목록을 표시합니다. 28 | 29 | __`{appname} select NAME`__ ......... 워크숍을 선택합니다. 30 | 31 | __`{appname} current`__ ............. 지금 선택한 워크숍을 보여줍니다. 32 | 33 | __`{appname} print`__ ............... 지금 선택한 워크숍의 설명을 출력합니다. 34 | 35 | __`{appname} next`__ ................ 지금 선택한 워크숍 다음의 완료되지 않은 워크숍의 설명을 출력합니다. 36 | 37 | __`{appname} reset`__ ............... 완료된 워크숍의 진행상황을 초기화합니다. 38 | 39 | __`{appname} run program.js`__ ...... 선택한 입력으로 프로그램을 실행합니다. 40 | 41 | __`{appname} verify program.js`__ ... 프로그램이 예측된 출력을 하는지 검증합니다. 42 | 43 | __`{appname} -l `__ ....... 지정된 언어로 시스템을 변경합니다. 44 | -------------------------------------------------------------------------------- /lib/commands/credits.js: -------------------------------------------------------------------------------- 1 | exports.filter = function (shop) { 2 | return shop.options.pkg && Array.isArray(shop.options.pkg.contributors) && shop.options.hideCredits !== true 3 | } 4 | exports.handler = function credits (shop) { 5 | var table = '# {{title}}\n' + 6 | '## {{credits.title}}\n' + 7 | '\n' + 8 | '| {{credits.name}} | {{credits.user}} |\n' + 9 | '|------------------|------------------|\n' 10 | 11 | table += shop.options.pkg.contributors.reduce(function createRow (result, line) { 12 | if (typeof line === 'string') { 13 | var data = /^([^(<]+)\s*(<([^>]*)>)?\s*(\((https?:\/\/[^)]+)\))?/.exec(line) 14 | line = { 15 | name: data[1], 16 | email: data[3], 17 | url: data[5] 18 | } 19 | } 20 | if (line) { 21 | result.push('| ') 22 | result.push(line.name) 23 | result.push(' | ') 24 | if (line.url) { 25 | var github = /^https?:\/\/(www\.)?github\.(com|io)\/([^)/]+)/.exec(line.url) 26 | if (github) { 27 | result.push('@' + github[3]) 28 | } 29 | } 30 | result.push(' |\n') 31 | } 32 | return result 33 | }, []).join('') 34 | 35 | shop.createMarkdownStream() 36 | .appendChain(table, 'md') 37 | .appendChain('\n') 38 | .pipe(require('../mseePipe')()) 39 | .pipe(process.stdout) 40 | } 41 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ ] 3 | , "bitwise": false 4 | , "camelcase": false 5 | , "curly": false 6 | , "eqeqeq": false 7 | , "forin": false 8 | , "immed": false 9 | , "latedef": false 10 | , "noarg": true 11 | , "noempty": true 12 | , "nonew": true 13 | , "plusplus": false 14 | , "quotmark": true 15 | , "regexp": false 16 | , "undef": true 17 | , "unused": true 18 | , "strict": false 19 | , "trailing": true 20 | , "maxlen": 120 21 | , "asi": true 22 | , "boss": true 23 | , "debug": true 24 | , "eqnull": true 25 | , "esnext": true 26 | , "evil": true 27 | , "expr": true 28 | , "funcscope": false 29 | , "globalstrict": false 30 | , "iterator": false 31 | , "lastsemic": true 32 | , "laxbreak": true 33 | , "laxcomma": true 34 | , "loopfunc": true 35 | , "multistr": false 36 | , "onecase": false 37 | , "proto": false 38 | , "regexdash": false 39 | , "scripturl": true 40 | , "smarttabs": false 41 | , "shadow": false 42 | , "sub": true 43 | , "supernew": false 44 | , "validthis": true 45 | , "browser": true 46 | , "couch": false 47 | , "devel": false 48 | , "dojo": false 49 | , "mootools": false 50 | , "node": true 51 | , "nonstandard": true 52 | , "prototypejs": false 53 | , "rhino": false 54 | , "worker": true 55 | , "wsh": false 56 | , "nomen": false 57 | , "onevar": true 58 | , "passfail": false 59 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workshopper-adventure", 3 | "version": "7.0.0", 4 | "description": "A terminal workshop runner framework (adventure compatible)", 5 | "main": "./index.js", 6 | "author": "Martin Heidegger (https://github.com/martinheidegger)", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/workshopper/workshopper-adventure.git" 10 | }, 11 | "license": "MIT", 12 | "dependencies": { 13 | "after": "^0.8.2", 14 | "chalk": "^3.0.0", 15 | "colors-tmpl": "~1.0.0", 16 | "combined-stream-wait-for-it": "^1.1.0", 17 | "commandico": "^2.0.4", 18 | "i18n-core": "^3.0.0", 19 | "latest-version": "^5.1.0", 20 | "msee": "^0.3.5", 21 | "simple-terminal-menu": "^2.0.0", 22 | "split": "^1.0.0", 23 | "string-to-stream": "^3.0.1", 24 | "strip-ansi": "^6.0.0", 25 | "through2": "^3.0.1", 26 | "workshopper-adventure-storage": "^3.0.0" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/workshopper/workshopper-adventure/issues" 30 | }, 31 | "homepage": "https://github.com/workshopper/workshopper-adventure", 32 | "directories": { 33 | "example": "examples" 34 | }, 35 | "devDependencies": { 36 | "sinon": "^9.0.1", 37 | "standard": "^14.3.1", 38 | "standard-version": "^8.0.1", 39 | "tape": "^4.13.0" 40 | }, 41 | "scripts": { 42 | "test": "standard && tape test/**/*", 43 | "release": "./node_modules/.bin/standard-version" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /i18n/usage/ru.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Возникли проблемы с {appname}? 4 | 5 | Команда экспертов готова помочь Вам в изучении основ Node.js, для этого просто заведите задачу в nodeschool: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | И дайте нам знать с какими сложностями Вы столкнулись. Не стесняйтесь, _глупых_ вопросов не бывает. 10 | 11 | Если вам нужна помощь в решении каких-то общих вопросов касаемых Node.js, то вы можете воспользоваться #Node.js каналом на Freenode IRC или написать в группу Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Нашли ошибку в {appname} или есть идеи по развитию? 16 | 17 | Официальный репозиторий {appname}: 18 | 19 | {appRepo} 20 | 21 | Заводите задачи или отсылайте свои изменения. 22 | 23 | 24 | ## Справка 25 | 26 | __`{appname}`__ ..................... Вызов меню для выбора задачи. 27 | 28 | __`{appname} list`__ ................ Показать список доступных задач. 29 | 30 | __`{appname} select НАЗВАНИЕ`__ ..... Выбор задачи. 31 | 32 | __`{appname} current`__ ............. Показать название текущей задачи. 33 | 34 | __`{appname} print`__ ............... Показать описание текущей задачи. 35 | 36 | __`{appname} next`__ ................ Показать описание следующей задачи. 37 | 38 | __`{appname} reset`__ ............... Сбросить результаты выполненых задач. 39 | 40 | __`{appname} run program.js`__ ...... Запустить переданное решение. 41 | 42 | __`{appname} verify program.js`__ ... Проверить переданное решение. 43 | 44 | __`{appname} -l <язык>`__ ........... Изменить язык. 45 | -------------------------------------------------------------------------------- /i18n/usage/sr.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Имате проблема са задатком {appname}? 4 | 5 | Тим високообучених пијаних лемура чека да вам помогне са свим проблемима везаним за Node.js. Идите на: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | Додајте __New Issue__ и реците нам који проблем имате. Нема _глупих_ питања! 10 | 11 | Ако вам треба неко бољи од наших лемура, идите на #Node.js канал на Freenode IRC-у. Такође, постоји Node.js Google група: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Нашли сте грешку у {appname} или желите да допринесете? 16 | 17 | Званична ризница за {appname} је: 18 | 19 | {appRepo} 20 | 21 | Слободно шаљите извештаје о грешкама или још боље, захтев за измену. 22 | 23 | ## Коришћење 24 | 25 | __`{appname}`__ ..................... Приказује изборник са радионицама. 26 | 27 | __`{appname} list`__ ................ Приказује листу радионица у посебним редовима. 28 | 29 | __`{appname} select NAME`__ ......... Изаберите радионицу. 30 | 31 | __`{appname} current`__ ............. Приказује тренутно изабрану радионицу. 32 | 33 | __`{appname} print`__ ............... Исписује упутства за тренутну радионицу. 34 | 35 | __`{appname} next`__ ................ Исписује упутства за следећу незавршену радионицу. 36 | 37 | __`{appname} reset`__ ............... Ресетује напредак кроз радионице. 38 | 39 | __`{appname} run program.js`__ ...... Покреће ваше решење. 40 | 41 | __`{appname} verify program.js`__ ... Проверава ваше решење наспрам очекиваног резултата. 42 | 43 | __`{appname} -l `__ ....... Промените језик. 44 | -------------------------------------------------------------------------------- /i18n/usage/uk.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## З’явились проблеми з {appname}? 4 | 5 | Команда експертів готова допомогти Вам у вивченні основ Node.js, просто перейдіть на: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | та додайте __Нову Задачу (New Issues)__ та дайте нам знати з якими проблемами ви зіштовхнулись. Не існує _дурноватих_ запитань! 10 | 11 | Якщо ви потребуєте допомоги в вирішені яких-небудь загальних запитань, Канал #Node.js на Freenode IRC зазвичай допомогає у пошуку допомоги. Також завітайте в групу Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Знайшли помилку в {appname}, або просто бажаєте співпрацювати? 16 | 17 | Офіційний репозиторй {appname}: 18 | 19 | {appRepo} 20 | 21 | Заводіть свої задачі та надсилайте вслані зміни. 22 | 23 | ## Використання 24 | 25 | __`{appname}`__ ..................... Показати меню для вибору завдання. 26 | 27 | __`{appname} list`__ ................ Показати список всіх доступних завдань. 28 | 29 | __`{appname} select NAME`__ ......... Обрати завдання. 30 | 31 | __`{appname} current`__ ............. Показати назву поточного завдання. 32 | 33 | __`{appname} print`__ ............... Показати опис поточної задачі. 34 | 35 | __`{appname} next`__ ................ Показати опис наступної задачі. 36 | 37 | __`{appname} reset`__ ............... Скинути поточний прогрес в проходженні воркшопу. 38 | 39 | __`{appname} run program.js`__ ...... Запусити переданий розв’язок. 40 | 41 | __`{appname} verify program.js`__ ... Перевірити передане завдання. 42 | 43 | __`{appname} -l `__ ....... Змінити мову. 44 | -------------------------------------------------------------------------------- /test/core.test.js: -------------------------------------------------------------------------------- 1 | const WA = require('../index') 2 | const test = require('tape') 3 | const sinon = require('sinon') 4 | const commandico = require('commandico') 5 | 6 | const prepareOptions = (options = {}) => { 7 | return { 8 | name: 'sample workshopper', 9 | ...options 10 | } 11 | } 12 | 13 | test('without options', (t) => { 14 | t.plan(1) 15 | t.throws(WA, Error, 'The workshopper needs a name to store the progress.') 16 | }) 17 | 18 | test('with options name', (t) => { 19 | const options = prepareOptions() 20 | const workshopper = WA(options) 21 | 22 | t.plan(1) 23 | t.ok(workshopper) 24 | }) 25 | 26 | test('cli', (t) => { 27 | const options = prepareOptions() 28 | const workshopper = WA(options) 29 | 30 | t.plan(1) 31 | t.ok(workshopper.cli instanceof commandico) 32 | }) 33 | 34 | test('#execute', (t) => { 35 | const options = prepareOptions() 36 | const workshopper = WA(options) 37 | sinon.stub(workshopper.cli, 'execute') 38 | const cli = workshopper.cli 39 | 40 | t.plan(1) 41 | workshopper.execute() 42 | t.ok(cli.execute.called) 43 | cli.execute.restore() 44 | }) 45 | 46 | test('#addExercise', (t) => { 47 | const exercise = { name: 'new exercise', id: 122 } 48 | const workshopper = WA(prepareOptions()) 49 | workshopper.addExercise(exercise) 50 | t.plan(1) 51 | t.equal(workshopper.exercises.length, 1) 52 | }) 53 | 54 | test('#getVersionString', (t) => { 55 | const options = prepareOptions({ version: '0.1.0' }) 56 | const workshopper = WA(options) 57 | 58 | t.plan(1) 59 | t.equal(workshopper.getVersionString(), `${options.name}@${options.version}`) 60 | }) 61 | -------------------------------------------------------------------------------- /i18n/usage/vi.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Có thắc mắc với một bài tập {appname} nào đó? 4 | 5 | Một đội ngũ chuyên gia luôn luôn lắng nghe và muốn giúp đỡ bạn để bạn có thể làm chủ được Node.js ở đây: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | và bạn có thể tạo một {bold}New Issue{/bold} và cho chúng tôi biết vấn đề bạn gặp phải. Bạn hoàn toàn có thể sự dụng tiếng Việt vì lực lượng người Việt rất hùng hậu ở đây. 10 | 11 | Ngoài ra, bạn có thể được giúp đỡ thong qua kênh #Node.js trên Freenode IRC. Hoặc có thể tham gia nhóm hỏi đáp trên Google Group tại: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Bạn tìm ra lỗi trong {appname} hoặc muốn đóng góp? 16 | 17 | Repository chính thức của {appname} là: 18 | 19 | {appRepo} 20 | 21 | Hãy báo lại cho chúng tôi lỗi bạn phát hiện ra hoặc tạo một pull request nếu bạn cải tiến được chương trình này. 22 | 23 | ## Usage 24 | 25 | __`{appname}`__ ..................... Hiển thị menu chọn bài tập. 26 | 27 | __`{appname} list`__ ................ Hiển thị danh sách các bài tập. 28 | 29 | __`{appname} select NAME`__ ......... Chọn một bài tập. 30 | 31 | __`{appname} current`__ ............. Hiển thị bài tập hiện tại. 32 | 33 | __`{appname} print`__ ............... Xem hướng dẫn cho bài tập hiện tại. 34 | 35 | __`{appname} next`__ ................ Xem hướng dẫn bài tập chưa hoàn thành tiếp sau bài hiện tại.. 36 | 37 | __`{appname} reset`__ ............... Reset toàn bộ các bài tập đã hoàn thành. 38 | 39 | __`{appname} run program.js`__ ...... Chạy chương trình của bạn với môi trường giả lập. 40 | 41 | __`{appname} verify program.js`__ ... Xác nhận chương trình của bạn. 42 | 43 | __`{appname} -l `__ ....... Thay đổi ngôn ngữ sử dụng. 44 | -------------------------------------------------------------------------------- /i18n/usage/nb-no.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Har du problemer med {appname} oppgaven? 4 | 5 | Et helt team med ivrige og hjelpsomme JavaScript-alver venter på å hjelpe deg lære grunnleggende Node.js, bare gå til denne adressen: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | og legg til en __New Issue__ og la oss få vite hva du har problemer med. Det finnes _ingen_ dumme spørsmål! 10 | 11 | Hvis du ser etter generell hjelp med Node.js, så er #Node.js kanalen på Freenode IRC ofte et veldig bra sted å finne noen som kan hjelpe. Det er også en Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Fant en feil i {appname} eller vil du bare bidra med noe? 16 | 17 | Det offisielle stedet {appname} er: 18 | 19 | {appRepo} 20 | 21 | Du må gjerne rapportere en feil eller (aller helst) sende en pull request. 22 | 23 | ## Bruk 24 | 25 | __`{appname}`__ ..................... Vis menyen for å velge oppgaver. 26 | 27 | __`{appname} list`__ ................ Vis en nylinje-separert liste av alle oppgaver. 28 | 29 | __`{appname} select NAME`__ ......... Velg en oppgave. 30 | 31 | __`{appname} current`__ ............. Vise den gjeldende oppgaven. 32 | 33 | __`{appname} print`__ ............... Skriv ut instruksjonene for gjeldende oppgave. 34 | 35 | __`{appname} next`__ ................ Skriv ut instruksjonene for den neste oppgaven du ikke har fullført i denne workshoppen. 36 | 37 | __`{appname} reset`__ ............... Nullstill alle utførte oppgaver. 38 | 39 | __`{appname} run program.js`__ ...... Kjør programmet mot valgt input. 40 | 41 | __`{appname} verify program.js`__ ... Verifiser programmet ditt mot forventet output. 42 | 43 | __`{appname} -l `__ .......... Endre systemet språk til det valgte språk. 44 | -------------------------------------------------------------------------------- /lib/commands/update.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn 2 | 3 | function getVersions (shop, callback) { 4 | var path = require('path') 5 | var pkgFile = path.resolve(shop.options.appDir, 'package.json') 6 | try { 7 | var pkg = require(pkgFile) 8 | } catch (e) { 9 | return callback(e) 10 | } 11 | require('latest-version')(pkg.name).then(function (latestVersion) { 12 | exports.info = { 13 | version: latestVersion 14 | } 15 | callback(null, pkg.name, pkg.version, latestVersion) 16 | }).catch(function (err) { 17 | console.log(err) 18 | callback(err) 19 | }) 20 | } 21 | exports.filter = function (shop) { 22 | return shop.options.appDir 23 | } 24 | exports.menu = true 25 | exports.handler = function (shop) { 26 | getVersions(shop, function (error, name, current, latest) { 27 | var stream = shop.createMarkdownStream() 28 | var __ = shop.i18n.__ 29 | stream.append('# {{title}}\n') 30 | if (error) { 31 | stream.append('Error while trying to evaluate package: ' + error) 32 | } else if (current === latest) { 33 | stream.append(__('update.latest_version', { version: current, name: name })) 34 | } else { 35 | var cmd = 'npm install ' + name + '@latest -g' 36 | stream.append(__('update.now', { current: current, latest: latest, cmd: cmd, name: name })) 37 | } 38 | stream 39 | .pipe(require('../mseePipe')()) 40 | .on('end', function () { 41 | if (current !== latest) { 42 | spawn('npm', ['install', name, '-g'], { 43 | stdio: [process.stdin, process.stdout, process.stderr] 44 | }) 45 | } else if (error) { 46 | process.exit(1) 47 | } 48 | }) 49 | .pipe(process.stdout, { end: false }) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /i18n/usage/en.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Having trouble with a {appname} exercise? 4 | 5 | A team of expert helper elves is eagerly waiting to assist you in mastering the basics of Node.js, simply go to: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | and add a __New Issue__ and let us know what you're having trouble with. There are no _dumb_ questions! 10 | 11 | If you're looking for general help with Node.js, the #Node.js channel on Freenode IRC is usually a great place to find someone willing to help. There is also the Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Found a bug with {appname} or just want to contribute? 16 | 17 | The official repository for {appname} is: 18 | 19 | {appRepo} 20 | 21 | Feel free to file a bug report or (preferably) a pull request. 22 | 23 | ## Usage 24 | 25 | __`{appname}`__ ..................... Show a menu to interactively select a workshop. 26 | 27 | __`{appname} list`__ ................ Show a newline-separated list of all the workshops. 28 | 29 | __`{appname} select NAME`__ ......... Select a workshop. 30 | 31 | __`{appname} current`__ ............. Show the currently selected workshop. 32 | 33 | __`{appname} print`__ ............... Print the instructions for the currently selected workshop. 34 | 35 | __`{appname} next`__ ................ Print the instructions for the next incomplete workshop after the currently selected workshop. 36 | 37 | __`{appname} reset`__ ............... Reset completed workshop progress. 38 | 39 | __`{appname} run program.js`__ ...... Run your program against the selected input. 40 | 41 | __`{appname} verify program.js`__ ... Verify your program against the expected output. 42 | 43 | __`{appname} -l `__ ....... Change the system to the specified language. 44 | -------------------------------------------------------------------------------- /i18n/usage/pl.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Problemy z zadaniem {appname}? 4 | 5 | Zespół doświadczonych elfów-pomocników tylko czeka żeby Ci pomóc w opanowaniu podstaw Node.js! Odwiedź: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | Dodaj __New Issue__ i daj nam znać z czym masz problemy. Nie istnieją _głupie_ pytania! 10 | 11 | Jeżeli poszukujesz ogólnej pomocy dotyczącej Node.js, kanał #Node.js w sieci Freenode IRC to doskonałe miejsce by odnaleźć kogoś skorego do pomocy. Możesz również zajrzeć na grupę Google Node.js: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Udało Ci się znaleźć błąd w {appname} lub chcesz się poudzielać? 16 | 17 | Oficjalne repozytorium {appname} to: 18 | 19 | {appRepo} 20 | 21 | Nie krępuj się i dodaj raport na temat znalezionego błędu lub (lepiej) napraw go i zrób pull request. 22 | 23 | ## Sposób użycia{/yellow}{/bold} 24 | 25 | __`{appname}`__ ..................... Pokaż menu aby interaktywnie wybrać warsztat. 26 | 27 | __`{appname} list`__ ................. Pokaż listę wszystkich warsztatów w osobnych wierszach. 28 | 29 | __`{appname} select NAME`__ ......... Wybierz warsztat. 30 | 31 | __`{appname} current`__ ............. Pokaż aktualnie wybrany warsztat. 32 | 33 | __`{appname} print`__ ............... Wypisz instrukcje dla obecnie wybranego warsztatu. 34 | 35 | __`{appname} next`__ ................ Wypisz instrukcje dla kolejnego nieukończonego warsztatu po obecnie wybranym. 36 | 37 | __`{appname} reset`__ ............... Wyzeruj postępy dla ukończonego warsztatu. 38 | 39 | __`{appname} run program.js`__ ...... Uruchom swój program dla wybranych danych wejściowych. 40 | 41 | __`{appname} verify program.js`__ ... Zweryfikuj swój program wobec oczekiwanych wyników. 42 | 43 | __`{appname} -l `__ ....... Zmień język aplikacji. 44 | -------------------------------------------------------------------------------- /i18n/usage/es.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## ¿Tienes problemas con un ejercicio de {appname}? 4 | 5 | Un equipo de ayudantes elfos expertos está esperando ayudarte ansiosamente en aprender lo básico de Node.js, simplemente ve a: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | agrega un __New Issue__ y haznos saber con qué estás teniendo problemas. 10 | 11 | __¡No hay preguntas _tontas_!__ 12 | 13 | Si estás buscando ayuda general con Node.js, el canal #Node.js en Freenode IRC es usualmente un buen lugar para comenzar. También está el grupo Node.js en Google: 14 | 15 | https://groups.google.com/forum/#!forum/nodejs 16 | 17 | ## ¿Encontraste un bug en {appname} o solamente quieres contribuir? 18 | 19 | El repositorio oficial de {appname} es: 20 | 21 | {appRepo} 22 | 23 | Sientete libre de abrir un issue o (preferentemente) un pull request. 24 | 25 | ## Usage 26 | 27 | __`{appname}`__ ..................... Show a menu to interactively select a workshop. 28 | 29 | __`{appname} list`__ ................ Show a newline-separated list of all the workshops. 30 | 31 | __`{appname} select NAME`__ ......... Select a workshop. 32 | 33 | __`{appname} current`__ ............. Show the currently selected workshop. 34 | 35 | __`{appname} print`__ ............... Print the instructions for the currently selected workshop. 36 | 37 | __`{appname} next`__ ................ Print the instructions for the next incomplete workshop after the currently selected workshop. 38 | 39 | __`{appname} reset`__ ............... Reset completed workshop progress. 40 | 41 | __`{appname} run program.js`__ ...... Run your program against the selected input. 42 | 43 | __`{appname} verify program.js`__ ... Verify your program against the expected output. 44 | 45 | __`{appname} -l `__ ....... Change the system to the specified language. 46 | -------------------------------------------------------------------------------- /i18n/usage/tr.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## {appname} alıştırması ile ilgili sıkıntı mı yaşıyorsun? 4 | 5 | Uzman yardımcı elflerden oluşan bir takım dört gözle, Node.js temelleri konusunda uzmanlaşman için yardımcı olmayı bekliyor. Aşağıda ki adrese git: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | ve __New Issue__ oluştur, nasıl bir problemle karşılaştığından da bashetmeyi unutma. _Aptalca_ soru diye bir şey yoktur! 10 | 11 | Eğer Node.js hakkında genel yardıma ihtiyacın varsa Freenode IRC'de ki #Node.js kanalı yardım edecek birini bulmak için güzel bir yer. Ayrıca Node.js Google Group isimli bir e-posta grubuda var: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## {appname} alıştırmasında hata mı buldun ya da katkıda mı bulunmak istiyorsun? 16 | 17 | {appname} alıştırmasının Resmi kod deposu: 18 | 19 | {appRepo} 20 | 21 | Hata raportu açmakta ya da (tercihen) Pull Request açmakta özgürsün. 22 | 23 | ## Kullanım 24 | 25 | __`{appname}`__ ..................... Etkileşimli atölye seçimi için menüyü göster. 26 | 27 | __`{appname} list`__ ................ Bütün atölyeleri ayrı satırda olacak şekilde listele. 28 | 29 | __`{appname} select NAME`__ ......... Atölye seç. 30 | 31 | __`{appname} current`__ ............. Seçili atölyeyi göster. 32 | 33 | __`{appname} print`__ ............... Seçili atölye için talimatları göster. 34 | 35 | __`{appname} next`__ ................ Seçili atölyeden sonraki tamamlanmamış atölyenin talimatlarını göster. 36 | 37 | __`{appname} reset`__ ............... Tamanlanmış atölyeleri sıfırla. 38 | 39 | __`{appname} run program.js`__ ...... Seçilmiş girdilerle programı çalıştır. 40 | 41 | __`{appname} verify program.js`__ ... Programın beklenin çıktıyı verip vermediğini kontrol et. 42 | 43 | __`{appname} -l `__ ....... Sistemi belirtilen dile değiştirin. 44 | -------------------------------------------------------------------------------- /i18n/usage/fi.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Onko sinulla ongelmia harjoitustehtävän {appname} kanssa? 4 | 5 | Ryhmä asiantuntijavelhojamme auttaa mielellään sinua oppimaan Node.js:n perusteet, voit mennä selaimellasi osoitteeseen: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | lisää uusi asia (__New Issue__) ja kerro, minkä asian kanssa sinulla on ongelmia. Tyhmiä kysymyksiä ei ole! 10 | 11 | Jos haluat yleistä apua Node.js:stä, #Node.js -kanava Freenode IRC:issä on tyypillisesti oikea paikka löytää joku, joka osaa auttaa. Googlessa on myös Node.js-ryhmä: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Löysitkö ohjelmointivirheen sovelluksessa {appname} vai haluatko muuten osallistua kehitystyöhön? 16 | 17 | Sovelluksen {appname} virallinen versionhallinta sijaitsee osoitteessa: 18 | 19 | {appRepo} 20 | 21 | Älä ujostele lähettää virheraporttia tai tarjota korjausehdotusta (pull request). 22 | 23 | ## Käyttö 24 | 25 | __`{appname}`__ ..................... Näytä valikko valitaksesi tehtävän. 26 | 27 | __`{appname} list`__ ................ Näytä rivinvaihdoilla eroteltu lista kaikista tehtävistä. 28 | 29 | __`{appname} select NAME`__ ......... Valitse tehtävä. 30 | 31 | __`{appname} current`__ ............. Näytä tällä hetkellä valittu tehtävä. 32 | 33 | __`{appname} print`__ ............... Tulosta ohjeet tällä hetkellä valitusta tehtävästä. 34 | 35 | __`{appname} next`__ ................ Tulosta ohjeet seuraavasta tekemättömästä tehtävästä tällä hetkellä valitun tehtävän jälkeen. 36 | 37 | __`{appname} reset`__ ............... Nollaa tehtäväkokoelman hyväksytyt tehtävät. 38 | 39 | __`{appname} run program.js`__ ...... Aja ohjelmasi verrataksesi odotettuun tulosteeseen. 40 | 41 | __`{appname} verify program.js`__ ... Tarkasta vastaukseksi. 42 | 43 | __`{appname} -l `__ ....... Vaihda järjestelmän kieli. 44 | -------------------------------------------------------------------------------- /i18n/usage/de.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Having trouble with a {appname} exercise? 4 | 5 | A team of expert helper elves is eagerly waiting to assist you in mastering the basics of Node.js, simply go to: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | and add a __New Issue__ and let us know what you're having trouble with. There are no _dumb_ questions! 10 | 11 | If you're looking for general help with Node.js, the #Node.js channel on Freenode IRC is usually a great place to find someone willing to help. There is also the Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Found a bug with {appname} or just want to contribute? 16 | 17 | The official repository for {appname} is: 18 | 19 | {appRepo} 20 | 21 | Feel free to file a bug report or (preferably) a pull request. 22 | 23 | ## Verwendung 24 | 25 | __`{appname}`__ ..................... Zeige ein Menü, um interaktiv eine Herausforderung auszuwählen. 26 | 27 | __`{appname} list`__ ................ Zeige eine Liste aller Herausforderungen, eine Herausforderung pro Zeile. 28 | 29 | __`{appname} select NAME`__ ......... Wähle eine Herausforderung. 30 | 31 | __`{appname} current`__ ............. Zeige die aktuell gewählte Herausforderung. 32 | 33 | __`{appname} print`__ ............... Gebe die Anweisungen für die aktuell gewählte Herausforderung aus. 34 | 35 | __`{appname} next`__ ................ Gebe die Anweisungen für die nächsten unvollständige Herausforderung nach der aktuell ausgewählten Herausforderung aus. 36 | 37 | __`{appname} reset`__ ............... Setze den Workshop-Fortschritt zurück. 38 | 39 | __`{appname} run program.js`__ ...... Führe dein Programm mit dem gewählten Eingaben aus. 40 | 41 | __`{appname} verify program.js`__ ... Verifiziere dein Programm mit den erwarteten Eingaben. 42 | 43 | __`{appname} -l `__ ....... Ändere das System zur ausgewählten Sprache. 44 | -------------------------------------------------------------------------------- /i18n/usage/it.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Having trouble with a {appname} exercise? 4 | 5 | A team of expert helper elves is eagerly waiting to assist you in mastering the basics of Node.js, simply go to: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | and add a __New Issue__ and let us know what you're having trouble with. There are no _dumb_ questions! 10 | 11 | If you're looking for general help with Node.js, the #Node.js channel on Freenode IRC is usually a great place to find someone willing to help. There is also the Node.js Google Group: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Found a bug with {appname} or just want to contribute? 16 | 17 | The official repository for {appname} is: 18 | 19 | {appRepo} 20 | 21 | Feel free to file a bug report or (preferably) a pull request. 22 | 23 | ## Istruzioni 24 | 25 | __`{appname}`__ ..................... Mostra un menu per scegliere un workshop in maniera interattiva. 26 | 27 | __`{appname} list`__ ................ Mostra una lista di tutti i workshop disponibili, uno per riga. 28 | 29 | __`{appname} select NAME`__ ......... Seleziona un workshop. 30 | 31 | __`{appname} current`__ ............. Mostra il workshop attualmente selezionato. 32 | 33 | __`{appname} print`__ ............... Mostra le istruzioni per il workshop attualmente selezionato. 34 | 35 | __`{appname} next`__ ................ Mostra le istruzioni per il workshop non ancora avviato successivo a quello attualmente selezionato. 36 | 37 | __`{appname} reset`__ ............... Ripristina il progresso dei workshop completati. 38 | 39 | __`{appname} run program.js`__ ...... Esegui il tuo programma sui dati di ingresso selezionati. 40 | 41 | __`{appname} verify program.js`__ ... Verifica il tuo programma rispetto ai dati in uscita attesi. 42 | 43 | __`{appname} -l `__ ....... Cambia la lingua attuale del sistema con quella desiderata. 44 | -------------------------------------------------------------------------------- /i18n/usage/pt-br.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Está tendo dificuldade com algum exercício do {appname}? 4 | 5 | Um time expert de elfos ajudantes está esperando para te ajudar a dominar as técnicas básicas do Node.js, basta você ir à: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | e adicionar uma __Nova Issue__ dizendo com o quê você está tendo problemas. Não existem perguntas _bobas_! 10 | 11 | Se você está procurando por ajuda com Node.js em geral, o canal #Node.js em Freenode IRC geralmente é um ótimo lugar para encontrar alguém disposto à ajudar. Também existe o grupo Google do Node.js: 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Encontrou um bug no {appname} ou quer contribuir? 16 | 17 | O repositório oficial do {appname} é: 18 | 19 | {appRepo} 20 | 21 | Sinta-se livre para indicar um bug ou (preferívelmente) um pull request. 22 | 23 | ## Instruções de Uso 24 | 25 | __`{appname}`__ ..................... Mostra um menu para selecionar interativamente um workshop. 26 | 27 | __`{appname} list`__ ................ Mostra uma lista contendo todos os workshops. 28 | 29 | __`{appname} select NAME`__ ......... Seleciona um workshop. 30 | 31 | __`{appname} current`__ ............. Mostra o workshop selecionado atualmente. 32 | 33 | __`{appname} print`__ ............... Imprime as instruções do workshop selecionado atualmente. 34 | 35 | __`{appname} next`__ ................ Imprime as instruções para o próximo workshop incompleto após o workshop selecionado atualmente. 36 | 37 | __`{appname} reset`__ ............... Zera o progresso completo do workshop. 38 | 39 | __`{appname} run program.js`__ ...... Roda o seu programa utilizando os critérios citados pelo workshop. 40 | 41 | __`{appname} verify program.js`__ ... Avalia seu programa utilizando os critérios citados pelo workshop. 42 | 43 | __`{appname} -l `__ ....... MUda o sistema para a linguagem especificada. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, Martin Heidegger (the "Original Author") 2 | All rights reserved. 3 | 4 | MIT +no-false-attribs License 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | Distributions of all or part of the Software intended to be used 19 | by the recipients as they would use the unmodified Software, 20 | containing modifications that substantially alter, remove, or 21 | disable functionality of the Software, outside of the documented 22 | configuration mechanisms provided by the Software, shall be 23 | modified such that the Original Author's bug reporting email 24 | addresses and urls are either replaced with the contact information 25 | of the parties responsible for the changes, or removed entirely. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | 36 | 37 | Except where noted, this license applies to any and all software 38 | programs and associated documentation files created by the 39 | Original Author, when distributed with the Software. 40 | -------------------------------------------------------------------------------- /lib/createExerciseMeta.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var util = require('../util') 3 | var path = require('path') 4 | 5 | function fileError (id, file) { 6 | var error = new Error(id) 7 | error.id = id 8 | error.exerciseFile = file 9 | error.toString = function () { 10 | return '[WorkshopperFileError: ' + id + ' @ ' + error.exerciseFile + ']' 11 | } 12 | return error 13 | } 14 | 15 | module.exports = function createExerciseMeta (exerciseDir, nameOrObject, fnOrObject, fn) { 16 | var meta 17 | var stat 18 | 19 | meta = (typeof nameOrObject === 'object') 20 | ? nameOrObject 21 | : (typeof fnOrObject === 'object') 22 | ? fnOrObject 23 | : {} 24 | 25 | if (typeof nameOrObject === 'string') { 26 | meta.name = nameOrObject 27 | } 28 | 29 | if (/^\/\//.test(meta.name)) { 30 | return 31 | } 32 | 33 | if (!meta.id) { 34 | meta.id = util.idFromName(meta.name) 35 | } 36 | 37 | if (!meta.dir) { 38 | meta.dir = util.dirFromName(exerciseDir, meta.name) 39 | } 40 | 41 | if (meta.dir && !meta.exerciseFile) { 42 | meta.exerciseFile = path.join(meta.dir, './exercise.js') 43 | } 44 | 45 | if (typeof fnOrObject === 'function') { 46 | meta.fn = fnOrObject 47 | } 48 | 49 | if (typeof fn === 'function') { 50 | meta.fn = fn 51 | } 52 | 53 | if (!meta.fn && meta.exerciseFile) { 54 | meta.fn = function () { 55 | try { 56 | stat = fs.statSync(meta.exerciseFile) 57 | } catch (err) { 58 | throw fileError('missing_file', meta.exerciseFile) 59 | } 60 | 61 | if (!stat || !stat.isFile()) { 62 | throw fileError('missing_file', meta.exerciseFile) 63 | } 64 | 65 | var exercise = require(meta.exerciseFile) 66 | if (typeof exercise === 'function') { 67 | exercise = exercise() 68 | } 69 | return exercise 70 | } 71 | } 72 | 73 | if (!meta.fn) { 74 | throw fileError('not_a_workshopper', meta.exerciseFile) 75 | } 76 | 77 | return meta 78 | } 79 | -------------------------------------------------------------------------------- /i18n/usage/fr.md: -------------------------------------------------------------------------------- 1 | # {title} 2 | 3 | ## Un souci avec un exercice de {appname} ? 4 | 5 | Une équipe d’assistants elfiques experts n’attend que l’occasion de vous aider à maîtriser les bases de Node.js, allez simplement sur: 6 | 7 | https://github.com/nodeschool/discussions/issues 8 | 9 | et ajoutez une __Nouvelle Requête__ (_New Issue_) pour nous faire savoir ce qui vous pose problème. Il n’y a pas de questions _débiles_! 10 | 11 | Si vous cherchez de l’aide plus globale sur Node.js, le canal #Node.js sur Freenode IRC est généralement un excellent point de départ pour trouver quelqu’un qui voudra bien vous aider. Il existe aussi un Google Group Node.js : 12 | 13 | https://groups.google.com/forum/#!forum/nodejs 14 | 15 | ## Vous avez trouvé un bogue dans {appname} ou voulez juste contribuer ? 16 | 17 | Le dépôt officiel pour {appname} est ici : 18 | 19 | {appRepo} 20 | 21 | N’hésitez pas à créer un ticket ou (de préférence) faire une _pull request_… 22 | 23 | 24 | ## Utilisation 25 | 26 | __`{appname}`__ ..................... Affiche un menu interactif pour choisir un exercice. 27 | 28 | __`{appname} list`__ ................ Affiche une liste des exercices, à raison d’un par ligne. 29 | 30 | __`{appname} select NAME`__ ......... Sélectionne un exercice. 31 | 32 | __`{appname} current`__ ............. Affiche l’exercice en cours. 33 | 34 | __`{appname} print`__ ............... Affiche les instructions pour l’exercice en cours. 35 | 36 | __`{appname} next`__ ................ Affiche les instructions pour le prochain exercice que vous n’avez pas encore terminé, après l’exercice courant. 37 | 38 | __`{appname} reset`__ ............... Réinitialise votre progression pour cet atelier. 39 | 40 | __`{appname} run program.js`__ ...... Exécute votre programme dans le contexte décrit par les instructions de l’exercice. 41 | 42 | __`{appname} verify program.js`__ ... Vérifie que votre programme résoud l’exercice. 43 | 44 | __`{appname} -l `__ ....... Utilise la langue spécifiée pour l’atelier. 45 | -------------------------------------------------------------------------------- /lib/commands/language.js: -------------------------------------------------------------------------------- 1 | function renderNonTty (shop, menuOptions) { 2 | var __ = shop.i18n.__ 3 | var stream = shop.createMarkdownStream() 4 | 5 | stream.append('\n' + 6 | '# {error.notty}\n' + 7 | '---\n' + 8 | '# {title}\n' + 9 | '---\n', 'md') 10 | 11 | shop.options.languages.forEach(function (language, no) { 12 | stream.append(' `{appName} lang ' + language + '`: ' + __('language.' + language) + ((shop.i18n.lang() === language) ? ' ... [' + __('language._current') + ']' : ''), 'md') 13 | }) 14 | stream 15 | .appendChain('---\n', 'md') 16 | .pipe(require('../mseePipe')()) 17 | .pipe(process.stdout) 18 | } 19 | 20 | exports.order = 1 21 | exports.filter = require('../langFilter') 22 | exports.aliases = ['lang'] 23 | exports.handler = function printLanguageMenu (shop, args) { 24 | const chalk = require('chalk') 25 | const __ = shop.i18n.__ 26 | const current = shop.i18n.lang() 27 | const menuOptions = { 28 | title: __('title'), 29 | subtitle: shop.i18n.has('subtitle') && __('subtitle'), 30 | menu: shop.options.languages.map(function (language) { 31 | return { 32 | label: chalk.bold('» ' + __('language.' + language)), 33 | marker: (current === language) ? ' [' + __('language._current') + ']' : '', 34 | handler: function () { 35 | shop.i18n.change(language) 36 | shop.execute(['menu']) 37 | } 38 | } 39 | }), 40 | extras: [{ 41 | label: chalk.bold(__('menu.cancel')), 42 | handler: shop.execute.bind(shop, ['menu']) 43 | }, { 44 | label: chalk.bold(__('menu.exit')), 45 | handler: process.exit.bind(process, 0) 46 | }] 47 | } 48 | 49 | if (args.length > 0) { 50 | try { 51 | shop.i18n.change(args[0]) 52 | } catch (e) {} 53 | console.log(shop.i18n.__('language._current') + ': ' + __('language.' + shop.i18n.lang())) 54 | process.exit() 55 | } 56 | 57 | shop.options.menuFactory.options.selected = shop.options.languages.reduce(function (selected, language, count) { 58 | return language === current ? count : selected 59 | }, undefined) 60 | var menu = shop.options.menuFactory.create(menuOptions) 61 | if (!menu) { 62 | renderNonTty(shop, menuOptions) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /i18n/zh-tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23m選擇一個題目,然後按下 \u001b[3mEnter\u001b[23m 開始嘗試!", 3 | "menu": { 4 | "credits": "感謝", 5 | "exit": "離開", 6 | "help": "幫助", 7 | "completed": "已完成", 8 | "language": "選擇語言 (CHOOSE LANGUAGE)", 9 | "cancel": "取消", 10 | "update": "更新" 11 | }, 12 | "language": { 13 | "_current": "目前", 14 | "de": "德文 (Deutsch)", 15 | "en": "英文 (English)", 16 | "fr": "法文 (français)", 17 | "ja": "日文 (日本語)", 18 | "zh-cn": "簡體中文", 19 | "zh-tw": "繁體中文", 20 | "es": "西班牙文 (Español)", 21 | "pt-br": "葡萄牙文 (Português)", 22 | "vi": "越南文 (Tiếng Việt)", 23 | "ru": "俄文 (Русский)", 24 | "uk": "烏克蘭語 (Українська)", 25 | "ko": "韓文 (한국어)", 26 | "pl": "波蘭語 (Polski)", 27 | "nb-no": "挪威語 (Norsk)", 28 | "it": "義大利文 (Italiano)", 29 | "sr": "塞爾維亞語 (српски)", 30 | "tr": "土耳其 (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "目前沒有選擇任何練習題,請從選單中選擇一個。", 35 | "missing": "找不到練習題: {{{name}}}", 36 | "unexpected_error": "無法執行 {{mode}}: {{{err}}}", 37 | "missing_file": "錯誤: {{{exerciseFile}}} 並不存在!", 38 | "not_a_workshopper": "錯誤: {{{exerciseFile}}} 並不是一個有效的練習題", 39 | "method_not_required": "{{method}} 不需要這個練習。", 40 | "preparing": "準備練習題時出錯: {{{err}}}", 41 | "loading": "載入練習題文字時出錯: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "目前的練習題完成之後就沒有其他未完成的練習題了。", 44 | "cleanup": "清理時出錯: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "失敗", 49 | "message": "你對 {{{currentExercise.name}}} 的解答並沒有通過。再試一次!\n" 50 | }, 51 | "pass": { 52 | "title": "通過", 53 | "message": "你對 {{{currentExercise.name}}} 的解答通過了!" 54 | }, 55 | "notes": { 56 | "compare": "這裡是官方給的參考解答,以便你做參考比較:\n", 57 | "load_error": "錯誤: 在印出解答檔案時發生錯誤: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "輸入 '{{appName}}' 來顯示選單。\n", 62 | "usage": "用法: {{appName}} {{mode}} mysubmission.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} 進度重置", 66 | "finished": "你已經完成所有的挑戰了!喔耶!\n", 67 | "state": "練習題 {{count}} 之 {{amount}}", 68 | "remaining": { 69 | "one": "你還剩下最後一個挑戰。", 70 | "other": "你還剩 {{count}} 個挑戰。" 71 | } 72 | }, 73 | "credits": { 74 | "title": "底下這些做出重大貢獻的 hacker 為您製作了 {{appName}}:", 75 | "name": "姓名", 76 | "user": "GitHub 用戶名" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** 已經是最新的版本了。\n\n**恭喜!**", 80 | "now": "{{name}}_@{{current}}_ 已經過時了,更新至 **{{latest}}** 請執行 \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23m选择一个题目,按 \u001b[3m回车\u001b[23m 开始", 3 | "menu": { 4 | "credits": "鸣谢", 5 | "exit": "退出", 6 | "help": "帮助", 7 | "completed": "完成", 8 | "language": "选择语言 (CHOOSE LANGUAGE)", 9 | "cancel": "取消", 10 | "update": "检查更新" 11 | }, 12 | "language": { 13 | "_current": "当前", 14 | "de": "德语 (Deutsch)", 15 | "en": "英语 (English)", 16 | "fr": "法国 (français)", 17 | "ja": "日语 (日本語)", 18 | "zh-cn": "简体中文", 19 | "zh-tw": "繁体中文", 20 | "es": "西班牙语 (Español)", 21 | "pt-br": "葡萄牙 (Português)", 22 | "vi": "越南 (Tiếng Việt)", 23 | "ru": "俄 (Русский)", 24 | "uk": "乌克兰语 (Українська)", 25 | "ko": "韩国语 (한국어)", 26 | "pl": "波兰语 (Polski)", 27 | "nb-no": "挪威 (Norsk)", 28 | "it": "意大利语 (Italiano)", 29 | "sr": "塞尔维亚语 (српски)", 30 | "tr": "土耳其 (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "当前没有选择任何习题,请从菜单选择一个。", 35 | "missing": "没有这个练习题: {{{name}}}", 36 | "unexpected_error": "无法执行 {{mode}}: {{{err}}}", 37 | "missing_file": "错误: {{{exerciseFile}}} 不存在!", 38 | "not_a_workshopper": "错误: {{{exerciseFile}}} 不是一个练习题。", 39 | "method_not_required": "{{method}} 不需要这个练习。", 40 | "preparing": "准备练习题时出错: {{{err}}}", 41 | "loading": "载入习题文本时候出错: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "当前习题之后没有未完成的习题了。", 44 | "cleanup": "清理过程出错: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "失败", 49 | "message": "你对 {{{currentExercise.name}}} 所作的答案未通过验证, 请再试一次!\n" 50 | }, 51 | "pass": { 52 | "title": "通过", 53 | "message": "你对 {{{currentExercise.name}}} 所作的答案已通过验证!" 54 | }, 55 | "notes": { 56 | "compare": "以下是官方给出的参考答案,以备你做比较参考:\n", 57 | "load_error": "错误: 显示答案的时候发生错误: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "输入运行 '{{appName}}' 命令以显示菜单.\n", 62 | "usage": "用法: {{appName}} {{mode}} mysubmission.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} 进度重置", 66 | "finished": "哇哦!你已经完成了所有练习题啦!\n", 67 | "state": "第 {{count}} 个习题,共 {{amount}} 个", 68 | "remaining": { 69 | "one": "你还剩最后一个习题", 70 | "other": "你还剩 {{count}} 个习题。" 71 | } 72 | }, 73 | "credits": { 74 | "title": "{{appName}} 的完成离不开以下列举的黑客们:", 75 | "name": "姓名", 76 | "user": "GitHub 用户名" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 80 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/commands/menu.js: -------------------------------------------------------------------------------- 1 | function getExtras (shop) { 2 | return shop.cli.commands.concat() 3 | .reverse() 4 | .filter(function isCommandInMenu (extra) { 5 | if (typeof extra.filter === 'function' && !extra.filter(shop)) { 6 | return false 7 | } 8 | return extra.menu !== false 9 | }) 10 | } 11 | 12 | function renderNonTty (shop, menuOptions) { 13 | var completed = shop.appStorage.get('completed') || [] 14 | var __ = shop.i18n.__ 15 | var stream = shop.createMarkdownStream() 16 | 17 | stream.append('\n' + 18 | '# {error.notty}\n' + 19 | '---\n' + 20 | '# {title}\n' + 21 | '---\n', 'md') 22 | 23 | shop.exercises.forEach(function (exercise, no) { 24 | stream.append(' `{appName} select ' + no + '`: ' + shop.i18n.__('exercise.' + exercise) + (completed.indexOf(exercise) >= 0 ? ' [' + shop.i18n.__('menu.completed') + ']' : ''), 'md') 25 | }) 26 | stream.append('---\n', 'md') 27 | 28 | getExtras(shop) 29 | .forEach(function (command) { 30 | stream.append(' `{appName} ' + command.aliases[0] + '`: ' + __('menu.' + command.aliases[0]) + '\n', 'md') 31 | }) 32 | stream 33 | .appendChain('\n---\n', 'md') 34 | .pipe(require('../mseePipe')()) 35 | .pipe(process.stdout) 36 | } 37 | 38 | exports.handler = function (shop) { 39 | const chalk = require('chalk') 40 | 41 | var __ = shop.i18n.__ 42 | var __n = shop.i18n.__n 43 | var completed = shop.appStorage.get('completed') || [] 44 | var current = shop.appStorage.get('current') 45 | var menuOptions = { 46 | title: __('title'), 47 | subtitle: shop.i18n.has('subtitle') && __('subtitle'), 48 | menu: shop.exercises.map(function (exercise) { 49 | return { 50 | label: chalk.bold('» ' + __('exercise.' + exercise)), 51 | marker: (completed.indexOf(exercise) >= 0) ? '[' + __('menu.completed') + ']' : '', 52 | handler: shop.execute.bind(shop, ['select', exercise]) 53 | } 54 | }), 55 | extras: getExtras(shop) 56 | .map(function (command) { 57 | return { 58 | label: chalk.bold(__n('menu.' + command.aliases[0], command.info)), 59 | handler: command.handler.bind(command, shop, []) 60 | } 61 | }) 62 | .concat({ 63 | label: chalk.bold(__('menu.exit')), 64 | handler: process.exit.bind(process, 0) 65 | }) 66 | } 67 | 68 | if (completed.indexOf(current) >= 0) { 69 | current = shop.getNext() 70 | } 71 | shop.options.menuFactory.options.selected = shop.exercises.reduce(function (selected, exercise, count) { 72 | return exercise === current ? count : selected 73 | }, undefined) 74 | shop.options.menuFactory.create(menuOptions) || renderNonTty(shop, menuOptions) 75 | } 76 | exports.menu = false 77 | -------------------------------------------------------------------------------- /i18n/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23m演習課題を選択、 \u001b[3mEnter\u001b[23m 押してスタートします。", 3 | "menu": { 4 | "credits": "謝辞", 5 | "exit": "終了する", 6 | "help": "ヘルプを表示する", 7 | "completed": "完了済み", 8 | "language": "言語を選択する (CHOOSE LANGUAGE)", 9 | "cancel": "キャンセル", 10 | "update": "アップデート チェック" 11 | }, 12 | "language": { 13 | "_current": "現在の言語", 14 | "de": "ドイツ語 (Deutsch)", 15 | "en": "英語 (English)", 16 | "fr": "フランス語 (français)", 17 | "ja": "日本語", 18 | "zh-cn": "中国語 (簡体)", 19 | "zh-tw": "中国語 (繁体)", 20 | "es": "スペイン語 (Español)", 21 | "pt-br": "ポルトガル語 (Português)", 22 | "vi": "ベトナム語 (Tiếng Việt)", 23 | "ru": "ロシア語 (Русский)", 24 | "uk": "ウクライナ語 (Українська)", 25 | "ko": "韓国語 (한국어)", 26 | "pl": "ポーランド語 (Polski)", 27 | "nb-no": "ノルウェー語 (Norsk)", 28 | "sr": "セルビア語 (српски)", 29 | "tr": "トルコ (Türkçe)" 30 | }, 31 | "error": { 32 | "exercise": { 33 | "none_active": "演習課題が選ばれていません。メニューから一つ選んでください。", 34 | "missing": "{{{name}}}という演習課題はありません。", 35 | "unexpected_error": "{{mode}}できませんでした:{{{err}}}", 36 | "missing_file": "エラー:{{{exerciseFile}}} は見つかりません。", 37 | "not_a_workshopper": "エラー:{{{exerciseFile}}} はワークショップの演習課題ではありません。", 38 | "method_not_required": "{{method}} はこの問題では利用できません。", 39 | "preparing": "演習課題の準備中に問題が発生しました:{{{err}}}", 40 | "loading": "演習課題のテキスト読み込み中に問題が発生しました:{{{err}}}" 41 | }, 42 | "no_uncomplete_left": "現在の演習課題の後に未完了の演習課題はありません。", 43 | "cleanup": "クリーンアップ中に問題が発生しました: {{{err}}}", 44 | "notty": "tty 互換の端末で実行してください。" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "残念!", 49 | "message": "「{{{currentExercise.name}}}」に対するあなたの回答は不合格でした。再度挑戦してみて下さい!\n" 50 | }, 51 | "pass": { 52 | "title": "おめでとう!", 53 | "message": "「{{{currentExercise.name}}}」に対するあなたの回答は合格です!" 54 | }, 55 | "notes": { 56 | "compare": "これが正式な回答です(もしあなたの回答と比較してみたいならどうぞ):\n", 57 | "load_error": "エラー: 回答の表示の際に問題が発生しました: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "メニューを表示するには '{{appName}}' と入力してください。\n", 62 | "usage": "使い方: {{appName}} {{mode}} mysubmission.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} の進捗状況がリセットされました。", 66 | "finished": "なんとあなたは全問クリア済みです!すばらしい!\n", 67 | "state": "{{amount}}問中{{count}}問目", 68 | "remaining": { 69 | "one": "一つのチャレンジが残っています!", 70 | "other": "あと{{count}}個のチャレンジが残っています。" 71 | } 72 | }, 73 | "credits": { 74 | "title": "{{appName}} をお届けしたのは、以下の献身的なハッカー達です:", 75 | "name": "名前", 76 | "user": "GitHub Username" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** は一番新しいバーションです。\n\n**おめでとうございます!**", 80 | "now": "{{name}}_@{{current}}_ は古いです、**{{latest}}** にアップデートをします。このコマンドを使っています:\n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23m연습문제를 고르고 \u001b[3mEnter\u001b[23m로 시작하세요.", 3 | "menu": { 4 | "credits": "CREDITS", 5 | "exit": "끝내기", 6 | "help": "도움말을 확인", 7 | "completed": "완료", 8 | "language": "언어를 선택 (CHOOSE LANGUAGE)", 9 | "cancel": "취소", 10 | "update": "업데이트를 확인" 11 | }, 12 | "language": { 13 | "_current": "지금 언어", 14 | "de": "독일어(Deutsch)", 15 | "en": "영어(English)", 16 | "fr": "프랑스어(français)", 17 | "ja": "일본어(日本語)", 18 | "zh-cn": "중국어(简体中文)", 19 | "zh-tw": "대만어(繁體中文)", 20 | "es": "스페인어(Español)", 21 | "pt-br": "포르투갈어(Português)", 22 | "vi": "베트남어(Tiếng Việt)", 23 | "ru": "러시아어(Русский)", 24 | "uk": "우크라이나어 (Українська)", 25 | "ko": "한국어", 26 | "pl": "폴란드어 (Polski)", 27 | "nb-no": "노르웨이어 (Norsk)", 28 | "it": "이탈리아어 (Italiano)", 29 | "sr": "세르비아어 (српски)", 30 | "tr": "터키의 (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "풀고 있는 연습 문제가 없습니다. 메뉴에서 골라주세요.", 35 | "missing": "{{{name}}}(이)라는 연습 문제는 없습니다.", 36 | "unexpected_error": "{{mode}}을(를) 할 수 없었습니다. {{{err}}}", 37 | "missing_file": "오류: {{{exerciseFile}}}은(는) 존재하지 않습니다!", 38 | "not_a_workshopper": "오류: {{{exerciseFile}}}은(는) 워크숍의 연습 문제가 아닙니다.", 39 | "method_not_required": "{{method}} 이 연습 필요하지 않습니다.", 40 | "preparing": "연습 문제의 준비에 실패했습니다. {{{err}}}", 41 | "loading": "연습 문제의 텍스트를 불러오는 중 오류가 발생했습니다. {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "이 연습 문제를 마지막으로 더 이상 연습 문제가 없습니다.", 44 | "cleanup": "청소 중 문제가 발생했습니다. {{{err}}}", 45 | "notty": "tty 호환 터미널에서 실행해주세요." 46 | }, 47 | "solution": { 48 | "fail": { 49 | "title": "실패", 50 | "message": "{{{currentExercise.name}}}의 해결책은 통과하지 못했습니다. 다시 한번 해보세요!\n" 51 | }, 52 | "pass": { 53 | "title": "통과", 54 | "message": "{{{currentExercise.name}}}의 해결책은 통과했습니다!" 55 | }, 56 | "notes": { 57 | "compare": "비교해보고 싶다면 공식 해답은 이렇습니다.\n", 58 | "load_error": "오류: 해답을 표시하는 도중 문제가 생겼습니다. {{{err}}}" 59 | } 60 | }, 61 | "ui": { 62 | "return": "메뉴를 보려면 '{{appName}}'을(를) 입력하세요.\n", 63 | "usage": "사용법: {{appName}} {{mode}} mysubmission.js" 64 | }, 65 | "progress": { 66 | "reset": "{{title}}의 진행 상황을 초기화했습니다.", 67 | "finished": "모든 연습 문제를 끝냈습니다! 야호!\n", 68 | "state": "연습 문제 {{amount}}개 중 {{count}}개 째", 69 | "remaining": { 70 | "one": "한 개의 도전만 남아있습니다!", 71 | "other": "앞으로 {{count}}개의 도전이 남아 있습니다." 72 | } 73 | }, 74 | "credits": { 75 | "title": "{{appName}} 는 이런 열성적인 해커에 의해 제공됩니다:", 76 | "name": "Name", 77 | "user": "GitHub Username" 78 | }, 79 | "update": { 80 | "latest_version": "{{name}}**@{{version}}** 는 최신 버전에서 이미.\n\n**치하!**", 81 | "now": "{{name}}_@{{current}}_ 는 다음 명령을 **{{latest}}**하는 업데이트, 오래된이다로 업데이트:\n\n```bash\n{{cmd}}\n```\n" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /i18n/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mДа почнете изаберите задатак и притисните \u001b[3mУлаз\u001b[23m", 3 | "menu": { 4 | "credits": "ЗАСЛУГЕ", 5 | "exit": "ИЗЛАЗ", 6 | "help": "ПОМОЋ", 7 | "completed": "ЗАВРШЕНО", 8 | "language": "ИЗАБЕРИ ЈЕЗИК (CHOOSE LANGUAGE)", 9 | "cancel": "ОТКАЖИ", 10 | "update": "АЖУРИРАЈ" 11 | }, 12 | "language": { 13 | "_current": "ТРЕНУТНИ", 14 | "de": "немачки (Deutsch)", 15 | "en": "енглески (English)", 16 | "fr": "француски (français)", 17 | "ja": "јапански (日本語)", 18 | "zh-cn": "кинески (поједностављени) (简体中文)", 19 | "zh-tw": "кинески (традиционални) (繁體中文)", 20 | "es": "шпански (Español)", 21 | "pt-br": "бразилски португалски (Português)", 22 | "vi": "вијетнамски (Tiếng Việt)", 23 | "ru": "руски (русский)", 24 | "ko": "корејски (한국어)", 25 | "pl": "пољски (Polski)", 26 | "uk": "украјински (Українська)", 27 | "nb-no": "норвешки (Norsk)", 28 | "it": "италијански (Italiano)", 29 | "sr": "српски", 30 | "tr": "турски (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Задатак није одабран, користите изборник.", 35 | "missing": "Нема задатка: {{{name}}}", 36 | "unexpected_error": "Грешка {{mode}}: {{{err}}}", 37 | "missing_file": "ГРЕШКА: {{{exerciseFile}}} није нађен!", 38 | "not_a_workshopper": "ГРЕШКА: {{{exerciseFile}}} није део радионице.", 39 | "method_not_required": "{{method}} није потребан за ову вежбу.", 40 | "preparing": "Грешка у припреми задатка: {{{err}}}", 41 | "loading": "Грешка при учитавању задатка: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Сви задаци су завршени", 44 | "cleanup": "Грешка чишћења: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "НЕТАЧНО", 49 | "message": "Ваше решење {{{currentExercise.name}}} није тачно. Пробајте поново!\n" 50 | }, 51 | "pass": { 52 | "title": "ТАЧНО", 53 | "message": "Ваше решење {{{currentExercise.name}}} је тачно!" 54 | }, 55 | "notes": { 56 | "compare": "Ово је правилно решење вашег задатка:\n", 57 | "load_error": "Грешка при учитавању датотеке с правим одговором: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "Унесите '{{appName}}' да видите изборник.\n", 62 | "usage": "Како да користите: {{appName}} {{mode}} mysubmission.js" 63 | }, 64 | "progress": { 65 | "reset": "Почните {{title}} испочетка", 66 | "finished": "Решили сте све задатке! Ура!\n", 67 | "state": "Задатак {{count}} од {{amount}}", 68 | "remaining": { 69 | "one": "Остао вам је један нерешен задатак.", 70 | "other": "Нерешених задатака: {{count}}." 71 | } 72 | }, 73 | "credits": { 74 | "title": "За {{appName}} су заслужни:", 75 | "name": "Име", 76 | "user": "GitHub налог" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** је већ ажуран.\n\n**Свака част!**", 80 | "now": "{{name}}_@{{current}}_ није ажуран, ажурирајте **{{latest}}** са \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mДля старта выберите задачу и нажмите \u001b[3mВвод\u001b[23m", 3 | "menu": { 4 | "credits": "ПРИМЕЧАНИЯ", 5 | "exit": "ВЫХОД", 6 | "help": "СПРАВКА", 7 | "completed": "ВЫПОЛНЕНО", 8 | "language": "ВЫБРАТЬ ЯЗЫК (CHOOSE LANGUAGE)", 9 | "cancel": "ОТМЕНА", 10 | "update": "ПРОВЕРИТЬ ОБНОВЛЕНИЯ" 11 | }, 12 | "language": { 13 | "_current": "ТЕКУЩИЙ", 14 | "de": "Немецкий (Deutsch)", 15 | "en": "Английский (English)", 16 | "fr": "Французский (français)", 17 | "ja": "Японский (日本語)", 18 | "zh-cn": "Китайский (简体中文)", 19 | "zh-tw": "Тайваньская (繁體中文)", 20 | "es": "Испанский (Español)", 21 | "pt-br": "Бразильский португальский (Português)", 22 | "vi": "Вьетнамский (Tiếng Việt)", 23 | "ru": "Русский", 24 | "ko": "Корейский (한국어)", 25 | "pl": "Польский (Polski)", 26 | "nb-no": "Норвежский (Norsk)", 27 | "it": "Италья́нский (Italiano)", 28 | "sr": "Сербский (српски)", 29 | "tr": "турецкий (Türkçe)" 30 | }, 31 | "error": { 32 | "exercise": { 33 | "none_active": "Задача не выбрана. Воспользуйтесь меню.", 34 | "missing": "Нет задачи: {{{name}}}", 35 | "unexpected_error": "Ошибка {{mode}}: {{{err}}}", 36 | "missing_file": "ОШИБКА: {{{exerciseFile}}} не найден!", 37 | "not_a_workshopper": "ОШИБКА: {{{exerciseFile}}} не является воркшоппером.", 38 | "method_not_required": "{{method}} не требуется для этого упражнения.", 39 | "preparing": "Ошибка подготовки задачи: {{{err}}}", 40 | "loading": "Ошибка загрузки задачи: {{{err}}}" 41 | }, 42 | "no_uncomplete_left": "Все задачи выполнены", 43 | "cleanup": "Ошибка сброса: {{{err}}}" 44 | }, 45 | "solution": { 46 | "fail": { 47 | "title": "НЕВЕРНО", 48 | "message": "Ваше решение {{{currentExercise.name}}} неверное. Попробуйте снова!\n" 49 | }, 50 | "pass": { 51 | "title": "ВЕРНО", 52 | "message": "Ваше решение {{{currentExercise.name}}} верное!" 53 | }, 54 | "notes": { 55 | "compare": "Это правильное решение задачи:\n", 56 | "load_error": "Ошибка при чтении файла с правильным ответом: {{{err}}}" 57 | } 58 | }, 59 | "ui": { 60 | "return": "Введите '{{appName}}' для вызова меню.\n", 61 | "usage": "Как использовать: {{appName}} {{mode}} mysubmission.js" 62 | }, 63 | "progress": { 64 | "reset": "{{title}} начать сначала", 65 | "finished": "Вы решили все задачи! Ура!\n", 66 | "state": "Задача {{count}} из {{amount}}", 67 | "remaining": { 68 | "one": "У вас осталась одна нерешенная задача.", 69 | "other": "У вас осталось {{count}} нерешенных задач." 70 | } 71 | }, 72 | "credits": { 73 | "title": "{{appName}} была создана следующими разработчиками:", 74 | "name": "Имя", 75 | "user": "GitHub аккаунт" 76 | }, 77 | "update": { 78 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 79 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /i18n/vi.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mChọn một bài tập và ấn \u001b[3mEnter\u001b[23m để bắt đầu", 3 | "menu": { 4 | "credits": "ĐÓNG GÓP", 5 | "exit": "THOÁT", 6 | "help": "TRỢ GIÚP", 7 | "completed": "HOÀN THÀNH", 8 | "language": "CHỌN NGÔN NGỮ (CHOOSE LANGUAGE)", 9 | "cancel": "HỦY BỎ", 10 | "update": "KIỂM TRA CẬP NHẬT" 11 | }, 12 | "language": { 13 | "_current": "HIỆN TẠI", 14 | "de": "Tiếng Đức (Deutsch)", 15 | "en": "Tiếng Anh (English)", 16 | "fr": "Tiếng Pháp (français)", 17 | "ja": "Tiếng Nhật (日本語)", 18 | "zh-cn": "Tiếng Trung (简体中文)", 19 | "zh-tw": "Tiếng Đài Loan (繁體中文)", 20 | "es": "Tiếng Tây Ban Nha (Español)", 21 | "pt-br": "Tiếng Brasil (Português)", 22 | "vi": "Tiếng Việt", 23 | "ru": "Tiếng Nga (Русский)", 24 | "uk": "Tiếng Ukraina (Українська)", 25 | "ko": "Hàn Quốc (한국어)", 26 | "pl": "Đánh Bóng (Polski)", 27 | "nb-no": "Tiếng Na Uy (Norsk)", 28 | "it": "Tiếng Ý (Italiano)", 29 | "sr": "Tiếng Serbia (српски)", 30 | "tr": "Thổ Nhĩ Kỳ (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Bạn chưa chọn bài tập. Hãy chọn lấy một bài từ menu.", 35 | "missing": "Không có bài tập nào là: {{{name}}}", 36 | "unexpected_error": "Không thể {{mode}}: {{{err}}}", 37 | "missing_file": "LỖI: {{{exerciseFile}}} không tồn tại!", 38 | "not_a_workshopper": "LỖI: {{{exerciseFile}}} không nằm trong danh sách bài tập.", 39 | "method_not_required": "{{method}} không cần thiết cho bài tập này.", 40 | "preparing": "Lỗi chuẩn bị bài tập: {{{err}}}", 41 | "loading": "Lỗi nạp bài tập: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Không có bài tập chưa hoàn thành nào sau bài tập hiện tại", 44 | "cleanup": "Lỗi dọn dẹp: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "SAI", 49 | "message": "Bài {{{currentExercise.name}}} của bạn chưa chuẩn. Thử lại xem sao!\n" 50 | }, 51 | "pass": { 52 | "title": "ĐÚNG", 53 | "message": "Bài {{{currentExercise.name}}} của bạn rất ngon!" 54 | }, 55 | "notes": { 56 | "compare": "Đây là giải pháp chuẩn, bạn tham khảo xem sao:\n", 57 | "load_error": "LỖI: Phát sinh lỗi in file giải pháp: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "Gõ '{{appName}}' để hiển thị menu.\n", 62 | "usage": "Cách dùng: {{appName}} {{mode}} mysubmission.js" 63 | }, 64 | "progress": { 65 | "reset": "reset lại bài tập {{title}}", 66 | "finished": "Thật tuyệt vời! Chúc mừng bạn đã vượt qua tất cả các thử thách\n", 67 | "state": "Bài số {{count}} / {{amount}}", 68 | "remaining": { 69 | "one": "Bạn còn một bài nữa thôi.", 70 | "other": "Bạn vẫn còn {{count}} bài." 71 | } 72 | }, 73 | "credits": { 74 | "title": "{{appName}} được đóng góp bởi các hacker tích cực sau:", 75 | "name": "Name", 76 | "user": "GitHub Username" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 80 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mSeleziona un esercizio e premi \u001b[3mInvio\u001b[23m per cominciare", 3 | "menu": { 4 | "credits": "CREDITI", 5 | "exit": "ESCI", 6 | "help": "AIUTO", 7 | "completed": "COMPLETATO", 8 | "language": "CAMBIA LA LINGUA (CHOOSE LANGUAGE)", 9 | "cancel": "ANNULLA", 10 | "update": "CERCA AGGIORNAMENTI" 11 | }, 12 | "language": { 13 | "_current": "ATTUALE", 14 | "de": "Tedesco (Deutsch)", 15 | "en": "Inglese (English)", 16 | "fr": "Francese (français)", 17 | "ja": "Giapponese (日本語)", 18 | "zh-cn": "Cinese (简体中文)", 19 | "zh-tw": "Taiwanese (繁體中文)", 20 | "es": "Spagnolo (Español)", 21 | "pt-br": "Portoghese brasiliano (Português)", 22 | "vi": "Vietnamita (Tiếng Việt)", 23 | "ru": "Russo (Русский)", 24 | "ko": "Coreano (한국어)", 25 | "pl": "Polacco (Polski)", 26 | "nb-no": "Norvegese (Norsk)", 27 | "it": "Italiano", 28 | "sr": "Serbo (српски)", 29 | "tr": "Turco (Türkçe)" 30 | }, 31 | "error": { 32 | "exercise": { 33 | "none_active": "Nessun esercizio attivo. Seleziona uno dal menu.", 34 | "missing": "Esercizio mancante: {{{name}}}", 35 | "unexpected_error": "Impossibile completare {{mode}}: {{{err}}}", 36 | "missing_file": "ERRORE: {{{exerciseFile}}} non esiste!", 37 | "not_a_workshopper": "ERRORE: {{{exerciseFile}}} non è un valido esercizio di workshopper", 38 | "method_not_required": "{{method}} non è richiesto per questo esercizio.", 39 | "preparing": "Errore durante la preparazione dell'esercizio: {{{err}}}", 40 | "loading": "Errore durante il caricamento del testo dell'esercizio: {{{err}}}" 41 | }, 42 | "no_uncomplete_left": "Tutti gli esercizi successivi all'esercizio attuale sono stati completati", 43 | "cleanup": "Errore durante il ripristino: {{{err}}}", 44 | "notty": "Si prega di eseguire all'interno di un terminale tty-compatibile." 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "FALLIMENTO", 49 | "message": "La tua soluzione a {{{currentExercise.name}}} non è corretta. Riprova!\n" 50 | }, 51 | "pass": { 52 | "title": "SUCCESSO", 53 | "message": "La tua soluzione a {{{currentExercise.name}}} è corretta!" 54 | }, 55 | "notes": { 56 | "compare": "Ecco la soluzione ufficiale in caso si voglia confrontarla con la propria:\n", 57 | "load_error": "ERRORE: Si è verificato un problema nel mostrare il file della soluzione: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "Scrivi '{{appName}}' per mostrare il menu.\n", 62 | "usage": "Uso: {{appName}} {{mode}} la_mia_soluzione.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} progress reset", 66 | "finished": "Evviva! Hai completato tutte le sfide!\n", 67 | "state": "Esercizio {{count}} di {{amount}}", 68 | "remaining": { 69 | "one": "Ti rimane una sola sfida irrisolta.", 70 | "other": "Ti rimangono {{count}} sfide irrisolte." 71 | } 72 | }, 73 | "update": { 74 | "latest_version": "{{name}}**@{{version}}** è già all'ultima versione.\n\n**Congratulazioni!**", 75 | "now": "{{name}}_@{{current}}_ è obsoleto, aggiornamento in corso a **{{latest}}** con \n\n```bash\n{{cmd}}\n```\n" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /i18n/pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mSelecione um exercício e pressione \u001b[3mEnter\u001b[23m para começar", 3 | "menu": { 4 | "credits": "CRÉDITOS", 5 | "exit": "SAIR", 6 | "help": "AJUDA", 7 | "completed": "COMPLETO", 8 | "language": "SELECIONE O IDIOMA (CHOOSE LANGUAGE)", 9 | "cancel": "CANCELAR", 10 | "update": "VERIFIQUE ATUALIZAÇÕES" 11 | }, 12 | "language": { 13 | "_current": "ATUAL", 14 | "de": "Alemão (Deutsch)", 15 | "en": "Inglês (English)", 16 | "fr": "Francês (français)", 17 | "ja": "Japonês (日本語)", 18 | "zh-cn": "Chinês (简体中文)", 19 | "zh-tw": "Taiuanês (繁體中文)", 20 | "es": "Espanhol (Español)", 21 | "pt-br": "Português", 22 | "vi": "Vietnamita (Tiếng Việt)", 23 | "ru": "Russo (Русский)", 24 | "uk": "Ucraniano (Українська)", 25 | "ko": "Coreano (한국어)", 26 | "pl": "Polonês (Polski)", 27 | "nb-no": "Norueguês (Norsk)", 28 | "it": "Italiano (Italiano)", 29 | "sr": "Sérvio (српски)", 30 | "tr": "Turco (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Nenhum exercício ativo. Selecione um do menu.", 35 | "missing": "Não existe o exercício: {{{name}}}", 36 | "unexpected_error": "Não foi possível {{mode}}: {{{err}}}", 37 | "missing_file": "ERRO: {{{exerciseFile}}} não existe!", 38 | "not_a_workshopper": "ERRO: {{{exerciseFile}}} não é um exercício do workshopper.", 39 | "method_not_required": "{{method}} não é necessário para este exercício.", 40 | "preparing": "Erro ao preparar exercício: {{{err}}}", 41 | "loading": "Erro ao carregar o texto do exercício: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Não existem mais exercícios incompletos após o exercício atual", 44 | "cleanup": "Erro de limpeza: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "FALHA", 49 | "message": "Sua solução para {{{currentExercise.name}}} não foi aprovada. Tente novamente!\n" 50 | }, 51 | "pass": { 52 | "title": "SUCESSO", 53 | "message": "Sua solução para {{{currentExercise.name}}} foi aprovada!" 54 | }, 55 | "notes": { 56 | "compare": "Aqui está a solução original, caso você deseje comparar:\n", 57 | "load_error": "ERRO: Houve um problema ao tentar exibir o arquivo de solução: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "Digite '{{appName}}' para mostrar o menu.\n", 62 | "usage": "Uso: {{appName}} {{mode}} minharesposta.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} progresso resetado", 66 | "finished": "Você terminou todos os desafios! Parabéns!\n", 67 | "state": "Exercício {{count}} de {{amount}}", 68 | "remaining": { 69 | "one": "Você tem um desafio restante.", 70 | "other": "Você tem {{count}} desafios restantes." 71 | } 72 | }, 73 | "credits": { 74 | "title": "{{appName}} é trazido até você pelos seguintes hackers dedicados:", 75 | "name": "Nome", 76 | "user": "GitHub Username" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 80 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/nb-no.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mVelg en oppgave og trykk \u001b[3mEnter\u001b[23m for å begynne", 3 | "menu": { 4 | "credits": "TAKK TIL", 5 | "exit": "AVSLUTT", 6 | "help": "HJELP", 7 | "completed": "FULLFØRT", 8 | "language": "VELG SPRÅK (CHOOSE LANGUAGE)", 9 | "cancel": "AVBRYT", 10 | "update": "SE ETTER OPPDATERING" 11 | }, 12 | "language": { 13 | "_current": "GJELDENDE", 14 | "de": "Tysk (Deutsch)", 15 | "en": "Engelsk", 16 | "fr": "Fansk (français)", 17 | "ja": "Japansk (日本語)", 18 | "zh-cn": "Kinesisk (简体中文)", 19 | "zh-tw": "Taivansk (繁體中文)", 20 | "es": "Spansk (Español)", 21 | "pt-br": "Brisiliansk Potugisisk (Português)", 22 | "vi": "Vietnamesisk (Tiếng Việt)", 23 | "ru": "Russisk (Русский)", 24 | "uk": "Ukrainsk (Українська)", 25 | "ko": "Koreansk (한국어)", 26 | "pl": "Polsk (Polski)", 27 | "nb-no": "Norsk (Norsk)", 28 | "it": "Italiensk (Italiano)", 29 | "sr": "Serbisk (српски)", 30 | "tr": "Turkish (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Ingen oppgave valgt. Velg en fra menyen.", 35 | "missing": "Fant ikke oppgaven: {{{name}}}", 36 | "unexpected_error": "Kunne ikke {{mode}}: {{{err}}}", 37 | "missing_file": "FEIL: {{{exerciseFile}}} finnes ikke!", 38 | "not_a_workshopper": "FEIL: {{{exerciseFile}}} er ikke en workshopper oppgave.", 39 | "method_not_required": "{{method}} er ikke nødvendig for denne øvelsen.", 40 | "preparing": "Feil ved oppsett av oppgave: {{{err}}}", 41 | "loading": "Feil ved lasting av oppgave: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Alle oppgaver er utført etter du har gjennomført denne oppgaven", 44 | "cleanup": "Feil under opprydding: {{{err}}}", 45 | "notty": "Vennligst start dette i et tty-kompatibel terminal program." 46 | }, 47 | "solution": { 48 | "fail": { 49 | "title": "FEIL", 50 | "message": "Din løsning på {{{currentExercise.name}}} er ikke godkjent. Prøv en gang til!\n" 51 | }, 52 | "pass": { 53 | "title": "RIKTIG", 54 | "message": "Løsningen din på {{{currentExercise.name}}} er riktig!" 55 | }, 56 | "notes": { 57 | "compare": "Her er den offisielle løsningen i tilfelle du vil sammenligne:\n", 58 | "load_error": "FEIL: Klarte ikke skrive ut løsningsfilene: {{{err}}}" 59 | } 60 | }, 61 | "ui": { 62 | "return": "Skriv '{{appName}}' for å se menyen.\n", 63 | "usage": "Bruk: {{appName}} {{mode}} mysubmission.js" 64 | }, 65 | "progress": { 66 | "reset": "Start {{title}} på nytt", 67 | "finished": "Du har fullført alle oppgavene! Hurra!\n", 68 | "state": "Oppgave {{count}} av {{amount}}", 69 | "remaining": { 70 | "one": "Du har en oppgave igjen.", 71 | "other": "Du har {{count}} oppgaver igjen." 72 | } 73 | }, 74 | "credits": { 75 | "title": "Takket være disse dedikerte hacker'ne har du kunnet bruke {{appName}}:", 76 | "name": "Navn", 77 | "user": "GitHub Brukernavn" 78 | }, 79 | "update": { 80 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 81 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin", 3 | "menu": { 4 | "credits": "CREDITS", 5 | "exit": "EXIT", 6 | "help": "HELP", 7 | "completed": "COMPLETED", 8 | "language": "CHOOSE LANGUAGE", 9 | "cancel": "CANCEL", 10 | "update": "CHECK FOR UPDATE" 11 | }, 12 | "language": { 13 | "_current": "CURRENT", 14 | "de": "German (Deutsch)", 15 | "en": "English", 16 | "fr": "French (français)", 17 | "ja": "Japanese (日本語)", 18 | "zh-cn": "Chinese (简体中文)", 19 | "zh-tw": "Taiwanese (繁體中文)", 20 | "es": "Spanish (Español)", 21 | "pt-br": "Brazilian Portuguese (Português)", 22 | "vi": "Vietnamese (Tiếng Việt)", 23 | "ru": "Russian (Русский)", 24 | "ko": "Korean (한국어)", 25 | "pl": "Polish (Polski)", 26 | "nb-no": "Norwegian (Norsk)", 27 | "it": "Italian (Italiano)", 28 | "uk": "Ukrainian (Українська)", 29 | "sr": "Serbian (српски)", 30 | "fi": "Finnish (suomi)", 31 | "tr": "Turkish (Türkçe)" 32 | }, 33 | "error": { 34 | "exercise": { 35 | "none_active": "No active exercise. Select one from the menu.", 36 | "missing": "No such exercise: {{{name}}}", 37 | "unexpected_error": "Could not {{mode}}: {{{err}}}", 38 | "missing_file": "ERROR: {{{exerciseFile}}} does not exist!", 39 | "not_a_workshopper": "ERROR: {{{exerciseFile}}} is not a workshopper exercise.", 40 | "method_not_required": "{{method}} is not required for this exercise.", 41 | "preparing": "Error preparing exercise: {{{err}}}", 42 | "loading": "Error loading exercise text: {{{err}}}" 43 | }, 44 | "no_uncomplete_left": "There are no incomplete exercises after the current exercise", 45 | "cleanup": "Error cleaning up: {{{err}}}", 46 | "notty": "Please start this in a tty-compatible terminal program." 47 | }, 48 | "solution": { 49 | "fail": { 50 | "title": "FAIL", 51 | "message": "Your solution to {{{currentExercise.name}}} didn't pass. Try again!\n" 52 | }, 53 | "pass": { 54 | "title": "PASS", 55 | "message": "Your solution to {{{currentExercise.name}}} passed!" 56 | }, 57 | "notes": { 58 | "compare": "Here's the official solution in case you want to compare notes:\n", 59 | "load_error": "ERROR: There was a problem printing the solution files: {{{err}}}" 60 | } 61 | }, 62 | "ui": { 63 | "return": "Type '{{appName}}' to show the menu.\n", 64 | "usage": "Usage: {{appName}} {{mode}} mysubmission.js" 65 | }, 66 | "progress": { 67 | "reset": "{{title}} progress reset", 68 | "finished": "You've finished all the challenges! Hooray!\n", 69 | "state": "Exercise {{count}} of {{amount}}", 70 | "remaining": { 71 | "one": "You have one challenge left.", 72 | "other": "You have {{count}} challenges left." 73 | } 74 | }, 75 | "credits": { 76 | "title": "{{appName}} is brought to you by the following dedicated hackers:", 77 | "name": "Name", 78 | "user": "GitHub Username" 79 | }, 80 | "update": { 81 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 82 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /i18n/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mОберіть завдання та натисніть \u001b[3mEnter\u001b[23m щоб почати", 3 | "menu": { 4 | "credits": "АВТОРСТВО", 5 | "exit": "ВИХІД", 6 | "help": "ДОПОМОГА", 7 | "completed": "ВИКОНАНО", 8 | "language": "ОБРАТИ МОВУ (CHOOSE LANGUAGE)", 9 | "cancel": "ВІДМІНИТИ", 10 | "update": "ПЕРЕВІРИТИ ОНОВЛЕННЯ" 11 | }, 12 | "language": { 13 | "_current": "ПОТОЧНА", 14 | "de": "Німецька (Deutsch)", 15 | "en": "Англійська (English)", 16 | "fr": "Французька (français)", 17 | "ja": "Японська (日本語)", 18 | "zh-cn": "Китайська (简体中文)", 19 | "zh-tw": "Тайванська (繁體中文)", 20 | "es": "Іспанська (Español)", 21 | "pt-br": "Бразильська португальська (Português)", 22 | "vi": "В’єтнамська (Tiếng Việt)", 23 | "uk": "Українська (Ukrainian)", 24 | "ru": "Російська (Русский)", 25 | "ko": "Корейська (한국어)", 26 | "pl": "Польська (Polski)", 27 | "nb-no": "Норвeжська (Norsk)", 28 | "it": "Iталійський (Italiano)", 29 | "sr": "Сербська (српски)", 30 | "tr": "турецька (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Завдання не обрано. Скористайтесь меню.", 35 | "missing": "Немає завдання: {{{name}}}", 36 | "unexpected_error": "Помилка {{mode}}: {{{err}}}", 37 | "missing_file": "ПОМИЛКА: {{{exerciseFile}}} не існує!", 38 | "not_a_workshopper": "ПОМИЛКА: {{{exerciseFile}}} не є воркшоппером.", 39 | "method_not_required": "{{method}} не потрібна для цієї вправи.", 40 | "preparing": "Помилка підготовки завдання: {{{err}}}", 41 | "loading": "Помилка завантаження завданя: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Немає невиконаних завдань", 44 | "cleanup": "Помилка скидання: {{{err}}}", 45 | "notty": "Будь ласка, запустіть це в tty-сумісному емуляторі терміналу." 46 | }, 47 | "solution": { 48 | "fail": { 49 | "title": "НЕ ВІРНО", 50 | "message": "Ваш роз’язок {{{currentExercise.name}}} не вірний. Спробуйте знову!\n" 51 | }, 52 | "pass": { 53 | "title": "ВІРНО", 54 | "message": "Ваш розв’язок {{{currentExercise.name}}} вірний!" 55 | }, 56 | "notes": { 57 | "compare": "Це правильний розв’язок завдання, якщо ви бажаєте звіритись:\n", 58 | "load_error": "ПОМИЛКА: Виникла проблема з читанням файлу з правильним розв’язком: {{{err}}}" 59 | } 60 | }, 61 | "ui": { 62 | "return": "Введіть '{{appName}}' для показу меню.\n", 63 | "usage": "Використання: {{appName}} {{mode}} mysubmission.js" 64 | }, 65 | "progress": { 66 | "reset": "{{title}} почати заново", 67 | "finished": "Ви виконали всі завдання! Ура!\n", 68 | "state": "Задача {{count}} з {{amount}}", 69 | "remaining": { 70 | "one": "У вас залишилось одне невиконане завдання.", 71 | "other": "У вас залишилось {{count}} невиконаних завдань." 72 | } 73 | }, 74 | "credits": { 75 | "title": "{{appName}} був створений для вас такими розробниками:", 76 | "name": "Ім’я", 77 | "user": "GitHub Аккаунт" 78 | }, 79 | "update": { 80 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 81 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mElige un ejercicio y presiona \u001b[3mEnter\u001b[23m para empezar", 3 | "menu": { 4 | "credits": "CREDITS", 5 | "exit": "SALIR", 6 | "help": "AYUDA", 7 | "completed": "COMPLETADO", 8 | "language": "ELEGIR IDIOMA (CHOOSE LANGUAGE)", 9 | "cancel": "CANCELAR", 10 | "update": "BUSCAR ACTUALIZACIONES" 11 | }, 12 | "language": { 13 | "_current": "ACTUAL", 14 | "de": "Alemán (Deutsch)", 15 | "en": "Inglés (English)", 16 | "fr": "Francés (français)", 17 | "ja": "Japonés (日本語)", 18 | "zh-cn": "Chino (简体中文)", 19 | "zh-tw": "Idioma Taiwanés (繁體中文)", 20 | "es": "Español", 21 | "pt-br": "Portugués (Português)", 22 | "vi": "Vietnamita (Tiếng Việt)", 23 | "ru": "Ruso (Русский)", 24 | "uk": "Ucranio (Українська)", 25 | "ko": "Coreano (한국어)", 26 | "pl": "Polaco (Polski)", 27 | "nb-no": "Noruego (Norsk)", 28 | "it": "Italiano (Italiano)", 29 | "sr": "Serbio (српски)", 30 | "tr": "Turco (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "No hay un ejercicio activo. Selecciona uno desde el menú.", 35 | "missing": "No se encontró el ejercicio: {{{name}}}", 36 | "unexpected_error": "No se pudo {{mode}}: {{{err}}}", 37 | "missing_file": "ERROR: {{{exerciseFile}}} no existe!", 38 | "not_a_workshopper": "ERROR: {{{exerciseFile}}} no es un ejercicio de workshopper.", 39 | "method_not_required": "{{method}} no es necesario para este ejercicio.", 40 | "preparing": "Error preparando el ejercicio: {{{err}}}", 41 | "loading": "Error cargando el texto del ejercicio: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "No hay ejercicios incompletos después del ejercicio actual.", 44 | "cleanup": "Error limpiando entorno: {{{err}}}" 45 | }, 46 | "solution": { 47 | "fail": { 48 | "title": "FALLO", 49 | "message": "Su solución a {{{currentExercise.name}}} no pasó. Inténtalo de nuevo!\n" 50 | }, 51 | "pass": { 52 | "title": "PASO", 53 | "message": "Su solución a {{{currentExercise.name}}} pasó sin problemas!" 54 | }, 55 | "notes": { 56 | "compare": "Aquí está la solución oficial si desea comparar notas:\n", 57 | "load_error": "ERROR: Hubo un problema mostrando el archivo de solución: {{{err}}}" 58 | } 59 | }, 60 | "ui": { 61 | "return": "Escriba '{{appName}}' para mostrar el menú.\n", 62 | "usage": "Uso: {{appName}} {{mode}} miprograma.js" 63 | }, 64 | "progress": { 65 | "reset": "{{title}} progreso reiniciado", 66 | "finished": "Has finalizado todos los ejercicios.! URRAA!!!\n", 67 | "state": "Ejercicio {{count}} de {{amount}}", 68 | "remaining": { 69 | "one": "Tienes sólo un ejercicio por hacer.", 70 | "other": "Tienes {{count}} ejercicios por hacer." 71 | } 72 | }, 73 | "credits": { 74 | "title": "{{appName}} es traído a usted por los siguientes dedicados hackers:", 75 | "name": "Nombre", 76 | "user": "Nombre de usuario en GitHub" 77 | }, 78 | "update": { 79 | "latest_version": "{{name}}**@{{version}}** ya está ATT y la última versión.\n\n**Congratulations!**", 80 | "now": "{{name}}_@{{current}}_ está fuera de- Dart, la actualización a **{{latest}}** con \n\n```bash\n{{cmd}}\n```\n" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle" : "\u001b[23mSélectionnez un exercice et tapez \u001b[3mEnter\u001b[23m pour démarrer", 3 | "menu": { 4 | "credits": "CRÉDITS", 5 | "exit": "QUITTER", 6 | "help": "AIDE", 7 | "completed": "FAIT", 8 | "language": "CHOISIR LA LANGUE (CHOOSE LANGUAGE)", 9 | "cancel": "ANNULER", 10 | "update": "VÉRIFIER LA MISE À JOUR" 11 | }, 12 | "language": { 13 | "_current": "ACTUEL", 14 | "de": "Allemand (Deutsch)", 15 | "en": "Anglais (English)", 16 | "fr": "Français", 17 | "ja": "Japonais (日本語)", 18 | "zh-cn": "Chinois (简体中文)", 19 | "zh-tw": "Taiwanais (繁體中文)", 20 | "es": "Espagnol (Español)", 21 | "pt-br": "Portugais brésilien (Português)", 22 | "vi": "Vietnamien (Tiếng Việt)", 23 | "ru": "Russe (Русский)", 24 | "ko": "Coréen (한국어)", 25 | "pl": "Polonais (Polski)", 26 | "nb-no": "Norvégien (Norsk)", 27 | "it": "Italien (Italiano)", 28 | "sr": "Serbe (српски)", 29 | "tr": "Turc (Türkçe)" 30 | }, 31 | "error": { 32 | "exercise": { 33 | "none_active": "Aucun exercice en cours. Choisissez-en un dans le menu.", 34 | "missing": "L’exercice {{{name}}} est introuvable.", 35 | "unexpected_error": "Impossible de {{mode}} : {{{err}}}", 36 | "missing_file": "ERREUR : {{{exerciseFile}}} n’existe pas !", 37 | "not_a_workshopper": "ERREUR : {{{exerciseFile}}} n’est pas un exercice d’atelier.", 38 | "method_not_required": "{{method}} est pas nécessaire pour cet exercice.", 39 | "preparing": "Erreur lors de la préparation de l’exercice : {{{err}}}", 40 | "loading": "Erreur au chargement du texte de l’exercice : {{{err}}}" 41 | }, 42 | "no_uncomplete_left": "Il ne reste aucun exercice à faire après celui-ci.", 43 | "cleanup": "Erreur au nettoyage : {{{err}}}" 44 | }, 45 | "solution": { 46 | "fail": { 47 | "title": "RATÉ", 48 | "message": "Votre solution pour {{{currentExercise.name}}} ne fonctionne pas. Réessayez !\n" 49 | }, 50 | "pass": { 51 | "title": "RÉUSSI", 52 | "message": "Votre solution pour {{{currentExercise.name}}} fonctionne !" 53 | }, 54 | "notes": { 55 | "compare": "Voici la solution officielle, si vous voulez comparer :\n", 56 | "load_error": "ERREUR : Un problème est survenu durant l’affichage des fichiers de solution : {{{err}}}" 57 | } 58 | }, 59 | "ui": { 60 | "return": "Tapez '{{appName}}' pour afficher le menu.\n", 61 | "usage": "Utilisation : {{appName}} {{mode}} ma-tentative.js" 62 | }, 63 | "progress": { 64 | "reset": "La progression de {{title}} a été réinitialisée", 65 | "finished": "Vous avez terminé tous les défis ! Bravo !\n", 66 | "state": "Exercice {{count}} sur {{amount}}", 67 | "remaining": { 68 | "one": "Il vous reste un défi.", 69 | "other": "Il vous reste {{count}} défis." 70 | } 71 | }, 72 | "credits": { 73 | "title": "{{appName}} vous est proprosé par les codeurs dévoués suivants:", 74 | "name": "Nom", 75 | "user": "Compte GitHub" 76 | }, 77 | "update": { 78 | "latest_version": "{{name}}**@{{version}}** est déjà à la dernière version.\n\n**Félicitations à vous!**", 79 | "now": "{{name}}_@{{current}}_ est de jour en dehors, la mise à jour à **{{latest}}** avec \n\n```bash\n{{cmd}}\n```\n" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu": { 3 | "exit": "VERLASSEN", 4 | "help": "HILFE", 5 | "completed": "ABGESCHLOSSEN", 6 | "language": "SPRACHE WÄHLEN (CHOOSE LANGUAGE)", 7 | "cancel": "ABBRECHEN", 8 | "update": "AUF UPDATE PRÜFEN" 9 | }, 10 | "language": { 11 | "_current": "AKTUELL", 12 | "de": "Deutsch", 13 | "en": "Englisch (English)", 14 | "fr": "Französisch (français)", 15 | "ja": "Japanisch (日本語)", 16 | "zh-cn": "Chinesisch (简体中文)", 17 | "zh-tw": "Taiwanesisch (繁體中文)", 18 | "es": "Spanisch (Español)", 19 | "pt-br": "Brasilianisches Portugiesisch (Português)", 20 | "vi": "Vietnamesisch (Tiếng Việt)", 21 | "ru": "Russisch (Русский)", 22 | "ko": "Koreanisch (한국어)", 23 | "pl": "Polnisch (Polski)", 24 | "nb-no": "Norwegisch (Norsk)", 25 | "it": "Italienisch (Italiano)", 26 | "sr": "Serbisch (српски)", 27 | "tr": "Türkisch (Türkçe)" 28 | }, 29 | "error": { 30 | "exercise": { 31 | "none_active": "Keine aktive Aufgabe. Wähle eine aus dem Menü.", 32 | "missing": "Aufgabe {{{name}}} fehlt.", 33 | "unexpected_error": "{{mode}} konnte nicht ausgeführt werden: {{{err}}}", 34 | "missing_file": "FEHLER: {{{exerciseFile}}} existiert nicht!", 35 | "not_a_workshopper": "FEHLER: {{{exerciseFile}}} ist keine workshopper Aufgabe.", 36 | "method_not_required": "{{method}} ist nicht notwendig für diese Aufgabe.", 37 | "preparing": "Fehler bei der Vorbereitung der Aufgabe: {{{err}}}", 38 | "loading": "Fehler beim Laden der Aufgabe: {{{err}}}" 39 | }, 40 | "no_uncomplete_left": "Es gibt keine unvollständigen Aufgaben nach der aktuellen Aufgabe", 41 | "cleanup": "Fehler beim Aufräumen: {{{err}}}", 42 | "notty": "Bitte führe das Programm in einem Terminal (tty) aus." 43 | }, 44 | "solution": { 45 | "fail": { 46 | "title": "NICHT BESTANDEN", 47 | "message": "Deine Lösung für {{{currentExercise.name}}} hat den Test nicht bestanden. Versuche es erneut!\n" 48 | }, 49 | "pass": { 50 | "title": "BESTANDEN", 51 | "message": "Deine Lösung für {{{currentExercise.name}}} hat den Test bestanden!" 52 | }, 53 | "notes": { 54 | "compare": "Hier ist die offizielle Lösung, falls du noch einmal vergleichen möchtest:\n", 55 | "load_error": "FEHLER: Es gab einen Fehler bei der Ausgabe der Lösung: {{{err}}}" 56 | } 57 | }, 58 | "ui": { 59 | "return": "Tippe '{{appName}}' um das Menü zu zeigen.\n", 60 | "usage": "Verwendung: {{appName}} {{mode}} meineabgabe.js" 61 | }, 62 | "progress": { 63 | "reset": "{{title}} Fortschritt zurückgesetzt", 64 | "finished": "Du hast alle Herausforderungen gemeistert! Hurra!\n", 65 | "state": "Aufgabe {{count}} von {{amount}}", 66 | "remaining": { 67 | "one": "Du hast eine Herausforderung übrig.", 68 | "other": "Du hast {{count}} Herausforderungen übrig." 69 | } 70 | }, 71 | "credits": { 72 | "title": "{{appName}} wurde von den folgenden, hart-arbeitenden Hackern erstellt:", 73 | "name": "Name", 74 | "user": "GitHub User" 75 | }, 76 | "update": { 77 | "latest_version": "{{name}}**@{{version}}** ist die neueste Version.\n\n**Super!**", 78 | "now": "{{name}}_@{{current}}_ ist veraltet. Starte automatisches update auf **{{latest}}** mit dem Befehl \n\n```bash\n{{cmd}}\n```\n" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mWybierz ćwiczenie i wciśnij \u001b[3mEnter\u001b[23m aby rozpocząć", 3 | "menu": { 4 | "credits": "AUTORZY", 5 | "exit": "WYJDŹ", 6 | "help": "POMOC", 7 | "completed": "UKOŃCZONO", 8 | "language": "WYBIERZ JĘZYK (CHOOSE LANGUAGE)", 9 | "cancel": "ANULUJ", 10 | "update": "SPRAWDŹ AKTUALIZACJE" 11 | }, 12 | "language": { 13 | "_current": "WYBRANO", 14 | "de": "Niemiecki (Deutsch)", 15 | "en": "Angielski (English)", 16 | "fr": "Francuski (français)", 17 | "ja": "Japoński (日本語)", 18 | "zh-cn": "Chiński (简体中文)", 19 | "zh-tw": "Tajwański (繁體中文)", 20 | "es": "Hiszpański (Español)", 21 | "pt-br": "Brazylijski Portugalski (Português)", 22 | "vi": "Wietnamski (Tiếng Việt)", 23 | "ru": "Rosyjski (Русский)", 24 | "uk": "Ukraiński (Українська)", 25 | "ko": "Koreański (한국어)", 26 | "pl": "Polski", 27 | "nb-no": "Norweski (Norsk)", 28 | "it": "Włoski (Italiano)", 29 | "sr": "serbski (српски)", 30 | "tr": "Turecki (Türkçe)" 31 | }, 32 | "error": { 33 | "exercise": { 34 | "none_active": "Brak aktywnego ćwiczenia. Wybierz jakieś z menu.", 35 | "missing": "Brak ćwiczenia: {{{name}}}", 36 | "unexpected_error": "Nie można {{mode}}: {{{err}}}", 37 | "missing_file": "BŁĄD: {{{exerciseFile}}} nie istnieje!", 38 | "not_a_workshopper": "BŁĄD: {{{exerciseFile}}} nie jest poprawnym ćwiczeniem.", 39 | "method_not_required": "{{method}} nie jest wymagane do tego ćwiczenia.", 40 | "preparing": "Błąd podczas przygotowywania ćwiczenia: {{{err}}}", 41 | "loading": "Błąd podczas wczytywania tekstu ćwiczenia: {{{err}}}" 42 | }, 43 | "no_uncomplete_left": "Nie ma żadnych nieukończonych ćwiczeń po obecnym", 44 | "cleanup": "Błąd podczas sprzątania: {{{err}}}", 45 | "notty": "Uruchom to porszę w terminalu kompatybilnym z tty." 46 | }, 47 | "solution": { 48 | "fail": { 49 | "title": "PORAŻKA", 50 | "message": "Twoje rozwiązanie dla {{{currentExercise.name}}} nie zdało egzaminu. Spróbuj ponownie!\n" 51 | }, 52 | "pass": { 53 | "title": "SUKCES", 54 | "message": "Twoje rozwiązanie dla {{{currentExercise.name}}} jest prawidłowe!" 55 | }, 56 | "notes": { 57 | "compare": "Oto oficjalne rozwiązanie dla porównania:\n", 58 | "load_error": "BŁĄD: Błąd podczas wczytywania plików rozwiązania: {{{err}}}" 59 | } 60 | }, 61 | "ui": { 62 | "return": "Wpisz '{{appName}}' aby pokazać menu.\n", 63 | "usage": "Sposób użycia: {{appName}} {{mode}} rozwiązanie.js" 64 | }, 65 | "progress": { 66 | "reset": "wyzerowano postępy dla {{title}}", 67 | "finished": "Udało Ci się wykonać wszystkie ćwiczenia! Hurra!\n", 68 | "state": "Ćwiczenie {{count}} z {{amount}}", 69 | "remaining": { 70 | "one": "Pozostało Ci jedno ćwiczenie.", 71 | "other": "Pozostało Ci {{count}} ćwiczeń." 72 | } 73 | }, 74 | "credits": { 75 | "title": "{{appName}} jest w Twoich rękach dzięki staraniom następujących hakerów:", 76 | "name": "Imię i Nazwisko", 77 | "user": "GitHub" 78 | }, 79 | "update": { 80 | "latest_version": "{{name}}**@{{version}}** is already at the latest version.\n\n**Congratulations!**", 81 | "now": "{{name}}_@{{current}}_ is out-of-date, updating to **{{latest}}** with \n\n```bash\n{{cmd}}\n```\n" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [7.0.0](https://github.com/workshopper/workshopper-adventure/compare/v6.1.1...v7.0.0) (2020-10-09) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * **deps:** changes in simple-terminal-menu might affect the ui/output of the workshopper. see https://github.com/martinheidegger/simple-terminal-menu/blob/master/CHANGELOG.md 11 | 12 | * **deps:** update to latest version of simple-terminal-menu ([#114](https://github.com/workshopper/workshopper-adventure/issues/114)) ([d005cfb](https://github.com/workshopper/workshopper-adventure/commit/d005cfb0ad508f2d11a59bcf30aae6e3ca10f975)) 13 | 14 | ### [6.1.1](https://github.com/workshopper/workshopper-adventure/compare/v6.1.0...v6.1.1) (2020-09-25) 15 | 16 | ## [6.1.0](https://github.com/workshopper/workshopper-adventure/compare/v6.0.4...v6.1.0) (2019-12-04) 17 | 18 | 19 | ### Features 20 | 21 | * **util/idFromName:** allow customize character for white space ([d1d925f](https://github.com/workshopper/workshopper-adventure/commit/d1d925f)) 22 | 23 | 24 | ## [6.0.4](https://github.com/workshopper/workshopper-adventure/compare/v6.0.3...v6.0.4) (2018-07-07) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * chalk dependency doesn’t have stripColor support anymore. ([9396bde](https://github.com/workshopper/workshopper-adventure/commit/9396bde)) 30 | 31 | 32 | 33 | 34 | ## [6.0.3](https://github.com/workshopper/workshopper-adventure/compare/v6.0.2...v6.0.3) (2017-10-19) 35 | 36 | 37 | 38 | 39 | ## [6.0.2](https://github.com/workshopper/workshopper-adventure/compare/v6.0.1...v6.0.2) (2017-04-04) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * **output:** run shows the error message twice, once too many ([bb1cd2d](https://github.com/workshopper/workshopper-adventure/commit/bb1cd2d)) 45 | * **output:** The i18n codes now are used correctly in the `next` command. ([c5ba41b](https://github.com/workshopper/workshopper-adventure/commit/c5ba41b)) 46 | * **style:** update for standard v9 ([18b55d3](https://github.com/workshopper/workshopper-adventure/commit/18b55d3)) 47 | 48 | 49 | 50 | 51 | ## [6.0.1](https://github.com/workshopper/workshopper-adventure/compare/v6.0.0...v6.0.1) (2017-02-19) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **package:** workshopper-exercise is not used in a test and shouldnt ever be used ([e09975c](https://github.com/workshopper/workshopper-adventure/commit/e09975c)) 57 | 58 | 59 | 60 | 61 | # [6.0.0](https://github.com/workshopper/workshopper-adventure/compare/v5.1.8...v6.0.0) (2017-02-19) 62 | 63 | 64 | ### Features 65 | 66 | * **docs:** started a change log, better late than never? ([c47fae9](https://github.com/workshopper/workshopper-adventure/commit/c47fae9)) 67 | * **i18n:** Updated i18n-core to latest version ([e4ac2c6](https://github.com/workshopper/workshopper-adventure/commit/e4ac2c6)) 68 | 69 | 70 | ### BREAKING CHANGES 71 | 72 | * i18n: i18n-core has been updated to work without mustache & sprint and also had some other breaking changes which makes this return a breaking change. 73 | 74 | 75 | 76 | # Change Log 77 | 78 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 79 | -------------------------------------------------------------------------------- /i18n/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mValitse harjoitustehtävä ja paina \u001b[3mEnter\u001b[23m aloittaaksesi", 3 | "menu": { 4 | "credits": "TEKIJÄT", 5 | "exit": "POISTU", 6 | "help": "APUA", 7 | "completed": "VALMIS", 8 | "language": "VALITSE KIELI", 9 | "cancel": "PERUUTA", 10 | "update": "HAE PÄIVITYKSIÄ" 11 | }, 12 | "language": { 13 | "_current": "NYKYINEN", 14 | "de": "Saksa (Deutsch)", 15 | "en": "Englanti (English)", 16 | "fr": "Ranska (français)", 17 | "ja": "Japani (日本語)", 18 | "zh-cn": "Mannerkiina (简体中文)", 19 | "zh-tw": "Taiwanin kiina (繁體中文)", 20 | "es": "Espanja (Español)", 21 | "pt-br": "Brasilian portugali (Português)", 22 | "vi": "Vietnam (Tiếng Việt)", 23 | "ru": "Venäjä (Русский)", 24 | "ko": "Korea (한국어)", 25 | "pl": "Puola (Polski)", 26 | "nb-no": "Norja (Norsk)", 27 | "it": "Italia (Italiano)", 28 | "uk": "Ukraina (Українська)", 29 | "sr": "Serbia (српски)", 30 | "fi": "Suomi", 31 | "tr": "Turkki (Türkçe)" 32 | }, 33 | "error": { 34 | "exercise": { 35 | "none_active": "Ei aktiivista harjoitustehtävää. Valitse tehtävä valikosta.", 36 | "missing": "Harjoitusta {{{name}}} ei löydy", 37 | "unexpected_error": "Ei voinut toteuttaa {{mode}}: {{{err}}}", 38 | "missing_file": "VIRHE: tiedostoa {{{exerciseFile}}} ei ole!", 39 | "not_a_workshopper": "VIRHE: {{{exerciseFile}}} ei ole workshopperin tunnistama harjoitustehtävä.", 40 | "method_not_required": "Menetelmää {{method}} ei tarvita tätä harjoitustehtävää varten.", 41 | "preparing": "Virhe valmistellessa harjoitustehtävää {{{err}}}", 42 | "loading": "Virhe ladattaessa harjoitustehtävän {{{err}}} tietoja" 43 | }, 44 | "no_uncomplete_left": "Nykyisen harjoitustehtävän jälkeen ei enää ole tekemättömiä tehtäviä", 45 | "cleanup": "Virhe siivottaessa tietoja: {{{err}}}", 46 | "notty": "Voisitko käynnistää tämän pääteohjelmassa, joka tukee valikoita." 47 | }, 48 | "solution": { 49 | "fail": { 50 | "title": "VÄÄRIN", 51 | "message": "Ratkaisusi tehtävään {{{currentExercise.name}}} ei ollut oikea. Yritä uudelleen!\n" 52 | }, 53 | "pass": { 54 | "title": "OIKEIN", 55 | "message": "Ratkaisusi tehtävään {{{currentExercise.name}}} on oikea!" 56 | }, 57 | "notes": { 58 | "compare": "Tässä on malliratkaisu vertailun vuoksi:\n", 59 | "load_error": "VIRHE: Malliratkaisutiedoston tulostus epäonnistui: {{{err}}}" 60 | } 61 | }, 62 | "ui": { 63 | "return": "Kirjoita '{{appName}}' nähdäksesi valikon.\n", 64 | "usage": "Käyttö: {{appName}} {{mode}} tehtava.js" 65 | }, 66 | "progress": { 67 | "reset": "{{title}} merkitty tekemättömäksi", 68 | "finished": "Onnittelut! Olet ratkaissut kaikki tehtävät!\n", 69 | "state": "{{count}} tehtävää tehty {{amount}}:sta", 70 | "remaining": { 71 | "one": "Sinulla on yksi harjoitustehtävä jäljellä.", 72 | "other": "Sinulla on {{count}} harjoitustehtävää jäljellä." 73 | } 74 | }, 75 | "credits": { 76 | "title": "Sovelluksen {{appName}} tarjoavat sinulle seuraavat sovelluskehittäjät:", 77 | "name": "Name", 78 | "user": "GitHub Username" 79 | }, 80 | "update": { 81 | "latest_version": "{{name}}**@{{version}}** on jo uusin version.\n\n**Onnittelut!**", 82 | "now": "{{name}}_@{{current}}_ on vanhentunut, päivitetään versioon **{{latest}}** komennolla \n\n```bash\n{{cmd}}\n```\n" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /i18n/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "subtitle": "\u001b[23mBir alıştırma seçiniz ve başlamak için \u001b[3mEnter\u001b[23m tuşuna basınız", 3 | "menu": { 4 | "credits": "KATKIDA BULUNANLAR", 5 | "exit": "ÇIKIŞ", 6 | "help": "YARDIM", 7 | "completed": "TAMAMLANDI", 8 | "language": "DİL SEÇ", 9 | "cancel": "İPTAL", 10 | "update": "GÜNCELLEME" 11 | }, 12 | "language": { 13 | "_current": "SEÇİLİ", 14 | "de": "Almanca (Deutsch)", 15 | "en": "İngilizce (English)", 16 | "fr": "Fransızca (français)", 17 | "ja": "Japonca (日本語)", 18 | "zh-cn": "Çinçe (简体中文)", 19 | "zh-tw": "Tayvanca (繁體中文)", 20 | "es": "İspanyolca (Español)", 21 | "pt-br": "Brazilya Portekizcesi (Português)", 22 | "vi": "Vietnam Dili (Tiếng Việt)", 23 | "ru": "Rusça (Русский)", 24 | "ko": "Korece (한국어)", 25 | "pl": "Lehçe (Polski)", 26 | "nb-no": "Norveççe (Norsk)", 27 | "it": "İtalyanca (Italiano)", 28 | "uk": "Ukraynaca (Українська)", 29 | "sr": "Sırpça Serbian (српски)", 30 | "fi": "Fince (suomi)", 31 | "tr": "Türkçe" 32 | }, 33 | "error": { 34 | "exercise": { 35 | "none_active": "Aktif alıştırma yok. Menüden bir tane seçiniz.", 36 | "missing": "Böyle bir alıştırma bulunamadı: {{{name}}}", 37 | "unexpected_error": "Beklenmedik Hata {{mode}}: {{{err}}}", 38 | "missing_file": "HATA: {{{exerciseFile}}} bulunamadı!", 39 | "not_a_workshopper": "HATA: {{{exerciseFile}}}, workshopper alıştırması değil.", 40 | "method_not_required": "{{method}}, bu alıştırma için gerekli değil.", 41 | "preparing": "Alıştırma hazırlanırken hata oluştu: {{{err}}}", 42 | "loading": "Alıştırma metni yüklenirken hata oluştu: {{{err}}}" 43 | }, 44 | "no_uncomplete_left": "Şu an ki alıştırmadan sonra yapılmamış alıştırma bulunmamaktadır", 45 | "cleanup": "Temizleme hatası: {{{err}}}", 46 | "notty": "Lütfen, bunu tty uyumlu bir konsol uygulamasında başlatınız." 47 | }, 48 | "solution": { 49 | "fail": { 50 | "title": "BAŞARISIZ", 51 | "message": "{{{currentExercise.name}}} alıştırması için çözümünüz doğru değil. Tekrar deneyiniz!\n" 52 | }, 53 | "pass": { 54 | "title": "BAŞARILI", 55 | "message": "{{{currentExercise.name}}} alıştırması için çözümünüz başarılı!" 56 | }, 57 | "notes": { 58 | "compare": "Bizim çözümümüz ile karşılaştırmak isterseniz eğer, buyrun, bizim çözümümüz:\n", 59 | "load_error": "HATA: Çözüm dosyası yazdırılırken hata oluştu: {{{err}}}" 60 | } 61 | }, 62 | "ui": { 63 | "return": "'{{appName}}' yazarak menüyü görüntüleyebilirsiniz.\n", 64 | "usage": "Kullanım: {{appName}} {{mode}} benimcozumum.js" 65 | }, 66 | "progress": { 67 | "reset": "{{title}} ilerlemeyi sıfırla", 68 | "finished": "Tüm meydan okumaları bitirdin! Yaşasın!\n", 69 | "state": "{{amount}} alıştırmadan {{count}}. alıştırmadasın", 70 | "remaining": { 71 | "one": "Geriye bir meydan okuma kaldı.", 72 | "other": "Geriye {{count}} adet meydan okuma kaldı." 73 | } 74 | }, 75 | "credits": { 76 | "title": "{{appName}} uygulaması aşağıdaki adanmış geliştiriciler tarafından hayata geçirildi:", 77 | "name": "İsim", 78 | "user": "GitHub Kullanıcı Adı" 79 | }, 80 | "update": { 81 | "latest_version": "{{name}}**@{{version}}** uygulaması en son sürümüne sahip.\n\n**Tebrikler!**", 82 | "now": "{{name}}_@{{current}}_ uygulaması is güncel değil, **{{latest}}** sürümüne yükselmek için şu komutu kullanabilirsin; \n\n```bash\n{{cmd}}\n```\n" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshopper-Adventure 2 | 3 | **A flexible terminal workshop runner framework** 4 | 5 | [![Join the chat at https://gitter.im/nodeschool/workshoppers](https://badges.gitter.im/nodeschool/workshoppers.svg)](https://gitter.im/nodeschool/workshoppers) [![Build Status](https://api.travis-ci.org/workshopper/workshopper-adventure.svg?branch=master)](https://travis-ci.org/workshopper/workshopper-adventure) [![Dependency Status](https://david-dm.org/workshopper/workshopper-adventure.svg)](https://david-dm.org/workshopper/workshopper-adventure) 6 | 7 | 8 | [![NPM](https://nodei.co/npm/workshopper-adventure.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/workshopper-adventure/) [![NPM](https://nodei.co/npm-dl/workshopper-adventure.png?months=3&height=3)](https://nodei.co/npm/workshopper-adventure/) 9 | 10 | ![Learn You The Node.js For Much Win!](https://raw.github.com/rvagg/learnyounode/master/learnyounode.png) 11 | 12 | **Workshopper** was used by **[learnyounode](https://github.com/rvagg/learnyounode)**, and other Node.js command-line workshop applications. 13 | 14 | **Adventure** was used by **[javascripting](https://github.com/sethvincent/javascripting)**, and other Node.js command-line workshop applications. 15 | 16 | **Workshopper-Adventure** allows you to create a workshop written 17 | like either of those frameworks! 18 | 19 | [@linclark](https://github.com/linclark) has written a good introduction on creating your own workshop, available [here](https://github.com/linclark/lin-clark.com/blob/master/content/blog/2014/07/01/authoring-nodejs-workshopper-lessons.md). 20 | 21 | ## High-level overview 22 | 23 | Workshopper-Adventure is essentially a *test-runner* with a fancy terminal UI. The Workshopper package itself is largely concerned with the menu system and some of the basic command-line parsing. Much of the work for executing the exercise validation is handled by [workshopper-exercise](http://github.com/rvagg/workshopper-exercise). 24 | 25 | 26 | ### Contributors 27 | 28 | workshopper is proudly brought to you by the following hackers: 29 | 30 | * [@rvagg](https://github.com/rvagg) 31 | * [@substack](https://github.com/substack) 32 | * [@maxogden](https://github.com/maxogden) 33 | * [@cjb](https://github.com/cjb) 34 | * [@wpreul](https://github.com/wpreul) 35 | * [@davglass](https://github.com/davglass) 36 | * [@Rowno](https://github.com/Rowno) 37 | * [@wilmoore](https://github.com/wilmoore) 38 | * [@evilpacket](https://github.com/evilpacket) 39 | * [@bobholt](https://github.com/bobholt) 40 | * [@jessmartin](https://github.com/jessmartin) 41 | * [@braz](https://github.com/braz) 42 | * [@timoxley](https://github.com/timoxley) 43 | * [@dominictarr](https://github.com/dominictarr) 44 | * [@dominhhai](https://github.com/dominhhai) 45 | * [@minatu2d](https://github.com/minatu2d) 46 | * [@wewark](https://github.com/wewark) 47 | 48 | #### Maintainers 49 | 50 | * [@martinheidegger](https://github.com/martinheidegger) 51 | 52 | 53 | ## License 54 | 55 | **Workshopper-Adventure** is Copyright (c) 2015 Martin Heidegger [@martinheidegger](https://github.com/martinheidegger) and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. 56 | 57 | it is originally a fork of **Workshopper** 58 | 59 | **Workshopper** is Copyright (c) 2013-2015 Rod Vagg [@rvagg](https://twitter.com/rvagg) and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. 60 | 61 | **Workshopper-Adventure** builds on the excellent work by [@substack](https://github.com/substack) and [@maxogden](https://github.com/maxogden) who created **[stream-adventure](https://github.com/substack/stream-adventure)** which serves as the original foundation for **Workshopper** and **learnyounode**. Portions of **Workshopper** may also be Copyright (c) 2013 [@substack](https://github.com/substack) and [@maxogden](https://github.com/maxogden) given that it builds on their original code. 62 | -------------------------------------------------------------------------------- /i18n.js: -------------------------------------------------------------------------------- 1 | const i18n = require('i18n-core') 2 | const i18nFs = require('i18n-core/lookup/fs') 3 | const i18nChain = require('i18n-core/lookup/chain') 4 | const i18nExtend = require('i18n-core/lookup/extend') 5 | const path = require('path') 6 | const UNDERLINE = 'Underline' 7 | const util = require('./util') 8 | const stripColor = require('strip-ansi') 9 | 10 | function commandify (s) { 11 | return String(s).toLowerCase().replace(/\s+/g, '-') 12 | } 13 | 14 | function chooseLang (globalStorage, appStorage, defaultLang, availableLangs, lang) { 15 | if (!!lang && typeof lang !== 'string') { 16 | throw new Error('Please supply a language. Available languages are: ' + availableLangs.join(', ')) 17 | } 18 | 19 | if (lang) { 20 | lang = lang.replace(/_/g, '-').toLowerCase() 21 | } 22 | 23 | if (availableLangs.indexOf(defaultLang) === -1) { 24 | throw new Error('The default language "' + defaultLang + ' is not one of the available languages?! Available languages are: ' + availableLangs.join(', ')) 25 | } 26 | 27 | if (lang && availableLangs.indexOf(lang) === -1) { 28 | 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.') 29 | } 30 | 31 | var data = ((appStorage && appStorage.get('lang')) || globalStorage.get('lang') || {}) 32 | 33 | if (availableLangs.indexOf(data.selected) === -1) { 34 | // The stored data is not available so lets use one of the other languages 35 | data.selected = lang || defaultLang 36 | } else { 37 | data.selected = lang || data.selected || defaultLang 38 | } 39 | globalStorage.save('lang', data) 40 | if (appStorage) { 41 | appStorage.save('lang', data) 42 | } 43 | 44 | return data.selected 45 | } 46 | 47 | module.exports = { 48 | init: function (options, globalStorage, appStorage) { 49 | var lookup = i18nChain( 50 | options.appDir ? i18nFs(path.resolve(options.appDir, './i18n')) : null 51 | , i18nFs(path.resolve(__dirname, './i18n')) 52 | ) 53 | var root = i18n(lookup) 54 | var choose = chooseLang.bind(null, globalStorage, appStorage, options.defaultLang, options.languages) 55 | var lang = choose(null) 56 | var translator = root.section(lang, true) 57 | // TODO: _excercises is unused... is this ok? 58 | // eslint-disable-next-line 59 | var result = i18n(i18nExtend(translator, function (key) { 60 | if (options[key]) { 61 | return options[key] 62 | } 63 | 64 | // legacy -- start 65 | if (key === 'title') { 66 | return options.name.toUpperCase() 67 | } 68 | 69 | if (key === 'appName' || key === 'appname' || key === 'ADVENTURE_NAME') { 70 | return options.name 71 | } 72 | 73 | if (key === 'rootdir') { 74 | return options.appDir 75 | } 76 | 77 | if (key === 'COMMAND' || key === 'ADVENTURE_COMMAND') { 78 | return commandify(options.name) 79 | // legacy -- end 80 | } 81 | 82 | var exercisePrefix = 'exercise.' 83 | if (key.indexOf(exercisePrefix) === 0) { 84 | return key.substr(exercisePrefix.length) 85 | } 86 | if (key.length > UNDERLINE.length) { 87 | var end = key.length - UNDERLINE.length 88 | if (key.indexOf(UNDERLINE) === end) { 89 | return util.repeat('\u2500', stripColor(result.__(key.substr(0, end))).length + 2) 90 | } 91 | } 92 | })) 93 | 94 | root.fallback = function (key) { 95 | return '?' + key + '?' 96 | } 97 | result.change = function (lng) { 98 | lang = choose(lng) 99 | translator.changeSection(lang) 100 | } 101 | result.extend = function (obj) { 102 | return i18n(i18nExtend(result, function (key) { 103 | return obj[key] 104 | })) 105 | } 106 | result.lang = function () { 107 | return lang 108 | } 109 | return result 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /adventure.js: -------------------------------------------------------------------------------- 1 | const inherits = require('util').inherits 2 | 3 | /* jshint -W079 */ 4 | const Core = require('./index') 5 | const error = require('./lib/print').error 6 | /* jshint +W079 */ 7 | 8 | function legacyCommands (item) { 9 | if (!item.aliases) { 10 | item.aliases = [] 11 | } 12 | if (item && item.name) { 13 | item.aliases.unshift(item.name) 14 | } 15 | return item 16 | } 17 | 18 | function LegacyAdventure (options) { 19 | if (!(this instanceof LegacyAdventure)) { 20 | return new LegacyAdventure(options) 21 | } 22 | if (typeof options === 'string') { 23 | options = { name: options } 24 | } 25 | if (!options) { 26 | options = {} 27 | } 28 | if (typeof options !== 'object') { 29 | return error('You need to provide an options object') 30 | } 31 | if (!options.commands) { 32 | options.commands = options.menuItems 33 | } 34 | if (options.commands) { 35 | options.commands = options.commands.map(legacyCommands) 36 | } 37 | if (options.modifiers) { 38 | options.modifiers = options.modifiers.map(legacyCommands) 39 | } 40 | 41 | if (options.helpFile) { 42 | options.help = { file: options.helpFile } 43 | } 44 | 45 | if (!options.footer) { 46 | if (options.footerFile) { 47 | options.footer = { file: options.footerFile } 48 | } 49 | } 50 | 51 | if (!options.defaultOutputType) { 52 | options.defaultOutputType = 'txt' 53 | } 54 | 55 | if (options.hideSolutions === undefined) { 56 | options.hideSolutions = true 57 | } 58 | 59 | if (options.hideRemaining === undefined) { 60 | options.hideRemaining = true 61 | } 62 | 63 | // an `onComplete` hook function *must* call the callback given to it when it's finished, async or not 64 | if (typeof options.onComplete === 'function') { 65 | this.onComplete = options.onComplete 66 | } 67 | Core.call(this, options) 68 | 69 | if (options.execute === 'now') { 70 | this.execute(process.argv.slice(2)) 71 | } else if (options.execute === 'immediatly') { 72 | setImmediate(this.execute.bind(this, process.argv.slice(2))) 73 | } 74 | 75 | // backwards compatibility support 76 | this.__defineGetter__('title', this.__.bind(this, 'title')) 77 | this.__defineGetter__('subtitle', this.__.bind(this, 'subtitle')) 78 | this.__defineGetter__('name', this.__.bind(this, 'name')) 79 | this.__defineGetter__('appName', this.__.bind(this, 'name')) 80 | this.__defineGetter__('appname', this.__.bind(this, 'name')) 81 | this.__defineGetter__('lang', this.i18n.lang.bind(this.i18n, 'lang')) 82 | this.__defineGetter__('width', function () { return this.menuFactory.options.width }.bind(this)) 83 | this.__defineGetter__('helpFile', function () { return this.options.helpFile }.bind(this)) 84 | this.__defineGetter__('footer', function () { return this.options.footer }.bind(this)) 85 | this.__defineGetter__('defaultLang', function () { return this.options.defaultLang }.bind(this)) 86 | this.__defineGetter__('languages', function () { return this.options.languages }.bind(this)) 87 | this.__defineGetter__('globalDataDir', function () { return this.globalStorage.dir }.bind(this)) 88 | this.__defineGetter__('dataDir', function () { return this.appStorage.dir }.bind(this)) 89 | this.__defineGetter__('datadir', function () { return this.appStorage.dir }.bind(this)) // adventure 90 | this.__defineGetter__('appDir', function () { return this.options.appDir }.bind(this)) 91 | this.__defineGetter__('exerciseDir', function () { return this.options.exerciseDir }.bind(this)) 92 | this.__defineGetter__('current', function () { return this.appStorage.get('current') }.bind(this)) 93 | this.__defineGetter__('_adventures', function () { return this.exercises }.bind(this)) 94 | this.__defineGetter__('state', function () { 95 | return { 96 | completed: this.appStorage.get('completed'), 97 | current: this.appStorage.get('current') 98 | } 99 | }) 100 | } 101 | 102 | inherits(LegacyAdventure, Core) 103 | LegacyAdventure.prototype.processResult = function (result, stream) { 104 | if (result) { 105 | stream.append(['```', result, '```']) 106 | } 107 | return stream 108 | } 109 | 110 | module.exports = LegacyAdventure 111 | -------------------------------------------------------------------------------- /lib/print.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const colorsTmpl = require('colors-tmpl') 4 | const through2 = require('through2') 5 | const split = require('split') 6 | const inherits = require('util').inherits 7 | const CombinedStream = require('combined-stream-wait-for-it') 8 | const StrToStream = require('string-to-stream') 9 | 10 | function getText (i18n, contents) { 11 | contents = colorsTmpl(contents) 12 | .replace(/\{+([^{}]+)\}+/gi, function (match, k) { 13 | var numPart = /^([^#]*)#([^#]+)$/.exec(k) 14 | if (numPart) { 15 | k = numPart[1] 16 | return i18n.has(k) ? getText(i18n, i18n.__n(k, parseFloat(numPart[2]))) : match 17 | } else { 18 | return i18n.has(k) ? getText(i18n, i18n.__(k)) : match 19 | } 20 | }) 21 | .replace(/\$([A-Z_]+)/g, function (match, k) { 22 | return i18n.has(k) ? getText(i18n, i18n.__(k)) : ('$' + k) 23 | }) 24 | 25 | if (i18n.has('appDir')) { 26 | // proper path resolution 27 | contents = contents.replace(/\{rootdir:([^}]+)\}/gi, function (match, subpath) { 28 | return 'file://' + path.join(i18n.__('appDir'), subpath) 29 | }) 30 | } 31 | 32 | return contents 33 | } 34 | 35 | function localisedFileName (lang, file) { 36 | // Since the files that will be printed are subject to user manipulation 37 | // a null can happen here, checking for it just in case. 38 | if (file === undefined || file === null) { 39 | return null 40 | } 41 | 42 | file = file.replace(/\{?\{lang\}?\}/g, lang) 43 | try { 44 | if (fs.accessSync ? (fs.accessSync(file, fs.R_OK) || true) : fs.existsSync(file)) { 45 | var stat = fs.statSync(file) 46 | if (stat && stat.isFile()) { 47 | return file 48 | } 49 | } 50 | } catch (e) {} 51 | return null 52 | } 53 | 54 | function localisedFirstFile (files, lang) { 55 | if (files === null) { 56 | return null 57 | } 58 | 59 | var file = null 60 | if (!Array.isArray(files)) { 61 | file = localisedFileName(lang, files) 62 | } else { 63 | for (var i = 0; i < files.length && !file; i++) { 64 | file = localisedFileName(lang, files[i]) 65 | } 66 | } 67 | return file 68 | } 69 | 70 | var PrintStream = function (i18n, lang) { 71 | if (!(this instanceof PrintStream)) { 72 | return new PrintStream(i18n, lang) 73 | } 74 | 75 | CombinedStream.call(this, {}) 76 | this.i18n = i18n 77 | this.lang = lang 78 | this.typeOpen = 'md' 79 | } 80 | 81 | inherits(PrintStream, CombinedStream) 82 | 83 | PrintStream.prototype._append = CombinedStream.prototype.append 84 | PrintStream.prototype._end = CombinedStream.prototype.end 85 | PrintStream.prototype.sub = function () { 86 | var sub = new PrintStream(this.i18n, this.lang) 87 | this._append(sub) 88 | return sub 89 | } 90 | PrintStream.prototype.append = function (content, contentType) { 91 | var stream = null 92 | 93 | if (typeof content === 'function') { 94 | content = content(this.i18n, this.lang) 95 | } 96 | 97 | if (content === null || content === undefined) { 98 | return false 99 | } 100 | 101 | if (Array.isArray(content)) { 102 | return content.reduce(function (found, child) { 103 | if (this.append(child)) { 104 | return true 105 | } 106 | return found 107 | }.bind(this), false) 108 | } 109 | 110 | if (Object.hasOwnProperty.call(content, 'first')) { 111 | return content.first.reduce(function (found, child) { 112 | return found || this.append(child) 113 | }.bind(this), false) 114 | } else if (Object.hasOwnProperty.call(content, 'files')) { 115 | var files = content.files 116 | .map(localisedFileName.bind(null, this.lang)) 117 | .filter(function (file) { return file !== null }) 118 | if (files.length > 0) { 119 | stream = this.sub() 120 | files.forEach(function (file) { 121 | stream.append('---', 'md') 122 | if (files.length > 1) { 123 | stream.append('`_' + file + '_` :') 124 | } 125 | stream.append({ file: file }) 126 | }) 127 | stream.append('---', 'md') 128 | return true 129 | } else { 130 | return false 131 | } 132 | } 133 | 134 | if (Object.hasOwnProperty.call(content, 'file')) { 135 | var file = localisedFirstFile(content.file, this.lang) 136 | if (file) { 137 | // In order to properly support stream we need to rewrite workshopper-exercise 138 | // to return an stream to the output instead of simply piping to stdout 139 | // stream = fs.createReadStream(file, {encoding: 'utf8'}) 140 | var str = fs.readFileSync(file, 'utf8') 141 | contentType = content.type || contentType || path.extname(file).replace(/^\./, '').toLowerCase() 142 | if (contentType !== 'md') { 143 | str = '```' + contentType + '\n' + str + '\n```\n' 144 | contentType = 'md' 145 | } 146 | stream = new StrToStream(str) 147 | } 148 | } else if (content.pipe) { 149 | stream = content 150 | } else if (Object.hasOwnProperty.call(content, 'text')) { 151 | contentType = content.type 152 | stream = new StrToStream(content.text + (content.skipNewline ? '' : '\n')) 153 | } else { 154 | stream = new StrToStream(content + '\n') 155 | } 156 | 157 | if (!stream) { 158 | return false 159 | } 160 | 161 | if (!contentType) { 162 | contentType = 'md' 163 | } 164 | 165 | var i18n = this.i18n 166 | var buffer = [] 167 | 168 | this._append( 169 | stream 170 | .pipe(split()) 171 | .pipe(through2({ objectMode: true }, function (contents, encoding, done) { 172 | buffer.push(getText(i18n, contents.toString())) 173 | done() 174 | }, function (done) { 175 | this.push({ 176 | text: buffer.join('\n'), 177 | type: contentType 178 | }) 179 | done() 180 | })) 181 | ) 182 | return true 183 | } 184 | PrintStream.prototype.write = function (data) { 185 | if (typeof data === 'string') { 186 | this.emit('data', data) 187 | return 188 | } 189 | if (this.typeOpen !== data.type) { 190 | if (data.type !== 'md') { 191 | data.text = '```' + data.type + '\n' + data.text 192 | } 193 | if (this.typeOpen !== 'md') { 194 | data.text = '\n```\n' + data.text 195 | } 196 | this.typeOpen = data.type 197 | } 198 | this.emit('data', data.text) 199 | } 200 | PrintStream.prototype.end = function () { 201 | if (this.typeOpen !== 'md') { 202 | this.emit('data', '```') 203 | } 204 | this._end() 205 | } 206 | 207 | PrintStream.prototype.appendChain = function (content, type) { 208 | this.append(content, type) 209 | return this 210 | } 211 | 212 | module.exports = PrintStream 213 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const commandico = require('commandico') 3 | const inherits = require('util').inherits 4 | const EventEmitter = require('events').EventEmitter 5 | 6 | const util = require('./util') 7 | const PrintStream = require('./lib/print') 8 | const storage = require('workshopper-adventure-storage') 9 | 10 | function WA (options) { 11 | if (!(this instanceof WA)) { 12 | return new WA(options) 13 | } 14 | 15 | if (!options) { 16 | options = {} 17 | } 18 | 19 | if (options.appDir) { 20 | options.appDir = util.getDir(options.appDir, '.') 21 | if (!options.name) { 22 | try { 23 | options.name = require(path.join(options.appDir, 'package.json')).name 24 | } catch (e) {} 25 | } 26 | } 27 | 28 | if (!options.name) { 29 | throw new Error('The workshopper needs a name to store the progress.') 30 | } 31 | 32 | if (!options.languages) { 33 | options.languages = ['en'] 34 | } 35 | 36 | if (!options.defaultLang) { 37 | options.defaultLang = options.languages[0] 38 | } 39 | 40 | if (!options.defaultOutputType) { 41 | options.defaultOutputType = 'md' 42 | } 43 | 44 | if (!options.pkg && options.appDir) { 45 | try { 46 | options.pkg = require(path.join(options.appDir, 'package.json')) 47 | } catch (e) {} 48 | } 49 | 50 | if (!options.appRepo && options.pkg) { 51 | options.appRepo = options.pkg.repository.url 52 | } 53 | 54 | if (!options.version && options.pkg) { 55 | options.version = options.pkg.version 56 | } 57 | 58 | if (options.appDir) { 59 | options.exerciseDir = util.getDir(options.exerciseDir || 'exercises', options.appDir) 60 | } 61 | 62 | if (!options.menu) { 63 | options.menu = { 64 | width: 73, 65 | x: 2, 66 | y: 2 67 | } 68 | } 69 | 70 | if (options.requireSubmission === undefined) { 71 | options.requireSubmission = false 72 | } 73 | 74 | if (!options.menuFactory) { 75 | options.menuFactory = require('simple-terminal-menu/factory')(options.menu, {}) 76 | } 77 | 78 | EventEmitter.call(this) 79 | 80 | this.options = options 81 | 82 | var globalStorage = storage(storage.userDir, '.config', 'workshopper') 83 | this.appStorage = storage(storage.userDir, '.config', options.name) 84 | 85 | this.exercises = [] 86 | this._meta = {} 87 | 88 | try { 89 | this.i18n = require('./i18n').init(options, globalStorage, this.appStorage) 90 | } catch (e) { 91 | console.log(e.message) 92 | process.exit(1) 93 | } 94 | this.__ = this.i18n.__ 95 | this.__n = this.i18n.__n 96 | 97 | this.cli = commandico(this, 'menu') 98 | .loadCommands(path.resolve(__dirname, './lib/commands')) 99 | .loadModifiers(path.resolve(__dirname, './lib/modifiers')) 100 | 101 | if (options.commands) { 102 | this.cli.addCommands(options.commands) 103 | } 104 | 105 | if (options.modifiers) { 106 | this.cli.addModifiers(options.modifiers) 107 | } 108 | } 109 | 110 | inherits(WA, EventEmitter) 111 | 112 | WA.prototype.execute = function (args) { 113 | return this.cli.execute(args) 114 | } 115 | 116 | WA.prototype.add = function (nameOrObject, fnOrObject, fn) { 117 | var meta 118 | try { 119 | meta = require('./lib/createExerciseMeta')(this.options.exerciseDir, nameOrObject, fnOrObject, fn) 120 | } catch (e) { 121 | console.log(e) 122 | return new Error(this.__('error.exercise.' + e.id, e)) 123 | } 124 | return this.addExercise(meta) 125 | } 126 | 127 | WA.prototype.addAll = function (list) { 128 | return list.map(this.add.bind(this)) 129 | } 130 | 131 | WA.prototype.addExercise = function (meta) { 132 | this.exercises.push(meta.name) 133 | this._meta[meta.id] = meta 134 | meta.number = this.exercises.length 135 | return this 136 | } 137 | 138 | WA.prototype.getVersionString = function () { 139 | return this.options.name + '@' + this.options.version 140 | } 141 | 142 | WA.prototype.countRemaining = function () { 143 | var completed = this.appStorage.get('completed') 144 | return this.exercises.length - (completed ? completed.length : 0) 145 | } 146 | 147 | WA.prototype.markCompleted = function (exerciseName, cb) { 148 | var completed = this.appStorage.get('completed') || [] 149 | 150 | if (completed.indexOf(exerciseName) === -1) { 151 | completed.push(exerciseName) 152 | } 153 | 154 | this.appStorage.save('completed', completed) 155 | 156 | if (this.onComplete.length === 0) { 157 | throw new Error('The workshoppers `.onComplete` method must have at least one callback argument') 158 | } 159 | return this.onComplete(cb) 160 | } 161 | 162 | WA.prototype.getNext = function () { 163 | var current = this.appStorage.get('current') 164 | var remainingAfterCurrent = this.exercises.slice(this.exercises.indexOf(current) + 1) 165 | var completed = this.appStorage.get('completed') || [] 166 | 167 | var incompleteAfterCurrent = remainingAfterCurrent.filter(function (elem) { 168 | return completed.indexOf(elem) < 0 169 | }) 170 | 171 | if (incompleteAfterCurrent.length === 0) { 172 | return new Error('error.no_uncomplete_left') 173 | } 174 | 175 | return incompleteAfterCurrent[0] 176 | } 177 | 178 | WA.prototype.onComplete = function (cb) { 179 | setImmediate(cb) 180 | } 181 | 182 | // overall exercise fail 183 | WA.prototype.exerciseFail = function (mode, exercise, stream, cb) { 184 | ( 185 | stream.append(exercise.fail, exercise.failType || this.options.defaultOutputType) || 186 | stream.append(this.options.fail, this.options.failType || this.options.defaultOutputType) 187 | ) && 188 | stream.append('\n') 189 | 190 | cb() 191 | } 192 | 193 | WA.prototype.getExerciseFiles = function (exercise, callback) { 194 | if (!exercise.hideSolutions && typeof exercise.getSolutionFiles === 'function') { 195 | return exercise.getSolutionFiles(callback) 196 | } 197 | 198 | setImmediate(callback.bind(null, null, exercise.solutionFiles || [])) 199 | } 200 | 201 | // overall exercise pass 202 | WA.prototype.exercisePass = function (mode, exercise, stream, cb) { 203 | this.getExerciseFiles(exercise, function (err, files) { 204 | if (err) { 205 | return cb(this.__('solution.notes.load_error', { err: err.message || err }), false, stream) 206 | } 207 | 208 | this.markCompleted(exercise.meta.name, function (err, completeMessage) { 209 | if (err) { 210 | return cb(err, false) 211 | } 212 | 213 | var appended = stream.append(completeMessage, this.options.defaultOutputType) || 214 | stream.append(exercise.pass, exercise.passType || this.options.defaultOutputType) || 215 | stream.append(this.options.pass, this.options.passType || this.options.defaultPassType) 216 | 217 | var hideSolutions = exercise.hideSolutions 218 | if (hideSolutions === undefined) { 219 | hideSolutions = this.options.hideSolutions 220 | } 221 | if (hideSolutions !== true) { 222 | if ((files && files.length > 0) || exercise.solution) { 223 | stream.append('{solution.notes.compare}') 224 | if (exercise.solutionPath) { 225 | stream.append({ files: [exercise.solutionPath] }) 226 | } 227 | } 228 | 229 | files && files.length > 0 230 | ? stream.append({ files: files }) 231 | : stream.append(exercise.solution, exercise.solutionType || this.options.defaultSolutionType) 232 | 233 | appended = true 234 | } 235 | 236 | var hideRemaining = exercise.hideRemaining 237 | if (hideRemaining === undefined) { 238 | hideRemaining = this.options.hideRemaining 239 | } 240 | 241 | if (hideRemaining !== true) { 242 | var remaining = this.countRemaining() 243 | remaining > 0 244 | ? stream.append( 245 | '{progress.remaining#' + remaining + '}\n\n' + 246 | '{ui.return}\n') 247 | : stream.append('{progress.finished}\n') 248 | appended = true 249 | } 250 | 251 | if (appended) { 252 | stream.append('\n') 253 | } 254 | 255 | cb(null, true) 256 | }.bind(this)) 257 | }.bind(this)) 258 | } 259 | 260 | WA.prototype.verify = function (args, specifier, contentOnly, cb) { 261 | return this.process('verify', args, specifier, contentOnly, cb) 262 | } 263 | 264 | WA.prototype.run = function (args, specifier, cb) { 265 | return this.process('run', args, specifier, cb) 266 | } 267 | 268 | WA.prototype.process = function (mode, args, specifier, contentOnly, cb) { 269 | var exercise = this.loadExercise(specifier) 270 | var stream = this.createMarkdownStream(exercise) 271 | 272 | if (!cb && typeof contentOnly === 'function') { 273 | cb = contentOnly 274 | contentOnly = false 275 | } 276 | 277 | var _cb = cb 278 | cb = function (err, pass) { 279 | // The method that creates the stream is supposed to know 280 | // when it will surely never add anything more to the 281 | // the stream 282 | stream.stopWaiting() 283 | // Zalgo protection 284 | setImmediate(function () { 285 | _cb(err, pass) 286 | }) 287 | } 288 | // The stream we return needs to be "not finished yet". Which 289 | // is why we mark it as "waiting". It will already push out the 290 | // stream of data but before `.stopWaiting` it will not send 291 | // the end event. 292 | stream.startWaiting() 293 | 294 | if (!exercise) { 295 | cb(this.__('error.exercise.missing', { name: specifier }), false) 296 | return stream 297 | } 298 | 299 | var requireSubmission = exercise.requireSubmission 300 | if (requireSubmission === undefined) { 301 | requireSubmission = this.options.requireSubmission 302 | } 303 | 304 | if (requireSubmission !== false && args.length === 0) { 305 | cb(this.__('ui.usage', { appName: this.options.name, mode: mode }), false, stream) 306 | return stream 307 | } 308 | 309 | var method = exercise[mode] 310 | if (!method) { 311 | cb(this.__('error.exercise.method_not_required', { method: mode }), false, stream) 312 | return stream 313 | } 314 | 315 | if (typeof method !== 'function') { 316 | // eslint-disable-next-line standard/no-callback-literal 317 | cb('The `.' + mode + '` object of the exercise `' + exercise.meta.id + ' is a `' + typeof method + '`. It should be a `function` instead.', false, stream) 318 | return stream 319 | } 320 | 321 | stream = this.executeExercise(exercise, mode, method, args, stream, contentOnly, cb) 322 | if (typeof exercise.on === 'function') { 323 | exercise.on('pass', function (message) { 324 | stream.append({ 325 | text: require('chalk').green.bold('\u2713 '), 326 | type: (message && message.type) || 'md', 327 | skipNewline: true 328 | }) 329 | stream.append(message, this.options.defaultOutputType) 330 | }.bind(this)) 331 | exercise.on('fail', function (message) { 332 | stream.append({ 333 | text: require('chalk').red.bold('\u2717 '), 334 | type: (message && message.type) || 'md', 335 | skipNewline: true 336 | }) 337 | stream.append(message, this.options.defaultOutputType) 338 | }.bind(this)) 339 | exercise.on('pass', this.emit.bind(this, 'pass', exercise, mode)) 340 | exercise.on('fail', this.emit.bind(this, 'fail', exercise, mode)) 341 | } 342 | return stream 343 | } 344 | 345 | WA.prototype.executeExercise = function (exercise, mode, method, args, stream, contentOnly, cb) { 346 | if (!cb && typeof contentOnly === 'function') { 347 | cb = contentOnly 348 | contentOnly = false 349 | } 350 | if (!contentOnly && mode === 'verify') { 351 | (stream.append(exercise.header, this.options.defaultOutputType) || 352 | stream.append(this.options.header, this.options.defaultOutputType)) 353 | } 354 | var result 355 | var finished = false 356 | var cleanup = function cleanup (err, pass, message, messageType) { 357 | if (finished) { 358 | return // TODO: make this easier to debug ... bad case of zalgo 359 | } 360 | 361 | finished = true 362 | 363 | if (message) { 364 | if (typeof message === 'string') { 365 | message = { 366 | text: message, 367 | type: messageType || this.options.defaultOutputType 368 | } 369 | } 370 | if (pass) { 371 | exercise.pass = [ 372 | exercise.pass || this.options.pass, 373 | message 374 | ] 375 | } else { 376 | exercise.fail = [ 377 | exercise.fail || this.options.fail, 378 | message 379 | ] 380 | } 381 | } 382 | 383 | if (err) { 384 | return cb(this.__('error.exercise.unexpected_error', { mode: mode, err: (err.stack || err) }), false) 385 | } 386 | 387 | var writeFooter = function () { 388 | if (!contentOnly && mode === 'verify') { 389 | // TODO: Make this footer great again once we fixed workshopper-exercise 390 | if (stream.append(exercise.footer, this.options.defaultOutputType) || 391 | stream.append(this.options.footer, this.options.defaultOutputType)) { 392 | stream.append('\n') 393 | } 394 | return true 395 | } 396 | return false 397 | }.bind(this) 398 | 399 | var end = function (err) { 400 | if (typeof exercise.end !== 'function') { 401 | writeFooter() 402 | return cb(null, pass) 403 | } 404 | 405 | exercise.end(mode, pass, function (cleanupErr) { 406 | if (cleanupErr) { 407 | return cb(this.__('error.cleanup', { err: cleanupErr.message || cleanupErr }), false) 408 | } 409 | 410 | writeFooter() 411 | cb(err, pass) 412 | }.bind(this)) 413 | }.bind(this) 414 | 415 | if (mode === 'run') { 416 | return setImmediate(end) 417 | } 418 | 419 | if (pass) { 420 | this.exercisePass(mode, exercise, stream, end) 421 | } else { 422 | this.exerciseFail(mode, exercise, stream, end) 423 | } 424 | }.bind(this) 425 | 426 | try { 427 | result = method.length <= 1 428 | ? cleanup(null, true, method.call(exercise, args)) 429 | : method.call(exercise, args, function callback (err, pass, message) { 430 | /* 431 | err ... Error that occured 432 | pass ... true = The run has worked 433 | message ... message to Append after the output 434 | 435 | callback(true) -> err=null, pass=true 436 | callback(false) -> err=null, pass=false 437 | callback() -> err=null, pass=null 438 | callback(null) -> err=null, pass=null 439 | callback(true, true) -> err=true, pass="x" 440 | callback(false, "x") -> err=false, pass="x" 441 | callback(null, "x") -> err=null, pass="x" 442 | callback("x", false) -> err="x", pass=false 443 | callback("x", true) -> err="x", pass=true ... pass should be ignored 444 | */ 445 | if (pass === undefined && (err === true || err === false || err === undefined || err === null)) { 446 | pass = err 447 | err = null 448 | } 449 | 450 | pass = (mode === 'run' || (pass && !exercise.fail)) 451 | err 452 | ? cleanup(err, null, message) 453 | : cleanup(null, pass, message) 454 | }) 455 | } catch (e) { 456 | cleanup(e, false) 457 | return stream 458 | } 459 | return this.processResult(result, stream) 460 | } 461 | WA.prototype.processResult = function (result, stream) { 462 | stream.append(result, this.options.defaultOutputType) 463 | return stream 464 | } 465 | WA.prototype.loadExercise = function (specifier) { 466 | var id 467 | if (specifier) { 468 | id = this.specifierToId(specifier) 469 | } else { 470 | id = util.idFromName(this.appStorage.get('current')) 471 | } 472 | 473 | if (!id) { 474 | return null 475 | } 476 | var meta = this._meta[id] 477 | if (!meta) { 478 | return null 479 | } 480 | 481 | var exercise = meta.fn() 482 | exercise.meta = meta 483 | 484 | if (typeof exercise.init === 'function') { 485 | exercise.init(this, meta.id, meta.name, meta.dir, meta.number) 486 | } 487 | 488 | return exercise 489 | } 490 | WA.prototype.specifierToId = function (specifier) { 491 | if (!isNaN(specifier)) { 492 | var number = parseInt(specifier, 10) 493 | if (number >= 0 && number < this.exercises.length) { 494 | specifier = this.exercises[number] 495 | } else { 496 | specifier = '' 497 | } 498 | } 499 | 500 | return util.idFromName(specifier) 501 | } 502 | WA.prototype.selectExercise = function (specifier) { 503 | var id = this.specifierToId(specifier) 504 | if (!id) { 505 | throw new Error(this.__('error.exercise.missing', { name: specifier })) 506 | } 507 | 508 | var meta = this._meta[id] 509 | if (!meta) { 510 | throw new Error(this.__('error.exercise.missing', { name: specifier })) 511 | } 512 | 513 | this.appStorage.save('current', meta.name) 514 | return meta.id 515 | } 516 | WA.prototype.createMarkdownStream = function (exercise) { 517 | var context = exercise ? this.createExerciseContext(exercise) : this.i18n 518 | return new PrintStream(context, this.i18n.lang()) 519 | } 520 | WA.prototype.createExerciseContext = function (exercise) { 521 | return this.i18n.extend({ 522 | 'currentExercise.name': this.__('exercise.' + exercise.meta.name), 523 | 'progress.count': exercise.meta.number, 524 | 'progress.total': this.exercises.length, 525 | 'progress.state_resolved': this.__('progress.state', { count: exercise.meta.number, amount: this.exercises.length }) 526 | }) 527 | } 528 | WA.prototype.getExerciseText = function printExercise (specifier, contentOnly, callback) { 529 | var exercise = this.loadExercise(specifier) 530 | var prepare 531 | 532 | if (arguments.length === 2) { 533 | callback = contentOnly 534 | contentOnly = false 535 | } 536 | 537 | if (!exercise) { 538 | callback(this.__('error.exercise.none_active')) 539 | } 540 | 541 | prepare = (typeof exercise.prepare === 'function') ? exercise.prepare.bind(exercise) : setImmediate 542 | prepare(function (err) { 543 | if (err) { 544 | return callback(this.__('error.exercise.preparing', { err: err.message || err })) 545 | } 546 | 547 | var getExerciseText = (typeof exercise.getExerciseText === 'function') ? exercise.getExerciseText.bind(exercise) : setImmediate 548 | getExerciseText(function (err, exerciseTextType, exerciseText) { 549 | if (err) { 550 | return callback(this.__('error.exercise.loading', { err: err.message || err })) 551 | } 552 | var stream = this.createMarkdownStream(exercise) 553 | var found = false 554 | 555 | if (!contentOnly) { 556 | stream.append(exercise.header, this.options.defaultOutputType) || 557 | stream.append(this.options.header, this.options.defaultOutputType) 558 | } 559 | if (stream.append(exercise.problem, exercise.problemType || this.options.defaultOutputType)) { 560 | found = true 561 | } 562 | if (stream.append(exerciseText, exerciseTextType || this.options.defaultOutputType)) { 563 | found = true 564 | } 565 | if (!found) { 566 | // eslint-disable-next-line standard/no-callback-literal 567 | return callback('The exercise "' + exercise.meta.name + '" is missing a problem definition!') 568 | } 569 | if (!contentOnly) { 570 | stream.append(exercise.footer, this.options.defaultOutputType) || 571 | (stream.append(this.options.footer, this.options.defaultOutputType) && 572 | stream.append('\n')) 573 | } 574 | callback(null, stream) 575 | }.bind(this)) 576 | }.bind(this)) 577 | } 578 | 579 | module.exports = WA 580 | --------------------------------------------------------------------------------