Feuer frei!
commit
27629263c4
|
@ -0,0 +1 @@
|
|||
node_modules/
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const argv = require('optimist')
|
||||
.usage('Usage: $0 <path> ...')
|
||||
.argv;
|
||||
|
||||
const generate = require('..');
|
||||
|
||||
argv._.forEach(run);
|
||||
|
||||
function run(path) {
|
||||
if (path === '-') {
|
||||
path = '/dev/stdin';
|
||||
}
|
||||
|
||||
try {
|
||||
const code = fs.readFileSync(path, 'utf8');
|
||||
const schemes = generate(code);
|
||||
|
||||
console.dir(schemes, {
|
||||
colors: true,
|
||||
depth: Infinity,
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error(ex.message);
|
||||
console.error(ex.stack);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const parse = require('./parser');
|
||||
const visit = require('./visitor');
|
||||
const make = require('./maker');
|
||||
|
||||
function collect(node, schemes) {
|
||||
const scheme = make(node);
|
||||
|
||||
const name = node.id.name;
|
||||
|
||||
assert(!schemes[name]);
|
||||
|
||||
scheme.name = name;
|
||||
|
||||
schemes[name] = scheme;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const visitor = {
|
||||
TypeAlias: collect,
|
||||
InterfaceDeclaration: collect,
|
||||
ClassDeclaration: collect,
|
||||
};
|
||||
|
||||
function generate(code) {
|
||||
const ast = parse(code);
|
||||
|
||||
const schemes = Object.create({});
|
||||
|
||||
visit(ast, visitor, schemes);
|
||||
|
||||
return schemes;
|
||||
}
|
||||
|
||||
module.exports = generate;
|
|
@ -0,0 +1,229 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const {partition, log} = require('./utils');
|
||||
|
||||
function make(node) {
|
||||
if (!handlers[node.type]) {
|
||||
log(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
return handlers[node.type](node);
|
||||
}
|
||||
|
||||
const handlers = {
|
||||
TypeAlias(node) {
|
||||
return make(node.right);
|
||||
},
|
||||
|
||||
InterfaceDeclaration(node) {
|
||||
return make(node.body);
|
||||
},
|
||||
|
||||
ClassDeclaration(node) {
|
||||
return make(node.body);
|
||||
},
|
||||
|
||||
ClassBody(node) {
|
||||
return {
|
||||
type: 'report',
|
||||
fields: node.body.map(make).filter(Boolean),
|
||||
};
|
||||
},
|
||||
|
||||
ClassProperty(node) {
|
||||
if (node.static) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let type = node.leadingComments && getLastPragma(node.leadingComments);
|
||||
|
||||
return {
|
||||
name: make(node.key),
|
||||
type: type || make(node.typeAnnotation),
|
||||
};
|
||||
},
|
||||
|
||||
ClassMethod(node) {
|
||||
return null;
|
||||
},
|
||||
|
||||
ObjectTypeAnnotation(node) {
|
||||
if (node.indexers.length > 0) {
|
||||
// Allow functions, getters and setters.
|
||||
const properties = node.properties.map(make).filter(Boolean);
|
||||
|
||||
assert.equal(properties.length, 0);
|
||||
assert.equal(node.indexers.length, 1);
|
||||
|
||||
return {
|
||||
type: 'map',
|
||||
values: make(node.indexers[0]),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'record',
|
||||
fields: node.properties.map(make).filter(Boolean),
|
||||
};
|
||||
},
|
||||
|
||||
ObjectTypeProperty(node) {
|
||||
let type = null;
|
||||
|
||||
if (node.leadingComments) {
|
||||
type = getLastPragma(node.leadingComments);
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
type = make(node.value);
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: make(node.key),
|
||||
type,
|
||||
};
|
||||
},
|
||||
|
||||
ObjectTypeIndexer(node) {
|
||||
const key = make(node.key);
|
||||
|
||||
assert.equal(key, 'string');
|
||||
|
||||
return make(node.value);
|
||||
},
|
||||
|
||||
TypeAnnotation(node) {
|
||||
return make(node.typeAnnotation);
|
||||
},
|
||||
|
||||
NumberTypeAnnotation(node) {
|
||||
return 'double';
|
||||
},
|
||||
|
||||
StringTypeAnnotation(node) {
|
||||
return 'string';
|
||||
},
|
||||
|
||||
BooleanTypeAnnotation(node) {
|
||||
return 'boolean';
|
||||
},
|
||||
|
||||
ArrayTypeAnnotation(node) {
|
||||
return {
|
||||
type: 'array',
|
||||
items: make(node.elementType),
|
||||
};
|
||||
},
|
||||
|
||||
UnionTypeAnnotation(node) {
|
||||
// TODO: flatten variants.
|
||||
|
||||
let [symbols, variants] = partition(node.types, isEnumSymbol);
|
||||
|
||||
symbols = symbols.map(unwrapEnumSymbol);
|
||||
variants = variants.map(make);
|
||||
|
||||
if (symbols.length > 0) {
|
||||
const enumeration = {
|
||||
type: 'enum',
|
||||
symbols,
|
||||
};
|
||||
|
||||
if (variants.length === 0) {
|
||||
return enumeration;
|
||||
}
|
||||
|
||||
variants.push(enumeration);
|
||||
}
|
||||
|
||||
return variants;
|
||||
},
|
||||
|
||||
NullableTypeAnnotation(node) {
|
||||
return ['null', make(node.typeAnnotation)];
|
||||
},
|
||||
|
||||
NullLiteralTypeAnnotation(node) {
|
||||
return {
|
||||
type: 'null',
|
||||
};
|
||||
},
|
||||
|
||||
StringLiteralTypeAnnotation(node) {
|
||||
return {
|
||||
type: 'enum',
|
||||
symbols: [node.value],
|
||||
};
|
||||
},
|
||||
|
||||
GenericTypeAnnotation(node) {
|
||||
return make(node.id);
|
||||
},
|
||||
|
||||
FunctionTypeAnnotation(node) {
|
||||
return null;
|
||||
},
|
||||
|
||||
Identifier(node) {
|
||||
return node.name;
|
||||
},
|
||||
|
||||
CommentLine(node) {
|
||||
const marker = '$avro ';
|
||||
|
||||
const value = node.value.trimLeft();
|
||||
|
||||
if (value.startsWith(marker)) {
|
||||
const pragma = value.slice(marker.length).trim();
|
||||
|
||||
assert(isValidPragma(pragma));
|
||||
|
||||
return pragma;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
function isValidPragma(pragma) {
|
||||
// TODO: support mixed.
|
||||
|
||||
switch (pragma) {
|
||||
case 'null':
|
||||
case 'int':
|
||||
case 'long':
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'bytes':
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getLastPragma(comments) {
|
||||
const pragmas = comments
|
||||
.map(make)
|
||||
.filter(Boolean);
|
||||
|
||||
return pragmas.length > 0 ? pragmas[pragmas.length - 1] : null;
|
||||
}
|
||||
|
||||
function isEnumSymbol(node) {
|
||||
return node.type === 'StringLiteralTypeAnnotation';
|
||||
}
|
||||
|
||||
function unwrapEnumSymbol(node) {
|
||||
return node.value;
|
||||
}
|
||||
|
||||
module.exports = make;
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const babylon = require('babylon');
|
||||
|
||||
function parse(code) {
|
||||
// This parse configuration is intended to be as permissive as possible.
|
||||
return babylon.parse(code, {
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
sourceType: 'module',
|
||||
plugins: [ '*', 'jsx', 'flow' ],
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parse;
|
|
@ -0,0 +1,19 @@
|
|||
function partition(iter, predicate) {
|
||||
const left = [];
|
||||
const right = [];
|
||||
|
||||
for (const item of iter) {
|
||||
(predicate(item) ? left : right).push(item);
|
||||
}
|
||||
|
||||
return [left, right];
|
||||
}
|
||||
|
||||
function log(arg, depth = 5) {
|
||||
console.dir(arg, {colors: true, depth});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
partition,
|
||||
log,
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
// Given the AST output of babylon parse, walk through in a depth-first order,
|
||||
// calling methods on the given visitor, providing context as the first argument.
|
||||
function visit(ast, visitor, state) {
|
||||
let stack;
|
||||
let parent;
|
||||
let keys = [];
|
||||
let index = -1;
|
||||
|
||||
do {
|
||||
++index;
|
||||
|
||||
if (stack && index === keys.length) {
|
||||
parent = stack.parent;
|
||||
keys = stack.keys;
|
||||
index = stack.index;
|
||||
stack = stack.prev;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = parent ? parent[keys[index]] : ast.program;
|
||||
|
||||
if (node && typeof node === 'object' && (node.type || node.length)) {
|
||||
const {type} = node;
|
||||
|
||||
if (type) {
|
||||
if (type in visitor) {
|
||||
const stop = visitor[type](node, state) === false;
|
||||
|
||||
if (stop) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack = { parent, keys, index, prev: stack };
|
||||
parent = node;
|
||||
keys = Object.keys(node);
|
||||
index = -1;
|
||||
}
|
||||
} while (stack);
|
||||
}
|
||||
|
||||
module.exports = visit;
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "flow2avro",
|
||||
"version": "0.1.0",
|
||||
"description": "Generate avro schemes for flowtype definitions",
|
||||
"author": "Paul Loyd <pavelko95@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/loyd/flow2avro.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/loyd/flow2avro/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"flow",
|
||||
"flowtype",
|
||||
"avro",
|
||||
"avsc"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"babylon": "./bin/babylon.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"babylon": "^6.18.0",
|
||||
"optimist": "^0.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue