protostar 0.1.0.beta1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +647 -0
- data/THIRD_PARTY_NOTICES.md +61 -0
- data/exe/protoc-gen-protostar +7 -0
- data/exe/protostar +7 -0
- data/lib/protostar/builder/generator.rb +71 -0
- data/lib/protostar/builder/sink.rb +35 -0
- data/lib/protostar/builder.rb +965 -0
- data/lib/protostar/cli.rb +267 -0
- data/lib/protostar/codec/decode_generator.rb +199 -0
- data/lib/protostar/codec/decoder.rb +296 -0
- data/lib/protostar/codec/encode_generator.rb +224 -0
- data/lib/protostar/codec/encoder.rb +121 -0
- data/lib/protostar/codec/sizer.rb +91 -0
- data/lib/protostar/codec.rb +15 -0
- data/lib/protostar/compiler/ast.rb +193 -0
- data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
- data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
- data/lib/protostar/compiler/emitters/grpc.rb +175 -0
- data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
- data/lib/protostar/compiler/emitters/rbs.rb +394 -0
- data/lib/protostar/compiler/emitters/ruby.rb +503 -0
- data/lib/protostar/compiler/emitters.rb +268 -0
- data/lib/protostar/compiler/include/LICENSE +32 -0
- data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
- data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
- data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
- data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
- data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
- data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
- data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
- data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
- data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
- data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
- data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
- data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
- data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
- data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
- data/lib/protostar/compiler/lexer.rb +659 -0
- data/lib/protostar/compiler/linker.rb +894 -0
- data/lib/protostar/compiler/option_interpreter.rb +363 -0
- data/lib/protostar/compiler/parser.rb +1696 -0
- data/lib/protostar/compiler/retention.rb +104 -0
- data/lib/protostar/compiler/run.rb +184 -0
- data/lib/protostar/compiler/source_info.rb +161 -0
- data/lib/protostar/compiler/symbol_table.rb +223 -0
- data/lib/protostar/compiler/text_format.rb +340 -0
- data/lib/protostar/compiler/validator.rb +944 -0
- data/lib/protostar/compiler.rb +304 -0
- data/lib/protostar/diagnostics/codes.rb +1307 -0
- data/lib/protostar/diagnostics/formats.rb +64 -0
- data/lib/protostar/diagnostics/renderer.rb +240 -0
- data/lib/protostar/diagnostics/source.rb +153 -0
- data/lib/protostar/diagnostics.rb +117 -0
- data/lib/protostar/dsl.rb +367 -0
- data/lib/protostar/enum.rb +70 -0
- data/lib/protostar/errors.rb +23 -0
- data/lib/protostar/google/protobuf/any_pb.rb +90 -0
- data/lib/protostar/google/protobuf/api_pb.rb +199 -0
- data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
- data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
- data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
- data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
- data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
- data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
- data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
- data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
- data/lib/protostar/google/protobuf/type_pb.rb +202 -0
- data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
- data/lib/protostar/google_protobuf_shim.rb +24 -0
- data/lib/protostar/json/encoder.rb +340 -0
- data/lib/protostar/json/errors.rb +34 -0
- data/lib/protostar/json/parser.rb +437 -0
- data/lib/protostar/json/path.rb +39 -0
- data/lib/protostar/json.rb +95 -0
- data/lib/protostar/lint/command.rb +197 -0
- data/lib/protostar/lint/config.rb +98 -0
- data/lib/protostar/lint/context.rb +126 -0
- data/lib/protostar/lint/directives.rb +56 -0
- data/lib/protostar/lint/fixer.rb +165 -0
- data/lib/protostar/lint/naming.rb +54 -0
- data/lib/protostar/lint/rule.rb +120 -0
- data/lib/protostar/lint/rules/comment_enum.rb +31 -0
- data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
- data/lib/protostar/lint/rules/comment_field.rb +33 -0
- data/lib/protostar/lint/rules/comment_message.rb +31 -0
- data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
- data/lib/protostar/lint/rules/comment_service.rb +31 -0
- data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
- data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
- data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
- data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
- data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
- data/lib/protostar/lint/rules/field_not_required.rb +39 -0
- data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
- data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
- data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/import_no_public.rb +29 -0
- data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
- data/lib/protostar/lint/rules/import_used.rb +33 -0
- data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
- data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
- data/lib/protostar/lint/rules/no_groups.rb +34 -0
- data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/package_defined.rb +28 -0
- data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
- data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
- data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
- data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
- data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
- data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
- data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
- data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/service_suffix.rb +28 -0
- data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
- data/lib/protostar/lint/runner.rb +135 -0
- data/lib/protostar/lint.rb +122 -0
- data/lib/protostar/message/accessors.rb +86 -0
- data/lib/protostar/message/coercion.rb +171 -0
- data/lib/protostar/message/field_info.rb +145 -0
- data/lib/protostar/message/reflection.rb +149 -0
- data/lib/protostar/message.rb +572 -0
- data/lib/protostar/plugin.rb +94 -0
- data/lib/protostar/schema/default_value.rb +113 -0
- data/lib/protostar/schema/descriptor.rb +41 -0
- data/lib/protostar/schema/draft/copy.rb +88 -0
- data/lib/protostar/schema/draft/resolution.rb +177 -0
- data/lib/protostar/schema/draft.rb +779 -0
- data/lib/protostar/schema/enum.rb +106 -0
- data/lib/protostar/schema/extension_range.rb +37 -0
- data/lib/protostar/schema/features.rb +153 -0
- data/lib/protostar/schema/field.rb +201 -0
- data/lib/protostar/schema/file.rb +217 -0
- data/lib/protostar/schema/freezer.rb +61 -0
- data/lib/protostar/schema/message.rb +104 -0
- data/lib/protostar/schema/oneof.rb +38 -0
- data/lib/protostar/schema/options.rb +367 -0
- data/lib/protostar/schema/pool.rb +179 -0
- data/lib/protostar/schema/proto.rb +576 -0
- data/lib/protostar/schema/resolver.rb +118 -0
- data/lib/protostar/schema/service.rb +89 -0
- data/lib/protostar/schema/source_info.rb +53 -0
- data/lib/protostar/schema.rb +65 -0
- data/lib/protostar/short_names/any.rb +7 -0
- data/lib/protostar/short_names/duration.rb +7 -0
- data/lib/protostar/short_names/empty.rb +7 -0
- data/lib/protostar/short_names/field_mask.rb +7 -0
- data/lib/protostar/short_names/struct.rb +9 -0
- data/lib/protostar/short_names/timestamp.rb +7 -0
- data/lib/protostar/short_names/wrappers.rb +15 -0
- data/lib/protostar/text/extensions.rb +114 -0
- data/lib/protostar/text/parser.rb +439 -0
- data/lib/protostar/text/printer.rb +304 -0
- data/lib/protostar/text/tokenizer.rb +116 -0
- data/lib/protostar/text.rb +67 -0
- data/lib/protostar/types.rb +152 -0
- data/lib/protostar/unknown_field.rb +58 -0
- data/lib/protostar/version.rb +5 -0
- data/lib/protostar/view/generator.rb +298 -0
- data/lib/protostar/view/map.rb +194 -0
- data/lib/protostar/view/repeated.rb +143 -0
- data/lib/protostar/view/scan.rb +183 -0
- data/lib/protostar/view.rb +283 -0
- data/lib/protostar/well_known/any.rb +52 -0
- data/lib/protostar/well_known/duration.rb +53 -0
- data/lib/protostar/well_known/list_value.rb +63 -0
- data/lib/protostar/well_known/struct.rb +55 -0
- data/lib/protostar/well_known/timestamp.rb +64 -0
- data/lib/protostar/well_known/value.rb +60 -0
- data/lib/protostar/well_known.rb +38 -0
- data/lib/protostar/wire/io_source.rb +40 -0
- data/lib/protostar/wire/reader.rb +254 -0
- data/lib/protostar/wire/writer.rb +201 -0
- data/lib/protostar/wire.rb +96 -0
- data/lib/protostar.rb +95 -0
- data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
- data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
- data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
- data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
- data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
- data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
- data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
- data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
- data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
- data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
- data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
- data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
- data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
- data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
- data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
- data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
- data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
- data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
- data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
- data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
- data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
- data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
- data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
- data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
- data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
- data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
- data/sig/protostar/lint.rbs +216 -0
- data/sig/protostar.rbs +326 -0
- metadata +366 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class Compiler
|
|
5
|
+
module Emitters
|
|
6
|
+
# Writes protostar's own `_pb.rb` files: a {Protostar::DSL} block
|
|
7
|
+
# describing the file, then constants named as protoc's Ruby generator
|
|
8
|
+
# names them.
|
|
9
|
+
class Ruby
|
|
10
|
+
# source_code_info path components (descriptor.proto field numbers)
|
|
11
|
+
FILE_MESSAGE = 4
|
|
12
|
+
FILE_ENUM = 5
|
|
13
|
+
FILE_SERVICE = 6
|
|
14
|
+
FILE_EXTENSION = 7
|
|
15
|
+
MESSAGE_FIELD = 2
|
|
16
|
+
MESSAGE_NESTED = 3
|
|
17
|
+
MESSAGE_ENUM = 4
|
|
18
|
+
MESSAGE_EXTENSION = 6
|
|
19
|
+
MESSAGE_ONEOF = 8
|
|
20
|
+
ENUM_VALUE = 2
|
|
21
|
+
SERVICE_METHOD = 2
|
|
22
|
+
|
|
23
|
+
# @param namespaces [Hash{String => String}] Ruby module paths ("A::B") by file name, overriding
|
|
24
|
+
# the package-derived modules; the bootstrap puts google.protobuf under Protostar::Google::Protobuf
|
|
25
|
+
# @param bootstrap_requires [Boolean] require imports of {BOOTSTRAP} files as
|
|
26
|
+
# "protostar/google/protobuf/..._pb" rather than by their own names
|
|
27
|
+
def initialize(namespaces: {}, bootstrap_requires: true)
|
|
28
|
+
@namespaces = namespaces
|
|
29
|
+
@bootstrap_requires = bootstrap_requires
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param files [Array<Schema::File>]
|
|
33
|
+
# @return [Hash{String => String}] output path to source
|
|
34
|
+
def emit(files)
|
|
35
|
+
files.to_h { |f| [Naming.require_name(f.name) + ".rb", file(f)] }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param file [Schema::File]
|
|
39
|
+
# @return [String] the generated source
|
|
40
|
+
def file(file)
|
|
41
|
+
Naming.check_ruby_package(file)
|
|
42
|
+
@file = file
|
|
43
|
+
@source = file.source_info
|
|
44
|
+
@out = +""
|
|
45
|
+
@out << "# frozen_string_literal: true\n\n"
|
|
46
|
+
@out << "# Generated by protostar from #{file.name}. Do not edit.\n\n"
|
|
47
|
+
@out << "require \"protostar\"\n"
|
|
48
|
+
file.dependencies.each { |dep| @out << "require #{Literal.string(require_path(dep))}\n" }
|
|
49
|
+
@out << "\n"
|
|
50
|
+
emit_file(file)
|
|
51
|
+
emit_constants(file)
|
|
52
|
+
@out
|
|
53
|
+
ensure
|
|
54
|
+
@file = @source = @reachable = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Only the {Protostar::DSL.file} statement, without requires and constants
|
|
58
|
+
# @param file [Schema::File]
|
|
59
|
+
# @return [String]
|
|
60
|
+
def definition(file)
|
|
61
|
+
@file = file
|
|
62
|
+
@source = file.source_info
|
|
63
|
+
@out = +""
|
|
64
|
+
emit_file(file)
|
|
65
|
+
@out
|
|
66
|
+
ensure
|
|
67
|
+
@file = @source = @reachable = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param name [String] an imported file name
|
|
71
|
+
# @return [String] what a generated file requires for it
|
|
72
|
+
def require_path(name)
|
|
73
|
+
if @bootstrap_requires && BOOTSTRAP.include?(name)
|
|
74
|
+
"protostar/#{Naming.require_name(name)}"
|
|
75
|
+
else
|
|
76
|
+
Naming.require_name(name)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def emit_file(file)
|
|
83
|
+
args = [Literal.string(file.name)]
|
|
84
|
+
args << "package: #{Literal.string(file.package)}" unless file.package.empty?
|
|
85
|
+
if file.edition
|
|
86
|
+
args << "syntax: :editions" unless file.proto_syntax == "editions"
|
|
87
|
+
args << "edition: #{file.edition.inspect}"
|
|
88
|
+
elsif file.proto_syntax
|
|
89
|
+
args << "syntax: #{file.proto_syntax.to_sym.inspect}"
|
|
90
|
+
end
|
|
91
|
+
deps = file.dependencies
|
|
92
|
+
args << ["dependencies: ", deps] unless deps.empty?
|
|
93
|
+
[[:public_dependencies, file.public_dependencies], [:weak_dependencies, file.weak_dependencies]].each do |key, list|
|
|
94
|
+
args << ["#{key}: ", list.map { |i| deps[i] }] unless list.empty?
|
|
95
|
+
end
|
|
96
|
+
args << ["option_dependencies: ", file.option_dependencies] unless file.option_dependencies.empty?
|
|
97
|
+
args.concat(option_args(file.options))
|
|
98
|
+
body = +""
|
|
99
|
+
file.messages.each_with_index { |m, i| message(body, 1, "f", m, [FILE_MESSAGE, i]) }
|
|
100
|
+
file.enums.each_with_index { |e, i| enum(body, 1, "f", e, [FILE_ENUM, i]) }
|
|
101
|
+
extensions(body, 1, "f", file.extensions, [FILE_EXTENSION])
|
|
102
|
+
file.services.each_with_index { |s, i| service(body, 1, "f", s, [FILE_SERVICE, i]) }
|
|
103
|
+
if body.empty?
|
|
104
|
+
@out << call("Protostar::DSL.file", args, 0) << "\n"
|
|
105
|
+
else
|
|
106
|
+
@out << call("Protostar::DSL.file", args, 0) << " do |f|\n" << body << "end\n"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def message(out, depth, var, desc, path)
|
|
111
|
+
comment(out, depth, path)
|
|
112
|
+
args = [Literal.string(desc.name)]
|
|
113
|
+
args << "visibility: #{desc.visibility.inspect}" if desc.visibility
|
|
114
|
+
args.concat(option_args(desc.options))
|
|
115
|
+
head = call("#{var}.message", args, depth)
|
|
116
|
+
body = +""
|
|
117
|
+
message_body(body, depth + 1, desc, path)
|
|
118
|
+
out << ind(depth) << head << (body.empty? ? "\n" : " do |m|\n#{body}#{ind(depth)}end\n")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def message_body(out, depth, desc, path)
|
|
122
|
+
fields = desc.fields
|
|
123
|
+
nested = desc.nested_messages
|
|
124
|
+
oneofs = desc.oneofs
|
|
125
|
+
owned = owned_nested(desc, fields)
|
|
126
|
+
canonical = canonical_oneofs?(fields, oneofs)
|
|
127
|
+
unless canonical
|
|
128
|
+
oneofs.each do |o|
|
|
129
|
+
out << ind(depth) << call("m.oneof", [o.name.to_sym.inspect] + option_args(o.options), depth) << "\n"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
items = field_items(fields, canonical)
|
|
133
|
+
emitted = {}
|
|
134
|
+
flush = lambda do |limit|
|
|
135
|
+
nested.each_index do |j|
|
|
136
|
+
next if j >= limit || emitted[j] || owned.value?(j)
|
|
137
|
+
emitted[j] = true
|
|
138
|
+
message(out, depth, "m", nested[j], path + [MESSAGE_NESTED, j])
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
items.each do |oneof_index, indexes|
|
|
142
|
+
owned_here = indexes.filter_map { |i| owned[i] }
|
|
143
|
+
flush.call(owned_here.max) unless owned_here.empty?
|
|
144
|
+
if oneof_index
|
|
145
|
+
comment(out, depth, path + [MESSAGE_ONEOF, oneof_index])
|
|
146
|
+
o = oneofs[oneof_index]
|
|
147
|
+
out << ind(depth) << call("m.oneof", [o.name.to_sym.inspect] + option_args(o.options), depth)
|
|
148
|
+
out << " do |o|\n"
|
|
149
|
+
indexes.each { |i| field(out, depth + 1, "o", desc, fields[i], owned[i], path + [MESSAGE_FIELD, i], false) }
|
|
150
|
+
out << ind(depth) << "end\n"
|
|
151
|
+
else
|
|
152
|
+
indexes.each { |i| field(out, depth, "m", desc, fields[i], owned[i], path + [MESSAGE_FIELD, i], !canonical) }
|
|
153
|
+
end
|
|
154
|
+
owned_here.each { |j| emitted[j] = true }
|
|
155
|
+
end
|
|
156
|
+
flush.call(nested.size)
|
|
157
|
+
desc.nested_enums.each_with_index { |e, i| enum(out, depth, "m", e, path + [MESSAGE_ENUM, i]) }
|
|
158
|
+
extensions(out, depth, "m", desc.extensions, path + [MESSAGE_EXTENSION])
|
|
159
|
+
desc.extension_range_descriptors.each do |r|
|
|
160
|
+
out << ind(depth) << call("m.extensions", [range(r.start, r.end)] + option_args(r.options), depth) << "\n"
|
|
161
|
+
end
|
|
162
|
+
reserved = desc.reserved_ranges.map { |r| range(r.begin, r.end) }
|
|
163
|
+
reserved.concat(desc.reserved_names.map { |n| Literal.string(n) })
|
|
164
|
+
out << ind(depth) << call("m.reserved", reserved, depth) << "\n" unless reserved.empty?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Fields grouped for emission: [oneof index or nil, [field indexes]]
|
|
168
|
+
def field_items(fields, canonical)
|
|
169
|
+
return fields.each_index.map { |i| [nil, [i]] } unless canonical
|
|
170
|
+
items = []
|
|
171
|
+
fields.each_with_index do |f, i|
|
|
172
|
+
o = f.oneof&.index
|
|
173
|
+
if o && !f.proto3_optional?
|
|
174
|
+
if items.last && items.last[0] == o
|
|
175
|
+
items.last[1] << i
|
|
176
|
+
else
|
|
177
|
+
items << [o, [i]]
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
items << [nil, [i]]
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
items
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Whether the oneofs are laid out as the DSL's block form and synthetic
|
|
187
|
+
# oneofs reproduce them: real oneofs in order of their contiguous
|
|
188
|
+
# members, then one synthetic oneof per proto3 optional field, named
|
|
189
|
+
# as protoc's parser names them
|
|
190
|
+
def canonical_oneofs?(fields, oneofs)
|
|
191
|
+
return true if oneofs.empty?
|
|
192
|
+
real = []
|
|
193
|
+
synthetic = []
|
|
194
|
+
fields.each_with_index do |f, i|
|
|
195
|
+
next unless (o = f.oneof&.index)
|
|
196
|
+
if f.proto3_optional?
|
|
197
|
+
synthetic << [o, f]
|
|
198
|
+
else
|
|
199
|
+
return false if real.include?(o) && real.last != o
|
|
200
|
+
return false if real.include?(o) && fields[i - 1].oneof&.index != o
|
|
201
|
+
real << o unless real.include?(o)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
return false unless real == (0...real.size).to_a
|
|
205
|
+
return false unless synthetic.map(&:first) == (real.size...oneofs.size).to_a
|
|
206
|
+
taken = fields.map(&:name) + oneofs.first(real.size).map(&:name)
|
|
207
|
+
names = Schema::Draft.synthetic_oneof_names(synthetic.map { |_, f| f.name }, taken)
|
|
208
|
+
synthetic.zip(names).all? do |(o, _), name|
|
|
209
|
+
oneofs[o].name == name && !oneofs[o].options.declared?
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Nested types written through `map` or `group`: field index to nested index
|
|
214
|
+
def owned_nested(desc, fields)
|
|
215
|
+
owned = {}
|
|
216
|
+
fields.each_with_index do |f, i|
|
|
217
|
+
nested = f.message_type
|
|
218
|
+
next unless nested && nested.containing_message.equal?(desc) && nested.file.equal?(desc.file)
|
|
219
|
+
owned[i] = nested.index if map_sugar(f, nested) || group_sugar?(f, nested)
|
|
220
|
+
end
|
|
221
|
+
# the sugar appends its nested type when called, so the order must agree
|
|
222
|
+
return {} unless owned.values == owned.values.sort
|
|
223
|
+
owned
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# [key type, value type, value type name] when `m.map` makes exactly this entry
|
|
227
|
+
def map_sugar(f, entry)
|
|
228
|
+
return nil unless f.declared_label == :repeated && entry.name == Schema::Draft.map_entry_name(f.name)
|
|
229
|
+
return nil if f.raw_default || f.oneof || f.proto3_optional?
|
|
230
|
+
return nil unless only?(entry.options, map_entry: true)
|
|
231
|
+
return nil unless entry.nested_messages.empty? && entry.nested_enums.empty? && entry.extensions.empty? &&
|
|
232
|
+
entry.extension_range_descriptors.empty? && entry.oneofs.empty? && entry.reserved_ranges.empty? &&
|
|
233
|
+
entry.reserved_names.empty? && entry.visibility.nil?
|
|
234
|
+
key, value = entry.fields
|
|
235
|
+
return nil unless entry.fields.size == 2 && entry_field?(key, "key", 1) && entry_field?(value, "value", 2)
|
|
236
|
+
return nil if key.type_name
|
|
237
|
+
features = f.options.declared? ? f.options[:features] : nil
|
|
238
|
+
[key, value].each do |kv|
|
|
239
|
+
if features
|
|
240
|
+
return nil unless only?(kv.options, features: features)
|
|
241
|
+
elsif kv.options.declared?
|
|
242
|
+
return nil
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
[key.declared_type, value.declared_type, value.type_name && ".#{value.type_name}"]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def entry_field?(field, name, number)
|
|
249
|
+
field.name == name && field.number == number && field.declared_label == :optional &&
|
|
250
|
+
field.json_name == name && field.raw_default.nil? && field.oneof.nil? && !field.proto3_optional?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Options declared with exactly these fields, no custom or unknown ones
|
|
254
|
+
def only?(options, **fields)
|
|
255
|
+
options.declared? && options.fields == fields && options.extensions.empty? && options.unknown_fields.empty?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def group_sugar?(f, nested)
|
|
259
|
+
f.declared_type == :group && f.name == Schema::Draft.group_field_name(nested.name) &&
|
|
260
|
+
!f.proto3_optional? && f.raw_default.nil?
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def field(out, depth, var, desc, f, owned, path, explicit_oneof)
|
|
264
|
+
comment(out, depth, path)
|
|
265
|
+
nested = owned && desc.nested_messages[owned]
|
|
266
|
+
if nested && (sugar = map_sugar(f, nested))
|
|
267
|
+
key_type, value_type, value_type_name = sugar
|
|
268
|
+
args = [f.name.to_sym.inspect, f.number.to_s, key_type.inspect, value_type.inspect]
|
|
269
|
+
args << Literal.string(value_type_name) if value_type_name
|
|
270
|
+
args << "json_name: #{Literal.string(f.json_name)}" if custom_json_name?(f)
|
|
271
|
+
args.concat(option_args(f.options))
|
|
272
|
+
out << ind(depth) << call("#{var}.map", args, depth) << "\n"
|
|
273
|
+
return
|
|
274
|
+
end
|
|
275
|
+
if nested
|
|
276
|
+
args = [Literal.string(nested.name), f.number.to_s]
|
|
277
|
+
args << "label: #{f.declared_label.inspect}" unless f.declared_label == :optional
|
|
278
|
+
args << "json_name: #{Literal.string(f.json_name)}" if custom_json_name?(f)
|
|
279
|
+
args << "oneof: #{f.oneof.name.to_sym.inspect}" if f.oneof && explicit_oneof
|
|
280
|
+
args.concat(option_args(f.options))
|
|
281
|
+
args << ["message_options: ", dsl_hash(nested.options)] if nested.options.declared?
|
|
282
|
+
body = +""
|
|
283
|
+
message_body(body, depth + 1, nested, path_for_nested(owned, path))
|
|
284
|
+
head = call("#{var}.group", args, depth)
|
|
285
|
+
out << ind(depth) << head << (body.empty? ? "\n" : " do |m|\n#{body}#{ind(depth)}end\n")
|
|
286
|
+
return
|
|
287
|
+
end
|
|
288
|
+
args = field_args(f)
|
|
289
|
+
args << "proto3_optional: true" if f.proto3_optional?
|
|
290
|
+
args << "oneof: #{f.oneof.name.to_sym.inspect}" if f.oneof && explicit_oneof
|
|
291
|
+
args.concat(option_args(f.options))
|
|
292
|
+
out << ind(depth) << call("#{var}.field", args, depth) << "\n"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def path_for_nested(index, field_path)
|
|
296
|
+
field_path[0...-2] + [MESSAGE_NESTED, index]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def field_args(f)
|
|
300
|
+
args = [f.name.to_sym.inspect, f.number.to_s, f.declared_type.inspect]
|
|
301
|
+
args << Literal.string(".#{f.type_name}") if f.type_name
|
|
302
|
+
args << "label: #{f.declared_label.inspect}" unless f.declared_label == :optional
|
|
303
|
+
args << "default: #{Literal.string(f.raw_default)}" if f.raw_default
|
|
304
|
+
args << "json_name: #{Literal.string(f.json_name)}" if custom_json_name?(f)
|
|
305
|
+
args
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def custom_json_name?(f)
|
|
309
|
+
f.json_name != Schema.json_name(f.name)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def extensions(out, depth, var, list, path)
|
|
313
|
+
i = 0
|
|
314
|
+
while i < list.size
|
|
315
|
+
extendee = list[i].extendee
|
|
316
|
+
out << ind(depth) << "#{var}.extend #{Literal.string(".#{extendee.full_name}")} do |e|\n"
|
|
317
|
+
while i < list.size && list[i].extendee.equal?(extendee)
|
|
318
|
+
f = list[i]
|
|
319
|
+
comment(out, depth + 1, path + [i])
|
|
320
|
+
args = field_args(f)
|
|
321
|
+
args << "proto3_optional: true" if f.proto3_optional?
|
|
322
|
+
args.concat(option_args(f.options))
|
|
323
|
+
out << ind(depth + 1) << call("e.field", args, depth + 1) << "\n"
|
|
324
|
+
i += 1
|
|
325
|
+
end
|
|
326
|
+
out << ind(depth) << "end\n"
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def enum(out, depth, var, desc, path)
|
|
331
|
+
comment(out, depth, path)
|
|
332
|
+
args = [Literal.string(desc.name)]
|
|
333
|
+
args << "visibility: #{desc.visibility.inspect}" if desc.visibility
|
|
334
|
+
args.concat(option_args(desc.options))
|
|
335
|
+
out << ind(depth) << call("#{var}.enum", args, depth) << " do |e|\n"
|
|
336
|
+
desc.values.each_with_index do |v, i|
|
|
337
|
+
comment(out, depth + 1, path + [ENUM_VALUE, i])
|
|
338
|
+
args = [v.name.to_sym.inspect, v.number.to_s] + option_args(v.options)
|
|
339
|
+
out << ind(depth + 1) << call("e.value", args, depth + 1) << "\n"
|
|
340
|
+
end
|
|
341
|
+
reserved = desc.reserved_ranges.map { |r| range(r.begin, r.end) }
|
|
342
|
+
reserved.concat(desc.reserved_names.map { |n| Literal.string(n) })
|
|
343
|
+
out << ind(depth + 1) << call("e.reserved", reserved, depth + 1) << "\n" unless reserved.empty?
|
|
344
|
+
out << ind(depth) << "end\n"
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def service(out, depth, var, desc, path)
|
|
348
|
+
comment(out, depth, path)
|
|
349
|
+
args = [Literal.string(desc.name)] + option_args(desc.options)
|
|
350
|
+
head = call("#{var}.service", args, depth)
|
|
351
|
+
if desc.rpcs.empty?
|
|
352
|
+
out << ind(depth) << head << "\n"
|
|
353
|
+
return
|
|
354
|
+
end
|
|
355
|
+
out << ind(depth) << head << " do |s|\n"
|
|
356
|
+
desc.rpcs.each_with_index do |m, i|
|
|
357
|
+
comment(out, depth + 1, path + [SERVICE_METHOD, i])
|
|
358
|
+
name = /\A[A-Za-z_][A-Za-z0-9_]*\z/.match?(m.name) ? m.name.to_sym.inspect : Literal.string(m.name)
|
|
359
|
+
args = [name, Literal.string(".#{m.input_type.full_name}"), Literal.string(".#{m.output_type.full_name}")]
|
|
360
|
+
args << "client_streaming: true" if m.client_streaming?
|
|
361
|
+
args << "server_streaming: true" if m.server_streaming?
|
|
362
|
+
args.concat(option_args(m.options))
|
|
363
|
+
out << ind(depth + 1) << call("s.rpc", args, depth + 1) << "\n"
|
|
364
|
+
end
|
|
365
|
+
out << ind(depth) << "end\n"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Options as keywords, custom ones under `extensions:`; verbatim
|
|
369
|
+
# under `options:` when that cannot be said with keywords. Each is a
|
|
370
|
+
# `[prefix, value]` pair, rendered by {#call}.
|
|
371
|
+
def option_args(options)
|
|
372
|
+
return [] unless options.declared?
|
|
373
|
+
h = dsl_hash(options)
|
|
374
|
+
return [["options: ", h]] if h.empty? || h.key?(:options)
|
|
375
|
+
h.map { |key, value| ["#{key}: ", value] }
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# An options message (or a message inside one) as the DSL spells it:
|
|
379
|
+
# fields by name, `extensions` by full name, `unknown_fields` as wire
|
|
380
|
+
# bytes; a field named like either of those two takes a String key. An
|
|
381
|
+
# extension the file reaches only through `import option` is written
|
|
382
|
+
# as unknown bytes, as protoc's own generator embeds it: the generated
|
|
383
|
+
# file does not load that import, so it may not be there to name the
|
|
384
|
+
# option.
|
|
385
|
+
def dsl_hash(value)
|
|
386
|
+
h = {}
|
|
387
|
+
value.fields.each { |k, v| h[RESERVED_KEYS.include?(k) ? k.to_s : k] = dsl_value(v) }
|
|
388
|
+
named, unnamed = value.extensions.partition { |f, _| reachable?(f) }
|
|
389
|
+
h[:extensions] = named.to_h { |f, v| [f.full_name, dsl_value(v)] } unless named.empty?
|
|
390
|
+
unknown = value.unknown_fields
|
|
391
|
+
unnamed.each { |f, v| unknown += Schema::MessageValue.new({}, {f => v}).encode(f.extendee) }
|
|
392
|
+
h[:unknown_fields] = unknown.b unless unknown.empty?
|
|
393
|
+
h
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
RESERVED_KEYS = %i[extensions unknown_fields].freeze
|
|
397
|
+
|
|
398
|
+
# Whether an extension is defined by the file or one it imports, so
|
|
399
|
+
# that loading the generated file can find it
|
|
400
|
+
def reachable?(extension)
|
|
401
|
+
@reachable ||= begin
|
|
402
|
+
names = {}
|
|
403
|
+
queue = [@file]
|
|
404
|
+
until queue.empty?
|
|
405
|
+
f = queue.shift
|
|
406
|
+
next if names[f.name]
|
|
407
|
+
names[f.name] = true
|
|
408
|
+
queue.concat(f.dependency_files)
|
|
409
|
+
end
|
|
410
|
+
names
|
|
411
|
+
end
|
|
412
|
+
@reachable.key?(extension.file.name)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def dsl_value(value)
|
|
416
|
+
case value
|
|
417
|
+
when Schema::MessageValue then dsl_hash(value)
|
|
418
|
+
when Array then value.map { |v| dsl_value(v) }
|
|
419
|
+
else value
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# A call on one line when it fits. Otherwise the top-level call puts
|
|
424
|
+
# one argument per line inside parentheses, and calls in blocks keep
|
|
425
|
+
# their positional arguments on the first line and continue with one
|
|
426
|
+
# keyword per line. Arguments are source Strings or `[prefix, value]` pairs.
|
|
427
|
+
def call(receiver, args, depth)
|
|
428
|
+
flat = args.map { |a| a.is_a?(Array) ? a[0] + Literal.flat(a[1]) : a }
|
|
429
|
+
one = if args.empty?
|
|
430
|
+
receiver
|
|
431
|
+
elsif depth.positive?
|
|
432
|
+
"#{receiver} #{flat.join(", ")}"
|
|
433
|
+
else
|
|
434
|
+
"#{receiver}(#{flat.join(", ")})"
|
|
435
|
+
end
|
|
436
|
+
return one if depth * 2 + one.size <= Literal::WIDTH
|
|
437
|
+
inner = (depth + 1) * 2
|
|
438
|
+
render = lambda do |a|
|
|
439
|
+
a.is_a?(Array) ? a[0] + Literal.call(a[1], inner, inner + a[0].size) : a
|
|
440
|
+
end
|
|
441
|
+
if depth.zero?
|
|
442
|
+
return "#{receiver}(\n#{args.map { |a| " " * inner + render.call(a) }.join(",\n")}\n)"
|
|
443
|
+
end
|
|
444
|
+
positional, keywords = args.partition { |a| a.is_a?(String) && !KEYWORD.match?(a) }
|
|
445
|
+
return one if keywords.empty?
|
|
446
|
+
head = positional.empty? ? receiver : "#{receiver} #{positional.join(", ")}"
|
|
447
|
+
lines = keywords.map { |a| " " * inner + render.call(a) }
|
|
448
|
+
"#{head},\n#{lines.join(",\n")}"
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
KEYWORD = /\A[a-z_][a-z0-9_]*: /
|
|
452
|
+
|
|
453
|
+
def range(first, last)
|
|
454
|
+
(first == last) ? first.to_s : "#{first}..#{last}"
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def comment(out, depth, path)
|
|
458
|
+
return unless @source
|
|
459
|
+
location = @source.location(path)
|
|
460
|
+
text = location&.leading_comments
|
|
461
|
+
return if text.nil? || text.empty?
|
|
462
|
+
lines = text.split("\n", -1)
|
|
463
|
+
lines.pop if lines.last == ""
|
|
464
|
+
lines.each do |line|
|
|
465
|
+
line = line.rstrip
|
|
466
|
+
out << ind(depth) << (line.empty? ? "#" : "##{line}") << "\n"
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def ind(depth)
|
|
471
|
+
" " * depth
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def emit_constants(file)
|
|
475
|
+
names = Naming.module_path(file, @namespaces[file.name])
|
|
476
|
+
lines = []
|
|
477
|
+
file.messages.each { |m| message_constants(lines, "", m) }
|
|
478
|
+
file.enums.each { |e| lines << enum_constant("", e) }
|
|
479
|
+
file.services.each do |s|
|
|
480
|
+
lines << "#{Naming.rubify_constant(s.name)} = Protostar::DSL.service(#{Literal.string(s.full_name)})"
|
|
481
|
+
end
|
|
482
|
+
return if lines.empty?
|
|
483
|
+
@out << "\n"
|
|
484
|
+
names.each_with_index { |n, i| @out << ind(i) << "module #{n}\n" }
|
|
485
|
+
lines.each { |l| @out << ind(names.size) << l << "\n" }
|
|
486
|
+
names.size.downto(1) { |i| @out << ind(i - 1) << "end\n" }
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def message_constants(lines, prefix, message)
|
|
490
|
+
return if message.map_entry?
|
|
491
|
+
const = prefix + Naming.rubify_constant(message.name)
|
|
492
|
+
lines << "#{const} = Protostar::DSL.message(#{Literal.string(message.full_name)})"
|
|
493
|
+
message.nested_messages.each { |m| message_constants(lines, "#{const}::", m) }
|
|
494
|
+
message.nested_enums.each { |e| lines << enum_constant("#{const}::", e) }
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def enum_constant(prefix, enum)
|
|
498
|
+
"#{prefix}#{Naming.rubify_constant(enum.name)} = Protostar::DSL.enum(#{Literal.string(enum.full_name)})"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
end
|