| "use strict"; |
| module.exports = proto_target; |
| |
| proto_target.private = true; |
| |
| var protobuf = require("../.."); |
| |
| var Namespace = protobuf.Namespace, |
| Enum = protobuf.Enum, |
| Type = protobuf.Type, |
| Field = protobuf.Field, |
| OneOf = protobuf.OneOf, |
| Service = protobuf.Service, |
| Method = protobuf.Method, |
| types = protobuf.types, |
| util = protobuf.util; |
| |
| function underScore(str) { |
| return str.substring(0,1) |
| + str.substring(1) |
| .replace(/([A-Z])(?=[a-z]|$)/g, function($0, $1) { return "_" + $1.toLowerCase(); }); |
| } |
| |
| var out = []; |
| var indent = 0; |
| var first = false; |
| var syntax = 3; |
| |
| function proto_target(root, options, callback) { |
| if (options) { |
| switch (options.syntax) { |
| case undefined: |
| case "proto3": |
| case "3": |
| syntax = 3; |
| break; |
| case "proto2": |
| case "2": |
| syntax = 2; |
| break; |
| default: |
| return callback(Error("invalid syntax: " + options.syntax)); |
| } |
| } |
| indent = 0; |
| first = false; |
| try { |
| buildRoot(root); |
| return callback(null, out.join("\n")); |
| } catch (err) { |
| return callback(err); |
| } finally { |
| out = []; |
| syntax = 3; |
| } |
| } |
| |
| function push(line) { |
| if (line === "") |
| out.push(""); |
| else { |
| var ind = ""; |
| for (var i = 0; i < indent; ++i) |
| ind += " "; |
| out.push(ind + line); |
| } |
| } |
| |
| function escape(str) { |
| return str.replace(/[\\"']/g, "\\$&") |
| .replace(/\r/g, "\\r") |
| .replace(/\n/g, "\\n") |
| .replace(/\u0000/g, "\\0"); // eslint-disable-line no-control-regex |
| } |
| |
| function value(v) { |
| switch (typeof v) { |
| case "boolean": |
| return v ? "true" : "false"; |
| case "number": |
| return v.toString(); |
| default: |
| return "\"" + escape(String(v)) + "\""; |
| } |
| } |
| |
| function buildRoot(root) { |
| root.resolveAll(); |
| var pkg = []; |
| var ptr = root; |
| var repeat = true; |
| do { |
| var nested = ptr.nestedArray; |
| if (nested.length === 1 && nested[0] instanceof Namespace && !(nested[0] instanceof Type || nested[0] instanceof Service)) { |
| ptr = nested[0]; |
| if (ptr !== root) |
| pkg.push(ptr.name); |
| } else |
| repeat = false; |
| } while (repeat); |
| out.push("syntax = \"proto" + syntax + "\";"); |
| if (pkg.length) |
| out.push("", "package " + pkg.join(".") + ";"); |
| |
| buildOptions(ptr); |
| ptr.nestedArray.forEach(build); |
| } |
| |
| function build(object) { |
| if (object instanceof Enum) |
| buildEnum(object); |
| else if (object instanceof Type) |
| buildType(object); |
| else if (object instanceof Field) |
| buildField(object); |
| else if (object instanceof OneOf) |
| buildOneOf(object); |
| else if (object instanceof Service) |
| buildService(object); |
| else if (object instanceof Method) |
| buildMethod(object); |
| else |
| buildNamespace(object); |
| } |
| |
| function buildNamespace(namespace) { // just a namespace, not a type etc. |
| push(""); |
| push("message " + namespace.name + " {"); |
| ++indent; |
| buildOptions(namespace); |
| consolidateExtends(namespace.nestedArray).remaining.forEach(build); |
| --indent; |
| push("}"); |
| } |
| |
| function buildEnum(enm) { |
| push(""); |
| push("enum " + enm.name + " {"); |
| buildOptions(enm); |
| ++indent; first = true; |
| Object.keys(enm.values).forEach(function(name) { |
| var val = enm.values[name]; |
| if (first) { |
| push(""); |
| first = false; |
| } |
| push(name + " = " + val + ";"); |
| }); |
| --indent; first = false; |
| push("}"); |
| } |
| |
| function buildRanges(keyword, ranges) { |
| if (ranges && ranges.length) { |
| var parts = []; |
| ranges.forEach(function(range) { |
| if (typeof range === "string") |
| parts.push("\"" + escape(range) + "\""); |
| else if (range[0] === range[1]) |
| parts.push(range[0]); |
| else |
| parts.push(range[0] + " to " + (range[1] === 0x1FFFFFFF ? "max" : range[1])); |
| }); |
| push(""); |
| push(keyword + " " + parts.join(", ") + ";"); |
| } |
| } |
| |
| function buildType(type) { |
| if (type.group) |
| return; // built with the sister-field |
| push(""); |
| push("message " + type.name + " {"); |
| ++indent; |
| buildOptions(type); |
| type.oneofsArray.forEach(build); |
| first = true; |
| type.fieldsArray.forEach(build); |
| consolidateExtends(type.nestedArray).remaining.forEach(build); |
| buildRanges("extensions", type.extensions); |
| buildRanges("reserved", type.reserved); |
| --indent; |
| push("}"); |
| } |
| |
| function buildField(field, passExtend) { |
| if (field.partOf || field.declaringField || field.extend !== undefined && !passExtend) |
| return; |
| if (first) { |
| first = false; |
| push(""); |
| } |
| if (field.resolvedType && field.resolvedType.group) { |
| buildGroup(field); |
| return; |
| } |
| var sb = []; |
| if (field.map) |
| sb.push("map<" + field.keyType + ", " + field.type + ">"); |
| else if (field.repeated) |
| sb.push("repeated", field.type); |
| else if (syntax === 2 || field.parent.group) |
| sb.push(field.required ? "required" : "optional", field.type); |
| else |
| sb.push(field.type); |
| sb.push(underScore(field.name), "=", field.id); |
| var opts = buildFieldOptions(field); |
| if (opts) |
| sb.push(opts); |
| push(sb.join(" ") + ";"); |
| } |
| |
| function buildGroup(field) { |
| push(field.rule + " group " + field.resolvedType.name + " = " + field.id + " {"); |
| ++indent; |
| buildOptions(field.resolvedType); |
| first = true; |
| field.resolvedType.fieldsArray.forEach(function(field) { |
| buildField(field); |
| }); |
| --indent; |
| push("}"); |
| } |
| |
| function buildFieldOptions(field) { |
| var keys; |
| if (!field.options || !(keys = Object.keys(field.options)).length) |
| return null; |
| var sb = []; |
| keys.forEach(function(key) { |
| var val = field.options[key]; |
| var wireType = types.packed[field.resolvedType instanceof Enum ? "int32" : field.type]; |
| switch (key) { |
| case "packed": |
| val = Boolean(val); |
| // skip when not packable or syntax default |
| if (wireType === undefined || syntax === 3 === val) |
| return; |
| break; |
| case "default": |
| if (syntax === 3) |
| return; |
| // skip default (resolved) default values |
| if (field.long && !util.longNeq(field.defaultValue, types.defaults[field.type]) || !field.long && field.defaultValue === types.defaults[field.type]) |
| return; |
| // enum defaults specified as strings are type references and not enclosed in quotes |
| if (field.resolvedType instanceof Enum) |
| break; |
| // otherwise fallthrough |
| default: |
| val = value(val); |
| break; |
| } |
| sb.push(key + "=" + val); |
| }); |
| return sb.length |
| ? "[" + sb.join(", ") + "]" |
| : null; |
| } |
| |
| function consolidateExtends(nested) { |
| var ext = {}; |
| nested = nested.filter(function(obj) { |
| if (!(obj instanceof Field) || obj.extend === undefined) |
| return true; |
| (ext[obj.extend] || (ext[obj.extend] = [])).push(obj); |
| return false; |
| }); |
| Object.keys(ext).forEach(function(extend) { |
| push(""); |
| push("extend " + extend + " {"); |
| ++indent; first = true; |
| ext[extend].forEach(function(field) { |
| buildField(field, true); |
| }); |
| --indent; |
| push("}"); |
| }); |
| return { |
| remaining: nested |
| }; |
| } |
| |
| function buildOneOf(oneof) { |
| push(""); |
| push("oneof " + underScore(oneof.name) + " {"); |
| ++indent; first = true; |
| oneof.oneof.forEach(function(fieldName) { |
| var field = oneof.parent.get(fieldName); |
| if (first) { |
| first = false; |
| push(""); |
| } |
| var opts = buildFieldOptions(field); |
| push(field.type + " " + underScore(field.name) + " = " + field.id + (opts ? " " + opts : "") + ";"); |
| }); |
| --indent; |
| push("}"); |
| } |
| |
| function buildService(service) { |
| push("service " + service.name + " {"); |
| ++indent; |
| service.methodsArray.forEach(build); |
| consolidateExtends(service.nestedArray).remaining.forEach(build); |
| --indent; |
| push("}"); |
| } |
| |
| function buildMethod(method) { |
| push(method.type + " " + method.name + " (" + (method.requestStream ? "stream " : "") + method.requestType + ") returns (" + (method.responseStream ? "stream " : "") + method.responseType + ");"); |
| } |
| |
| function buildOptions(object) { |
| if (!object.options) |
| return; |
| first = true; |
| Object.keys(object.options).forEach(function(key) { |
| if (first) { |
| first = false; |
| push(""); |
| } |
| var val = object.options[key]; |
| push("option " + key + " = " + JSON.stringify(val) + ";"); |
| }); |
| } |