140 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const blockC = require('./region-matchers/block-c');
 | |
| const html = require('./region-matchers/html');
 | |
| const inlineC = require('./region-matchers/inline-c');
 | |
| const inlineCOnly = require('./region-matchers/inline-c-only');
 | |
| const inlineHash = require('./region-matchers/inline-hash');
 | |
| const NO_NAME_REGION = '';
 | |
| const DEFAULT_PLASTER = '. . .';
 | |
| const {mapObject} = require('../utils');
 | |
| 
 | |
| module.exports = function regionParser() {
 | |
|   return regionParserImpl;
 | |
| };
 | |
| 
 | |
| regionParserImpl.regionMatchers = {
 | |
|   ts: inlineC,
 | |
|   js: inlineC,
 | |
|   es6: inlineC,
 | |
|   dart: inlineC,
 | |
|   html: html,
 | |
|   css: blockC,
 | |
|   yaml: inlineHash,
 | |
|   jade: inlineCOnly,
 | |
|   json: inlineC,
 | |
|   'json.annotated': inlineC
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param contents string
 | |
|  * @param fileType string
 | |
|  * @returns {contents: string, regions: {[regionName: string]: string}}
 | |
|  */
 | |
| function regionParserImpl(contents, fileType) {
 | |
|   const regionMatcher = regionParserImpl.regionMatchers[fileType];
 | |
|   const openRegions = [];
 | |
|   const regionMap = {};
 | |
| 
 | |
|   if (regionMatcher) {
 | |
|     let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
 | |
|     const lines = contents.split(/\r?\n/).filter((line, index) => {
 | |
|       const startRegion = line.match(regionMatcher.regionStartMatcher);
 | |
|       const endRegion = line.match(regionMatcher.regionEndMatcher);
 | |
|       const updatePlaster = line.match(regionMatcher.plasterMatcher);
 | |
| 
 | |
|       // start region processing
 | |
|       if (startRegion) {
 | |
|         // open up the specified region
 | |
|         const regionNames = getRegionNames(startRegion[1]);
 | |
|         if (regionNames.length === 0) {
 | |
|           regionNames.push('');
 | |
|         }
 | |
|         regionNames.forEach(regionName => {
 | |
|           const region = regionMap[regionName];
 | |
|           if (region) {
 | |
|             if (region.open) {
 | |
|               throw new RegionParserError(
 | |
|                   `Tried to open a region, named "${regionName}", that is already open`, index);
 | |
|             }
 | |
|             region.open = true;
 | |
|             region.lines.push(plaster);
 | |
|           } else {
 | |
|             regionMap[regionName] = {lines: [], open: true};
 | |
|           }
 | |
|           openRegions.push(regionName);
 | |
|         });
 | |
| 
 | |
|         // end region processing
 | |
|       } else if (endRegion) {
 | |
|         if (openRegions.length === 0) {
 | |
|           throw new RegionParserError('Tried to close a region when none are open', index);
 | |
|         }
 | |
|         // close down the specified region (or most recent if no name is given)
 | |
|         const regionNames = getRegionNames(endRegion[1]);
 | |
|         if (regionNames.length === 0) {
 | |
|           regionNames.push(openRegions[openRegions.length - 1]);
 | |
|         }
 | |
| 
 | |
|         regionNames.forEach(regionName => {
 | |
|           const region = regionMap[regionName];
 | |
|           if (!region || !region.open) {
 | |
|             throw new RegionParserError(
 | |
|                 `Tried to close a region, named "${regionName}", that is not open`, index);
 | |
|           }
 | |
|           region.open = false;
 | |
|           removeLast(openRegions, regionName);
 | |
|         });
 | |
| 
 | |
|         // doc plaster processing
 | |
|       } else if (updatePlaster) {
 | |
|         plaster = regionMatcher.createPlasterComment(updatePlaster[1].trim());
 | |
| 
 | |
|         // simple line of content processing
 | |
|       } else {
 | |
|         openRegions.forEach(regionName => regionMap[regionName].lines.push(line));
 | |
|         // do not filter out this line from the content
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       // this line contained an annotation so let's filter it out
 | |
|       return false;
 | |
|     });
 | |
|     if (!regionMap['']) {
 | |
|       regionMap[''] = {lines};
 | |
|     }
 | |
|     return {
 | |
|       contents: lines.join('\n'),
 | |
|       regions: mapObject(regionMap, (regionName, region) => leftAlign(region.lines).join('\n'))
 | |
|     };
 | |
|   } else {
 | |
|     return {contents, regions: {}};
 | |
|   }
 | |
| }
 | |
| 
 | |
| function getRegionNames(input) {
 | |
|   return (input.trim() === '') ? [] : input.split(',').map(name => name.trim());
 | |
| }
 | |
| 
 | |
| function removeLast(array, item) {
 | |
|   const index = array.lastIndexOf(item);
 | |
|   array.splice(index, 1);
 | |
| }
 | |
| 
 | |
| function leftAlign(lines) {
 | |
|   let indent = Number.MAX_VALUE;
 | |
|   lines.forEach(line => {
 | |
|     const lineIndent = line.search(/\S/);
 | |
|     if (lineIndent !== -1) {
 | |
|       indent = Math.min(lineIndent, indent);
 | |
|     }
 | |
|   });
 | |
|   return lines.map(line => line.substr(indent));
 | |
| }
 | |
| 
 | |
| function RegionParserError(message, index) {
 | |
|   const lineNum = index + 1;
 | |
|   this.message = `regionParser: ${message} (at line ${lineNum}).`;
 | |
|   this.lineNum = lineNum;
 | |
|   this.stack = (new Error()).stack;
 | |
| }
 | |
| RegionParserError.prototype = Object.create(Error.prototype);
 | |
| RegionParserError.prototype.constructor = RegionParserError; |