magicprotorb 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.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magicprotorb
4
+ # Synthesizes gRPC Service/Stub classes from the service descriptors in a
5
+ # FileDescriptorProto — the runtime equivalent of a generated
6
+ # `*_services_pb.rb`. Requires the optional `grpc` gem, loaded lazily so that
7
+ # message-only users never pay for it.
8
+ module ServiceBuilder
9
+ module_function
10
+
11
+ def build(file)
12
+ require "grpc"
13
+ package = file.package
14
+ file.service.each { |service| build_service(package, service) }
15
+ end
16
+
17
+ def build_service(package, service)
18
+ # Use to_h to read the repeated `method` field: ServiceDescriptorProto#method
19
+ # collides with Ruby's Object#method.
20
+ descriptor = service.to_h
21
+ name = descriptor[:name]
22
+ full_name = qualify(package, name)
23
+
24
+ service_class = define_service_class(full_name, descriptor[:method] || [])
25
+
26
+ mod = Registrar.ensure_module(Naming.package_modules(package) + [Naming.constant_name(name)])
27
+ mod.const_set(:Service, service_class) unless mod.const_defined?(:Service, false)
28
+ mod.const_set(:Stub, service_class.rpc_stub_class) unless mod.const_defined?(:Stub, false)
29
+ end
30
+
31
+ def define_service_class(full_name, methods)
32
+ pool = Google::Protobuf::DescriptorPool.generated_pool
33
+
34
+ Class.new do
35
+ include GRPC::GenericService
36
+
37
+ self.marshal_class_method = :encode
38
+ self.unmarshal_class_method = :decode
39
+ self.service_name = full_name
40
+
41
+ methods.each do |method|
42
+ request = pool.lookup(ServiceBuilder.strip(method[:input_type])).msgclass
43
+ response = pool.lookup(ServiceBuilder.strip(method[:output_type])).msgclass
44
+ # stream(...) is provided by GenericService's DSL inside the class body.
45
+ request = stream(request) if method[:client_streaming]
46
+ response = stream(response) if method[:server_streaming]
47
+ rpc method[:name].to_sym, request, response
48
+ end
49
+ end
50
+ end
51
+
52
+ def qualify(package, name)
53
+ package.nil? || package.empty? ? name : "#{package}.#{name}"
54
+ end
55
+
56
+ # Descriptor type references are fully-qualified with a leading dot
57
+ # (".greet.HelloRequest"); the pool is keyed without it.
58
+ def strip(type_name)
59
+ type_name.sub(/\A\./, "")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magicprotorb
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "google/protobuf"
4
+ require "google/protobuf/descriptor_pb"
5
+
6
+ require_relative "magicprotorb/version"
7
+
8
+ module Magicprotorb
9
+ class Error < StandardError; end
10
+
11
+ # Raised when the native compiler rejects a .proto (syntax error, unresolved
12
+ # import, etc.).
13
+ class CompileError < Error; end
14
+ end
15
+
16
+ # The Rust compiler extension (defines Magicprotorb::Compiler._compile).
17
+ require_relative "magicprotorb/magicprotorb_native"
18
+
19
+ require_relative "magicprotorb/naming"
20
+ require_relative "magicprotorb/include_path"
21
+ require_relative "magicprotorb/registrar"
22
+ require_relative "magicprotorb/service_builder"
23
+ require_relative "magicprotorb/loader"
24
+ require_relative "magicprotorb/require_hook"
25
+
26
+ module Magicprotorb
27
+ # Public, programmatic entry points. The primary interface is the require hook
28
+ # installed below; these mirror it for callers who prefer an explicit API.
29
+
30
+ module_function
31
+
32
+ # The include roots currently searched for protos (MAGICPROTORB_PATH then
33
+ # $LOAD_PATH), highest priority first.
34
+ def include_paths
35
+ IncludePath.roots
36
+ end
37
+
38
+ # Programmatic equivalent of `require "magicprotorb/<proto>_pb"`. Accepts a
39
+ # canonical proto path with or without the .proto extension.
40
+ def import(proto)
41
+ Loader.load_messages(normalize(proto))
42
+ end
43
+
44
+ # Programmatic equivalent of `require "magicprotorb/<proto>_services_pb"`.
45
+ def import_services(proto)
46
+ Loader.load_services(normalize(proto))
47
+ end
48
+
49
+ def normalize(proto)
50
+ proto.end_with?(".proto") ? proto : "#{proto}.proto"
51
+ end
52
+ end
53
+
54
+ # Install the import hook. After this, `require "magicprotorb/foo/bar_pb"` works.
55
+ Kernel.prepend(Magicprotorb::RequireHook)
@@ -0,0 +1,29 @@
1
+ module Magicprotorb
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ # Raised when the native compiler rejects a .proto.
8
+ class CompileError < Error
9
+ end
10
+
11
+ # Compiles greet/hello.proto and defines its message/enum constants.
12
+ # Equivalent to `require "magicprotorb/greet/hello_pb"`.
13
+ def self.import: (String proto) -> void
14
+
15
+ # As import, plus synthesizes the gRPC Service/Stub.
16
+ # Equivalent to `require "magicprotorb/greet/hello_services_pb"`.
17
+ def self.import_services: (String proto) -> void
18
+
19
+ # The include roots currently searched (MAGICPROTORB_PATH, then $LOAD_PATH).
20
+ def self.include_paths: () -> Array[String]
21
+
22
+ def self.normalize: (String proto) -> String
23
+
24
+ # The Rust extension's compiler entry point.
25
+ class Compiler
26
+ # Returns a serialized FileDescriptorSet (binary string).
27
+ def self._compile: (String proto_path, Array[String] include_dirs) -> String
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magicprotorb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sam
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-05-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: google-protobuf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.21'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.21'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rb_sys
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.9'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.9'
47
+ description: |
48
+ magicprotorb lets you `require "magicprotorb/foo/bar_pb"` and have foo/bar.proto
49
+ compiled to descriptors and registered at require time. The dotted require path
50
+ mirrors the canonical proto path 1:1, so the require name, the file location, and
51
+ the descriptor name can never drift apart. A small Rust extension (built on the
52
+ pure-Rust protox compiler) turns .proto text into a FileDescriptorSet, which is
53
+ then registered through the stock protobuf DescriptorPool — making the resulting
54
+ message classes indistinguishable from generated ones.
55
+ email:
56
+ executables: []
57
+ extensions:
58
+ - ext/magicprotorb_native/extconf.rb
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".rubocop.yml"
62
+ - Cargo.lock
63
+ - Cargo.toml
64
+ - DESIGN.md
65
+ - README.md
66
+ - Rakefile
67
+ - ext/magicprotorb_native/Cargo.toml
68
+ - ext/magicprotorb_native/extconf.rb
69
+ - ext/magicprotorb_native/src/lib.rs
70
+ - lib/magicprotorb.rb
71
+ - lib/magicprotorb/include_path.rb
72
+ - lib/magicprotorb/loader.rb
73
+ - lib/magicprotorb/naming.rb
74
+ - lib/magicprotorb/registrar.rb
75
+ - lib/magicprotorb/require_hook.rb
76
+ - lib/magicprotorb/service_builder.rb
77
+ - lib/magicprotorb/version.rb
78
+ - sig/magicprotorb.rbs
79
+ homepage: https://github.com/grpyc/magicproto-rb
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ homepage_uri: https://github.com/grpyc/magicproto-rb
84
+ source_code_uri: https://github.com/grpyc/magicproto-rb
85
+ rubygems_mfa_required: 'true'
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 3.0.0
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.3.7
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Import .proto files directly in Ruby — no protoc, no generated _pb.rb, no
105
+ build step.
106
+ test_files: []