/** * 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.writeProtractorTest = writeProtractorTest; exports.merge = merge; exports.checkBrokenLinks = checkBrokenLinks; exports.Doc = Doc; exports.ngVersions = function() { var versions = [], regex = /^v([1-9]\d*(?:\.\d+\S+)+)$/; //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]); } }); //match the future version of AngularJS that is set in the package.json file return expandVersions(sortVersionsNaturally(versions), exports.ngCurrentVersion().full); function expandVersions(versions, latestVersion) { var RC_VERSION = /rc\d/; //copy the array to avoid changing the versions param data //the latest version is not on the git tags list, but //docs.angularjs.org will always point to master as of 1.2 versions = versions.concat([latestVersion]); var firstUnstable, expanded = []; for(var i=versions.length-1;i>=0;i--) { var version = versions[i], split = version.split('.'), isMaster = version == latestVersion, isStable = split[1] % 2 === 0 && !RC_VERSION.test(version); var title = 'AngularJS - v' + version; var docsPath = version < '1.0.2' ? 'docs-' + version : 'docs'; var url = isMaster ? 'http://docs.angularjs.org' : 'http://code.angularjs.org/' + version + '/' + docsPath; expanded.push({ version : version, stable : isStable, title : title, group : (isStable ? 'Stable' : 'Unstable'), url : url }); }; return expanded; }; function sortVersionsNaturally(versions) { var versionMap = {}, NON_RC_RELEASE_NUMBER = 999; for(var i = versions.length - 1; i >= 0; i--) { var version = versions[i]; var split = version.split(/\.|rc/); var baseVersion = split[0] + '.' + split[1] + '.' + split[2]; //create a map of RC versions for each version //this way each RC version can be sorted in "natural" order versionMap[baseVersion] = versionMap[baseVersion] || []; //NON_RC_RELEASE_NUMBER is used to signal the non-RC version for the release and //it will always appear at the top of the list since the number is so high! versionMap[baseVersion].push( version == baseVersion ? NON_RC_RELEASE_NUMBER : parseInt(version.match(/rc\.?(\d+)/)[1])); }; //flatten the map so that the RC versions occur in a natural sorted order //and the official non-RC version shows up at the top of the list of sorted //RC versions! var angularVersions = []; sortedKeys(versionMap).forEach(function(key) { var versions = versionMap[key]; //basic numerical sort versions.sort(function(a,b) { return a - b; }); versions.forEach(function(v) { angularVersions.push(v == NON_RC_RELEASE_NUMBER ? key : key + 'rc' + v); }); }); return angularVersions; }; function sortedKeys(obj) { var keys = []; for(var key in obj) { keys.push(key); }; keys.sort(true); return keys; }; }; 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.protractorTests = this.protractorTests || []; this.requires = this.requires || []; this.param = this.param || []; this.properties = this.properties || []; this.methods = this.methods || []; this.events = this.events || []; this.links = this.links || []; this.anchors = this.anchors || []; } 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 || ''); }); if (this.ngdoc === 'error') { words.push(this.getMinerrNamespace()); words.push(this.getMinerrCode()); } words.sort(); return words.join(' '); }, shortDescription : function() { if (!this.description) return this.description; var text = this.description.split("\n")[0]; text = text.replace(/<.+?\/?>/g, ''); text = text.replace(/{/g,'{'); text = text.replace(/}/g,'}'); return text; }, 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) { var hashIdx = url.indexOf('#'); // Lowercase hash parts of the links, // so that we can keep correct API names even when the urls are lowercased. if (hashIdx !== -1) { url = url.substr(0, hashIdx) + url.substr(hashIdx).toLowerCase(); } 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(/(