Merge pull request #2479 from ahocevar/generate-externs

Node task to generate externs for OpenLayers 3
This commit is contained in:
Andreas Hocevar
2014-08-11 18:29:04 +02:00
7 changed files with 369 additions and 40 deletions

View File

@@ -13,19 +13,33 @@ var path = require('path');
* @param {Object} opts Options.
*/
exports.publish = function(data, opts) {
var cwd = process.cwd();
// get all doclets with the "api" property or define (excluding enums,
// typedefs and events)
function getTypes(data) {
var types = [];
data.forEach(function(name) {
types.push(name.replace(/^function$/, 'Function'));
});
return types;
}
// get all doclets with the "api" property or define (excluding events) or
// with olx namespace
var docs = data(
[{define: {isObject: true}}, {api: {isString: true}}],
{isEnum: {'!is': true}},
{kind: {'!is': 'typedef'}},
[
{define: {isObject: true}},
{api: {isString: true}},
function() {
return this.meta && (/[\\\/]externs$/).test(this.meta.path);
}
],
{kind: {'!is': 'file'}},
{kind: {'!is': 'event'}}).get();
// get symbols data, filter out those that are members of private classes
var symbols = [];
var defines = [];
var typedefs = [];
var externs = [];
docs.filter(function(doc) {
var include = true;
var constructor = doc.memberof;
@@ -36,24 +50,84 @@ exports.publish = function(data, opts) {
}
return include;
}).forEach(function(doc) {
if (doc.define) {
var isExterns = (/[\\\/]externs$/).test(doc.meta.path);
if (isExterns && doc.longname.indexOf('olx.') === 0) {
if (doc.kind == 'typedef') {
typedefs.push({
name: doc.longname,
types: ['{}']
});
} else {
var typedef = typedefs[typedefs.length - 1];
var type = typedef.types[0];
typedef.types[0] = type
.replace(/\}$/, ', ' + doc.longname.split('#')[1] +
': (' + getTypes(doc.type.names).join('|') + ')}')
.replace('{, ', '{');
}
} else if (doc.define) {
defines.push({
name: doc.longname,
description: doc.description,
path: path.join(doc.meta.path, doc.meta.filename),
default: doc.define.default
});
} else if (doc.kind == 'typedef' || doc.isEnum === true) {
typedefs.push({
name: doc.longname,
types: getTypes(doc.type.names)
});
} else {
symbols.push({
var types;
var symbol = {
name: doc.longname,
kind: doc.kind,
description: doc.classdesc || doc.description,
path: path.join(doc.meta.path, doc.meta.filename)
});
};
if (doc.type) {
symbol.types = getTypes(doc.type.names);
}
if (doc.params) {
var params = [];
doc.params.forEach(function(param) {
var paramInfo = {
name: param.name
};
params.push(paramInfo);
paramInfo.types = getTypes(param.type.names);
if (typeof param.variable == 'boolean') {
paramInfo.variable = param.variable;
}
if (typeof param.optional == 'boolean') {
paramInfo.optional = param.optional;
}
});
symbol.params = params;
}
if (doc.returns) {
symbol.returns = getTypes(doc.returns[0].type.names);
}
if (doc.tags) {
doc.tags.every(function(tag) {
if (tag.title == 'template') {
symbol.template = tag.value;
return false;
}
return true;
});
}
var target = isExterns ? externs : symbols;
target.push(symbol);
}
});
process.stdout.write(
JSON.stringify({symbols: symbols, defines: defines}, null, 2));
JSON.stringify({
symbols: symbols,
defines: defines,
typedefs: typedefs,
externs: externs
}, null, 2));
};

View File

@@ -52,6 +52,13 @@ GeoJSONCRS.prototype.properties;
var GeoJSONCRSCode = function() {};
/**
* @constructor
*/
var GeoJSONCRSName = function() {};
/**
* @type {string}
* TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 is fixed
@@ -60,13 +67,6 @@ var GeoJSONCRSCode = function() {};
GeoJSONCRSName.prototype.code;
/**
* @constructor
*/
var GeoJSONCRSName = function() {};
/**
* @type {string}
*/

View File

@@ -106,6 +106,20 @@ olx.GeolocationOptions.prototype.projection;
olx.LogoOptions;
/**
* Link url for the logo. Will be followed when the logo is clicked.
* @type {string}
*/
olx.LogoOptions.prototype.href;
/**
* Image src for the logo
* @type {string}
*/
olx.LogoOptions.prototype.src;
/**
* @typedef {{map: (ol.Map|undefined),
* maxLines: (number|undefined),
@@ -1186,6 +1200,7 @@ olx.format.PolylineOptions;
/**
* The factor by which the coordinates values will be scaled.
* Default is `1e5`.
* @type {number}
*/
olx.format.PolylineOptions.prototype.factor;
@@ -2788,7 +2803,7 @@ olx.source.FormatVectorOptions.prototype.format;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.FormatVectorOptions.prototype.logo;
@@ -2830,7 +2845,7 @@ olx.source.GeoJSONOptions.prototype.defaultProjection;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.GeoJSONOptions.prototype.logo;
@@ -2901,7 +2916,7 @@ olx.source.GPXOptions.prototype.doc;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.GPXOptions.prototype.logo;
@@ -2976,7 +2991,7 @@ olx.source.TileImageOptions.prototype.crossOrigin;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.TileImageOptions.prototype.logo;
@@ -3074,7 +3089,7 @@ olx.source.TileVectorOptions.prototype.format;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.TileVectorOptions.prototype.logo;
@@ -3152,7 +3167,7 @@ olx.source.TopoJSONOptions.prototype.defaultProjection;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.TopoJSONOptions.prototype.logo;
@@ -3360,7 +3375,7 @@ olx.source.KMLOptions.prototype.extractStyles;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.KMLOptions.prototype.logo;
@@ -3531,7 +3546,7 @@ olx.source.OSMXMLOptions.prototype.doc;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.OSMXMLOptions.prototype.logo;
@@ -3614,7 +3629,7 @@ olx.source.ImageCanvasOptions.prototype.canvasFunction;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ImageCanvasOptions.prototype.logo;
@@ -3671,7 +3686,7 @@ olx.source.ImageVectorOptions.prototype.attributions;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ImageVectorOptions.prototype.logo;
@@ -3762,7 +3777,7 @@ olx.source.ImageWMSOptions.prototype.serverType;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ImageWMSOptions.prototype.logo;
@@ -3902,7 +3917,7 @@ olx.source.ImageStaticOptions.prototype.imageSize;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ImageStaticOptions.prototype.logo;
@@ -3964,7 +3979,7 @@ olx.source.ServerVectorOptions.prototype.strategy;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ServerVectorOptions.prototype.logo;
@@ -4072,7 +4087,7 @@ olx.source.TileWMSOptions.prototype.hidpi;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.TileWMSOptions.prototype.logo;
@@ -4159,7 +4174,7 @@ olx.source.VectorOptions.prototype.features;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.VectorOptions.prototype.logo;
@@ -4225,7 +4240,7 @@ olx.source.StaticVectorOptions.prototype.format;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.StaticVectorOptions.prototype.logo;
@@ -4311,7 +4326,7 @@ olx.source.WMTSOptions.prototype.crossOrigin;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.WMTSOptions.prototype.logo;
@@ -4451,7 +4466,7 @@ olx.source.XYZOptions.prototype.crossOrigin;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.XYZOptions.prototype.logo;
@@ -4551,7 +4566,7 @@ olx.source.ZoomifyOptions.prototype.crossOrigin;
/**
* Logo.
* @type{string|olx.LogoOptions|undefined}
* @type {string|olx.LogoOptions|undefined}
*/
olx.source.ZoomifyOptions.prototype.logo;

View File

@@ -554,7 +554,7 @@ ol.Map.prototype.disposeInternal = function() {
* be `null`. To stop detection, callback functions can return a truthy
* value.
* @param {S=} opt_this Value to use as `this` when executing `callback`.
* @param {function(this: U, ol.layer.Layer): boolean=} opt_layerFilter Layer
* @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
* filter function, only layers which are visible and for which this
* function returns `true` will be tested for features. By default, all
* visible layers will be tested. Feature overlays will always be tested.

196
tasks/generate-externs.js Normal file
View File

@@ -0,0 +1,196 @@
var fs = require('fs');
var path = require('path');
var async = require('async');
var fse = require('fs-extra');
var nomnom = require('nomnom');
var generateInfo = require('./generate-info');
/**
* Read the symbols from info file.
* @param {funciton(Error, Array.<string>, Array.<Object>)} callback Called
* with the patterns and symbols (or any error).
*/
function getInfo(callback) {
generateInfo(function(err) {
if (err) {
callback(new Error('Trouble generating info: ' + err.message));
return;
}
var typedefs = require('../build/info.json').typedefs;
var symbols = require('../build/info.json').symbols;
var externs = require('../build/info.json').externs;
callback(null, typedefs, symbols, externs);
});
}
/**
* Generate externs code given a list symbols.
* @param {Array.<Object>} typedefs List of typedefs.
* @param {Array.<Object>} symbols List of symbols.
* @param {Array.<Object>} externs List of externs.
* @param {string|undefined} namespace Target object for exported symbols.
* @return {string} Export code.
*/
function generateExterns(typedefs, symbols, externs) {
var lines = [];
var processedSymbols = {};
var constructors = {};
function addNamespaces(name) {
var parts = name.split('.');
parts.pop();
var namespace = [];
parts.forEach(function(part) {
namespace.push(part);
var partialNamespace = namespace.join('.');
if (!(partialNamespace in processedSymbols ||
partialNamespace in constructors)) {
lines.push('/**');
lines.push(' * @type {Object}');
lines.push(' */');
lines.push(nameToJS(partialNamespace) + ';');
lines.push('\n');
}
});
}
function nameToJS(name) {
processedSymbols[name] = true;
if (name.indexOf('.') == -1) {
name = 'var ' + name;
}
return name;
}
function noGoogTypes(typesWithGoog) {
typesWithoutGoog = [];
typesWithGoog.forEach(function(type) {
typesWithoutGoog.push(type.replace(/^goog\..*$/, '*'));
});
return typesWithoutGoog;
}
function processSymbol(symbol) {
addNamespaces(symbol.name.split('#')[0]);
var name = symbol.name;
if (name.indexOf('#') > 0) {
name = symbol.name.replace('#', '.prototype.');
var constructor = symbol.name.split('#')[0];
if (!(constructor in constructors)) {
constructors[constructor] = true;
lines.push('/**');
lines.push(' * @constructor');
lines.push(' */');
lines.push(nameToJS(constructor) + ' = function() {};');
lines.push('\n');
}
}
lines.push('/**');
if (symbol.kind == 'class') {
constructors[name] = true;
lines.push(' * @constructor');
}
if (symbol.types) {
lines.push(' * @type {' + noGoogTypes(symbol.types).join('|') + '}');
}
var args = [];
if (symbol.params) {
symbol.params.forEach(function(param) {
args.push(param.name);
lines.push(' * @param {' +
(param.variable ? '...' : '') +
noGoogTypes(param.types).join('|') +
(param.optional ? '=' : '') +
'} ' + param.name);
});
}
if (symbol.returns) {
lines.push(' * @return {' + noGoogTypes(symbol.returns).join('|') + '}');
}
if (symbol.template) {
lines.push(' * @template ' + symbol.template);
}
lines.push(' */');
if (symbol.kind == 'function' || symbol.kind == 'class') {
lines.push(nameToJS(name) + ' = function(' + args.join(', ') + ') {};');
} else {
lines.push(nameToJS(name) + ';');
}
lines.push('\n');
}
externs.forEach(processSymbol);
typedefs.forEach(function(typedef) {
addNamespaces(typedef.name);
lines.push('/**');
lines.push(' * @typedef {' + noGoogTypes(typedef.types).join('|') + '}');
lines.push(' */');
lines.push(nameToJS(typedef.name) + ';');
lines.push('\n');
});
symbols.forEach(processSymbol);
return lines.join('\n');
}
/**
* Generate the exports code.
*
* @param {function(Error, string)} callback Called with the exports code or any
* error generating it.
*/
function main(callback) {
async.waterfall([
getInfo,
function(typedefs, symbols, externs, done) {
var code, err;
try {
code = generateExterns(typedefs, symbols, externs);
} catch (e) {
err = e;
}
done(err, code);
}
], callback);
}
/**
* If running this module directly, read the config file, call the main
* function, and write the output file.
*/
if (require.main === module) {
var options = nomnom.options({
output: {
position: 0,
required: true,
help: 'Output path for the generated externs file.'
}
}).parse();
async.waterfall([
main,
fse.outputFile.bind(fse, options.output)
], function(err) {
if (err) {
console.error(err.message);
process.exit(1);
} else {
process.exit(0);
}
});
}
/**
* Export main function.
*/
module.exports = main;

View File

@@ -7,6 +7,11 @@ var fse = require('fs-extra');
var walk = require('walk').walk;
var sourceDir = path.join(__dirname, '..', 'src');
var externsDir = path.join(__dirname, '..', 'externs');
var externsPaths = [
path.join(externsDir, 'olx.js'),
path.join(externsDir, 'geojson.js')
];
var infoPath = path.join(__dirname, '..', 'build', 'info.json');
var jsdoc = path.join(__dirname, '..', 'node_modules', '.bin', 'jsdoc');
var jsdocConfig = path.join(
@@ -33,16 +38,44 @@ function getInfoTime(callback) {
}
/**
* Test whether externs/olx.js is newer than the provided date.
* @param {Date} date Modification time of info file.
* @param {function(Error, Date, boolen)} callback Called with any
* error, the mtime of the info file (zero date if it doesn't exist), and
* whether externs/olx.js is newer than that date.
*/
function getNewerExterns(date, callback) {
var newer = false;
var walker = walk(externsDir);
walker.on('file', function(root, stats, next) {
var sourcePath = path.join(root, stats.name);
externsPaths.forEach(function(path) {
if (sourcePath == path && stats.mtime > date) {
newer = true;
}
});
next();
});
walker.on('errors', function() {
callback(new Error('Trouble walking ' + sourceDir));
});
walker.on('end', function() {
callback(null, date, newer);
});
}
/**
* Generate a list of all .js paths in the source directory if any are newer
* than the provided date.
* @param {Date} date Modification time of info file.
* @param {boolean} newer Whether externs/olx.js is newer than date.
* @param {function(Error, Array.<string>)} callback Called with any
* error and the array of source paths (empty if none newer).
*/
function getNewer(date, callback) {
var paths = [];
var newer = false;
function getNewer(date, newer, callback) {
var paths = [].concat(externsPaths);
var walker = walk(sourceDir);
walker.on('file', function(root, stats, next) {
@@ -219,6 +252,7 @@ function writeInfo(info, callback) {
function main(callback) {
async.waterfall([
getInfoTime,
getNewerExterns,
getNewer,
spawnJSDoc,
addSymbolProvides,

View File

@@ -89,6 +89,16 @@ The `defines` section of `build.json` above lists common settings for the Closur
Called internally to generate a `build/exports.js` file optionally with a limited set of exports.
## `generate-externs.js`
Can be called to generate a Closure externs file for the full OpenLayers 3 API.
See the `--help` option for more detail.
node tasks/generate-externs.js --help
This is useful for projects that use the Closure Compiler to build, but want to use OpenLayers 3 as external library rather than building together with OpenLayers 3.
## `generate-info.js`
Called internally to parse the library for annotations and write out a `build/info.json` file.