doctor
Advanced tools
Comparing version 0.2.3 to 0.3.0
@@ -30,7 +30,7 @@ /* | ||
.describe('transform', 'use transform rules') | ||
.alias('report', 'r') | ||
.default('report', true) | ||
.describe('report', 'use report rules') | ||
.default('render', true) | ||
@@ -45,3 +45,3 @@ .describe('render', 'use render module') | ||
.describe('ast', 'return ast') | ||
.alias('output', 'o') | ||
@@ -61,3 +61,3 @@ .default('output', false) | ||
// }) | ||
.argv; | ||
@@ -78,4 +78,3 @@ | ||
transform: argv.transform, | ||
unknown: argv.unknown, | ||
sync: argv.sync | ||
unknown: argv.unknown | ||
}; | ||
@@ -82,0 +81,0 @@ |
@@ -9,5 +9,3 @@ /* | ||
var argv = require('optimist').argv; | ||
//var parser = require('uglify-js').parser; | ||
var fs = require('fs'); | ||
//var traverse = require('traverse'); | ||
var async = require('async'); | ||
@@ -24,34 +22,29 @@ var Path = require('path'); | ||
var u = require('./util'); | ||
function getAst(options, astList, astPathList, cb) { | ||
(function (async, u) { | ||
if (options.sync) { | ||
async = u.sync.async; | ||
} | ||
async.forEachSeries(options.files, function (file, cb) { | ||
options.progressCb({ | ||
type: 'get-ast', | ||
file: file, | ||
message: 'get ast: ' + file | ||
}); | ||
nast.astFromFile(options, file, function (e, ast) { | ||
if (e) { | ||
return cb(e); | ||
} | ||
astList.push(ast); | ||
astPathList.push(ast.path); | ||
cb(null); | ||
}); | ||
}, function (err) { | ||
if (err) { | ||
return cb(err); | ||
async.forEachSeries(options.files, function (file, cb) { | ||
options.progressCb({ | ||
type: 'get-ast', | ||
file: file, | ||
message: 'get ast: ' + file | ||
}); | ||
nast.astFromFile(options, file, function (e, ast) { | ||
if (e) { | ||
return cb(e); | ||
} | ||
astPathList = u.localizePaths(astPathList); | ||
astList.forEach(function (ast, i) { | ||
ast.fullPath = ast.path; | ||
ast.path = astPathList[i]; | ||
}); | ||
astList.push(ast); | ||
astPathList.push(ast.path); | ||
cb(null); | ||
}); | ||
}(async, u)); | ||
}, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
astPathList = u.localizePaths(astPathList); | ||
astList.forEach(function (ast, i) { | ||
ast.fullPath = ast.path; | ||
ast.path = astPathList[i]; | ||
}); | ||
cb(null); | ||
}); | ||
} | ||
@@ -86,3 +79,2 @@ | ||
} | ||
// no sync here yet | ||
@@ -317,591 +309,296 @@ var requires = []; | ||
function examine(options, progressCb, cb) { | ||
(function (Path, fs, async, u) { | ||
var syncFinished = false; | ||
options = _.clone(options); | ||
if (typeof options.follow === 'undefined') { | ||
options.follow = true; | ||
} | ||
if (typeof cb === 'undefined') { | ||
cb = progressCb; | ||
progressCb = function () {}; | ||
} | ||
options.progressCb = progressCb; | ||
options = _.clone(options); | ||
options.sync = options.sync || false; | ||
if (typeof options.follow === 'undefined') { | ||
options.follow = true; | ||
if (!Array.isArray(options.files)) { | ||
cb(new Error('must pass in array of files')); | ||
} | ||
if (options.output === true) { | ||
options.output = 'output'; | ||
} | ||
if (options.transform === true || typeof options.transform === 'undefined') { | ||
options.transform = 'default'; | ||
} | ||
if (options.report === true || typeof options.report === 'undefined') { | ||
options.report = 'default'; | ||
} | ||
if (options.view === true) { | ||
options.view = 'default'; | ||
} | ||
// if (options.report && !options.render) { | ||
// options.render = true; | ||
// } | ||
if (options.render === true || typeof options.render === 'undefined') { | ||
options.render = 'default'; | ||
} | ||
if (typeof options.unknown === 'undefined') { | ||
options.unknown = true; | ||
} | ||
var outputDir = null; | ||
var outputReportName = null; | ||
if (options.output) { | ||
outputDir = Path.dirname(options.output); | ||
if (Path.extname(options.output) === '.json') { | ||
outputReportName = Path.basename(options.output); | ||
} else { | ||
outputDir = options.output; | ||
} | ||
if (typeof cb === 'undefined') { | ||
cb = progressCb; | ||
progressCb = function () {}; | ||
} | ||
options.progressCb = progressCb; | ||
var origCb = cb; | ||
var syncError = null; | ||
var syncResult = null; | ||
} | ||
if (outputReportName) { | ||
options.outputReportName = outputReportName; | ||
} | ||
var astList = []; | ||
var astPathList = []; | ||
var done = false; | ||
if (options.sync) { | ||
Path = u.sync.path; | ||
fs = u.sync.fs; | ||
async = u.sync.async; | ||
u = u.sync; | ||
cb = function (err, result) { | ||
syncFinished = true; | ||
syncError = err; | ||
syncResult = result; | ||
}; | ||
} | ||
if (!Array.isArray(options.files)) { | ||
cb(new Error('must pass in array of files')); | ||
} | ||
if (options.output === true) { | ||
options.output = 'output'; | ||
} | ||
if (options.transform === true || typeof options.transform === 'undefined') { | ||
options.transform = 'default'; | ||
} | ||
if (options.report === true || typeof options.report === 'undefined') { | ||
options.report = 'default'; | ||
} | ||
if (options.view === true) { | ||
options.view = 'default'; | ||
} | ||
// if (options.report && !options.render) { | ||
// options.render = true; | ||
// } | ||
if (options.render === true || typeof options.render === 'undefined') { | ||
options.render = 'default'; | ||
} | ||
if (typeof options.unknown === 'undefined') { | ||
options.unknown = true; | ||
} | ||
var outputDir = null; | ||
var outputReportName = null; | ||
if (options.output) { | ||
outputDir = Path.dirname(options.output); | ||
if (Path.extname(options.output) === '.json') { | ||
outputReportName = Path.basename(options.output); | ||
} else { | ||
outputDir = options.output; | ||
function finish(result) { | ||
progressCb({ | ||
type: 'create-result', | ||
message: 'create result' | ||
}); | ||
if (!options.ast && !options.render) { | ||
result = result.report; | ||
} else if (!options.ast) { | ||
result = result.files; | ||
} else { | ||
var cleanAst = nast.cleanAst(result.ast); | ||
result.ast = cleanAst; | ||
if (!options.report) { | ||
result = cleanAst; | ||
} else if (!options.render) { | ||
result = {ast: cleanAst, report: result.report}; | ||
} | ||
} | ||
if (outputReportName) { | ||
options.outputReportName = outputReportName; | ||
} | ||
var astList = []; | ||
var astPathList = []; | ||
var done = false; | ||
return cb(null, result); | ||
} | ||
function finish(result) { | ||
progressCb({ | ||
type: 'create-result', | ||
message: 'create result' | ||
}); | ||
if (!options.ast && !options.render) { | ||
result = result.report; | ||
} else if (!options.ast) { | ||
result = result.files; | ||
} else { | ||
var cleanAst = nast.cleanAst(result.ast); | ||
result.ast = cleanAst; | ||
if (!options.report) { | ||
result = cleanAst; | ||
} else if (!options.render) { | ||
result = {ast: cleanAst, report: result.report}; | ||
} | ||
} | ||
syncFinished = true; | ||
return cb(null, result); | ||
function writeOutput(result) { | ||
if (!options.output) { | ||
return finish(result); | ||
} | ||
function writeOutput(result) { | ||
if (!options.output) { | ||
return finish(result); | ||
} | ||
progressCb({ | ||
type: 'write-output', | ||
message: 'write output' | ||
}); | ||
function writeFile(file, cb) { | ||
var filePath = Path.join(outputDir, file); | ||
// this will not work for sync, but we shouldn't be writing files | ||
// for sync anyway | ||
mkdirp(Path.dirname(filePath), parseInt(755, 8), function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
var fileString = result.files[file]; | ||
if (typeof fileString !== 'string') { | ||
try { | ||
// if circular references were introduced, this will fail | ||
fileString = JSON.stringify(fileString, null, 2); | ||
} catch (e) { | ||
return cb(e); | ||
} | ||
} | ||
fs.writeFile(filePath, fileString, cb); | ||
}); | ||
} | ||
async.forEach(Object.keys(result.files), writeFile, function (err) { | ||
progressCb({ | ||
type: 'write-output', | ||
message: 'write output' | ||
}); | ||
function writeFile(file, cb) { | ||
var filePath = Path.join(outputDir, file); | ||
mkdirp(Path.dirname(filePath), parseInt(755, 8), function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
finish(result); | ||
var fileString = result.files[file]; | ||
if (typeof fileString !== 'string') { | ||
try { | ||
// if circular references were introduced, this will fail | ||
fileString = JSON.stringify(fileString, null, 2); | ||
} catch (e) { | ||
return cb(e); | ||
} | ||
} | ||
fs.writeFile(filePath, fileString, cb); | ||
}); | ||
} | ||
async.forEach(Object.keys(result.files), writeFile, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
finish(result); | ||
}); | ||
} | ||
function renderReport(result) { | ||
progressCb({ | ||
type: 'render-report', | ||
message: 'render report' | ||
}); | ||
result.files = {}; | ||
if (!result.report) { | ||
return writeOutput(result); | ||
function renderReport(result) { | ||
progressCb({ | ||
type: 'render-report', | ||
message: 'render report' | ||
}); | ||
result.files = {}; | ||
if (!result.report) { | ||
return writeOutput(result); | ||
} | ||
render(options, result.report, function (err, files) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
render(options, result.report, function (err, files) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
Object.keys(files).forEach(function (file, i) { | ||
result.files[file] = files[file]; | ||
}); | ||
return writeOutput(result); | ||
Object.keys(files).forEach(function (file, i) { | ||
result.files[file] = files[file]; | ||
}); | ||
} | ||
return writeOutput(result); | ||
}); | ||
} | ||
// function postProcess(result, cb) { | ||
// if (!result.report) { | ||
// return renderReport(result); | ||
// } | ||
// result.report = postProcessReport(result.report); | ||
// return renderReport(result); | ||
// } | ||
// function postProcess(result, cb) { | ||
// if (!result.report) { | ||
// return renderReport(result); | ||
// } | ||
// result.report = postProcessReport(result.report); | ||
// return renderReport(result); | ||
// } | ||
// function followRequired(result) { | ||
// pickupRequiredFiles(options, astList, astPathList, result, function (err, newResult) { | ||
// if (err) { | ||
// return cb(err); | ||
// } | ||
// return postProcess(newResult); | ||
// }); | ||
// } | ||
// function followRequired(result) { | ||
// pickupRequiredFiles(options, astList, astPathList, result, function (err, newResult) { | ||
// if (err) { | ||
// return cb(err); | ||
// } | ||
// return postProcess(newResult); | ||
// }); | ||
// } | ||
function doMakeReport(ast) { | ||
progressCb({ | ||
type: 'make-report', | ||
message: 'make report' | ||
}); | ||
makeReport(options, ast, function (err, report) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
//return followRequired(report); | ||
return renderReport(report); | ||
//return postProcess(report); | ||
}); | ||
} | ||
function doMakeReport(ast) { | ||
progressCb({ | ||
type: 'make-report', | ||
message: 'make report' | ||
}); | ||
makeReport(options, ast, function (err, report) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
//return followRequired(report); | ||
return renderReport(report); | ||
//return postProcess(report); | ||
}); | ||
} | ||
function doTransformAst() { | ||
progressCb({ | ||
type: 'transform-ast', | ||
message: 'transform ast' | ||
}); | ||
transformAst(options, astList, function (err, transformedAst) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return doMakeReport(transformedAst); | ||
}); | ||
} | ||
function doTransformAst() { | ||
progressCb({ | ||
type: 'transform-ast', | ||
message: 'transform ast' | ||
}); | ||
transformAst(options, astList, function (err, transformedAst) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return doMakeReport(transformedAst); | ||
}); | ||
} | ||
function hoistToc(astList) { | ||
var tocIndex = -1; | ||
for (var i = 0; i < astList.length; i++) { | ||
var ast = astList[i]; | ||
if (ast.type === 'markdown') { | ||
if (ast.path === 'TOC.md' || ast.path === 'toc.md') { | ||
tocIndex = i; | ||
break; | ||
} | ||
function hoistToc(astList) { | ||
var tocIndex = -1; | ||
for (var i = 0; i < astList.length; i++) { | ||
var ast = astList[i]; | ||
if (ast.type === 'markdown') { | ||
if (ast.path === 'TOC.md' || ast.path === 'toc.md') { | ||
tocIndex = i; | ||
break; | ||
} | ||
} | ||
if (tocIndex >= 0) { | ||
var tocAst = astList[tocIndex]; | ||
astList.splice(tocIndex, 1); | ||
astList.splice(0, 0, tocAst); | ||
} | ||
} | ||
function doGetAst(moduleInfo) { | ||
moduleInfo = moduleInfo || {}; | ||
(function (originalOptions) { | ||
var options = _.clone(originalOptions); | ||
options.files = moduleInfo.sorted || options.files; | ||
getAst(options, astList, astPathList, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
if (moduleInfo.sorted) { | ||
astList.forEach(function (ast, i) { | ||
var m = {}; | ||
if (moduleInfo.pathToModule && | ||
ast.fullPath in moduleInfo.pathToModule) { | ||
m = moduleInfo.pathToModule[ast.fullPath]; | ||
if (!ast.package && m.package) { | ||
ast.package = m.package; | ||
} | ||
} | ||
if (moduleInfo.modulePaths.indexOf(ast.fullPath) < 0) { | ||
ast.required = true; | ||
} | ||
}); | ||
} | ||
hoistToc(astList); | ||
return doTransformAst(); | ||
}); | ||
}(options)); | ||
if (tocIndex >= 0) { | ||
var tocAst = astList[tocIndex]; | ||
astList.splice(tocIndex, 1); | ||
astList.splice(0, 0, tocAst); | ||
} | ||
} | ||
function followRequired() { | ||
if (!options.follow) { | ||
return doGetAst(); | ||
} | ||
progressCb({ | ||
type: 'follow-required', | ||
message: 'find required modules' | ||
}); | ||
findRequired(options, options.files, function (err, moduleInfo) { | ||
function doGetAst(moduleInfo) { | ||
moduleInfo = moduleInfo || {}; | ||
(function (originalOptions) { | ||
var options = _.clone(originalOptions); | ||
options.files = moduleInfo.sorted || options.files; | ||
getAst(options, astList, astPathList, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return doGetAst(moduleInfo); | ||
if (moduleInfo.sorted) { | ||
astList.forEach(function (ast, i) { | ||
var m = {}; | ||
if (moduleInfo.pathToModule && | ||
ast.fullPath in moduleInfo.pathToModule) { | ||
m = moduleInfo.pathToModule[ast.fullPath]; | ||
if (!ast.package && m.package) { | ||
ast.package = m.package; | ||
} | ||
} | ||
if (moduleInfo.modulePaths.indexOf(ast.fullPath) < 0) { | ||
ast.required = true; | ||
} | ||
}); | ||
} | ||
hoistToc(astList); | ||
return doTransformAst(); | ||
}); | ||
} | ||
}(options)); | ||
} | ||
function getCommentParser() { | ||
progressCb({ | ||
type: 'get-comment-parser', | ||
message: 'get comment parser' | ||
}); | ||
nast.pegParser({grammar: 'comment-tags', sync: options.sync}, function (err, parser) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
options.commentParser = parser; | ||
return followRequired(); | ||
}); | ||
function followRequired() { | ||
if (!options.follow) { | ||
return doGetAst(); | ||
} | ||
// this will not run synchronously | ||
function copyView() { | ||
if (!options.view) { | ||
return getCommentParser(); | ||
progressCb({ | ||
type: 'follow-required', | ||
message: 'find required modules' | ||
}); | ||
findRequired(options, options.files, function (err, moduleInfo) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
options.view = Array.isArray(options.view) ? options.view : [options.view]; | ||
function eachView(view, cb) { | ||
progressCb({ | ||
type: 'copy-view', | ||
message: 'copy view: ' + view | ||
}); | ||
var viewChoices = [view, Path.join(__dirname, '../view', view)]; | ||
u.findDir(viewChoices, | ||
function viewExists(dir) { | ||
ncp(dir, outputDir, {stopOnError: true}, function (err) { | ||
cb(err); | ||
}); | ||
}, | ||
function viewNotFound() { | ||
cb(new Error('View directory ' + view + ' does not exist.')); | ||
} | ||
); | ||
} | ||
async.forEachSeries(options.view, eachView, function (err) { | ||
if (err) { | ||
cb(err); | ||
} | ||
return getCommentParser(); | ||
}); | ||
} | ||
return doGetAst(moduleInfo); | ||
}); | ||
} | ||
function checkOutputDir() { | ||
if (!outputDir) { | ||
return copyView(); | ||
function getCommentParser() { | ||
progressCb({ | ||
type: 'get-comment-parser', | ||
message: 'get comment parser' | ||
}); | ||
nast.pegParser({grammar: 'comment-tags'}, function (err, parser) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
var fsExists = fs.exists || Path.exists; | ||
fsExists(outputDir, function (exists) { | ||
if (!exists) { | ||
return cb(new Error('output directory ' + outputDir + ' does not exist')); | ||
} | ||
return copyView(); | ||
}); | ||
} | ||
checkOutputDir(); | ||
options.commentParser = parser; | ||
return followRequired(); | ||
}); | ||
} | ||
if (options.sync) { | ||
if (!syncFinished) { | ||
throw new Error('Problem attempting synchronous examine.'); | ||
} else { | ||
origCb(syncError, syncResult); | ||
} | ||
function copyView() { | ||
if (!options.view) { | ||
return getCommentParser(); | ||
} | ||
}(Path, fs, async, u)); | ||
} | ||
/* | ||
__EXPERIMENTAL!__ __(and not even close to finished)__ Registers a compiler | ||
for the .js extension that will compile harmony files to ECMAScript 5. | ||
@private | ||
*/ | ||
function harmony() { | ||
function compiler(module, filename) { | ||
// don't attempt to run doctor code through harmony compiler before | ||
// doctor's dynamic requires are finished; otherwise, we'll deadlock | ||
if (!harmony.isStarted || harmony.isReady) { | ||
harmony.isStarted = true; | ||
var options = { | ||
files: [filename], | ||
grammar: 'harmony', | ||
report: ['ast'], | ||
render: ['source'], | ||
transform: ['harmony-es5'], | ||
sync: true, | ||
follow: false | ||
}; | ||
var source; | ||
var error; | ||
examine(options, function (err, report) { | ||
if (err) { | ||
error = err; | ||
} else { | ||
var key = Object.keys(report)[0]; | ||
source = report[key]; | ||
} | ||
harmony.isReady = true; | ||
options.view = Array.isArray(options.view) ? options.view : [options.view]; | ||
function eachView(view, cb) { | ||
progressCb({ | ||
type: 'copy-view', | ||
message: 'copy view: ' + view | ||
}); | ||
//return module._compile(content, filename); | ||
if (error) { | ||
//throw error; | ||
throw SyntaxError("In " + filename + ", " + error.message + " on line " + error.line); | ||
} | ||
if (!error) { | ||
try { | ||
return module._compile(source, filename); | ||
} catch (e) { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
var viewChoices = [view, Path.join(__dirname, '../view', view)]; | ||
u.findDir(viewChoices, | ||
function viewExists(dir) { | ||
ncp(dir, outputDir, {stopOnError: true}, function (err) { | ||
cb(err); | ||
}); | ||
}, | ||
function viewNotFound() { | ||
cb(new Error('View directory ' + view + ' does not exist.')); | ||
} | ||
} else { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
); | ||
} | ||
async.forEachSeries(options.view, eachView, function (err) { | ||
if (err) { | ||
cb(err); | ||
} | ||
} else { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
} | ||
return getCommentParser(); | ||
}); | ||
} | ||
require.extensions['.js'] = compiler; | ||
} | ||
/* | ||
__EXPERIMENTAL!__ Registers a compiler for the .js extension that will catch | ||
function entry/exit. | ||
@param options {Object|RegExp} Pass in a regular expression apply the compiler | ||
only to paths matching that regular expression. | ||
@param enterCb {function} Calls this function (passing it info) when a | ||
function is entered. | ||
@param exitCb {Fucntion} Calls this function (passing it info) when a function | ||
is exited. | ||
@example | ||
var doctor = require('doctor'); | ||
function hookMessage(title, info) { | ||
return title + " " + info.name + ":\n" + | ||
" type: " + info.type + "\n" + | ||
" line: " + info.line + "\n" + | ||
" column: " + info.column + "\n" + | ||
" path: " + info.filename; | ||
} | ||
doctor.hookEnterExit( | ||
/foobar/, | ||
function (info) { | ||
console.log(hookMessage("Enter", info)); | ||
}, | ||
function (info) { | ||
console.log(hookMessage("Exit", info)); | ||
function checkOutputDir() { | ||
if (!outputDir) { | ||
return copyView(); | ||
} | ||
); | ||
require('./foobar'); | ||
@private | ||
*/ | ||
function hookEnterExit(options, enterCb, exitCb) { | ||
if (typeof options === 'function') { | ||
exitCb = enterCb; | ||
enterCb = options; | ||
options = {}; | ||
} | ||
if (options instanceof RegExp) { | ||
var re = options; | ||
options = {include: re}; | ||
} | ||
enterCb = enterCb || function () {}; | ||
if (typeof exitCb === 'undefined') { | ||
exitCb = enterCb; | ||
} else { | ||
exitCb = exitCb || function () {}; | ||
} | ||
hookEnterExit.enterCb = enterCb; | ||
hookEnterExit.exitCb = exitCb; | ||
function includeFile(filename) { | ||
//console.log(filename); | ||
return filename.match(options.include); | ||
} | ||
function compiler(module, filename) { | ||
if (!includeFile(filename)) { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
} | ||
//console.log("FILE:" + filename) | ||
// don't attempt to run doctor code through enter-exit compiler before | ||
// doctor's dynamic requires are finished; otherwise, we'll deadlock | ||
if (!hookEnterExit.isStarted || hookEnterExit.isReady) { | ||
hookEnterExit.isStarted = true; | ||
var options = { | ||
files: [filename], | ||
grammar: 'javascript', | ||
report: ['ast'], | ||
render: ['source'], | ||
transform: ['enter-exit'], | ||
sync: true, | ||
follow: false, | ||
hookDoctorPath: __filename | ||
}; | ||
var source; | ||
var error; | ||
examine(options, function (err, report) { | ||
if (err) { | ||
error = err; | ||
} else { | ||
var key = Object.keys(report)[0]; | ||
source = report[key]; | ||
} | ||
//console.log(err); | ||
//console.log(source); | ||
hookEnterExit.isReady = true; | ||
}); | ||
//return module._compile(content, filename); | ||
if (error) { | ||
//throw error; | ||
throw new SyntaxError("In " + filename + ", " + error.message + " on line " + error.line); | ||
var fsExists = fs.exists || Path.exists; | ||
fsExists(outputDir, function (exists) { | ||
if (!exists) { | ||
return cb(new Error('output directory ' + outputDir + ' does not exist')); | ||
} | ||
if (!error) { | ||
try { | ||
var m = module._compile(source, filename); | ||
return m; | ||
} catch (e) { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
} | ||
} else { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
} | ||
} else { | ||
return module._compile(fs.readFileSync(filename, 'utf-8'), filename); | ||
} | ||
return copyView(); | ||
}); | ||
} | ||
require.extensions['.js'] = compiler; | ||
checkOutputDir(); | ||
} | ||
function _callEnterHook() { | ||
hookEnterExit.enterCb.apply(null, arguments); | ||
} | ||
function _callExitHook() { | ||
hookEnterExit.exitCb.apply(null, arguments); | ||
} | ||
var enterExitHooks = {}; | ||
function _callHook(type, filename, info) { | ||
info.hook = type; | ||
if (filename) { | ||
enterExitHooks[filename][type + 'Cb'].call(null, info); | ||
} else { | ||
hookEnterExit[type + 'Cb'].call(null, info); | ||
} | ||
} | ||
function _wrapCall(fn, ctx, args, filename, info) { | ||
_callHook('enter', filename, info); | ||
var result = fn.apply(ctx, args); | ||
_callHook('exit', filename, info); | ||
return result; | ||
} | ||
/* | ||
__EXPERIMENTAL__ Inserts enter/exit hooks into a JavaScript file. Registers | ||
callback hooks for that file. | ||
@param filename {String} Filename of file to trasform. Callback hooks are | ||
registered to this filename. | ||
@param enterCb {function} Function to call when entering function. If exitCb | ||
is not defined, enterCb will be called for exit as well. | ||
@param [exitCb] {function} Function to call when exiting function. | ||
@param [sourceCb] {function} Function to call when building the source for a function. | ||
@example | ||
var doctor = require('doctor'); | ||
var source = doctor.insertEnterExitHooksSync('foobar.js', function (info) { | ||
console.log(JSON.stringify(info, null, 2)); | ||
}); | ||
eval(source); | ||
@private | ||
*/ | ||
function insertEnterExitHooksSync(filename, enterCb, exitCb, sourceCb) { | ||
var options = { | ||
files: [filename], | ||
grammar: 'javascript', | ||
transform: ['enter-exit'], | ||
report: ['ast'], | ||
render: ['source'], | ||
sync: true, | ||
follow: false, | ||
hookFilename: filename, | ||
hookSourceCb: sourceCb, | ||
hookDoctorPath: __filename | ||
}; | ||
enterCb = enterCb || function () {}; | ||
if (typeof exitCb === 'undefined') { | ||
exitCb = enterCb; | ||
} else { | ||
exitCb = exitCb || function () {}; | ||
} | ||
enterExitHooks[filename] = {enterCb: enterCb, exitCb: exitCb}; | ||
var source; | ||
examine(options, function (error, report) { | ||
if (error) { | ||
throw new SyntaxError("In " + filename + ", " + error.message + " on line " + error.line); | ||
} else { | ||
var key = Object.keys(report)[0]; | ||
source = report[key]; | ||
} | ||
}); | ||
return source; | ||
} | ||
module.exports.examine = examine; | ||
module.exports.harmony = harmony; | ||
module.exports.hookEnterExit = hookEnterExit; | ||
module.exports._callEnterHook = _callEnterHook; | ||
module.exports._callExitHook = _callExitHook; | ||
module.exports.insertEnterExitHooksSync = insertEnterExitHooksSync; | ||
module.exports._callHook = _callHook; | ||
module.exports._wrapCall = _wrapCall; | ||
module.exports.examine = examine; |
@@ -1,7 +0,7 @@ | ||
var fs = require('fs'); | ||
var peg = require('pegjs'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var async = require('async'); | ||
var fsCompare = require('fs-compare'); | ||
var quickpeg = require('quickpeg').config({pegjs: {cache: true}}); | ||
@@ -11,4 +11,2 @@ var AstNode = require('./ast-node'); | ||
var pegParsers = {}; | ||
/* | ||
@@ -142,107 +140,68 @@ Walks a tree of AST nodes. | ||
function pegParser(options, extension, cb) { | ||
(function (path, fs, async, u) { | ||
if (options.sync) { | ||
path = u.sync.path; | ||
fs = u.sync.fs; | ||
async = u.sync.async; | ||
u = u.sync; | ||
} | ||
if (typeof cb === 'undefined') { | ||
cb = extension; | ||
extension = null; | ||
} | ||
if (typeof cb === 'undefined') { | ||
cb = extension; | ||
extension = null; | ||
var grammarFiles = []; | ||
if (typeof options === 'string') { | ||
options = {grammar: options}; | ||
} | ||
var grammar = options.grammar; | ||
if (typeof grammar === 'object') { | ||
if (grammar[extension]) { | ||
grammar = grammar[extension]; | ||
} | ||
var grammarFiles = []; | ||
if (typeof options === 'string') { | ||
options = {grammar: options}; | ||
} | ||
if (typeof grammar !== 'string') { | ||
if (extension === 'js') { | ||
grammar = 'javascript'; | ||
} else if (extension === 'md') { | ||
grammar = 'markdown'; | ||
} | ||
var grammar = options.grammar; | ||
if (typeof grammar === 'object') { | ||
if (grammar[extension]) { | ||
grammar = grammar[extension]; | ||
} | ||
} | ||
if (typeof grammar !== 'string') { | ||
if (extension === 'js') { | ||
grammar = 'javascript'; | ||
} else if (extension === 'md') { | ||
grammar = 'markdown'; | ||
} | ||
} | ||
if (grammar) { | ||
grammarFiles.push(grammar); | ||
grammarFiles.push(path.join(__dirname, '../grammar', grammar + '.pegjs')); | ||
} else { | ||
grammarFiles.push(path.join(__dirname, '../grammar/javascript.pegjs')); | ||
} | ||
} | ||
if (grammar) { | ||
grammarFiles.push(grammar); | ||
grammarFiles.push(path.join(__dirname, '../grammar', grammar + '.pegjs')); | ||
} else { | ||
grammarFiles.push(path.join(__dirname, '../grammar/javascript.pegjs')); | ||
} | ||
var parser = null; | ||
var parserGrammarFile = null; | ||
var grammarErr = null; | ||
function existsNewerCompiledGrammarFile(grammarFile, cb) { | ||
u.compareFileDates(grammarFile, grammarFile + '.js', function (c) { | ||
cb(c > 0); | ||
}); | ||
function findGrammarFile(wrapper, cb) { | ||
var grammarFile = wrapper.file; | ||
var cachedParser = quickpeg.parserFromMemory(grammarFile); | ||
if (cachedParser) { | ||
wrapper.parser = cachedParser; | ||
return cb(true); | ||
} | ||
function findGrammarFile(grammarFile, cb) { | ||
if (pegParsers[grammarFile]) { | ||
parser = pegParsers[grammarFile]; | ||
parserGrammarFile = grammarFile; | ||
return cb(true); | ||
var fsExists = fs.exists || path.exists; | ||
fsExists(grammarFile, function (exists) { | ||
if (!exists) { | ||
return cb(false); | ||
} | ||
var fsExists = fs.exists || path.exists; | ||
fsExists(grammarFile, function (exists) { | ||
if (!exists) { | ||
return cb(false); | ||
quickpeg(grammarFile, function (err, cachedParser) { | ||
if (err) { | ||
wrapper.err = err; | ||
return; | ||
} | ||
existsNewerCompiledGrammarFile(grammarFile, function (existsCompiled) { | ||
if (existsCompiled) { | ||
var cachedParser; | ||
try { | ||
cachedParser = require(grammarFile + '.js'); | ||
} catch (e) { | ||
// something wrong with compiled grammar, just write new one | ||
} | ||
if (cachedParser && cachedParser.parse) { | ||
parser = cachedParser; | ||
pegParsers[grammarFile] = parser; | ||
parserGrammarFile = grammarFile; | ||
return cb(true); | ||
} | ||
} | ||
fs.readFile(grammarFile, 'utf-8', function (err, data) { | ||
if (err) { | ||
grammarErr = err; | ||
return cb(true); | ||
} | ||
try { | ||
parser = peg.buildParser(data); | ||
pegParsers[grammarFile] = parser; | ||
parserGrammarFile = grammarFile; | ||
// cache the grammar for next time | ||
} catch (e) { | ||
grammarErr = e; | ||
return cb(true); | ||
} | ||
fs.writeFile(grammarFile + '.js', 'module.exports = ' + parser.toSource(), function (err) { | ||
if (err) { | ||
grammarErr = err; | ||
} | ||
return cb(true); | ||
}); | ||
}); | ||
}); | ||
wrapper.parser = cachedParser; | ||
return cb(true); | ||
}); | ||
}); | ||
} | ||
var grammarFileWrappers = grammarFiles.map(function (file) { | ||
return { | ||
parser: null, | ||
file: file, | ||
err: null | ||
}; | ||
}); | ||
async.detectSeries(grammarFileWrappers, findGrammarFile, function (wrapper) { | ||
if (wrapper.err || wrapper.parser) { | ||
return cb(wrapper.err, wrapper.parser, wrapper.file); | ||
} | ||
async.some(grammarFiles, findGrammarFile, function () { | ||
if (grammarErr || parser) { | ||
return cb(grammarErr, parser, parserGrammarFile); | ||
} | ||
cb(new Error('grammar file not found')); | ||
}); | ||
}(path, fs, async, u)); | ||
cb(new Error('grammar file not found')); | ||
}); | ||
} | ||
@@ -339,65 +298,58 @@ | ||
function astFromFile(options, file, cb) { | ||
(function (fs) { | ||
if (options.sync) { | ||
fs = u.sync.fs; | ||
} | ||
function convertSourceFile(file, packageName, packagePath) { | ||
fs.readFile(file, 'utf-8', function (err, source) { | ||
function convertSourceFile(file, packageName, packagePath) { | ||
fs.readFile(file, 'utf-8', function (err, source) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
var extension = path.extname(file); | ||
if (extension[0] === '.') { | ||
extension = extension.substr(1); | ||
} | ||
astFromSource(options, source, extension, function (err, ast) { | ||
if (err) { | ||
err.file = file; | ||
return cb(err); | ||
} | ||
var extension = path.extname(file); | ||
if (extension[0] === '.') { | ||
extension = extension.substr(1); | ||
if (packageName) { | ||
ast.package = {name: packageName, path: packagePath}; | ||
if (packagePath[0] !== '/') { | ||
ast.package.path = path.join(process.cwd(), packagePath); | ||
} | ||
ast.package.originalPath = packagePath; | ||
} | ||
astFromSource(options, source, extension, function (err, ast) { | ||
if (err) { | ||
err.file = file; | ||
return cb(err); | ||
ast.originalPath = file; | ||
if (file[0] === '/') { | ||
ast.path = file; | ||
} else { | ||
ast.path = path.join(process.cwd(), file); | ||
} | ||
return cb(null, ast); | ||
}); | ||
}); | ||
} | ||
function convertPackage() { | ||
if (path.basename(file) === 'package.json') { | ||
fs.readFile(file, 'utf-8', function (err, pkgString) { | ||
try { | ||
var pkg = JSON.parse(pkgString); | ||
if (!pkg.main) { | ||
return cb(new Error(file + " has no main modules.")); | ||
} | ||
if (packageName) { | ||
ast.package = {name: packageName, path: packagePath}; | ||
if (packagePath[0] !== '/') { | ||
ast.package.path = path.join(process.cwd(), packagePath); | ||
} | ||
ast.package.originalPath = packagePath; | ||
var main = path.join(path.dirname(file), pkg.main); | ||
if (!main.match(/\.js$/)) { | ||
main = main + '.js'; | ||
} | ||
ast.originalPath = file; | ||
if (file[0] === '/') { | ||
ast.path = file; | ||
} else { | ||
ast.path = path.join(process.cwd(), file); | ||
} | ||
return cb(null, ast); | ||
}); | ||
convertSourceFile(main, pkg.name, file); | ||
} catch (packageParseError) { | ||
packageParseError.message = 'Error parsing ' + file + ' - ' + packageParseError.message; | ||
return cb(packageParseError); | ||
} | ||
}); | ||
} else { | ||
convertSourceFile(file, null); | ||
} | ||
} | ||
function convertPackage() { | ||
if (path.basename(file) === 'package.json') { | ||
fs.readFile(file, 'utf-8', function (err, pkgString) { | ||
try { | ||
var pkg = JSON.parse(pkgString); | ||
if (!pkg.main) { | ||
return cb(new Error(file + " has no main modules.")); | ||
} | ||
var main = path.join(path.dirname(file), pkg.main); | ||
if (!main.match(/\.js$/)) { | ||
main = main + '.js'; | ||
} | ||
convertSourceFile(main, pkg.name, file); | ||
} catch (packageParseError) { | ||
packageParseError.message = 'Error parsing ' + file + ' - ' + packageParseError.message; | ||
return cb(packageParseError); | ||
} | ||
}); | ||
} else { | ||
convertSourceFile(file, null); | ||
} | ||
} | ||
convertPackage(); | ||
}(fs)); | ||
convertPackage(); | ||
} | ||
@@ -645,6 +597,3 @@ | ||
function parserForGrammar(grammarFile) { | ||
if (grammarFile in pegParsers) { | ||
return pegParsers[grammarFile]; | ||
} | ||
return null; | ||
return quickpeg.parserFromMemory(grammarFile); | ||
} | ||
@@ -651,0 +600,0 @@ |
@@ -13,47 +13,40 @@ var async = require('async'); | ||
function render(options, report, cb) { | ||
(function (async, u) { | ||
if (options.sync) { | ||
async = u.sync.async; | ||
u = u.sync; | ||
} | ||
var reportName = options.outputReportName || 'report.json'; | ||
var files = {}; | ||
files[reportName] = report; | ||
var renderFunctions = u.toArray(options.render || 'default'); | ||
try { | ||
renderFunctions = u.findFunctions(renderFunctions, 'render'); | ||
} catch (findError) { | ||
return cb(findError); | ||
} | ||
function eachRender(fn, cb) { | ||
fn(options, files, function (err, renderedFiles) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
files = renderedFiles; | ||
cb(null, files); | ||
}); | ||
} | ||
async.forEachSeries(renderFunctions, eachRender, function (err) { | ||
// if (err) { | ||
// return cb(err); | ||
// } | ||
// Object.keys(files).forEach(function (filename, i) { | ||
// var file = files[filename]; | ||
// if (typeof file !== 'string') { | ||
// try { | ||
// // if circular references were introduced, this will fail | ||
// var fileString = JSON.stringify(file, null, 2); | ||
// files[filename] = fileString; | ||
// } catch (jsonError) { | ||
// return cb(jsonError); | ||
// } | ||
// } | ||
// }); | ||
cb(err, files); | ||
var reportName = options.outputReportName || 'report.json'; | ||
var files = {}; | ||
files[reportName] = report; | ||
var renderFunctions = u.toArray(options.render || 'default'); | ||
try { | ||
renderFunctions = u.findFunctions(renderFunctions, 'render'); | ||
} catch (findError) { | ||
return cb(findError); | ||
} | ||
function eachRender(fn, cb) { | ||
fn(options, files, function (err, renderedFiles) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
files = renderedFiles; | ||
cb(null, files); | ||
}); | ||
}(async, u)); | ||
} | ||
async.forEachSeries(renderFunctions, eachRender, function (err) { | ||
// if (err) { | ||
// return cb(err); | ||
// } | ||
// Object.keys(files).forEach(function (filename, i) { | ||
// var file = files[filename]; | ||
// if (typeof file !== 'string') { | ||
// try { | ||
// // if circular references were introduced, this will fail | ||
// var fileString = JSON.stringify(file, null, 2); | ||
// files[filename] = fileString; | ||
// } catch (jsonError) { | ||
// return cb(jsonError); | ||
// } | ||
// } | ||
// }); | ||
cb(err, files); | ||
}); | ||
} | ||
module.exports = render; |
266
lib/util.js
/* | ||
This module contains various utility functions used by doctor. | ||
It notably contains some nasty synchronous versions of asynchronous modules. | ||
Say what? | ||
Yeah, that's right. This is so doctor can run synchronously or ansynhronously. | ||
Doctor wants to run asynchronously, so when we have to run synchronously (such | ||
as during that ugly startup phase where you have to deal with synchronous | ||
require), we have to fake it out with synchronous versions of asynchronous | ||
modules. This is ugly, but it's better than having to write two different | ||
versions of doctor. | ||
*/ | ||
@@ -21,110 +10,2 @@ | ||
// turn synchronous things into callbacks to they can stand in | ||
// for asynchronous things | ||
function callbackify(thing, canError) { | ||
if (typeof thing === 'function') { | ||
return function () { | ||
try { | ||
var result = thing.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length - 1)); | ||
if (canError) { | ||
arguments[arguments.length - 1](null, result); | ||
} else { | ||
arguments[arguments.length - 1](result); | ||
} | ||
} catch (err) { | ||
if (canError) { | ||
arguments[arguments.length - 1](err); | ||
} else { | ||
arguments[arguments.length - 1](false); | ||
} | ||
} | ||
}; | ||
} else if (typeof thing === 'object' && !_.isArray(thing)) { | ||
var copy = _.extend({}, thing); | ||
_(copy).each(function (f, key) { | ||
copy[key].__name = key; | ||
if (key.substring(key.length - 4) === 'Sync') { | ||
copy[key.substring(0, key.length - 4)] = callbackify(f, | ||
key === 'existsSync' ? false : true); | ||
copy[key.substring(0, key.length - 4)].__name = key; | ||
} | ||
}); | ||
return copy; | ||
} else { | ||
return thing; | ||
} | ||
} | ||
var fsSync = callbackify(fs); | ||
var pathSync = callbackify(path); | ||
// hack in a synchronous version of async | ||
var asyncSync = { | ||
/* | ||
Synchronous forEachSeries, with an asynchronous signature. Because it's | ||
synchronous, it can also be used as the forEach function. | ||
See the async module for details. | ||
*/ | ||
forEachSeries: function (array, eachCb, finalCb) { | ||
var error = null; | ||
function flag(err) { | ||
if (err) { | ||
error = err; | ||
} | ||
} | ||
for (var i = 0; i < array.length; i++) { | ||
var item = array[i]; | ||
eachCb(item, flag); | ||
if (error) { | ||
break; | ||
} | ||
} | ||
finalCb(error); | ||
}, | ||
/* | ||
Synchronous detect, with an asynchronous signature. | ||
See the async module for details. | ||
*/ | ||
detect: function (array, passCb, resultCb) { | ||
var detected; | ||
var detectedValue; | ||
function flag(yes) { | ||
detected = yes; | ||
if (detected) { | ||
detectedValue = this; | ||
} | ||
} | ||
for (var i = 0; i < array.length; i++) { | ||
var item = array[i]; | ||
passCb(item, flag.bind(item)); | ||
if (detected) { | ||
break; | ||
} | ||
} | ||
resultCb(detectedValue); | ||
}, | ||
/* | ||
Synchronous some, with an asynchronous signature. | ||
See the async module for details. | ||
*/ | ||
some: function (array, passCb, doneCb) { | ||
var detected; | ||
function flag(yes) { | ||
detected = yes; | ||
} | ||
for (var i = 0; i < array.length; i++) { | ||
var item = array[i]; | ||
passCb(item, flag); | ||
if (detected) { | ||
break; | ||
} | ||
} | ||
doneCb(detected); | ||
} | ||
}; | ||
asyncSync.forEach = asyncSync.forEachSeries; | ||
/* | ||
@@ -159,45 +40,2 @@ Take an array or a value, and if it's a value, wrap it in an array. | ||
/* | ||
Compare the modified time stamps of two files. | ||
@param {String} a Path to first file. | ||
@param {String} b Path to second file. | ||
@param {function} cb Function to call with comparison value. | ||
@param {boolean} sync Flag to force function to run synchronously. | ||
*/ | ||
function compareFileDates(a, b, cb, sync) { | ||
(function (fs) { | ||
if (sync) { | ||
fs = fsSync; | ||
} | ||
fs.stat(a, function (errA, statA) { | ||
fs.stat(b, function (errB, statB) { | ||
if (errA && errB) { | ||
// assuming neither file exists | ||
return cb(0); | ||
} | ||
if (errA) { | ||
return cb(1); | ||
} | ||
if (errB) { | ||
return cb(-1); | ||
} | ||
if (statB.mtime > statA.mtime) { | ||
return cb(1); | ||
} | ||
if (statB.mtime < statA.mtime) { | ||
return cb(-1); | ||
} | ||
return cb(0); | ||
}); | ||
}); | ||
}(fs)); | ||
} | ||
/* | ||
@copy compareFileDates | ||
*/ | ||
function compareFileDatesSync(a, b, cb) { | ||
compareFileDates(a, b, cb, true); | ||
} | ||
/* | ||
Require the specified rule modules, either from the default location or from | ||
@@ -293,27 +131,17 @@ the user-specified location. | ||
/* Check if a directory exists. */ | ||
function dirExists(dir, cb, sync) { | ||
(function (path, fs) { | ||
if (sync) { | ||
path = pathSync; | ||
fs = fsSync; | ||
function dirExists(dir, cb) { | ||
var fsExists = fs.exists || path.exists; | ||
fsExists(dir, function (exists) { | ||
if (!exists) { | ||
return cb(false); | ||
} | ||
var fsExists = fs.exists || path.exists; | ||
fsExists(dir, function (exists) { | ||
if (!exists) { | ||
fs.stat(dir, function (err, stat) { | ||
if (err) { | ||
return cb(false); | ||
} | ||
fs.stat(dir, function (err, stat) { | ||
if (err) { | ||
return cb(false); | ||
} | ||
return cb(stat.isDirectory()); | ||
}); | ||
return cb(stat.isDirectory()); | ||
}); | ||
}(path, fs)); | ||
}); | ||
} | ||
function dirExistsSync(dir, cb) { | ||
dirExists(dir, cb, true); | ||
} | ||
/* | ||
@@ -324,29 +152,15 @@ Find one of a list of files. | ||
@param {function} elseCb If one is not found, do this. | ||
@param {boolean} sync Flag to force function to run synchronously. | ||
*/ | ||
function findFile(fileList, ifCb, elseCb, sync) { | ||
(function (async, fs, path) { | ||
if (sync) { | ||
async = asyncSync; | ||
fs = fsSync; | ||
function findFile(fileList, ifCb, elseCb) { | ||
var fsExists = fs.exists || path.exists; | ||
async.detect(fileList, fsExists, function (file) { | ||
if (file) { | ||
ifCb(file); | ||
} else { | ||
elseCb(); | ||
} | ||
var fsExists = fs.exists || path.exists; | ||
async.detect(fileList, fsExists, function (file) { | ||
if (file) { | ||
ifCb(file); | ||
} else { | ||
elseCb(); | ||
} | ||
}); | ||
}(async, fs, path)); | ||
}); | ||
} | ||
/* | ||
@copy findFile | ||
*/ | ||
function findFileSync(fileList, ifCb, elseCb) { | ||
findFile(fileList, ifCb, elseCb, true); | ||
} | ||
/* | ||
Find one of a list of directories. | ||
@@ -356,29 +170,14 @@ @param {Array} dirList Possible directories to find. | ||
@param {function} elseCb If one is not found, do this. | ||
@param {boolean} sync Flag to force function to run synchronously. | ||
*/ | ||
function findDir(dirList, ifCb, elseCb, sync) { | ||
(function (async, path, dirExists) { | ||
if (sync) { | ||
async = asyncSync; | ||
path = pathSync; | ||
dirExists = dirExistsSync; | ||
function findDir(dirList, ifCb, elseCb) { | ||
async.detect(dirList, dirExists, function (dir) { | ||
if (dir) { | ||
ifCb(dir); | ||
} else { | ||
elseCb(); | ||
} | ||
async.detect(dirList, dirExists, function (dir) { | ||
if (dir) { | ||
ifCb(dir); | ||
} else { | ||
elseCb(); | ||
} | ||
}); | ||
}(async, path, dirExists)); | ||
}); | ||
} | ||
/* | ||
@copy findDir | ||
*/ | ||
function findDirSync(dirList, ifCb, elseCb) { | ||
findDir(dirList, ifCb, elseCb, true); | ||
} | ||
/* | ||
Compress absolute paths down to minimal non-colliding lengths. | ||
@@ -468,20 +267,5 @@ @param {Array} pathList List of absolute paths. | ||
module.exports.findDir = findDir; | ||
module.exports.compareFileDates = compareFileDates; | ||
module.exports.toArray = toArray; | ||
module.exports.localizePaths = localizePaths; | ||
module.exports.cleanUndefinedProperties = cleanUndefinedProperties; | ||
module.exports.isCapitalized = isCapitalized; | ||
module.exports.fs = fs; | ||
module.exports.path = path; | ||
module.exports.async = async; | ||
var sync = {}; | ||
_.extend(sync, module.exports); | ||
sync.compareFileDates = compareFileDatesSync; | ||
sync.findFile = findFileSync; | ||
sync.findDir = findDirSync; | ||
sync.fs = fsSync; | ||
sync.path = pathSync; | ||
sync.async = asyncSync; | ||
module.exports.sync = sync; | ||
module.exports.isCapitalized = isCapitalized; |
@@ -5,3 +5,3 @@ { | ||
"description": "Create documentation from or otherwise analyze/transform JavaScript source.", | ||
"version": "0.2.3", | ||
"version": "0.3.0", | ||
"homepage": "https://github.com/jdeal/doctor", | ||
@@ -13,19 +13,17 @@ "repository": { | ||
"main": "lib/doctor", | ||
"engines": { | ||
"node": ">= 0.4.12" | ||
}, | ||
"dependencies": { | ||
"optimist": "~0.3.1", | ||
"async": "~0.1.9", | ||
"ncp": "=0.2.6", | ||
"pegjs": "=0.6.2", | ||
"async": "~0.2.9", | ||
"ncp": "=0.4.2", | ||
"handlebars": "~1.0.0", | ||
"underscore": "~1.2.2", | ||
"mkdirp": "~0.3.4", | ||
"marked": "~0.2.5" | ||
"mkdirp": "=0.3.4", | ||
"marked": "=0.2.5", | ||
"fs-compare": "=0.0.4", | ||
"quickpeg": "=0.0.3" | ||
}, | ||
"devDependencies": { | ||
"mocha": ">=0.12.1", | ||
"chai": ">=0.3.3", | ||
"js-yaml": "~0.3.5" | ||
"mocha": "=0.12.1", | ||
"chai": "=0.3.3", | ||
"js-yaml": "=0.3.5" | ||
}, | ||
@@ -32,0 +30,0 @@ "bin": { |
@@ -5,6 +5,2 @@ # doctor | ||
__Doctor is still under development, so be careful.__ It is probably stable enough to be useful, but no promises. | ||
![pinch](https://github.com/jdeal/doctor/raw/master/images/pinch-points-warning-143.png) | ||
Doctor converts JavaScript source to documentation, using rules to rely on | ||
@@ -11,0 +7,0 @@ conventions so that comment tags are (mostly) not needed. |
@@ -948,4 +948,2 @@ var Path = require('path'); | ||
if (modulesToRemove.indexOf(moduleName) >= 0) { | ||
//console.log(key) | ||
//console.log(item.visibility); | ||
if (item.visibility === 'public') { | ||
@@ -1128,45 +1126,2 @@ // don't remove public item | ||
// rules.push({ | ||
// type: 'markdown-link', | ||
// report: function (node, report) { | ||
// var isToc = node.item('isToc'); | ||
// if (isToc) { | ||
// if (node.nodes && node.nodes[0] && node.nodes[0].value) { | ||
// return { | ||
// key: '' | ||
// } | ||
// } | ||
// console.log(node.nodes[0].value) | ||
// } | ||
// } | ||
// }); | ||
// rules.push({ | ||
// type: 'markdown-content', | ||
// report: function (node, report) { | ||
// var content = node.item('document-content'); | ||
// content.push(node.value); | ||
// } | ||
// }); | ||
// rules.push({ | ||
// type: 'end-markdown', | ||
// report: function (node, report) { | ||
// var content = node.item('document-content'); | ||
// content = content.join(''); | ||
// var item = node.item('document-item'); | ||
// item.content = content; | ||
// } | ||
// }); | ||
// rules.push({ | ||
// type: '', | ||
// match: function (node) { | ||
// return node.type.substring(0, 'markdown-'.length) === 'markdown-'; | ||
// }, | ||
// report: function (node, report) { | ||
// console.log("begin:" + node.type); | ||
// } | ||
// }); | ||
module.exports = rules; | ||
@@ -1173,0 +1128,0 @@ |
@@ -1,9 +0,8 @@ | ||
/*global suite:false, test:false*/ | ||
/*global suite:false, test:false, before:false*/ | ||
var peg = require('pegjs'); | ||
var fs = require('fs'); | ||
var Path = require('path'); | ||
var quickpeg = require('quickpeg'); | ||
var grammar = fs.readFileSync(Path.join(__dirname, '../grammar/comment-tags.pegjs'), 'utf8'); | ||
var parser = peg.buildParser(grammar); | ||
var parser; | ||
@@ -14,2 +13,9 @@ var assert = require('chai').assert; | ||
before(function (done) { | ||
quickpeg(Path.join(__dirname, '../grammar/comment-tags.pegjs'), function (err, p) { | ||
parser = p; | ||
done(); | ||
}); | ||
}); | ||
test('simple returns tag', function () { | ||
@@ -16,0 +22,0 @@ var comment = "@returns description"; |
@@ -67,52 +67,2 @@ /*global suite:false, test:false*/ | ||
test('compareFileDates old file, new file', function (done) { | ||
var newFile = 'new1.txt'; | ||
fs.writeFileSync(newFile, 'data'); | ||
util.compareFileDates(__filename, newFile, function (cmp) { | ||
assert.equal(cmp, 1); | ||
fs.unlinkSync(newFile); | ||
done(); | ||
}); | ||
}); | ||
test('compareFileDates new file, old file', function (done) { | ||
var newFile = 'new2.txt'; | ||
fs.writeFileSync(newFile, 'data'); | ||
util.compareFileDates(newFile, __filename, function (cmp) { | ||
assert.equal(cmp, -1); | ||
fs.unlinkSync(newFile); | ||
done(); | ||
}); | ||
}); | ||
test('compareFileDates same file', function (done) { | ||
util.compareFileDates(__filename, __filename, function (cmp) { | ||
assert.equal(cmp, 0); | ||
done(); | ||
}); | ||
}); | ||
test('compareFileDates file a does not exist', function (done) { | ||
util.compareFileDates('doesNotExist', __filename, function (cmp) { | ||
assert.equal(cmp, 1); | ||
done(); | ||
}); | ||
}); | ||
test('compareFileDates file b does not exist', function (done) { | ||
util.compareFileDates(__filename, 'doesNotExist', function (cmp) { | ||
assert.equal(cmp, -1); | ||
done(); | ||
}); | ||
}); | ||
test('compareFileDates neither file exists', function (done) { | ||
util.compareFileDates('doesNotExist', 'doesNotExistEither', function (cmp) { | ||
assert.equal(cmp, 0); | ||
done(); | ||
}); | ||
}); | ||
test('findFunctions', function () { | ||
@@ -119,0 +69,0 @@ var dir = 'render'; |
@@ -7,2 +7,6 @@ # [Overview](README.md) | ||
## [count-console-log](examples/count-console-log/README.md) | ||
## [count-console-log](examples/count-console-log/README.md) | ||
## [public-api](examples/public-api/README.md) | ||
## [remove-debug](examples/remove-debug/README.md) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
5638697
9
87800
962
+ Addedfs-compare@=0.0.4
+ Addedquickpeg@=0.0.3
- Removedpegjs@=0.6.2
- Removedasync@0.1.22(transitive)
- Removedmarked@0.2.10(transitive)
- Removedmkdirp@0.3.5(transitive)
Updatedasync@~0.2.9
Updatedmarked@=0.2.5
Updatedmkdirp@=0.3.4
Updatedncp@=0.4.2