File: generator.rb

package info (click to toggle)
ruby-beefcake 1.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 196 kB
  • sloc: ruby: 1,655; makefile: 4
file content (313 lines) | stat: -rw-r--r-- 7,573 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# encoding: ASCII-8BIT
# The above line allows concatenation of constant strings like ".pb.rb" to
# maintain the internal format of the buffers, rather than converting the
# buffer to US-ASCII

require 'beefcake'
require 'stringio'

class CodeGeneratorRequest
  include Beefcake::Message


  class FieldDescriptorProto
    include Beefcake::Message

    module Type
      ## 0 is reserved for errors.
      ## Order is weird for historical reasons.
      TYPE_DOUBLE         = 1
      TYPE_FLOAT          = 2
      TYPE_INT64          = 3   ## Not ZigZag encoded.  Negative numbers
      ## take 10 bytes.  Use TYPE_SINT64 if negative
      ## values are likely.
      TYPE_UINT64         = 4
      TYPE_INT32          = 5   ## Not ZigZag encoded.  Negative numbers
      ## take 10 bytes.  Use TYPE_SINT32 if negative
      ## values are likely.
      TYPE_FIXED64        = 6
      TYPE_FIXED32        = 7
      TYPE_BOOL           = 8
      TYPE_STRING         = 9
      TYPE_GROUP          = 10 ## Tag-delimited aggregate.
      TYPE_MESSAGE        = 11 ## Length-delimited aggregate.

      ## New in version 2.
      TYPE_BYTES          = 12
      TYPE_UINT32         = 13
      TYPE_ENUM           = 14
      TYPE_SFIXED32       = 15
      TYPE_SFIXED64       = 16
      TYPE_SINT32         = 17 ## Uses ZigZag encoding.
      TYPE_SINT64         = 18 ## Uses ZigZag encoding.
    end

    module Label
      LABEL_OPTIONAL      = 1
      LABEL_REQUIRED      = 2
      LABEL_REPEATED      = 3
    end

    optional :name,   :string, 1
    optional :number, :int32,  3
    optional :label,  Label,  4

    ## If type_name is set, this need not be set.  If both this and type_name
    ## are set, this must be either TYPE_ENUM or TYPE_MESSAGE.
    optional :type, Type, 5

    ## For message and enum types, this is the name of the type.  If the name
    ## starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
    ## rules are used to find the type (i.e. first the nested types within this
    ## message are searched, then within the parent, on up to the root
    ## namespace).
    optional :type_name, :string, 6

    ## For extensions, this is the name of the type being extended.  It is
    ## resolved in the same manner as type_name.
    optional :extended, :string, 2

    ## For numeric types, contains the original text representation of the value.
    ## For booleans, "true" or "false".
    ## For strings, contains the default text contents (not escaped in any way).
    ## For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
    optional :default_value, :string, 7
  end


  class EnumValueDescriptorProto
    include Beefcake::Message

    optional :name,   :string, 1
    optional :number, :int32,  2
    # optional EnumValueOptions options = 3;
  end

  class EnumDescriptorProto
    include Beefcake::Message

    optional :name, :string, 1
    repeated :value, EnumValueDescriptorProto, 2
    # optional :options, EnumOptions, 3
  end

  class DescriptorProto
    include Beefcake::Message

    optional :name, :string, 1

    repeated :field,       FieldDescriptorProto, 2
    repeated :extended,    FieldDescriptorProto, 6
    repeated :nested_type, DescriptorProto,      3
    repeated :enum_type,   EnumDescriptorProto,  4
  end


  class FileDescriptorProto
    include Beefcake::Message

    optional :name, :string, 1       # file name, relative to root of source tree
    optional :package, :string, 2    # e.g. "foo", "foo.bar", etc.

    repeated :message_type, DescriptorProto,     4;
    repeated :enum_type,    EnumDescriptorProto, 5;
  end


  repeated :file_to_generate, :string, 1
  optional :parameter, :string, 2

  repeated :proto_file, FileDescriptorProto, 15
end

class CodeGeneratorResponse
  include Beefcake::Message

  class File
    include Beefcake::Message

    optional :name,    :string, 1
    optional :content, :string, 15
  end

  repeated :file, File, 15
end

module Beefcake
  class Generator

    L = CodeGeneratorRequest::FieldDescriptorProto::Label
    T = CodeGeneratorRequest::FieldDescriptorProto::Type


    def self.compile(ns, req)
      file = req.proto_file.map do |file|
        g = new(StringIO.new)
        g.compile(ns, file)

        g.c.rewind
        CodeGeneratorResponse::File.new(
          :name => File.basename(file.name, ".proto") + ".pb.rb",
          :content => g.c.read
        )
      end

      CodeGeneratorResponse.new(:file => file)
    end

    attr_reader :c

    def initialize(c)
      @c = c
      @n = 0
    end

    def indent(&blk)
      @n += 1
      blk.call
      @n -= 1
    end

    def indent!(n)
      @n = n
    end

    def define!(mt)
      puts
      puts "class #{mt.name}"

      indent do
        puts "include Beefcake::Message"

        ## Enum Types
        Array(mt.enum_type).each do |et|
          enum!(et)
        end

        ## Nested Types
        Array(mt.nested_type).each do |nt|
          define!(nt)
        end
      end
      puts "end"
    end

    def message!(pkg, mt)
      puts
      puts "class #{mt.name}"

      indent do
        ## Generate Types
        Array(mt.nested_type).each do |nt|
          message!(pkg, nt)
        end

        ## Generate Fields
        Array(mt.field).each do |f|
          field!(pkg, f)
        end
      end

      puts "end"
    end

    def enum!(et)
      puts
      puts "module #{et.name}"
      indent do
        et.value.each do |v|
          puts "%s = %d" % [v.name, v.number]
        end
      end
      puts "end"
    end

    def field!(pkg, f)
      # Turn the label into Ruby
      label = name_for(f, L, f.label)

      # Turn the name into a Ruby
      name = ":#{f.name}"

      # Determine the type-name and convert to Ruby
      type = if f.type_name
        # We have a type_name so we will use it after converting to a
        # Ruby friendly version
        t = f.type_name
        if pkg
          t = t.gsub(pkg, "") # Remove the leading package name
        end
        t = t.gsub(/^\.*/, "")       # Remove leading `.`s

        t.gsub(".", "::")  # Convert to Ruby namespacing syntax
      else
        ":#{name_for(f, T, f.type)}"
      end

      # Finally, generate the declaration
      out = "%s %s, %s, %d" % [label, name, type, f.number]

      if f.default_value
        v = case f.type
        when T::TYPE_ENUM
          "%s::%s" % [type, f.default_value]
        when T::TYPE_STRING, T::TYPE_BYTES
          '"%s"' % [f.default_value.gsub('"', '\"')]
        else
          f.default_value
        end

        out += ", :default => #{v}"
      end

      puts out
    end

    # Determines the name for a
    def name_for(b, mod, val)
      b.name_for(mod, val).to_s.gsub(/.*_/, "").downcase
    end

    def compile(ns, file)
      package_part = file.package ? "for #{file.package}" : ''
      puts "## Generated from #{file.name} #{package_part}".strip
      puts "require \"beefcake\""
      puts

      ns!(ns) do
        Array(file.enum_type).each do |et|
          enum!(et)
        end

        file.message_type.each do |mt|
          define! mt
        end

        file.message_type.each do |mt|
          message!(file.package, mt)
        end
      end
    end

    def ns!(modules, &blk)
      if modules.empty?
        blk.call
      else
        puts "module #{modules.first}"
        indent do
          ns!(modules[1..-1], &blk)
        end
        puts "end"
      end
    end

    def puts(msg=nil)
      if msg
        c.puts(("  " * @n) + msg)
      else
        c.puts
      end
    end

  end
end