Socket
Socket
Sign inDemoInstall

dgeni-packages

Package Overview
Dependencies
Maintainers
1
Versions
147
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dgeni-packages - npm Package Compare versions

Comparing version 0.9.6 to 0.10.0-beta.1

base/processors/debugDumpProcessor.js

50

base/index.js

@@ -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 /&amp;#42;.

*/
module.exports = {
name: 'unescape-comments',
runAfter: ['docs-rendered'],
runBefore: ['writing-files'],
process: function(docs) {
_.forEach(docs, function(doc) {
doc.renderedContent = doc.renderedContent.replace(/\/&amp;#42;/g, '/*').replace(/&amp;#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(/\/&amp;#42;/g, '/*').replace(/&amp;#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>"
]
}

@@ -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'
]
},
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc