| 
									
										
										
										
											2020-11-27 19:02:05 +01:00
										 |  |  | 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( | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |         `Line ${ | 
					
						
							|  |  |  |           baseLineNumber + index | 
					
						
							|  |  |  |         } has two markers. Each line should only have one.`
 | 
					
						
							| 
									
										
										
										
											2020-11-27 19:02:05 +01:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (line.match(formattedMarkerRE)) { | 
					
						
							|  |  |  |       throw Error( | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |         `Line ${ | 
					
						
							|  |  |  |           baseLineNumber + index | 
					
						
							|  |  |  |         } has a malformed marker. It should be --fcc-editable-region--`
 | 
					
						
							| 
									
										
										
										
											2020-11-27 19:02:05 +01:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = addSeeds; | 
					
						
							|  |  |  | module.exports.editableRegionMarker = editableRegionMarker; |