gulp-structify
Version:
Generates WebGL-compatible Structs and StructBuffers from a template file.
541 lines • 25.4 kB
JavaScript
"use strict";
var through2 = require("through2");
var ts_type_info_1 = require("ts-type-info");
function transformFile(file, template) {
// destructure src file
var className = template.name;
var interfaceName = className + "Like";
var decapClassName = decapitalize(className);
var properties = normalizeProperties(template.properties);
var documentation = template.documentationComment;
var arrayType = template.extendsTypes[0].typeArguments[0].text;
// Warn that file is autogenerated
file.onBeforeWrite = function (writer) { return writer.write("// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n"); };
// Restore function bodies
file.functions.filter(function (f) { return f.tsNode; }).forEach(function (func) {
var node = func.tsNode;
func.onWriteFunctionBody = function (writer) {
for (var _i = 0, _a = node.body.statements; _i < _a.length; _i++) {
var statement = _a[_i];
writer.newLine().write(statement.getText());
}
};
});
// Add interface with properties copied from template
file.addInterface({
name: interfaceName,
isExported: true,
documentationComment: documentation,
properties: properties
});
processImports();
processTemplate();
processStruct();
processStructBuffer();
function processImports() {
// Remove unnecessary imports
file.imports.forEach(function (imprt, index, imports) {
switch (imprt.moduleSpecifier) {
case "gulp-structify/like":
imports.splice(index, 1);
break;
}
});
// Add struct, buf, and mixin imports
file.addImport({
moduleSpecifier: 'gulp-structify/struct',
namedImports: [{ name: 'Struct' }]
});
file.addImport({
moduleSpecifier: 'gulp-structify/buffer',
namedImports: [{ name: 'StructBuffer' }]
});
file.addImport({
moduleSpecifier: 'gulp-structify/mixin',
namedImports: [{ name: 'applyMixins' }]
});
}
function processTemplate() {
// Remove template super class
template.extendsTypes.length = 0;
// Make sure template is exported
template.isExported = true;
// Process template methods
template.methods.filter(function (m) { return m.tsNode; }).forEach(function (method) {
var node = method.tsNode;
var params = method.parameters;
// Restore method bodies
method.onWriteFunctionBody = function (writer) {
for (var _i = 0, _a = node.body.statements; _i < _a.length; _i++) {
var statement = _a[_i];
writer.newLine().write(statement.getText());
}
};
// Process @like annotations
params.forEach(function (param) {
param.decorators.forEach(function (decorator, index, decorators) {
if (decorator.name === "like") {
decorators.splice(index, 1); // Remove decorator
if (param.type.text === className) {
param.type.text = interfaceName; // Replace class type with interface type
}
}
});
});
});
// Add constructor
template.setConstructor({
documentationComment: documentation,
parameters: properties.map(function (prop) {
return {
name: prop.name, type: prop.type, defaultExpression: "0"
};
}),
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
var prop = properties_1[_i];
writer.newLine().write("this." + prop.name + " = " + prop.name + ";");
}
}
});
// create function parameters
var other = "other";
var e = "e";
var k = "k";
var p_other = { name: other, type: interfaceName };
var p_k = { name: k, type: 'number' };
var p_e = { name: e, type: 'number' };
// Generate automatic template functions
template.addMethod({
name: 'set',
parameters: [p_other],
returnType: "void",
documentationComment: "Sets each component of this " + className + " to that of the other " + className + ".",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_2 = properties; _i < properties_2.length; _i++) {
var prop = properties_2[_i];
writer.newLine().write("this." + prop.name + " = " + other + "." + prop.name + ";");
}
}
});
template.addMethod({
name: 'set$',
parameters: properties,
returnType: "void",
documentationComment: "Sets each component of this " + className + ".",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_3 = properties; _i < properties_3.length; _i++) {
var prop = properties_3[_i];
writer.newLine().write("this." + prop.name + " = " + prop.name + ";");
}
}
});
template.addMethod({
name: 'setScalar',
parameters: [p_k],
returnType: "void",
documentationComment: "Sets each component of this " + className + " to the specified scalar.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_4 = properties; _i < properties_4.length; _i++) {
var prop = properties_4[_i];
writer.newLine().write("this." + prop.name + " = " + k + ";");
}
}
});
template.addMethod({
name: 'add',
parameters: [p_other],
returnType: "void",
documentationComment: "Adds the other " + className + " to this " + className + " componentwise.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_5 = properties; _i < properties_5.length; _i++) {
var prop = properties_5[_i];
writer.newLine().write("this." + prop.name + " += " + other + "." + prop.name + ";");
}
}
});
template.addMethod({
name: 'add$',
parameters: properties,
returnType: "void",
documentationComment: "Adds the specified values to this " + className + " componentwise.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_6 = properties; _i < properties_6.length; _i++) {
var prop = properties_6[_i];
writer.newLine().write("this." + prop.name + " += " + prop.name + ";");
}
}
});
template.addMethod({
name: 'subtract',
parameters: [p_other],
returnType: "void",
documentationComment: "Subtracts the other " + className + " from this " + className + " componentwise.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_7 = properties; _i < properties_7.length; _i++) {
var prop = properties_7[_i];
writer.newLine().write("this." + prop.name + " -= " + other + "." + prop.name + ";");
}
}
});
template.addMethod({
name: 'subtract$',
parameters: properties,
returnType: "void",
documentationComment: "Subtracts the specified values from this " + className + " componentwise.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_8 = properties; _i < properties_8.length; _i++) {
var prop = properties_8[_i];
writer.newLine().write("this." + prop.name + " -= " + prop.name + ";");
}
}
});
template.addMethod({
name: 'mulScalar',
parameters: [p_k],
returnType: "void",
documentationComment: "Multiplies each component of this " + className + " by the specified scalar.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_9 = properties; _i < properties_9.length; _i++) {
var prop = properties_9[_i];
writer.newLine().write("this." + prop.name + " *= " + k + ";");
}
}
});
template.addMethod({
name: 'divScalar',
parameters: [p_k],
returnType: "void",
documentationComment: "Divides each component of this " + className + " by the specified scalar.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_10 = properties; _i < properties_10.length; _i++) {
var prop = properties_10[_i];
writer.newLine().write("this." + prop.name + " /= " + k + ";");
}
}
});
template.addMethod({
name: 'equals',
parameters: [p_other],
returnType: "boolean",
documentationComment: "Checks if each component of this " + className + " is exactly equal to that of the other " + className + ".",
onWriteFunctionBody: function (writer) {
var statements = properties.map(function (p) {
return "this." + p.name + " === " + other + "." + p.name;
});
writer.write("return " + statements.join(" && ") + ";");
}
});
template.addMethod({
name: 'equalsScalar',
parameters: [p_k],
returnType: "boolean",
documentationComment: "Checks if each component of this " + className + " is exactly equal to the specified scalar.",
onWriteFunctionBody: function (writer) {
var statements = properties.map(function (prop) {
return "this." + prop.name + " === " + k;
});
writer.write("return " + statements.join(" && ") + ";");
}
});
template.addMethod({
name: 'epsilonEquals',
parameters: [p_other, p_e],
returnType: "boolean",
documentationComment: "Checks if each component of this " + className + " is approximately equal to that of the other " + className + ".",
onWriteFunctionBody: function (writer) {
var statements = properties.map(function (prop) {
return "Math.abs(this." + prop.name + " - " + other + "." + prop.name + ") <= " + e;
});
writer.write("return " + statements.join(" && ") + ";");
}
});
template.addMethod({
name: 'epsilonEqualsScalar',
parameters: [p_k, p_e],
returnType: "boolean",
documentationComment: "Checks if each component of this " + className + " is approximately equal to the specified scalar.",
onWriteFunctionBody: function (writer) {
var statements = properties.map(function (prop) {
return "Math.abs(this." + prop.name + " - " + k + ") <= " + e;
});
writer.write("return " + statements.join(" && ") + ";");
}
});
template.addMethod({
name: 'toString',
documentationComment: "Returns a string representation of this " + className + ".",
returnType: "string",
onWriteFunctionBody: function (writer) {
var expressions = properties.map(function (prop) {
return prop.name + ": ${this." + prop.name + "}";
});
writer.write("return `{ " + expressions.join(", ") + " }`");
}
});
addStaticMethodsToClass(template);
}
function processStruct() {
// Add "Struct" class
var struct = file.addClass({
name: className + "Struct",
isExported: true,
extendsTypes: ["Struct<" + arrayType + ">"],
documentationComment: "A " + className + " backed by a " + arrayType + ".",
constructorDef: {
parameters: [{ name: "data", type: arrayType, defaultExpression: "new " + arrayType + "(" + properties.length + ")" }],
documentationComment: "Creates a " + className + " struct backed by the specified data.",
onWriteFunctionBody: function (writer) {
writer.newLine().write("super(data);");
}
}
});
struct.onAfterWrite = function (writer) {
writer.newLine().write("applyMixins(" + struct.name + ", " + className + ");");
};
// Add accessors
properties.forEach(function (p, i) {
struct.addMethod({
name: "get " + p.name,
documentationComment: p.documentationComment,
onWriteFunctionBody: function (writer) {
writer.write("return this.data[" + i + "];");
}
});
struct.addMethod({
name: "set " + p.name,
parameters: [{ name: "value", type: "number" }],
documentationComment: p.documentationComment,
onWriteFunctionBody: function (writer) {
writer.write("this.data[" + i + "] = value;");
}
});
});
addStaticMethodsToClass(struct);
addMethodPropertiesToClass(struct);
}
function processStructBuffer() {
// Add struct buffer class
var buffer = file.addClass({
isExported: true,
name: className + "Buffer",
documentationComment: "A " + className + " buffer backed by a " + arrayType + ".",
extendsTypes: ["StructBuffer<" + arrayType + ">"],
});
buffer.onAfterWrite = function (writer) {
writer.newLine().write("applyMixins(" + buffer.name + ", " + className + ");");
};
// Add accessors
properties.forEach(function (prop, i) {
var comment = prop.documentationComment.replace("this " + className, "the current " + className);
buffer.addMethod({
name: "get " + prop.name,
documentationComment: comment,
onWriteFunctionBody: function (writer) {
writer.write("return this.data[this.dataPosition + " + i + "];");
}
});
buffer.addMethod({
name: "set " + prop.name,
parameters: [{ name: "value", type: "number" }],
documentationComment: comment,
onWriteFunctionBody: function (writer) {
writer.write("this.data[this.dataPosition + " + i + "] = value;");
}
});
});
// Add methods to class
var p_position = { name: "position", type: "number" };
var p_src = { name: "src", type: interfaceName };
var p_dst = { name: "dst", type: interfaceName, isOptional: true };
buffer.addMethod({
name: "structLength",
documentationComment: "Gets the number of properties in a " + className + ", namely " + properties.length + ".",
onWriteFunctionBody: function (writer) {
writer.write("return " + properties.length + ";");
}
});
buffer.addMethod({
name: "aget",
parameters: [p_position, p_dst],
documentationComment: "Gets the components of the " + className + " at the specified position of this buffer.",
onWriteFunctionBody: function (writer) {
writer.newLine().write("if (" + p_dst.name + " === void 0){ " + p_dst.name + " = new " + className + "()};");
writer.newLine().write("let dataPos = position * this.structLength();");
for (var _i = 0, properties_11 = properties; _i < properties_11.length; _i++) {
var prop = properties_11[_i];
writer.newLine().write(p_dst.name + "." + prop.name + " = this.data[dataPos++];");
}
writer.newLine().write("return " + p_dst.name + ";");
}
});
buffer.addMethod({
name: "rget",
parameters: [p_dst],
documentationComment: "Gets the components of the current " + className + ", then moves to the next position of this buffer.",
onWriteFunctionBody: function (writer) {
writer.newLine().write("if (" + p_dst.name + " === void 0){ " + p_dst.name + " = new " + className + "()};");
for (var _i = 0, properties_12 = properties; _i < properties_12.length; _i++) {
var prop = properties_12[_i];
writer.newLine().write(p_dst.name + "." + prop.name + " = this.data[this.dataPosition++];");
}
writer.newLine().write("return " + p_dst.name + ";");
}
});
buffer.addMethod({
name: "aset",
parameters: [p_position, p_src],
documentationComment: "Sets each component of the " + className + " at the specified position to that of the src " + className + ".",
onWriteFunctionBody: function (writer) {
writer.newLine().write("let dataPos = position * this.structLength();");
for (var _i = 0, properties_13 = properties; _i < properties_13.length; _i++) {
var prop = properties_13[_i];
writer.newLine().write("this.data[dataPos++] = " + p_src.name + "." + prop.name + ";");
}
}
});
buffer.addMethod({
name: "aset$",
parameters: [p_position].concat(properties),
documentationComment: "Sets each component of the " + className + " at the specified position.",
onWriteFunctionBody: function (writer) {
writer.newLine().write("let dataPos = position * this.structLength();");
for (var _i = 0, properties_14 = properties; _i < properties_14.length; _i++) {
var prop = properties_14[_i];
writer.newLine().write("this.data[dataPos++] = " + prop.name + ";");
}
}
});
buffer.addMethod({
name: "rset",
parameters: [p_src],
documentationComment: "Sets each component of the current " + className + " to that of the src " + className + ", then moves to the next position of this buffer.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_15 = properties; _i < properties_15.length; _i++) {
var prop = properties_15[_i];
writer.newLine().write("this.data[this.dataPosition++] = " + p_src.name + "." + prop.name + ";");
}
}
});
buffer.addMethod({
name: "rset$",
parameters: properties,
documentationComment: "Sets each component of the current " + className + ", then moves to the next position of this buffer.",
onWriteFunctionBody: function (writer) {
for (var _i = 0, properties_16 = properties; _i < properties_16.length; _i++) {
var prop = properties_16[_i];
writer.newLine().write("this.data[this.dataPosition++] = " + prop.name + ";");
}
}
});
buffer.addStaticMethod({
name: "create",
parameters: [{ name: "capacity", type: "number" }],
documentationComment: "Creates an empty " + className + " buffer with the specified " + className + " capacity.",
onWriteFunctionBody: function (writer) {
writer.write("return new " + buffer.name + "(new " + arrayType + "(capacity * " + properties.length + "));");
},
});
addMethodPropertiesToClass(buffer);
}
function addMethodPropertiesToClass(cls) {
template.methods.forEach(function (method) {
cls.addProperty({
name: method.name,
documentationComment: method.documentationComment,
type: getMethodType(method)
});
});
}
function addStaticMethodsToClass(cls) {
template.methods
.filter(function (method) {
var name = method.name;
return name.indexOf("set") === 0 &&
name.indexOf(" ") === -1 &&
name !== "setScalar" &&
(name !== "set$" || cls !== template); // not set$ method and template class
})
.forEach(function (setter) {
var parameters = normalizeMethodParams(setter.parameters);
var args = parameters.map(function (p) { return p.name; });
var name = setter.name.substring("set".length);
if (name === "" || name === "$") {
name = "create" + name;
}
cls.addStaticMethod({
name: decapitalize(name),
parameters: parameters,
onWriteFunctionBody: function (writer) {
writer.newLine().write("let " + decapClassName + " = new " + cls.name + "();");
writer.newLine().write(decapClassName + "." + setter.name + "(" + args.join(", ") + ");");
writer.newLine().write("return " + decapClassName + ";");
}
});
});
}
return file;
}
function normalizeProperties(properties) {
return properties.map(function (p) {
return {
name: p.name, type: p.type.text, documentationComment: p.documentationComment
};
});
}
// Helper functions:
function normalizeMethodParams(params) {
return params.map(function (param) {
return {
name: param.name,
type: param.type.node ? param.type.node.text : param.type.text,
isOptional: param.isOptional,
isRestParameter: param.isRestParameter,
defaultExpression: param.defaultExpression ? param.defaultExpression.text : null,
};
});
}
function getMethodType(method) {
var paramsToString = method.parameters.map(function (param) {
var name = param.name;
var type = param.type.text;
var isOptional = (param.isOptional || param.defaultExpression) ? "?" : "";
return "" + name + isOptional + ": " + type;
}).join(", ");
return "(" + paramsToString + ") => " + method.returnType.text;
}
function decapitalize(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
module.exports = function structify() {
return through2.obj(function (file, encoding, callback) {
var info = ts_type_info_1.getInfoFromFiles([file.path], { includeTsNodes: true });
var tsFile = info.getFile(function (f) { return f.fileName.indexOf(file.relative) !== -1; });
// If file is valid .ts
if (tsFile) {
var templateImport_1;
tsFile.imports.forEach(function (imprt, index, imports) {
switch (imprt.moduleSpecifier) {
case "gulp-structify/template":
templateImport_1 = imprt.namedImports[0];
imports.splice(index, 1);
break;
}
});
// If file contains Template import
if (templateImport_1) {
var searchText_1 = (templateImport_1.alias || templateImport_1.name) + "<";
var templateSubclass = tsFile.getClass(function (cls) {
return cls.extendsTypes.some(function (extnds) { return extnds.text.indexOf(searchText_1) !== -1; });
});
// If file contains Template subclass
if (templateSubclass) {
transformFile(tsFile, templateSubclass);
file.contents = new Buffer(tsFile.write());
}
}
callback(null, file);
}
});
};
//# sourceMappingURL=main.js.map