|
| 1 | +var _ = require("lodash"); |
| 2 | + |
| 3 | +var doubleAt = /@@/g, |
| 4 | + matchTag = /^\s*@(\w+)/, |
| 5 | + matchSpace = /^\s*/; |
| 6 | +/** |
| 7 | + * @function documentjs.process.comment |
| 8 | + * @parent documentjs.process.methods |
| 9 | + * |
| 10 | + * @signature `documentjs.process.comment(options, callback)` |
| 11 | + * |
| 12 | + * Processes a comment and produces a docObject. |
| 13 | + * |
| 14 | + * @param {documentjs.process.processOptions} options An options object that contains |
| 15 | + * the code and comment to process. |
| 16 | + * |
| 17 | + * @param {function(documentjs.process.docObject,documentjs.process.docObject)} callback(newDoc,newScope) |
| 18 | + * |
| 19 | + * A function that is called back with a docObject created from the code and the scope |
| 20 | + * `docObject`. If |
| 21 | + * no docObject is created, `newDoc` will be null. |
| 22 | + * |
| 23 | + * @body |
| 24 | + * |
| 25 | + * ## Processing rules |
| 26 | + * |
| 27 | + * The processing rules can be found in the [documentjs.Tag Tag interface]. |
| 28 | + */ |
| 29 | +module.exports = function(options, addDocObjectToDocMap){ |
| 30 | + |
| 31 | + var docObject = options.docObject || {}, |
| 32 | + comment = options.content || options.comment, |
| 33 | + docMap = options.docMap, |
| 34 | + scope = options.scope, |
| 35 | + tags = options.tags || defaultTags; |
| 36 | + |
| 37 | + |
| 38 | + var i = 0, |
| 39 | + lines = typeof comment == 'string' ? comment.split("\n") : comment, |
| 40 | + len = lines.length, |
| 41 | + // a stack of the tagData and tag |
| 42 | + typeDataStack = [], |
| 43 | + tagName, |
| 44 | + curTag, |
| 45 | + // the docData that a th |
| 46 | + curTagData, |
| 47 | + |
| 48 | + indentation, |
| 49 | + indentationStack = []; |
| 50 | + |
| 51 | + var state = { |
| 52 | + defaultWriteProp: undefined, |
| 53 | + docObject: docObject, |
| 54 | + scope: scope, |
| 55 | + docMap: docMap |
| 56 | + }; |
| 57 | + |
| 58 | + _.defaults(docObject,{ |
| 59 | + body: "", |
| 60 | + description: "" |
| 61 | + }); |
| 62 | + |
| 63 | + // for each line |
| 64 | + for ( var l = 0; l < len; l++ ) { |
| 65 | + |
| 66 | + // see if it starts with something that looks like a @tag |
| 67 | + var line = lines[l], |
| 68 | + match = line.match(matchTag); |
| 69 | + |
| 70 | + //console.log(">",line, indentationStack.map(function(foo){ return foo.tag.name }) ); |
| 71 | + |
| 72 | + // if we have a tag |
| 73 | + if ( match ) { |
| 74 | + |
| 75 | + // get the tag object |
| 76 | + tagName = match[1].toLowerCase(); |
| 77 | + curTag = tags[tagName]; |
| 78 | + |
| 79 | + indentation = line.match( matchSpace )[0]; |
| 80 | + |
| 81 | + // get the current data |
| 82 | + curTagData = getFromStack(indentationStack, indentation, state.docObject, curTag && curTag.keepStack); |
| 83 | + |
| 84 | + // if we don't have a tag object |
| 85 | + if (!curTag ) { |
| 86 | + // do default behavior |
| 87 | + tags._default.add.call(state.docObject, line, curTagData, state.scope, docMap, state.defaultWriteProp, options ); |
| 88 | + continue; |
| 89 | + } |
| 90 | + |
| 91 | + // call the tag types add method |
| 92 | + try{ |
| 93 | + curTagData = curTag.add.call(state.docObject, line, curTagData, state.scope, docMap, state.defaultWriteProp, options ); |
| 94 | + } catch(e){ |
| 95 | + console.log("ERROR:"); |
| 96 | + console.log(" tag -", tagName); |
| 97 | + console.log(" line-",line); |
| 98 | + throw e; |
| 99 | + } |
| 100 | + |
| 101 | + if(Array.isArray(curTagData) && typeof curTagData[0] === "string") { |
| 102 | + |
| 103 | + handleCtrl(curTagData, state, indentationStack, addDocObjectToDocMap); |
| 104 | + |
| 105 | + } else if ( curTagData ) { |
| 106 | + |
| 107 | + indentationStack.push({ |
| 108 | + tag: curTag, |
| 109 | + tagData: curTagData, |
| 110 | + indentation: indentation |
| 111 | + }); |
| 112 | + |
| 113 | + } // if no curTag data, it's a single line tag, keep things where they are |
| 114 | + |
| 115 | + } |
| 116 | + else { |
| 117 | + // we have a normal line |
| 118 | + //clean up @@abc becomes @abc |
| 119 | + line = line.replace(doubleAt, "@"); |
| 120 | + |
| 121 | + var last = _.last(indentationStack); |
| 122 | + |
| 123 | + // if we a lastTag (we are on a multi-line tag) |
| 124 | + if ( last && last.tag ) { |
| 125 | + // we should probably clean up the line |
| 126 | + line = line.replace(last.indentation,""); |
| 127 | + |
| 128 | + last.tag.addMore.call(state.docObject, line, last.tagData, state.scope, docMap); |
| 129 | + } else { |
| 130 | + // write to the default place |
| 131 | + writeToDefault(state, state.docObject, line); |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + // call end on any tags still left |
| 137 | + getFromStack(indentationStack, "", state.docObject); |
| 138 | + addDocObjectToDocMap(state.docObject, state.scope); |
| 139 | +}; |
| 140 | + |
| 141 | +// pop off the stack until indentation matches |
| 142 | +var getFromStack = function(indentationStack, indentation, docObject, keepStack ){ |
| 143 | + if(!keepStack) { |
| 144 | + while(indentationStack.length && _.last(indentationStack).indentation >= indentation) { |
| 145 | + var top = indentationStack.pop(); |
| 146 | + if(top.tag && top.tag.end) { |
| 147 | + top.tag.end.call(docObject, top.tagData); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + return indentationStack.length ? _.last(indentationStack).tagData : docObject; |
| 152 | +}; |
| 153 | + |
| 154 | +var writeToDefault = function(state, docObject, line) { |
| 155 | + if(state.defaultWriteProp){ |
| 156 | + docObject[state.defaultWriteProp] += line + "\n"; |
| 157 | + } else { |
| 158 | + // if we don't have two newlines, keep adding to description |
| 159 | + if( docObject.body ){ |
| 160 | + docObject.body += line + "\n"; |
| 161 | + } else if(!docObject.description){ |
| 162 | + docObject.description += line + "\n"; |
| 163 | + } else if(!line || /^[\s]/.test( line ) ){ |
| 164 | + state.defaultWriteProp = "body"; |
| 165 | + docObject[state.defaultWriteProp] += line + "\n"; |
| 166 | + } else { |
| 167 | + docObject.description += line + "\n"; |
| 168 | + } |
| 169 | + } |
| 170 | +}; |
| 171 | + |
| 172 | +var handleCtrl = function(curTagData, state, stack, addDocObjectToDocMap){ |
| 173 | + // depending on curTagData, we do different things: |
| 174 | + // if we get ['push',{DATA}], this means we are an |
| 175 | + // 'inline' tag, meaning we are going to add |
| 176 | + // content to whatever tag we are currently in |
| 177 | + // @codestart and @codeend are the best examples of this |
| 178 | + var command = curTagData[0]; |
| 179 | + |
| 180 | + if ( command == 'push' ) { // |
| 181 | + // sets as the current object to add to |
| 182 | + stack.push({ |
| 183 | + tag: lastTag, |
| 184 | + tagData: lastTagData, |
| 185 | + indentation: "" |
| 186 | + }); |
| 187 | + // set ourselves as the current lastTag and the 2nd |
| 188 | + // item in the array as curTagData |
| 189 | + curData = curTagData[1]; |
| 190 | + lastTag = curTag; |
| 191 | + } |
| 192 | + // if we get ['pop', text], |
| 193 | + // add text to the previous parent tag |
| 194 | + else if ( command == 'pop' || command == 'poppop' ) { |
| 195 | + // get the last tag |
| 196 | + var last = stack.pop(); |
| 197 | + if ( command === 'poppop' ) { |
| 198 | + last = stack.pop(); |
| 199 | + } |
| 200 | + // as long as we had a previous tag |
| 201 | + if ( last && last.tag ) { |
| 202 | + //call the previous tag's addMore |
| 203 | + last.tag.addMore.call(state.docObject, curTagData[1], last.tagData); |
| 204 | + } else { |
| 205 | + // otherwise, add to the default place to write to |
| 206 | + state.docObject[state.defaultWriteProp || "body"] += "\n" + curTagData[1] |
| 207 | + } |
| 208 | + } else if ( command == 'scope') { |
| 209 | + // allow the total replacement of docObject for @add |
| 210 | + if(curTagData[2]) { |
| 211 | + state.docObject = curTagData[2]; |
| 212 | + } |
| 213 | + |
| 214 | + // might need to change the head of the scope |
| 215 | + // curData = |
| 216 | + state.scope = curTagData[1]; |
| 217 | + |
| 218 | + } else if ( command == 'default' ) { |
| 219 | + // if we get ['default',PROPNAME] |
| 220 | + // we change default write to prop name |
| 221 | + // this will make it so if we aren't in a tag, all default |
| 222 | + // lines to to the defaultWriteProp |
| 223 | + // this is used by @constructor |
| 224 | + state.defaultWriteProp = curTagData[1]; |
| 225 | + stack.splice(0, stack.length); |
| 226 | + } else if( command == 'add') { |
| 227 | + // we are adding something docMap |
| 228 | + addDocObjectToDocMap(curTagData[1]); |
| 229 | + } |
| 230 | +}; |
0 commit comments