/* eslint-disable no-inline-comments */ import fs from 'fs-extra'; import path from 'path'; import _ from 'lodash'; import { dasherize } from './utils'; const jsonLinePrefix = '//--JSON:'; const paragraphBreak = ''; class ChallengeFile { constructor(dir, name, suffix) { this.dir = dir; this.name = name; this.suffix = suffix; } filePath() { return path.join(this.dir, this.name + this.suffix); } write(contents) { if (_.isArray(contents)) { contents = contents.join('\n'); } fs.writeFile(this.filePath(), contents, err => { if (err) { throw err; } }); } readChunks() { // todo: make this work async // todo: make sure it works with encodings let data = fs.readFileSync(this.filePath()); let lines = data.toString().split(/(?:\r\n|\r|\n)/g); function removeLeadingEmptyLines(array) { let emptyString = /^\s*$/; while (array && Array.isArray(array) && emptyString.test(array[0])) { array.shift(); } } let chunkEnd = /(/; while (!endOfFile()) { let line = nextLine(); if (chunkEnd.test(line)) { return translations; } else if (langChunk.test(line)) { let langCode = line.match(langChunk)[1]; translations[langCode] = readProperties(); line = nextLine(); if (!chunkEnd.test(line)) { throw `Expected --end--: ${line}`; } } } throw `Unexpected end of the file while reading translations. ${this.filePath()}`; } function readFiles() { let files = {}; let fileChunk = //; while (!endOfFile()) { let line = nextLine(); if (chunkEnd.test(line)) { return files; } else if (fileChunk.test(line)) { let name = line.match(fileChunk)[1]; let ext = line.match(fileChunk)[2]; let key = name + ext; files[key] = {}; files[key].key = key; files[key].ext = ext; files[key].name = name; Object.assign(files[key], readProperties()); line = nextLine(); if (!chunkEnd.test(line)) { throw `Expected --end--: ${line}`; } } } throw `Unexpected end of the file while reading files. ${this.filePath()}`; } function readProperty() { let property = []; while (!endOfFile()) { let line = nextLine(); if (chunkEnd.test(line)) { removeLeadingEmptyLines(property); return property; } else if (line.startsWith(jsonLinePrefix)) { line = JSON.parse(line.slice(jsonLinePrefix.length)); property.push(line); } else { property.push(line); } } throw `Unexpected end of the file while reading a property. ${this.filePath()}`; } function readProperties() { let properties = {}; let chunkStart = /( { if (_.isString(part)) { out.push(part.toString()); out.push(paragraphBreak); } else { // Descriptions are weird since sometimes they're text and sometimes // they're "steps" which appear one at a time with optional pix and // captions and links, or "questions" with choices and explanations... // For now we preserve non-string descriptions via JSON but this is // not a great solution. // It would be better if "steps" and "description" were separate fields. // For the record, the (unnamed) fields in step are: // 0: image URL // 1: caption // 2: text // 3: link URL out.push(jsonLinePrefix + JSON.stringify(part)); } }); if (out[out.length - 1] === paragraphBreak) { out.pop(); } return out; } expandedTests(tests) { if (!tests) { return []; } let out = []; tests.forEach(test => { if (_.isString(test)) { out.push(test); } else { // todo: figure out what to do about these id-title challenge links out.push(jsonLinePrefix + JSON.stringify(test)); } }); return out; } unpackedHTML() { let text = []; text.push(''); text.push(''); text.push(''); text.push(''); text.push(''); text.push(''); text.push(''); text.push(`

${this.challenge.title}

`); text.push(`

This is the unpacked version of ${this.superBlockName}/${this.challengeBlockName} (challenge id ${this.challenge.id}).

`); text.push('

Open the JavaScript console to see test results.

'); text.push(`

Edit this HTML file (between <!-- marks only!) and run npm run repack to incorporate your changes into the challenge database.

`); text.push('

Title

'); text.push(''); text.push(this.challenge.title); text.push(''); text.push(''); text.push('

Description

'); text.push('
'); text.push(''); if (this.challenge.description.length) { text.push( this.expandedDescription(this.challenge.description).join('\n') ); } text.push(''); text.push('
'); text.push(''); text.push('

Translations

'); text.push(`

Format of translation unit:

<!--language-code-->
<!--title-->
Title
<!--end-->
<!--description-->
Description
<!--end-->
<!--end-->

`); text.push('
'); text.push(''); if (this.challenge.hasOwnProperty('translations')) { const translations = this.challenge.translations; const keys = Object.keys(translations); if (keys) { keys.forEach(lang => { text.push(`

${lang}

`); text.push(``); const translation = translations[lang]; if (translation.title) { text.push('

Title

'); text.push(''); text.push(translation.title); text.push(''); } if (translation.description && translation.description.length) { text.push('

Description

'); text.push(''); text.push( this.expandedDescription(translation.description).join('\n') ); text.push(''); } text.push(''); }); } } text.push(''); text.push('
'); text.push(''); text.push('

Files

'); text.push(`

Format of file:

<!--name.ext-->
<!--contents-->
Contents
<!--end-->
<!--head-->
Head
<!--end-->
<!--tail-->
Tail
<!--end-->
<!--end-->

`); text.push('
'); text.push(''); if (this.challenge.files) { Object.keys(this.challenge.files).forEach(key => { let file = this.challenge.files[key]; let { contents, head, tail } = file; text.push(`

${file.name + '.' + file.ext}

`); text.push(``); text.push('

Contents

'); text.push('
');
        text.push(_.escape(contents.join('\n')));
        text.push('
'); text.push('
');
        text.push('');
        text.push(contents.join('\n'));
        text.push('');
        text.push('
'); text.push('

Head

'); text.push('
');
        text.push(_.escape(head.join('\n')));
        text.push('
'); text.push('
');
        text.push('');
        text.push(head.join('\n'));
        text.push('');
        text.push('
'); text.push('

Tail

'); text.push('
');
        text.push(_.escape(tail.join('\n')));
        text.push('
'); text.push('
');
        text.push('');
        text.push(tail.join('\n'));
        text.push('');
        text.push('
'); text.push(''); }); } text.push(''); text.push('
'); text.push(''); text.push('

Solution

'); text.push( ''); text.push(''); text.push('

Tests

'); text.push(''); text.push(''); text.push(''); text.push(''); return text; } } export { UnpackedChallenge };