| /** |
| * This plugin creates a summary tag, if missing, from the first sentence in the description. |
| * |
| * @module plugins/summarize |
| */ |
| exports.handlers = { |
| /** |
| * Autogenerate summaries, if missing, from the description, if present. |
| */ |
| newDoclet({doclet}) { |
| let endTag; |
| let tags; |
| let stack; |
| |
| // If the summary is missing, grab the first sentence from the description |
| // and use that. |
| if (doclet && !doclet.summary && doclet.description) { |
| // The summary may end with `.$`, `. `, or `.<` (a period followed by an HTML tag). |
| doclet.summary = doclet.description.split(/\.$|\.\s|\.</)[0]; |
| // Append `.` as it was removed in both cases, or is possibly missing. |
| doclet.summary += '.'; |
| |
| // This is an excerpt of something that is possibly HTML. |
| // Balance it using a stack. Assume it was initially balanced. |
| tags = doclet.summary.match(/<[^>]+>/g) || []; |
| stack = []; |
| |
| tags.forEach(tag => { |
| const idx = tag.indexOf('/'); |
| |
| if (idx === -1) { |
| // start tag -- push onto the stack |
| stack.push(tag); |
| } else if (idx === 1) { |
| // end tag -- pop off of the stack |
| stack.pop(); |
| } |
| |
| // otherwise, it's a self-closing tag; don't modify the stack |
| }); |
| |
| // stack should now contain only the start tags that lack end tags, |
| // with the most deeply nested start tag at the top |
| while (stack.length > 0) { |
| // pop the unmatched tag off the stack |
| endTag = stack.pop(); |
| // get just the tag name |
| endTag = endTag.substring(1, endTag.search(/[ >]/)); |
| // append the end tag |
| doclet.summary += `</${endTag}>`; |
| } |
| |
| // and, finally, if the summary starts and ends with a <p> tag, remove it; let the |
| // template decide whether to wrap the summary in a <p> tag |
| doclet.summary = doclet.summary.replace(/^<p>(.*)<\/p>$/i, '$1'); |
| } |
| } |
| }; |