dgeni-packages
Advanced tools
Comparing version 0.9.6 to 0.10.0-beta.1
@@ -1,28 +0,30 @@ | ||
module.exports = function(config) { | ||
var Package = require('dgeni').Package; | ||
// Create a set of pseudo processors that act as markers to help position real | ||
// processors at the right place in the pipeline | ||
var processors = config.append('processing.processors', [ | ||
{ name: 'reading-files' }, | ||
{ name: 'files-read', runAfter: ['reading-files'] }, | ||
{ name: 'processing-docs', runAfter: ['files-read'] }, | ||
{ name: 'docs-processed', runAfter: ['processing-docs'] }, | ||
{ name: 'adding-extra-docs', runAfter: ['docs-processed'] }, | ||
{ name: 'extra-docs-added', runAfter: ['adding-extra-docs'] }, | ||
{ name: 'rendering-docs', runAfter: ['extra-docs-added'] }, | ||
{ name: 'docs-rendered', runAfter: ['rendering-docs'] }, | ||
{ name: 'writing-files', runAfter: ['docs-rendered'] }, | ||
{ name: 'files-written', runAfter: ['writing-files'] } | ||
]); | ||
// Define the `base` package | ||
module.exports = new Package('base') | ||
// A set of pseudo processors that act as markers to help position real processors at the right | ||
// place in the pipeline | ||
.processor({ name: 'reading-files' }) | ||
.processor({ name: 'files-read', $runAfter: ['reading-files'] }) | ||
.processor({ name: 'processing-docs', $runAfter: ['files-read'] }) | ||
.processor({ name: 'docs-processed', $runAfter: ['processing-docs'] }) | ||
.processor({ name: 'adding-extra-docs', $runAfter: ['docs-processed'] }) | ||
.processor({ name: 'extra-docs-added', $runAfter: ['adding-extra-docs'] }) | ||
.processor({ name: 'rendering-docs', $runAfter: ['extra-docs-added'] }) | ||
.processor({ name: 'docs-rendered', $runAfter: ['rendering-docs'] }) | ||
.processor({ name: 'writing-files', $runAfter: ['docs-rendered'] }) | ||
.processor({ name: 'files-written', $runAfter: ['writing-files'] }) | ||
config.append('processing.processors', [ | ||
require('./processors/read-files'), | ||
require('./processors/render-docs'), | ||
require('./processors/templateFinder'), | ||
require('./processors/unescape-comments'), | ||
require('./processors/write-files') | ||
]); | ||
// Real processors for this package | ||
.processor(require('./processors/read-files')) | ||
.processor(require('./processors/render-docs')) | ||
.processor(require('./processors/unescape-comments')) | ||
.processor(require('./processors/write-files')) | ||
.processor(require('./processors/debugDumpProcessor')) | ||
return config; | ||
}; | ||
// Helper services | ||
.factory(require('./services/templateFinder')) | ||
.factory(require('./services/encodeCodeBlock')) | ||
.factory(require('./services/trimIndentation')) | ||
.factory(require('./services/createDocMessage')); |
@@ -0,1 +1,2 @@ | ||
require('es6-shim'); | ||
var path = require('canonical-path'); | ||
@@ -7,73 +8,181 @@ var Q = require('q'); | ||
var glob = require('glob'); | ||
var log = require('winston'); | ||
var Minimatch = require("minimatch").Minimatch; | ||
/** | ||
* @dgPackage read-files | ||
* @description A doc processor that reads documents from files and adds them to the docs array | ||
* @dgPackage readFilesProcessor | ||
* | ||
* @description Read documents from files and add them to the docs collection | ||
* | ||
* @property {string} basePath The path against which all paths to files are resolved | ||
* | ||
* @property {Array.<String|Object>} sourceFiles A collection of info about what files to read. | ||
* If the item is a string then it is treated as a glob pattern. If the item is an object then | ||
* it can have the following properties: | ||
* | ||
* * `basePath` {string} the `relativeFile` property of the generated docs will be relative | ||
* to this path. This path is relative to `readFileProcessor.basePath`. Defaults to `.`. | ||
* * `include` {string} glob pattern of files to include (relative to `readFileProcessor.basePath`) | ||
* * `exclude` {string} glob pattern of files to exclude (relative to `readFileProcessor.basePath`) | ||
* * `reader` {string} name of a file reader to use for these files | ||
* | ||
* @property {Array.<Function>} fileReaders A collection of file readers. A file reader is an object | ||
* that has the following properties/methods: | ||
* * `name` - the name of the file reader so that sourceFiles can reference it | ||
* * `getDocs(fileInfo)` - this method is called to read the content of the file specified | ||
* by the `fileInfo` object and return an array of documents. | ||
* * `defaultPattern {Regex}` - a regular expression used to match to source files if no fileReader | ||
* is specified in the sourceInfo item. | ||
* | ||
*/ | ||
module.exports = { | ||
name: 'read-files', | ||
runAfter: ['reading-files'], | ||
runBefore: ['files-read'], | ||
module.exports = function readFilesProcessor(log) { | ||
return { | ||
$validate: { | ||
basePath: { presence: true }, | ||
sourceFiles: { presence: true }, | ||
fileReaders: { presence: true }, | ||
}, | ||
$runAfter: ['reading-files'], | ||
$runBefore: ['files-read'], | ||
$process: function() { | ||
var fileReaders = this.fileReaders; | ||
var fileReaderMap = getFileReaderMap(fileReaders); | ||
var basePath = this.basePath; | ||
process: function(docs, config) { | ||
var sourcePromises = this.sourceFiles.map(function(sourceInfo) { | ||
var projectPath = config.get('source.projectPath'); | ||
if ( !projectPath ) { | ||
throw new Error('Missing configuration property.\n' + | ||
'You must provide the path to the root of the project in `config.source.projectPath`'); | ||
} | ||
sourceInfo = normalizeSourceInfo(basePath, sourceInfo); | ||
var basePath = config.basePath || process.cwd(); | ||
var fileReaders = config.source.fileReaders; | ||
var sourceFiles = config.source.files; | ||
log.debug('Source Info:\n', sourceInfo); | ||
return Q.all(_.map(sourceFiles, function(fileInfo) { | ||
var pattern, files; | ||
return getSourceFiles(sourceInfo).then(function(files) { | ||
// These fileinfo patterns and basePaths should be relative to the basePath but if we don't have | ||
// a basepath specified then we just use the config.basePath or current working directory | ||
if ( _.isString(fileInfo) ) { | ||
fileInfo = { pattern: fileInfo, basePath: basePath }; | ||
} else if ( _.isObject(fileInfo) ) { | ||
fileInfo.basePath = fileInfo.basePath || basePath; | ||
} else { | ||
throw new Error('Invalid files parameter. ' + | ||
'You must pass an array of items, each of which is either a string or an object of the form ' + | ||
'{ pattern: "...", basePath: "..." }'); | ||
} | ||
var docsPromises = []; | ||
// Ensure that the pattern is relative | ||
fileInfo.pattern = path.relative(fileInfo.basePath, path.resolve(fileInfo.basePath, fileInfo.pattern)); | ||
log.debug('Found ' + files.length + ' files:\n', files); | ||
log.debug('reading files: ', fileInfo); | ||
files.forEach(function(file) { | ||
files = glob.sync(fileInfo.pattern, { cwd: fileInfo.basePath }); | ||
// Load up each file and extract documents using the appropriate fileReader | ||
var docsPromise = qfs.read(file).then(function(content) { | ||
log.debug('Found ' + files.length + ' files'); | ||
// Choose a file reader for this file | ||
var fileReader = fileReaderMap.get(sourceInfo.fileReader) || matchFileReader(fileReaders, file); | ||
var docPromises = []; | ||
_.forEach(files, function(file) { | ||
_.any(fileReaders, function(extractor) { | ||
if ( extractor.pattern.test(file) ) { | ||
docPromises.push(qfs.read(path.resolve(fileInfo.basePath, file)).then(function(content) { | ||
var docs = extractor.processFile(file, content, fileInfo.basePath); | ||
log.debug('Reading File Content\nFile Path:', file, '\nFile Reader:', fileReader.name); | ||
_.forEach(docs, function(doc) { | ||
doc.fileName = path.basename(doc.file, '.'+doc.fileType); | ||
doc.relativePath = path.relative(projectPath, path.resolve(doc.basePath, doc.file)); | ||
var fileInfo = createFileInfo(file, content, sourceInfo, fileReader); | ||
var docs = fileReader.getDocs(fileInfo); | ||
// Attach the fileInfo object to each doc | ||
docs.forEach(function(doc) { | ||
doc.fileInfo = fileInfo; | ||
}); | ||
return docs; | ||
})); | ||
return true; | ||
} | ||
}); | ||
docsPromises.push(docsPromise); | ||
}); | ||
return Q.all(docsPromises).then(_.flatten); | ||
}); | ||
}); | ||
return Q.all(sourcePromises).then(_.flatten); | ||
} | ||
}; | ||
}; | ||
return Q.all(docPromises).then(_.flatten); | ||
})) | ||
function createFileInfo(file, content, sourceInfo, fileReader) { | ||
return { | ||
fileReader: fileReader.name, | ||
filePath: file, | ||
baseName: path.basename(file, path.extname(file)), | ||
extension: path.extname(file).replace(/^\./, ''), | ||
basePath: sourceInfo.basePath, | ||
relativePath: path.relative(sourceInfo.basePath, file), | ||
content: content | ||
}; | ||
} | ||
.then(_.flatten); | ||
function getFileReaderMap(fileReaders) { | ||
var fileReaderMap = new Map(); | ||
fileReaders.forEach(function(fileReader) { | ||
if ( !fileReader.name ) { | ||
throw new Error('Invalid File Reader: It must have a name property'); | ||
} | ||
if ( typeof fileReader.getDocs !== 'function' ) { | ||
throw new Error('Invalid File Reader: "' + fileReader.name + '": It must have a getDocs property'); | ||
} | ||
fileReaderMap.set(fileReader.name, fileReader); | ||
}); | ||
return fileReaderMap; | ||
} | ||
function matchFileReader(fileReaders, file) { | ||
// We can't use fileReaders.find here because q-io overrides the es6-shim find() function | ||
var found = _.find(fileReaders, function(fileReader) { | ||
// If no defaultPattern is defined then match everything | ||
return !fileReader.defaultPattern || fileReader.defaultPattern.test(file); | ||
}); | ||
if ( !found ) { throw new Error('No file reader found for ' + file); } | ||
return found; | ||
} | ||
/** | ||
* Resolve the relative include/exclude paths in the sourceInfo object, | ||
* @private | ||
*/ | ||
function normalizeSourceInfo(basePath, sourceInfo) { | ||
if ( _.isString(sourceInfo) ) { | ||
sourceInfo = { include: sourceInfo }; | ||
} else if ( !_.isObject(sourceInfo) ) { | ||
throw new Error('Invalid sourceFiles parameter. ' + | ||
'You must pass an array of items, each of which is either a string or an object of the form ' + | ||
'{ include: "...", basePath: "...", exclude: "...", fileReader: "..." }'); | ||
} | ||
}; | ||
sourceInfo.basePath = path.resolve(basePath, sourceInfo.basePath || '.'); | ||
sourceInfo.include = path.resolve(basePath, sourceInfo.include); | ||
sourceInfo.exclude = sourceInfo.exclude && path.resolve(basePath, sourceInfo.exclude); | ||
return sourceInfo; | ||
} | ||
function getSourceFiles(sourceInfo) { | ||
var excludeMatcher = sourceInfo.exclude && new Minimatch(sourceInfo.exclude); | ||
var filesPromise = Q.nfcall(glob, sourceInfo.include); | ||
return filesPromise.then(function(files) { | ||
// Filter the files on whether they match the `exclude` property and whether they are files | ||
var filteredFilePromises = files.map(function(file) { | ||
if ( excludeMatcher && excludeMatcher.match(file) ) { | ||
// Return a promise for `null` if the path is excluded | ||
// Doing this first - it is synchronous - saves us even making the isFile call if not needed | ||
return Q(null); | ||
} else { | ||
// Return a promise for the file if path is a file, otherwise return a promise for `null` | ||
return qfs.isFile(file).then(function(isFile) { return isFile ? file : null; }); | ||
} | ||
}); | ||
// Return a promise to a filtered list of files, those that are files and not excluded | ||
// (i.e. those that are not `null` from the previous block of code) | ||
return Q.all(filteredFilePromises).then(function(filteredFiles) { | ||
return filteredFiles.filter(function(filteredFile) { return filteredFile; }); | ||
}); | ||
}); | ||
} |
@@ -0,39 +1,60 @@ | ||
require('es6-shim'); | ||
var _ = require('lodash'); | ||
var log = require('winston'); | ||
/** | ||
* @dgProcessor render-doc | ||
* @dgProcessor renderDoc | ||
* @description | ||
* Render the set of documents using the provided `templateEngine`, to the output folder, | ||
* using the extra data and the templates found by `templateFinder`. | ||
* @param {object} docs The documents to render | ||
* @param {Config} config The Dgeni configuration object | ||
* @param {object} extraData All the injectable components in the base injector | ||
* @param {object} templateFinder A helper that can match docs to templates | ||
* @param {object} templateEngine The engine that will render the docs/templates | ||
* Render the set of documents using the provided `templateEngine`, to `doc.renderedContent` | ||
* using the `extraData`, `helpers` and the templates found by `templateFinder`. | ||
* | ||
* @param {Logger} log A service for logging error and information messages | ||
* @param {TemplateFinder} templateFinder A service that matches templates to docs. A default | ||
* templateFinder is provided in this base package. | ||
* @param {TemplateEngine} templateEngine An instance of a templateEngine that will render the | ||
* docs/templates. A templateEngine is an object that has | ||
* a `getRenderer()` method, which itself returns a | ||
* `render(template, data)` function. The base package | ||
* doesn't provide a default templateEngine. | ||
* There is a Nunjucks based engine in the nunjucks module. | ||
* | ||
* @property {Map} extraData Extra data that will be passed to the rendering engine. Your | ||
* services and processors can add data to this object to be made | ||
* available in templates when they are rendered. | ||
* @property {Map} helpers Extra helper functions that will be passed to the rendering engine. | ||
* Your services and processors can add helper functions to this | ||
* object to be made available in templates when they are rendered. | ||
*/ | ||
var plugin = module.exports = { | ||
name: 'render-docs', | ||
runAfter: ['rendering-docs'], | ||
runBefore: ['docs-rendered'], | ||
module.exports = function renderDocsProcessor(log, templateFinder, templateEngine, createDocMessage) { | ||
return { | ||
helpers: {}, | ||
extraData: {}, | ||
process: function render(docs, config, extraData, templateFinder, templateEngine) { | ||
$runAfter: ['rendering-docs'], | ||
$runBefore: ['docs-rendered'], | ||
$validate: { | ||
helpers: { }, | ||
extraData: { } | ||
}, | ||
$process: function process(docs) { | ||
// Extract any extra helper functions/data from the config | ||
var helpers = _.defaults(Object.create(null), config.rendering.helpers); | ||
var render = templateEngine.getRenderer(); | ||
var findTemplate = templateFinder.getFinder(); | ||
docs.forEach(function(doc) { | ||
log.debug('Rendering doc:', doc.id || doc.name || doc.path); | ||
try { | ||
var data = _.defaults( | ||
{ doc: doc, docs: docs }, | ||
this.extraData, | ||
this.helpers); | ||
var templateFile = findTemplate(data.doc); | ||
doc.renderedContent = render(templateFile, data); | ||
} catch(ex) { | ||
log.debug(_.omit(doc, ['content', 'moduleDoc', 'components', 'serviceDoc', 'providerDoc'])); | ||
throw new Error(createDocMessage('Failed to render', doc, ex)); | ||
} | ||
}, this); | ||
_.forEach(docs, function(doc) { | ||
log.debug('Rendering doc', doc.id, doc.name); | ||
try { | ||
var data = _.defaults(Object.create(null), { doc: doc, docs: docs }, extraData, helpers); | ||
var templateFile = templateFinder(data.doc); | ||
doc.renderedContent = templateEngine.render(templateFile, data); | ||
} catch(ex) { | ||
console.log(_.omit(doc, ['content', 'moduleDoc', 'components', 'serviceDoc', 'providerDoc'])); | ||
throw new Error('Failed to render doc "' + doc.id + '" from file "' + doc.file + '" line ' + doc.startingLine + '\n Error Message follows:\n' + ex.stack); | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
}; | ||
}; |
var _ = require('lodash'); | ||
/** | ||
* @dgProcessor unescape-comments | ||
* @dgProcessor unescapeCommentsProcessor | ||
* @description | ||
@@ -10,12 +10,12 @@ * Some files (like CSS) use the same comment markers as the jsdoc comments, such as /&#42;. | ||
*/ | ||
module.exports = { | ||
name: 'unescape-comments', | ||
runAfter: ['docs-rendered'], | ||
runBefore: ['writing-files'], | ||
process: function(docs) { | ||
_.forEach(docs, function(doc) { | ||
doc.renderedContent = doc.renderedContent.replace(/\/&#42;/g, '/*').replace(/&#42;\//g, '*/'); | ||
}); | ||
} | ||
module.exports = function unescapeCommentsProcessor() { | ||
return { | ||
$runAfter: ['docs-rendered'], | ||
$runBefore: ['writing-files'], | ||
$process: function(docs) { | ||
_.forEach(docs, function(doc) { | ||
doc.renderedContent = doc.renderedContent.replace(/\/&#42;/g, '/*').replace(/&#42;\//g, '*/'); | ||
}); | ||
} | ||
}; | ||
}; |
var _ = require('lodash'); | ||
var path = require('canonical-path'); | ||
var log = require('winston'); | ||
var fs = require('q-io/fs'); | ||
@@ -8,30 +7,37 @@ var Q = require('q'); | ||
/** | ||
* @dgProcessor write-files | ||
* @dgProcessor writeFilesProcessor | ||
* @param {Object} log A service that provides logging | ||
* @description Write the value of `doc.renderedContent` to a file a `doc.outputPath`. | ||
* @property {String} outputFolder The base path to the folder where files are outputted | ||
*/ | ||
module.exports = { | ||
name: 'write-files', | ||
runAfter:['writing-files'], | ||
runBefore: ['files-written'], | ||
process: function(docs, config) { | ||
var outputFolder = path.resolve(config.basePath, config.rendering.outputFolder); | ||
return Q.all(_.map(docs, function(doc) { | ||
module.exports = function writeFilesProcessor(log, readFilesProcessor) { | ||
return { | ||
outputFolder: null, | ||
$validate: { | ||
outputFolder: { presence: true }, | ||
}, | ||
$runAfter:['writing-files'], | ||
$runBefore: ['files-written'], | ||
$process: function(docs) { | ||
var outputFolder = this.outputFolder; | ||
return Q.all(_.map(docs, function(doc) { | ||
if ( !doc.outputPath ) { | ||
log.debug('Document "' + doc.id + ', ' + doc.docType + '" has no outputPath.'); | ||
} else { | ||
if ( !doc.outputPath ) { | ||
log.debug('Document "' + doc.id + ', ' + doc.docType + '" has no outputPath.'); | ||
} else { | ||
var outputFile = path.resolve(outputFolder, doc.outputPath); | ||
var outputFile = path.resolve(readFilesProcessor.basePath, outputFolder, doc.outputPath); | ||
log.silly('writing file', outputFile); | ||
return writeFile(outputFile, doc.renderedContent).then(function() { | ||
log.debug('written file', outputFile); | ||
return outputFile; | ||
}); | ||
log.silly('writing file', outputFile); | ||
return writeFile(outputFile, doc.renderedContent).then(function() { | ||
log.debug('written file', outputFile); | ||
return outputFile; | ||
}); | ||
} | ||
})).then(function() { | ||
return docs; | ||
}); | ||
} | ||
} | ||
})).then(function() { | ||
return docs; | ||
}); | ||
} | ||
}; | ||
}; | ||
@@ -38,0 +44,0 @@ |
@@ -1,5 +0,8 @@ | ||
var package = require('../index'); | ||
var basePackage = require('../index'); | ||
var Package = require('dgeni').Package; | ||
describe('base package', function() { | ||
it("should be instance of Package", function() { | ||
expect(basePackage instanceof Package).toBeTruthy(); | ||
}); | ||
}); |
var Q = require('q'); | ||
var MockFS = require('q-io/fs-mock'); | ||
var rewire = require('rewire'); | ||
var plugin = rewire('../../processors/read-files'); | ||
var path = require('canonical-path'); | ||
var readFilesFactory = require('../../processors/read-files'); | ||
var _ = require('lodash'); | ||
var Config = require('dgeni').Config; | ||
var mockLog = require('dgeni/lib/mocks/log')(/* true */); | ||
var mockFiles = { | ||
"docs": { | ||
"a.js": "// Mock code file", | ||
"b.ngdoc": "mock documentation file" | ||
}, | ||
"src": { | ||
"a.js": "// Mock code file", | ||
"b.js": "// Other mock code file" | ||
} | ||
}; | ||
function tidyUp(promise, done) { | ||
return promise.then(function() { | ||
done(); | ||
},function(err) { | ||
console.log('ERROR', err.stack); | ||
done(err); | ||
}); | ||
} | ||
plugin.__set__('fs', MockFS(mockFiles)); | ||
plugin.__set__('glob.sync', function(pattern) { | ||
// Strip off the "./" from the start of the pattern | ||
pattern = pattern.replace(/^\.\//,''); | ||
return _.keys(mockFiles[pattern]); | ||
}); | ||
function createReadFilesProcessor(fileReaders, sourceFiles, basePath) { | ||
var processor = readFilesFactory(mockLog); | ||
processor.fileReaders = fileReaders; | ||
processor.sourceFiles = sourceFiles; | ||
processor.basePath = path.resolve(__dirname, basePath); | ||
return processor; | ||
} | ||
var mockNgDocFileReader = { | ||
pattern: /\.ngdoc$/, | ||
processFile: function(file, content) { | ||
return [{ content: content, file: file, fileType: 'ngdoc' }]; | ||
} | ||
}; | ||
var mockJsFileReader = { | ||
pattern: /\.js$/, | ||
processFile: function(file, content) { | ||
return [{ content: content, file: file, fileType: 'js' }]; | ||
} | ||
}; | ||
describe('read-files doc processor', function() { | ||
var config; | ||
beforeEach(function() { | ||
config = new Config(); | ||
}); | ||
it("should complain if a file reader is not valid", function() { | ||
expect(function() { | ||
var processor = createReadFilesProcessor([ {} ], ['docs/*'], '../fixtures'); | ||
processor.$process(); | ||
}).toThrowError('Invalid File Reader: It must have a name property'); | ||
it("should throw an error if the projectPath has not been set", function() { | ||
expect(function() { | ||
plugin.process({ source: { files: [] }}); | ||
}).toThrow(); | ||
var processor = createReadFilesProcessor([ { name: 'badFileReader' } ], ['docs/*'], '../fixtures'); | ||
processor.$process(); | ||
}).toThrowError('Invalid File Reader: "badFileReader": It must have a getDocs property'); | ||
}); | ||
it('should traverse the specified folder tree, reading each matching file', function() { | ||
it('should iterate over matching files, providing fileInfo to the reader', function(done) { | ||
config = new Config({ | ||
source: { | ||
projectPath: '.', | ||
fileReaders: [mockNgDocFileReader, mockJsFileReader], | ||
files: ['./docs', './src'] | ||
} | ||
var mockFileReader = { | ||
name: 'mockFileReader', | ||
getDocs: function(fileInfo) { return [{ fileInfo2: fileInfo }]; } | ||
}; | ||
processor = createReadFilesProcessor([mockFileReader], ['docs/*'], '../fixtures'); | ||
var promise = processor.$process().then(function(docs) { | ||
expect(docs.length).toEqual(2); | ||
expect(docs[0].fileInfo).toEqual({ | ||
fileReader: 'mockFileReader', | ||
filePath: path.resolve(processor.basePath, 'docs/a.js'), | ||
baseName: 'a', | ||
extension: 'js', | ||
basePath: processor.basePath, | ||
relativePath: 'docs/a.js', | ||
content: '// Mock code file' | ||
}); | ||
expect(docs[0].fileInfo2).toBe(docs[0].fileInfo); | ||
expect(docs[1].fileInfo).toEqual({ | ||
fileReader: 'mockFileReader', | ||
filePath: path.resolve(processor.basePath, 'docs/b.ngdoc'), | ||
baseName: 'b', | ||
extension: 'ngdoc', | ||
basePath: processor.basePath, | ||
relativePath: 'docs/b.ngdoc', | ||
content: 'mock documentation file' | ||
}); | ||
expect(docs[1].fileInfo2).toBe(docs[1].fileInfo); | ||
}); | ||
var injectables = { | ||
value: function() {} | ||
tidyUp(promise, done); | ||
}); | ||
describe('fileReaders', function() { | ||
var mockNgDocFileReader = { | ||
name: 'mockNgDocFileReader', | ||
defaultPattern: /\.ngdoc$/, | ||
getDocs: function(fileInfo) { return [{}]; } | ||
}; | ||
plugin.process([], config).then(function(docs) { | ||
expect(docs.length).toEqual(4); | ||
expect(docs[0]).toEqual({ | ||
file: "docs/a.js", | ||
content: "// Mock code file", | ||
fileType: 'js', | ||
fileName: "a" | ||
var mockJsFileReader = { | ||
name: 'mockJsFileReader', | ||
defaultPattern: /\.js$/, | ||
getDocs: function(fileInfo) { return [{}]; } | ||
}; | ||
it("should use the first file reader that matches if none is specified for a sourceInfo", function(done) { | ||
processor = createReadFilesProcessor([mockNgDocFileReader, mockJsFileReader], ['docs/*'], '../fixtures'); | ||
var promise = processor.$process().then(function(docs) { | ||
expect(docs[0].fileInfo.extension).toEqual('js'); | ||
expect(docs[0].fileInfo.fileReader).toEqual('mockJsFileReader'); | ||
expect(docs[1].fileInfo.extension).toEqual('ngdoc'); | ||
expect(docs[1].fileInfo.fileReader).toEqual('mockNgDocFileReader'); | ||
}); | ||
expect(docs[1]).toEqual({ | ||
file: "docs/b.ngdoc", | ||
content: "mock documentation file", | ||
fileType: 'ngdoc', | ||
fileName: 'b' | ||
tidyUp(promise, done); | ||
}); | ||
it("should use the fileReader named in the sourceInfo, rather than try to match one", function(done) { | ||
processor = createReadFilesProcessor([mockNgDocFileReader, mockJsFileReader], [{ include: 'docs/*', fileReader: 'mockJsFileReader' }], '../fixtures'); | ||
var promise = processor.$process().then(function(docs) { | ||
expect(docs[0].fileInfo.extension).toEqual('js'); | ||
expect(docs[0].fileInfo.fileReader).toEqual('mockJsFileReader'); | ||
expect(docs[1].fileInfo.extension).toEqual('ngdoc'); | ||
expect(docs[1].fileInfo.fileReader).toEqual('mockJsFileReader'); | ||
}); | ||
expect(docs[2]).toEqual({ | ||
file: "src/a.js", | ||
content: "// Mock code file", | ||
fileType: 'js', | ||
fileName: 'a' | ||
tidyUp(promise, done); | ||
}); | ||
}); | ||
describe('exclusions', function() { | ||
it("should exclude files that match the exclude property of a sourceInfo", function(done) { | ||
var mockFileReader = { | ||
name: 'mockFileReader', | ||
getDocs: function(fileInfo) { return [{ }]; } | ||
}; | ||
processor = createReadFilesProcessor([mockFileReader], [{ include: 'docs/*', exclude:'**/*.ngdoc' }], '../fixtures'); | ||
var promise = processor.$process().then(function(docs) { | ||
expect(docs.length).toEqual(1); | ||
expect(docs[0].fileInfo.extension).toEqual('js'); | ||
}); | ||
expect(docs[3]).toEqual({ | ||
file: "src/b.js", | ||
content: "// Other mock code file", | ||
fileType: 'js', | ||
fileName: 'a' | ||
}).done(); | ||
tidyUp(promise, done); | ||
}); | ||
}); | ||
describe("relative paths", function() { | ||
it("should set the relativePath on the doc.fileInfo property correctly", function(done) { | ||
var mockFileReader = { | ||
name: 'mockFileReader', | ||
getDocs: function(fileInfo) { return [{ }]; } | ||
}; | ||
processor = createReadFilesProcessor([mockFileReader], [{ include: 'src/**/*', basePath:'src' }], '../fixtures'); | ||
var promise = processor.$process().then(function(docs) { | ||
expect(docs.length).toEqual(2); | ||
expect(docs[0].fileInfo.relativePath).toEqual('f1/a.js'); | ||
expect(docs[1].fileInfo.relativePath).toEqual('f2/b.js'); | ||
}); | ||
tidyUp(promise, done); | ||
}); | ||
}); | ||
}); |
@@ -1,4 +0,57 @@ | ||
var Config = require('dgeni').Config; | ||
var renderDocsFactory = require('../../processors/render-docs'); | ||
var mockLog, mockTemplateFinder, mockTemplateEngine, renderSpy, findTemplateSpy; | ||
var mockLog = require('dgeni/lib/mocks/log')(/* true */); | ||
beforeEach(function() { | ||
findTemplateSpy = createSpy('findTemplate').and.returnValue('SOME TEMPLATE'); | ||
renderSpy = jasmine.createSpy('render'); | ||
mockTemplateFinder = { | ||
getFinder: function() { return findTemplateSpy; } | ||
}; | ||
mockTemplateEngine = { | ||
getRenderer: function() { return renderSpy; } | ||
}; | ||
}); | ||
describe("render-docs", function() { | ||
it("should call the templateFinder for each doc", function() { | ||
var doc1 = {}, doc2 = {}, docs = [ doc1, doc2 ]; | ||
var processor = renderDocsFactory(mockLog, mockTemplateFinder, mockTemplateEngine); | ||
processor.$process(docs); | ||
expect(findTemplateSpy.calls.count()).toEqual(2); | ||
expect(findTemplateSpy.calls.argsFor(0)).toEqual([doc1]); | ||
expect(findTemplateSpy.calls.argsFor(1)).toEqual([doc2]); | ||
}); | ||
it("should call the templateEngine.render with the template and data", function() { | ||
var doc1 = { id: 1 }, doc2 = { id: 2 }, docs = [ doc1, doc2 ]; | ||
var someProp = {}, someMethod = function() {}; | ||
var processor = renderDocsFactory(mockLog, mockTemplateFinder, mockTemplateEngine); | ||
processor.extraData.someProp = someProp; | ||
processor.helpers.someMethod = someMethod; | ||
processor.$process(docs); | ||
expect(renderSpy.calls.count()).toEqual(2); | ||
expect(renderSpy.calls.argsFor(0)).toEqual(['SOME TEMPLATE', | ||
{ doc: doc1, docs: docs, someProp: someProp, someMethod: someMethod }]); | ||
expect(renderSpy.calls.argsFor(1)).toEqual(['SOME TEMPLATE', | ||
{ doc: doc2, docs: docs, someProp: someProp, someMethod: someMethod }]); | ||
}); | ||
it("should place the result of calling templateEngine.render into doc.renderedContent", function() { | ||
var doc1 = { id: 1 }, doc2 = { id: 2 }, docs = [ doc1, doc2 ]; | ||
renderSpy.and.returnValue('RENDERED CONTENT'); | ||
var processor = renderDocsFactory(mockLog, mockTemplateFinder, mockTemplateEngine); | ||
processor.$process(docs); | ||
expect(doc1.renderedContent).toEqual('RENDERED CONTENT'); | ||
expect(doc2.renderedContent).toEqual('RENDERED CONTENT'); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
var processor = require('../../processors/unescape-comments'); | ||
var processorFactory = require('../../processors/unescape-comments'); | ||
@@ -8,5 +8,5 @@ describe("escaped-comments doc processor", function() { | ||
}; | ||
processor.process([doc]); | ||
processorFactory().$process([doc]); | ||
expect(doc.renderedContent).toEqual('Some text containing /* a comment */\nSome text containing /* a comment */'); | ||
}); | ||
}); |
@@ -1,19 +0,97 @@ | ||
## v0.9.6 07/21/2014 | ||
## v0.10.0-beta.1 25th July 2014 | ||
* feat(api-docs): allow packageName to be specified as a tag 08:08 PM Peter Bacon Darwin 211a4cb4 | ||
**This is a major rearchitecture in line with changing to | ||
[dgeni@0.4.0](https://github.com/angular/dgeni/blob/master/CHANGELOG.md#v040-beta1-25th-july-2014)** | ||
There are numerous breaking changes surrounding this release and that of dgeni 0.4.0. | ||
The most important changes are | ||
## v0.9.5 07/17/2014 | ||
* Configuration is done directly on the Processors and Services using Configuration Blocks, which | ||
are defined on Packages. | ||
* Everything is now dependency injected. Dgeni deals with instantiating Processors and Services | ||
but if you have properties on these that reference objects that should also be instantiated by the | ||
DI system then you can either ask for them to be injected into the config block: | ||
* fix(ng-doc/component-groups-generate): allow path/outputPath to be configurable 48d105bc | ||
* Revert "fix(ng-doc/component-groups-generate processor): use path/outputPath specified in process... 71ced7dd | ||
```js | ||
myPackage.config(function(processor1, someObj) { | ||
processor1.someProp = someObj; | ||
}); | ||
``` | ||
use the `injector` directly: | ||
## v0.9.4 07/17/2014 | ||
```js | ||
myPackage.config(function(processor1, injector) { | ||
processor1.someProp = injector.invoke(someObjFactory); | ||
}); | ||
``` | ||
** WARNING - this release had an invalid breaking change. Don't use it ** | ||
or use the `getInjectables()` helper service: | ||
* fix(utils/code): encode HTML entities 5091e1c1 | ||
* fix(ng-doc/component-groups-generate processor): use path/outputPath specified in processing.api-... f2c3776f | ||
```js | ||
myPackage.config(function(processor1, getInjectables) { | ||
processor1.someProp = getInjectables([someObjFactory, someOtherObjFactory]); | ||
}); | ||
``` | ||
* All real processors have changed their names from dash-case to camelCase. This is because it is | ||
easier for their names to be valid JavaScript identifiers. | ||
The most significant commits are: | ||
* fix(utils/code): encode HTML entities 13b99152 | ||
* feat(base/debugDumpProcessor): add new processor 4c126792 | ||
* feat(dgeni package): add initial package for documenting dgeni configurations 2bfa92b2 | ||
* refact(parseExamplesProcessor): use Map() for example.files d926879a | ||
* fix(ngdoc/module tag-def): module is the first segment of the relative path 649f3051 | ||
* fix(jsdoc/description tag-def): capture non-tag specific description ed68438d | ||
* feat(base/createDocMessage): add new service db11bc44 | ||
* fix(*): doc.file is now doc.fileInfo.filePath fb600502 | ||
* fix(ngdoc/collectPartialNamesProcessor): compute-id was renamed to computeIdProcessor 0158fb3b | ||
* fix(ngdoc/apiDocs): compute-path was renamed to computePathProcessor 9396f8c3 | ||
* fix(ngdoc/apiDocsProcessor): compute-id was renamed to computeIdProcessor a83d7fc9 | ||
* feat(ngdoc package): add getTypeClass service addebf63 | ||
* refact(base/code): rename to encodeCodeBlock 2ae134ff | ||
* feat(getTypeClass): add new service 9c49ff9d | ||
* fix(ngdocFileReader): must have an explicit name 9c5dd397 | ||
* test(nunjucks/templateEngine): templateEngine now relies on templateFinder 0269acf5 | ||
* fix(nunjucks/marked custom tag): add service name for DI injection 9069c24f | ||
* refact(trimIndentation): convert to DI service 3ab6c9ed | ||
* fix(nunjucks/templateEngine): get the templateFolders from the templateFinder 8760aa7f | ||
* fix(inlineTagProcessor): inline tag definitions are optional 380dd474 | ||
* fix(computePathProcessor): let writeFileProcessor deal with outputFolder 4057deb8 | ||
* fix(jsdoc fileReader): file-readers need explicit names cad6d043 | ||
* fix(jsdoc package): pseudo processors need to use $runBefore, etc. 5d32020f | ||
* fix(renderDocsProcessor): extra and helpers and optional 6bc1f16a | ||
* fix(readFilesProcessor): resolve include and exclude paths correctly f7ea5f78 | ||
* refact(computePathProcessor): read outputFolder config from writeFilesProcessor 828b48c3 | ||
* feat(api-docs): allow packageName to be specified as a tag 83c7e1fa | ||
* fix(jsdoc package): add trimWhitespaceTransform to package 75f52df1 | ||
* refact(templateFinder): now call getFinder() to get the actual function e8c015d9 | ||
* refact(jsdoc transforms): convert to DI services 5e92ff46 | ||
* feat(ngdocs/moduleMap): add new service to support apiDocsProcessor et al 19a37a27 | ||
* fix(runnableExample inline-tag): examples is now a Map d1857779 | ||
* test(tag-defs): split out tests into separate files 9be421f7 | ||
* fix(jsdoc package): jsdocFileReader should be loaded as a service 0c92271c | ||
* refact(ngdoc/tag-defs): convert to DI injectables d8512597 | ||
* refact(ngdoc/link inline tag): convert to DI injectable 77e51df3 | ||
* refact(ngdoc/code tag): convert to DI injectable 1d23cd4e | ||
* refact(ngdoc/code filter): convert to DI injectable 8c871b3d | ||
* feat(getLinkInfo): add new service 32aa8427 | ||
* refact(partialNameMap): rename and convert to DI service 49acc949 | ||
* feat(getPartialNames): add new service c6326268 | ||
* feat(parseCodeName): add new service e02fe91c | ||
* refact(base/services): move code and trimIndentation to be DI services 00d17816 | ||
* refact(examples service): move to its own service (as part of no-config update) 16d7819e | ||
* refact(renderDocsProcessor): templateEngine now has a `getRenderer()` method 4f50737b | ||
* refact(jsdoc): convert transforms to services 6dc5417f | ||
* refact(extractTagsProcessor): move computation into smaller functions fa1821c1 | ||
* refact(tagExtractor): move into the extractTagsProcessor 49e51da4 | ||
* refact(tagParser): move into the parseTagsProcessor efba4e9b | ||
* feat(read-files): add path exclusion and update to no-config 6853d759 | ||
* refact(*): update to use new processor configuration style c54fd8d6 | ||
* refact(*): use new dgeni Packages 128c2e61 | ||
## v0.9.3 05/22/2014 | ||
@@ -20,0 +98,0 @@ |
var path = require('canonical-path'); | ||
var packagePath = __dirname; | ||
var Package = require('dgeni').Package; | ||
module.exports = function(config) { | ||
module.exports = new Package('examples', ['jsdoc']) | ||
config.append('processing.processors', [ | ||
require('./processors/examples-parse'), | ||
require('./processors/examples-generate'), | ||
]); | ||
.processor(require('./processors/examples-parse')) | ||
.processor(require('./processors/examples-generate')) | ||
config.append('processing.inlineTagDefinitions', [ | ||
require('./inline-tag-defs/runnableExample') | ||
]); | ||
.factory(require('./services/examples')) | ||
.factory(require('./inline-tag-defs/runnableExample')) | ||
config.set('processing.examples.commonFiles', { | ||
scripts: [], | ||
stylesheets: [] | ||
}); | ||
.config(function(templateFinder, generateExamplesProcessor) { | ||
generateExamplesProcessor.templateFolder = 'examples'; | ||
templateFinder.templateFolders.unshift(path.resolve(packagePath, 'templates')); | ||
}) | ||
config.prepend('rendering.templateFolders', path.resolve(packagePath, 'templates')); | ||
return config; | ||
}; | ||
.config(function(inlineTagProcessor, runnableExampleInlineTagDef) { | ||
inlineTagProcessor.inlineTagDefinitions.push(runnableExampleInlineTagDef); | ||
}); |
@@ -1,19 +0,24 @@ | ||
module.exports = { | ||
name: 'runnableExample', | ||
description: 'Inject the specified runnable example into the doc', | ||
handlerFactory: function(examples) { | ||
return function(doc, tagName, description) { | ||
/** | ||
* @dgService runnableExampleInlineTagDef | ||
* @description | ||
* Inject the specified runnable example into the doc | ||
*/ | ||
module.exports = function runnableExampleInlineTagDef(examples, createDocMessage) { | ||
return { | ||
name: 'runnableExample', | ||
handler: function(doc, tagName, description) { | ||
// The tag description should contain the id of the runnable example doc | ||
var example = examples[description]; | ||
var example = examples.get(description); | ||
if ( !example ) { | ||
throw new Error('No example exists with id "' + description + '".'); | ||
throw new Error(createDocMessage('No example exists with id "' + description + '".', doc)); | ||
} | ||
if ( !example.runnableExampleDoc ) { | ||
throw new Error('Example "' + description + '" does not have an associated runnableExampleDoc. Are you missing a processor (examples-generate)?"'); | ||
throw new Error(createDocMessage('Example "' + description + '" does not have an associated runnableExampleDoc. Are you missing a processor (examples-generate)?"', doc)); | ||
} | ||
return example.runnableExampleDoc.renderedContent; | ||
}; | ||
} | ||
} | ||
}; | ||
}; |
var _ = require('lodash'); | ||
var log = require('winston'); | ||
var path = require('canonical-path'); | ||
var templateFolder, deployments; | ||
/** | ||
* @dgProcessor generateExamplesProcessor | ||
* @description | ||
* Create doc objects of the various things that need to be rendered for an example | ||
* This includes the files that will be run in an iframe, the code that will be injected | ||
* into the HTML pages and the protractor test files. | ||
*/ | ||
module.exports = function generateExamplesProcessor(log, examples) { | ||
function outputPath(example, fileName) { | ||
return path.join(example.outputFolder, fileName); | ||
} | ||
return { | ||
$runAfter: ['adding-extra-docs'], | ||
$runBefore: ['extra-docs-added'], | ||
$validate: { | ||
deployments: { presence: true }, | ||
templateFolder: { presence: true} | ||
}, | ||
$process: function(docs) { | ||
examples.forEach(function(example) { | ||
function createExampleDoc(example, deployment, stylesheets, scripts) { | ||
var deploymentQualifier = deployment.name === 'default' ? '' : ('-' + deployment.name); | ||
var commonFiles = (deployment.examples && deployment.examples.commonFiles) || {}; | ||
var dependencyPath = deployment.examples.dependencyPath || '.'; | ||
var stylesheets = []; | ||
var scripts = []; | ||
var exampleDoc = { | ||
id: example.id + deploymentQualifier, | ||
docType: 'example', | ||
template: path.join(templateFolder, 'index.template.html'), | ||
file: example.doc.file, | ||
startingLine: example.doc.startingLine, | ||
example: example, | ||
path: example.id + deploymentQualifier, | ||
outputPath: example.outputFolder + '/index' + deploymentQualifier + '.html' | ||
}; | ||
// The index file is special, see createExampleDoc() | ||
example.indexFile = example.files.get('index.html'); | ||
example.files.delete('index.html'); | ||
// Copy in the common scripts and stylesheets | ||
exampleDoc.scripts = _.map(commonFiles.scripts, function(script) { return { path: script }; }); | ||
exampleDoc.stylesheets = _.map(commonFiles.stylesheets || [], function(stylesheet) { return { path: stylesheet }; }); | ||
// Create a new document for each file of the example | ||
example.files.forEach(function(file) { | ||
// Copy in any dependencies for this example | ||
if ( example.deps ) { | ||
_.forEach(example.deps.split(';'), function(dependency) { | ||
var filePath = /(https?:)?\/\//.test(dependencyPath) ? | ||
dependencyPath + dependency : | ||
path.join(dependencyPath, dependency); | ||
exampleDoc.scripts.push({ path: filePath }); | ||
}); | ||
} | ||
var fileDoc = this.createFileDoc(example, file); | ||
docs.push(fileDoc); | ||
// Attach the specific scripts and stylesheets for this example | ||
exampleDoc.stylesheets = exampleDoc.stylesheets.concat(stylesheets); | ||
exampleDoc.scripts = exampleDoc.scripts.concat(scripts); | ||
// Store a reference to the fileDoc for attaching to the exampleDocs | ||
if ( file.type == 'css' ) { | ||
stylesheets.push(fileDoc); | ||
} else if ( file.type == 'js' ) { | ||
scripts.push(fileDoc); | ||
} | ||
}, this); | ||
// If there is content specified for the index.html file then use its contents for this doc | ||
if ( example.indexFile ) { | ||
exampleDoc.fileContents = example.indexFile.fileContents; | ||
} | ||
// Create an index.html document for the example (one for each deployment type) | ||
_.forEach(this.deployments, function(deployment) { | ||
var exampleDoc = this.createExampleDoc(example, deployment, stylesheets, scripts); | ||
docs.push(exampleDoc); | ||
}, this); | ||
return exampleDoc; | ||
} | ||
// Create the doc that will be injected into the website as a runnable example | ||
var runnableExampleDoc = this.createRunnableExampleDoc(example); | ||
docs.push(runnableExampleDoc); | ||
example.runnableExampleDoc = runnableExampleDoc; | ||
function createFileDoc(example, file) { | ||
var fileDoc = { | ||
docType: 'example-' + file.type, | ||
id: example.id + '/' + file.name, | ||
template: path.join(templateFolder, 'template.' + file.type), | ||
file: example.doc.file, | ||
startingLine: example.doc.startingLine, | ||
example: example, | ||
path: file.name, | ||
outputPath: outputPath(example, file.name), | ||
fileContents: file.fileContents | ||
}; | ||
return fileDoc; | ||
} | ||
// Create the manifest that will be sent to Plunker | ||
docs.push(this.createManifestDoc(example)); | ||
function createRunnableExampleDoc(example) { | ||
var exampleDoc = { | ||
id: example.id + '-runnableExample', | ||
docType: 'runnableExample', | ||
file: example.doc.file, | ||
startingLine: example.doc.startingLine, | ||
example: example | ||
}; | ||
return exampleDoc; | ||
} | ||
}, this); | ||
}, | ||
function createManifestDoc(example) { | ||
outputPath: function(example, fileName) { | ||
return path.join(example.outputFolder, fileName); | ||
}, | ||
var files = _(example.files) | ||
.omit('index.html') | ||
.map(function(file) { | ||
return file.name; | ||
}) | ||
.value(); | ||
createExampleDoc: function(example, deployment, stylesheets, scripts) { | ||
var deploymentQualifier = deployment.name === 'default' ? '' : ('-' + deployment.name); | ||
var commonFiles = (deployment.examples && deployment.examples.commonFiles) || {}; | ||
var dependencyPath = (deployment.examples && deployment.examples.dependencyPath) || '.'; | ||
var manifestDoc = { | ||
id: example.id + '-manifest', | ||
docType: 'example-manifest', | ||
template: path.join(templateFolder, 'manifest.template.json'), | ||
file: example.doc.file, | ||
startingLine: example.doc.startingLine, | ||
example: example, | ||
files: files, | ||
outputPath: outputPath(example, 'manifest.json') | ||
}; | ||
return manifestDoc; | ||
} | ||
var exampleDoc = { | ||
id: example.id + deploymentQualifier, | ||
docType: 'example', | ||
template: path.join(this.templateFolder, 'index.template.html'), | ||
fileInfo: example.doc.fileInfo, | ||
startingLine: example.doc.startingLine, | ||
endingLine: example.doc.endingLine, | ||
example: example, | ||
path: example.id + deploymentQualifier, | ||
outputPath: example.outputFolder + '/index' + deploymentQualifier + '.html' | ||
}; | ||
module.exports = { | ||
name: 'examples-generate', | ||
description: 'Create doc objects of the various things that need to be rendered for an example.\n' + | ||
'This includes the files that will be run in an iframe, the code that will be injected' + | ||
'into the HTML pages and the protractor test files', | ||
runAfter: ['adding-extra-docs'], | ||
runBefore: ['extra-docs-added'], | ||
process: function(docs, examples, config) { | ||
// Copy in the common scripts and stylesheets | ||
exampleDoc.scripts = _.map(commonFiles.scripts, function(script) { return { path: script }; }); | ||
exampleDoc.stylesheets = _.map(commonFiles.stylesheets || [], function(stylesheet) { return { path: stylesheet }; }); | ||
deployments = config.get('deployment.environments'); | ||
if ( !deployments ) { | ||
throw new Error('No deployment environments found in the config.'); | ||
} | ||
// Copy in any dependencies for this example | ||
if ( example.deps ) { | ||
_.forEach(example.deps.split(';'), function(dependency) { | ||
var filePath = /(https?:)?\/\//.test(dependencyPath) ? | ||
dependencyPath + dependency : | ||
path.join(dependencyPath, dependency); | ||
exampleDoc.scripts.push({ path: filePath }); | ||
}); | ||
} | ||
templateFolder = config.get('processing.examples.templateFolder', 'examples'); | ||
// Attach the specific scripts and stylesheets for this example | ||
exampleDoc.stylesheets = exampleDoc.stylesheets.concat(stylesheets); | ||
exampleDoc.scripts = exampleDoc.scripts.concat(scripts); | ||
_.forOwn(examples, function(example) { | ||
// If there is content specified for the index.html file then use its contents for this doc | ||
if ( example.indexFile ) { | ||
exampleDoc.fileContents = example.indexFile.fileContents; | ||
} | ||
var stylesheets = []; | ||
var scripts = []; | ||
return exampleDoc; | ||
}, | ||
// The index file is special, see createExampleDoc() | ||
example.indexFile = example.files['index.html']; | ||
createFileDoc: function(example, file) { | ||
var fileDoc = { | ||
docType: 'example-' + file.type, | ||
id: example.id + '/' + file.name, | ||
template: path.join(this.templateFolder, 'template.' + file.type), | ||
fileInfo: example.doc.fileInfo, | ||
startingLine: example.doc.startingLine, | ||
endingLine: example.doc.endingLine, | ||
example: example, | ||
path: file.name, | ||
outputPath: this.outputPath(example, file.name), | ||
fileContents: file.fileContents | ||
}; | ||
return fileDoc; | ||
}, | ||
// Create a new document for each file of the example | ||
_(example.files) | ||
// We don't want to create a file for index.html, see createExampleDoc() | ||
.omit('index.html') | ||
.forEach(function(file) { | ||
createRunnableExampleDoc: function(example) { | ||
var exampleDoc = { | ||
id: example.id + '-runnableExample', | ||
docType: 'runnableExample', | ||
fileInfo: example.doc.fileInfo, | ||
startingLine: example.doc.startingLine, | ||
endingLine: example.doc.endingLine, | ||
example: example | ||
}; | ||
return exampleDoc; | ||
}, | ||
var fileDoc = createFileDoc(example, file); | ||
docs.push(fileDoc); | ||
createManifestDoc: function(example) { | ||
// Store a reference to the fileDoc for attaching to the exampleDocs | ||
if ( file.type == 'css' ) { | ||
stylesheets.push(fileDoc); | ||
} else if ( file.type == 'js' ) { | ||
scripts.push(fileDoc); | ||
} | ||
}); | ||
var files = example.files.keys(); | ||
// Create an index.html document for the example (one for each deployment type) | ||
_.forEach(deployments, function(deployment) { | ||
var exampleDoc = createExampleDoc(example, deployment, stylesheets, scripts); | ||
docs.push(exampleDoc); | ||
}); | ||
// Create the doc that will be injected into the website as a runnable example | ||
var runnableExampleDoc = createRunnableExampleDoc(example); | ||
docs.push(runnableExampleDoc); | ||
example.runnableExampleDoc = runnableExampleDoc; | ||
// Create the manifest that will be sent to Plunker | ||
docs.push(createManifestDoc(example)); | ||
}); | ||
} | ||
}; | ||
var manifestDoc = { | ||
id: example.id + '-manifest', | ||
docType: 'example-manifest', | ||
template: path.join(this.templateFolder, 'manifest.template.json'), | ||
fileInfo: example.doc.fileInfo, | ||
startingLine: example.doc.startingLine, | ||
endingLine: example.doc.endingLine, | ||
example: example, | ||
files: files, | ||
outputPath: this.outputPath(example, 'manifest.json') | ||
}; | ||
return manifestDoc; | ||
} | ||
}; | ||
}; |
@@ -0,6 +1,4 @@ | ||
require('es6-shim'); | ||
var _ = require('lodash'); | ||
var log = require('winston'); | ||
var path = require('canonical-path'); | ||
var trimIndentation = require('../../utils/trim-indentation'); | ||
var marked = require('marked'); | ||
@@ -12,72 +10,82 @@ var EXAMPLE_REGEX = /<example([^>]*)>([\S\s]+?)<\/example>/g; | ||
function extractAttributes(attributeText) { | ||
var attributes = Object.create(null); | ||
attributeText.replace(ATTRIBUTE_REGEX, function(match, prop, val1, val2){ | ||
attributes[prop] = val1 || val2; | ||
}); | ||
return attributes; | ||
} | ||
/** | ||
* @dgProcessor parseExamplesProcessor | ||
* @description | ||
* Search the documentation for examples that need to be extracted | ||
*/ | ||
module.exports = function parseExamplesProcessor(log, examples, trimIndentation, createDocMessage) { | ||
return { | ||
outputFolder: undefined, | ||
$runAfter: ['files-read'], | ||
$runBefore: ['parsing-tags'], | ||
$process: function(docs) { | ||
function extractFiles(exampleText) { | ||
var files = Object.create(null); | ||
exampleText.replace(FILE_REGEX, function(match, attributesText, contents) { | ||
var file = extractAttributes(attributesText); | ||
if ( !file.name ) { | ||
throw new Error('Missing name attribute in file: ' + match); | ||
} | ||
var outputFolder = this.outputFolder || 'examples'; | ||
// Extract the contents of the file | ||
file.fileContents = trimIndentation(contents); | ||
file.language = path.extname(file.name).substr(1); | ||
file.type = file.type || file.language || 'file'; | ||
file.attributes = _.omit(file, ['fileContents']); | ||
docs.forEach(function(doc) { | ||
try { | ||
doc.content = doc.content.replace(EXAMPLE_REGEX, function processExample(match, attributeText, exampleText) { | ||
// Store this file information | ||
files[file.name] = file; | ||
}); | ||
return files; | ||
} | ||
var example = extractAttributes(attributeText); | ||
var id = uniqueName(examples, 'example-' + (example.name || 'example')); | ||
_.assign(example, { | ||
attributes: _.omit(example, ['files', 'doc']), | ||
files: extractFiles(exampleText), | ||
id: id, | ||
doc: doc, | ||
outputFolder: path.join(outputFolder, id) | ||
}); | ||
var uniqueName = function(container, name) { | ||
if ( container[name] ) { | ||
var index = 1; | ||
while(container[name + index]) { | ||
index += 1; | ||
// store the example information for later | ||
log.debug('Storing example', id); | ||
examples.set(id, example); | ||
return '{@runnableExample ' + id + '}'; | ||
}); | ||
} catch(error) { | ||
throw new Error(createDocMessage('Failed to parse examples', doc, error)); | ||
} | ||
}); | ||
} | ||
name = name + index; | ||
}; | ||
function extractAttributes(attributeText) { | ||
var attributes = Object.create(null); | ||
attributeText.replace(ATTRIBUTE_REGEX, function(match, prop, val1, val2){ | ||
attributes[prop] = val1 || val2; | ||
}); | ||
return attributes; | ||
} | ||
return name; | ||
}; | ||
module.exports = { | ||
name: 'examples-parse', | ||
description: 'Search the documentation for examples that need to be extracted', | ||
runAfter: ['files-read'], | ||
runBefore: ['parsing-tags'], | ||
exports: { | ||
examples: ['value', Object.create(null) ] | ||
}, | ||
process: function(docs, examples, config) { | ||
function extractFiles(exampleText) { | ||
var files = new Map(); | ||
exampleText.replace(FILE_REGEX, function(match, attributesText, contents) { | ||
var file = extractAttributes(attributesText); | ||
if ( !file.name ) { | ||
throw new Error('Missing name attribute in file: ' + match); | ||
} | ||
var outputFolder = config.get('processing.examples.outputFolder', 'examples'); | ||
// Extract the contents of the file | ||
file.fileContents = trimIndentation(contents); | ||
file.language = path.extname(file.name).substr(1); | ||
file.type = file.type || file.language || 'file'; | ||
file.attributes = _.omit(file, ['fileContents']); | ||
_.forEach(docs, function(doc) { | ||
doc.content = doc.content.replace(EXAMPLE_REGEX, function processExample(match, attributeText, exampleText) { | ||
var example = extractAttributes(attributeText); | ||
example.attributes = _.omit(example, ['files', 'doc']); | ||
var id = uniqueName(examples, 'example-' + (example.name || 'example')); | ||
example.files = extractFiles(exampleText); | ||
example.id = id; | ||
example.doc = doc; | ||
example.outputFolder = path.join(outputFolder, example.id); | ||
// store the example information for later | ||
log.debug('Storing example', id); | ||
examples[id] = example; | ||
return '{@runnableExample ' + id + '}'; | ||
}); | ||
// Store this file information | ||
files.set(file.name, file); | ||
}); | ||
return files; | ||
} | ||
function uniqueName(container, name) { | ||
if ( container.has(name) ) { | ||
var index = 1; | ||
while(container.has(name + index)) { | ||
index += 1; | ||
} | ||
name = name + index; | ||
} | ||
return name; | ||
} | ||
}; |
@@ -1,5 +0,8 @@ | ||
var package = require('../index'); | ||
var examplesPackage = require('../index'); | ||
var Package = require('dgeni').Package; | ||
describe('examples package', function() { | ||
}); | ||
it("should be instance of Package", function() { | ||
expect(examplesPackage instanceof Package).toBeTruthy(); | ||
}); | ||
}); |
@@ -1,57 +0,44 @@ | ||
var plugin = require('../../processors/examples-generate'); | ||
var Config = require('dgeni').Config; | ||
require('es6-shim'); | ||
var generateExamplesProcessorFactory = require('../../processors/examples-generate'); | ||
var mockLog = require('dgeni/lib/mocks/log'); | ||
var _ = require('lodash'); | ||
describe("examples-generate processor", function() { | ||
var docs, examples, config; | ||
var templateFolder, deployments, docs, examples; | ||
beforeEach(function() { | ||
config = new Config({ | ||
processing: { | ||
examples: { | ||
templateFolder: 'examples' | ||
} | ||
}, | ||
deployment: { | ||
environments: [ | ||
{ | ||
name: 'default', | ||
examples: { | ||
commonFiles: [], | ||
dependencyPath: '.' | ||
}, | ||
}, | ||
{ | ||
name: 'other', | ||
examples: { | ||
commonFiles: { | ||
scripts: [ 'someFile.js', 'someOtherFile.js' ], | ||
}, | ||
dependencyPath: '..' | ||
} | ||
} | ||
] | ||
} | ||
docs = [{ file: 'a.b.js' }]; | ||
examples = new Map(); | ||
files = new Map(); | ||
files.set('index.html', { type: 'html', name: 'index.html', fileContents: 'index.html content' }); | ||
files.set('app.js', { type: 'js', name: 'app.js', fileContents: 'app.js content' }); | ||
files.set('app.css', { type: 'css', name: 'app.css', fileContents: 'app.css content' }); | ||
files.set('app.spec.js', { type: 'spec', name: 'app.spec.js', fileContents: 'app.spec.js content' }); | ||
examples.set('a.b.c', { | ||
id: 'a.b.c', | ||
doc: docs[0], | ||
outputFolder: 'examples', | ||
deps: 'dep1.js;dep2.js', | ||
files: files | ||
}); | ||
docs = [ | ||
{ file: 'a.b.js' } | ||
]; | ||
examples = { 'a.b.c': | ||
processor = generateExamplesProcessorFactory(mockLog, examples); | ||
processor.templateFolder = 'examples'; | ||
processor.deployments = [ | ||
{ | ||
id: 'a.b.c', | ||
doc: docs[0], | ||
outputFolder: 'examples', | ||
deps: 'dep1.js;dep2.js', | ||
files: { | ||
'index.html': { type: 'html', name: 'index.html', fileContents: 'index.html content' }, | ||
'app.js': { type: 'js', name: 'app.js', fileContents: 'app.js content' }, | ||
'app.css': { type: 'css', name: 'app.css', fileContents: 'app.css content' }, | ||
'app.spec.js': { type: 'spec', name: 'app.spec.js', fileContents: 'app.spec.js content' } | ||
} | ||
name: 'default', | ||
examples: { commonFiles: [], dependencyPath: '.' }, | ||
}, | ||
{ | ||
name: 'other', | ||
examples: { commonFiles: { scripts: [ 'someFile.js', 'someOtherFile.js' ], }, dependencyPath: '..' } | ||
} | ||
}; | ||
]; | ||
plugin.process(docs, examples, config); | ||
processor.$process(docs); | ||
@@ -58,0 +45,0 @@ }); |
@@ -1,22 +0,18 @@ | ||
var rewire = require('rewire'); | ||
var plugin = rewire('../../processors/examples-parse'); | ||
var Config = require('dgeni').Config; | ||
var log = require('winston'); | ||
require('es6-shim'); | ||
var parseExamplesProcessorFactory = require('../../processors/examples-parse'); | ||
var mockLog = require('dgeni/lib/mocks/log')(false); | ||
var createDocMessageFactory = require('../../../base/services/createDocMessage'); | ||
var _ = require('lodash'); | ||
describe("examples-parse doc processor", function() { | ||
var config; | ||
var processor, examples, mockTrimIndentation; | ||
beforeEach(function() { | ||
config = new Config(); | ||
log.level = 'error'; | ||
examples = new Map(); | ||
mockTrimIndentation = jasmine.createSpy('trimIndentation').and.callFake(function(value) { return value; }); | ||
processor = parseExamplesProcessorFactory(mockLog, examples, mockTrimIndentation, createDocMessageFactory()); | ||
}); | ||
it("should be called examples", function() { | ||
expect(plugin.name).toEqual('examples-parse'); | ||
}); | ||
it("should extract example tags from the doc content", function() { | ||
var examples = {}; | ||
var docs = [ | ||
@@ -34,12 +30,12 @@ { | ||
]; | ||
plugin.process(docs, examples, config); | ||
expect(examples['example-bar']).toEqual(jasmine.objectContaining({ name:'bar', moo1:'nar1', id: 'example-bar'})); | ||
expect(examples['example-bar1']).toEqual(jasmine.objectContaining({ name:'bar', moo2:'nar2', id: 'example-bar1'})); | ||
expect(examples['example-value']).toEqual(jasmine.objectContaining({ name:'value', id: 'example-value'})); | ||
expect(examples['example-with-files']).toEqual(jasmine.objectContaining({ name: 'with-files', id: 'example-with-files'})); | ||
processor.$process(docs); | ||
expect(examples.get('example-bar')).toEqual(jasmine.objectContaining({ name:'bar', moo1:'nar1', id: 'example-bar'})); | ||
expect(examples.get('example-bar1')).toEqual(jasmine.objectContaining({ name:'bar', moo2:'nar2', id: 'example-bar1'})); | ||
expect(examples.get('example-value')).toEqual(jasmine.objectContaining({ name:'value', id: 'example-value'})); | ||
expect(examples.get('example-with-files')).toEqual(jasmine.objectContaining({ name: 'with-files', id: 'example-with-files'})); | ||
// Jasmine doesn't like that the files property hasn't got a hasOwnProperty method because it was created using Object.create(null); | ||
// So we map it into something else | ||
var files = _.map(examples['example-with-files'].files, function(file) { return _.clone(file); }); | ||
expect(files).toEqual([ | ||
// var files = _.map(examples.get('example-with-files').files, function(file) { return _.clone(file); }); | ||
expect(Array.from(examples.get('example-with-files').files)).toEqual([ | ||
jasmine.objectContaining({ name: 'app.js', type: 'js', fileContents: 'aaa', language: 'js' }), | ||
@@ -52,3 +48,2 @@ jasmine.objectContaining({ name: 'app.spec.js', type: 'spec', fileContents: 'bbb', language: 'js' }) | ||
it("should compute unique ids for each example", function() { | ||
var examples = []; | ||
var docs = [{ | ||
@@ -58,5 +53,5 @@ content: '<example name="bar">some example content 1</example>\n' + | ||
}]; | ||
plugin.process(docs, examples, config); | ||
expect(examples['example-bar'].id).toEqual('example-bar'); | ||
expect(examples['example-bar1'].id).toEqual('example-bar1'); | ||
processor.$process(docs); | ||
expect(examples.get('example-bar').id).toEqual('example-bar'); | ||
expect(examples.get('example-bar1').id).toEqual('example-bar1'); | ||
}); | ||
@@ -69,3 +64,3 @@ | ||
plugin.process([doc], [], config); | ||
processor.$process([doc]); | ||
@@ -72,0 +67,0 @@ expect(doc.content).toEqual('Some content before {@runnableExample example-bar} and some after'); |
@@ -6,48 +6,65 @@ var _ = require('lodash'); | ||
module.exports = { | ||
pattern: /\.js$/, | ||
processFile: function(filePath, contents, basePath) { | ||
/** | ||
* @dgService jsdocFileReader | ||
* @description | ||
* This file reader will pull a doc for each jsdoc style comment in the source file | ||
* (by default .js) | ||
* | ||
* The doc will initially have the form: | ||
* ``` | ||
* { | ||
* content: 'the content of the comment', | ||
* startingLine: xxx, | ||
* endingLine: xxx, | ||
* codeNode: someASTNode | ||
* codeAncestors: arrayOfASTNodes | ||
* } | ||
* ``` | ||
*/ | ||
module.exports = function jsdocFileReader() { | ||
return { | ||
name: 'jsdocFileReader', | ||
defaultPattern: /\.js$/, | ||
getDocs: function(fileInfo) { | ||
var ast = jsParser.parse(contents, { | ||
loc: true, | ||
range: true, | ||
comment: true | ||
}); | ||
var ast = jsParser.parse(fileInfo.content, { | ||
loc: true, | ||
range: true, | ||
comment: true | ||
}); | ||
return _(ast.comments) | ||
return _(ast.comments) | ||
.filter(function(comment) { | ||
// To test for a jsdoc comment (i.e. starting with /** ), we need to check for a leading | ||
// star since the parser strips off the first "/*" | ||
return comment.type === 'Block' && comment.value.charAt(0) === '*'; | ||
}) | ||
.filter(function(comment) { | ||
// To test for a jsdoc comment (i.e. starting with /** ), we need to check for a leading | ||
// star since the parser strips off the first "/*" | ||
return comment.type === 'Block' && comment.value.charAt(0) === '*'; | ||
}) | ||
.map(function(comment) { | ||
.map(function(comment) { | ||
// Strip off any leading stars | ||
text = comment.value.replace(LEADING_STAR, ''); | ||
// Strip off any leading stars | ||
text = comment.value.replace(LEADING_STAR, ''); | ||
// Trim off leading and trailing whitespace | ||
text = text.trim(); | ||
// Trim off leading and trailing whitespace | ||
text = text.trim(); | ||
// Extract the information about the code directly after this comment | ||
var codeNode = walk.findNodeAfter(ast, comment.range[1]); | ||
var codeAncestors = codeNode && walk.ancestor(ast, codeNode.node); | ||
// Extract the information about the code directly after this comment | ||
var codeNode = walk.findNodeAfter(ast, comment.range[1]); | ||
var codeAncestors = codeNode && walk.ancestor(ast, codeNode.node); | ||
// Create a doc from this comment | ||
return { | ||
fileType: 'js', | ||
startingLine: comment.loc.start.line, | ||
endingLine: comment.loc.end.line, | ||
file: filePath, | ||
basePath: basePath, | ||
content: text, | ||
codeNode: codeNode, | ||
codeAncestors: codeAncestors | ||
}; | ||
// Create a doc from this comment | ||
return { | ||
startingLine: comment.loc.start.line, | ||
endingLine: comment.loc.end.line, | ||
content: text, | ||
codeNode: codeNode, | ||
codeAncestors: codeAncestors | ||
}; | ||
}) | ||
}) | ||
.value(); | ||
} | ||
}; | ||
.value(); | ||
} | ||
}; | ||
}; |
@@ -1,32 +0,39 @@ | ||
module.exports = function(config) { | ||
var Package = require('dgeni').Package; | ||
require('../base')(config); | ||
module.exports = new Package('jsdoc', [require('../base')]) | ||
config.append('source.fileReaders', require('./file-readers/jsdoc')); | ||
// Add in extra pseudo marker processors | ||
.processor({ name: 'parsing-tags', $runAfter: ['files-read'], $runBefore: ['processing-docs'] }) | ||
.processor({ name: 'tags-parsed', $runAfter: ['parsing-tags'], $runBefore: ['processing-docs'] }) | ||
.processor({ name: 'extracting-tags', $runAfter: ['tags-parsed'], $runBefore: ['processing-docs'] }) | ||
.processor({ name: 'tags-extracted', $runAfter: ['extracting-tags'], $runBefore: ['processing-docs'] }) | ||
config.append('processing.processors', [ | ||
{ name: 'parsing-tags', runAfter: ['files-read'], runBefore: ['processing-docs'] }, | ||
{ name: 'tags-parsed', runAfter: ['parsing-tags'], runBefore: ['processing-docs'] }, | ||
{ name: 'extracting-tags', runAfter: ['tags-parsed'], runBefore: ['processing-docs'] }, | ||
{ name: 'tags-extracted', runAfter: ['extracting-tags'], runBefore: ['processing-docs'] } | ||
]); | ||
// Add in the real processors for this package | ||
.processor(require('./processors/code-name')) | ||
.processor(require('./processors/parse-tags')) | ||
.processor(require('./processors/extract-tags')) | ||
.processor(require('./processors/compute-path')) | ||
.processor(require('./processors/inline-tags')) | ||
config.append('processing.processors', [ | ||
require('./processors/code-name'), | ||
require('./processors/tagDefinitions'), | ||
require('./processors/tagParser'), | ||
require('./processors/tagExtractor'), | ||
require('./processors/defaultTagTransforms'), | ||
require('./processors/parse-tags'), | ||
require('./processors/extract-tags'), | ||
require('./processors/compute-path'), | ||
require('./processors/inline-tags') | ||
]); | ||
.factory(require('./services/transforms/extract-name')) | ||
.factory(require('./services/transforms/extract-type')) | ||
.factory(require('./services/transforms/unknown-tag')) | ||
.factory(require('./services/transforms/whole-tag')) | ||
.factory(require('./services/transforms/trim-whitespace')) | ||
config.append('processing.tagDefinitions', require('./tag-defs')); | ||
.factory(require('./file-readers/jsdoc')) | ||
config.append('processing.defaultTagTransforms', require('./tag-defs/transforms/trim-whitespace')); | ||
// Configure the processors | ||
return config; | ||
}; | ||
.config(function(readFilesProcessor, jsdocFileReader) { | ||
readFilesProcessor.fileReaders = [jsdocFileReader]; | ||
}) | ||
.config(function(parseTagsProcessor, getInjectables) { | ||
parseTagsProcessor.tagDefinitions = getInjectables(require('./tag-defs')); | ||
}) | ||
.config(function(extractTagsProcessor, trimWhitespaceTransform) { | ||
extractTagsProcessor.defaultTagTransforms = [trimWhitespaceTransform]; | ||
}); |
@@ -1,63 +0,64 @@ | ||
var _ = require('lodash'); | ||
var log = require('dgeni').log; | ||
/** | ||
* @dgProcessor code-name | ||
* @dgProcessor codeNameProcessor | ||
* @description Infer the name of the document from name of the following code | ||
*/ | ||
module.exports = { | ||
name: 'code-name', | ||
runAfter: ['files-read'], | ||
runBefore: ['processing-docs'], | ||
process: function(docs) { | ||
_.forEach(docs, function(doc) { | ||
doc.codeName = doc.codeNode && findCodeName(doc.codeNode.node); | ||
}); | ||
return docs; | ||
module.exports = function codeNameProcessor(log) { | ||
return { | ||
$runAfter: ['files-read'], | ||
$runBefore: ['processing-docs'], | ||
$process: function(docs) { | ||
docs.forEach(function(doc) { | ||
doc.codeName = doc.codeNode && findCodeName(doc.codeNode.node); | ||
if ( doc.codeName ) { | ||
log.silly('found codeName: ', doc.codeName); | ||
} | ||
}); | ||
return docs; | ||
} | ||
}; | ||
/** | ||
* Recurse down the code AST node that is associated with this doc for a name | ||
* @param {Object} node The esprima node information for the code to find the name of | ||
* @return {String} The name of the code or null if none found. | ||
*/ | ||
function findCodeName(node) { | ||
var match; | ||
switch(node.type) { | ||
case 'FunctionDeclaration': | ||
return node.id && node.id.name; | ||
case 'ExpressionStatement': | ||
return findCodeName(node.expression); | ||
case 'AssignmentExpression': | ||
return findCodeName(node.right) || findCodeName(node.left); | ||
case 'FunctionExpression': | ||
return node.id && node.id.name; | ||
case 'MemberExpression': | ||
return findCodeName(node.property); | ||
case 'CallExpression': | ||
return findCodeName(node.callee); | ||
case 'Identifier': | ||
return node.name; | ||
case 'ReturnStatement': | ||
return findCodeName(node.argument); | ||
case 'Property': | ||
return findCodeName(node.value) || findCodeName(node.key); | ||
case 'ObjectExpression': | ||
return null; | ||
case 'ArrayExpression': | ||
return null; | ||
case 'Literal': | ||
return node.value; | ||
case 'Program': | ||
return node.body[0] ? findCodeName(node.body[0]) : null; | ||
case 'VariableDeclaration': | ||
return findCodeName(node.declarations[0]); | ||
case 'VariableDeclarator': | ||
return node.id && node.id.name; | ||
default: | ||
log.warn('HELP! Unrecognised node type: ' + node.type); | ||
log.warn(node); | ||
return null; | ||
} | ||
} | ||
}; | ||
/** | ||
* Recurse down the code AST node that is associated with this doc for a name | ||
* @param {Object} node The esprima node information for the code to find the name of | ||
* @return {String} The name of the code or null if none found. | ||
*/ | ||
function findCodeName(node) { | ||
var match; | ||
switch(node.type) { | ||
case 'FunctionDeclaration': | ||
return node.id && node.id.name; | ||
case 'ExpressionStatement': | ||
return findCodeName(node.expression); | ||
case 'AssignmentExpression': | ||
return findCodeName(node.right) || findCodeName(node.left); | ||
case 'FunctionExpression': | ||
return node.id && node.id.name; | ||
case 'MemberExpression': | ||
return findCodeName(node.property); | ||
case 'CallExpression': | ||
return findCodeName(node.callee); | ||
case 'Identifier': | ||
return node.name; | ||
case 'ReturnStatement': | ||
return findCodeName(node.argument); | ||
case 'Property': | ||
return findCodeName(node.value) || findCodeName(node.key); | ||
case 'ObjectExpression': | ||
return null; | ||
case 'ArrayExpression': | ||
return null; | ||
case 'Literal': | ||
return node.value; | ||
case 'Program': | ||
return node.body[0] ? findCodeName(node.body[0]) : null; | ||
case 'VariableDeclaration': | ||
return findCodeName(node.declarations[0]); | ||
case 'VariableDeclarator': | ||
return node.id && node.id.name; | ||
default: | ||
log.warn('HELP! Unrecognised node type: ' + node.type); | ||
log.warn(node); | ||
return null; | ||
} | ||
} |
@@ -1,33 +0,32 @@ | ||
var _ = require('lodash'); | ||
var log = require('winston'); | ||
var path = require('canonical-path'); | ||
/** | ||
* @dgProcessor computePath | ||
* @description Compute the path and outputPath for docs that do not already have them | ||
*/ | ||
module.exports = function computePathProcessor(log) { | ||
return { | ||
$runAfter: ['docs-processed'], | ||
$runBefore: ['rendering-docs'], | ||
$process: function(docs) { | ||
module.exports = { | ||
name: 'compute-path', | ||
description: 'Compute the path and outputPath for docs that do not already have them', | ||
runAfter: ['docs-processed'], | ||
runBefore: ['rendering-docs'], | ||
process: function(docs, config) { | ||
docs.forEach(function(doc) { | ||
var contentsFolder = config.get('rendering.contentsFolder'); | ||
if ( !contentsFolder ) { | ||
throw new Error('Invalid configuration. You must provide config.rendering.contentsFolder'); | ||
} | ||
doc.path = doc.path || doc.name || doc.codeName; | ||
_.forEach(docs, function(doc) { | ||
doc.path = doc.path || doc.name || doc.codeName; | ||
if ( !doc.path ) { | ||
doc.path = path.join(path.dirname(doc.fileInfo.file)); | ||
if ( doc.fileInfo.baseName !== 'index' ) { | ||
doc.path = path.join(doc.path,doc.fileInfo.baseName); | ||
} | ||
} | ||
if ( !doc.path ) { | ||
doc.path = path.join(path.dirname(doc.file)); | ||
if ( doc.fileName !== 'index' ) { | ||
doc.path += '/' + doc.fileName; | ||
if ( !doc.outputPath ) { | ||
doc.outputPath = path.join(doc.path + '.html'); | ||
} | ||
} | ||
if ( !doc.outputPath ) { | ||
doc.outputPath = contentsFolder + '/' + doc.path + '.html'; | ||
} | ||
}); | ||
} | ||
}; | ||
log.silly('computed path:', '"' + doc.path + '"', 'and outputPath:', '"' + doc.outputPath + '"'); | ||
}); | ||
} | ||
}; | ||
}; |
@@ -1,2 +0,1 @@ | ||
var log = require('winston'); | ||
var _ = require('lodash'); | ||
@@ -9,15 +8,192 @@ | ||
*/ | ||
var plugin = module.exports = { | ||
name: 'extract-tags', | ||
runAfter: ['extracting-tags'], | ||
runBefore: ['tags-extracted'], | ||
process: function(docs, tagExtractor) { | ||
_.forEach(docs, function(doc) { | ||
log.debug('extracting tags from "' + doc.file + '"" at line #' + doc.startingLine); | ||
tagExtractor(doc); | ||
module.exports = function extractTagsProcessor(log, parseTagsProcessor, createDocMessage) { | ||
return { | ||
defaultTagTransforms: [], | ||
$validate: { | ||
defaultTagTransforms: { presence: true } | ||
}, | ||
$runAfter: ['extracting-tags'], | ||
$runBefore: ['tags-extracted'], | ||
$process: function(docs) { | ||
var tagExtractor = createTagExtractor(parseTagsProcessor.tagDefinitions, this.defaultTagTransforms); | ||
docs.forEach(function(doc) { | ||
log.debug(createDocMessage('extracting tags', doc)); | ||
tagExtractor(doc); | ||
}); | ||
} | ||
}; | ||
/** | ||
* Create a function that will extract information, to properties on the tag or doc, from the tags | ||
* that were parsed from the doc. | ||
* | ||
* @param {Array} tagDefinitions | ||
* A collection of tagDefinitions to extract from the parsed tags. | ||
* @param {function(doc, tag, value)|Array.<function(doc, tag, value)>} [defaultTagTransforms] | ||
* A single transformation function (or collection of transformation functions) to apply | ||
* to every tag that is extracted. | ||
*/ | ||
function createTagExtractor(tagDefinitions, defaultTagTransforms, createDocMessage) { | ||
// Compute a default transformation function | ||
var defaultTransformFn = getTransformationFn(defaultTagTransforms); | ||
// Add some useful methods to the tagDefs | ||
var tagDefs = _.map(tagDefinitions, function(tagDef) { | ||
// Make a copy of the tagDef as we are going to modify it | ||
tagDef = _.clone(tagDef); | ||
// Compute this tagDefs specific transformation function | ||
var transformFn = getTransformationFn(tagDef.transforms); | ||
// Attach a transformation function to the cloned tagDef | ||
// running the specific transforms followed by the default transforms | ||
var tagProperty = tagDef.tagProperty || 'description'; | ||
tagDef.getProperty = function(doc, tag) { | ||
var value = tag[tagProperty]; | ||
value = transformFn(doc, tag, value); | ||
value = defaultTransformFn(doc, tag, value); | ||
return value; | ||
}; | ||
return tagDef; | ||
}); | ||
return function tagExtractor(doc) { | ||
// Try to extract each of the tags defined in the tagDefs collection | ||
_.forEach(tagDefs, function(tagDef) { | ||
log.silly('extracting tags for: ' + tagDef.name); | ||
var docProperty = tagDef.docProperty || tagDef.name; | ||
log.silly(' - to be attached to doc.' + docProperty); | ||
// Collect the tags for this tag def | ||
var tags = doc.tags.getTags(tagDef.name); | ||
// No tags found for this tag def | ||
if ( tags.length === 0 ) { | ||
// This tag is required so throw an error | ||
if ( tagDef.required ) { | ||
throw new Error(createDocMessage('Missing tag "' + tagDef.name, doc)); | ||
} | ||
applyDefault(doc, docProperty, tagDef); | ||
} else { | ||
readTags(doc, docProperty, tagDef, tags); | ||
} | ||
}); | ||
if ( doc.tags.badTags.length > 0 ) { | ||
log.warn(formatBadTagErrorMessage(doc)); | ||
} | ||
}; | ||
} | ||
}; | ||
function applyDefault(doc, docProperty, tagDef) { | ||
log.silly(' - tag not found'); | ||
// Apply the default function if there is one | ||
if ( tagDef.defaultFn ) { | ||
log.silly(' - applying default value function'); | ||
var defaultValue = tagDef.defaultFn(doc); | ||
log.silly(' - default value: ', defaultValue); | ||
if ( defaultValue !== undefined ) { | ||
// If the defaultFn returns a value then use this as the document property | ||
if ( tagDef.multi ) { | ||
doc[docProperty] = (doc[docProperty] || []).concat(defaultValue); | ||
} else { | ||
doc[docProperty] = defaultValue; | ||
} | ||
} | ||
} | ||
} | ||
function readTags(doc, docProperty, tagDef, tags) { | ||
// Does this tagDef expect multiple instances of the tag? | ||
if ( tagDef.multi ) { | ||
// We may have multiple tags for this tag def, so we put them into an array | ||
doc[docProperty] = doc[docProperty] || []; | ||
_.forEach(tags, function(tag) { | ||
// Transform and add the tag to the array | ||
doc[docProperty].push(tagDef.getProperty(doc, tag)); | ||
}); | ||
} else { | ||
// We only expect one tag for this tag def | ||
if ( tags.length > 1 ) { | ||
throw new Error(createDocMessage('Only one of "' + tagDef.name + '" (or its aliases) allowed. There were ' + tags.length, doc)); | ||
} | ||
// Transform and apply the tag to the document | ||
doc[docProperty] = tagDef.getProperty(doc, tags[0]); | ||
} | ||
log.silly(' - tag extracted: ', doc[docProperty]); | ||
} | ||
/** | ||
* Create a function to transform from the tag to doc property | ||
* @param {function(doc, tag, value)|Array.<function(doc, tag, value)>} transform | ||
* The transformation to apply to the tag | ||
* @return {function(doc, tag, value)} A single function that will do the transformation | ||
*/ | ||
function getTransformationFn(transforms) { | ||
if ( _.isFunction(transforms) ) { | ||
// transform is a single function so just use that | ||
return transforms; | ||
} | ||
if ( _.isArray(transforms) ) { | ||
// transform is an array then we will apply each in turn like a pipe-line | ||
return function(doc, tag, value) { | ||
_.forEach(transforms, function(transform) { | ||
value = transform(doc, tag, value); | ||
}); | ||
return value; | ||
}; | ||
} | ||
if ( !transforms ) { | ||
// No transform is specified so we just provide a default | ||
return function(doc, tag, value) { return value; }; | ||
} | ||
throw new Error('Invalid transformFn in tag definition, ' + tagDef.name + | ||
' - you must provide a function or an array of functions.'); | ||
} | ||
function formatBadTagErrorMessage(doc) { | ||
var id = (doc.id || doc.name); | ||
id = id ? '"' + id + '" ' : ''; | ||
var message = createDocMessage('Invalid tags found', doc) + '\n'; | ||
_.forEach(doc.tags.badTags, function(badTag) { | ||
var description = (_.isString(badTag.description) && (badTag.description.substr(0, 20) + '...')); | ||
if ( badTag.name ) { | ||
description = badTag.name + ' ' + description; | ||
} | ||
if ( badTag.typeExpression ) { | ||
description = '{' + badTag.typeExpression + '} ' + description; | ||
} | ||
message += 'Line: ' + badTag.startingLine + ': @' + badTag.tagName + ' ' + description + '\n'; | ||
_.forEach(badTag.errors, function(error) { | ||
message += ' * ' + error + '\n'; | ||
}); | ||
}); | ||
return message + '\n'; | ||
} | ||
}; |
var _ = require('lodash'); | ||
var log = require('winston'); | ||
require('es6-shim'); | ||
var INLINE_TAG = /\{@([^\s]+)\s+([^\}]*)\}/g; | ||
// We add InlineTagHandler onto the end of the tag name to help prevent naming collisions | ||
// in the injector | ||
function handlerId(name) { | ||
return name + 'InlineTagHandler'; | ||
} | ||
/** | ||
* @dgProcessor inlineTagProcessor | ||
* @description | ||
* Search the docs for inline tags that need to have content injected. | ||
* | ||
* Inline tags are defined by a collection of inline tag definitions. Each definition is an injectable function, | ||
* which should create an object containing, as minimum, a `name` property and a `handler` method, but also, | ||
* optionally, `description` and `aliases` properties. | ||
* | ||
* * The `name` should be the canonical tag name that should be handled. | ||
* * The `aliases` should be an array of additional tag names that should be handled. | ||
* * The `handler` should be a method of the form: `function(doc, tagName, tagDescription, docs) { ... }` | ||
* The | ||
* For example: | ||
* | ||
* ``` | ||
* function(partialNames) { | ||
* return { | ||
* name: 'link', | ||
* handler: function(doc, tagName, tagDescription, docs) { ... }}, | ||
* description: 'Handle inline link tags', | ||
* aliases: ['codeLink'] | ||
* }; | ||
* } | ||
* ``` | ||
*/ | ||
module.exports = function inlineTagProcessor(log, createDocMessage) { | ||
return { | ||
inlineTagDefinitions: [], | ||
$runAfter: ['docs-rendered'], | ||
$runBefore: ['writing-files'], | ||
$process: function(docs) { | ||
module.exports = { | ||
name: 'inline-tags', | ||
description: 'Search the docs for inline tags that need to have content injected', | ||
runAfter: ['docs-rendered'], | ||
runBefore: ['writing-files'], | ||
process: function(docs, config, injector) { | ||
var definitions = this.inlineTagDefinitions; | ||
var definitionMap = getMap(definitions); | ||
// A collection of inline tag definitions. Each should have, as minimum `name` and `handlerFactory` | ||
// properties, but also optionally a definition and aliases tags | ||
// e.g. | ||
// { | ||
// name: 'link', | ||
// handlerFactory: function(docs, partialNames) { return function handler(doc, tagName, tagDescription) { ... }}, | ||
// description: 'Handle inline link tags', | ||
// aliases: ['codeLink'] | ||
// } | ||
var inlineTagDefinitions = config.get('processing.inlineTagDefinitions', []); | ||
var handlerFactories = {}; | ||
_.forEach(inlineTagDefinitions, function(definition, index) { | ||
if ( !definition.name ) { | ||
throw new Error('Invalid configuration: inlineTagDefinition at position ' + index + ' is missing a name property'); | ||
} | ||
if ( !definition.handlerFactory ) { | ||
throw new Error('Invalid configuration: inlineTagDefinition ' + definition.name + ' missing handlerFactory'); | ||
} | ||
// Add the | ||
handlerFactories[handlerId(definition.name)] = ['factory', definition.handlerFactory]; | ||
_.forEach(definition.aliases, function(alias) { | ||
handlerFactories[handlerId(alias)] = ['factory', definition.handlerFactory]; | ||
}); | ||
}); | ||
injector = injector.createChild([handlerFactories]); | ||
// Walk the docs and parse the inline-tags | ||
_.forEach(docs, function(doc) { | ||
_.forEach(docs, function(doc) { | ||
if ( doc.renderedContent ) { | ||
if ( doc.renderedContent ) { | ||
// Replace any inline tags found in the rendered content | ||
doc.renderedContent = doc.renderedContent.replace(INLINE_TAG, function(match, tagName, tagDescription) { | ||
// Replace any inline tags found in the rendered content | ||
doc.renderedContent = doc.renderedContent.replace(INLINE_TAG, function(match, tagName, tagDescription) { | ||
if ( handlerFactories[handlerId(tagName)] ) { | ||
var definition = definitionMap.get(tagName); | ||
if ( definition ) { | ||
// Get the handler for this tag from the injector | ||
var handler = injector.get(handlerId(tagName)); | ||
try { | ||
try { | ||
// It is easier to trim the description here than to fiddle around with the INLINE_TAG regex | ||
tagDescription = tagDescription && tagDescription.trim(); | ||
// It is easier to trim the description here than to fiddle around with the INLINE_TAG regex | ||
tagDescription = tagDescription && tagDescription.trim(); | ||
// Call the handler with the parameters that its factory would not have been able to get from the injector | ||
return definition.handler(doc, tagName, tagDescription, docs); | ||
// Call the handler with the parameters that its factory would not have been able to get | ||
// from the injector | ||
return handler(doc, tagName, tagDescription); | ||
} catch(e) { | ||
throw new Error(createDocMessage('There was a problem running the @' + tagName + ' inline tag handler for ' + match, doc, e)); | ||
} | ||
} catch(e) { | ||
throw new Error('There was a problem running the @' + tagName + | ||
' inline tag handler for ' + match + '\n' + | ||
'Doc: ' + doc.id + '\n' + | ||
'File: ' + doc.file + '\n' + | ||
'Line: ' + doc.startingLine + '\n' + | ||
'Message: \n' + e.message); | ||
} else { | ||
log.warn(createDocMessage('No handler provided for inline tag "' + match + '"', doc)); | ||
return match; | ||
} | ||
} else { | ||
log.warn('No handler provided for inline tag "' + match + '" for "' + doc.id + '" in file "' + doc.file + '" at line ' + doc.startingLine); | ||
return match; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}; | ||
}; | ||
}); | ||
} | ||
}); | ||
} | ||
}; | ||
function getMap(objects) { | ||
var map = new Map(); | ||
objects.forEach(function(object) { | ||
map.set(object.name, object); | ||
if ( object.aliases ) { | ||
object.aliases.forEach(function(alias) { | ||
map.set(alias, object); | ||
}); | ||
} | ||
}); | ||
return map; | ||
} | ||
@@ -1,23 +0,131 @@ | ||
var _ = require('lodash'); | ||
var log = require('winston'); | ||
require('es6-shim'); | ||
var TagCollection = require('../lib/TagCollection'); | ||
var Tag = require('../lib/Tag'); | ||
/** | ||
* @dgProcessor parse-tags | ||
* @description Parse the doc for tags | ||
* @dgProcessor parseTagsProcessor | ||
* @description Parse the doc for jsdoc style tags | ||
*/ | ||
var plugin = module.exports = { | ||
name: 'parse-tags', | ||
runAfter: ['parsing-tags'], | ||
runBefore: ['tags-parsed'], | ||
process: function(docs, tagParser) { | ||
module.exports = function parseTagsProcessor(log, createDocMessage) { | ||
return { | ||
tagDefinitions: [], | ||
$validate: { | ||
tagDefinitions: { presence: true } | ||
}, | ||
$runAfter: ['parsing-tags'], | ||
$runBefore: ['tags-parsed'], | ||
$process: function(docs) { | ||
_.forEach(docs, function(doc) { | ||
try { | ||
doc.tags = tagParser(doc.content, doc.startingLine); | ||
} catch(e) { | ||
log.error('Error parsing tags for doc starting at ' + doc.startingLine + ' in file ' + doc.file); | ||
throw e; | ||
var tagParser = createTagParser(this.tagDefinitions); | ||
docs.forEach(function(doc) { | ||
try { | ||
doc.tags = tagParser(doc.content, doc.startingLine); | ||
} catch(e) { | ||
var message = createDocMessage('Error parsing tags', doc, e); | ||
log.error(message); | ||
throw new Error(message); | ||
} | ||
}); | ||
} | ||
}; | ||
}; | ||
/** | ||
* Create a map of the tagDefinitions keyed on their name or alias | ||
* @param {Array} tagDefinitions A collection of tag definitions to map | ||
* @return {Map} [description] | ||
*/ | ||
function createTagDefMap(tagDefinitions) { | ||
// Create a map of the tagDefinitions so that we can look up tagDefs based on name or alias | ||
var map = new Map(); | ||
tagDefinitions.forEach(function(tagDefinition) { | ||
map.set(tagDefinition.name, tagDefinition); | ||
if ( tagDefinition.aliases ) { | ||
tagDefinition.aliases.forEach(function(alias) { | ||
map.set(alias, tagDefinition); | ||
}); | ||
} | ||
}); | ||
return map; | ||
} | ||
/** | ||
* Create a new tagParser that can parse a set of jsdoc-style tags from a document | ||
* @param {Array} tagDefMap A map of tag definitions keyed on tagName/aliasName. | ||
*/ | ||
function createTagParser(tagDefinitions) { | ||
var END_OF_LINE = /\r?\n/; | ||
var TAG_MARKER = /^\s*@(\S+)\s*(.*)$/; | ||
var CODE_FENCE = /^\s*```/; | ||
var tagDefMap = createTagDefMap(tagDefinitions); | ||
/** | ||
* tagParser | ||
* @param {string} content The text to parse for tags | ||
* @param {number} startingLine The line in the doc file where this text begins | ||
* @return {TagCollection} A collection of tags that were parsed | ||
*/ | ||
return function tagParser(content, startingLine) { | ||
var lines = content.split(END_OF_LINE); | ||
var lineNumber = 0; | ||
var line, match, tagDef; | ||
var descriptionLines = []; | ||
var current; // The current that that is being extracted | ||
var inCode = false; // Are we inside a fenced, back-ticked, code block | ||
var tags = new TagCollection(); // Contains all the tags that have been found | ||
// Extract the description block | ||
do { | ||
line = lines[lineNumber]; | ||
if ( CODE_FENCE.test(line) ) { | ||
inCode = !inCode; | ||
} | ||
}); | ||
} | ||
}; | ||
// We ignore tags if we are in a code block | ||
match = TAG_MARKER.exec(line); | ||
tagDef = match && tagDefMap.get(match[1]); | ||
if ( !inCode && match && ( !tagDef || !tagDef.ignore ) ) { | ||
// Only store tags that are unknown or not ignored | ||
current = new Tag(tagDef, match[1], match[2], startingLine + lineNumber); | ||
break; | ||
} | ||
lineNumber += 1; | ||
descriptionLines.push(line); | ||
} while(lineNumber < lines.length); | ||
tags.description = descriptionLines.join('\n'); | ||
lineNumber += 1; | ||
// Extract the tags | ||
while(lineNumber < lines.length) { | ||
line = lines[lineNumber]; | ||
if ( CODE_FENCE.test(line) ) { | ||
inCode = !inCode; | ||
} | ||
// We ignore tags if we are in a code block | ||
match = TAG_MARKER.exec(line); | ||
tagDef = match && tagDefMap.get(match[1]); | ||
if ( !inCode && match && (!tagDef || !tagDef.ignore) ) { | ||
tags.addTag(current); | ||
current = new Tag(tagDef, match[1], match[2], startingLine + lineNumber); | ||
} else { | ||
current.description = current.description ? (current.description + '\n' + line) : line; | ||
} | ||
lineNumber += 1; | ||
} | ||
if ( current ) { | ||
tags.addTag(current); | ||
} | ||
return tags; | ||
}; | ||
} |
module.exports = [ | ||
{ | ||
file : 'some/file.js', | ||
startingLine : 3, | ||
fileType : 'js', | ||
content: | ||
@@ -17,5 +15,3 @@ "@ngdoc object\n" + | ||
{ | ||
file: 'some/file.js', | ||
startingLine: 18, | ||
fileType : 'js', | ||
content: | ||
@@ -31,5 +27,3 @@ "@ngdoc function\n" + | ||
{ | ||
file: 'some/file.js', | ||
startingLine: 39, | ||
fileType : 'js', | ||
content: | ||
@@ -36,0 +30,0 @@ "@ngdoc function\n" + |
@@ -1,2 +0,3 @@ | ||
var extractor = require('../../file-readers/jsdoc'); | ||
var path = require('canonical-path'); | ||
var fileReaderFactory = require('../../file-readers/jsdoc'); | ||
@@ -6,9 +7,30 @@ var srcJsContent = require('./_test-data/srcJsFile.js'); | ||
describe("js doc extractor", function() { | ||
describe("pattern", function() { | ||
describe("jsdoc fileReader", function() { | ||
var fileReader; | ||
var createFileInfo = function(file, content, basePath) { | ||
return { | ||
fileReader: fileReader.name, | ||
filePath: file, | ||
baseName: path.basename(file, path.extname(file)), | ||
extension: path.extname(file).replace(/^\./, ''), | ||
basePath: basePath, | ||
relativePath: path.relative(basePath, file), | ||
content: content | ||
}; | ||
}; | ||
beforeEach(function() { | ||
fileReader = fileReaderFactory(); | ||
}); | ||
describe("defaultPattern", function() { | ||
it("should only match js files", function() { | ||
expect(extractor.pattern.test('abc.js')).toBeTruthy(); | ||
expect(extractor.pattern.test('abc.ngdoc')).toBeFalsy(); | ||
expect(fileReader.defaultPattern.test('abc.js')).toBeTruthy(); | ||
expect(fileReader.defaultPattern.test('abc.ngdoc')).toBeFalsy(); | ||
}); | ||
}); | ||
@@ -20,3 +42,4 @@ | ||
it('should return a collection of documents extracted from the file', function() { | ||
var docs = extractor.processFile('some/file.js', srcJsContent); | ||
var fileInfo = createFileInfo('some/file.js', srcJsContent, '.'); | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs.length).toEqual(3); | ||
@@ -29,4 +52,5 @@ expect(docs[0]).toEqual(jasmine.objectContaining(docsFromJsContent[0])); | ||
it("should strip off the leading whitespace/stars from each line of the comments", function() { | ||
var docs = extractor.processFile('some/file.js', '/** abc \n * other stuff \n\t\t*last line.\n*/\n'); | ||
it("should strip off the leading whitespace/stars from each line of the comments", function() { | ||
var fileInfo = createFileInfo('some/file.js', '/** abc \n * other stuff \n\t\t*last line.\n*/\n', '.'); | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs[0].content).toEqual('abc \nother stuff \nlast line.'); | ||
@@ -37,3 +61,4 @@ }); | ||
it("should ignore non-jsdoc comments", function() { | ||
var docs = extractor.processFile('some/file.js', '/** Some jsdoc comment */\n// A line comment\n\/* A non-jsdoc block comment*/'); | ||
var fileInfo = createFileInfo('some/file.js', '/** Some jsdoc comment */\n// A line comment\n\/* A non-jsdoc block comment*/', '.'); | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs.length).toEqual(1); | ||
@@ -44,3 +69,4 @@ }); | ||
it("should find the next code item following the comment and attach it to the doc", function() { | ||
var docs = extractor.processFile('some/file.js', srcJsContent); | ||
var fileInfo = createFileInfo('some/file.js', srcJsContent, '.'); | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs.length).toEqual(3); | ||
@@ -52,11 +78,18 @@ expect(docs[0].codeNode.node.type).toEqual('FunctionDeclaration'); | ||
it("should not break if the comment has no code", function() { | ||
extractor.processFile('some/file.js', 'function main() { } /** @some jsdoc comment */'); | ||
var fileInfo = createFileInfo('some/file.js', 'function main() { } /** @some jsdoc comment */', '.'); | ||
expect(function() { | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs.length).toEqual(1); | ||
}).not.toThrow(); | ||
}); | ||
it("should not remove windows new line characters when stripping stars from comments", function() { | ||
var docs = extractor.processFile('some/file.js', '/** Some jsdoc comment\r\n* over multiple\r\n* lines\r\n**/'); | ||
expect(docs[0].content).toEqual('Some jsdoc comment\r\nover multiple\r\nlines'); | ||
var fileInfo = createFileInfo('some/file.js', '/** Some jsdoc comment\r\n* over multiple\r\n* lines\r\n**/', '.'); | ||
var docs = fileReader.getDocs(fileInfo); | ||
expect(docs[0].content).toEqual('Some jsdoc comment\r\nover multiple\r\nlines'); | ||
}); | ||
}); | ||
}); |
@@ -1,8 +0,8 @@ | ||
var package = require('../index'); | ||
var Config = require('dgeni').Config; | ||
var jsdocPackage = require('../index'); | ||
var Package = require('dgeni').Package; | ||
describe('jsdoc package', function() { | ||
it("should load the package", function() { | ||
package(new Config()); | ||
it("should be instance of Package", function() { | ||
expect(jsdocPackage instanceof Package).toBeTruthy(); | ||
}); | ||
}); |
@@ -1,9 +0,12 @@ | ||
var processor = require('../../processors/code-name'); | ||
var codeNameProcessorFactory = require('../../processors/code-name'); | ||
var jsParser = require('esprima'); | ||
var mockLog = jasmine.createSpyObj('log', ['error', 'warn', 'info', 'debug', 'silly']); | ||
describe('code-name doc processor', function() { | ||
it("should understand CallExpressions", function() { | ||
var processor = codeNameProcessorFactory(mockLog); | ||
var ast = jsParser.parse('(function foo() { })()'); | ||
var doc = { codeNode: { node: ast } }; | ||
processor.process([doc]); | ||
processor.$process([doc]); | ||
expect(doc.codeName).toEqual('foo'); | ||
@@ -13,5 +16,6 @@ }); | ||
it("should understand ArrayExpressions", function() { | ||
var processor = codeNameProcessorFactory(mockLog); | ||
var ast = jsParser.parse("$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];"); | ||
var doc = { codeNode: { node: ast } }; | ||
processor.process([doc]); | ||
processor.$process([doc]); | ||
expect(doc.codeName).toEqual('$inject'); | ||
@@ -18,0 +22,0 @@ }); |
@@ -1,12 +0,10 @@ | ||
var processor = require('../../processors/compute-path'); | ||
var computePathProcessorFactory = require('../../processors/compute-path'); | ||
var _ = require('lodash'); | ||
var Config = require('dgeni').Config; | ||
var mockLog = require('dgeni/lib/mocks/log')(false); | ||
describe("compute-path doc processor", function() { | ||
var processor; | ||
var config; | ||
beforeEach(function() { | ||
config = new Config(); | ||
config.set('rendering.contentsFolder', 'partials'); | ||
processor = computePathProcessorFactory(mockLog); | ||
}); | ||
@@ -16,16 +14,20 @@ | ||
var doc1 = { | ||
file: 'a/b/c/foo.ngdoc', | ||
fileName: 'foo' | ||
fileInfo: { | ||
file: 'a/b/c/foo.ngdoc', | ||
baseName: 'foo' | ||
} | ||
}; | ||
var doc2 = { | ||
file: 'x/y/z/index.html', | ||
fileName: 'index' | ||
fileInfo: { | ||
file: 'x/y/z/index.html', | ||
baseName: 'index' | ||
} | ||
}; | ||
processor.process([doc1, doc2], config); | ||
processor.$process([doc1, doc2]); | ||
expect(doc1.path).toEqual('a/b/c/foo'); | ||
expect(doc1.outputPath).toEqual('partials/a/b/c/foo.html'); | ||
expect(doc1.outputPath).toEqual('a/b/c/foo.html'); | ||
expect(doc2.path).toEqual('x/y/z'); | ||
expect(doc2.outputPath).toEqual('partials/x/y/z.html'); | ||
expect(doc2.outputPath).toEqual('x/y/z.html'); | ||
}); | ||
@@ -35,11 +37,13 @@ | ||
var doc = { | ||
file: 'x/y/z/index.html', | ||
fileName: 'index', | ||
fileInfo: { | ||
file: 'x/y/z/index.html', | ||
baseName: 'index' | ||
}, | ||
path: 'a/b/c' | ||
}; | ||
processor.process([doc], config); | ||
processor.$process([doc]); | ||
expect(doc.path).toEqual('a/b/c'); | ||
expect(doc.outputPath).toEqual('partials/a/b/c.html'); | ||
expect(doc.outputPath).toEqual('a/b/c.html'); | ||
}); | ||
@@ -50,8 +54,10 @@ | ||
var doc = { | ||
file: 'x/y/z/index.html', | ||
fileName: 'index', | ||
fileInfo: { | ||
file: 'x/y/z/index.html', | ||
baseName: 'index' | ||
}, | ||
outputPath: 'a/b/c/foo.bar' | ||
}; | ||
processor.process([doc], config); | ||
processor.$process([doc]); | ||
@@ -58,0 +64,0 @@ expect(doc.path).toEqual('x/y/z'); |
@@ -1,15 +0,235 @@ | ||
var rewire = require('rewire'); | ||
var plugin = rewire('../../processors/extract-tags'); | ||
var factory = require('../../processors/extract-tags'); | ||
var mockLog = require('dgeni/lib/mocks/log')( /* true */ ); | ||
var Tag = require('../../lib/Tag'); | ||
var TagCollection = require('../../lib/TagCollection'); | ||
var createDocMessageFactory = require('../../../base/services/createDocMessage'); | ||
describe("extract-tags doc processor plugin", function() { | ||
it("should have name 'extract-tags", function() { | ||
expect(plugin.name).toEqual('extract-tags'); | ||
function createTagDefContainer(tagDefs) { | ||
return { | ||
tagDefinitions: tagDefs | ||
}; | ||
} | ||
function createDoc(tags) { | ||
return { | ||
fileInfo: { | ||
filePath: 'some/file.js' | ||
}, | ||
tags: new TagCollection(tags) | ||
}; | ||
} | ||
function createProcessor(tagDefs) { | ||
return factory(mockLog, createTagDefContainer(tagDefs), createDocMessageFactory()); | ||
} | ||
describe("extractTagsProcessor", function() { | ||
it("should have name the correct name", function() { | ||
expect(factory.name).toEqual('extractTagsProcessor'); | ||
}); | ||
it("should call extractTags on the document", function() { | ||
var doc = {}; | ||
var extractTagsSpy = jasmine.createSpy('extractTags'); | ||
plugin.process([doc], extractTagsSpy); | ||
expect(extractTagsSpy).toHaveBeenCalledWith(doc); | ||
describe('default tag-def', function() { | ||
it("should the extract the description property to a property with the name of the tagDef", function() { | ||
var tagDef = { name: 'a' }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content'); | ||
}); | ||
}); | ||
describe("tag-defs with docProperty", function() { | ||
it("should assign the extracted value to the docProperty", function() { | ||
var tagDef = { name: 'a', docProperty: 'b' }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toBeUndefined(); | ||
expect(doc.b).toEqual('some content'); | ||
}); | ||
}); | ||
describe("tag-defs with multi", function() { | ||
it("should assign the extracted value(s) to an array on the doc", function() { | ||
var tagDef = { name: 'a', multi: true }; | ||
var processor = createProcessor([tagDef]); | ||
var tag1 = new Tag(tagDef, 'a', 'some content', 123); | ||
var tag2 = new Tag(tagDef, 'a', 'some other content', 256); | ||
var docA = createDoc([tag1]); | ||
var docB = createDoc([tag1, tag2]); | ||
processor.$process([docA]); | ||
expect(docA.a).toEqual(['some content']); | ||
processor.$process([docB]); | ||
expect(docB.a).toEqual(['some content', 'some other content']); | ||
}); | ||
}); | ||
describe("tag-defs with required", function() { | ||
it("should throw an error if the tag is missing", function() { | ||
var tagDef = { name: 'a', required: true }; | ||
var processor = createProcessor([tagDef]); | ||
var doc = createDoc([]); | ||
expect(function() { | ||
processor.$process([doc]); | ||
}).toThrow(); | ||
}); | ||
}); | ||
describe("tag-defs with tagProperty", function() { | ||
it("should assign the specified tag property to the document", function() { | ||
var tagDef = { name: 'a', tagProperty: 'b' }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
tag.b = 'special value'; | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('special value'); | ||
}); | ||
}); | ||
describe("tag-defs with defaultFn", function() { | ||
it("should run the defaultFn if the tag is missing", function() { | ||
var defaultFn = jasmine.createSpy('defaultFn').and.returnValue('default value'); | ||
var tagDef = { name: 'a', defaultFn: defaultFn }; | ||
var processor = createProcessor([tagDef]); | ||
var doc = createDoc([]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('default value'); | ||
expect(defaultFn).toHaveBeenCalled(); | ||
}); | ||
describe("and mult", function() { | ||
it("should run the defaultFn if the tag is missing", function() { | ||
var defaultFn = jasmine.createSpy('defaultFn').and.returnValue('default value'); | ||
var tagDef = { name: 'a', defaultFn: defaultFn, multi: true }; | ||
var processor = createProcessor([tagDef]); | ||
var doc = createDoc([]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual(['default value']); | ||
expect(defaultFn).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); | ||
describe("transforms", function() { | ||
describe("(single)", function() { | ||
it("should apply the transform to the extracted value", function() { | ||
function addA(doc, tag, value) { return value + '*A*'; } | ||
var tagDef = { name: 'a', transforms: addA }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content*A*'); | ||
}); | ||
it("should allow changes to tag and doc", function() { | ||
function transform(doc, tag, value) { doc.x = 'x'; tag.y = 'y'; return value; } | ||
var tagDef = { name: 'a', transforms: transform }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content'); | ||
expect(doc.x).toEqual('x'); | ||
expect(tag.y).toEqual('y'); | ||
}); | ||
}); | ||
describe("(multiple)", function() { | ||
it("should apply the transforms to the extracted value", function() { | ||
function addA(doc, tag, value) { return value + '*A*'; } | ||
function addB(doc, tag, value) { return value + '*B*'; } | ||
var tagDef = { name: 'a', transforms: [ addA, addB ] }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content*A**B*'); | ||
}); | ||
it("should allow changes to tag and doc", function() { | ||
function transform1(doc, tag, value) { doc.x = 'x'; return value; } | ||
function transform2(doc, tag, value) { tag.y = 'y'; return value; } | ||
var tagDef = { name: 'a', transforms: [transform1, transform2] }; | ||
var processor = createProcessor([tagDef]); | ||
var tag = new Tag(tagDef, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content'); | ||
expect(doc.x).toEqual('x'); | ||
expect(tag.y).toEqual('y'); | ||
}); | ||
}); | ||
}); | ||
describe("default transforms", function() { | ||
it("should apply the default transformations to all tags", function() { | ||
var tagDef1 = { name: 'a' }; | ||
var tagDef2 = { name: 'b' }; | ||
function addA(doc, tag, value) { return value + '*A*'; } | ||
var processor = createProcessor([tagDef1, tagDef2]); | ||
processor.defaultTagTransforms = [addA]; | ||
var tag1 = new Tag(tagDef1, 'a', 'some content', 123); | ||
var tag2 = new Tag(tagDef2, 'b', 'some other content', 123); | ||
var doc = createDoc([tag1, tag2]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content*A*'); | ||
expect(doc.b).toEqual('some other content*A*'); | ||
}); | ||
it("should apply the default transformations after tag specific transforms", function() { | ||
function addA(doc, tag, value) { return value + '*A*'; } | ||
function addB(doc, tag, value) { return value + '*B*'; } | ||
var tagDef1 = { name: 'a', transforms: addA }; | ||
var processor = createProcessor([tagDef1]); | ||
processor.defaultTagTransforms = [addB]; | ||
var tag = new Tag(tagDef1, 'a', 'some content', 123); | ||
var doc = createDoc([tag]); | ||
processor.$process([doc]); | ||
expect(doc.a).toEqual('some content*A**B*'); | ||
}); | ||
}); | ||
}); |
@@ -1,25 +0,24 @@ | ||
var Config = require('dgeni').Config; | ||
var processor = require('../../processors/inline-tags'); | ||
var di = require('di'); | ||
var log = require('winston'); | ||
var factory = require('../../processors/inline-tags'); | ||
var mockLog = require('dgeni/lib/mocks/log')(); | ||
var createDocMessageFactory = require('../../../base/services/createDocMessage'); | ||
function createProcessor() { | ||
return factory(mockLog, createDocMessageFactory()); | ||
} | ||
describe("inline-tags processor", function() { | ||
var config; | ||
beforeEach(function() { | ||
config = new Config(); | ||
it("should have the correct name", function() { | ||
expect(factory.name).toEqual('inlineTagProcessor'); | ||
}); | ||
it("should be called 'inline-tags'", function() { | ||
expect(processor.name).toEqual('inline-tags'); | ||
}); | ||
it("should run after docs are rendered and before writing files", function() { | ||
expect(processor.runAfter).toEqual(['docs-rendered']); | ||
expect(processor.runBefore).toEqual(['writing-files']); | ||
var processor = createProcessor(); | ||
expect(processor.$runAfter).toEqual(['docs-rendered']); | ||
expect(processor.$runBefore).toEqual(['writing-files']); | ||
}); | ||
describe("process", function() { | ||
describe("$process", function() { | ||
@@ -40,24 +39,17 @@ it("should parse the inline tags from the renderedContent", function() { | ||
// Provide a mock tag handler to track what tags have been handled | ||
var tagsFound = []; | ||
var mockInlineTagDefinition = { | ||
name: 'handledTag', | ||
handlerFactory: function(docs) { | ||
return function(doc, tagName, tagDescription) { | ||
tagsFound.push({ name: tagName, description: tagDescription }); | ||
return '<Tag Handled>'; | ||
}; | ||
handler: function(doc, tagName, tagDescription, docs) { | ||
tagsFound.push({ name: tagName, description: tagDescription }); | ||
return '<Tag Handled>'; | ||
} | ||
}; | ||
// We spy on log.warn since this will be called on each missing handler | ||
spyOn(log, 'warn'); | ||
var processor = createProcessor(); | ||
processor.inlineTagDefinitions = [mockInlineTagDefinition]; | ||
// Create the injector that the processor will use | ||
var injector = new di.Injector([ { docs: ['value', docs] } ]); | ||
// Provide a mock inline tag handler | ||
config.set('processing.inlineTagDefinitions', [mockInlineTagDefinition]); | ||
// Run the processor | ||
var results = processor.process(docs, config, injector); | ||
var results = processor.$process(docs); | ||
@@ -68,4 +60,4 @@ // This processor should not return anything. All its work is done on the docs, in place | ||
// We expect the unhandled tag to be reported | ||
expect(log.warn).toHaveBeenCalled(); | ||
expect(log.warn.calls.argsFor(0)[0]).toMatch(/No handler provided for inline tag "\{@unhandledTag some description\}"/); | ||
expect(mockLog.warn).toHaveBeenCalled(); | ||
expect(mockLog.warn.calls.argsFor(0)[0]).toContain('No handler provided for inline tag "{@unhandledTag some description}"'); | ||
@@ -72,0 +64,0 @@ // We expect the handler to have been invoked for the handledTag |
@@ -1,20 +0,92 @@ | ||
var parseTagsProcessor = require('../../processors/parse-tags'); | ||
var factory = require('../../processors/parse-tags'); | ||
var mockLog = require('dgeni/lib/mocks/log')(); | ||
describe("parse-tags processor", function() { | ||
it("should be called 'parse-tags'", function() { | ||
expect(parseTagsProcessor.name).toEqual('parse-tags'); | ||
var processor; | ||
var tagDefinitions = [ | ||
{ name: 'id' }, | ||
{ name: 'description' }, | ||
{ name: 'param' }, | ||
{ name: 'other-tag', ignore: true } | ||
]; | ||
beforeEach(function() { | ||
processor = factory(mockLog); | ||
processor.tagDefinitions = tagDefinitions; | ||
}); | ||
it("should have name the correct name", function() { | ||
expect(factory.name).toEqual('parseTagsProcessor'); | ||
}); | ||
it("should be run in the correct place", function() { | ||
expect(parseTagsProcessor.runAfter).toEqual([ 'parsing-tags' ]); | ||
expect(parseTagsProcessor.runBefore).toEqual([ 'tags-parsed' ]); | ||
expect(processor.$runAfter).toEqual([ 'parsing-tags' ]); | ||
expect(processor.$runBefore).toEqual([ 'tags-parsed' ]); | ||
}); | ||
it("should call tagParser for each doc and assign the result to `doc.tags`", function() { | ||
var testTag = {}; | ||
var tagParser = jasmine.createSpy('tagParser').and.returnValue([testTag]); | ||
var doc = {}; | ||
parseTagsProcessor.process([doc], tagParser); | ||
expect(tagParser).toHaveBeenCalled(); | ||
expect(doc.tags).toEqual([testTag]); | ||
it("should only return tags that are not ignored", function() { | ||
var content = 'Some initial content\n@id some.id\n' + | ||
'@description Some description\n@other-tag Some other tag\n' + | ||
'@param some param\n@param some other param'; | ||
var doc = {content: content, startingLine: 10}; | ||
processor.$process([doc]); | ||
expect(doc.tags.tags[0]).toEqual( | ||
jasmine.objectContaining({ tagName: 'id', description: 'some.id', startingLine: 11 }) | ||
); | ||
// Not that the description tag contains what appears to be another tag but it was ignored so | ||
// is consumed into the description tag! | ||
expect(doc.tags.tags[1]).toEqual( | ||
jasmine.objectContaining({ tagName: 'description', description: 'Some description\n@other-tag Some other tag', startingLine: 12}) | ||
); | ||
expect(doc.tags.tags[2]).toEqual( | ||
jasmine.objectContaining({ tagName: 'param', description: 'some param', startingLine: 14 }) | ||
); | ||
expect(doc.tags.tags[3]).toEqual( | ||
jasmine.objectContaining({ tagName: 'param', description: 'some other param', startingLine: 15 }) | ||
); | ||
}); | ||
it("should cope with tags that have no 'description'", function() { | ||
var content = '@id\n@description some description'; | ||
var doc = { content: content, startingLine: 123 }; | ||
processor.$process([doc]); | ||
expect(doc.tags.tags[0]).toEqual(jasmine.objectContaining({ tagName: 'id', description: '' })); | ||
expect(doc.tags.tags[1]).toEqual(jasmine.objectContaining({ tagName: 'description', description: 'some description' })); | ||
}); | ||
it("should cope with empty content or no known tags", function() { | ||
expect(function() { | ||
processor.$process([{ content: '', startingLine: 123 }]); | ||
}).not.toThrow(); | ||
expect(function() { | ||
processor.$process([{ content: '@unknownTag some text', startingLine: 123 }]); | ||
}).not.toThrow(); | ||
}); | ||
it("should ignore @tags inside back-ticked code blocks", function() { | ||
processor.tagDefinitions = [{ name: 'a' }, { name: 'b' }]; | ||
var content = | ||
'@a some text\n\n' + | ||
'```\n' + | ||
' some code\n' + | ||
' @b not a tag\n' + | ||
'```\n\n' + | ||
'more text\n' + | ||
'@b is a tag'; | ||
var doc = { content: content }; | ||
processor.$process([doc]); | ||
expect(doc.tags.getTag('a').description).toEqual('some text\n\n' + | ||
'```\n' + | ||
' some code\n' + | ||
' @b not a tag\n' + | ||
'```\n\n' + | ||
'more text' | ||
); | ||
expect(doc.tags.getTags('b').length).toEqual(1); | ||
expect(doc.tags.getTag('b').description).toEqual('is a tag'); | ||
}); | ||
}); |
@@ -1,76 +0,25 @@ | ||
var path = require('canonical-path'); | ||
var _ = require('lodash'); | ||
var extractName = require('./transforms/extract-name'); | ||
var extractType = require('./transforms/extract-type'); | ||
var wholeTag = require('./transforms/whole-tag'); | ||
module.exports = [ | ||
{ | ||
name: 'name' | ||
}, | ||
{ | ||
name: 'memberof', | ||
defaultFn: function(doc) { | ||
if ( doc.docType === 'event' || doc.docType === 'property' || doc.docType === 'method' ) { | ||
throw new Error('Missing tag "@memberof" for doc of type "'+ doc.docType + '" in file "' + doc.file + '" at line ' + doc.startingLine); | ||
} | ||
}, | ||
transforms: function(doc, tag, value) { | ||
if ( !(doc.docType === 'event' || doc.docType === 'property' || doc.docType === 'method') ) { | ||
throw new Error('"@'+ tag.name +'" tag found on non-'+ doc.docType +' document in file "' + doc.file + '" at line ' + doc.startingLine); | ||
} | ||
return value; | ||
} | ||
}, | ||
{ | ||
name: 'param', | ||
multi: true, | ||
docProperty: 'params', | ||
transforms: [ extractType, extractName, wholeTag ] | ||
}, | ||
{ | ||
name: 'property', | ||
multi: true, | ||
docProperty: 'properties', | ||
transforms: [ extractType, extractName, wholeTag ] | ||
}, | ||
{ | ||
name: 'returns', | ||
aliases: ['return'], | ||
transforms: [ extractType, wholeTag ] | ||
}, | ||
{ | ||
name: 'type', | ||
transforms: [ extractType, wholeTag ] | ||
}, | ||
{ | ||
name: 'requires', | ||
multi: true | ||
}, | ||
{ name: 'module' }, | ||
{ name: 'description' }, | ||
{ name: 'deprecated' }, | ||
{ name: 'private' }, | ||
{ name: 'see', multi: true }, | ||
{ name: 'usage' }, | ||
{ name: 'animations' }, | ||
{ name: 'constructor' }, | ||
{ name: 'class' }, | ||
{ name: 'classdesc' }, | ||
{ name: 'global' }, | ||
{ name: 'namespace' }, | ||
{ name: 'kind' }, | ||
{ name: 'function' }, | ||
{ name: 'method' } | ||
require('./name'), | ||
require('./memberof'), | ||
require('./param'), | ||
require('./property'), | ||
require('./returns'), | ||
require('./type'), | ||
require('./requires'), | ||
require('./module'), | ||
require('./description'), | ||
require('./deprecated'), | ||
require('./private'), | ||
require('./see'), | ||
require('./usage'), | ||
require('./animations'), | ||
require('./constructor'), | ||
require('./class'), | ||
require('./classdesc'), | ||
require('./global'), | ||
require('./namespace'), | ||
require('./kind'), | ||
require('./function'), | ||
require('./method') | ||
]; |
/** | ||
* @dgService ngdocFileReader | ||
* @description | ||
* This extractor will pull the content from an ngdoc file | ||
* This file reader will pull the contents from a text file (by default .ngdoc) | ||
* | ||
* The doc will initially have the form: | ||
* ``` | ||
* { | ||
* fileType: 'ngdoc', | ||
* file: 'path/to/file.ngdoc' | ||
* content: 'the content of the file', | ||
* startingLine: 1 | ||
* } | ||
* ``` | ||
*/ | ||
module.exports = { | ||
pattern: /\.ngdoc$/, | ||
processFile: function(filePath, contents, basePath) { | ||
// We return a single element array because ngdoc files only contain one document | ||
return [{ | ||
content: contents, | ||
file: filePath, | ||
basePath: basePath, | ||
fileType: 'ngdoc', | ||
startingLine: 1 | ||
}]; | ||
} | ||
module.exports = function ngdocFileReader() { | ||
return { | ||
name: 'ngdocFileReader', | ||
defaultPattern: /\.ngdoc$/, | ||
getDocs: function(fileInfo) { | ||
// We return a single element array because ngdoc files only contain one document | ||
return [{ | ||
content: fileInfo.content, | ||
startingLine: 1 | ||
}]; | ||
} | ||
}; | ||
}; |
@@ -1,40 +0,49 @@ | ||
var _ = require('lodash'); | ||
var path = require('canonical-path'); | ||
var packagePath = __dirname; | ||
var Package = require('dgeni').Package; | ||
module.exports = function(config) { | ||
module.exports = new Package('ngdoc', [require('../jsdoc'), require('../nunjucks')]) | ||
require('../jsdoc')(config); | ||
require('../nunjucks')(config); | ||
.factory(require('./file-readers/ngdoc')) | ||
.factory(require('./inline-tag-defs/link')) | ||
.factory(require('./services/getLinkInfo')) | ||
.factory(require('./services/getTypeClass')) | ||
.factory(require('./services/getPartialNames')) | ||
.factory(require('./services/parseCodeName')) | ||
.factory(require('./services/partialNameMap')) | ||
.factory(require('./services/moduleMap')) | ||
config.merge('rendering.nunjucks.config.tags', { | ||
variableStart: '{$', | ||
variableEnd: '$}' | ||
}); | ||
.processor(require('./processors/apiDocs')) | ||
.processor(require('./processors/generateComponentGroups')) | ||
.processor(require('./processors/computePath')) // Override the current computePathProcessor with our own | ||
.processor(require('./processors/computeId')) | ||
.processor(require('./processors/filterNgdocs')) | ||
.processor(require('./processors/collectPartialNames')) | ||
config.append('source.fileReaders', require('./file-readers/ngdoc')); | ||
config.append('processing.tagDefinitions', require('./tag-defs')); | ||
config.append('processing.inlineTagDefinitions', [ | ||
require('./inline-tag-defs/link') | ||
]); | ||
.config(function(readFilesProcessor, ngdocFileReader) { | ||
readFilesProcessor.fileReaders.push(ngdocFileReader); | ||
}) | ||
// Replace the default compute-path processor | ||
var processors = config.get('processing.processors'); | ||
_.remove(processors, {name: 'compute-path'}); | ||
config.append('processing.processors', [ | ||
require('./processors/compute-path') | ||
]); | ||
config.append('processing.processors', [ | ||
require('./processors/partial-names'), | ||
require('./processors/filter-ngdocs'), | ||
require('./processors/compute-id'), | ||
require('./processors/api-docs'), | ||
require('./processors/component-groups-generate') | ||
]); | ||
.config(function(parseTagsProcessor, getInjectables) { | ||
parseTagsProcessor.tagDefinitions = | ||
parseTagsProcessor.tagDefinitions.concat(getInjectables(require('./tag-defs'))); | ||
}) | ||
config.prepend('rendering.templateFolders', path.resolve(packagePath, 'templates')); | ||
config.prepend('rendering.templatePatterns', [ | ||
.config(function(inlineTagProcessor, linkInlineTagDef) { | ||
inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef); | ||
}) | ||
.config(function(templateFinder, templateEngine, getInjectables) { | ||
templateFinder.templateFolders.unshift(path.resolve(__dirname, 'templates')); | ||
templateEngine.config.tags = { | ||
variableStart: '{$', | ||
variableEnd: '$}' | ||
}; | ||
templateFinder.templatePatterns = [ | ||
'${ doc.template }', | ||
@@ -47,15 +56,11 @@ '${doc.area}/${ doc.id }.${ doc.docType }.template.html', | ||
'${ doc.docType }.template.html' | ||
]); | ||
].concat(templateEngine.templatePatterns); | ||
config.append('rendering.filters', [ | ||
templateEngine.filters = templateEngine.filters.concat(getInjectables([ | ||
require('./rendering/filters/code'), | ||
require('./rendering/filters/link'), | ||
require('./rendering/filters/type-class') | ||
]); | ||
])); | ||
config.append('rendering.tags', [ | ||
require('./rendering/tags/code') | ||
]); | ||
return config; | ||
}; | ||
templateEngine.tags = templateEngine.tags.concat(getInjectables([require('./rendering/tags/code')])); | ||
}); |
var INLINE_LINK = /(\S+)(?:\s+([\s\S]+))?/; | ||
module.exports = { | ||
name: 'link', | ||
description: 'Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors', | ||
handlerFactory: function(partialNames) { | ||
module.exports = function linkInlineTagDef(getLinkInfo, createDocMessage) { | ||
return { | ||
name: 'link', | ||
description: 'Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors', | ||
handler: function(doc, tagName, tagDescription) { | ||
return function handleLinkTags(doc, tagName, tagDescription) { | ||
// Parse out the uri and title | ||
return tagDescription.replace(INLINE_LINK, function(match, uri, title) { | ||
var linkInfo = partialNames.getLink(uri, title, doc); | ||
var linkInfo = getLinkInfo(uri, title, doc); | ||
if ( !linkInfo.valid ) { | ||
throw new Error(linkInfo.error); | ||
throw new Error(createDocMessage(linkInfo.error, doc)); | ||
} | ||
return '<a href="' + linkInfo.url + '">' + linkInfo.title + '</a>'; | ||
}); | ||
}; | ||
} | ||
} | ||
}; | ||
}; |
@@ -1,7 +0,8 @@ | ||
var code = require('../../../utils/code'); | ||
module.exports = { | ||
name: 'code', | ||
process: function(str, lang) { | ||
return code(str, true, lang); | ||
} | ||
module.exports = function(encodeCodeBlock) { | ||
return { | ||
name: 'code', | ||
process: function(str, lang) { | ||
return encodeCodeBlock(str, true, lang); | ||
} | ||
}; | ||
}; |
var _ = require('lodash'); | ||
var log = require('winston'); | ||
module.exports = { | ||
name: 'link', | ||
process: function(url, title, doc) { | ||
return _.template('{@link ${url} ${title} }', { url: url, title: title }); | ||
} | ||
module.exports = function() { | ||
return { | ||
name: 'link', | ||
process: function(url, title, doc) { | ||
return _.template('{@link ${url} ${title} }', { url: url, title: title }); | ||
} | ||
}; | ||
}; |
@@ -1,8 +0,6 @@ | ||
module.exports = { | ||
name: 'typeClass', | ||
process: function(str) { | ||
var typeClass = str.toLowerCase().match(/^[-\w]+/) || []; | ||
typeClass = typeClass[0] ? typeClass[0] : 'object'; | ||
return 'label type-hint type-hint-' + typeClass; | ||
} | ||
module.exports = function(getTypeClass) { | ||
return { | ||
name: 'typeClass', | ||
process: getTypeClass | ||
}; | ||
}; |
@@ -1,28 +0,27 @@ | ||
var trimIndentation = require('../../../utils/trim-indentation'); | ||
var code = require('../../../utils/code'); | ||
module.exports = function(trimIndentation, encodeCodeBlock) { | ||
return { | ||
tags: ['code'], | ||
module.exports = { | ||
tags: ['code'], | ||
parse: function(parser, nodes) { | ||
var tok = parser.nextToken(); | ||
var args = parser.parseSignature(null, true); | ||
parser.advanceAfterBlockEnd(tok.value); | ||
parse: function(parser, nodes) { | ||
var tok = parser.nextToken(); | ||
var args = parser.parseSignature(null, true); | ||
parser.advanceAfterBlockEnd(tok.value); | ||
var content = parser.parseUntilBlocks("endcode"); | ||
var tag = new nodes.CallExtension(this, 'process', args, [content]); | ||
parser.advanceAfterBlockEnd(); | ||
var content = parser.parseUntilBlocks("endcode"); | ||
var tag = new nodes.CallExtension(this, 'process', args, [content]); | ||
parser.advanceAfterBlockEnd(); | ||
return tag; | ||
}, | ||
return tag; | ||
}, | ||
process: function(context, lang, content) { | ||
if ( !content ) { | ||
content = lang; | ||
lang = undefined; | ||
process: function(context, lang, content) { | ||
if ( !content ) { | ||
content = lang; | ||
lang = undefined; | ||
} | ||
var trimmedString = trimIndentation(content()); | ||
var codeString = encodeCodeBlock(trimmedString, false, lang); | ||
return codeString; | ||
} | ||
var trimmedString = trimIndentation(content()); | ||
var codeString = code(trimmedString, false, lang); | ||
return codeString; | ||
} | ||
}; | ||
}; |
@@ -1,18 +0,40 @@ | ||
var ngdocExtractor = require('../../file-readers/ngdoc'); | ||
var ngdocFileReaderFactory = require('../../file-readers/ngdoc'); | ||
var path = require('canonical-path'); | ||
describe("extractNgdoc", function() { | ||
describe("pattern", function() { | ||
it("should only match ngdoc files", function() { | ||
expect(ngdocExtractor.pattern.test('abc.ngdoc')).toBeTruthy(); | ||
expect(ngdocExtractor.pattern.test('abc.js')).toBeFalsy(); | ||
describe("ngdocFileReader", function() { | ||
var fileReader; | ||
var createFileInfo = function(file, content, basePath) { | ||
return { | ||
fileReader: fileReader.name, | ||
filePath: file, | ||
baseName: path.basename(file, path.extname(file)), | ||
extension: path.extname(file).replace(/^\./, ''), | ||
basePath: basePath, | ||
relativePath: path.relative(basePath, file), | ||
content: content | ||
}; | ||
}; | ||
beforeEach(function() { | ||
fileReader = ngdocFileReaderFactory(); | ||
}); | ||
describe("defaultPattern", function() { | ||
it("should match .ngdoc files", function() { | ||
expect(fileReader.defaultPattern.test('abc.ngdoc')).toBeTruthy(); | ||
expect(fileReader.defaultPattern.test('abc.js')).toBeFalsy(); | ||
}); | ||
}); | ||
describe("process", function() { | ||
describe("getDocs", function() { | ||
it('should return an object containing info about the file and its contents', function() { | ||
expect(ngdocExtractor.processFile('foo/bar.ngdoc', 'A load of content', 'base/path')).toEqual([{ | ||
var fileInfo = createFileInfo('foo/bar.ngdoc', 'A load of content', 'base/path'); | ||
expect(fileReader.getDocs(fileInfo)).toEqual([{ | ||
content: 'A load of content', | ||
file: 'foo/bar.ngdoc', | ||
fileType: 'ngdoc', | ||
startingLine: 1, | ||
basePath: 'base/path' | ||
startingLine: 1 | ||
}]); | ||
@@ -19,0 +41,0 @@ }); |
@@ -1,5 +0,8 @@ | ||
var package = require('../index'); | ||
var ngdocPackage = require('../index'); | ||
var Package = require('dgeni').Package; | ||
describe('ngdoc package', function() { | ||
it("should be instance of Package", function() { | ||
expect(ngdocPackage instanceof Package).toBeTruthy(); | ||
}); | ||
}); |
@@ -1,17 +0,10 @@ | ||
var _ = require('lodash'); | ||
var logger = require('winston'); | ||
var linkTagDef = require('../../inline-tag-defs/link'); | ||
var PartialNames = require('../../utils/partial-names').PartialNames; | ||
var tagDefFactory = require('../../inline-tag-defs/link'); | ||
var createDocMessageFactory = require('../../../base/services/createDocMessage'); | ||
describe("links inline tag handler", function() { | ||
var doc, links, logLevel, partialNames, linkHandler; | ||
var tagDef, getLinkInfoSpy, doc, links; | ||
it("should have name 'link'", function() { | ||
expect(linkTagDef.name).toEqual('link'); | ||
}); | ||
beforeEach(function() { | ||
logLevel = logger.level; | ||
logger.level = 'warn'; | ||
spyOn(logger, 'warn'); | ||
getLinkInfoSpy = jasmine.createSpy('getLinkInfo'); | ||
tagDef = tagDefFactory(getLinkInfoSpy, createDocMessageFactory()); | ||
@@ -33,29 +26,29 @@ doc = { | ||
partialNames = new PartialNames(); | ||
partialNames.addDoc(doc); | ||
linkHandler = linkTagDef.handlerFactory(partialNames); | ||
}); | ||
afterEach(function() { | ||
logger.level = logLevel; | ||
it("should have name 'link'", function() { | ||
expect(tagDef.name).toEqual('link'); | ||
}); | ||
it("should convert urls to HTML anchors", function() { | ||
expect(linkHandler(doc, 'link', 'some/url link')).toEqual('<a href="some/url">link</a>'); | ||
it("should use the result of getLinkInfo to create a HTML anchor", function() { | ||
getLinkInfoSpy.and.returnValue({ | ||
valid: true, | ||
url: 'some/url', | ||
title: 'link' | ||
}); | ||
expect(tagDef.handler(doc, 'link', 'some/url link')).toEqual('<a href="some/url">link</a>'); | ||
expect(getLinkInfoSpy).toHaveBeenCalled(); | ||
}); | ||
it("should parse empty space within a link's title", function() { | ||
expect(linkHandler(doc, 'link', 'another/url link that spans\n two lines')).toEqual('<a href="another/url">link that spans\n two lines</a>'); | ||
}); | ||
it("should convert code links to anchors with formatted code", function() { | ||
expect(linkHandler(doc, 'link', 'ngInclude')).toEqual('<a href="api/ng/directive/ngInclude"><code>ngInclude</code></a>'); | ||
}); | ||
it("should check that any links in the links property of a doc reference a valid doc", function() { | ||
it("should throw an error if the link is invalid", function() { | ||
getLinkInfoSpy.and.returnValue({ | ||
valid: false, | ||
error: 'Invalid link (does not match any doc): "module:ngOther.directive:ngDirective"' | ||
}); | ||
expect(function() { | ||
linkHandler(doc, 'link', 'module:ngOther.directive:ngDirective'); | ||
}).toThrowError(/Invalid link \(does not match any doc\): "module:ngOther\.directive:ngDirective"/); | ||
tagDef.handler(doc, 'link', 'module:ngOther.directive:ngDirective'); | ||
}).toThrowError('Invalid link (does not match any doc): "module:ngOther.directive:ngDirective" - doc "module:ng.directive:ngInclude"'); | ||
expect(getLinkInfoSpy).toHaveBeenCalled(); | ||
}); | ||
}); |
@@ -1,8 +0,14 @@ | ||
var rewire = require('rewire'); | ||
var filter = rewire('../../../rendering/filters/code'); | ||
var codeFilterFactory = require('../../../rendering/filters/code'); | ||
describe("code custom filter", function() { | ||
var codeFilter, codeSpy; | ||
beforeEach(function() { | ||
codeSpy = jasmine.createSpy('code').and.callFake(function(value) { return '<code>' + value + '</code>'; }); | ||
codeFilter = codeFilterFactory(codeSpy); | ||
}); | ||
it("should have the name 'code'", function() { | ||
expect(filter.name).toEqual('code'); | ||
expect(codeFilter.name).toEqual('code'); | ||
}); | ||
@@ -12,7 +18,3 @@ | ||
it("should call the code utility", function() { | ||
var codeSpy = jasmine.createSpy('code'); | ||
filter.__set__('code', codeSpy); | ||
filter.process('function foo() { }'); | ||
codeFilter.process('function foo() { }'); | ||
expect(codeSpy).toHaveBeenCalledWith('function foo() { }', true, undefined); | ||
@@ -23,9 +25,5 @@ }); | ||
it("should pass the language to the code utility", function() { | ||
var codeSpy = jasmine.createSpy('code'); | ||
filter.__set__('code', codeSpy); | ||
filter.process('function foo() { }', 'js'); | ||
codeFilter.process('function foo() { }', 'js'); | ||
expect(codeSpy).toHaveBeenCalledWith('function foo() { }', true, 'js'); | ||
}); | ||
}); |
@@ -1,5 +0,10 @@ | ||
var filter = require('../../../rendering/filters/link'); | ||
var filterFactory = require('../../../rendering/filters/link'); | ||
describe("link filter", function() { | ||
var filter; | ||
beforeEach(function() { | ||
filter = filterFactory(); | ||
}); | ||
it("should have the name 'link'", function() { | ||
@@ -6,0 +11,0 @@ expect(filter.name).toEqual('link'); |
@@ -1,10 +0,11 @@ | ||
var filter = require('../../../rendering/filters/type-class'); | ||
var filterFactory = require('../../../rendering/filters/type-class'); | ||
describe("type-class filter", function() { | ||
it("should convert the given type into a CSS class", function() { | ||
expect(filter.process('object')).toEqual('label type-hint type-hint-object'); | ||
expect(filter.process('string')).toEqual('label type-hint type-hint-string'); | ||
expect(filter.process('function()')).toEqual('label type-hint type-hint-function'); | ||
expect(filter.process('Regex')).toEqual('label type-hint type-hint-regex'); | ||
it("should call getTypeClass", function() { | ||
var getTypeClassSpy = jasmine.createSpy('getTypeClass'); | ||
var filter = filterFactory(getTypeClassSpy); | ||
filter.process('object'); | ||
expect(getTypeClassSpy).toHaveBeenCalled(); | ||
}); | ||
}); |
@@ -1,11 +0,11 @@ | ||
var rewire = require('rewire'); | ||
var codeTag = rewire('../../../rendering/tags/code'); | ||
var codeTagFactory = require('../../../rendering/tags/code'); | ||
var nunjucks = require('nunjucks'); | ||
describe("code custom tag", function() { | ||
var codeSpy, env; | ||
var codeTag, trimIndentationSpy, codeSpy, env; | ||
beforeEach(function() { | ||
trimIndentationSpy = jasmine.createSpy('trimIndentation').and.callFake(function(value) { return value.trim(); }); | ||
codeSpy = jasmine.createSpy('code'); | ||
codeTag.__set__('code', codeSpy); | ||
codeTag = codeTagFactory(trimIndentationSpy, codeSpy); | ||
@@ -12,0 +12,0 @@ env = nunjucks.configure('views'); |
@@ -1,119 +0,17 @@ | ||
var path = require('canonical-path'); | ||
var _ = require('lodash'); | ||
module.exports = [ | ||
{ | ||
name: 'ngdoc', | ||
required: true, | ||
docProperty: 'docType' | ||
}, | ||
{ | ||
name: 'name', | ||
required: true, | ||
transforms: function(doc, tag, value) { | ||
var INPUT_TYPE = /input\[(.+)\]/; | ||
if ( doc.docType === 'input' ) { | ||
var match = INPUT_TYPE.exec(value); | ||
if ( !match ) { | ||
throw new Error('Invalid input directive name. It should be of the form: "input[inputType]" but was "' + value + '"'); | ||
} | ||
doc.inputType = match[1]; | ||
} | ||
return value; | ||
} | ||
}, | ||
{ | ||
name: 'area', | ||
defaultFn: function(doc) { | ||
// Code files are put in the 'api' area | ||
// Other files compute their area from the first path segment | ||
return (doc.fileType === 'js') ? 'api' : doc.file.split('/')[0]; | ||
} | ||
}, | ||
{ | ||
name: 'module', | ||
defaultFn: function(doc) { | ||
if ( doc.area === 'api' ) { | ||
// Calculate the module from the second segment of the file path | ||
// (the first being the area) | ||
return path.dirname(doc.file).split('/')[1]; | ||
} | ||
} | ||
}, | ||
{ name: 'id' }, | ||
{ | ||
name: 'restrict', | ||
defaultFn: function(doc) { | ||
if ( doc.docType === 'directive' || doc.docType === 'input' ) { | ||
return { element: false, attribute: true, cssClass: false, comment: false }; | ||
} | ||
}, | ||
transforms: function(doc, tag, value) { | ||
return { | ||
element: _.contains(value, 'E'), | ||
attribute: _.contains(value, 'A'), | ||
cssClass: _.contains(value, 'C'), | ||
comment: _.contains(value, 'M') | ||
}; | ||
} | ||
}, | ||
{ | ||
name: 'eventType', | ||
transforms: function(doc, tag, value) { | ||
var EVENTTYPE_REGEX = /^([^\s]*)\s+on\s+([\S\s]*)/; | ||
var match = EVENTTYPE_REGEX.exec(value); | ||
// Attach the target to the doc | ||
doc.eventTarget = match[2]; | ||
// And return the type | ||
return match[1]; | ||
} | ||
}, | ||
{ | ||
name: 'example', | ||
multi: true, | ||
docProperty: 'examples' | ||
}, | ||
{ | ||
name: 'element', | ||
defaultFn: function(doc) { | ||
if ( doc.docType === 'directive' || doc.docType === 'input') { | ||
return'ANY'; | ||
} | ||
} | ||
}, | ||
{ | ||
name: 'fullName' | ||
}, | ||
{ | ||
name: 'scope', | ||
transforms: function(doc, tag) { return true; } | ||
}, | ||
{ | ||
name: 'priority', | ||
defaultFn: function(doc) { return 0; } | ||
}, | ||
{ name: 'title' }, | ||
{ name: 'parent' }, | ||
{ name: 'packageName' } | ||
]; | ||
require('./ngdoc'), | ||
require('./name'), | ||
require('./area'), | ||
require('./module'), | ||
require('./id'), | ||
require('./restrict'), | ||
require('./eventType'), | ||
require('./example'), | ||
require('./element'), | ||
require('./fullName'), | ||
require('./priority'), | ||
require('./title'), | ||
require('./parent'), | ||
require('./packageName'), | ||
require('./scope') | ||
]; |
@@ -0,1 +1,3 @@ | ||
var Package = require('dgeni').Package; | ||
/** | ||
@@ -5,19 +7,17 @@ * @dgPackage nunjucks | ||
*/ | ||
module.exports = function(config) { | ||
module.exports = new Package('nunjucks', ['base']) | ||
config.append('processing.processors', require('./nunjucks-template-engine')); | ||
.factory(require('./services/nunjucks-template-engine')) | ||
.factory(require('./rendering/tags/marked')) | ||
config.append('rendering.filters', require('./rendering/filters/change-case')); | ||
config.append('rendering.filters', [ | ||
require('./rendering/filters/first-line'), | ||
require('./rendering/filters/first-paragraph'), | ||
require('./rendering/filters/json'), | ||
require('./rendering/filters/marked') | ||
]); | ||
config.append('rendering.tags', [ | ||
require('./rendering/tags/marked') | ||
]); | ||
}; | ||
.config(function(templateEngine, markedNunjucksTag) { | ||
templateEngine.tags.push(markedNunjucksTag); | ||
templateEngine.filters = templateEngine.filters | ||
.concat(require('./rendering/filters/change-case')) | ||
.concat([ | ||
require('./rendering/filters/first-line'), | ||
require('./rendering/filters/first-paragraph'), | ||
require('./rendering/filters/json'), | ||
require('./rendering/filters/marked') | ||
]); | ||
}); |
var marked = require('marked'); | ||
var trimIndentation = require('../../../utils/trim-indentation'); | ||
@@ -8,23 +7,25 @@ /** | ||
*/ | ||
module.exports = { | ||
tags: ['marked'], | ||
module.exports = function markedNunjucksTag(trimIndentation) { | ||
return { | ||
tags: ['marked'], | ||
parse: function(parser, nodes) { | ||
parser.advanceAfterBlockEnd(); | ||
parse: function(parser, nodes) { | ||
parser.advanceAfterBlockEnd(); | ||
var content = parser.parseUntilBlocks("endmarked"); | ||
var tag = new nodes.CallExtension(this, 'process', null, [content]); | ||
parser.advanceAfterBlockEnd(); | ||
var content = parser.parseUntilBlocks("endmarked"); | ||
var tag = new nodes.CallExtension(this, 'process', null, [content]); | ||
parser.advanceAfterBlockEnd(); | ||
return tag; | ||
}, | ||
return tag; | ||
}, | ||
process: function(context, content) { | ||
var contentString = content(); | ||
var indent = trimIndentation.calcIndent(contentString); | ||
var trimmedString = trimIndentation.trimIndent(contentString, indent); | ||
var markedString = marked(trimmedString); | ||
var reindentedString = trimIndentation.reindent(markedString, indent); | ||
return reindentedString; | ||
} | ||
process: function(context, content) { | ||
var contentString = content(); | ||
var indent = trimIndentation.calcIndent(contentString); | ||
var trimmedString = trimIndentation.trimIndent(contentString, indent); | ||
var markedString = marked(trimmedString); | ||
var reindentedString = trimIndentation.reindent(markedString, indent); | ||
return reindentedString; | ||
} | ||
}; | ||
}; |
{ | ||
"name": "dgeni-packages", | ||
"version": "0.9.6", | ||
"version": "0.10.0-beta.1", | ||
"description": "A collection of dgeni packages for generating documentation from source code", | ||
"scripts": { | ||
"test": "node ./node_modules/jasmine-node/bin/jasmine-node */spec", | ||
"cover": "istanbul cover ./node_modules/jasmine-node/bin/jasmine-node -- */spec" | ||
"test": "jasmine-node */spec", | ||
"cover": "istanbul cover jasmine-node -- */spec" | ||
}, | ||
@@ -33,24 +33,23 @@ "repository": { | ||
"dependencies": { | ||
"dgeni": "~0.3.0", | ||
"canonical-path": "0.0.2", | ||
"catharsis": "^0.7.0", | ||
"change-case": "^2.1.0", | ||
"dgeni": "^0.4.0", | ||
"es6-shim": "^0.11.1", | ||
"esprima": "^1.0.4", | ||
"glob": "~3.2.8", | ||
"graceful-fs": "~2.0.1", | ||
"lodash": "~2.4.1", | ||
"winston": "~0.7.2", | ||
"graceful-fs": "~2.0.1", | ||
"marked": "~0.2.10", | ||
"minimatch": "^0.3.0", | ||
"node-html-encoder": "0.0.2", | ||
"nunjucks": "~1.0.1", | ||
"q": "~1.0.0", | ||
"glob": "~3.2.8", | ||
"q-io": "~1.10.9", | ||
"nunjucks": "~1.0.1", | ||
"catharsis": "^0.7.0", | ||
"esprima": "^1.0.4", | ||
"change-case": "^2.1.0", | ||
"marked": "~0.2.10", | ||
"node-html-encoder": "0.0.2" | ||
"winston": "~0.7.2" | ||
}, | ||
"peerDependencies": { | ||
"dgeni": "~0.3.0" | ||
}, | ||
"devDependencies": { | ||
"rewire": "~2.0.0", | ||
"jasmine-node": "~2.0.0", | ||
"di": "~0.0.1" | ||
"istanbul": "^0.2.7", | ||
"jasmine-node": "^2.0.0", | ||
"rewire": "~2.0.0" | ||
}, | ||
@@ -65,5 +64,4 @@ "contributors": [ | ||
"Stéphane Reynaud <forresst@voila.fr>", | ||
"Matthew Harris <ftmomatt@gmail.com>", | ||
"Nick Shearer <nick.shearer@software.dell.com>" | ||
"Matthew Harris <ftmomatt@gmail.com>" | ||
] | ||
} |
201
README.md
@@ -18,44 +18,74 @@ # Dgeni Packages | ||
#### `base` Package | ||
## `base` Package | ||
This package contains the following processors: | ||
### Processors | ||
* `read-files` - used to load up documents from files. This processor can be configured to use a | ||
* `debugDumpProcessor` - dump the current state of the docs array to a file (disabled by default) | ||
* `readFilesProcessor` - used to load up documents from files. This processor can be configured to use a | ||
set of **file readers**. There are file readers in the `jsdoc` and `ngdoc` packages. | ||
* `render-docs` - render the documents into a property (`doc.renderedContent`) using a | ||
* `renderDocsProcessor` - render the documents into a property (`doc.renderedContent`) using a | ||
`templateEngine`, which must be provided separately - see `nunjucks` package. | ||
* `templateFinder` - search folders using patterns to find a template that matches a given document. | ||
* `unescape-comments` - unescape comment markers that would break the jsdoc comment style, | ||
* `unescapeCommentsProcessor` - unescape comment markers that would break the jsdoc comment style, | ||
e.g. `*/` | ||
* `write-files` - write the docs to disk | ||
* `writeFilesProcessor` - write the docs to disk | ||
#### `nunjucks` Package | ||
### Services | ||
* `createDocMessage` - a helper for creating nice messages about documents (useful in logging and | ||
errors) | ||
* `encodeDocBlock` - convert a block of code into HTML | ||
* `templateFinder` - search folders using patterns to find a template that matches a given document. | ||
* `trimIndentation` - "intelligently" trim whitespace indentation from the start of each line of a block | ||
of text. | ||
The template used to render the doc is computed by the `templateFinder`, which uses the first match | ||
from a set of patterns in a set of folders, provided in the configuration. This allows a lot of control to provide | ||
generic templates for most situations and specific templates for exceptional cases. | ||
Here is an example of some standard template patterns: | ||
```js | ||
templateFinder.templatePatterns = [ | ||
'${ doc.template }', | ||
'${doc.area}/${ doc.id }.${ doc.docType }.template.html', | ||
'${doc.area}/${ doc.id }.template.html', | ||
'${doc.area}/${ doc.docType }.template.html', | ||
'${ doc.id }.${ doc.docType }.template.html', | ||
'${ doc.id }.template.html', | ||
'${ doc.docType }.template.html' | ||
] | ||
``` | ||
## `nunjucks` Package | ||
This package provides a nunjucks driven implementation of the `templateEngine` required by the | ||
`base` package `render-docs` processor. | ||
`base` package `renderDocsPocessor`. The "nunjucks" JavaScript template tool-kit to generates HTML | ||
based on the data in each document. We have nunjucks templates, tags and filters that | ||
can render links and text as markdown and will highlight code. | ||
### Services | ||
* `nunjucks-template-engine` - provide a `templateEngine` that uses the Nunjucks template library | ||
to render the documents into text, such as HTML or JS, based on templates. | ||
#### `jsdoc` Package | ||
## `jsdoc` Package | ||
This package contains the following file-readers: | ||
### File Readers: | ||
* `jsdoc` - can read documents from jsdoc style comments in source code files. | ||
This package contains the following processors: | ||
### Processors | ||
* `code-name` - infer the name of the document from the code following the document in the source | ||
* `codeNameProcessor` - infer the name of the document from the code following the document in the source | ||
file. | ||
* `compute-path` - infer the path to the document, used for writing the file and for navigation | ||
* `computePathProcessor` - infer the path to the document, used for writing the file and for navigation | ||
to the document in a web app. | ||
* `defaultTagTransforms` - provide a collection of tag transform functions to apply to every tag. | ||
See the transforms in the `tagExtractor` processor. | ||
* `parse-tags` - use a `tagParser` to parses the jsdoc tags in the document content. | ||
* `extract-tags` - use a `tagExtractor` to extract information from the parsed tags. | ||
* `inline-tags` - Search the docs for inline tags that need to have content injected | ||
* `tagDefinitions` - provides a collection of tag definitions, and a map of the same, to be used by | ||
the `tagParser` and `tagExtractor`. | ||
* `tagExtractor` - provides a service to extract tags information and convert it to specific | ||
properties on the document, based on a set of tag-definitions. | ||
* `parseTagsProcessor` - use a `tagParser` to parses the jsdoc tags in the document content. | ||
* `extractTagsProcessor` - use a `tagExtractor` to extract information from the parsed tags. | ||
* `inlineTagsProcessor` - Search the docs for inline tags that need to have content injected | ||
### Tag Definitions | ||
The `jsdoc` package contains definitions for a number of standard jsdoc tags including: `name`, | ||
@@ -65,20 +95,30 @@ `memberof`, `param`, `property`, `returns`, `module`, `description`, `usage`, | ||
`kind`. | ||
* `tagParser` - provides a service to parse the content of a document to get all the jsdoc style | ||
tags out. | ||
### Services | ||
This package provides a number of tagTransform services that are used in tag Definitions to transform | ||
the value of the tag from the string in the comment to something more meaningful in the doc. | ||
* `extractNameTransform` - extract a name from a tag | ||
* `extractTypeTransform` - extract a type from a tag | ||
* `trimWhitespaceTransform` - trim whitespace from before and after the tag value | ||
* `unknownTagTransform` - add an error to the tag if it is unknown | ||
* `wholeTagTransform` - Use the whole tag as the value rather than using a tag property | ||
### Templates | ||
**This package does not provide any templates nor a `templateEngine` to render templates (use the | ||
`nunjucks` package to add this).** | ||
### `ngdoc` Package | ||
## `ngdoc` Package | ||
The `ngdoc` Package also loads up the `jsdoc` and `nunjucks` packages automatically. | ||
The `ngdoc` Package depends upon the `jsdoc` and `nunjucks` packages. | ||
This package contains the following file readers, in addition to those provided by the `jsdocs` | ||
package: | ||
## File Readers | ||
* `ngdoc` - can pull a single document from an ngdoc content file. | ||
On top of the processors provided by the `jsdoc` package, this packages adds the following processors: | ||
### Processors | ||
* `api-docs` - | ||
* `apiDocsProcessor` - | ||
@@ -93,25 +133,43 @@ This processor runs computations that are specifically related to docs for API components. It does the following: | ||
api-docs has the following configuration options available (listed with the default values set): | ||
apiDocsProcessor has the following configuration options available (listed with the default values set): | ||
```js | ||
config.set('processing.api-docs', { | ||
outputPath: '${area}/${module}/${docType}/${name}.html', // The path to write an api document's page to. | ||
path: '${area}/${module}/${docType}/${name}', // The url for a document's page. | ||
moduleOutputPath: '${area}/${name}/index.html', // The path to write an api module's page to. | ||
modulePath: '${area}/${name}' // The url for a module's page. | ||
apiDocsProcessor.apiDocsPath = undefined; // This is a required property that you must set | ||
apiDocsProcessor.outputPathTemplate = '${area}/${module}/${docType}/${name}.html'; | ||
apiDocsProcessor.apiPathTemplate = '${area}/${module}/${docType}/${name}'; | ||
apiDocsProcessor.moduleOutputPathTemplate = '${area}/${name}/index.html'; | ||
apiDocsProcessor.modulePathTemplate = '${area}/${name}'; | ||
}); | ||
``` | ||
* `component-groups-generate` - | ||
* `generateComponentGroupsProcessor` - | ||
Generate documents for each group of components (by type) within a module | ||
* `compute-id` - | ||
* `computeIdProcessor` - | ||
Compute the id property of the doc based on the tags and other meta-data | ||
* `filter-ngdocs` - | ||
* `computePathProcessor` - | ||
Compute the path and outputPath for docs that do not already have them | ||
* `filterNgdocsProcessor` - | ||
For AngularJS we are only interested in documents that contain the @ngdoc tag. This processor | ||
removes docs that do not contain this tag. | ||
* `partial-names` - | ||
* `collectPartialNames` - | ||
Add all the docs to the partialNameMap | ||
This package also provides a set of templates for generating an HTML file for each document: api, | ||
### Services | ||
* `getLinkInfo()` | ||
* `getPartialNames()` | ||
* `gettypeClass()` | ||
* `moduleMap` | ||
* `parseCodeName()` | ||
* `patialNameMap` | ||
### Templates | ||
This package provides a set of templates for generating an HTML file for each document: api, | ||
directive, error, filter function, input, module, object, overview, provider, service, type and a | ||
@@ -123,52 +181,25 @@ number to support rendering of the runnable examples. | ||
`` | ||
templateEngine.config.tags = { | ||
variableStart: '{$', | ||
variableEnd: '$}' | ||
}; | ||
``` | ||
config.merge('rendering.nunjucks.config.tags', { | ||
variableStart: '{$', | ||
variableEnd: '$}' | ||
}); | ||
``` | ||
### `examples` Package | ||
## `examples` Package | ||
This package is a mix in that provides additional processors for working with examples in the docs: | ||
This package is a mix-in that provides functionality for working with examples in the docs. | ||
* `examples-parse` - | ||
Parse the `<example>` tags from the content, generating new docs that will be converted to extra | ||
files that can be loaded by the application and used, for example, in live in-place demos of the | ||
examples and e2e testing. | ||
* `examples-generate` - | ||
### Processors | ||
* `parseExamplesProcessor` - | ||
Parse the `<example>` tags from the content and add them to the `examples` service | ||
* `generateExamplesProcessor` - | ||
Add new docs to the docs collection for each example in the `examples` service that will be rendered | ||
as files that can be run in the browser, for example as live in-place demos of the examples or for | ||
e2e testing. | ||
### Services | ||
## HTML Rendering | ||
* examples - a hash map holding each example by name | ||
We render each of these documents as an HTML page. We use the "nunjucks" JavaScript template | ||
tool-kit to generate HTML based on the data in each document. We have nunjucks tags and filters that | ||
can render links and text as markdown and will highlight code. | ||
The template used to render the doc is computed by a `templateFinder`, which uses the first match | ||
from a set of patterns in a set of folders, provided in the configuration. This allows a lot of control to provide | ||
generic templates for most situations and specific templates for exceptional cases. | ||
Here is an example of the angularjs patterns: | ||
``` | ||
rendering: { | ||
... | ||
templatePatterns: [ | ||
'${ doc.template }', | ||
'${ doc.id }.${ doc.docType }.template.html', | ||
'${ doc.id }.template.html', | ||
'${ doc.docType }.template.html' | ||
], | ||
... | ||
templateFolders: [ | ||
'templates' | ||
] | ||
}, | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
239757
208
4843
202
16
1
+ Addedes6-shim@^0.11.1
+ Addedminimatch@^0.3.0
+ Addedansi-regex@5.0.1(transitive)
+ Addedansi-styles@4.3.0(transitive)
+ Addedasync@2.6.4(transitive)
+ Addedcliui@7.0.4(transitive)
+ Addedclonedeep@2.0.0(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedcolors@1.0.3(transitive)
+ Addeddependency-graph@0.7.2(transitive)
+ Addeddgeni@0.4.14(transitive)
+ Addedemoji-regex@8.0.0(transitive)
+ Addedes6-shim@0.11.1(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedget-caller-file@2.0.5(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedisstream@0.1.2(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedobjectdiff@1.1.0(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedstring-width@4.2.3(transitive)
+ Addedstrip-ansi@6.0.1(transitive)
+ Addedvalidate.js@0.12.0(transitive)
+ Addedwinston@2.4.7(transitive)
+ Addedwrap-ansi@7.0.0(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyargs@16.2.0(transitive)
+ Addedyargs-parser@20.2.9(transitive)
- Removeddependency-graph@0.1.0(transitive)
- Removeddgeni@0.3.0(transitive)
- Removedrimraf@2.2.8(transitive)
- Removedunderscore@1.4.4(transitive)
Updateddgeni@^0.4.0