freeCodeCamp/tools/challenge-parser/challengeSeed-to-data.js

125 lines
3.3 KiB
JavaScript

const visit = require('unist-util-visit');
const { selectAll, select } = require('hast-util-select');
const { sectionFilter } = require('./utils');
const seedRE = /(.+)-seed$/;
const headRE = /(.+)-setup$/;
const tailRE = /(.+)-teardown$/;
const editableRegionMarker = '--fcc-editable-region--';
function defaultFile(lang) {
return {
key: `index${lang}`,
ext: lang,
name: 'index',
contents: '',
head: '',
tail: ''
};
}
function createCodeGetter(codeKey, regEx, seeds) {
return container => {
const {
properties: { id }
} = container;
const lang = id.match(regEx)[1];
const key = `index${lang}`;
const code = select('code', container).children[0].value;
if (key in seeds) {
seeds[key] = {
...seeds[key],
[codeKey]: code
};
} else {
seeds[key] = {
...defaultFile(lang),
[codeKey]: code
};
}
};
}
// TODO: any reason to worry about CRLF?
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');
}
function createPlugin() {
return function transformer(tree, file) {
function visitor(node) {
if (sectionFilter(node, 'challengeSeed')) {
let seeds = {};
const codeDivs = selectAll('div', node);
const seedContainers = codeDivs.filter(({ properties: { id } }) =>
seedRE.test(id)
);
seedContainers.forEach(createCodeGetter('contents', seedRE, seeds));
const headContainers = codeDivs.filter(({ properties: { id } }) =>
headRE.test(id)
);
headContainers.forEach(createCodeGetter('head', headRE, seeds));
const tailContainers = codeDivs.filter(({ properties: { id } }) =>
tailRE.test(id)
);
tailContainers.forEach(createCodeGetter('tail', tailRE, seeds));
file.data = {
...file.data,
files: seeds
};
// TODO: make this readable.
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 = [];
}
});
// TODO: TESTS!
}
}
visit(tree, 'element', visitor);
};
}
exports.challengeSeedToData = createPlugin;
exports.createCodeGetter = createCodeGetter;
exports.defaultFile = defaultFile;