237 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			237 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 markdown = require('markdown').markdown; | ||
|  | 
 | ||
|  | function markdown2html(markdownString) { | ||
|  |   // the slice removes the <p>...</p> wrapper output by Markdown processor
 | ||
|  |   return markdown.toHTML(markdownString.trim()).slice(3, -4); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  | 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: #ffff; | ||
|  | 
 | ||
|  |   //-- 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; |