require.paths.push("./lib"); require.paths.push(__dirname); var fs = require('fs'), spawn = require('child_process').spawn, mustache = require('mustache'), callback = require('callback'), Showdown = require('showdown').Showdown; var documentation = { section:{}, all:[] }; var SRC_DIR = "docs/"; var OUTPUT_DIR = "build/docs/"; var work = callback.chain(function () { console.log('Parsing Angular Reference Documentation'); mkdirPath(OUTPUT_DIR, work.waitFor(function(){ findJsFiles('src', work.waitMany(function(file) { //console.log('reading', file, '...'); findNgDoc(file, work.waitMany(function(doc) { parseNgDoc(doc); if (doc.ngdoc) { delete doc.raw.text; var section = documentation.section; (section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc); documentation.all.push(doc); console.log('Found:', doc.ngdoc + ':' + doc.shortName); mergeTemplate( doc.ngdoc + '.template', doc.name + '.html', doc, work.waitFor()); } })); })); })); }).onError(function(err){ console.log('ERROR:', err.stack || err); }).onDone(function(){ mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain()); mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain()); copy('docs-scenario.html', callback.chain()); copy('index.html', callback.chain()); mergeTemplate('docs.js', 'docs.js', documentation, callback.chain()); mergeTemplate('wiki_widgets.css', 'wiki_widgets.css', documentation, callback.chain()); mergeTemplate('wiki_widgets.js', 'wiki_widgets.js', documentation, callback.chain()); console.log('DONE'); }); if (!this.testmode) work(); //////////////////// function noop(){} function mkdirPath(path, callback) { var parts = path.split(/\//); path = '.'; (function next(){ if (parts.length) { path += '/' + parts.shift(); fs.mkdir(path, 0777, next); } else { callback(); } })(); } function copy(name, callback){ fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){ if (err) return this.error(err); fs.writeFile(OUTPUT_DIR + name, content, callback); })); } function mergeTemplate(template, output, doc, callback){ fs.readFile(SRC_DIR + template, callback.waitFor(function(err, template){ if (err) return this.error(err); var content = mustache.to_html(template.toString(), doc); fs.writeFile(OUTPUT_DIR + output, content, callback); })); } function trim(string) { return string.replace(/^[\s\n\r]+/g, '').replace(/[\s\n\r]+$/g, ''); } function unknownTag(doc, name) { var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name; console.log(error); throw new Error(error); } function valueTag(doc, name, value) { doc[name] = value; } function escapedHtmlTag(doc, name, value) { doc[name] = value.replace(/&/g, '&').replace(//g, '>'); } function markdownTag(doc, name, value) { doc[name] = markdown(value.replace(/^#/gm, '##')); } function markdown(text) { text = text.replace(//gm, '<angular/>'); return new Showdown.converter().makeHtml(text); } var TAG = { ngdoc: valueTag, example: escapedHtmlTag, scenario: valueTag, namespace: valueTag, css: valueTag, see: valueTag, usageContent: valueTag, 'function': valueTag, description: markdownTag, TODO: markdownTag, returns: markdownTag, paramDescription: markdownTag, exampleDescription: markdownTag, name: function(doc, name, value) { doc.name = value; doc.shortName = value.split(/\./).pop(); }, param: function(doc, name, value){ doc.param = doc.param || []; doc.paramRest = doc.paramRest || []; var match = value.match(/^({([^\s=]+)(=)?}\s*)?(([^\s=]+)|\[(\S+)+=([^\]]+)\])\s+(.*)/); if (match) { var param = { type: match[2], name: match[6] || match[5], 'default':match[7], description:value.replace(match[0], match[8]) }; doc.param.push(param); if (!doc.paramFirst) { doc.paramFirst = param; } else { doc.paramRest.push(param); } } else { throw "[" + doc.raw.file + ":" + doc.raw.line + "]: @param must be in format '{type} name=value description' got: " + value; } } }; function parseNgDoc(doc){ var atName; var atText; var match; doc.raw.text.split(/\n/).forEach(function(line, lineNumber){ if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) { // we found @name ... // if we have existing name if (atName) { (TAG[atName] || unknownTag)(doc, atName, trim(atText.join('\n'))); } atName = match[1]; atText = []; if(match[3]) atText.push(match[3]); } else { if (atName) { atText.push(line); } else { // ignore } } }); if (atName) { (TAG[atName] || unknownTag)(doc, atName, atText.join('\n')); } } function findNgDoc(file, callback) { fs.readFile(file, callback.waitFor(function(err, content){ var lines = content.toString().split(/\n\r?/); var doc; var match; var inDoc = false; lines.forEach(function(line, lineNumber){ lineNumber++; // is the comment starting? if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) { line = match[1]; inDoc = true; doc = {raw:{file:file, line:lineNumber, text:[]}}; } // are we done? if (inDoc && line.match(/\*\//)) { doc.raw.text = doc.raw.text.join('\n'); doc.raw.text = doc.raw.text.replace(/^\n/, ''); if (doc.raw.text.match(/@ngdoc/)){ callback(doc); } doc = null; inDoc = false; } // is the comment add text if (inDoc){ doc.raw.text.push(line.replace(/^\s*\*\s?/, '')); } }); callback.done(); })); } function findJsFiles(dir, callback){ fs.readdir(dir, callback.waitFor(function(err, files){ if (err) return this.error(err); files.forEach(function(file){ var path = dir + '/' + file; fs.lstat(path, callback.waitFor(function(err, stat){ if (err) return this.error(err); if (stat.isDirectory()) findJsFiles(path, callback.waitMany(callback)); else if (/\.js$/.test(path)) callback(path); })); }); callback.done(); })); }