EmbeddedProto 4.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. EmbeddedProto/EmbeddedProto.py +122 -0
  2. EmbeddedProto/Features.py +255 -0
  3. EmbeddedProto/Field.py +1295 -0
  4. EmbeddedProto/Oneof.py +74 -0
  5. EmbeddedProto/ProtoFile.py +210 -0
  6. EmbeddedProto/TypeDefinitions.py +367 -0
  7. EmbeddedProto/__init__.py +0 -0
  8. EmbeddedProto/__main__.py +28 -0
  9. EmbeddedProto/config.py +173 -0
  10. EmbeddedProto/custom_header.py +513 -0
  11. EmbeddedProto/embedded_proto_options.proto +62 -0
  12. EmbeddedProto/embedded_proto_options_pb2.py +37 -0
  13. EmbeddedProto/field_options.py +344 -0
  14. EmbeddedProto/main.py +316 -0
  15. EmbeddedProto/src/EmbeddedProto/BytesStringCallback.h +476 -0
  16. EmbeddedProto/src/EmbeddedProto/Defines.h +118 -0
  17. EmbeddedProto/src/EmbeddedProto/EmptyArray.h +81 -0
  18. EmbeddedProto/src/EmbeddedProto/Errors.h +53 -0
  19. EmbeddedProto/src/EmbeddedProto/FieldStringBytes.h +793 -0
  20. EmbeddedProto/src/EmbeddedProto/Fields.h +832 -0
  21. EmbeddedProto/src/EmbeddedProto/Functional.h +412 -0
  22. EmbeddedProto/src/EmbeddedProto/MessageCallback.h +688 -0
  23. EmbeddedProto/src/EmbeddedProto/MessageInterface.h +792 -0
  24. EmbeddedProto/src/EmbeddedProto/MessageSizeCalculator.h +109 -0
  25. EmbeddedProto/src/EmbeddedProto/MessageState.h +207 -0
  26. EmbeddedProto/src/EmbeddedProto/ReadBufferFixedSize.h +204 -0
  27. EmbeddedProto/src/EmbeddedProto/ReadBufferInterface.h +181 -0
  28. EmbeddedProto/src/EmbeddedProto/ReadBufferSection.h +232 -0
  29. EmbeddedProto/src/EmbeddedProto/RepeatedField.h +1053 -0
  30. EmbeddedProto/src/EmbeddedProto/RepeatedFieldCallback.h +432 -0
  31. EmbeddedProto/src/EmbeddedProto/RepeatedFieldFixedSize.h +367 -0
  32. EmbeddedProto/src/EmbeddedProto/Version.h +39 -0
  33. EmbeddedProto/src/EmbeddedProto/WireFormatter.h +852 -0
  34. EmbeddedProto/src/EmbeddedProto/WriteBufferFixedSize.h +114 -0
  35. EmbeddedProto/src/EmbeddedProto/WriteBufferInterface.h +134 -0
  36. EmbeddedProto/src/EmbeddedProto.h +55 -0
  37. EmbeddedProto/templates/FieldBasic_Deserialize.h.jinja2 +26 -0
  38. EmbeddedProto/templates/FieldBasic_GetSet.h.jinja2 +89 -0
  39. EmbeddedProto/templates/FieldBytes_GetSet.h.jinja2 +86 -0
  40. EmbeddedProto/templates/FieldEnum_Deserialize.h.jinja2 +50 -0
  41. EmbeddedProto/templates/FieldEnum_GetSet.h.jinja2 +93 -0
  42. EmbeddedProto/templates/FieldErrorRecursive_GetSet.h.jinja2 +29 -0
  43. EmbeddedProto/templates/FieldMap_GetSet.h.jinja2 +203 -0
  44. EmbeddedProto/templates/FieldMsg_Deserialize.h.jinja2 +44 -0
  45. EmbeddedProto/templates/FieldMsg_GetSet.h.jinja2 +93 -0
  46. EmbeddedProto/templates/FieldRepeated_GetSet.h.jinja2 +94 -0
  47. EmbeddedProto/templates/FieldString_GetSet.h.jinja2 +86 -0
  48. EmbeddedProto/templates/Field_DeserializePartial.h.jinja2 +33 -0
  49. EmbeddedProto/templates/Field_Serialize.h.jinja2 +112 -0
  50. EmbeddedProto/templates/Field_SerializePartial.h.jinja2 +119 -0
  51. EmbeddedProto/templates/Header.h.jinja2 +108 -0
  52. EmbeddedProto/templates/TypeDefEnum.h.jinja2 +45 -0
  53. EmbeddedProto/templates/TypeDefMsg.h.jinja2 +791 -0
  54. EmbeddedProto/templates/TypeOneof.h.jinja2 +178 -0
  55. EmbeddedProto/version.json +3 -0
  56. embeddedproto-4.0.0b1.dist-info/METADATA +141 -0
  57. embeddedproto-4.0.0b1.dist-info/RECORD +63 -0
  58. embeddedproto-4.0.0b1.dist-info/WHEEL +5 -0
  59. embeddedproto-4.0.0b1.dist-info/entry_points.txt +3 -0
  60. embeddedproto-4.0.0b1.dist-info/licenses/LICENSE +30 -0
  61. embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/GPL-3.0-only.txt +674 -0
  62. embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/LicenseRef-EmbeddedProto-Commercial.txt +12 -0
  63. embeddedproto-4.0.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,62 @@
1
+ syntax = "proto3";
2
+
3
+ import "google/protobuf/descriptor.proto";
4
+
5
+ package EmbeddedProto;
6
+
7
+ /**
8
+ * Options for Embedded Proto fields.
9
+ *
10
+ * maxLength: Used to set the maximum length for repeated fields, strings, and bytes.
11
+ * For repeated fields, this sets the array size.
12
+ * For strings and bytes, this sets the maximum character/byte length.
13
+ *
14
+ * nestedMaxLength: Used specifically for repeated string/bytes fields to set the
15
+ * maximum length of the string/bytes elements within the array.
16
+ * This allows setting both the array size (via maxLength) and
17
+ * the individual element size (via nestedMaxLength).
18
+ *
19
+ * customStorage: When set the user supplies the storage type for this field via a
20
+ * template parameter. Applies to repeated, string, bytes and message
21
+ * fields. The supplied type must derive from the matching base class.
22
+ *
23
+ * callbackStorage: When set the field streams its data through user callbacks instead
24
+ * of storing them resident. See field-level documentation below.
25
+ */
26
+ message Options {
27
+ uint32 maxLength = 1; // Maximum length for repeated fields, strings, or bytes
28
+ uint32 nestedMaxLength = 2; // Only used for string/bytes fields in a repeated array. The
29
+ // option is used to set the length of the string/byte while
30
+ // maxLength is used to set the length of the repeated array.
31
+ bool customStorage = 3; // When set the user supplies the storage type for this field
32
+ // via a template parameter. Applies to repeated, string, bytes
33
+ // and message fields. The supplied type must derive from the
34
+ // matching base class (RepeatedField, BaseStringBytes or
35
+ // MessageInterface). Takes precedence over maxLength and
36
+ // generates no size template parameter.
37
+ bool callbackStorage = 4; // When set the field streams its data through user callbacks
38
+ // instead of storing them resident. Supported field types:
39
+ // - repeated scalar/enum: emits ::EmbeddedProto::RepeatedFieldCallback<T>,
40
+ // serialized EXPANDED (one tag per element)
41
+ // - singular string/bytes: emits ::EmbeddedProto::BytesStringCallback<T>,
42
+ // streamed in windows staged in memory bound at run time with
43
+ // set_<field>_window(), so the window size is a platform choice
44
+ // and not part of the schema
45
+ // - repeated message: emits ::EmbeddedProto::MessageCallback<T>,
46
+ // requires DELIMITED (group) framing; rejected if LENGTH_DELIMITED
47
+ // - map: emits ::EmbeddedProto::MessageCallback<Entry>, entries stay
48
+ // length delimited so DELIMITED is not required. The map keeps no
49
+ // entries, so no key lookup accessors are generated.
50
+ // Rejected on: oneof members, singular scalar/enum/message,
51
+ // explicit-presence singular scalars, repeated string/bytes.
52
+ uint32 keyMaxLength = 5; // Only used for map fields with a string or bytes key. Sets the
53
+ // length of the key, while maxLength sets the number of entries
54
+ // the map can hold.
55
+ uint32 valueMaxLength = 6; // Only used for map fields with a string or bytes value. Sets the
56
+ // length of the value, while maxLength sets the number of entries
57
+ // the map can hold.
58
+ }
59
+
60
+ extend google.protobuf.FieldOptions {
61
+ Options options = 1141; // Custom options for Embedded Proto fields
62
+ }
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: embedded_proto_options.proto
5
+ # Protobuf Python Version: 7.35.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 7,
15
+ 35,
16
+ 1,
17
+ '',
18
+ 'embedded_proto_options.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x65mbedded_proto_options.proto\x12\rEmbeddedProto\x1a google/protobuf/descriptor.proto\"\x93\x01\n\x07Options\x12\x11\n\tmaxLength\x18\x01 \x01(\r\x12\x17\n\x0fnestedMaxLength\x18\x02 \x01(\r\x12\x15\n\rcustomStorage\x18\x03 \x01(\x08\x12\x17\n\x0f\x63\x61llbackStorage\x18\x04 \x01(\x08\x12\x14\n\x0ckeyMaxLength\x18\x05 \x01(\r\x12\x16\n\x0evalueMaxLength\x18\x06 \x01(\r:G\n\x07options\x12\x1d.google.protobuf.FieldOptions\x18\xf5\x08 \x01(\x0b\x32\x16.EmbeddedProto.Optionsb\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'embedded_proto_options_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ DESCRIPTOR._loaded_options = None
35
+ _globals['_OPTIONS']._serialized_start=82
36
+ _globals['_OPTIONS']._serialized_end=229
37
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,344 @@
1
+ #
2
+ # Copyright (C) 2020-2026 Embedded AMS B.V. - All Rights Reserved
3
+ #
4
+ # This file is part of Embedded Proto.
5
+ #
6
+ # Embedded Proto is dual licensed. You may use it under the terms of the
7
+ # GNU General Public License version 3 (GPLv3) as published by the Free
8
+ # Software Foundation, or under a commercial license from Embedded AMS B.V.
9
+ #
10
+ # Under the GPLv3 you must release the source code of
11
+ # any product you distribute that includes Embedded Proto or code
12
+ # generated by it. A commercial license removes that obligation.
13
+ # See <https://embeddedproto.com/pricing/>.
14
+ #
15
+ # Embedded Proto is distributed WITHOUT ANY WARRANTY; without even the
16
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ # See the LICENSE file in the root of the repository for details.
18
+ #
19
+ # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-EmbeddedProto-Commercial
20
+ #
21
+ # Embedded AMS B.V., Hoorn, the Netherlands - info at EmbeddedProto dot com
22
+ #
23
+
24
+ """Read the external field options file(s).
25
+
26
+ Embedded Proto's per field options (``maxLength``, ``nestedMaxLength``,
27
+ ``customStorage`` and ``callbackStorage``) are normally written inline in the
28
+ ``.proto``. That file is a shared, cross language contract though, while these
29
+ options are specific to one target. This module reads the same options from an
30
+ external JSON file instead, so a field can be sized without touching a schema
31
+ you do not own.
32
+
33
+ The file is keyed by scope, mirroring the proto: package parts, message names,
34
+ nested message names and finally the field name. A scope may also be written as a
35
+ dotted path, ``"foo.telemetry"`` says the same as ``"foo": {"telemetry": ...}``.
36
+ Options live only in the object belonging to a field, nothing is inherited::
37
+
38
+ {
39
+ "SensorPackage": {
40
+ "SensorFrame": {
41
+ "samples": { "maxLength": 128 }
42
+ }
43
+ }
44
+ }
45
+
46
+ Because the file is keyed by package and message it has no relation to the path
47
+ of any ``.proto``. One file can hold the options for every schema in a build and
48
+ may live anywhere, which is what makes configuring a third party schema possible.
49
+
50
+ JSON keeps the whole thing in the standard library, like ``configparser`` does
51
+ for ``config.py``. It also guarantees that keys are strings; in YAML the bare
52
+ keys ``on``, ``no`` and ``off`` are booleans, which would collide with the legal
53
+ field names of the same spelling.
54
+ """
55
+
56
+ import json
57
+ import os
58
+ import re
59
+ import sys
60
+
61
+ from EmbeddedProto import embedded_proto_options_pb2
62
+
63
+ # The options that may appear in an options file and the JSON type each accepts.
64
+ # These are the fields of the Options message in embedded_proto_options.proto.
65
+ UNSIGNED = "unsigned"
66
+ BOOLEAN = "boolean"
67
+ OPTION_TYPES = {"maxLength": UNSIGNED,
68
+ "nestedMaxLength": UNSIGNED,
69
+ "customStorage": BOOLEAN,
70
+ "callbackStorage": BOOLEAN,
71
+ "keyMaxLength": UNSIGNED,
72
+ "valueMaxLength": UNSIGNED}
73
+
74
+ # Generator wide settings live under one reserved top level key. A proto identifier can not start with a dollar
75
+ # sign, so the key can never collide with a package name.
76
+ SETTINGS_KEY = "$EmbeddedProtoSetting"
77
+ DEFAULT_HEADER_EXTENSION = ".h"
78
+ SETTING_TYPES = {"headerExtension": "extension"}
79
+ EXTENSION_PATTERN = re.compile(r"^\.[A-Za-z0-9_.]+$")
80
+
81
+
82
+ def warn(message):
83
+ """Write a warning to stderr. Protoc passes plugin stderr on to the user."""
84
+ sys.stderr.write("EmbeddedProto options file: " + message + "\n")
85
+
86
+
87
+ # Format one scope path for use in an error message.
88
+ def path_str(path):
89
+ return ".".join(path) if path else "<root>"
90
+
91
+
92
+ # Check the value of a single option and return it. Booleans are a subclass of int in Python, so an unsigned option
93
+ # must reject them explicitly or "maxLength": true would silently size a buffer to one.
94
+ def check_option_value(name, value, path, source):
95
+ location = source + ": " + path_str(path) + "." + name + ": "
96
+ if UNSIGNED == OPTION_TYPES[name]:
97
+ if isinstance(value, bool) or not isinstance(value, int):
98
+ raise Exception(location + "expected a whole number, got " + json.dumps(value) + ".")
99
+ if 0 > value:
100
+ raise Exception(location + "expected a positive number, got " + json.dumps(value) + ".")
101
+ elif not isinstance(value, bool):
102
+ raise Exception(location + "expected true or false, got " + json.dumps(value) + ".")
103
+ return value
104
+
105
+
106
+ # Walk one parsed file and check everything that can be checked without the proto definitions: an object holds
107
+ # either options or names, never both, and every option value has the right type. Whether a name exists in the schema,
108
+ # and whether an option sits on a field rather than on a message, needs the descriptors and is checked in
109
+ # validate_against().
110
+ def check_structure(node, path, source):
111
+ if not isinstance(node, dict):
112
+ raise Exception(source + ": " + path_str(path) + ": expected an object, got "
113
+ + json.dumps(node) + ".")
114
+
115
+ # The value tells an option from a name: an option is a number or a flag, a message or field is an object. Going
116
+ # by the value rather than by the key is what lets a field be named after an option, "maxLength" included.
117
+ name_keys = [key for key, value in node.items() if isinstance(value, dict)]
118
+ option_keys = [key for key in node if key not in name_keys]
119
+
120
+ # An object either describes one field, holding its options, or it is a scope holding messages and fields.
121
+ if name_keys and option_keys:
122
+ raise Exception(source + ": " + path_str(path) + ": mixes the option(s) "
123
+ + ", ".join(sorted(option_keys)) + " with the name(s) "
124
+ + ", ".join(sorted(name_keys)) + ". Options belong to a field, "
125
+ "a message or package holds no options of its own.")
126
+
127
+ for name in option_keys:
128
+ if name not in OPTION_TYPES:
129
+ raise Exception(source + ": " + path_str(path) + ": unknown option " + name + ". Known options are "
130
+ + ", ".join(sorted(OPTION_TYPES)) + ".")
131
+ check_option_value(name, node[name], path, source)
132
+
133
+ for name in name_keys:
134
+ check_structure(node[name], path + [name], source)
135
+
136
+
137
+ # Check the settings object of one file and return it. Settings are plain values under a reserved key, they never
138
+ # mix with the scope tree.
139
+ def check_settings(node, source):
140
+ location = source + ": " + SETTINGS_KEY + ": "
141
+ if not isinstance(node, dict):
142
+ raise Exception(location + "expected an object, got " + json.dumps(node) + ".")
143
+ for name, value in node.items():
144
+ if name not in SETTING_TYPES:
145
+ raise Exception(location + "unknown setting " + name + ". Known settings are "
146
+ + ", ".join(sorted(SETTING_TYPES)) + ".")
147
+ if not isinstance(value, str) or not EXTENSION_PATTERN.match(value):
148
+ raise Exception(location + name + ": expected a file extension starting with a dot, like \".pb.hpp\", got "
149
+ + json.dumps(value) + ".")
150
+ return node
151
+
152
+
153
+ # Merge the settings of one file into the settings read so far. The first file to set a value wins, a later file that
154
+ # disagrees is reported, as a setting applies to the whole build and can not be overlaid per board.
155
+ def merge_settings(target, addition, source):
156
+ for name, value in addition.items():
157
+ if name in target and target[name] != value:
158
+ warn(source + ": " + SETTINGS_KEY + "." + name + " is already set to " + json.dumps(target[name])
159
+ + " by an earlier file, ignoring " + json.dumps(value) + ".")
160
+ else:
161
+ target[name] = value
162
+
163
+
164
+ # Merge the tree of one file into the tree read so far. Values from the later file win, per option, so a project can
165
+ # keep a shared base file and a board specific overlay.
166
+ def merge_tree(target, addition):
167
+ for key, value in addition.items():
168
+ if isinstance(value, dict) and isinstance(target.get(key), dict):
169
+ merge_tree(target[key], value)
170
+ else:
171
+ target[key] = value
172
+ return target
173
+
174
+
175
+ # Expand every key holding a dotted path into nested objects. A dot can only mean scope nesting: protoc allows no
176
+ # dot in a package part, a message name or a field name, so the two spellings mean the same thing. A file may mix
177
+ # them, "foo.telemetry" and "foo": { "telemetry": ... } end up in one and the same subtree.
178
+ def expand_dotted_keys(node, path, source):
179
+ if not isinstance(node, dict):
180
+ return node
181
+
182
+ result = {}
183
+ for key, value in node.items():
184
+ parts = key.split(".")
185
+ if any(not part for part in parts):
186
+ raise Exception(source + ": " + path_str(path + [key])
187
+ + ": a dotted path may not have an empty part.")
188
+
189
+ # Rebuild the key as nested objects, innermost first.
190
+ branch = expand_dotted_keys(value, path + [key], source)
191
+ for part in reversed(parts):
192
+ branch = {part: branch}
193
+ merge_tree(result, branch)
194
+
195
+ return result
196
+
197
+
198
+ class OptionsFile:
199
+ """The merged content of zero or more field options files."""
200
+
201
+ def __init__(self, tree=None, sources=None, settings=None):
202
+ # The scope tree as read from the file(s), keys mirroring the proto.
203
+ self.tree = tree if tree is not None else {}
204
+ # The paths the tree was read from, in the order given, for error messages.
205
+ self.sources = list(sources) if sources else []
206
+ # The generator wide settings read from the file(s).
207
+ self.settings = settings if settings is not None else {}
208
+
209
+ def header_extension(self):
210
+ """The extension of the generated header files, including the dot."""
211
+ return self.settings.get("headerExtension", DEFAULT_HEADER_EXTENSION)
212
+
213
+ # An options file that holds nothing behaves exactly like no options file at all.
214
+ def __bool__(self):
215
+ return bool(self.tree)
216
+
217
+ def describe_sources(self):
218
+ return ", ".join(self.sources) if self.sources else "<none>"
219
+
220
+ def resolve(self, scope_path, field_name):
221
+ """Return ``{option: value}`` for one field, empty when nothing is set.
222
+
223
+ The path is walked with names taken from the proto definitions, never
224
+ guessed from their spelling, so a field named ``maxLength`` resolves like
225
+ any other.
226
+ """
227
+ node = self.tree
228
+ for name in list(scope_path) + [field_name]:
229
+ if not isinstance(node, dict) or name not in node:
230
+ return {}
231
+ node = node[name]
232
+ if not isinstance(node, dict):
233
+ return {}
234
+ return {key: value for key, value in node.items() if key in OPTION_TYPES}
235
+
236
+ def to_options(self, scope_path, field_name):
237
+ """The resolved options of one field as an Options message, or None."""
238
+ values = self.resolve(scope_path, field_name)
239
+ if not values:
240
+ return None
241
+ options = embedded_proto_options_pb2.Options()
242
+ for name, value in values.items():
243
+ setattr(options, name, value)
244
+ return options
245
+
246
+ def validate_against(self, schema):
247
+ """Report entries that match nothing in the schema of this build.
248
+
249
+ A misspelled field name silently sizing nothing is exactly the wrong
250
+ size build this feature must not cause, so it is an error. One file may
251
+ however hold the options for schemas that this particular protoc run does
252
+ not compile. Entries below a top level name that is absent from the run
253
+ are therefore skipped, while everything under a name that is present is
254
+ checked in full.
255
+ """
256
+ for name, node in self.tree.items():
257
+ if name in schema:
258
+ self.validate_node(node, schema[name], [name])
259
+
260
+ # Walk one subtree of the file next to the matching part of the schema. The schema maps a message name to its own
261
+ # children and a field name to None, mirroring the proto definitions.
262
+ def validate_node(self, node, schema_node, path):
263
+ source = self.describe_sources()
264
+
265
+ # A field: the object below it may hold options and nothing else.
266
+ if schema_node is None:
267
+ unknown = sorted(key for key in node if key not in OPTION_TYPES)
268
+ if unknown:
269
+ raise Exception(source + ": " + path_str(path) + ": unknown option(s) "
270
+ + ", ".join(unknown) + ". Known options are "
271
+ + ", ".join(sorted(OPTION_TYPES)) + ".")
272
+ return
273
+
274
+ # A message or package: it holds messages and fields, never options.
275
+ for key, value in node.items():
276
+ if key not in schema_node:
277
+ if key in OPTION_TYPES:
278
+ raise Exception(source + ": " + path_str(path) + ": the option " + key
279
+ + " is set on a message or package. Options are set per "
280
+ "field, in the object of the field itself.")
281
+ raise Exception(source + ": " + path_str(path + [key])
282
+ + " does not exist in the proto definitions.")
283
+ self.validate_node(value, schema_node[key], path + [key])
284
+
285
+
286
+ def load(paths):
287
+ """Read and merge the given options files into one OptionsFile.
288
+
289
+ Every path was named explicitly by the user, so a missing or unreadable file
290
+ is an error: silently continuing would build the wrong buffer sizes.
291
+ """
292
+ tree = {}
293
+ sources = []
294
+ settings = {}
295
+ for path in paths:
296
+ if not os.path.isfile(path):
297
+ raise Exception("The options file " + path + " does not exist.")
298
+ try:
299
+ with open(path, "r", encoding="utf-8") as handle:
300
+ content = json.load(handle)
301
+ except json.JSONDecodeError as error:
302
+ raise Exception(path + ":" + str(error.lineno) + ":" + str(error.colno) + ": "
303
+ + error.msg + ".")
304
+ except OSError as error:
305
+ raise Exception("The options file " + path + " could not be read: "
306
+ + error.strerror + ".")
307
+
308
+ # The settings leave the tree before it is walked, they are no scope.
309
+ if isinstance(content, dict) and SETTINGS_KEY in content:
310
+ merge_settings(settings, check_settings(content.pop(SETTINGS_KEY), path), path)
311
+
312
+ content = expand_dotted_keys(content, [], path)
313
+ check_structure(content, [], path)
314
+ merge_tree(tree, content)
315
+ sources.append(path)
316
+
317
+ return OptionsFile(tree, sources, settings)
318
+
319
+
320
+ def schema_from_descriptors(proto_files):
321
+ """Build the scope tree of the proto definitions in this protoc run.
322
+
323
+ A message maps to its own children, a field maps to None. The result mirrors
324
+ the shape of an options file, so the two can be walked side by side.
325
+ """
326
+ schema = {}
327
+ for proto_file in proto_files:
328
+ node = schema
329
+ for part in proto_file.package.split(".") if proto_file.package else []:
330
+ node = node.setdefault(part, {})
331
+ for message in proto_file.message_type:
332
+ add_message_to_schema(message, node)
333
+ return schema
334
+
335
+
336
+ # Add one message and everything nested in it to the schema tree.
337
+ def add_message_to_schema(message, node):
338
+ entry = node.setdefault(message.name, {})
339
+ for field in message.field:
340
+ entry[field.name] = None
341
+ for nested in message.nested_type:
342
+ # Map entries are synthesised by protoc and hold no user options.
343
+ if not nested.options.map_entry:
344
+ add_message_to_schema(nested, entry)