/** * All parsing/transformation code goes here. All code here should be sync to ease testing. */ var DOM = require('./dom.js').DOM; var htmlEscape = require('./dom.js').htmlEscape; var Example = require('./example.js').Example; var NEW_LINE = /\n\r?/; var globalID = 0; var fs = require('fs'); var fspath = require('path'); var shell = require('shelljs'); var gruntUtil = require('../../lib/grunt/utils.js'); var errorsJson; var marked = require('marked'); marked.setOptions({ gfm: true, tables: true }); var lookupMinerrMsg = function (doc) { var code, namespace; if (errorsJson === undefined) { errorsJson = require('../../build/errors.json').errors; } namespace = doc.getMinerrNamespace(); code = doc.getMinerrCode(); if (namespace === undefined) { return errorsJson[code]; } return errorsJson[namespace][code]; }; exports.trim = trim; exports.metadata = metadata; exports.scenarios = scenarios; exports.merge = merge; exports.Doc = Doc; exports.ngVersions = function() { var line, versions = [], regex = /^v([1-9]\d*(?:\.\d+)+)$/; //only fetch >= 1.0.0 versions shell.exec('git tag', {silent: true}).output.split(/\s*\n\s*/) .forEach(function(line) { var matches = regex.exec(line); if(matches && matches.length > 0) { versions.push(matches[1]); } }); versions.push(exports.ngCurrentVersion().number); return versions; }; exports.ngCurrentVersion = function() { return gruntUtil.getVersion(); }; var BOOLEAN_ATTR = {}; ['multiple', 'selected', 'checked', 'disabled', 'readOnly', 'required'].forEach(function(value) { BOOLEAN_ATTR[value] = true; }); ////////////////////////////////////////////////////////// function Doc(text, file, line) { if (typeof text == 'object') { for ( var key in text) { this[key] = text[key]; } } else { this.text = text; this.file = file; this.line = line; } this.scenarios = this.scenarios || []; this.requires = this.requires || []; this.param = this.param || []; this.properties = this.properties || []; this.methods = this.methods || []; this.events = this.events || []; this.links = this.links || []; } Doc.METADATA_IGNORE = (function() { var words = fs.readFileSync(__dirname + '/ignore.words', 'utf8'); return words.toString().split(/[,\s\n\r]+/gm); })(); Doc.prototype = { keywords: function keywords() { var keywords = {}; var words = []; Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; }); function extractWords(text) { var tokens = text.toLowerCase().split(/[\.\s,`'"#]+/mg); tokens.forEach(function(key){ var match = key.match(/^((ng:|[\$_a-z])[\w\-_]+)/); if (match){ key = match[1]; if (!keywords[key]) { keywords[key] = true; words.push(key); } } }); } extractWords(this.text); this.properties.forEach(function(prop) { extractWords(prop.text || prop.description || ''); }); this.methods.forEach(function(method) { extractWords(method.text || method.description || ''); }); words.sort(); return words.join(' '); }, getMinerrNamespace: function () { if (this.ngdoc !== 'error') { throw new Error('Tried to get the minErr namespace, but @ngdoc ' + this.ngdoc + ' was supplied. It should be @ngdoc error'); } return this.name.split(':')[0]; }, getMinerrCode: function () { if (this.ngdoc !== 'error') { throw new Error('Tried to get the minErr error code, but @ngdoc ' + this.ngdoc + ' was supplied. It should be @ngdoc error'); } return this.name.split(':')[1]; }, /** * Converts relative urls (without section) into absolute * Absolute url means url with section * * @example * - if the link is inside any api doc: * angular.widget -> api/angular.widget * * - if the link is inside any guid doc: * intro -> guide/intro * * @param {string} url Absolute or relative url * @returns {string} Absolute url */ convertUrlToAbsolute: function(url) { if (url.substr(-1) == '/') return url + 'index'; if (url.match(/\//)) return url; return this.section + '/' + url; }, markdown: function(text) { if (!text) return text; var self = this, IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/, IS_ANGULAR = /^(api\/)?(angular|ng|AUTO)\./, IS_HASH = /^#/, parts = trim(text).split(/([\s\S]*?<\/pre>|[\s\S]*?<\/doc:example>|]*>[\s\S]*?<\/example>)/), seq = 0, placeholderMap = {}; function placeholder(text) { var id = 'REPLACEME' + (seq++); placeholderMap[id] = text; return id; } function extractInlineDocCode(text, tag) { if(tag == 'all') { //use a greedy operator to match the last tag regex = /\/\/([.\s\S]+)\/\/<\/docs>/im; } else { //use a non-greedy operator to match the next tag regex = new RegExp("\/\/([.\\s\\S]+?)\/\/<\/docs>","im"); } var matches = regex.exec(text.toString()); return matches && matches.length > 1 ? matches[1] : ""; } parts.forEach(function(text, i) { parts[i] = (text || ''). replace(/([\s\S]*?)<\/example>/gmi, function(_, module, deps, animations, content) { var example = new Example(self.scenarios); if(animations) { example.enableAnimations(); } example.setModule(module); example.addDeps(deps); content.replace(/([\s\S]*?)<\/file>/gmi, function(_, name, content) { example.addSource(name, content); }); content.replace(//gmi, function(_, file, tag, name) { if(fs.existsSync(file)) { var content = fs.readFileSync(file, 'utf8'); if(content && content.length > 0) { if(tag && tag.length > 0) { content = extractInlineDocCode(content, tag); } name = name && name.length > 0 ? name : fspath.basename(file); example.addSource(name, content); } } return ''; }) return placeholder(example.toHtml()); }). replace(/(?:\*\s+)?/i, function(_, file, tag) { if(fs.existsSync(file)) { var content = fs.readFileSync(file, 'utf8'); if(tag && tag.length > 0) { content = extractInlineDocCode(content, tag); } return content; } }). replace(/^]*)?>([\s\S]*)<\/doc:example>/mi, function(_, attrs, content) { var html, script, scenario, example = new Example(self.scenarios); example.setModule((attrs||'module=""').match(/^\s*module=["'](.*)["']\s*$/)[1]); content. replace(/]*)?>([\s\S]*)<\/doc:source>/mi, function(_, attrs, content) { example.addSource('index.html', content. replace(/