Cleaned up the generation script, fixing the doc-blocks above the client actions.

Replaced the transport, giving it all of the functionality that was brought over to the client and making the client simply a place for the API to live. Essentially a shell that can easily be removed.

spec'd out the TransportRequest which will eventually inherit from one of server possible promise implementations and will be plugable. It will also implement the "abort" functionality needed in an environment like node.js
This commit is contained in:
Spencer Alger
2013-10-29 08:48:29 -07:00
parent cb35524096
commit 984a55f6c0
25 changed files with 1128 additions and 1052 deletions

View File

@ -0,0 +1,123 @@
var _ = require('../../../src/lib/utils');
var EventEmitter = require('events').EventEmitter;
var aliases = require('./aliases');
var castExistsRE = /exists/;
var usesBulkBodyRE = /^(bulk|msearch)$/;
var urlParamRE = /\{(\w+)\}/g;
var specCount = 0;
var actions = [];
var doneParsing = false;
require('../../get_spec')
.get('api/*.json')
.on('entry', transformFile)
.on('end', function () {
doneParsing = true;
if (actions.length === specCount) {
module.exports.emit('ready', actions);
}
});
function transformFile(entry) {
specCount++;
// itterate all of the specs within the file, should only be one
_.each(JSON.parse(entry.data), function (def, name) {
var steps = name.split('.');
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;
}
if (castExistsRE.test(name)) {
spec.castExists = true;
}
var urls = _.difference(def.url.paths, aliases[name]);
urls = _.map(urls, function (url, i) {
var optionalVars = {};
var requiredVars = {};
var param;
var target;
var match;
if (url.charAt(0) !== '/') {
url = '/' + url;
}
while (match = urlParamRE.exec(url)) {
param = def.url.parts[match[1]] || {};
target = (param.required || !param.default) ? requiredVars : optionalVars;
target[match[1]] = _.omit(param, 'required');
}
[requiredVars, optionalVars].forEach(function (vars) {
_.each(vars, function (v, name) {
vars[name] = _.omit(v, 'description');
});
});
return _.omit({
fmt: url.replace(urlParamRE, '<%=$1%>'),
opt: _.size(optionalVars) ? optionalVars : null,
req: _.size(requiredVars) ? requiredVars : null,
sortOrder: _.size(requiredVars) * -1
}, function (v) {
return !v;
});
});
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
return _.omit(url, 'sortOrder');
});
spec.params = _.transform(spec.params, function (note, param, name) {
param.name = name;
note[name] = _.pick(param, [
'type', 'default', 'options', 'required'
]);
}, {});
// escape method names with "special" keywords
var location = _.map(spec.name.split('.'), _.camelCase)
.join('.prototype.')
.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
var action = {
spec: _.pick(spec, [
'methods',
'params',
'urls',
'needBody',
'bulkBody',
'castExists',
'castNotFound'
]),
location: location,
docUrl: def.documentation,
name: spec.name,
allParams: allParams
};
if (actions.push(action) === specCount && doneParsing) {
module.exports.emit('ready', action);
}
});
}
module.exports = new EventEmitter();

View File

@ -9,83 +9,18 @@ var urlParamRE = /\{(\w+)\}/g;
var outputPath = _.joinPath(__dirname, '../../../src/lib/api.js');
require('./spec').on('ready', function (specs) {
require('./actions').on('ready', function (actions) {
var defs = [];
var namespaces = [];
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('.');
}
}));
clean(outputPath);
var actions = _.map(specs, function (spec) {
spec.urls = _.map(
_.sortBy(
_.transform(spec.urls, function (note, url, i) {
var optionalVars = {};
var requiredVars = {};
var param;
var target;
var match;
if (url.charAt(0) !== '/') {
url = '/' + url;
}
while (match = urlParamRE.exec(url)) {
param = spec.urlParts[match[1]] || {};
target = (param.required || !param.default) ? requiredVars : optionalVars;
target[match[1]] = _.omit(param, 'required');
}
[requiredVars, optionalVars].forEach(function (vars) {
_.each(vars, function (v, name) {
vars[name] = _.omit(v, 'description');
});
});
note.push(_.omit({
fmt: url.replace(urlParamRE, '<%=$1%>'),
opt: _.size(optionalVars) ? optionalVars : null,
req: _.size(requiredVars) ? requiredVars : null,
sortOrder: _.size(requiredVars) * -1
}, function (v) { return !v; }));
}, [])
, 'sortOrder')
, function (url) {
return _.omit(url, 'sortOrder');
});
var docUrl = spec.docUrl;
var location = _.map(spec.name.split('.'), _.camelCase).join('.');
spec = _.pick(spec, [
'methods',
'params',
'urls',
'needBody',
'bulkBody',
'castNotFound'
]);
spec.params = _.transform(spec.params, function (note, param, name) {
param.name = name;
note[name] = _.pick(param, [
'type', 'default', 'options', 'required'
]);
}, {});
if (~location.indexOf('.')) {
var steps = location.split('.');
namespaces.push(steps.slice(0, -1).join('.'));
location = steps.join('.prototype.');
}
// escape method names with "special" keywords
location = location.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
return {
spec: spec,
location: location,
docUrl: docUrl
};
});
console.log('writing', actions.length, 'api actions to', outputPath);
fs.writeFileSync(outputPath, templates.apiFile({
actions: actions,

View File

@ -1,58 +0,0 @@
var _ = require('../../../src/lib/utils');
var EventEmitter = require('events').EventEmitter;
var aliases = require('./aliases');
var castNotFoundRE = /exists/;
var usesBulkBodyRE = /^(bulk|msearch)$/;
var specCount = 0;
var completedSpecs = [];
var doneParsing = false;
require('../../get_spec')
.get('api/*.json')
.on('entry', transformFile)
.on('end', function () {
doneParsing = true;
if (completedSpecs.length === specCount) {
module.exports.emit('ready', completedSpecs);
}
});
function transformFile(entry) {
specCount++;
var file = entry.data;
// itterate all of the specs within the file, should only be one
_.each(JSON.parse(file), function (def, name) {
var steps = name.split('.');
var spec = {
name: name,
methods: _.map(def.methods, function (m) { return m.toUpperCase(); }),
docUrl: def.documentation,
urlParts: def.url.parts,
params: def.url.params,
urls: _.difference(def.url.paths, aliases[name]),
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;
}
if (castNotFoundRE.test(name)) {
spec.castNotFound = true;
}
if (completedSpecs.push(spec) === specCount && doneParsing) {
module.exports.emit('ready', completedSpecs);
}
});
}
module.exports = new EventEmitter();

View File

@ -1,4 +1,4 @@
var ca = require('./client_action').create;
var ca = require('./client_action');
var errors = require('./errors');
var api = module.exports = {};

View File

@ -1,8 +1,8 @@
/**
* Perform a [<%= spec.name %>](<%= docUrl %>) request
* Perform a [<%= name %>](<%= docUrl %>) request
*
* @param {Object} params - An object with parameters used to carry out this action<%
_.each(spec.params, function(param, paramName) { %>
_.each(allParams, function(param, paramName) { %>
* @param {<%= paramType(param.type) %>} <%= paramWithDefault('params.' + paramName, param.default) %><%
if (param.description) {
%> - <%= param.description %><%

View File

@ -1,8 +1,7 @@
var _ = require('../../../../src/lib/utils')
, fs = require('fs')
, path = require('path')
, urlParamRE = /\{(\w+)\}/g;
var _ = require('../../../../src/lib/utils');
var fs = require('fs');
var path = require('path');
/**
@ -16,7 +15,7 @@ function lines(i) {
if (line === '') {
// no indent on empty lines
l.lines.push('');
} else if (typeof line !== 'undefined') {
} else if (line === void 0) {
l.lines.push(_.repeat(' ', l.indent) + line);
}
return l;
@ -81,152 +80,6 @@ var templates = {};
*/
var templateGlobals = {
writeParams: function (indent, params, namespace) {
var l = lines(indent);
_.each(params, function (param, name) {
if (!param.required) {
l('if (typeof params.' + name + ' !== \'undefined\') {').in();
}
l.split(templates[param.type || 'any']({
get: 'params.' + name,
set: namespace + name,
name: name
}));
if (!param.required) {
l.out();
l('}');
}
l('');
});
return l.toString();
},
writeBrowserParams: function (indent, params, namespace) {
var l = lines(indent);
_.each(params, function (param, name) {
if (!param.required) {
l('if (_.has(params, ' + stringify(name) + ')) {').in();
}
switch (param.type) {
case 'enum':
l(
namespace + name + ' = _.' +
(param.type || 'any') + 'Param(params.' + name + ', ' + stringify(param.options) +
');'
);
break;
default:
l(namespace + name + ' = _.' + (param.type || 'any') + 'Param(params.' + name + ');');
break;
}
if (!param.required) {
l.out('}');
}
l('');
});
return l.toString();
},
writeUrls: function (indent, urls, urlParams, queryStringParams) {
var l = lines(indent);
function urlVarIsRequired(varDetails) {
varDetails = typeof varDetails === 'string' ? urlParams[varDetails] : varDetails;
return varDetails && (varDetails.required || !varDetails.default);
}
// turn a url string into an object describing the url, then sort them in decending order by how many args they have
urls = _.sortBy(urls, function (url) {
var requiredVars = _.filter(_.collectMatches(url, urlParamRE), function (match) {
return urlVarIsRequired(urlParams[match[1]]);
});
return requiredVars ? requiredVars.length * -1 : 0;
});
_.each(urls, function (url, urlIndex) {
// collect the vars from the url and replace them to form the js that will build the url
var makeL = lines(), vars = [];
makeL('request.path = \'' + url.replace(urlParamRE, function (match, varName) {
var varDetails = urlParams[varName];
varDetails.name = varName;
vars.push(varDetails);
if (urlVarIsRequired(varDetails)) {
return '\' + encodeURIComponent(parts.' + varName + ') + \'';
} else {
return '\' + encodeURIComponent(parts.' + varName + ' || ' + stringify(varDetails.default) + ') + \'';
}
}) + '\';');
makeL(_.filter(_.map(vars, function (v, i) {
if (_.has(queryStringParams, v.name)) {
// delete the param so that it's not used later on in the queryString
return 'delete params.' + v.name + ';';
}
})).join(' '));
if (vars.length || urlIndex) {
var requiredVars = _.filter(vars, urlVarIsRequired);
var condition = _.map(requiredVars, function (v) {
return 'parts.' + v.name + ')';
}).join(' && ');
l((urlIndex > 0 ? 'else ' : '') + (condition ? 'if (' + condition + ') ' : '') + '{')
.in()
.split(makeL.toString())
.out('}');
if (urlIndex === urls.length - 1 && condition) {
l('else {')
.in('throw new TypeError(\'Unable to build a path with those params. Supply at least ' +
vars.join(', ') + '\');'
)
.out('}');
}
} else {
l.split(makeL.toString());
}
});
l('');
return l.toString();
},
writeRequestObjectBody: function (indent, name, body, methods) {
var parts = [], l = lines(indent);
if (~name.indexOf('exists')) {
parts.push('ignore: _.union([404], params.ignore)');
} else {
parts.push('ignore: params.ignore');
}
if (body) {
if (_.contains(['bulk', 'msearch'], name)) {
parts.push('body: this.client.config.serializer.bulkBody(params.body || null)');
} else {
parts.push('body: params.body || null');
}
}
if (methods.length === 1) {
parts.push('method: ' + stringify(methods[0]));
}
_.each(parts, function (part, i) {
l(part + (i < parts.length - 1 ? ',' : ''));
});
return l.toString();
},
stringify: stringify,
_: _,
@ -254,22 +107,6 @@ var templateGlobals = {
}
},
returnStatement: function (indent, name) {
var l = lines(indent);
if (name.match(/(^|\.)exists/)) {
l('this.client.request(request, function (err, response) {')
.in('if (err instanceof errors.NotFound) {')
.in('cb(err, false);')
.out('} else {')
.in('cb(err, true);')
.out('}')
.out('});');
} else {
l('this.client.request(request, cb);');
}
return l.toString();
},
partials: templates
};
@ -290,6 +127,5 @@ fs.readdirSync(path.resolve(__dirname)).forEach(function (filename) {
templates.text = templates.string;
module.exports = {
apiFile: templates.api_file,
urlParamRE: urlParamRE
apiFile: templates.api_file
};