Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions lib/beefcake/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ 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)
Expand Down Expand Up @@ -192,19 +191,19 @@ def define!(mt)
puts "end"
end

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

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

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

Expand All @@ -222,7 +221,7 @@ def enum!(et)
puts "end"
end

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

Expand All @@ -234,19 +233,13 @@ def field!(pkg, f)
# 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
namespaced_type(t, pkg, ns)
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
Expand All @@ -263,6 +256,30 @@ def field!(pkg, f)
puts out
end

# Generates a correctly namespaced ruby class name
# Removes pkg from type_pcs or from namespace
# Removes from type_pcs when infered type is same as pkg
# Removes from namespace when decendent is same as pkg
#
# @param type [String] A field type name
# @param pkg [String] The package name from the protobuf
# @param ns [String] The user provided namespace
#
# @return [String] A correctly namespaced ruby class name
def namespaced_type(type, pkg, ns)
type_key = type.gsub(/^:/, '').to_sym
return type if Buffer::WIRES.keys.include?(type_key)

type_pcs = type.split('.').reject!(&:empty?)
namespace = ns.clone
if type_pcs.first == pkg
type_pcs.shift
elsif namespace.last.casecmp(pkg)
namespace.pop
end
namespace.concat(type_pcs).join('::')
end

# Determines the name for a
def name_for(b, mod, val)
b.name_for(mod, val).to_s.gsub(/.*_/, "").downcase
Expand All @@ -284,7 +301,7 @@ def compile(ns, file)
end

file.message_type.each do |mt|
message!(file.package, mt)
message!(file.package, mt, ns)
end
end
end
Expand Down
25 changes: 25 additions & 0 deletions test/generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ def test_generate_two_level_namespace
assert_match(/module Top\s*\n\s*module Bottom/m, @res.file.first.content)
end

def test_generate_top_namespaced_class_name
@res = Beefcake::Generator.compile(['Top'], @req)
assert_equal(CodeGeneratorResponse, @res.class)
assert_match(
/ Top\:\:Person\:\:PhoneType/,
@res.file.first.content
)
refute_match(
/ Person\:\:PhoneType/,
@res.file.first.content
)
end

def test_generate_two_level_namespaced_class_name
@res = Beefcake::Generator.compile(['Top', 'Gun'], @req)
assert_equal(CodeGeneratorResponse, @res.class)
assert_match(
/ Top\:\:Gun\:\:Person\:\:PhoneType/,
@res.file.first.content
)
refute_match(
/ Person\:\:PhoneType/,
@res.file.first.content
)
end
# Covers the regression of encoding a CodeGeneratorResponse under 1.9.2-p136 raising
# Encoding::CompatibilityError: incompatible character encodings: ASCII-8BIT and US-ASCII
def test_encode_decode_generated_response
Expand Down