280 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * NOT a dgeni service because we need to be able to use it externally
 | |
|  * as well as from dgeni.
 | |
|  * @description
 | |
|  *
 | |
|  */
 | |
| var _ = require('lodash');
 | |
| 
 | |
| var nullLine = '###';
 | |
| var nullLinePattern = new RegExp(nullLine + '\n', 'g');
 | |
| 
 | |
| module.exports = {
 | |
|   buildRegionDocs: buildRegionDocs,
 | |
|   getRegionDoc: getRegionDoc,
 | |
|   removeDocTags: removeDocTags
 | |
| };
 | |
| 
 | |
| // split out each fragment in {content} into a separate doc
 | |
| // a fragment is a section of text surrounded by
 | |
| //   1) In front: a comment marker followed by '#docregion' followed by an optional region name. For example:
 | |
| //        <-- #docregion foo --> for html
 | |
| //     or // #docregion foo      for js/ts
 | |
| //   2) In back: a comment marker followed by '#enddocregion'
 | |
| // Regions can be nested and any regions not 'closed' are automatically closed at the end of the doc.
 | |
| 
 | |
| // empty enddocregion always closes last region started.
 | |
| // enddocregions with names that do no match start region tags get ignored.
 | |
| function buildRegionDocs(content, extn) {
 | |
|   var commentInfo = getCommentInfo(extn);
 | |
|   if (!commentInfo) {
 | |
|     return  [ { content: content } ];
 | |
|   }
 | |
| 
 | |
|   var lines = result = content.split(/\r?\n/);
 | |
| 
 | |
|   var docStack = []; // items will be both popped and removed from the middle
 | |
|   var docMap = {};
 | |
|   var docPlaster = '. . .';
 | |
|   var doc;
 | |
|   var regionNames;
 | |
|   lines.forEach(function(line, ix) {
 | |
|     if (isCommentLine(line, commentInfo.prefix)) {
 | |
|       if (hasRegionTag(line)) {
 | |
|         lines[ix] = nullLine;
 | |
|         regionNames = getRegionNames(line);
 | |
|         regionNames.forEach(function(rn) {
 | |
|           doc = docMap[rn];
 | |
|           if (!doc) {
 | |
|             // regionName may be ''
 | |
|             doc = {regionName: rn, ranges: [ { startIx: ix} ] };
 | |
|             docMap[rn] = doc;
 | |
|           } else {
 | |
|             // only add a new range if prev range is closed
 | |
|             var lastRange = doc.ranges[doc.ranges.length-1];
 | |
|             if (lastRange.endIx) {
 | |
|               doc.ranges.push({startIx: ix});
 | |
|             }
 | |
|           }
 | |
|           docStack.push(doc);
 | |
|         });
 | |
| 
 | |
|       } else if (hasEndRegionTag(line)) {
 | |
|         lines[ix] = nullLine;
 | |
|         regionNames = getEndRegionNames(line);
 | |
|         regionNames.forEach(function(rn) {
 | |
|           // handle endregions with no name specially.
 | |
|           // They operate on the last region created.
 | |
|           if (rn.length == 0) {
 | |
|             if (docStack.length) {
 | |
|               // update last item on the stack
 | |
|               doc = docStack.pop();
 | |
|               doc.ranges[doc.ranges.length - 1].endIx = ix;
 | |
|             }
 | |
|           } else {
 | |
|             doc = docMap[rn];
 | |
|             // ignore endregion if name is specified but not found.
 | |
|             if (doc) {
 | |
|               doc.ranges[doc.ranges.length - 1].endIx = ix;
 | |
|               // remove doc from stack
 | |
|               _.remove(docStack, function (item) {
 | |
|                 return item.regionName === rn;
 | |
|               });
 | |
|             }
 | |
|           }
 | |
|         });
 | |
|       } else if (hasDocPlasterTag(line)) {
 | |
|         line[ix] = nullLine;
 | |
|         docPlaster =  getDocPlaster(line);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   var docs = _.values(docMap);
 | |
|   var plasterComment = docPlaster && commentInfo.plasterPattern.replace('{tag}', docPlaster);
 | |
|   docs = reattachDocs(docs, lines, plasterComment);
 | |
|   return docs;
 | |
| }
 | |
| 
 | |
| function getRegionDoc(content, extn, regionName) {
 | |
|   var docs = buildRegionDocs(content, extn);
 | |
|   var doc = _.find(docs, function (doc) {
 | |
|     return doc.regionName === regionName;
 | |
|   });
 | |
|   return doc && doc.content;
 | |
| }
 | |
| 
 | |
| function removeDocTags(content, extn) {
 | |
|   var commentInfo = getCommentInfo(extn);
 | |
|   if (commentInfo == null) {
 | |
|     return content;
 | |
|   }
 | |
|   var lines = result = content.split(/\r?\n/);
 | |
| 
 | |
|   lines.forEach(function(line, ix) {
 | |
|     if (isCommentLine(line, commentInfo.prefix)) {
 | |
|       if (hasDocTag(line)) {
 | |
|         lines[ix] = nullLine;
 | |
|       }
 | |
|     }
 | |
|   });
 | |
|   var result = joinLines(lines);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| function reattachDocs(docs, lines, plasterComment) {
 | |
| 
 | |
|   docs.forEach(function(doc) {
 | |
|     var content;
 | |
|     var fragLines = [];
 | |
|     doc.ranges.forEach(function (range) {
 | |
|       var subLines;
 | |
|       if (range.endIx) {
 | |
|         subLines = lines.slice(range.startIx + 1, range.endIx);
 | |
|       } else {
 | |
|         subLines = lines.slice(range.startIx + 1);
 | |
|       }
 | |
|       if (plasterComment && fragLines.length) {
 | |
|         // pad is the padding on the previous line
 | |
|         var pad = fragLines[fragLines.length - 1].match(/(\s*)/)[0];
 | |
|         fragLines.push(pad + plasterComment);
 | |
|       }
 | |
|       fragLines = fragLines.concat(subLines);
 | |
|     });
 | |
|     fragLines = trimLeftIndent(fragLines);
 | |
|     doc.content = joinLines(fragLines);
 | |
|   });
 | |
|   return docs;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| function getCommentInfo(extension) {
 | |
|   var commentInfo;
 | |
|   switch (extension) {
 | |
|     case 'ts':
 | |
|     case 'js':
 | |
|     case 'dart':
 | |
|       commentInfo = {
 | |
|         prefix: '//',
 | |
|         plasterPattern: '/* {tag} */'
 | |
|       };
 | |
|       break;
 | |
|     case 'html':
 | |
|       commentInfo = {
 | |
|         prefix: '<!--',
 | |
|         plasterPattern: '<!-- {tag} -->'
 | |
|       };
 | |
|       break;
 | |
|     case 'css':
 | |
|       commentInfo = {
 | |
|         prefix: '/*',
 | |
|         plasterPattern: '/* {tag} */'
 | |
|       };
 | |
|       break;
 | |
|     case 'json':
 | |
|       return null;
 | |
|     case 'yaml':
 | |
|       commentInfo = {
 | |
|         prefix: '#',
 | |
|         plasterPattern: '# {tag} '
 | |
|       };
 | |
|       break;
 | |
|     case 'jade':
 | |
|       commentInfo = {
 | |
|         prefix: '//',
 | |
|         plasterPattern: '// {tag} '
 | |
|       };
 | |
|       break;
 | |
|     default:
 | |
|       return null;
 | |
|   }
 | |
|   return commentInfo;
 | |
| }
 | |
| 
 | |
| function isCommentLine(line, commentPrefix) {
 | |
|   return line.trim().indexOf(commentPrefix) == 0;
 | |
| }
 | |
| 
 | |
| function hasDocTag(line) {
 | |
|   return hasRegionTag(line) || hasEndRegionTag(line) || hasDocPlasterTag(line);
 | |
| }
 | |
| 
 | |
| function hasRegionTag(line) {
 | |
|   return line.indexOf("#docregion") >= 0;
 | |
| }
 | |
| 
 | |
| function hasEndRegionTag(line) {
 | |
|   return line.indexOf("#enddocregion") >= 0;
 | |
| }
 | |
| 
 | |
| function hasDocPlasterTag(line) {
 | |
|   return line.indexOf("#docplaster") >= 0;
 | |
| }
 | |
| 
 | |
| function getRegionNames(line) {
 | |
|   return extractRegionNames(line, /#docregion\s*(\S.*)/);
 | |
| }
 | |
| 
 | |
| function getEndRegionNames(line) {
 | |
|   return extractRegionNames(line, /#enddocregion\s*(\S.*)/);
 | |
| }
 | |
| 
 | |
| function getDocPlaster(line) {
 | |
|   var rx =  /#docplaster\s*(\S.*)/;
 | |
|   try {
 | |
|     var plaster = line.match(rx)[1];
 | |
|     plaster = plaster.replace("-->","").replace('\*\/',"");
 | |
|     return plaster.trim();
 | |
|   } catch (e) {
 | |
|     return null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function extractRegionNames(line, rx) {
 | |
|   try {
 | |
|     var names = line.match(rx)[1];
 | |
|     names = names.replace(/\s*/g,'');
 | |
|     // Hack for html regions that look like <!-- #docregion --> or */
 | |
|     names = names.replace("-->","").replace('\*\/',"");
 | |
|     names = names.split(',');
 | |
|     return names;
 | |
|   } catch (e) {
 | |
|     return [''];
 | |
|   }
 | |
| }
 | |
| 
 | |
| function trimLeftIndent(lines) {
 | |
|   var minIx  = 100;
 | |
|   var ok = lines.every(function(line) {
 | |
|     // var ix = line.search(/\S/);
 | |
|     var ix = line.search(/[^ ]/);
 | |
|     if (ix === 0) return false;
 | |
|     if (ix === -1) return true;
 | |
|     if (ix > 0) {
 | |
|       minIx = Math.min(minIx, ix);
 | |
|     }
 | |
|     return true;
 | |
|   });
 | |
|   if ( (!ok) || minIx === 100) return lines;
 | |
| 
 | |
|   var result = lines.map(function(line) {
 | |
|     if (line.length > minIx) {
 | |
|       return line.substr(minIx);
 | |
|     } else {
 | |
|       // this can happen if line is all blanks and shorter than mixIx
 | |
|       return line;
 | |
|     }
 | |
|   });
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| function joinLines(lines) {
 | |
|   var content = lines.join('\n');
 | |
|   // eliminate all #docregion lines
 | |
|   content = content.replace(nullLinePattern, '');
 | |
|   if (content.substr(-3) === nullLine) {
 | |
|     content = content.substr(0, content.length - 3);
 | |
|   }
 | |
|   return content;
 | |
| } |