Files
elasticsearch-js/scripts/generate/js_api.js
Spencer Alger b36a6590e8 Stopped overriding the ping method provided by the 1.0 API, but it didn't match the "castExists" regex so...
Fixed the lack of castExists for ping/1.0 by removing castExists all together from the API. It was the
case anyway that all HEAD requests needed to be cast, so now the clientAction module will set that
param when the spec is set to make HEAD requests. The transport.request still expects that parameter.

Switched the apiVersion implementation to use funcEnum, and exposed the options on the Client constructor.
Docs to come.
2014-01-16 15:43:33 -07:00

303 lines
8.5 KiB
JavaScript

module.exports = function (branch, done) {
/**
* Read the API actions form the rest-api-spec repo.
* @type {[type]}
*/
var _ = require('../../src/lib/utils');
var fs = require('relative-fs').relativeTo(__dirname);
var async = require('async');
var chalk = require('chalk');
var path = require('path');
var templates = require('./templates');
var usesBulkBodyRE = /^(bulk|msearch)$/;
var urlParamRE = /\{(\w+)\}/g;
var files; // populated in readSpecFiles
var apiSpec; // populated by parseSpecFiles
var docVars; // slightly modified clone of apiSpec for the docs
var branchSuffix = branch === 'master' ? '' : '_' + _.snakeCase(branch);
var aliases = require('./aliases' + branchSuffix);
// generate the API
async.series([
readSpecFiles,
parseSpecFiles,
writeApiFile,
ensureDocsDir,
formatDocVars,
writeMethodDocs
], function (err) {
console.log('');
done(err);
});
function readSpecFiles(done) {
var apiDir = require('path').join(__dirname, '../../src/elasticsearch/rest-api-spec/api/');
files = fs.readdirSync(apiDir).map(function (filename) {
var module = require(apiDir + filename);
delete require.cache[apiDir + filename];
return module;
});
done();
}
function parseSpecFiles(done) {
var actions = [];
files.forEach(function (spec) {
__puke__transformSpec(spec).forEach(function (action) {
actions.push(action);
});
});
// collect the namespaces from the action locations
var namespaces = _.filter(_.map(actions, function (action) {
if (~action.location.indexOf('.')) {
var path = action.location.split('.').slice(0, -1);
_.pull(path, 'prototype');
return path.join('.');
}
}));
// seperate the proxy actions
var groups = _.groupBy(actions, function (action) {
return action.proxy ? 'proxies' : 'normal';
});
apiSpec = {
actions: groups.normal,
proxies: groups.proxies,
namespaces: _.unique(namespaces.sort(), true)
};
done();
}
function writeApiFile(done) {
var outputPath = require('path').join(__dirname, '../../src/lib/api' + branchSuffix + '.js');
fs.writeFileSync(outputPath, templates.apiFile(apiSpec));
console.log(chalk.white.bold('wrote'), apiSpec.actions.length, 'api actions to', outputPath);
done();
}
function ensureDocsDir(done) {
fs.stat('../../docs', function (err, stat) {
if (err) {
if (err.message.match(/enoent/i)) {
fs.mkdir('../../docs', done);
} else {
done(err);
}
} else if (stat.isDirectory()) {
done();
} else {
done(new Error('../../docs exists, but it is not a directory'));
}
});
}
function formatDocVars(done) {
// merge the actions and proxies to make
// itteration easir and keep them in order
docVars = _.omit(apiSpec, 'proxies');
docVars.actions = _.sortBy(
[].concat(apiSpec.actions).concat(apiSpec.proxies),
'name'
);
docVars.branch = branch;
docVars.branchSuffix = branchSuffix.replace(/_/g, '-');
done();
}
function writeMethodDocs(done) {
var filename = path.resolve(__dirname, '../../docs/api_methods' + branchSuffix + '.asciidoc');
fs.writeFile(
filename,
templates.apiMethods(docVars),
function (err) {
if (!err) {
console.log(chalk.white.bold('wrote'), branch + ' method docs to', filename);
}
done(err);
}
);
}
function __puke__transformSpec(spec) {
var actions = [];
// itterate all of the specs within the file, should only be one
_.each(spec, function (def, name) {
//camelcase the name
name = _.map(name.split('.'), _.camelCase).join('.');
if (name === 'cat.aliases') {
def.documentation = 'http://www.elasticsearch.org/guide/en/elasticsearch/reference/master/cat.html';
}
var steps = name.split('.');
function transformParamKeys(note, param, key) {
var cmlKey = _.camelCase(key);
if (cmlKey !== key) {
param.name = key;
}
note[cmlKey] = param;
}
def.url.params = _.transform(def.url.params, transformParamKeys, {});
def.url.parts = _.transform(def.url.parts, transformParamKeys, {});
var allParams = _.extend({}, def.url.params, def.url.parts);
var spec = {
name: name,
methods: _.map(def.methods, function (m) { return m.toUpperCase(); }),
params: def.url.params,
body: def.body || null,
path2lib: _.repeat('../', steps.length + 1) + 'lib/'
};
if (def.body && def.body.requires) {
spec.needBody = true;
}
if (usesBulkBodyRE.test(name)) {
spec.bulkBody = true;
}
var urls = _.difference(def.url.paths, aliases[name]);
var urlSignatures = [];
urls = _.map(urls, function (url) {
var optionalVars = {};
var requiredVars = {};
var param;
var name;
var target;
var match;
if (url.charAt(0) !== '/') {
url = '/' + url;
}
while (match = urlParamRE.exec(url)) {
name = _.camelCase(match[1]);
param = def.url.parts[name] || {};
target = (param.required || !param.default) ? requiredVars : optionalVars;
target[name] = _.omit(param, 'required', 'description', 'name');
}
urlSignatures.push(_.union(_.keys(optionalVars), _.keys(requiredVars)).sort().join(':'));
return _.omit({
fmt: url.replace(urlParamRE, function (full, match) {
return '<%=' + _.camelCase(match) + '%>';
}),
opt: _.size(optionalVars) ? optionalVars : null,
req: _.size(requiredVars) ? requiredVars : null,
sortOrder: _.size(requiredVars) * -1
}, function (v) {
return !v;
});
});
if (urlSignatures.length !== _.unique(urlSignatures).length) {
throw new Error('Multiple URLS with the same signature detected for ' + spec.name + '\n' + _.pluck(urls, 'fmt').join('\n') + '\n');
}
if (urls.length > 1) {
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
return _.omit(url, 'sortOrder');
});
} else {
spec.url = urls[0];
}
spec.params = _.transform(spec.params, function (note, param, name) {
// param.name = name;
note[name] = _.pick(param, [
'type', 'default', 'options', 'required', 'name'
]);
}, {});
if (_.size(spec.params) === 0) {
delete spec.params;
}
// escape method names with "special" keywords
var location = spec.name.split('.').join('.prototype.')
.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
var action = {
_methods: spec.methods,
spec: _.pick(spec, [
'params',
'url',
'urls',
'needBody',
'bulkBody'
]),
location: location,
docUrl: def.documentation,
name: spec.name,
allParams: allParams
};
function hasMethod(/* ...methods */) {
for (var i = 0; i < arguments.length; i++) {
if (~action._methods.indexOf(arguments[i])) {
continue;
} else {
return false;
}
}
return true;
}
function methodsAre(/* ...methods */) {
return hasMethod.apply(null, arguments) && arguments.length === action._methods.length;
}
var method;
if (action._methods.length === 1) {
method = action._methods[0];
} else {
// we need to define what the default method(s) will be
if (hasMethod('DELETE', 'POST')) {
method = 'POST';
}
else if (methodsAre('DELETE')) {
method = 'DELETE';
}
else if (methodsAre('POST', 'PUT')) {
method = action.name.match(/put/i) ? 'PUT' : 'POST';
}
else if (methodsAre('GET', 'POST')) {
method = 'POST';
}
else if (methodsAre('GET', 'HEAD')) {
method = 'GET';
}
}
if (method) {
if (method !== 'GET') {
action.spec.method = method;
}
} else {
throw new Error('unable to pick a method for ' + JSON.stringify(action, null, ' '));
}
if (action.name === 'create') {
action.proxy = 'index';
action.transformBody = 'params.op_type = \'create\';';
}
actions.push(action);
});
return actions;
}
};