diff --git a/lib/beefcake/generator.rb b/lib/beefcake/generator.rb index cbba2b4..ff4c5c4 100644 --- a/lib/beefcake/generator.rb +++ b/lib/beefcake/generator.rb @@ -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) @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/test/generator_test.rb b/test/generator_test.rb index ecccd53..b5296f6 100644 --- a/test/generator_test.rb +++ b/test/generator_test.rb @@ -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