// Fragile, hacky script that finds exercises in chapters, extracts // their starting code, and collects it into a big JSON object // together with the solution code. const PJSON = require("./pseudo_json") var fs = require("fs"); var output = [], failed = false; var allSolutions = fs.readdirSync("code/solutions/").filter(function(file) { return !/^2[012]/.test(file); }); var dir = fs.readdirSync("."); dir.sort(); dir.forEach(function(file) { var match = /^((\d+).*).md$/.exec(file), chapNum = match && match[2]; if (!match) return; var text = fs.readFileSync(file, "utf8"); let meta = (/{{meta (.*)}}/.exec(text) || {1: "{}"})[1] var includes = /\bload_files: (\[.*?\])/.exec(meta) if (includes) includes = JSON.parse(includes[1]); var chapter = {number: +chapNum, id: match[1], title: text.match(/(?:^|\n)# (.*?)\n/)[1], start_code: getStartCode(text, includes), exercises: [], include: includes}; var zip = chapterZipFile(text, chapter); var extraLinks = meta.match(/\bcode_links: (\[.*?\])/); if (extraLinks) extraLinks = JSON.parse(extraLinks[1]); if (extraLinks || zip) chapter.links = (zip ? [zip] : []).concat(extraLinks || []); var exerciseSection = text.indexOf("\n## Exercises\n"); var exerciseBlock = exerciseSection >= 0 ? text.slice(exerciseSection) : ""; var header = /\n### (.*?)\n/g, nextHeader = /\n##+ \w/g; var num = 1; while (match = header.exec(exerciseBlock)) { nextHeader.lastIndex = header.lastIndex let foundNext = nextHeader.exec(exerciseBlock) var nextsection = foundNext ? foundNext.index : -1 for (var pos = header.lastIndex;;) { var ifdef = exerciseBlock.indexOf("{{if interactive", pos); if (ifdef == -1 || nextsection > 0 && nextsection < ifdef) break; var indef = exerciseBlock.slice(pos = ifdef + 15, exerciseBlock.indexOf("if}}", ifdef)); var sourceBlock = indef.match(/```(.*)\n([^]+?)\n```/); if (!sourceBlock || sourceBlock[1].indexOf("null") > -1) continue; var type = sourceBlock[1].indexOf("html") > -1 ? "html" : "js"; var file = chapNum + "_" + num + "_" + match[1].toLowerCase().replace(/[^\-\s\w]/g, "").replace(/\s/g, "_") + "." + type; try { var solution = fs.readFileSync("code/solutions/" + file, "utf8"); var extra = /^\s*\s*(\n\n\n", include: ["code/draw_graph.js", "code/chapter/22_fast.js"], exercises: [ {name: "Pathfinding", file: "code/solutions/22_1_pathfinding.js", number: 1, type: "js", code: "function findPath(a, b) {\n // Your code here...\n}\n\nvar graph = treeGraph(4, 4);\nvar root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", solution: fs.readFileSync("code/solutions/22_1_pathfinding.js", "utf8") }, {name: "Timing", file: "code/solutions/22_2_timing.js", number: 2, type: "js", code: "", solution: fs.readFileSync("code/solutions/22_2_timing.js", "utf8") }, {name: "Optimizing", file: "code/solutions/22_3_optimizing.js", number: 3, type: "js", code: "", solution: fs.readFileSync("code/solutions/22_3_optimizing.js", "utf8") } ] }); if (allSolutions.length) { console.error("Solution files " + allSolutions + " were not used."); failed = true; } if (!failed) console.log("var chapterData = " + JSON.stringify(output, null, 2) + ";"); else process.exit(1); function prepareHTML(code, include) { return "\n" + (include || []).map(function(s) { return "\n"; }).join("") + "\n" + code; } function guessType(code) { return /^[\s\w\n:]*", chapter.include); zip.file(chapter.id + "/index.html", html); } if (spec[1].indexOf("node") != -1) { zip.file(chapter.id + "/code/load.js", fs.readFileSync("code/load.js", "utf8")); var js = chapter.start_code; if (chapter.include) js = "// load dependencies\nrequire(\"./code/load\")(" + chapter.include.map(JSON.stringify).join(", ") + ");\n\n" + js; zip.file(chapter.id + "/run_with_node.js", js); } fs.writeFileSync(name, zip.generate({type: "nodebuffer"})); return name; }