Skip to content

Instantly share code, notes, and snippets.

@iamdustan
Last active August 31, 2016 19:59
Show Gist options
  • Save iamdustan/2e36a440b5702e14376bfba56d303427 to your computer and use it in GitHub Desktop.
Save iamdustan/2e36a440b5702e14376bfba56d303427 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
/**
* This script will generate a JavaScript program to interact with the IBM
* Watson API. Try it out yourself!
*
* ```
* $ git clone https://gist.github.com/2e36a440b5702e14376bfba56d303427.git watson
* $ cd watson
* $ chmod +x watson.sh
* $ ./watson.sh > output.js
* ```
*/
'use strict';
const Https = require('https');
const j = require('jscodeshift');
Https.request({hostname: 'watson-api-explorer.mybluemix.net', path: '/listings/text-to-speech-v1.json'},
res => {
res.setEncoding('utf8');
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => performMagic(JSON.parse(body)));
})
.end();
/**
* Quick and dirty Object.entries polyfill
*/
const entries = (obj) => {
const result = [];
for (const key in obj) {
result.push([key, obj[key]]);
}
return result;
}
/**
* This is where the aptly named magic begins. This loops through each path in
* the Swagger definition and generates our new program from it and outputs it
* to stdout.
*/
const performMagic = (json) => {
const e = entries(json.paths);
const code = e
.filter((_, i) => i === e.length - 1)
.map(([path, methods]) => {
const code = [];
for (const method in methods) {
code.push(toFn(path, method, methods[method]));
}
return code;
})
.reduce((result, current) => result.concat(current), []);
const program = j.program(code);
program.comments = [j.commentBlock('* @flow ')];
process.stdout.write(j(program).toSource());
};
const capitalize = str => str[0].toUpperCase() + str.slice(1).toLowerCase();
/**
* A simplistic transform of method + path to generate the function name.
* @example:
* genFnName('get', '/v1/synthesize') => 'getV1Synthesize';
* genFnName('delete', '/v1/synthesize') => 'deleteV1Synthesize';
*/
const genFnName = (method, path) =>
path.split(/\/|_/g).filter(Boolean).reduce((str, part) =>
str + capitalize(part),
method.toLowerCase()
)
.replace(/{(\w+)}/g, (match, word) => capitalize(word))
/**
* Generate the type annotation reference for the arguments
**/
const genTypeAnnotation = (param) => {
if (param.type === 'string') {
if (param.enum) {
return j.unionTypeAnnotation(param.enum.map(e => j.stringLiteralTypeAnnotation(e, e)));
}
return j.stringTypeAnnotation();
}
if (param.schema && param.schema.$ref) {
return j.genericTypeAnnotation(j.identifier(param.schema.$ref.replace('#/definitions/', '')), null);
}
// TODO: an exercise to the reader to handle all the types you may have
return j.anyTypeAnnotation();
};
/**
* Generate the function arguments
**/
const genArgs = (params) => !params ? [] : params.map(param => Object.assign(
j.identifier(param.name + ':'), // hack around recast printer
{typeAnnotation: genTypeAnnotation(param)}
));
/**
* Format description assuming single indentation and comment block like what
* you see in this file. This does not respect word boundaries.
*/
const formatDescription = (str) =>
str.match(/.{1,76}/g).reduce((str, part) => str + '\n * ' + part, '*') + '\n *'
/**
* Generate a JS AST from a Swagger entry.
*/
const toFn = (path, method, obj) => {
return Object.assign(
j.exportDeclaration(false, j.functionDeclaration(
j.identifier(genFnName(method, path)),
genArgs(obj.parameters),
j.blockStatement([])
)),
{comments: [j.commentBlock(formatDescription(obj.description))]}
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment