/* jshint node: true */ /*! * 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) */ var markdown = require('markdown').markdown; function markdown2html(markdownString) { // the slice removes the

...

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 `` 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;