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.
- EmbeddedProto/EmbeddedProto.py +122 -0
- EmbeddedProto/Features.py +255 -0
- EmbeddedProto/Field.py +1295 -0
- EmbeddedProto/Oneof.py +74 -0
- EmbeddedProto/ProtoFile.py +210 -0
- EmbeddedProto/TypeDefinitions.py +367 -0
- EmbeddedProto/__init__.py +0 -0
- EmbeddedProto/__main__.py +28 -0
- EmbeddedProto/config.py +173 -0
- EmbeddedProto/custom_header.py +513 -0
- EmbeddedProto/embedded_proto_options.proto +62 -0
- EmbeddedProto/embedded_proto_options_pb2.py +37 -0
- EmbeddedProto/field_options.py +344 -0
- EmbeddedProto/main.py +316 -0
- EmbeddedProto/src/EmbeddedProto/BytesStringCallback.h +476 -0
- EmbeddedProto/src/EmbeddedProto/Defines.h +118 -0
- EmbeddedProto/src/EmbeddedProto/EmptyArray.h +81 -0
- EmbeddedProto/src/EmbeddedProto/Errors.h +53 -0
- EmbeddedProto/src/EmbeddedProto/FieldStringBytes.h +793 -0
- EmbeddedProto/src/EmbeddedProto/Fields.h +832 -0
- EmbeddedProto/src/EmbeddedProto/Functional.h +412 -0
- EmbeddedProto/src/EmbeddedProto/MessageCallback.h +688 -0
- EmbeddedProto/src/EmbeddedProto/MessageInterface.h +792 -0
- EmbeddedProto/src/EmbeddedProto/MessageSizeCalculator.h +109 -0
- EmbeddedProto/src/EmbeddedProto/MessageState.h +207 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferFixedSize.h +204 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferInterface.h +181 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferSection.h +232 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedField.h +1053 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedFieldCallback.h +432 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedFieldFixedSize.h +367 -0
- EmbeddedProto/src/EmbeddedProto/Version.h +39 -0
- EmbeddedProto/src/EmbeddedProto/WireFormatter.h +852 -0
- EmbeddedProto/src/EmbeddedProto/WriteBufferFixedSize.h +114 -0
- EmbeddedProto/src/EmbeddedProto/WriteBufferInterface.h +134 -0
- EmbeddedProto/src/EmbeddedProto.h +55 -0
- EmbeddedProto/templates/FieldBasic_Deserialize.h.jinja2 +26 -0
- EmbeddedProto/templates/FieldBasic_GetSet.h.jinja2 +89 -0
- EmbeddedProto/templates/FieldBytes_GetSet.h.jinja2 +86 -0
- EmbeddedProto/templates/FieldEnum_Deserialize.h.jinja2 +50 -0
- EmbeddedProto/templates/FieldEnum_GetSet.h.jinja2 +93 -0
- EmbeddedProto/templates/FieldErrorRecursive_GetSet.h.jinja2 +29 -0
- EmbeddedProto/templates/FieldMap_GetSet.h.jinja2 +203 -0
- EmbeddedProto/templates/FieldMsg_Deserialize.h.jinja2 +44 -0
- EmbeddedProto/templates/FieldMsg_GetSet.h.jinja2 +93 -0
- EmbeddedProto/templates/FieldRepeated_GetSet.h.jinja2 +94 -0
- EmbeddedProto/templates/FieldString_GetSet.h.jinja2 +86 -0
- EmbeddedProto/templates/Field_DeserializePartial.h.jinja2 +33 -0
- EmbeddedProto/templates/Field_Serialize.h.jinja2 +112 -0
- EmbeddedProto/templates/Field_SerializePartial.h.jinja2 +119 -0
- EmbeddedProto/templates/Header.h.jinja2 +108 -0
- EmbeddedProto/templates/TypeDefEnum.h.jinja2 +45 -0
- EmbeddedProto/templates/TypeDefMsg.h.jinja2 +791 -0
- EmbeddedProto/templates/TypeOneof.h.jinja2 +178 -0
- EmbeddedProto/version.json +3 -0
- embeddedproto-4.0.0b1.dist-info/METADATA +141 -0
- embeddedproto-4.0.0b1.dist-info/RECORD +63 -0
- embeddedproto-4.0.0b1.dist-info/WHEEL +5 -0
- embeddedproto-4.0.0b1.dist-info/entry_points.txt +3 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSE +30 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/GPL-3.0-only.txt +674 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/LicenseRef-EmbeddedProto-Commercial.txt +12 -0
- 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)
|