protobug-compiler 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/exe/protoc-gen-protobug +16 -0
- data/lib/protobug/compiler/builder.rb +260 -0
- data/lib/protobug/compiler/builder_gen.rb +75 -0
- data/lib/protobug/compiler.rb +500 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6c3c9d32d7ba7357c9bda140e4b67634871c4ea151c0a35684f9a6dd9030f53a
|
4
|
+
data.tar.gz: 58b29936acaffa8906497d83488d4f9bf2a46d3ff4418f6680905c688ed84209
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33ad398f7e589f6cfb6b1a67473dd28d5c81d3bf5714b7f4cbc31a0fb2524dfa953babbea5853c2772b5dcb2bce9849fee35ccb089b4743f205cdb7455932af4
|
7
|
+
data.tar.gz: 034d83ffaa55fb291437704742fe62185584e18c451b2346fbacb5f830105afe10297226f063b2ac8e7a25d9d8788fb2f4c9837622a528211933bf9c390971a8
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Samuel Giddins
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "protobug_compiler_protos"
|
5
|
+
require "protobug/compiler"
|
6
|
+
|
7
|
+
compiler_registry = Protobug::Registry.new do |registry|
|
8
|
+
Google::Protobuf::Compiler.register_plugin_protos(registry)
|
9
|
+
end
|
10
|
+
|
11
|
+
request = Google::Protobuf::Compiler::CodeGeneratorRequest.decode($stdin, registry: compiler_registry)
|
12
|
+
SimpleCov.command_name "protoc-gen-protobug:#{Digest::SHA256.hexdigest(request.to_json)}" if defined?(SimpleCov)
|
13
|
+
|
14
|
+
response = Protobug::Compiler.compile!(request)
|
15
|
+
|
16
|
+
$stdout.print response.class.encode(response)
|
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "forwardable"
|
5
|
+
require "prettier_print"
|
6
|
+
|
7
|
+
module Protobug
|
8
|
+
class Compiler
|
9
|
+
module Builder
|
10
|
+
def self.build_file
|
11
|
+
builder = File.new
|
12
|
+
if block_given?
|
13
|
+
yield builder
|
14
|
+
builder.render
|
15
|
+
else
|
16
|
+
builder
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Statement
|
21
|
+
attr_reader :items
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@items = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def comment(text)
|
28
|
+
append Comment.new(text)
|
29
|
+
end
|
30
|
+
|
31
|
+
def empty
|
32
|
+
append Token.new(type: :empty, content: "")
|
33
|
+
end
|
34
|
+
|
35
|
+
def identifier(name)
|
36
|
+
append Token.new(type: :identifier, content: name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def literal(content)
|
40
|
+
append Token.new(type: :literal, content: content)
|
41
|
+
end
|
42
|
+
|
43
|
+
def block(&blk)
|
44
|
+
append Group.new(
|
45
|
+
name: "block", items: [], close: "end", multi: true, indent: 2
|
46
|
+
).tap(&blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dot(name)
|
50
|
+
append(Token.new(type: :delimiter, content: ".")).identifier(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def op(operator)
|
54
|
+
append Token.new(type: :operator, content: operator)
|
55
|
+
end
|
56
|
+
|
57
|
+
def render(q) # rubocop:disable Naming/MethodParameterName
|
58
|
+
first = true
|
59
|
+
prev = nil
|
60
|
+
q.group do
|
61
|
+
items.each do |item|
|
62
|
+
next if item.empty?
|
63
|
+
|
64
|
+
if !first && !item.compact? && !prev&.compact?
|
65
|
+
if (item.is_a?(Token) && item.type == :operator) || (prev.is_a?(Token) && prev.type == :operator) ||
|
66
|
+
item.is_a?(Comment)
|
67
|
+
q.text " "
|
68
|
+
elsif !(item.is_a?(Group) && item.name == :call) && !(item.is_a?(Group) && item.multi)
|
69
|
+
q.fill_breakable " "
|
70
|
+
end
|
71
|
+
end
|
72
|
+
first = false unless item.compact? || prev&.compact?
|
73
|
+
prev = item
|
74
|
+
|
75
|
+
item.render(q)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def append(item)
|
81
|
+
items << item
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def empty?
|
86
|
+
items.none? { |item| !item.empty? }
|
87
|
+
end
|
88
|
+
|
89
|
+
def compact? = false
|
90
|
+
end
|
91
|
+
|
92
|
+
Group = Struct.new(:name, :items, :open, :close, :separator, :multi, :indent,
|
93
|
+
keyword_init: true) do
|
94
|
+
include Builder
|
95
|
+
|
96
|
+
def comment(text)
|
97
|
+
append Statement.new.comment(text)
|
98
|
+
end
|
99
|
+
|
100
|
+
def empty
|
101
|
+
append Statement.new.empty
|
102
|
+
end
|
103
|
+
|
104
|
+
def block(&blk)
|
105
|
+
append Statement.new.block(&blk)
|
106
|
+
end
|
107
|
+
|
108
|
+
def identifier(name)
|
109
|
+
append Statement.new.identifier(name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def literal(content)
|
113
|
+
append Statement.new.literal(content)
|
114
|
+
end
|
115
|
+
|
116
|
+
def op(operator)
|
117
|
+
append Statement.new.op(operator)
|
118
|
+
end
|
119
|
+
|
120
|
+
def render(q) # rubocop:disable Naming/MethodParameterName
|
121
|
+
if name == "block" && empty?
|
122
|
+
q.breakable "; "
|
123
|
+
q.text "end"
|
124
|
+
end
|
125
|
+
|
126
|
+
q.group do
|
127
|
+
q.text open if open
|
128
|
+
q.nest(indent || 0) do
|
129
|
+
write_items(q)
|
130
|
+
end
|
131
|
+
return unless close
|
132
|
+
|
133
|
+
multi ? q.breakable_force : q.breakable_empty
|
134
|
+
q.text close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def append(item)
|
139
|
+
items << item
|
140
|
+
item
|
141
|
+
end
|
142
|
+
|
143
|
+
def empty?
|
144
|
+
return false if name != :call && (open || close)
|
145
|
+
|
146
|
+
items.none? { |item| !item.empty? }
|
147
|
+
end
|
148
|
+
|
149
|
+
def compact? = false
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def write_items(q) # rubocop:disable Naming/MethodParameterName
|
154
|
+
multi ? q.breakable_force : q.breakable_empty
|
155
|
+
q.seplist(items.reject(&:empty?), lambda {
|
156
|
+
if separator == "\n" || multi
|
157
|
+
q.breakable_force
|
158
|
+
elsif separator
|
159
|
+
q.text separator.rstrip
|
160
|
+
if separator.end_with?(" ")
|
161
|
+
q.breakable
|
162
|
+
else
|
163
|
+
q.breakable_empty
|
164
|
+
end
|
165
|
+
end
|
166
|
+
}, :each) do |item|
|
167
|
+
item.render(q)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class File < DelegateClass(Group)
|
173
|
+
def initialize
|
174
|
+
@headers = []
|
175
|
+
super(Group.new(name: "file", items: [], multi: true))
|
176
|
+
end
|
177
|
+
|
178
|
+
def header_comment(text)
|
179
|
+
@headers << text
|
180
|
+
end
|
181
|
+
|
182
|
+
def render
|
183
|
+
q = PrettierPrint.new(+"")
|
184
|
+
@headers.each_with_index do |header, idx|
|
185
|
+
q.breakable_force if idx.positive?
|
186
|
+
Comment.new(header).render(q)
|
187
|
+
q.breakable_force
|
188
|
+
end
|
189
|
+
super(q)
|
190
|
+
q.trim
|
191
|
+
q.breakable_force
|
192
|
+
q.flush
|
193
|
+
q.output
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
Comment = Struct.new(:comment) do
|
198
|
+
def render(q) # rubocop:disable Naming/MethodParameterName
|
199
|
+
if comment.start_with?("#")
|
200
|
+
q.text(comment)
|
201
|
+
else
|
202
|
+
prefixed = comment.start_with?(" ")
|
203
|
+
|
204
|
+
q.group do
|
205
|
+
q.break_parent
|
206
|
+
q.seplist(
|
207
|
+
comment.chomp.gsub(/^/m, prefixed ? "#" : "# ").tap do |s|
|
208
|
+
s.gsub!(/^#\s*$/m, "#")
|
209
|
+
end.each_line(chomp: true),
|
210
|
+
-> { q.breakable_empty }
|
211
|
+
) do |line|
|
212
|
+
q.text line
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def empty? = false
|
219
|
+
def compact? = false
|
220
|
+
end
|
221
|
+
|
222
|
+
Token = Struct.new(:type, :content,
|
223
|
+
keyword_init: true) do
|
224
|
+
def render(q) # rubocop:disable Naming/MethodParameterName
|
225
|
+
case type
|
226
|
+
when :empty, :keyword, :identifier, :delimiter, :operator
|
227
|
+
q.text content
|
228
|
+
when :literal
|
229
|
+
case content
|
230
|
+
when Integer
|
231
|
+
int_part = Integer(content)
|
232
|
+
formatted_int = int_part.abs.to_s.reverse.gsub(/...(?=.)/, '\&_').reverse
|
233
|
+
formatted_int.insert(0, "-") if int_part.negative?
|
234
|
+
q.text formatted_int
|
235
|
+
else
|
236
|
+
q.text content.inspect
|
237
|
+
end
|
238
|
+
else
|
239
|
+
raise "Unknown token type: #{type}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def empty? = false
|
244
|
+
|
245
|
+
COMPACT_OPERATORS = %w[** .. ...].freeze # rubocop:disable Lint/ConstantDefinitionInBlock
|
246
|
+
|
247
|
+
def compact?
|
248
|
+
case type
|
249
|
+
when :operator
|
250
|
+
COMPACT_OPERATORS.include?(content)
|
251
|
+
when :delimiter
|
252
|
+
content == "."
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
require_relative "builder_gen"
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Protobug
|
4
|
+
class Compiler
|
5
|
+
module Builder
|
6
|
+
class Statement
|
7
|
+
def _class
|
8
|
+
append Token.new(type: :keyword, content: "class")
|
9
|
+
end
|
10
|
+
|
11
|
+
def _def
|
12
|
+
append Token.new(type: :keyword, content: "def")
|
13
|
+
end
|
14
|
+
|
15
|
+
def _module
|
16
|
+
append Token.new(type: :keyword, content: "module")
|
17
|
+
end
|
18
|
+
|
19
|
+
def parens(item, &blk)
|
20
|
+
append Group.new(
|
21
|
+
name: :parens,
|
22
|
+
items: [item],
|
23
|
+
open: "(",
|
24
|
+
close: ")",
|
25
|
+
indent: 2
|
26
|
+
).tap(&blk)
|
27
|
+
end
|
28
|
+
|
29
|
+
def list(*items, &blk)
|
30
|
+
append Group.new(
|
31
|
+
name: :list,
|
32
|
+
items: items,
|
33
|
+
separator: ",",
|
34
|
+
indent: 2
|
35
|
+
).tap(&blk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def index(*items, &blk)
|
39
|
+
append Group.new(
|
40
|
+
name: :index,
|
41
|
+
items: items,
|
42
|
+
open: "[",
|
43
|
+
close: "]",
|
44
|
+
separator: ","
|
45
|
+
).tap(&blk)
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(*args, &blk)
|
49
|
+
append Group.new(
|
50
|
+
name: :call,
|
51
|
+
items: args,
|
52
|
+
open: "(",
|
53
|
+
close: ")",
|
54
|
+
separator: ", ",
|
55
|
+
indent: 2
|
56
|
+
).tap(&blk)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Group
|
61
|
+
def _class
|
62
|
+
append Statement.new._class
|
63
|
+
end
|
64
|
+
|
65
|
+
def _def
|
66
|
+
append Statement.new._def
|
67
|
+
end
|
68
|
+
|
69
|
+
def _module
|
70
|
+
append Statement.new._module
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,500 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "protobug_compiler_protos"
|
4
|
+
|
5
|
+
require_relative "compiler/builder"
|
6
|
+
|
7
|
+
module Protobug
|
8
|
+
class Compiler
|
9
|
+
def self.compile!(request)
|
10
|
+
response = Google::Protobuf::Compiler::CodeGeneratorResponse.new
|
11
|
+
begin
|
12
|
+
new(request, response).compile!
|
13
|
+
rescue StandardError => e
|
14
|
+
response.error = e.full_message
|
15
|
+
end
|
16
|
+
response
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(request, response)
|
20
|
+
@request = request
|
21
|
+
@response = response
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :request, :response, :files
|
25
|
+
|
26
|
+
class Files
|
27
|
+
def initialize
|
28
|
+
@descs_by_name = {}
|
29
|
+
@files_by_path = {}
|
30
|
+
@num_files = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def register_file(file)
|
34
|
+
@files_by_path[file.name] = register_decl(FileDescriptorProto.new(file, nil, path: []))
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch(name)
|
38
|
+
@files_by_path.fetch(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_type(name)
|
42
|
+
@descs_by_name.fetch(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def register_decl(decl)
|
46
|
+
raise ArgumentError, "parent must be a Descriptor" if decl.parent && !decl.parent.is_a?(Descriptor)
|
47
|
+
|
48
|
+
unless decl.is_a?(FileDescriptorProto)
|
49
|
+
raise "already registered: #{decl.full_name}" if @descs_by_name[decl.full_name]
|
50
|
+
|
51
|
+
@descs_by_name[decl.full_name] = decl
|
52
|
+
end
|
53
|
+
|
54
|
+
decl.each_declaration do |d|
|
55
|
+
register_decl(d)
|
56
|
+
end
|
57
|
+
decl
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Descriptor
|
62
|
+
attr_reader :descriptor, :parent, :file, :source_loc
|
63
|
+
|
64
|
+
def initialize(descriptor, parent, path:)
|
65
|
+
unless descriptor.is_a?(self.class.descriptor_class)
|
66
|
+
raise ArgumentError,
|
67
|
+
"#{descriptor.class} (#{descriptor}) is not #{self.class.descriptor_class}"
|
68
|
+
end
|
69
|
+
|
70
|
+
raise ArgumentError, "parent must be a Descriptor" if parent && !parent.is_a?(Descriptor)
|
71
|
+
|
72
|
+
@descriptor = descriptor
|
73
|
+
@parent = parent
|
74
|
+
@file = parent&.file || self
|
75
|
+
@path = path
|
76
|
+
|
77
|
+
super(descriptor)
|
78
|
+
|
79
|
+
@source_loc = if parent
|
80
|
+
file.source_code_info.location.find do |loc|
|
81
|
+
loc.path == path
|
82
|
+
end
|
83
|
+
else
|
84
|
+
source_code_info.location[1]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def full_name
|
89
|
+
if parent
|
90
|
+
"#{parent.full_name}.#{name}"
|
91
|
+
else
|
92
|
+
descriptor.package
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_constant
|
97
|
+
if parent
|
98
|
+
"#{parent.to_constant}::#{name}"
|
99
|
+
else
|
100
|
+
return file.options.ruby_package if file.options&.ruby_package?
|
101
|
+
|
102
|
+
parts = descriptor.package.split(".")
|
103
|
+
parts.map! do |part|
|
104
|
+
part.split("_").map(&:capitalize).join
|
105
|
+
end
|
106
|
+
parts.join("::")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.included(base)
|
111
|
+
base.extend(ClassMethods)
|
112
|
+
base.class_eval do
|
113
|
+
methods = []
|
114
|
+
|
115
|
+
fields_by_name.each do |name, field|
|
116
|
+
unless field.is_a?(Field::MessageField) &&
|
117
|
+
/\Agoogle\.protobuf\.([^.]*Descriptor[^.]*)\z/ =~ field.message_type
|
118
|
+
next
|
119
|
+
end
|
120
|
+
raise "expected #{self}.#{name} to be repeated" unless field.repeated?
|
121
|
+
|
122
|
+
message_name = Regexp.last_match(1)
|
123
|
+
|
124
|
+
methods << name
|
125
|
+
|
126
|
+
define_method(name) do
|
127
|
+
field_type = Compiler.const_get(message_name)
|
128
|
+
super().map.with_index { |d, idx| field_type.new(d, self, path: @path + [field.number, idx]) }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
define_method(:each_declaration) do |&blk|
|
133
|
+
return enum_for(__method__) unless blk # rubocop:disable Lint/ToEnumArguments
|
134
|
+
|
135
|
+
decls = methods.flat_map { send(_1) }
|
136
|
+
decls.sort_by! { |d| d.source_loc&.span || [] }
|
137
|
+
decls.each(&blk)
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module ClassMethods
|
144
|
+
def descriptor_class
|
145
|
+
Google::Protobuf.const_get(name.split("::").last)
|
146
|
+
end
|
147
|
+
|
148
|
+
def fields_by_number = descriptor_class.fields_by_number
|
149
|
+
def fields_by_name = descriptor_class.fields_by_name
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class FileDescriptorProto < DelegateClass(Google::Protobuf::FileDescriptorProto)
|
154
|
+
include Descriptor
|
155
|
+
|
156
|
+
def file_name
|
157
|
+
ruby_package = options.ruby_package if options&.ruby_package?
|
158
|
+
prefix = if ruby_package
|
159
|
+
ruby_package.split("::").map!(&:downcase).join("/")
|
160
|
+
else
|
161
|
+
package.split(".").map!(&:downcase).join("/")
|
162
|
+
end
|
163
|
+
name.gsub(%r{^.*?([^/]+)\.proto$}, "#{prefix}/\\1_pb.rb")
|
164
|
+
end
|
165
|
+
|
166
|
+
def loc_by_path(path)
|
167
|
+
source_code_info.location.find { |loc| loc.path == path }
|
168
|
+
end
|
169
|
+
|
170
|
+
attr_accessor :file_to_generate
|
171
|
+
end
|
172
|
+
|
173
|
+
class DescriptorProto < DelegateClass(Google::Protobuf::DescriptorProto)
|
174
|
+
include Descriptor
|
175
|
+
end
|
176
|
+
|
177
|
+
class FieldDescriptorProto < DelegateClass(Google::Protobuf::FieldDescriptorProto)
|
178
|
+
include Descriptor
|
179
|
+
end
|
180
|
+
|
181
|
+
class EnumDescriptorProto < DelegateClass(Google::Protobuf::EnumDescriptorProto)
|
182
|
+
include Descriptor
|
183
|
+
end
|
184
|
+
|
185
|
+
class EnumValueDescriptorProto < DelegateClass(Google::Protobuf::EnumValueDescriptorProto)
|
186
|
+
include Descriptor
|
187
|
+
end
|
188
|
+
|
189
|
+
class ServiceDescriptorProto < DelegateClass(Google::Protobuf::ServiceDescriptorProto)
|
190
|
+
include Descriptor
|
191
|
+
end
|
192
|
+
|
193
|
+
class OneofDescriptorProto < DelegateClass(Google::Protobuf::OneofDescriptorProto)
|
194
|
+
include Descriptor
|
195
|
+
end
|
196
|
+
|
197
|
+
def compile!
|
198
|
+
response.supported_features |=
|
199
|
+
Google::Protobuf::Compiler::CodeGeneratorResponse::Feature::FEATURE_PROTO3_OPTIONAL.value
|
200
|
+
@files = Files.new
|
201
|
+
|
202
|
+
request.proto_file.each do |file|
|
203
|
+
files.register_file(file)
|
204
|
+
end
|
205
|
+
|
206
|
+
request.file_to_generate.each do |name|
|
207
|
+
files.fetch(name).file_to_generate = true
|
208
|
+
end
|
209
|
+
|
210
|
+
request.file_to_generate.each do |name| # rubocop:disable Style/CombinableLoops
|
211
|
+
file = files.fetch(name)
|
212
|
+
file_out = Google::Protobuf::Compiler::CodeGeneratorResponse::File.new
|
213
|
+
file_out.name = file.file_name
|
214
|
+
file_out.content = file_contents(files, file)
|
215
|
+
response.add_file(file_out)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def emit_decls(descriptor, group)
|
220
|
+
source_loc = descriptor.source_loc
|
221
|
+
return unless source_loc
|
222
|
+
|
223
|
+
source_loc.leading_detached_comments&.each do |c|
|
224
|
+
group.comment(c)
|
225
|
+
group.empty
|
226
|
+
end
|
227
|
+
|
228
|
+
group.comment(source_loc.leading_comments) if source_loc.leading_comments?
|
229
|
+
|
230
|
+
case descriptor
|
231
|
+
when DescriptorProto
|
232
|
+
group._class.identifier(descriptor.name).block do |g|
|
233
|
+
g.identifier("extend").identifier("Protobug::Message")
|
234
|
+
g.empty
|
235
|
+
g.identifier("self").dot("full_name").op("=").literal(descriptor.full_name)
|
236
|
+
|
237
|
+
requires_empty = true
|
238
|
+
descriptor.each_declaration do |decl|
|
239
|
+
g.empty if requires_empty
|
240
|
+
requires_empty = emit_decls(decl, g) &&
|
241
|
+
!decl.is_a?(FieldDescriptorProto) && !decl.is_a?(OneofDescriptorProto)
|
242
|
+
end
|
243
|
+
|
244
|
+
if descriptor.reserved_range.any? || descriptor.reserved_name.any?
|
245
|
+
g.empty
|
246
|
+
descriptor.file.loc_by_path(descriptor.source_loc.path +
|
247
|
+
[DescriptorProto.fields_by_name.fetch("reserved_range").number])&.tap do |loc|
|
248
|
+
g.comment(loc.leading_comments) if loc.leading_comments?
|
249
|
+
end
|
250
|
+
descriptor.reserved_range.each_with_index do |range, idx|
|
251
|
+
descriptor.file.loc_by_path(descriptor.source_loc.path + [
|
252
|
+
DescriptorProto.fields_by_name.fetch("reserved_range").number, idx
|
253
|
+
]).tap do |loc|
|
254
|
+
g.comment(loc.leading_comments) if loc.leading_comments?
|
255
|
+
end
|
256
|
+
g.identifier("reserved_range").call do |c|
|
257
|
+
c.literal(range.start).op("...").literal(range.end)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
descriptor.reserved_name.each do |name|
|
261
|
+
g.identifier("reserved_name").call do |c|
|
262
|
+
c.literal(name)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
when EnumDescriptorProto
|
268
|
+
group._class.identifier(descriptor.name).block do |g|
|
269
|
+
g.identifier("extend").identifier("Protobug::Enum")
|
270
|
+
g.empty
|
271
|
+
g.identifier("self").dot("full_name").op("=")
|
272
|
+
.literal(descriptor.full_name)
|
273
|
+
g.empty
|
274
|
+
|
275
|
+
descriptor.each_declaration do |decl|
|
276
|
+
emit_decls(decl, g)
|
277
|
+
end
|
278
|
+
|
279
|
+
if descriptor.reserved_range.any? || descriptor.reserved_name.any?
|
280
|
+
g.empty
|
281
|
+
descriptor.reserved_range.each do |range|
|
282
|
+
g.identifier("reserved_range").call do |c|
|
283
|
+
c.literal(range.start).op("..").literal(range.end - 1)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
descriptor.reserved_name.each do |name|
|
287
|
+
g.identifier("reserved_name").call do |c|
|
288
|
+
c.literal(name)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
when EnumValueDescriptorProto
|
294
|
+
const_name = descriptor.name.start_with?("k") ? "K_#{descriptor.name[1..]}" : descriptor.name
|
295
|
+
const_name = "K_#{const_name}" unless const_name.match?(/\A[A-Z]/)
|
296
|
+
group.identifier(const_name).op("=").identifier("new").call do |c|
|
297
|
+
c.literal(descriptor.name)
|
298
|
+
c.literal(descriptor.number)
|
299
|
+
end.dot("freeze")
|
300
|
+
when FieldDescriptorProto
|
301
|
+
if descriptor.extendee?
|
302
|
+
containing_type = files.fetch_type(
|
303
|
+
if descriptor.extendee.start_with?(".")
|
304
|
+
descriptor.extendee[1..]
|
305
|
+
else
|
306
|
+
"#{descriptor.parent.full_name}.#{descriptor.extendee}"
|
307
|
+
end
|
308
|
+
)
|
309
|
+
group.comment("extension: #{containing_type.full_name}\n #{descriptor.name} #{descriptor.number}")
|
310
|
+
return # rubocop:disable Lint/NonLocalExitFromIterator
|
311
|
+
end
|
312
|
+
|
313
|
+
type = descriptor.type.name.downcase.delete_prefix("type_").to_sym
|
314
|
+
|
315
|
+
if descriptor.type_name?
|
316
|
+
referenced_type = files.fetch_type(descriptor.type_name.delete_prefix("."))
|
317
|
+
type = :map unless referenced_type.source_loc
|
318
|
+
end
|
319
|
+
|
320
|
+
group.identifier(
|
321
|
+
case descriptor.label
|
322
|
+
when Google::Protobuf::FieldDescriptorProto::Label::LABEL_OPTIONAL
|
323
|
+
"optional"
|
324
|
+
when Google::Protobuf::FieldDescriptorProto::Label::LABEL_REPEATED
|
325
|
+
if type == :map
|
326
|
+
"map"
|
327
|
+
else
|
328
|
+
"repeated"
|
329
|
+
end
|
330
|
+
when Google::Protobuf::FieldDescriptorProto::Label::LABEL_REQUIRED
|
331
|
+
"required"
|
332
|
+
else
|
333
|
+
raise "Unknown label: #{descriptor.label}"
|
334
|
+
end
|
335
|
+
).call do |c|
|
336
|
+
c.literal(descriptor.number)
|
337
|
+
c.literal(descriptor.name)
|
338
|
+
c.identifier("type:").literal(type) unless type == :map
|
339
|
+
|
340
|
+
if type == :map
|
341
|
+
c.identifier("key_type:")
|
342
|
+
.literal(referenced_type.field[0].type.name.downcase.delete_prefix("type_").to_sym)
|
343
|
+
value_type = referenced_type.field[1].type.name.downcase.delete_prefix("type_").to_sym
|
344
|
+
c.identifier("value_type:")
|
345
|
+
.literal(value_type)
|
346
|
+
if referenced_type.field[1].type_name?
|
347
|
+
c.identifier("#{value_type}_type:")
|
348
|
+
.literal(referenced_type.field[1].type_name.delete_prefix("."))
|
349
|
+
end
|
350
|
+
elsif descriptor.type_name?
|
351
|
+
c.identifier("#{type}_type:").literal(descriptor.type_name.delete_prefix("."))
|
352
|
+
end
|
353
|
+
|
354
|
+
packed = descriptor.options&.packed
|
355
|
+
# TODO: exclude other types that cannot be packed
|
356
|
+
if !descriptor.options&.packed? && !%i[message bytes string map].include?(type)
|
357
|
+
packed = descriptor.label == Google::Protobuf::FieldDescriptorProto::Label::LABEL_REPEATED &&
|
358
|
+
descriptor.file.syntax == "proto3"
|
359
|
+
end
|
360
|
+
c.identifier("packed:").literal(packed) if packed
|
361
|
+
if descriptor.json_name? && descriptor.json_name != descriptor.name
|
362
|
+
c.identifier("json_name:").literal(descriptor.json_name)
|
363
|
+
end
|
364
|
+
if descriptor.oneof_index?
|
365
|
+
oneof = descriptor.parent.oneof_decl[descriptor.oneof_index]
|
366
|
+
synthetic = descriptor.proto3_optional && (descriptor.parent.field.count do |f|
|
367
|
+
f.oneof_index? && f.oneof_index == descriptor.oneof_index
|
368
|
+
end == 1)
|
369
|
+
c.identifier("oneof:").literal(oneof.name.to_sym) unless synthetic
|
370
|
+
end
|
371
|
+
if descriptor.label == Google::Protobuf::FieldDescriptorProto::Label::LABEL_OPTIONAL &&
|
372
|
+
descriptor.file.syntax == "proto3" && !descriptor.proto3_optional
|
373
|
+
c.identifier("proto3_optional:").literal(false)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
when OneofDescriptorProto
|
377
|
+
group.empty if source_loc.leading_comments?
|
378
|
+
# no-op
|
379
|
+
else
|
380
|
+
raise "Unknown descriptor type: #{descriptor.class}"
|
381
|
+
end.tap do |s|
|
382
|
+
s.comment(source_loc.trailing_comments) if source_loc.trailing_comments?
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def file_contents(files, file)
|
387
|
+
Builder.build_file do |f|
|
388
|
+
f.header_comment "frozen_string_literal: true"
|
389
|
+
f.header_comment "Code generated by protoc-gen-protobug. DO NOT EDIT."
|
390
|
+
|
391
|
+
f.comment "source: #{file.name}"
|
392
|
+
f.comment "syntax: #{file.syntax? ? file.syntax : "proto2"}"
|
393
|
+
f.comment "package: #{file.package}"
|
394
|
+
f.comment "options:"
|
395
|
+
if file.options
|
396
|
+
file.options.class.fields_by_name.each_key do |name|
|
397
|
+
next unless file.options.send(:"#{name}?")
|
398
|
+
|
399
|
+
value = case value = file.options.send(name)
|
400
|
+
when Enum::InstanceMethods
|
401
|
+
value.name
|
402
|
+
when Symbol
|
403
|
+
raise "Unknown symbol: #{value} for #{name} in #{file.options.inspect}"
|
404
|
+
else
|
405
|
+
value.inspect
|
406
|
+
end
|
407
|
+
|
408
|
+
f.comment " #{name}: #{value}"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
file.source_loc.leading_detached_comments.each do |c|
|
413
|
+
f.empty
|
414
|
+
f.comment c
|
415
|
+
end
|
416
|
+
|
417
|
+
f.empty
|
418
|
+
f.identifier("require").literal("protobug")
|
419
|
+
|
420
|
+
local, external = file.dependency.map do |dep|
|
421
|
+
files.fetch(dep)
|
422
|
+
end.partition(&:file_to_generate)
|
423
|
+
if external.any?
|
424
|
+
f.empty
|
425
|
+
external.each do |dep|
|
426
|
+
f.identifier("require").literal(dep.file_name.delete_suffix(".rb"))
|
427
|
+
end
|
428
|
+
end
|
429
|
+
if local.any?
|
430
|
+
f.empty
|
431
|
+
local.each do |dep|
|
432
|
+
relative_path = Pathname(dep.file_name.delete_suffix(".rb"))
|
433
|
+
.relative_path_from(Pathname(file.file_name).dirname)
|
434
|
+
.to_path
|
435
|
+
f.identifier("require_relative").literal(relative_path)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
f.empty
|
440
|
+
|
441
|
+
g = f
|
442
|
+
file.to_constant.split("::").each do |part|
|
443
|
+
g._module.identifier(part).block { |g_| g = g_ }
|
444
|
+
end
|
445
|
+
|
446
|
+
first = true
|
447
|
+
file.each_declaration do |decl|
|
448
|
+
g.empty unless first
|
449
|
+
emit_decls(decl, g)
|
450
|
+
first = false
|
451
|
+
end
|
452
|
+
|
453
|
+
g.empty unless first
|
454
|
+
g._def.identifier("self")
|
455
|
+
.dot("register_#{File.basename file.name.delete_suffix(".proto")}_protos")
|
456
|
+
.call do |c|
|
457
|
+
c.identifier("registry")
|
458
|
+
end.block do |defn|
|
459
|
+
file.dependency.each do |dep|
|
460
|
+
defn.identifier(files.fetch(dep).to_constant)
|
461
|
+
.dot("register_#{File.basename dep.delete_suffix(".proto")}_protos")
|
462
|
+
.call do |c|
|
463
|
+
c.identifier("registry")
|
464
|
+
end
|
465
|
+
end
|
466
|
+
emit_register(defn, file)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def emit_register(defn, descriptor)
|
472
|
+
case descriptor
|
473
|
+
when DescriptorProto, EnumDescriptorProto
|
474
|
+
return unless descriptor.source_loc
|
475
|
+
|
476
|
+
defn.identifier("registry").dot("register").call do |c|
|
477
|
+
c.identifier(descriptor.to_constant)
|
478
|
+
end
|
479
|
+
when FieldDescriptorProto
|
480
|
+
return unless descriptor.extendee?
|
481
|
+
|
482
|
+
containing_type = files.fetch_type(
|
483
|
+
if descriptor.extendee.start_with?(".")
|
484
|
+
descriptor.extendee[1..]
|
485
|
+
else
|
486
|
+
"#{descriptor.parent.full_name}.#{descriptor.extendee}"
|
487
|
+
end
|
488
|
+
)
|
489
|
+
defn.comment("extension: #{containing_type.full_name}\n #{descriptor.type} #{descriptor.number}")
|
490
|
+
when FileDescriptorProto, EnumValueDescriptorProto, ServiceDescriptorProto, OneofDescriptorProto
|
491
|
+
# no-op
|
492
|
+
else
|
493
|
+
raise "Unknown descriptor type: #{descriptor.class}"
|
494
|
+
end
|
495
|
+
descriptor.each_declaration do |decl|
|
496
|
+
emit_register(defn, decl)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protobug-compiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Giddins
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-04-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: delegate
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: forwardable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: prettier_print
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.2.1
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.2'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.2.1
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: protobug
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.1.0
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 0.1.0
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: protobug_compiler_protos
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.1.0
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.1.0
|
89
|
+
description:
|
90
|
+
email:
|
91
|
+
- segiddins@segiddins.me
|
92
|
+
executables:
|
93
|
+
- protoc-gen-protobug
|
94
|
+
extensions: []
|
95
|
+
extra_rdoc_files: []
|
96
|
+
files:
|
97
|
+
- CHANGELOG.md
|
98
|
+
- LICENSE.txt
|
99
|
+
- exe/protoc-gen-protobug
|
100
|
+
- lib/protobug/compiler.rb
|
101
|
+
- lib/protobug/compiler/builder.rb
|
102
|
+
- lib/protobug/compiler/builder_gen.rb
|
103
|
+
homepage: https://github.com/segiddins/protobug
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata:
|
107
|
+
allowed_push_host: https://rubygems.org/
|
108
|
+
rubygems_mfa_required: 'true'
|
109
|
+
homepage_uri: https://github.com/segiddins/protobug
|
110
|
+
source_code_uri: https://github.com/segiddins/protobug
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 3.0.0
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.5.9
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: An protobuf compiler for protobug
|
130
|
+
test_files: []
|