From 14c9ed8974ec4b5b24d04ebfacfec7c784136790 Mon Sep 17 00:00:00 2001 From: Alex Chaffee Date: Fri, 23 Mar 2018 15:53:59 -0400 Subject: [PATCH] feat(seed): unpack/repack properly handles paragraph breaks --- README.md | 5 +++- repack.js | 25 +--------------- unpack.js | 36 ++++++++++++++--------- unpackedChallenge.js | 69 +++++++++++++++++++++++++++++++++----------- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f038a5ab64..7999c4537d 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,11 @@ For each challenge section, there is a JSON file (fields documented below) conta `npm run unpack` extracts challenges into separate files for easier viewing and editing. The files are `.gitignore`d and will *not* be checked in, and all mongo seed importing will keep using the existing system; this is essentially a tool for editing `challenge.json` files. These HTML files are self-contained and run their own tests -- open a browser JS console to see the test results. -`npm run repack` gathers up the unpacked/edited HTML files into challenge-block JSON files. Use `git diff` to see the changes +`npm run repack` gathers up the unpacked/edited HTML files into challenge-block JSON files. Use `git diff` to see the changes. +When editing the unpacked files, you must only edit lines between comment fences like `` and ``. In descriptions, you can insert a paragraph break with ``. + +Unpacked lines that begin with `//--JSON:` are parsed and inserted verbatim. ## Links diff --git a/repack.js b/repack.js index 9328b3e611..149d66697d 100644 --- a/repack.js +++ b/repack.js @@ -19,25 +19,9 @@ function directoriesIn(parentDir) { } let superBlocks = directoriesIn(unpackedRoot); -console.log(superBlocks); - -function diffFiles(originalFilePath, changedFilePath) { - // todo: async - console.log(`diffing ${originalFilePath} and ${changedFilePath}`); - let original = fs.readFileSync(originalFilePath).toString(); - let repacked = fs.readFileSync(changedFilePath).toString(); - - let changes = jsdiff.diffLines(original, repacked, { newlineIsToken: true }); - changes.forEach((change) => { - if (change.added || change.removed) { - console.log(JSON.stringify(change, null, 2)); - } - }); - console.log(''); -} - superBlocks.forEach(superBlock => { let superBlockPath = path.join(unpackedRoot, superBlock); + console.log(`Repacking ${superBlockPath}...`); let blocks = directoriesIn(superBlockPath); blocks.forEach(blockName => { let blockPath = path.join(superBlockPath, blockName); @@ -59,13 +43,6 @@ superBlocks.forEach(superBlock => { path.join(seedChallengesRoot, superBlock, blockName + '.json'); // todo: async fs.writeFileSync(outputFilePath, JSON.stringify(block, null, 2)); - - // todo: make this a command-line option instead - let doDiff = false; - if (doDiff) { - diffFiles(blockFilePath, outputFilePath); - } - }); }); diff --git a/unpack.js b/unpack.js index 36410e4653..6307b19fa1 100644 --- a/unpack.js +++ b/unpack.js @@ -17,21 +17,29 @@ import {UnpackedChallenge, ChallengeFile} from './unpackedChallenge'; // bundle up the test-running JS function createUnpackedBundle() { - let unpackedFile = path.join(__dirname, 'unpacked.js'); - let b = browserify(unpackedFile).bundle(); - b.on('error', console.error); - let unpackedBundleFile = - path.join(__dirname, 'unpacked', 'unpacked-bundle.js'); - const bundleFileStream = fs.createWriteStream(unpackedBundleFile); - bundleFileStream.on('finish', () => { - console.log('Wrote bundled JS into ' + unpackedBundleFile); + let unpackedDir = path.join(__dirname, 'unpacked'); + fs.mkdirp(unpackedDir, (err) => { + if (err && err.code !== 'EEXIST') { + console.log(err); + throw err; + } + + let unpackedFile = path.join(__dirname, 'unpacked.js'); + let b = browserify(unpackedFile).bundle(); + b.on('error', console.error); + let unpackedBundleFile = + path.join(unpackedDir, 'unpacked-bundle.js'); + const bundleFileStream = fs.createWriteStream(unpackedBundleFile); + bundleFileStream.on('finish', () => { + console.log('Wrote bundled JS into ' + unpackedBundleFile); + }); + bundleFileStream.on('pipe', () => { + console.log('Writing bundled JS...'); + }); + bundleFileStream.on('error', console.error); + b.pipe(bundleFileStream); + // bundleFileStream.end(); // do not do this prematurely! }); - bundleFileStream.on('pipe', () => { - console.log('Writing bundled JS...'); - }); - bundleFileStream.on('error', console.error); - b.pipe(bundleFileStream); - // bundleFileStream.end(); // do not do this prematurely! } let currentlyUnpackingDir = null; diff --git a/unpackedChallenge.js b/unpackedChallenge.js index f419c2eb26..79a594d51d 100644 --- a/unpackedChallenge.js +++ b/unpackedChallenge.js @@ -4,6 +4,7 @@ import path from 'path'; import _ from 'lodash'; const jsonLinePrefix = '//--JSON:'; +const paragraphBreak = ''; class ChallengeFile { constructor(dir, name, suffix) { @@ -27,6 +28,7 @@ class ChallengeFile { }); } + readChunks() { // todo: make this work async // todo: make sure it works with encodings @@ -34,17 +36,39 @@ class ChallengeFile { let lines = data.toString().split(/(?:\r\n|\r|\n)/g); let chunks = {}; let readingChunk = null; + let currentParagraph = []; + + function removeLeadingEmptyLines(array) { + let emptyString = /^\s*$/; + while (array && Array.isArray(array) && emptyString.test(array[0])) { + array.shift(); + } + } + lines.forEach(line => { let chunkEnd = /( { + removeLeadingEmptyLines(chunks[key]); + }); + // console.log(JSON.stringify(chunks, null, 2)); return chunks; } @@ -116,15 +145,16 @@ class UnpackedChallenge { return `${prefix}-${this.challenge.id}`; } - expandedDescription(description) { + expandedDescription() { let out = []; - description.forEach(part => { + this.challenge.description.forEach(part => { 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 expanations... + // 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. @@ -136,7 +166,10 @@ class UnpackedChallenge { out.push(jsonLinePrefix + JSON.stringify(part)); } }); - // indent by 2 + + if (out[ out.length - 1 ] === paragraphBreak) { + out.pop(); + } return out; } @@ -171,15 +204,17 @@ class UnpackedChallenge { (challenge id ${this.challenge.id}).

`); text.push('

Open the JavaScript console to see test results.

'); - // text.push(`

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

`); + text.push(`

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

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

Description

'); text.push('
'); text.push(''); - text.push(this.expandedDescription(this.challenge.description).join('\n')); + if (this.challenge.description.length) { + text.push(this.expandedDescription().join('\n')); + } text.push(''); text.push('
'); @@ -218,7 +253,7 @@ class UnpackedChallenge { // Note: none of the challenges have more than one solution // todo: should we deal with multiple solutions or not? if (this.challenge.solutions && this.challenge.solutions.length > 0) { - let solution = this.challenge.solutions[0]; + let solution = this.challenge.solutions[ 0 ]; text.push(solution); } text.push('');