protojson 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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