Skip to content

Commit

Permalink
🐑
Browse files Browse the repository at this point in the history
  • Loading branch information
soutaro committed Jun 29, 2020
1 parent 8ca75e1 commit 0ad02ae
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ gemspec

gem "rake", "~> 12.0"
gem "minitest", "~> 5.0"

gem "rbs", path: "../rbs"
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
PATH
remote: ../rbs
specs:
rbs (0.4.0)

PATH
remote: .
specs:
rbs_protobuf (0.1.0)
google-protobuf (~> 3.12)
rbs (~> 0.4.0)

GEM
remote: https://rubygems.org/
specs:
google-protobuf (3.12.2)
minitest (5.14.1)
rake (12.3.3)

Expand All @@ -15,6 +23,7 @@ PLATFORMS
DEPENDENCIES
minitest (~> 5.0)
rake (~> 12.0)
rbs!
rbs_protobuf!

BUNDLED WITH
Expand Down
11 changes: 11 additions & 0 deletions bin/protoc-gen-dumper
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby

$LOAD_PATH << File.join(__dir__, "../lib")

require "rbs_protobuf"

input = STDIN.read()
File.write(ENV["PROTOC_DUMPER_OUT"] || "a.pb.out", input)

response = Google::Protobuf::Compiler::CodeGeneratorResponse.new
print Google::Protobuf::Compiler::CodeGeneratorResponse.encode(response)
18 changes: 18 additions & 0 deletions examples/search_request.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto2";

message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;

enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [default = UNIVERSAL];
}
9 changes: 9 additions & 0 deletions exe/protoc-gen-rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env ruby

$LOAD_PATH << File.join(__dir__, "../lib")

require "rbs_protobuf"

input = Google::Protobuf::Compiler::CodeGeneratorRequest.decode(STDIN.read)
response = RbsProtobufTest::Translator.new(input).response
print Google::Protobuf::Compiler::CodeGeneratorResponse.encode(response)
7 changes: 7 additions & 0 deletions lib/rbs_protobuf.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
require "rbs_protobuf/version"

require "rbs"

require "google/protobuf/descriptor_pb"
require "google/protobuf/plugin_pb"

require "rbs_protobuf/translator"

module RbsProtobuf
class Error < StandardError; end
# Your code goes here...
Expand Down
249 changes: 249 additions & 0 deletions lib/rbs_protobuf/translator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
module RbsProtobuf
class Translator
attr_reader :input

def initialize(input)
@input = input
end

def response
@response ||= generate_response()
end

def generate_response
response = Google::Protobuf::Compiler::CodeGeneratorResponse.new

input.proto_file.each do |file|
response.file << Google::Protobuf::Compiler::CodeGeneratorResponse::File.new(
name: rbs_name(file.name),
content: rbs_content(file)
)
end

response
end

def rbs_name(proto_name)
dirname = File.dirname(proto_name)
basename = File.basename(proto_name, File.extname(proto_name))
rbs_name = basename + "_pb.rbs"

File.join(dirname, rbs_name)
end

def rbs_content(file)
decls = []

file.message_type.each do |message|
decls << message_to_decl(message)
end

StringIO.new.tap do |io|
RBS::Writer.new(out: io).write(decls)
end.string
end

def message_to_decl(message, prefix: RBS::Namespace.empty)
name = message.name

RBS::AST::Declarations::Class.new(
name: RBS::TypeName.new(name: name.to_sym, namespace: prefix),
super_class: nil,
type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
location: nil,
comment: RBS::AST::Comment.new(location: nil, string: "#{message.name}"),
members: [],
annotations: []
).tap do |class_decl|
keywords = {}

message.enum_type.each do |enum|
class_decl.members << RBS::AST::Declarations::Module.new(
name: RBS::TypeName.new(name: enum.name.to_sym, namespace: RBS::Namespace.empty),
self_type: nil,
type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
members: [],
comment: nil,
location: nil,
annotations: []
).tap do |enum_decl|
enum_decl.members << RBS::AST::Declarations::Alias.new(
name: RBS::TypeName.new(name: :symbols, namespace: RBS::Namespace.empty),
type: RBS::Types::Union.new(
types: enum.value.map do |v|
RBS::Types::Literal.new(literal: v.name.to_sym, location: nil)
end,
location: nil
),
location: nil,
comment: nil,
annotations: []
)

enum.value.each do |value|
enum_decl.members << RBS::AST::Declarations::Constant.new(
name: RBS::TypeName.new(name: value.name.to_sym, namespace: RBS::Namespace.empty),
type: RBS::BuiltinNames::Integer.instance_type,
location: nil,
comment: nil
)
end

enum_decl.members << RBS::AST::Members::MethodDefinition.new(
name: :lookup,
kind: :singleton,
annotations: [],
comment: nil,
location: nil,
overload: false,
attributes: [],
types: [
RBS::MethodType.new(
type_params: [],
type: RBS::Types::Function.empty(
RBS::Types::Optional.new(
type: RBS::Types::Alias.new(
name: RBS::TypeName.new(name: :symbols, namespace: RBS::Namespace.empty),
location: nil
),
location: nil
)
).update(required_positionals: [
RBS::Types::Function::Param.new(
name: :number,
type: RBS::BuiltinNames::Integer.instance_type
)
]),
block: nil,
location: nil
)
]
)

enum_decl.members << RBS::AST::Members::MethodDefinition.new(
name: :resolve,
kind: :singleton,
annotations: [],
comment: nil,
location: nil,
overload: false,
attributes: [],
types: [
RBS::MethodType.new(
type_params: [],
type: RBS::Types::Function.empty(
RBS::Types::Optional.new(
type: RBS::BuiltinNames::Integer.instance_type,
location: nil
)
).update(required_positionals: [
RBS::Types::Function::Param.new(
name: :symbol,
type: RBS::BuiltinNames::Symbol.instance_type
)
]),
block: nil,
location: nil
)
]
)
end
end

message.field.each do |field|
case
when field.type == :TYPE_ENUM
enum_name = field.type_name.split('.').last

symbols = RBS::Types::Alias.new(
name: RBS::TypeName.new(
name: :symbols,
namespace: RBS::Namespace.new(path: [enum_name.to_sym], absolute: false)
),
location: nil
)
type = RBS::Types::Union.new(types: [symbols, RBS::BuiltinNames::Integer.instance_type], location: nil)

class_decl.members << RBS::AST::Members::AttrReader.new(
name: field.name.to_sym,
type: symbols,
ivar_name: false,
location: nil,
comment: nil,
annotations: []
)
class_decl.members << RBS::AST::Members::AttrWriter.new(
name: field.name.to_sym,
type: type,
ivar_name: false,
location: nil,
comment: nil,
annotations: []
)
when field.type == :TYPE_MESSAGE
else
# Scalar values
type = base_type(field.type)
class_decl.members << RBS::AST::Members::AttrAccessor.new(
name: field.name.to_sym,
type: type,
ivar_name: false,
location: nil,
comment: nil,
annotations: []
)
end

if field.label == :LABEL_REPEATED
type = RBS::BuiltinNames::Array.instance_type(type)
end

keywords[field.name.to_sym] = type
end

method_type = RBS::MethodType.new(
type_params: [],
type: RBS::Types::Function
.empty(RBS::Types::Bases::Void.new(location: nil))
.update(optional_keywords: keywords.transform_values {|kw_type|
RBS::Types::Function::Param.new(name: nil, type: kw_type)
}),
block: nil,
location: nil
)

class_decl.members << RBS::AST::Members::MethodDefinition.new(
name: :initialize,
kind: :instance,
annotations: [],
comment: nil,
location: nil,
overload: false,
attributes: [],
types: [method_type]
)
end
end

def base_type(type)
case type
when :TYPE_STRING, :TYPE_BYTES
RBS::BuiltinNames::String.instance_type
when :TYPE_INT32, :TYPE_UINT32, :TYPE_INT64, :TYPE_UINT64, :TYPE_FIXED64, :TYPE_FIXED32
RBS::BuiltinNames::Integer.instance_type
when :TYPE_DOUBLE, :TYPE_FLOAT
RBS::BuiltinNames::Float.instance_type
when :TYPE_BOOL
RBS::Types::Union.new(
location: nil,
types: [
RBS::BuiltinNames::TrueClass.instance_type,
RBS::BuiltinNames::FalseClass.instance_type
]
)
else
RBS::Types::Bases::Any.new(location: nil)
end
end
end
end
22 changes: 22 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,25 @@
require "rbs_protobuf"

require "minitest/autorun"
require "tmpdir"
require "open3"

module TestHelper
def read_code_generator_request(proto_path)
full_path = File.join(__dir__, "../examples", proto_path)
dumper_path = File.join(__dir__, "../bin/protoc-gen-dumper")

Dir.mktmpdir do |dir|
Dir.chdir(dir) do
Open3.capture2("protoc",
"--plugin=#{dumper_path}",
"--dumper_out=/", # --dumper_out is ignored
"--proto_path=#{File.join(__dir__, "../examples")}",
full_path)
end

content = File.read(File.join(dir, "a.pb.out"))
Google::Protobuf::Compiler::CodeGeneratorRequest.decode(content)
end
end
end
17 changes: 17 additions & 0 deletions test/translator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require "test_helper"

class TranslatorTest < Minitest::Test
include TestHelper

def test_search_request
input = read_code_generator_request("search_request.proto")

translator = RbsProtobuf::Translator.new(input)
res = translator.response

files = res.file
assert_equal 1, files.size

puts files[0].content
end
end

0 comments on commit 0ad02ae

Please sign in to comment.