You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			239 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			239 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
| /*!
 | |
|  * Bootstrap Grunt task for parsing Less docstrings
 | |
|  * http://getbootstrap.com
 | |
|  * Copyright 2014 Twitter, Inc.
 | |
|  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 | |
|  */
 | |
| 'use strict';
 | |
| 
 | |
| var Remarkable = require('remarkable');
 | |
| 
 | |
| function markdown2html(markdownString) {
 | |
|   var md = new Remarkable();
 | |
| 
 | |
|   // the slice removes the <p>...</p> wrapper output by Markdown processor
 | |
|   return md.render(markdownString.trim()).slice(3, -5);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| Mini-language:
 | |
|   //== This is a normal heading, which starts a section. Sections group variables together.
 | |
|   //## Optional description for the heading
 | |
| 
 | |
|   //=== This is a subheading.
 | |
| 
 | |
|   //** Optional description for the following variable. You **can** use Markdown in descriptions to discuss `<html>` stuff.
 | |
|   @foo: #fff;
 | |
| 
 | |
|   //-- This is a heading for a section whose variables shouldn't be customizable
 | |
| 
 | |
|   All other lines are ignored completely.
 | |
| */
 | |
| 
 | |
| 
 | |
| var CUSTOMIZABLE_HEADING = /^[/]{2}={2}(.*)$/;
 | |
| var UNCUSTOMIZABLE_HEADING = /^[/]{2}-{2}(.*)$/;
 | |
| var SUBSECTION_HEADING = /^[/]{2}={3}(.*)$/;
 | |
| var SECTION_DOCSTRING = /^[/]{2}#{2}(.+)$/;
 | |
| var VAR_ASSIGNMENT = /^(@[a-zA-Z0-9_-]+):[ ]*([^ ;][^;]*);[ ]*$/;
 | |
| var VAR_DOCSTRING = /^[/]{2}[*]{2}(.+)$/;
 | |
| 
 | |
| function Section(heading, customizable) {
 | |
|   this.heading = heading.trim();
 | |
|   this.id = this.heading.replace(/\s+/g, '-').toLowerCase();
 | |
|   this.customizable = customizable;
 | |
|   this.docstring = null;
 | |
|   this.subsections = [];
 | |
| }
 | |
| 
 | |
| Section.prototype.addSubSection = function (subsection) {
 | |
|   this.subsections.push(subsection);
 | |
| };
 | |
| 
 | |
| function SubSection(heading) {
 | |
|   this.heading = heading.trim();
 | |
|   this.id = this.heading.replace(/\s+/g, '-').toLowerCase();
 | |
|   this.variables = [];
 | |
| }
 | |
| 
 | |
| SubSection.prototype.addVar = function (variable) {
 | |
|   this.variables.push(variable);
 | |
| };
 | |
| 
 | |
| function VarDocstring(markdownString) {
 | |
|   this.html = markdown2html(markdownString);
 | |
| }
 | |
| 
 | |
| function SectionDocstring(markdownString) {
 | |
|   this.html = markdown2html(markdownString);
 | |
| }
 | |
| 
 | |
| function Variable(name, defaultValue) {
 | |
|   this.name = name;
 | |
|   this.defaultValue = defaultValue;
 | |
|   this.docstring = null;
 | |
| }
 | |
| 
 | |
| function Tokenizer(fileContent) {
 | |
|   this._lines = fileContent.split('\n');
 | |
|   this._next = undefined;
 | |
| }
 | |
| 
 | |
| Tokenizer.prototype.unshift = function (token) {
 | |
|   if (this._next !== undefined) {
 | |
|     throw new Error('Attempted to unshift twice!');
 | |
|   }
 | |
|   this._next = token;
 | |
| };
 | |
| 
 | |
| Tokenizer.prototype._shift = function () {
 | |
|   // returning null signals EOF
 | |
|   // returning undefined means the line was ignored
 | |
|   if (this._next !== undefined) {
 | |
|     var result = this._next;
 | |
|     this._next = undefined;
 | |
|     return result;
 | |
|   }
 | |
|   if (this._lines.length <= 0) {
 | |
|     return null;
 | |
|   }
 | |
|   var line = this._lines.shift();
 | |
|   var match = null;
 | |
|   match = SUBSECTION_HEADING.exec(line);
 | |
|   if (match !== null) {
 | |
|     return new SubSection(match[1]);
 | |
|   }
 | |
|   match = CUSTOMIZABLE_HEADING.exec(line);
 | |
|   if (match !== null) {
 | |
|     return new Section(match[1], true);
 | |
|   }
 | |
|   match = UNCUSTOMIZABLE_HEADING.exec(line);
 | |
|   if (match !== null) {
 | |
|     return new Section(match[1], false);
 | |
|   }
 | |
|   match = SECTION_DOCSTRING.exec(line);
 | |
|   if (match !== null) {
 | |
|     return new SectionDocstring(match[1]);
 | |
|   }
 | |
|   match = VAR_DOCSTRING.exec(line);
 | |
|   if (match !== null) {
 | |
|     return new VarDocstring(match[1]);
 | |
|   }
 | |
|   var commentStart = line.lastIndexOf('//');
 | |
|   var varLine = (commentStart === -1) ? line : line.slice(0, commentStart);
 | |
|   match = VAR_ASSIGNMENT.exec(varLine);
 | |
|   if (match !== null) {
 | |
|     return new Variable(match[1], match[2]);
 | |
|   }
 | |
|   return undefined;
 | |
| };
 | |
| 
 | |
| Tokenizer.prototype.shift = function () {
 | |
|   while (true) {
 | |
|     var result = this._shift();
 | |
|     if (result === undefined) {
 | |
|       continue;
 | |
|     }
 | |
|     return result;
 | |
|   }
 | |
| };
 | |
| 
 | |
| function Parser(fileContent) {
 | |
|   this._tokenizer = new Tokenizer(fileContent);
 | |
| }
 | |
| 
 | |
| Parser.prototype.parseFile = function () {
 | |
|   var sections = [];
 | |
|   while (true) {
 | |
|     var section = this.parseSection();
 | |
|     if (section === null) {
 | |
|       if (this._tokenizer.shift() !== null) {
 | |
|         throw new Error('Unexpected unparsed section of file remains!');
 | |
|       }
 | |
|       return sections;
 | |
|     }
 | |
|     sections.push(section);
 | |
|   }
 | |
| };
 | |
| 
 | |
| Parser.prototype.parseSection = function () {
 | |
|   var section = this._tokenizer.shift();
 | |
|   if (section === null) {
 | |
|     return null;
 | |
|   }
 | |
|   if (!(section instanceof Section)) {
 | |
|     throw new Error('Expected section heading; got: ' + JSON.stringify(section));
 | |
|   }
 | |
|   var docstring = this._tokenizer.shift();
 | |
|   if (docstring instanceof SectionDocstring) {
 | |
|     section.docstring = docstring;
 | |
|   }
 | |
|   else {
 | |
|     this._tokenizer.unshift(docstring);
 | |
|   }
 | |
|   this.parseSubSections(section);
 | |
| 
 | |
|   return section;
 | |
| };
 | |
| 
 | |
| Parser.prototype.parseSubSections = function (section) {
 | |
|   while (true) {
 | |
|     var subsection = this.parseSubSection();
 | |
|     if (subsection === null) {
 | |
|       if (section.subsections.length === 0) {
 | |
|         // Presume an implicit initial subsection
 | |
|         subsection = new SubSection('');
 | |
|         this.parseVars(subsection);
 | |
|       }
 | |
|       else {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     section.addSubSection(subsection);
 | |
|   }
 | |
| 
 | |
|   if (section.subsections.length === 1 && !(section.subsections[0].heading) && section.subsections[0].variables.length === 0) {
 | |
|     // Ignore lone empty implicit subsection
 | |
|     section.subsections = [];
 | |
|   }
 | |
| };
 | |
| 
 | |
| Parser.prototype.parseSubSection = function () {
 | |
|   var subsection = this._tokenizer.shift();
 | |
|   if (subsection instanceof SubSection) {
 | |
|     this.parseVars(subsection);
 | |
|     return subsection;
 | |
|   }
 | |
|   this._tokenizer.unshift(subsection);
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| Parser.prototype.parseVars = function (subsection) {
 | |
|   while (true) {
 | |
|     var variable = this.parseVar();
 | |
|     if (variable === null) {
 | |
|       return;
 | |
|     }
 | |
|     subsection.addVar(variable);
 | |
|   }
 | |
| };
 | |
| 
 | |
| Parser.prototype.parseVar = function () {
 | |
|   var docstring = this._tokenizer.shift();
 | |
|   if (!(docstring instanceof VarDocstring)) {
 | |
|     this._tokenizer.unshift(docstring);
 | |
|     docstring = null;
 | |
|   }
 | |
|   var variable = this._tokenizer.shift();
 | |
|   if (variable instanceof Variable) {
 | |
|     variable.docstring = docstring;
 | |
|     return variable;
 | |
|   }
 | |
|   this._tokenizer.unshift(variable);
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| 
 | |
| module.exports = Parser;
 |