UNPKG

bit-docs-process-tags

Version:
248 lines (215 loc) 7.28 kB
var _ = require("lodash"); var defaultTags = require("./tags/tags"); var doubleAt = /@@/g; var matchTag = /^\s*@(\w+)/; var matchSpace = /^\s*/; /** * @parent bit-docs-process-tags/modules * @module {function} bit-docs-process-tags/process-tags * * @signature `processTags(options, callback)` * * Use this module to process a [bit-docs-process-tags/types/tagBlock] and use * any matching [bit-docs-process-tags/types/tag] in the * [bit-docs-process-tags/types/tagCollection] to further process the line and * return a new [bit-docs/types/docObject]. * * @param {bit-docs-process-tags/types/processTagsOptions} options * * The block to process and the [bit-docs-process-tags/types/tagCollection]. * * Notable options: * * - `tags`: [bit-docs-process-tags/types/tagCollection] to draw from. * - `comment`: [bit-docs-process-tags/types/tagBlock] being processed. * * @param {bit-docs/types/processorCallback} callback * * Callback to call when done processing. * * The new [bit-docs/types/docObject] might be null if no [bit-docs-process-tags/types/tag]s * in the [bit-docs-process-tags/types/tagCollection] matched a line in the * [bit-docs-process-tags/types/tagBlock]. * * @body * * ## Use * * This module manages a [bit-docs-process-tags/types/processTagsStack] using * [bit-docs-process-tags/types/processTagsCommand] commands. * * Plugins add to the [bit-docs-process-tags/types/tagCollection] using the * `tags` hook so the [bit-docs-process-tags/types/tag] processing rules are applied when * processing blocks. */ module.exports = function(options, addDocObjectToDocMap){ var docObject = options.docObject || {}, comment = options.content || options.comment, docMap = options.docMap, scope = options.scope, tags = _.defaults(options.tags || {}, defaultTags); var i = 0, lines = typeof comment == 'string' ? comment.split("\n") : comment, len = lines.length, // a stack of the tagData and tag typeDataStack = [], tagName, curTag, // the docData that a th curTagData, indentation, indentationStack = []; var state = { defaultWriteProp: undefined, docObject: docObject, scope: scope, docMap: docMap }; _.defaults(docObject,{ body: "", description: "" }); // for each line for ( var l = 0; l < len; l++ ) { // see if it starts with something that looks like a @tag var line = lines[l], match = line.match(matchTag); //console.log(">",line, indentationStack.map(function(foo){ return foo.tag.name }) ); // if we have a tag if ( match ) { // get the tag object tagName = match[1].toLowerCase(); curTag = tags[tagName]; indentation = line.match( matchSpace )[0]; // get the current data curTagData = getFromStack(indentationStack, indentation, state.docObject, curTag && curTag.keepStack); // if we don't have a tag object if (!curTag ) { // do default behavior tags._default.add.call(state.docObject, line, curTagData, state.scope, docMap, state.defaultWriteProp, options ); continue; } // call the tag types add method try{ curTagData = curTag.add.call(state.docObject, line, curTagData, state.scope, docMap, state.defaultWriteProp, options ); } catch(e){ console.log("ERROR:"); console.log(" tag -", tagName); console.log(" line-",line); throw e; } if(Array.isArray(curTagData) && typeof curTagData[0] === "string") { handleCtrl(curTagData, state, indentationStack, addDocObjectToDocMap); } else if ( curTagData ) { indentationStack.push({ tag: curTag, tagData: curTagData, indentation: indentation }); } // if no curTag data, it's a single line tag, keep things where they are } else { // we have a normal line //clean up @@abc becomes @abc line = line.replace(doubleAt, "@"); var last = _.last(indentationStack); // if we a lastTag (we are on a multi-line tag) if ( last && last.tag ) { // we should probably clean up the line line = line.replace(last.indentation,""); last.tag.addMore.call(state.docObject, line, last.tagData, state.scope, docMap); } else { // write to the default place writeToDefault(state, state.docObject, line); } } } // call end on any tags still left getFromStack(indentationStack, "", state.docObject); addDocObjectToDocMap(state.docObject, state.scope); }; // pop off the stack until indentation matches var getFromStack = function(indentationStack, indentation, docObject, keepStack ){ if(!keepStack) { while(indentationStack.length && _.last(indentationStack).indentation >= indentation) { var top = indentationStack.pop(); if(top.tag && top.tag.end) { top.tag.end.call(docObject, top.tagData); } } } return indentationStack.length ? _.last(indentationStack).tagData : docObject; }; var writeToDefault = function(state, docObject, line) { if(state.defaultWriteProp){ docObject[state.defaultWriteProp] += line + "\n"; } else { // if we don't have two newlines, keep adding to description if( docObject.body ){ docObject.body += line + "\n"; } else if(!docObject.description){ docObject.description += line + "\n"; } else if(!line || /^[\s]/.test( line ) ){ state.defaultWriteProp = "body"; docObject[state.defaultWriteProp] += line + "\n"; } else { docObject.description += line + "\n"; } } }; var handleCtrl = function(curTagData, state, stack, addDocObjectToDocMap){ // depending on curTagData, we do different things: // if we get ['push',{DATA}], this means we are an // 'inline' tag, meaning we are going to add // content to whatever tag we are currently in // @codestart and @codeend are the best examples of this var command = curTagData[0]; if ( command == 'push' ) { // // sets as the current object to add to stack.push({ tag: lastTag, tagData: lastTagData, indentation: "" }); // set ourselves as the current lastTag and the 2nd // item in the array as curTagData curData = curTagData[1]; lastTag = curTag; } // if we get ['pop', text], // add text to the previous parent tag else if ( command == 'pop' || command == 'poppop' ) { // get the last tag var last = stack.pop(); if ( command === 'poppop' ) { last = stack.pop(); } // as long as we had a previous tag if ( last && last.tag ) { //call the previous tag's addMore last.tag.addMore.call(state.docObject, curTagData[1], last.tagData); } else { // otherwise, add to the default place to write to state.docObject[state.defaultWriteProp || "body"] += "\n" + curTagData[1] } } else if ( command == 'scope') { // allow the total replacement of docObject for @add if(curTagData[2]) { state.docObject = curTagData[2]; } // might need to change the head of the scope // curData = state.scope = curTagData[1]; } else if ( command == 'default' ) { // if we get ['default',PROPNAME] // we change default write to prop name // this will make it so if we aren't in a tag, all default // lines to to the defaultWriteProp // this is used by @constructor state.defaultWriteProp = curTagData[1]; stack.splice(0, stack.length); } else if( command == 'add') { // we are adding something docMap addDocObjectToDocMap(curTagData[1]); } };