splunk-sdk
Version:
SDK for usage with the Splunk REST API
293 lines (265 loc) • 6.9 kB
JavaScript
/*!
* Dox
* Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/*!
* Module dependencies.
*/
var markdown = require('./markdown').parse
, escape = require('./utils').escape;
/**
* Library version.
*/
exports.version = '0.1.2';
/**
* Parse comments in the given string of `js`.
*
* @param {String} js
* @return {Array}
* @see exports.parseComment
* @api public
*/
exports.parseComments = function(js, filename){
var comments = []
, comment
, buf = ''
, ignore
, within
, code
, curLine = 1;
for (var i = 0, len = js.length; i < len; ++i) {
// start comment
if (!within && '/' == js[i] && '*' == js[i+1]) {
// code following previous comment
if (buf.trim().length) {
comment = comments[comments.length - 1];
comment.code = code = buf.trim();
comment.ctx = exports.parseCodeContext(code);
buf = '';
}
i += 2;
within = true;
ignore = '!' == js[i];
// end comment
} else if (within && '*' == js[i] && '/' == js[i+1]) {
i += 2;
buf = buf.replace(/^ *\* ?/gm, '');
var comment = exports.parseComment(buf);
comment.ignore = ignore;
comment.line = curLine + 1;
comment.filename = filename;
comments.push(comment);
within = ignore = false;
buf = '';
// buffer comment or code
} else {
buf += js[i];
}
if (js[i] === '\n') {
curLine++;
}
}
// trailing code
if (buf.trim().length) {
comment = comments[comments.length - 1];
code = buf.trim();
comment.code = code;
comment.ctx = exports.parseCodeContext(code);
}
return comments;
};
/**
* Parse the given comment `str`.
*
* The comment object returned contains the following
*
* - `tags` array of tag objects
* - `description` the first line of the comment
* - `body` lines following the description
* - `content` both the description and the body
* - `isPrivate` true when "@api private" is used
*
* @param {String} str
* @return {Object}
* @see exports.parseTag
* @api public
*/
exports.parseComment = function(str) {
str = str.trim();
var comment = { tags: [] }
, description = {};
// parse comment body
description.full = str.split('\n@')[0].replace(/^([\w ]+):$/gm, '## $1');
description.summary = description.full.split('\n\n')[0];
description.body = description.full.split('\n\n').slice(1).join('\n\n');
comment.description = description;
// parse tags
if (~str.indexOf('\n@')) {
var tags = '@' + str.split('\n@').slice(1).join('\n@');
// Allow for multi-line tags
var rawLines = tags.split('\n');
var lines = [];
rawLines.forEach(function(line) {
var trimmedLine = line.trim();
if (trimmedLine && trimmedLine[0] === '@') {
lines.push(line);
}
else if (trimmedLine) {
lines[lines.length - 1] += ("\n" + line);
}
});
comment.tags = lines.map(exports.parseTag);
comment.isPrivate = comment.tags.some(function(tag){
return 'api' == tag.type && 'private' == tag.visibility;
})
}
// markdown
description.full = markdown(description.full);
description.summary = markdown(description.summary);
description.body = markdown(description.body);
return comment;
}
/**
* Parse tag string "@param {Array} name description" etc.
*
* @param {String}
* @return {Object}
* @api public
*/
exports.parseTag = function(str) {
var tag = {}
, parts = str.split(/ +/)
, type = tag.type = parts.shift().replace('@', '').split("\n")[0].trim();
switch (type) {
case 'param':
tag.types = exports.parseTagTypes(parts.shift());
tag.name = parts.shift() || '';
tag.description = markdown(parts.join(' '));
break;
case 'example':
tag.content = markdown(str.replace("@example", ""));
break;
case 'return':
tag.types = exports.parseTagTypes(parts.shift());
tag.description = markdown(parts.join(' '));
break;
case 'see':
if (~str.indexOf('http')) {
tag.title = parts.length > 1
? parts.shift()
: '';
tag.url = parts.join(' ');
} else {
tag.local = parts.join(' ');
}
case 'api':
tag.visibility = parts.shift();
break;
case 'type':
tag.types = exports.parseTagTypes(parts.shift());
break;
default:
tag.content = (parts || []).join(' ');
}
return tag;
}
/**
* Parse tag type string "{Array|Object}" etc.
*
* @param {String} str
* @return {Array}
* @api public
*/
exports.parseTagTypes = function(str) {
return str
.replace(/[{}]/g, '')
.split(/ *[|,\/] */);
};
/**
* Parse the context from the given `str` of js.
*
* This method attempts to discover the context
* for the comment based on it's code. Currently
* supports:
*
* - function statements
* - function expressions
* - prototype methods
* - prototype properties
* - methods
* - properties
* - declarations
*
* @param {String} str
* @return {Object}
* @api public
*/
exports.parseCodeContext = function(str){
var str = str.split('\n')[0];
// function statement
if (/^function (\w+)\(/.exec(str)) {
return {
type: 'function'
, name: RegExp.$1
, string: RegExp.$1 + '()'
};
// function expression
} else if (/^var *(\w+) *= *function/.exec(str)) {
return {
type: 'function'
, name: RegExp.$1
, string: RegExp.$1 + '()'
};
// prototype method
} else if (/^(\w+)\.prototype\.(\w+) *= *function/.exec(str)) {
return {
type: 'method'
, constructor: RegExp.$1
, name: RegExp.$2
, string: RegExp.$1 + '.prototype.' + RegExp.$2 + '()'
};
// prototype property
} else if (/^(\w+)\.prototype\.(\w+) *= *([^\n;]+)/.exec(str)) {
return {
type: 'property'
, constructor: RegExp.$1
, name: RegExp.$2
, value: RegExp.$3
, string: RegExp.$1 + '.prototype' + RegExp.$2
};
// method
} else if (/^(\w+)\.(\w+) *= *function/.exec(str)) {
return {
type: 'method'
, receiver: RegExp.$1
, name: RegExp.$2
, string: RegExp.$1 + '.' + RegExp.$2 + '()'
};
// property
} else if (/^(\w+)\.(\w+) *= *([^\n;]+)/.exec(str)) {
return {
type: 'property'
, receiver: RegExp.$1
, name: RegExp.$2
, value: RegExp.$3
, string: RegExp.$1 + '.' + RegExp.$2
};
// declaration
} else if (/^var +(\w+) *= *([^\n;]+)/.exec(str)) {
return {
type: 'declaration'
, name: RegExp.$1
, value: RegExp.$2
, string: RegExp.$1
};
// property declaration
} else if (/^(\w+) *: *([^\n;]+)/.exec(str)) {
return {
type: 'method'
, name: RegExp.$1
, value: RegExp.$2
, string: RegExp.$1
};
}
};