From 282e7d922bcebd16add94a97c4fd568e86af61f8 Mon Sep 17 00:00:00 2001 From: Randell Dawson Date: Thu, 27 Aug 2020 18:57:51 -0700 Subject: [PATCH] feat: create script to add step between two steps --- curriculum/package.json | 1 + curriculum/tools/README.md | 15 +++++- curriculum/tools/create-empty-steps.js | 18 +++++-- curriculum/tools/create-next-step.js | 23 ++++---- curriculum/tools/create-step-between.js | 53 +++++++++++++++++++ curriculum/tools/utils.js | 70 +++++++++++++++++++++---- 6 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 curriculum/tools/create-step-between.js diff --git a/curriculum/package.json b/curriculum/package.json index faf7320617..10bfda8a1b 100644 --- a/curriculum/package.json +++ b/curriculum/package.json @@ -16,6 +16,7 @@ "scripts": { "create-empty-steps": "cross-env CALLING_DIR=$INIT_CWD node ./tools/create-empty-steps", "create-next-step": "cross-env CALLING_DIR=$INIT_CWD node ./tools/create-next-step", + "create-step-between": "cross-env CALLING_DIR=$INIT_CWD node ./tools/create-step-between", "reorder-steps": "cross-env CALLING_DIR=$INIT_CWD node ./tools/reorder-steps", "develop": "gulp", "lint": "gulp lint", diff --git a/curriculum/tools/README.md b/curriculum/tools/README.md index 6a689c7b27..547d3bd454 100644 --- a/curriculum/tools/README.md +++ b/curriculum/tools/README.md @@ -14,8 +14,8 @@ A one-off script that will automatically add the next step based on the last ste npm run create-next-step ``` -## [create-next-step.js](create-next-step.js) -A one-off script that will automatically add a specified numbers of steps at a specific starting step number. The challenge seed code will be empty. +## [create-empty-steps.js](create-empty-steps.js) +A one-off script that automatically adds a specified number of steps at a specific starting step number. The challenge seed code for all steps created will be empty. **Note:** This script also runs [reorder-steps.js](reorder-steps.js). @@ -26,6 +26,17 @@ A one-off script that will automatically add a specified numbers of steps at a s npm run create-empty-steps start=X num=Y # where X is the starting step number and Y is the number of steps to create. ``` +## [create-step-between.js](create-step-between.js) +A one-off script that automatically adds a new step between two existing consecutive steps. The challenge seed code will use the existing starting step's challenge seed code with the editable region markers (ERMs) removed. + +**Note:** This script also runs [reorder-steps.js](reorder-steps.js). + +### How to run script: +1. Change to the directory of the project. +2. Run the following npm command: + ```bash + npm run create-step-between start=X end=Y # where X is the starting step number and Y is the following step number. + ## [reorder-steps.js](reorder-steps.js) A one-off script that automatically reorders the step files in a project's markdown files based on the filename. It also updates the `challengeOrder` property array in the project's `meta.json` with the new order of the steps. diff --git a/curriculum/tools/create-empty-steps.js b/curriculum/tools/create-empty-steps.js index 68c2cad1ab..36f25fcc85 100644 --- a/curriculum/tools/create-empty-steps.js +++ b/curriculum/tools/create-empty-steps.js @@ -1,8 +1,14 @@ -const path = require('path'); +const { + reorderSteps, + createStepFile, + getExistingStepNums, + getProjectPath +} = require('./utils'); -const { reorderSteps, createStepFile } = require('./utils'); +const anyStepExists = (steps, stepsToFind) => + stepsToFind.some(num => steps.includes(num)); -const projectPath = (process.env.CALLING_DIR || process.cwd()) + path.sep; +const projectPath = getProjectPath(); const argValuePairs = process.argv.slice(2); const args = argValuePairs.reduce((argsObj, arg) => { @@ -23,6 +29,12 @@ if (num > 20) { const maxStepNum = stepStart + num - 1; +const existingSteps = getExistingStepNums(projectPath); + +if (anyStepExists(existingSteps, [start, maxStepNum])) { + throw `Step not created. At least one of the steps specified between ${start} and ${maxStepNum} already exists.`; +} + for (let stepNum = stepStart; stepNum <= maxStepNum; stepNum++) { createStepFile({ stepNum, projectPath }); } diff --git a/curriculum/tools/create-next-step.js b/curriculum/tools/create-next-step.js index 07c32a0b4e..7512630616 100644 --- a/curriculum/tools/create-next-step.js +++ b/curriculum/tools/create-next-step.js @@ -1,7 +1,12 @@ const fs = require('fs'); const path = require('path'); -const { reorderSteps, createStepFile } = require('./utils'); +const { + reorderSteps, + createStepFile, + getChallengeSeed, + getProjectPath +} = require('./utils'); const getLastStepFileContent = () => { const filesArr = []; @@ -20,24 +25,14 @@ const getLastStepFileContent = () => { if (filesArr.length !== lastStepFileNum) { throw `Error: The last file step is ${lastStepFileNum} and there are ${filesArr.length} files.`; } - const fileContent = fs.readFileSync(projectPath + fileName, 'utf8'); - const matchedSection = fileContent - .toString() - .match(/
(?[\s\S]+)<\/section>/); - let finalChallengeSeed; - if (matchedSection) { - let { - groups: { challengeSeed } - } = matchedSection; - finalChallengeSeed = challengeSeed ? challengeSeed : ''; - } + return { nextStepNum: lastStepFileNum + 1, - challengeSeed: finalChallengeSeed + challengeSeed: getChallengeSeed(projectPath + fileName) }; }; -const projectPath = (process.env.CALLING_DIR || process.cwd()) + path.sep; +const projectPath = getProjectPath(); const { nextStepNum, challengeSeed } = getLastStepFileContent(); diff --git a/curriculum/tools/create-step-between.js b/curriculum/tools/create-step-between.js new file mode 100644 index 0000000000..128c739abe --- /dev/null +++ b/curriculum/tools/create-step-between.js @@ -0,0 +1,53 @@ +const { + reorderSteps, + createStepFile, + getChallengeSeed, + padWithLeadingZeros, + getExistingStepNums, + getProjectPath +} = require('./utils'); + +const allStepsExist = (steps, stepsToFind) => + stepsToFind.every(num => steps.includes(num)); + +const projectPath = getProjectPath(); +const argValuePairs = process.argv.slice(2); + +const args = argValuePairs.reduce((argsObj, arg) => { + const [argument, value] = arg.replace(/\s/g, '').split('='); + if (!argument || !value) { + throw `Invalid argument/value specified: ${arg}`; + } + return { ...argsObj, [argument]: value }; +}, {}); + +let { start, end } = args; +start = parseInt(start, 10); +end = parseInt(end, 10); + +if ( + !Number.isInteger(start) || + !Number.isInteger(end) || + start < 1 || + start !== end - 1 +) { + throw 'Step not created. Steps specified must be' + + ' consecutive numbers and start step must be greater than 0.'; +} + +const existingSteps = getExistingStepNums(projectPath); +if (!allStepsExist(existingSteps, [start, end])) { + throw 'Step not created. At least one of the steps specified does not exist.'; +} + +const challengeSeed = getChallengeSeed( + `${projectPath}part-${padWithLeadingZeros(start)}.md` +); +createStepFile({ + stepNum: start, + projectPath, + challengeSeed, + stepBetween: true +}); +console.log(`Sucessfully added step between step #${start} and step #${end}`); +reorderSteps(); diff --git a/curriculum/tools/utils.js b/curriculum/tools/utils.js index 0e8a29c354..36e51803d6 100644 --- a/curriculum/tools/utils.js +++ b/curriculum/tools/utils.js @@ -20,11 +20,20 @@ const removeErms = seedCode => { .join('\n'); }; -const createStepFile = ({ projectPath, stepNum, challengeSeed = '' }) => { +const createStepFile = ({ + projectPath, + stepNum, + challengeSeed = '', + stepBetween = false +}) => { if (challengeSeed) { challengeSeed = removeErms(challengeSeed); } + const descStepNum = stepBetween ? stepNum + 1 : stepNum; + const stepDescription = `${ + stepBetween ? 'new' : '' + } step ${descStepNum} instructions`; const challengeSeedSection = `
${challengeSeed.trim()} @@ -41,7 +50,7 @@ isHidden: true ## Description
-step ${stepNum} instructions +${stepDescription}
@@ -61,14 +70,13 @@ tests: ${challengeSeedSection} `; - fs.writeFileSync( - `${projectPath}part-${padWithLeadingZeros(stepNum)}.md`, - template - ); + let finalStepNum = padWithLeadingZeros(stepNum); + finalStepNum += stepBetween ? 'a' : ''; + fs.writeFileSync(`${projectPath}part-${finalStepNum}.md`, template); }; const reorderSteps = () => { - const projectPath = (process.env.CALLING_DIR || process.cwd()) + path.sep; + const projectPath = getProjectPath(); const projectName = process.env.CALLING_DIR ? process.env.CALLING_DIR.split(path.sep) @@ -110,6 +118,7 @@ const reorderSteps = () => { } } }); + if (foundFinal) { filesArr.push('final.md'); } @@ -161,8 +170,47 @@ const reorderSteps = () => { console.log('Reordered steps'); }; -module.exports = { - padWithLeadingZeros, - createStepFile, - reorderSteps +const getChallengeSeed = challengeFilePath => { + const fileContent = fs.readFileSync(challengeFilePath, 'utf8'); + const matchedSection = fileContent + .toString() + .match(/
(?[\s\S]+)<\/section>/); + let finalChallengeSeed = ''; + if (matchedSection) { + let { + groups: { challengeSeed } + } = matchedSection; + finalChallengeSeed = challengeSeed ? challengeSeed : ''; + } + return finalChallengeSeed; +}; + +const getExistingStepNums = projectPath => { + return fs.readdirSync(projectPath).reduce((stepNums, fileName) => { + if ( + path.extname(fileName).toLowerCase() === '.md' && + !fileName.endsWith('final.md') + ) { + let stepNum = fileName.split('.')[0].split('-')[1]; + if (!/^\d{3}$/.test(stepNum)) { + throw `Step not created. File ${fileName} has a step number containing non-digits.` + + ' Please run reorder-steps script first.'; + } + stepNum = parseInt(stepNum, 10); + stepNums.push(stepNum); + } + return stepNums; + }, []); +}; + +const getProjectPath = () => + (process.env.CALLING_DIR || process.cwd()) + path.sep; + +module.exports = { + createStepFile, + getChallengeSeed, + padWithLeadingZeros, + reorderSteps, + getExistingStepNums, + getProjectPath };