--- /dev/null
+"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) + ";");
+ });
+}