const getAllBetween = require('./utils/between-headings'); // const visit = require('unist-util-visit'); const visitChildren = require('unist-util-visit-children'); const { root } = require('mdast-builder'); const { getFileVisitor } = require('./utils/get-file-visitor'); const { isEmpty } = require('lodash'); const editableRegionMarker = '--fcc-editable-region--'; function findRegionMarkers(file) { const lines = file.contents.split('\n'); const editableLines = lines .map((line, id) => (line.trim() === editableRegionMarker ? id : -1)) .filter(id => id >= 0); if (editableLines.length > 2) { throw Error('Editable region has too many markers: ' + editableLines); } if (editableLines.length === 0) { return null; } else if (editableLines.length === 1) { throw Error(`Editable region not closed`); } else { return editableLines; } } function removeLines(contents, toRemove) { const lines = contents.split('\n'); return lines.filter((_, id) => !toRemove.includes(id)).join('\n'); } // TODO: DRY this. Start with an array of markers and go from there. function addSeeds() { function transformer(tree, file) { const seedTree = root(getAllBetween(tree, `--seed--`)); // Not all challenges have seeds (video challenges, for example), so we stop // processing in these cases. if (isEmpty(seedTree.children)) return; const contentsTree = root(getAllBetween(seedTree, `--seed-contents--`)); const headTree = root(getAllBetween(seedTree, `--before-user-code--`)); const tailTree = root(getAllBetween(seedTree, `--after-user-code--`)); const seeds = {}; // While before and after code are optional, the contents are not if (isEmpty(contentsTree.children)) throw Error('## --seed-contents-- must appear in # --seed-- sections'); const visitForContents = visitChildren( getFileVisitor(seeds, 'contents', validateEditableMarkers) ); const visitForHead = visitChildren(getFileVisitor(seeds, 'head')); const visitForTail = visitChildren(getFileVisitor(seeds, 'tail')); visitForContents(contentsTree); visitForHead(headTree); visitForTail(tailTree); file.data = { ...file.data, files: seeds }; // process region markers - remove them from content and add them to data Object.keys(seeds).forEach(key => { const fileData = seeds[key]; const editRegionMarkers = findRegionMarkers(fileData); if (editRegionMarkers) { fileData.contents = removeLines(fileData.contents, editRegionMarkers); if (editRegionMarkers[1] <= editRegionMarkers[0]) { throw Error('Editable region must be non zero'); } fileData.editableRegionBoundaries = editRegionMarkers; } else { fileData.editableRegionBoundaries = []; } }); } return transformer; } function validateEditableMarkers({ value, position }) { const twoMarkersRE = RegExp( editableRegionMarker + '.*' + editableRegionMarker ); const formattedMarkerRE = /--fcc - editable - region--/; const lines = value.split('\n'); const baseLineNumber = position.start.line + 1; lines.forEach((line, index) => { if (line.match(twoMarkersRE)) { throw Error( `Line ${baseLineNumber + index} has two markers. Each line should only have one.` ); } if (line.match(formattedMarkerRE)) { throw Error( `Line ${baseLineNumber + index} has a malformed marker. It should be --fcc-editable-region--` ); } }); } module.exports = addSeeds; module.exports.editableRegionMarker = editableRegionMarker;