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,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "protostar"
|
|
6
|
+
|
|
7
|
+
module Protostar
|
|
8
|
+
# `protostar compile`, with protoc's flag names and protoc's messages
|
|
9
|
+
#
|
|
10
|
+
# protostar compile -I proto --ruby_out=lib --grpc_out=lib acme/service.proto
|
|
11
|
+
#
|
|
12
|
+
# `compile` is the default command. Without `-I` every input's own directory
|
|
13
|
+
# is an include path, and one input with no `--*_out` prints its native Ruby:
|
|
14
|
+
#
|
|
15
|
+
# protostar proto/acme/person.proto > person_pb.rb
|
|
16
|
+
class CLI
|
|
17
|
+
USAGE = "Usage: protostar [compile] [OPTION] PROTO_FILES\n protostar lint [OPTION] PROTO_FILES_OR_DIRS\n" \
|
|
18
|
+
" protostar --version"
|
|
19
|
+
DEFAULTS = "Without -I, the directory of each PROTO_FILE is an include path and the file is named by its\n" \
|
|
20
|
+
"basename. With one PROTO_FILE and no --*_out, what --ruby_out would write is printed to stdout."
|
|
21
|
+
NO_STDIN = "Reading a .proto from stdin (-) is not supported; pass the file's path."
|
|
22
|
+
Emitters = Compiler::Emitters
|
|
23
|
+
RBS_EDITIONS = "is an editions file, but code generator --rbs_out hasn't been updated to support editions yet. " \
|
|
24
|
+
"Please ask the owner of this code generator to add support or switch back to proto2/proto3.\n\n" \
|
|
25
|
+
"See https://protobuf.dev/editions/overview/ for more information."
|
|
26
|
+
NO_MAPPING = "File does not reside within any path specified using --proto_path (or -I). You must specify a " \
|
|
27
|
+
"--proto_path which encompasses this file. Note that the proto_path must be an exact prefix of the .proto " \
|
|
28
|
+
"file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent " \
|
|
29
|
+
"(it's harder than you think)."
|
|
30
|
+
|
|
31
|
+
# Raised to stop with a message on stderr and exit status 1
|
|
32
|
+
class Failure < Protostar::Error; end
|
|
33
|
+
|
|
34
|
+
# @param argv [Array<String>]
|
|
35
|
+
# @param out [IO]
|
|
36
|
+
# @param err [IO]
|
|
37
|
+
# @return [Integer] the exit status
|
|
38
|
+
def self.run(argv, out: $stdout, err: $stderr)
|
|
39
|
+
new(out, err).run(argv)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Whether to colour what goes to `io`: a terminal, and NO_COLOR unset
|
|
43
|
+
# @api private
|
|
44
|
+
def self.color?(io)
|
|
45
|
+
io.respond_to?(:tty?) && io.tty? && ENV.fetch("NO_COLOR", "").empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(out, err)
|
|
49
|
+
@out = out
|
|
50
|
+
@err = err
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Integer]
|
|
54
|
+
def run(argv)
|
|
55
|
+
argv = argv.dup
|
|
56
|
+
case argv.first
|
|
57
|
+
when "--version", "-v"
|
|
58
|
+
@out.puts "protostar #{VERSION}"
|
|
59
|
+
0
|
|
60
|
+
when "--help", "-h", nil
|
|
61
|
+
@out.puts USAGE, "", DEFAULTS, "", parser({}).summarize
|
|
62
|
+
argv.empty? ? 1 : 0
|
|
63
|
+
when "compile"
|
|
64
|
+
argv.shift
|
|
65
|
+
compile(argv)
|
|
66
|
+
when "lint"
|
|
67
|
+
argv.shift
|
|
68
|
+
Lint::Command.new(@out, @err).run(argv)
|
|
69
|
+
else
|
|
70
|
+
return compile(argv) unless command?(argv.first)
|
|
71
|
+
@err.puts "Unknown command: #{argv.first}", USAGE
|
|
72
|
+
1
|
|
73
|
+
end
|
|
74
|
+
rescue Failure => e
|
|
75
|
+
@err.write(e.message)
|
|
76
|
+
@err.write("\n") unless e.message.end_with?("\n")
|
|
77
|
+
1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# A bare word that is not a file is a mistyped command, not a missing input
|
|
83
|
+
def command?(arg)
|
|
84
|
+
!arg.start_with?("-") && !arg.end_with?(".proto") && !::File.exist?(arg)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parser(opts)
|
|
88
|
+
OptionParser.new do |o|
|
|
89
|
+
o.banner = USAGE
|
|
90
|
+
o.on("-I", "--proto_path=PATH", "Directory to search for imports; repeatable, or colon-separated") do |v|
|
|
91
|
+
(opts[:include] ||= []).concat(v.split(":"))
|
|
92
|
+
end
|
|
93
|
+
o.on("--ruby_out=DIR", "Write protostar's native Ruby (*_pb.rb)") { |v| opts[:ruby] = v }
|
|
94
|
+
o.on("--google_out=DIR", "Write protoc's google-protobuf Ruby (*_pb.rb)") { |v| opts[:google] = v }
|
|
95
|
+
o.on("--rbs_out=DIR", "Write RBS signatures (*_pb.rbs): for protostar's classes with --ruby_out, else protoc's") { |v| opts[:rbs] = v }
|
|
96
|
+
o.on("--grpc_out=DIR", "Write gRPC service stubs (*_services_pb.rb)") { |v| opts[:grpc] = v }
|
|
97
|
+
o.on("--descriptor_set_out=FILE", "Write a FileDescriptorSet") { |v| opts[:descriptor_set] = v }
|
|
98
|
+
o.on("--include_imports", "With --descriptor_set_out, include every import") { opts[:include_imports] = true }
|
|
99
|
+
o.on("--include_source_info", "With --descriptor_set_out, keep source info") { opts[:include_source_info] = true }
|
|
100
|
+
o.on("--error_format=FORMAT", "gcc (default), msvs, or rich: rustc-style, with the source and a fix") do |v|
|
|
101
|
+
opts[:error_format] = v
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def compile(argv)
|
|
107
|
+
opts = {}
|
|
108
|
+
files = begin
|
|
109
|
+
parser(opts).parse(argv)
|
|
110
|
+
rescue OptionParser::ParseError => e
|
|
111
|
+
raise Failure, e.message
|
|
112
|
+
end
|
|
113
|
+
format = opts.fetch(:error_format, "gcc")
|
|
114
|
+
raise Failure, "Unknown error format: #{format}" unless %w[gcc msvs rich].include?(format)
|
|
115
|
+
@format = format.to_sym
|
|
116
|
+
raise Failure, NO_STDIN if files.include?("-")
|
|
117
|
+
raise Failure, "Missing input file." if files.empty?
|
|
118
|
+
outputs = %i[ruby google rbs grpc descriptor_set].select { |k| opts[k] }
|
|
119
|
+
if outputs.empty?
|
|
120
|
+
raise Failure, "--include_imports writes a set of files; add --descriptor_set_out=FILE." if opts[:include_imports]
|
|
121
|
+
raise Failure, "#{files.size} files cannot share stdout; add --ruby_out=DIR to write a _pb.rb for each." if files.size > 1
|
|
122
|
+
end
|
|
123
|
+
include = opts[:include] || default_include(files)
|
|
124
|
+
compiler = Compiler.new(include_paths: include)
|
|
125
|
+
names = files.map { |f| opts[:include] ? virtual_name(f, include, compiler) : default_name(f, include, compiler) }
|
|
126
|
+
compiled = begin
|
|
127
|
+
compiler.compile(names, source_info: true)
|
|
128
|
+
rescue Compiler::Error => e
|
|
129
|
+
lines = e.diagnostics.map { |d| show(d, compiler) }
|
|
130
|
+
lines.concat(import_hints(e.diagnostics, include)) unless opts[:include]
|
|
131
|
+
raise Failure, lines.join("\n")
|
|
132
|
+
end
|
|
133
|
+
compiler.diagnostics.each { |d| @err.puts show(d, compiler) }
|
|
134
|
+
named = names.map { |n| compiled.find { |f| f.name == n } }.uniq
|
|
135
|
+
return print_ruby(named.first) if outputs.empty?
|
|
136
|
+
written = generate(opts, named, compiled, compiler)
|
|
137
|
+
written.each do |path, content|
|
|
138
|
+
FileUtils.mkdir_p(::File.dirname(path))
|
|
139
|
+
::File.binwrite(path, content)
|
|
140
|
+
end
|
|
141
|
+
0
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# protoc's one line, or with --error_format=rich, rustc's several
|
|
145
|
+
def show(diagnostic, compiler)
|
|
146
|
+
return diagnostic.format(@format) unless @format == :rich
|
|
147
|
+
@renderer ||= Diagnostics::Renderer.new(color: CLI.color?(@err),
|
|
148
|
+
source: ->(d) { (path = compiler.resolve(d.file)) && ::File.binread(path) })
|
|
149
|
+
@renderer.render(diagnostic)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def print_ruby(file)
|
|
153
|
+
@out.write(Emitters::Ruby.new.file(file))
|
|
154
|
+
0
|
|
155
|
+
rescue Emitters::Error => e
|
|
156
|
+
raise Failure, "#{file.name}: #{e.message}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The directories of the inputs on disk, in the order given. Each input is
|
|
160
|
+
# named by its basename then, so two inputs of one name cannot both be
|
|
161
|
+
def default_include(files)
|
|
162
|
+
seen = {}
|
|
163
|
+
files.select { |f| ::File.file?(f) }.map do |f|
|
|
164
|
+
other = seen[::File.basename(f)] ||= f
|
|
165
|
+
unless ::File.expand_path(other) == ::File.expand_path(f)
|
|
166
|
+
raise Failure, "#{other} and #{f} are both #{::File.basename(f)}; pass -I with a directory above both to tell them apart."
|
|
167
|
+
end
|
|
168
|
+
::File.dirname(f)
|
|
169
|
+
end.uniq { |dir| ::File.expand_path(dir) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def default_name(arg, include, compiler)
|
|
173
|
+
return virtual_name(arg, include, compiler) unless ::File.file?(arg)
|
|
174
|
+
name = ::File.basename(arg)
|
|
175
|
+
found = compiler.resolve(name)
|
|
176
|
+
return name if found == ::File.expand_path(arg)
|
|
177
|
+
raise Failure, "#{arg}: Input is shadowed by #{shorten(found)}, which is next to another input. Pass -I to choose one."
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# An import not found, but one directory above an input's, was most
|
|
181
|
+
# likely meant relative to that parent
|
|
182
|
+
def import_hints(diagnostics, include)
|
|
183
|
+
missing = diagnostics.select { |d| d.line.nil? && d.message == "File not found." }.map(&:file).uniq
|
|
184
|
+
missing.filter_map do |name|
|
|
185
|
+
parent = include.map { |dir| ::File.dirname(::File.expand_path(dir)) }
|
|
186
|
+
.find { |dir| ::File.file?(::File.join(dir, name)) }
|
|
187
|
+
next unless parent
|
|
188
|
+
shown = shorten(parent)
|
|
189
|
+
"#{name} is in #{shown}/, which is not an include path. Add -I #{shown} to find it there."
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def shorten(dir)
|
|
194
|
+
cwd = ::Dir.pwd
|
|
195
|
+
return "." if dir == cwd
|
|
196
|
+
dir.delete_prefix("#{cwd}/")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Every output, generated before anything is written, as protoc does
|
|
200
|
+
def generate(opts, named, compiled, compiler)
|
|
201
|
+
written = {}
|
|
202
|
+
%i[ruby google rbs grpc].each do |kind|
|
|
203
|
+
next unless (dir = opts[kind])
|
|
204
|
+
check_dir(dir)
|
|
205
|
+
flag = "--#{kind}_out"
|
|
206
|
+
named.each do |file|
|
|
207
|
+
if kind == :rbs && file.syntax == :editions && !native_rbs?(opts)
|
|
208
|
+
raise Failure, "#{file.name}: #{RBS_EDITIONS}--rbs_out: "
|
|
209
|
+
end
|
|
210
|
+
begin
|
|
211
|
+
emitter(kind, compiler, opts).emit([file]).each { |path, content| written[::File.join(dir, path)] = content }
|
|
212
|
+
rescue Emitters::Error => e
|
|
213
|
+
raise Failure, "#{flag}: #{file.name}: #{e.message}"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
if (path = opts[:descriptor_set])
|
|
218
|
+
set = Emitters::DescriptorSet.new(include_imports: opts[:include_imports],
|
|
219
|
+
include_source_info: opts[:include_source_info], pool: compiler.pool)
|
|
220
|
+
written[path] = set.emit(named, available: compiled)
|
|
221
|
+
end
|
|
222
|
+
written
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def emitter(kind, compiler, opts)
|
|
226
|
+
case kind
|
|
227
|
+
when :ruby then Emitters::Ruby.new
|
|
228
|
+
when :google then Emitters::GoogleRuby.new(pool: compiler.pool)
|
|
229
|
+
when :rbs then native_rbs?(opts) ? Emitters::ProtostarRbs.new : Emitters::Rbs.new
|
|
230
|
+
when :grpc then Emitters::Grpc.new
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# --rbs_out describes protostar's classes next to --ruby_out, and
|
|
235
|
+
# google-protobuf's as protoc does otherwise
|
|
236
|
+
def native_rbs?(opts)
|
|
237
|
+
opts[:ruby] && !opts[:google]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def check_dir(dir)
|
|
241
|
+
raise Failure, "#{dir}/: No such file or directory" unless ::File.directory?(dir)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# protoc's MakeInputsBeProtoPathRelative: a file argument is a path on
|
|
245
|
+
# disk under an include path, or else a name relative to one
|
|
246
|
+
def virtual_name(arg, include, compiler)
|
|
247
|
+
unless ::File.exist?(arg)
|
|
248
|
+
return arg if compiler.resolve(arg)
|
|
249
|
+
raise Failure, "Could not make proto path relative: #{arg}: No such file or directory"
|
|
250
|
+
end
|
|
251
|
+
mapped = disk_to_virtual(arg, include)
|
|
252
|
+
return mapped if mapped
|
|
253
|
+
return arg if compiler.resolve(arg)
|
|
254
|
+
raise Failure, "#{arg}: #{NO_MAPPING}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def disk_to_virtual(arg, include)
|
|
258
|
+
path = Compiler.canonical_path(arg)
|
|
259
|
+
include.each do |dir|
|
|
260
|
+
dir = Compiler.canonical_path(dir)
|
|
261
|
+
return path if dir.empty? && !path.start_with?("/")
|
|
262
|
+
return path.delete_prefix("#{dir}/") if path.start_with?("#{dir}/")
|
|
263
|
+
end
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Codec
|
|
5
|
+
# Writes a decoding loop for one message class as Ruby source and evals
|
|
6
|
+
# it into the class as `__decode_fields`. The loop dispatches on the whole
|
|
7
|
+
# tag with one `case` (field number and wire type at once), reads varints
|
|
8
|
+
# of one byte and fixed-width values inline, and keeps the read position
|
|
9
|
+
# in a local. Anything it has no branch for (unknown fields, wire type
|
|
10
|
+
# mismatches, end-group tags, malformed tags) goes to {Decoder.field}.
|
|
11
|
+
#
|
|
12
|
+
# A generic loop over field descriptors spent most of its time in method
|
|
13
|
+
# calls on the field table; this does the same work with a fraction of them.
|
|
14
|
+
# @api private
|
|
15
|
+
module DecodeGenerator
|
|
16
|
+
F = Message::FieldInfo
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param klass [Class]
|
|
21
|
+
# @param fields [Array<Message::FieldInfo>]
|
|
22
|
+
def install(klass, fields)
|
|
23
|
+
klass.class_eval(source(fields), "(protostar decoder #{klass.descriptor.full_name})", 1)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def source(fields)
|
|
27
|
+
branches = fields.map { |info| branch(info) }.join
|
|
28
|
+
packed = fields.select { |info| info.packable && VARINT_KINDS.include?(info.kind) }.map { |info| packed_method(info) }
|
|
29
|
+
<<~RUBY
|
|
30
|
+
# frozen_string_literal: true
|
|
31
|
+
#{packed.join}
|
|
32
|
+
def __decode_fields(r, depth, end_group, frozen)
|
|
33
|
+
#{"values = @values" unless fields.empty?}
|
|
34
|
+
buf = r.buffer
|
|
35
|
+
limit = r.limit
|
|
36
|
+
pos = r.pos
|
|
37
|
+
while pos < limit
|
|
38
|
+
at = pos
|
|
39
|
+
if (tag = buf.getbyte(pos)) < 0x80
|
|
40
|
+
pos += 1
|
|
41
|
+
elsif pos + 1 < limit && (b = buf.getbyte(pos + 1)) < 0x80
|
|
42
|
+
tag = (tag & 0x7F) | (b << 7)
|
|
43
|
+
pos += 2
|
|
44
|
+
else
|
|
45
|
+
r.pos = pos
|
|
46
|
+
tag = r.read_tag_varint
|
|
47
|
+
pos = r.pos
|
|
48
|
+
end
|
|
49
|
+
#{branches.empty? ? "if false" : "case tag"}
|
|
50
|
+
#{branches}
|
|
51
|
+
else
|
|
52
|
+
r.pos = pos
|
|
53
|
+
if Protostar::Codec::Decoder.field(self, r, tag, at, depth, end_group, frozen)
|
|
54
|
+
@unknown&.freeze
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
limit = r.limit
|
|
58
|
+
pos = r.pos
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
r.pos = pos
|
|
62
|
+
Protostar::Codec::Decoder.finish(self, r, end_group)
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def branch(info)
|
|
68
|
+
i = info.index
|
|
69
|
+
tag = (info.number << 3) | info.wire_type
|
|
70
|
+
out = "when #{tag} # #{info.name}\n"
|
|
71
|
+
out << if info.map
|
|
72
|
+
slow("Protostar::Codec::Decoder.map_entry(self, values, self.class.__fields[#{i}], r, depth, frozen)")
|
|
73
|
+
elsif info.message?
|
|
74
|
+
if info.repeated
|
|
75
|
+
slow("Protostar::Codec::Decoder.repeated_message(values, self.class.__fields[#{i}], r, depth, frozen, at)")
|
|
76
|
+
else
|
|
77
|
+
slow("Protostar::Codec::Decoder.singular_message(values, self.class.__fields[#{i}], r, depth, frozen, at)")
|
|
78
|
+
end
|
|
79
|
+
elsif info.kind == F::ENUM
|
|
80
|
+
enum(info)
|
|
81
|
+
else
|
|
82
|
+
"#{read(info)}#{store(info, "v")}"
|
|
83
|
+
end
|
|
84
|
+
if info.packable
|
|
85
|
+
packed_tag = (info.number << 3) | Wire::LENGTH_DELIMITED
|
|
86
|
+
out << "when #{packed_tag} # #{info.name}, packed\n"
|
|
87
|
+
out << if VARINT_KINDS.include?(info.kind)
|
|
88
|
+
"#{varint("n")}raise Protostar::DecodeError.new(\"length \#{n} runs past the end of the input\", offset: pos) " \
|
|
89
|
+
"if pos + n > limit\npos = __decode_packed#{i}(r, buf, pos, pos + n, limit, (values[#{i}] ||= []))\n"
|
|
90
|
+
else
|
|
91
|
+
slow("Protostar::Codec::Decoder.packed(self, values, self.class.__fields[#{i}], r)")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
out
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
VARINT_KINDS = [F::INT32, F::INT64, F::UINT32, F::UINT64, F::SINT32, F::SINT64, F::BOOL].freeze
|
|
98
|
+
|
|
99
|
+
# A method reading a packed run of varints into `list` in one loop,
|
|
100
|
+
# rather than a Decoder call per element; it returns the position
|
|
101
|
+
# after the run. Kept out of `__decode_fields`, which YJIT runs slower
|
|
102
|
+
# with the loops inline.
|
|
103
|
+
def packed_method(info)
|
|
104
|
+
<<~RUBY
|
|
105
|
+
def __decode_packed#{info.index}(r, buf, pos, stop, limit, list)
|
|
106
|
+
while pos < stop
|
|
107
|
+
#{read(info, "stop")}list << v
|
|
108
|
+
end
|
|
109
|
+
pos
|
|
110
|
+
end
|
|
111
|
+
RUBY
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Hands the reader to a Decoder method and picks the position back up
|
|
115
|
+
def slow(call)
|
|
116
|
+
"r.pos = pos\n#{call}\npos = r.pos\n"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Source reading a varint into `var`, inline when it is one or two
|
|
120
|
+
# bytes; `lim` is where its input ends (a packed run's end, say)
|
|
121
|
+
def varint(var, lim = "limit")
|
|
122
|
+
fallback = if lim == "limit"
|
|
123
|
+
"r.pos = pos\n#{var} = r.read_varint\npos = r.pos\n"
|
|
124
|
+
else
|
|
125
|
+
"r.pos = pos\nr.pop_limit(#{lim})\n#{var} = r.read_varint\nr.pop_limit(limit)\npos = r.pos\n"
|
|
126
|
+
end
|
|
127
|
+
<<~RUBY
|
|
128
|
+
if pos < #{lim} && (#{var} = buf.getbyte(pos)) < 0x80
|
|
129
|
+
pos += 1
|
|
130
|
+
elsif pos + 1 < #{lim} && (b = buf.getbyte(pos + 1)) < 0x80
|
|
131
|
+
#{var} = (#{var} & 0x7F) | (b << 7)
|
|
132
|
+
pos += 2
|
|
133
|
+
else
|
|
134
|
+
#{fallback}end
|
|
135
|
+
RUBY
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def fixed(size, directive)
|
|
139
|
+
<<~RUBY
|
|
140
|
+
raise Protostar::DecodeError.new("truncated #{size * 8}-bit value", offset: pos) if pos + #{size} > limit
|
|
141
|
+
v = buf.unpack1(#{directive.inspect}, offset: pos)
|
|
142
|
+
pos += #{size}
|
|
143
|
+
RUBY
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Source that leaves the field's next value in `v`
|
|
147
|
+
def read(info, lim = "limit")
|
|
148
|
+
case info.kind
|
|
149
|
+
when F::INT32 then "#{varint("v", lim)}v -= 0x1_0000_0000 if (v &= 0xFFFF_FFFF) >= 0x8000_0000\n"
|
|
150
|
+
when F::INT64 then "#{varint("v", lim)}v -= 0x1_0000_0000_0000_0000 if v >= 0x8000_0000_0000_0000\n"
|
|
151
|
+
when F::UINT32 then "#{varint("v", lim)}v &= 0xFFFF_FFFF\n"
|
|
152
|
+
when F::UINT64 then varint("v", lim)
|
|
153
|
+
when F::SINT32 then "#{varint("v", lim)}v &= 0xFFFF_FFFF\nv = (v >> 1) ^ -(v & 1)\n"
|
|
154
|
+
when F::SINT64 then "#{varint("v", lim)}v = (v >> 1) ^ -(v & 1)\n"
|
|
155
|
+
when F::BOOL then "#{varint("v", lim)}v = v != 0\n"
|
|
156
|
+
when F::STRING, F::BYTES then bytes(info)
|
|
157
|
+
else fixed(*F::FIXED.fetch(info.kind))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def bytes(info)
|
|
162
|
+
src = varint("n")
|
|
163
|
+
src << "raise Protostar::DecodeError.new(\"length \#{n} runs past the end of the input\", offset: pos) if pos + n > limit\n"
|
|
164
|
+
src << "v = buf.byteslice(pos, n)\npos += n\n"
|
|
165
|
+
if info.kind == F::BYTES
|
|
166
|
+
src << "v.force_encoding(Encoding::BINARY).freeze\n"
|
|
167
|
+
else
|
|
168
|
+
src << "v.force_encoding(Encoding::UTF_8)\n"
|
|
169
|
+
if info.utf8_validation
|
|
170
|
+
src << "raise Protostar::DecodeError.new(\"invalid UTF-8 in #{info.descriptor.full_name}\", offset: pos - n) " \
|
|
171
|
+
"unless v.valid_encoding?\n"
|
|
172
|
+
end
|
|
173
|
+
src << "v.freeze\n"
|
|
174
|
+
end
|
|
175
|
+
src
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def enum(info)
|
|
179
|
+
src = info.closed_enum ? "start = pos\n#{varint("v")}" : varint("v")
|
|
180
|
+
src << "v -= 0x1_0000_0000 if (v &= 0xFFFF_FFFF) >= 0x8000_0000\n"
|
|
181
|
+
src << "if (e = self.class.__fields[#{info.index}].enum_names[v])\n"
|
|
182
|
+
src << store(info, "e")
|
|
183
|
+
src << if info.closed_enum
|
|
184
|
+
"else\nProtostar::Codec::Decoder.closed_enum_value(self, #{info.number}, buf, start, pos)\n"
|
|
185
|
+
else
|
|
186
|
+
"else\n#{store(info, "v")}"
|
|
187
|
+
end
|
|
188
|
+
src << "end\n"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def store(info, var)
|
|
192
|
+
i = info.index
|
|
193
|
+
return "(values[#{i}] ||= []) << #{var}\n" if info.repeated
|
|
194
|
+
siblings = info.siblings ? info.siblings.map { |s| "values[#{s}] = nil\n" }.join : ""
|
|
195
|
+
"#{siblings}values[#{i}] = #{var}\n"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|