protojson 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.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in protojson.gemspec
4
+ gemspec
5
+
6
+ gem 'ruby_protobuf', '~>0.4.10'
7
+ gem 'activesupport', '~>3.0.5'
8
+ gem 'i18n', '~>0.5.0'
9
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Juan de Bravo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+
2
+ # Introduction
3
+
4
+ A Ruby gem for Google's Protocol Buffers messages using three different encodings JSON based syntax instead of the
5
+ original binary protocol.
6
+
7
+ This gem extends the *ruby_protobuf* gem [1] to allow these new encodings in a protocol buffer message:
8
+
9
+ - [1] ruby_protobuf gem: [link to github project](https://github.com/macks/ruby-protobuf)
10
+
11
+ - [2] [ProtoJson specification](https://github.com/drslump/ProtoJson) by [DrSlump](https://github.com/drslump)
12
+
13
+ # Installation
14
+
15
+ gem install protojson
16
+
17
+ # Supported formats
18
+
19
+ - *Hashmap*: A tipical JSON message, with key:value pairs where the key is a string representing the field name.
20
+
21
+ - *Tagmap*: Very similar to Hashmap, but instead of having the field name as key it has the field tag number
22
+ as defined in the proto definition. This can save much space when transferring the messages, since usually field names
23
+ are longer than numbers.
24
+
25
+ - *Indexed*: Takes the Tagmap format a further step and optimizes the size needed for tag numbers by packing all of them
26
+ as a string, where each character represents a tag, and placing it as the first element of an array.
27
+
28
+
29
+ # How to use
30
+
31
+ ## Serialize a message
32
+
33
+ require 'addressbook.pb'
34
+ require 'protojson'
35
+ person = Tutorial::Person.new
36
+ person.parse_from_file ARGV[0]
37
+
38
+ Protobuf::Message::encoding = Protobuf::Message::EncodingType::INDEXED
39
+
40
+ value = person.serialize_to_string
41
+
42
+ puts value
43
+
44
+ ## Parse a message
45
+
46
+ require 'addressbook.pb'
47
+ require 'protojson'
48
+
49
+ person = Tutorial::Person.new
50
+ person.parse_from_file ARGV[0]
51
+
52
+ Protobuf::Message::encoding = Protobuf::Message::EncodingType::INDEXED
53
+
54
+ value = person.serialize_to_string
55
+
56
+ person = Tutorial::Person.new
57
+
58
+ person.parse_from_string(value)
59
+
60
+ p person
61
+
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler'
2
+ require 'rake/rdoctask'
3
+ require 'rake/clean'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ desc "Show the different encodings using a test file"
8
+ namespace :test do
9
+ task :person do
10
+ sh "ruby spec/main.rb spec/person"
11
+ end
12
+ end
13
+
14
+
15
+ Rake::RDocTask.new do |rd|
16
+ rd.main = "README.md"
17
+ rd.rdoc_files.include("README.md", "LICENSE", "lib/**/*.rb")
18
+ rd.title = 'ProtoJson'
19
+ end
20
+
@@ -0,0 +1,262 @@
1
+ require 'active_support/core_ext'
2
+
3
+ #
4
+ # This is an extension of Protobud::Message class
5
+ # which aims to support both JSON and INDEXED formats.
6
+ #
7
+ # Author:: Juan de Bravo (juandebravo@gmail.com, juan@pollinimini.net)
8
+ # Author:: Ivan -DrSlump- Montes (drslump@pollinimini.net)
9
+ # License:: --
10
+ #
11
+ module Protobuf
12
+
13
+ class Message
14
+
15
+ # Enum type that defines the supported encodings
16
+ # - Binary
17
+ # - Hashmap
18
+ # - Indexed
19
+ # - Indexed
20
+ module EncodingType
21
+ BINARY = 0,
22
+ HASHMAP = 1,
23
+ TAGMAP = 2,
24
+ INDEXED = 3
25
+ end
26
+
27
+ # static variable to define the specific encoding to be used.
28
+ # By default, use the primitive encoding type (BINARY)
29
+ @@encoding = Protobuf::Message::EncodingType::BINARY
30
+
31
+ # encoding setter
32
+ def self.encoding=(encoding)
33
+ @@encoding = encoding
34
+ end
35
+
36
+ # encoding getter
37
+ def self.encoding
38
+ @@encoding
39
+ end
40
+
41
+ # Encode the message to a hash object defined as a collection for key/values
42
+ # where each element has:
43
+ # - key: field tag
44
+ # - value:
45
+ # - if field.is_a? message_field => field.value.serialized_to_hash
46
+ # - if field.is_a? enum_field => field.value.value
47
+ # - else => field.value
48
+ #
49
+ # @return [Hash] a specific Message encoded in an Hash object
50
+ def serialize_to_hash(encoding_key = :tag)
51
+ raise NotInitializedError unless self.initialized?
52
+ # var to store the encoded message fields
53
+ result = {}
54
+
55
+ # lambda function that extract the field tag and value
56
+ # it's called recursively if value is a MessageField
57
+ field_value_to_string = lambda { |field, value|
58
+ field_value = \
59
+ if field.optional? && !self.has_field?(field.name)
60
+ ''
61
+ else
62
+ case field
63
+ when Field::MessageField
64
+ if value.nil?
65
+ nil
66
+ else
67
+ value.serialize_to_hash(encoding_key)
68
+ end
69
+ when Field::EnumField
70
+ if value.is_a?(EnumValue)
71
+ value.value
72
+ else
73
+ value.to_i
74
+ end
75
+ else
76
+ value
77
+ end
78
+ end
79
+ return field.send(encoding_key), field_value
80
+ }
81
+
82
+ # per each message field create a new element in result var with
83
+ # key = field.tag and value = field.value
84
+ self.each_field do |field, value|
85
+ # create a vector if field is repeated
86
+ if field.repeated? && !value.empty?
87
+ key_value = []
88
+ key = nil
89
+ value.each do |v|
90
+ key, val = field_value_to_string.call(field, v) # always return the same key
91
+ key_value.push val
92
+ end
93
+ # field is not repeated but is not empty
94
+ elsif !field.repeated?
95
+ key, key_value = field_value_to_string.call(field, value)
96
+ # empty field, discard
97
+ else
98
+ next
99
+ end
100
+ # new element in result Hash
101
+ result[key.to_s] = key_value
102
+ end
103
+ result
104
+ end
105
+
106
+ # allow new serialization, not only binary
107
+ alias :serialize_to_string_old :serialize_to_string
108
+
109
+ # This method is used to serialize the message to a specific format
110
+ # based on the class variable *encoding*
111
+ def serialize_to_string(string='')
112
+ case @@encoding
113
+ when EncodingType::BINARY # primitive format
114
+ serialize_to_string_old(string)
115
+ when EncodingType::HASHMAP # JSON format with key = field.name
116
+ serialize_to_json(:name)
117
+ when EncodingType::TAGMAP # JSON format with key = field.tag
118
+ serialize_to_json(:tag)
119
+ when EncodingType::INDEXED # INDEXED format
120
+ serialize_to_indexed
121
+ else
122
+ raise ArgumentError, "Invalid encoding type"
123
+ end
124
+ end
125
+
126
+ # This method serializes the message in JSON format
127
+ def serialize_to_json(encoding_key = :tag)
128
+ serialize_to_hash(encoding_key).to_json
129
+ end
130
+
131
+ # This method serializes the message in INDEXED format
132
+ def serialize_to_indexed
133
+ data = serialize_to_hash
134
+ self.class.serialize_hash_to_indexed(data).to_json
135
+ end
136
+
137
+ # This class method serializes a Hash
138
+ def self.serialize_hash_to_indexed(value)
139
+ !value.is_a?(Hash) and raise ArgumentError, "value must be a hash"
140
+ # index value
141
+ index = ""
142
+ index.respond_to?(:force_encoding) and index.force_encoding("UTF-8") # 1.9.2
143
+ # field values
144
+ result = []
145
+ value.each_pair { |key, value|
146
+ index << key.to_i+48 # ASCII integer. 1 => 49, ...
147
+ # recursive call if value is a Hash
148
+ if value.is_a?(Hash)
149
+ value = self.class.serialize_hash_to_indexed(value)
150
+ # array => serializes each element
151
+ elsif value.is_a?(Array)
152
+ value.map! { |val|
153
+ if val.is_a?(Hash)
154
+ serialize_hash_to_indexed(val)
155
+ else
156
+ val
157
+ end
158
+ }
159
+ end
160
+ # insert encoded value in Array
161
+ result.push value
162
+ }
163
+ # include index as first element
164
+ result.unshift(index)
165
+ end
166
+
167
+ # This method parses a JSON encoded message to the self.class object
168
+ def parse_from_json(data, decoding_key = :tag)
169
+ # decode if data is JSON
170
+ data.is_a?(String) and data = ActiveSupport::JSON.decode(data)
171
+
172
+ # per each hash element:
173
+ data.each_pair { |key, value|
174
+ key = decoding_key.eql?(:tag) ? key.to_i : key.to_s
175
+
176
+ # get the field object using the key (field tag)
177
+ #field = self.get_field_by_tag(key.to_i)
178
+ field = self.send("get_field_by_#{decoding_key}".to_sym, key)
179
+
180
+ if field.nil?
181
+ # ignore unknown field
182
+ elsif field.repeated?
183
+ # create the element
184
+ array = self.__send__(field.name)
185
+ value.each { |val|
186
+ # if value is a complex field, create the object and parse the content
187
+ if field.is_a?(Protobuf::Field::MessageField)
188
+ instance = field.type.new
189
+ val = instance.parse_from_json(val, decoding_key)
190
+ end
191
+ # include each element in the parent element field
192
+ array.push(val)
193
+ }
194
+ else
195
+ # if value is a complex field, create the object and parse the content
196
+ if field.is_a?(Protobuf::Field::MessageField)
197
+ instance = field.type.new
198
+ value = instance.parse_from_json(value, decoding_key)
199
+ end
200
+ # set the message field
201
+ self.__send__("#{field.name}=", value)
202
+ end
203
+ }
204
+ self
205
+ end
206
+
207
+ # This method parses a INDEXED encoded message to a hash using
208
+ # klass message as model.
209
+ # We need to know the specific klass to decode to differenciate between
210
+ # vectors and message fields
211
+ def self.parse_indexed_to_hash(data, klass)
212
+ values = {}
213
+ index = data.shift
214
+ index.each_codepoint { |tag|
215
+ tag = tag-48
216
+ field = klass.get_field_by_tag(tag)
217
+ val = data.shift
218
+
219
+ if val.is_a?(Array) && field.is_a?(Protobuf::Field::MessageField)
220
+ if field.repeated?
221
+ val.map! { |v|
222
+ v = self.parse_indexed_to_hash(v, field.type)
223
+ }
224
+ else
225
+ val = self.parse_indexed_to_hash(val, field.type)
226
+ end
227
+ end
228
+ values[tag.to_s] = val
229
+ }
230
+ values
231
+ end
232
+
233
+ # This method parses a INDEXED encoded message to a hash using
234
+ # self.class message as model.
235
+ def parse_from_indexed(data)
236
+ # decode if data is JSON
237
+ data.is_a?(String) and data = ActiveSupport::JSON.decode(data)
238
+ values = self.class.parse_indexed_to_hash(data, self)
239
+ parse_from_json(values)
240
+ end
241
+
242
+ # allow new parsing, not only binary
243
+ alias :parse_from_string_old :parse_from_string
244
+
245
+ # This method is used to parse the message from a specific format
246
+ # based on the class variable *encoding*
247
+ def parse_from_string(string)
248
+ case @@encoding
249
+ when EncodingType::BINARY
250
+ parse_from_string_old(string)
251
+ when EncodingType::HASHMAP
252
+ parse_from_json(string, :name)
253
+ when EncodingType::TAGMAP
254
+ parse_from_json(string, :tag)
255
+ when EncodingType::INDEXED
256
+ parse_from_indexed(string)
257
+ else
258
+ raise ArgumentError, "Invalid encoding type"
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,3 @@
1
+ module Protojson
2
+ VERSION = "0.1.0"
3
+ end
data/lib/protojson.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'protobuf/message/message'
2
+ require 'extensions/protobuf/message'
3
+
4
+ module Protojson
5
+ # Your code goes here...
6
+ end
data/protojson.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "protojson/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "protojson"
7
+ s.version = Protojson::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Juan de Bravo", "Ivan -DrSlump- Montes"]
10
+ s.email = ["juandebravo@gmail.com", "drslump@pollinimini.net"]
11
+ s.homepage = ""
12
+ s.summary = %q{Ruby extension to ruby-protobuf to enable three new encodings}
13
+ s.description = %q{A Ruby gem for Google's Protocol Buffers messages using three different encodings JSON
14
+ based syntax instead of the original binary protocol. Supported formats
15
+
16
+ - Hashmap: A tipical JSON message, with key:value pairs where the key is a string representing
17
+ the field name.
18
+
19
+ - Tagmap: Very similar to Hashmap, but instead of having the field name as key it has the
20
+ field tag number as defined in the proto definition.
21
+
22
+ - Indexed: Takes the Tagmap format a further step and optimizes the size needed for
23
+ tag numbers by packing all of them as a string, where each character represents a tag,
24
+ and placing it as the first element of an array.}
25
+
26
+ s.rubyforge_project = "protojson"
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.files.delete("Gemfile.lock")
30
+ s.files.delete(".gitignore")
31
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
33
+ s.require_paths = ["lib"]
34
+ end
@@ -0,0 +1,56 @@
1
+ ### Generated by rprotoc. DO NOT EDIT!
2
+ ### <proto file: examples/addressbook.proto>
3
+ # package tutorial;
4
+ #
5
+ # message Person {
6
+ # required string name = 1;
7
+ # required int32 id = 2;
8
+ # optional string email = 3;
9
+ #
10
+ # enum PhoneType {
11
+ # MOBILE = 0;
12
+ # HOME = 1;
13
+ # WORK = 2;
14
+ # }
15
+ #
16
+ # message PhoneNumber {
17
+ # required string number = 1;
18
+ # optional PhoneType type = 2 [default = HOME];
19
+ # }
20
+ #
21
+ # repeated PhoneNumber phone = 4;
22
+ # }
23
+ #
24
+ # message AddressBook {
25
+ # repeated Person person = 1;
26
+ # }
27
+
28
+ require 'protobuf/message/message'
29
+ require 'protobuf/message/enum'
30
+ require 'protobuf/message/service'
31
+ require 'protobuf/message/extend'
32
+
33
+ module Tutorial
34
+ class Person < ::Protobuf::Message
35
+ defined_in __FILE__
36
+ required :string, :name, 1
37
+ required :int32, :id, 2
38
+ optional :string, :email, 3
39
+ class PhoneType < ::Protobuf::Enum
40
+ defined_in __FILE__
41
+ MOBILE = 0
42
+ HOME = 1
43
+ WORK = 2
44
+ end
45
+ class PhoneNumber < ::Protobuf::Message
46
+ defined_in __FILE__
47
+ required :string, :number, 1
48
+ optional :PhoneType, :type, 2, :default => :HOME
49
+ end
50
+ repeated :PhoneNumber, :phone, 4
51
+ end
52
+ class AddressBook < ::Protobuf::Message
53
+ defined_in __FILE__
54
+ repeated :Person, :person, 1
55
+ end
56
+ end
data/spec/main.rb ADDED
@@ -0,0 +1,55 @@
1
+
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
3
+ $:.unshift File.join(File.dirname(__FILE__),'.')
4
+
5
+ require 'rubygems'
6
+ require 'addressbook.pb'
7
+ require 'protojson'
8
+
9
+ unless ARGV.size == 1
10
+ puts "Usage: #{$0} ADDRESS_BOOK_FILE"
11
+ exit
12
+ end
13
+
14
+ person = Tutorial::Person.new
15
+ person.parse_from_file ARGV[0]
16
+
17
+ puts ""
18
+ puts "Person data"
19
+ puts "..........."
20
+ p person
21
+
22
+ Protobuf::Message::encoding = Protobuf::Message::EncodingType::INDEXED
23
+
24
+ value = person.serialize_to_string
25
+
26
+ puts ""
27
+ puts "Indexed encoding"
28
+ puts "................"
29
+ puts value
30
+
31
+ Protobuf::Message::encoding = Protobuf::Message::EncodingType::TAGMAP
32
+
33
+ value = person.serialize_to_string
34
+
35
+ puts ""
36
+ puts "Tagmap encoding"
37
+ puts "..............."
38
+ puts value
39
+
40
+ Protobuf::Message::encoding = Protobuf::Message::EncodingType::HASHMAP
41
+
42
+ value = person.serialize_to_string
43
+
44
+ puts ""
45
+ puts "Hashmap encoding"
46
+ puts "................"
47
+ puts value
48
+ puts ""
49
+
50
+ #Protobuf::Message::encoding = Protobuf::Message::EncodingType::BINARY
51
+
52
+ #value = person.serialize_to_string
53
+
54
+ #puts value
55
+
data/spec/person ADDED
Binary file
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protojson
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Juan de Bravo
13
+ - Ivan -DrSlump- Montes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-28 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: |-
23
+ A Ruby gem for Google's Protocol Buffers messages using three different encodings JSON
24
+ based syntax instead of the original binary protocol. Supported formats
25
+
26
+ - Hashmap: A tipical JSON message, with key:value pairs where the key is a string representing
27
+ the field name.
28
+
29
+ - Tagmap: Very similar to Hashmap, but instead of having the field name as key it has the
30
+ field tag number as defined in the proto definition.
31
+
32
+ - Indexed: Takes the Tagmap format a further step and optimizes the size needed for
33
+ tag numbers by packing all of them as a string, where each character represents a tag,
34
+ and placing it as the first element of an array.
35
+ email:
36
+ - juandebravo@gmail.com
37
+ - drslump@pollinimini.net
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - Gemfile
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - lib/extensions/protobuf/message.rb
50
+ - lib/protojson.rb
51
+ - lib/protojson/version.rb
52
+ - protojson.gemspec
53
+ - spec/addressbook.pb.rb
54
+ - spec/main.rb
55
+ - spec/person
56
+ has_rdoc: true
57
+ homepage: ""
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project: protojson
84
+ rubygems_version: 1.3.7
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Ruby extension to ruby-protobuf to enable three new encodings
88
+ test_files:
89
+ - spec/addressbook.pb.rb
90
+ - spec/main.rb
91
+ - spec/person