pbbuilder 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95c1a8225cbe27f9523fe58f6e0a706108ae12839d1b091b6a2a70d02d5363cc
4
+ data.tar.gz: b133c221d7f27e76626632c488309c4c1d8c929e1725d34298847c42ad5f7350
5
+ SHA512:
6
+ metadata.gz: 033030a337a7b3a7e334719d564dfe098c7e1f7b54e8cea18ace761d51adb091c7fbe0b6b42d527ebc1e3b622e8d3332f4400a1444e263b55c2a95cf9a8e92fd
7
+ data.tar.gz: 76f971940ae26fcd1360552cec8e5e9bb3160f21a39e36184d2bc700524f480c1b5261781dd760be5d33d702c4e2a95739e5f32d890d348f6f698dc0ee1a315f
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /doc/
3
+ /log/*.log
4
+ /pkg/
5
+ /tmp/
6
+ /test/dummy/db/*.sqlite3
7
+ /test/dummy/db/*.sqlite3-*
8
+ /test/dummy/log/*.log
9
+ /test/dummy/storage/
10
+ /test/dummy/tmp/
11
+ .byebug_history
12
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in pbbuilder.gemspec.
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Bouke van der Bijl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Pbbuilder
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'pbbuilder'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install pbbuilder
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rake/testtask"
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.pattern = "test/**/*_test.rb"
10
+ t.verbose = false
11
+ end
12
+
13
+ task default: :test
data/bin/test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("../test", __dir__)
3
+
4
+ require "bundler/setup"
5
+ require "rails/plugin/test"
data/lib/pbbuilder.rb ADDED
@@ -0,0 +1,118 @@
1
+ # Pbbuilder makes it easy to create a protobuf message using the builder pattern
2
+ # It is heavily inspired by jbuilder
3
+ #
4
+ # Given this example message definition:
5
+ # message Person {
6
+ # string name = 1;
7
+ # repeated Person friends = 2;
8
+ # }
9
+ #
10
+ # You could use Pbbuilder as follows:
11
+ # person = RPC::Person.new
12
+ # Pbbuilder.new(person) do |pb|
13
+ # pb.name "Hello"
14
+ # pb.friends [1, 2, 3] do |number|
15
+ # pb.name "Friend ##{number}"
16
+ # end
17
+ # end
18
+ #
19
+ # message.name => "Hello"
20
+ #
21
+ # It basically works exactly like jbuilder. The main difference is that it can use introspection to figure out what kind
22
+ # of protobuf message it needs to create.
23
+
24
+ class Pbbuilder
25
+ def initialize(message)
26
+ @message = message
27
+
28
+ yield self if ::Kernel.block_given?
29
+ end
30
+
31
+ def method_missing(field, *args, &block)
32
+ set!(field, *args, &block)
33
+ end
34
+
35
+ def respond_to_missing?(field)
36
+ !!@message.class.descriptor.lookup(field.to_s)
37
+ end
38
+
39
+ def set!(field, *args, &block)
40
+ name = field.to_s
41
+ descriptor = @message.class.descriptor.lookup(name)
42
+
43
+ if block
44
+ raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
45
+
46
+ if descriptor.label == :repeated
47
+ # pb.field @array { |element| pb.name = element.name }
48
+ raise ::ArgumentError, "wrong number of arguments (expected 1)" unless args.length == 1
49
+ collection = args.first
50
+ _append_repeated(name, descriptor, collection, &block)
51
+ return
52
+ end
53
+
54
+ raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
55
+ # pb.field { pb.name = "hello" }
56
+ message = (@message[name] ||= _new_message_from_descriptor(descriptor))
57
+ _scope(message, &block)
58
+ elsif args.length == 1
59
+ @message[name] = args.first
60
+ else
61
+ # pb.field @value, :id, :name, :url
62
+ element = args.shift
63
+ if descriptor.label == :repeated
64
+ # If the message field that's being assigned is a repeated field, then we assume that `element` is enumerable.
65
+ # This way you can do something like pb.repeated_field @array, :id, :name
66
+ # This will create a message out of every object in @array, copying over the :id and :name values.
67
+ set!(name, element) do |item|
68
+ extract!(item, *args)
69
+ end
70
+ else
71
+ extract!(element, *args)
72
+ end
73
+ end
74
+ end
75
+
76
+ def extract!(element, *args)
77
+ args.each do |arg|
78
+ value = element.send(arg)
79
+ @message[arg.to_s] = value
80
+ end
81
+ end
82
+
83
+ def target!
84
+ @message
85
+ end
86
+
87
+ private
88
+
89
+ def _append_repeated(name, descriptor, collection, &block)
90
+ raise ::ArgumentError, "expected Enumerable" unless collection.respond_to?(:map)
91
+ elements = collection.map do |element|
92
+ message = _new_message_from_descriptor(descriptor)
93
+ _scope(message) { block.call(element) }
94
+ end
95
+
96
+ @message[name].push(*elements)
97
+ end
98
+
99
+ def _scope(message)
100
+ old_message = @message
101
+ @message = message
102
+ yield
103
+ message
104
+ ensure
105
+ @message = old_message
106
+ end
107
+
108
+ def _new_message_from_descriptor(descriptor)
109
+ raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
110
+
111
+ # Here we're using Protobuf reflection to create an instance of the message class
112
+ message_descriptor = descriptor.subtype
113
+ message_class = message_descriptor.msgclass
114
+ message_class.new
115
+ end
116
+ end
117
+
118
+ require "pbbuilder/railtie" if defined?(Rails)
@@ -0,0 +1,12 @@
1
+ # Basically copied and pasted from JbuilderHandler, except it uses Pbbuilder
2
+
3
+ class PbbuilderHandler
4
+ # This builds up a Ruby string, that Rails' templating system `eval`s to create the view result.
5
+ # In our case the view result is a Protobuf message.
6
+ def self.call(template, source = nil)
7
+ source ||= template.source
8
+ # We need to keep `source` on the first line, so line numbers are correct if there's an error
9
+ %{pb=Pbbuilder.new(response_class.new); #{source}
10
+ pb.target!}
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require "pbbuilder/handler"
2
+
3
+ class Pbbuilder
4
+ class Railtie < ::Rails::Railtie
5
+ initializer :register_handler do
6
+ ActiveSupport.on_load :action_view do
7
+ ActionView::Template.register_template_handler :pbbuilder, PbbuilderHandler
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pbbuilder do
3
+ # # Task goes here
4
+ # end
data/pbbuilder.gemspec ADDED
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "pbbuilder"
3
+ spec.version = "0.1.0"
4
+ spec.authors = ["Bouke van der Bijl"]
5
+ spec.email = ["bouke@cheddar.me"]
6
+ spec.homepage = "https://github.com/cheddar-me/pbbuilder"
7
+ spec.summary = "Generate Protobuf messages via a Builder-style DSL"
8
+ spec.license = "MIT"
9
+
10
+ spec.files = `git ls-files`.split("\n")
11
+ spec.test_files = `git ls-files -- test/*`.split("\n")
12
+
13
+ spec.add_dependency "rails", "~> 6.1.3"
14
+ spec.add_dependency "google-protobuf", "~> 3.15.5"
15
+ end
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+ require "pbbuilder"
3
+
4
+ class PbbuilderTest < ActiveSupport::TestCase
5
+ test "it makes it possible to create a person" do
6
+ person = Pbbuilder.new(API::Person.new) do |pb|
7
+ pb.name "Hello world"
8
+ pb.friends 1..3 do |number|
9
+ pb.name "Friend ##{number}"
10
+ end
11
+ end.target!
12
+ assert_equal person.name, "Hello world"
13
+ assert_equal person.friends.first.name, "Friend #1"
14
+ end
15
+
16
+ test "it can extract fields in a nice way" do
17
+ klass = Struct.new(:name)
18
+ friends = [klass.new("Friend 1"), klass.new("Friend 2")]
19
+ person = Pbbuilder.new(API::Person.new) do |pb|
20
+ pb.name "Hello world"
21
+ pb.friends friends, :name
22
+ end.target!
23
+
24
+ assert_equal person.name, "Hello world"
25
+ assert_equal person.friends.size, 2
26
+ assert_equal person.friends.first.name, "Friend 1"
27
+ assert_equal person.friends.last.name, "Friend 2"
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require "rails"
5
+ require "rails/test_help"
6
+ require "rails/test_unit/reporter"
7
+
8
+ Rails::TestUnitReporter.executable = "bin/test"
9
+
10
+ require "google/protobuf"
11
+
12
+ Google::Protobuf::DescriptorPool.generated_pool.build do
13
+ add_file("pbbuilder.proto", syntax: :proto3) do
14
+ add_message "pbbuildertest.Person" do
15
+ optional :name, :string, 1
16
+ repeated :friends, :message, 2, "pbbuildertest.Person"
17
+ end
18
+ end
19
+ end
20
+
21
+ module API
22
+ Person = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Person").msgclass
23
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pbbuilder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bouke van der Bijl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.1.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: google-protobuf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.15.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.15.5
41
+ description:
42
+ email:
43
+ - bouke@cheddar.me
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - MIT-LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - bin/test
54
+ - lib/pbbuilder.rb
55
+ - lib/pbbuilder/handler.rb
56
+ - lib/pbbuilder/railtie.rb
57
+ - lib/tasks/pbbuilder_tasks.rake
58
+ - pbbuilder.gemspec
59
+ - test/pbbuilder_test.rb
60
+ - test/test_helper.rb
61
+ homepage: https://github.com/cheddar-me/pbbuilder
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.2.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Generate Protobuf messages via a Builder-style DSL
84
+ test_files:
85
+ - test/pbbuilder_test.rb
86
+ - test/test_helper.rb