| /*! |
| * Bootstrap Grunt task for parsing Less docstrings |
| * https://getbootstrap.com/ |
| * Copyright 2014-2019 Twitter, Inc. |
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/v3-dev/LICENSE) |
| */ |
| |
| 'use strict'; |
| |
| var Markdown = require('markdown-it'); |
| |
| function markdown2html(markdownString) { |
| var md = new Markdown(); |
| |
| // 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; |