rubybits 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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.md +7 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/rubybits.rb +309 -0
- data/rubybits.gemspec +71 -0
- data/spec/rubybits_spec.rb +298 -0
- data/spec/spec_helper.rb +12 -0
- metadata +165 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.1.0"
|
10
|
+
gem "yard", "~> 0.6.0"
|
11
|
+
gem "bluecloth", "~> 2.0.9"
|
12
|
+
gem "bundler", "~> 1.0.0"
|
13
|
+
gem "jeweler", "~> 1.5.1"
|
14
|
+
gem "rcov", ">= 0"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
bluecloth (2.0.9)
|
5
|
+
diff-lcs (1.1.2)
|
6
|
+
git (1.2.5)
|
7
|
+
jeweler (1.5.1)
|
8
|
+
bundler (~> 1.0.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rake
|
11
|
+
rake (0.8.7)
|
12
|
+
rcov (0.9.9)
|
13
|
+
rspec (2.1.0)
|
14
|
+
rspec-core (~> 2.1.0)
|
15
|
+
rspec-expectations (~> 2.1.0)
|
16
|
+
rspec-mocks (~> 2.1.0)
|
17
|
+
rspec-core (2.1.0)
|
18
|
+
rspec-expectations (2.1.0)
|
19
|
+
diff-lcs (~> 1.1.2)
|
20
|
+
rspec-mocks (2.1.0)
|
21
|
+
yard (0.6.3)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bluecloth (~> 2.0.9)
|
28
|
+
bundler (~> 1.0.0)
|
29
|
+
jeweler (~> 1.5.1)
|
30
|
+
rcov
|
31
|
+
rspec (~> 2.1.0)
|
32
|
+
yard (~> 0.6.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Micah Wylde
|
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,7 @@
|
|
1
|
+
# RubyBits
|
2
|
+
|
3
|
+
RubyBits is a library that makes dealing with binary formats easier. In
|
4
|
+
particular, it provides the Structure class, which allows for easy parsing
|
5
|
+
and creation of binary strings according to specific formats. More usage
|
6
|
+
information can be found in the docs (generated by `rake yard`) or by looking
|
7
|
+
at the specs.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "rubybits"
|
16
|
+
gem.homepage = "http://github.com/mwylde/rubybits"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{A library that makes dealing with bit strings and binary formats easier, inspired by BitStruct}
|
19
|
+
gem.description = %Q{RubyBits simplifies the task of parsing and generating binary strings in particular formats.}
|
20
|
+
gem.email = "mwylde@wesleyan.edu"
|
21
|
+
gem.authors = ["Micah Wylde"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'yard'
|
43
|
+
YARD::Rake::YardocTask.new do |t|
|
44
|
+
t.options = ['--no-private']
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/rubybits.rb
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
# Provides various utilities for working with binary formats.
|
2
|
+
module RubyBits
|
3
|
+
# Raised when you set a field to a value that is invalid for the type of
|
4
|
+
# the field (i.e., too large or the wrong type)
|
5
|
+
class FieldValueException < Exception; end
|
6
|
+
|
7
|
+
# You can subclass RubyBits::Strcuture to define new binary formats. This
|
8
|
+
# can be used for lots of purposes: reading binary data, communicating in
|
9
|
+
# binary formats (like TCP/IP, http, etc).
|
10
|
+
#
|
11
|
+
# Currently, three field types are supported: unsigned, signed and variable. Unsigned
|
12
|
+
# and signed fields are big-endian and can be any number of bits in size. Unsigned
|
13
|
+
# integers are assumed to be encoded with two's complement. Variable fields are binary
|
14
|
+
# strings with their size defined by the value of another field (given by passing that
|
15
|
+
# field's name to the :length option). This size is assumed to be in bits; if it is
|
16
|
+
# in fact in bytes, you should pass :byte to the :unit option (see the example).
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# class NECProjectorFormat < RubyBits::Structure
|
20
|
+
# unsigned :id1, 8, "Identification data assigned to each command"
|
21
|
+
# unsigned :id2, 8, "Identification data assigned to each command"
|
22
|
+
# unsigned :p_id, 8, "Projector ID"
|
23
|
+
# unsigned :m_code, 4, "Model code for projector"
|
24
|
+
# unsigned :len, 12, "Length of data in bytes"
|
25
|
+
# variable :data, 8, "Packet data", :length => :len, :unit => :byte
|
26
|
+
# unsigned :checksum,8, "Checksum"
|
27
|
+
#
|
28
|
+
# checksum :checksum do |bytes|
|
29
|
+
# bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# NECProjectorFormat.parse(buffer)
|
34
|
+
# # => [[<NECProjectorFormat>, <NECProjectorFormat>], rest]
|
35
|
+
#
|
36
|
+
# NECProjectorFormat.new(:id1 => 0x44, :id2 => 2, :p_id => 0, :m_code => 0, :len => 5, :data => "hello").to_s.bytes.to_a
|
37
|
+
# # => [0x44, 0x2, 0x05, 0x00, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x5F]
|
38
|
+
class Structure < Object
|
39
|
+
class << self
|
40
|
+
private
|
41
|
+
#@private
|
42
|
+
FIELD_TYPES = {
|
43
|
+
:unsigned => {
|
44
|
+
:validator => proc{|val, size, options| val.is_a?(Fixnum) && val < 2**size},
|
45
|
+
:unpack => proc {|s, offset, length, options|
|
46
|
+
number = 0
|
47
|
+
s_iter = s.bytes
|
48
|
+
byte = 0
|
49
|
+
# advance the iterator by the number of whole or partial bytes in the offset (offset div 8)
|
50
|
+
((offset.to_f/8).ceil).times{|i| byte = s_iter.next}
|
51
|
+
|
52
|
+
length.times{|bit|
|
53
|
+
byte = s_iter.next if offset % 8 == 0
|
54
|
+
src_bit = (7-offset%8)
|
55
|
+
number |= (1 << (length-1-bit)) if (byte & (1 << src_bit)) > 0
|
56
|
+
#puts "Reading: #{src_bit} from #{"%08b" % byte} => #{(byte & (1 << src_bit)) > 0 ? 1 : 0}"
|
57
|
+
offset += 1
|
58
|
+
}
|
59
|
+
number
|
60
|
+
}
|
61
|
+
},
|
62
|
+
:signed => {
|
63
|
+
:validator => proc{|val, size, options| val.is_a?(Fixnum) && val.abs < 2**(size-1)},
|
64
|
+
:unpack => proc{|s, offset, length, options|
|
65
|
+
number = 0
|
66
|
+
s_iter = s.bytes
|
67
|
+
byte = 0
|
68
|
+
# advance the iterator by the number of whole bytes in the offset (offset div 8)
|
69
|
+
((offset.to_f/8).ceil).times{|i| byte = s_iter.next}
|
70
|
+
# is this a positive number? yes if the most significant bit is 0
|
71
|
+
byte = s_iter.next if offset % 8 == 0
|
72
|
+
pos = byte & (1 << 7 - offset%8) == 0
|
73
|
+
#puts "String: #{s.bytes.to_a.collect{|x| "%08b" % x}.join(" ")}"
|
74
|
+
#puts "Byte: #{"%08b" % byte}, offset: #{offset}"
|
75
|
+
|
76
|
+
length.times{|bit|
|
77
|
+
byte = s_iter.next if offset % 8 == 0 && bit > 7
|
78
|
+
src_bit = (7-offset%8)
|
79
|
+
number |= (1 << (length-1-bit)) if ((byte & (1 << src_bit)) > 0) ^ (!pos)
|
80
|
+
offset += 1
|
81
|
+
}
|
82
|
+
#puts "Pos #{pos}, number: #{number}"
|
83
|
+
pos ? number : -number-1
|
84
|
+
}
|
85
|
+
},
|
86
|
+
:variable => {
|
87
|
+
:validator => proc{|val, size, options| val.is_a?(String)},
|
88
|
+
:unpack => proc{|s, offset, length, options|
|
89
|
+
output = []
|
90
|
+
s_iter = s.bytes
|
91
|
+
byte = 0
|
92
|
+
# advance the iterator by the number of whole bytes in the offset (offset div 8)
|
93
|
+
((offset.to_f/8).ceil).times{|i| byte = s_iter.next}
|
94
|
+
length.times{|bit|
|
95
|
+
byte = s_iter.next if offset % 8 == 0
|
96
|
+
output << 0 if bit % 8 == 0
|
97
|
+
|
98
|
+
src_bit = (7-offset%8)
|
99
|
+
output[-1] |= (1 << (7-bit%8)) if (byte & (1 << src_bit)) > 0
|
100
|
+
offset += 1
|
101
|
+
}
|
102
|
+
output.pack("c*")
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
FIELD_TYPES.each{|kind, field|
|
107
|
+
define_method kind do |name, size, description, *options|
|
108
|
+
field(kind, name, size, description, field[:validator], options[0])
|
109
|
+
end
|
110
|
+
}
|
111
|
+
|
112
|
+
define_method :variable do |name, description, *options|
|
113
|
+
field(:variable, name, nil, description, FIELD_TYPES[:variable][:validator], options[0])
|
114
|
+
end
|
115
|
+
|
116
|
+
public
|
117
|
+
# Sets the checksum field. Setting a checksum field alters the functionality
|
118
|
+
# in several ways: the checksum is automatically calculated and set, and #parse
|
119
|
+
# will only consider a bitstring to be a valid instance of the structure if it
|
120
|
+
# has a checksum appropriate to its data.
|
121
|
+
# @param field [Symbol] the field that contains the checksum data
|
122
|
+
# @yield [bytes] block that should calculate the checksum given bytes, which is
|
123
|
+
# an array of bytes representing the full structure, with the checksum field
|
124
|
+
# set to 0
|
125
|
+
def checksum field, &block
|
126
|
+
@_checksum_field = [field, block]
|
127
|
+
self.class_eval %{
|
128
|
+
def #{field}
|
129
|
+
calculate_checksum unless @_calculating_checksum || @_checksum_cached
|
130
|
+
@__#{field}
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
# A list of the fields in the class
|
136
|
+
def fields; @_fields; end
|
137
|
+
|
138
|
+
# The checksum field
|
139
|
+
def checksum_field; @_checksum_field; end
|
140
|
+
|
141
|
+
# Determines whether a string is a valid message
|
142
|
+
# @param string [String] a binary string to be tested
|
143
|
+
# @return [Boolean] whether the string is in fact a valid message
|
144
|
+
def valid_message? string
|
145
|
+
!!from_string(string)[0]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Parses a message from the binary string assuming that the message starts at the first byte
|
149
|
+
# of the string
|
150
|
+
# @param string [String] a binary string to be interpreted
|
151
|
+
# @return [Array<Structure, string>] a pair with the first element being a structure object with
|
152
|
+
# the data from the input string (or nil if not a valid structure) and the second being the
|
153
|
+
# left-over bytes from the string (those after the message or the entire string if no valid
|
154
|
+
# message was found)
|
155
|
+
def from_string(string)
|
156
|
+
message = self.new
|
157
|
+
iter = 0
|
158
|
+
checksum = nil
|
159
|
+
fields.each{|field|
|
160
|
+
kind, name, size, description, options = field
|
161
|
+
options ||= {}
|
162
|
+
size = (kind == :variable) ? message.send(options[:length]) : size
|
163
|
+
size *= 8 if options[:unit] == :byte
|
164
|
+
begin
|
165
|
+
value = FIELD_TYPES[kind][:unpack].call(string, iter, size, options)
|
166
|
+
message.send("#{name}=", value)
|
167
|
+
checksum = value if checksum_field && name == checksum_field[0]
|
168
|
+
rescue StopIteration, FieldValueException => e
|
169
|
+
return [nil, string]
|
170
|
+
end
|
171
|
+
iter += size
|
172
|
+
}
|
173
|
+
# if there's a checksum, make sure the provided one is valid
|
174
|
+
return [nil, string] unless message.checksum == checksum if checksum_field
|
175
|
+
[message, string[((iter/8.0).ceil)..-1]]
|
176
|
+
end
|
177
|
+
|
178
|
+
# Parses out all of the messages in a given string assuming that the first message
|
179
|
+
# starts at the first byte, and there are no bytes between messages (though messages
|
180
|
+
# are not allowed to span bytes; i.e., all messages must be byte-aligned).
|
181
|
+
# @param string [String] a binary string containing the messages to be parsed
|
182
|
+
# @return [Array<Array<Structure>, String>] a pair with the first element being an
|
183
|
+
# array of messages parsed out of the string and the second being whatever part of
|
184
|
+
# the string was left over after parsing.
|
185
|
+
def parse(string)
|
186
|
+
messages = []
|
187
|
+
last_message = true
|
188
|
+
while last_message
|
189
|
+
last_message, string = from_string(string)
|
190
|
+
#puts "Found message: #{last_message.to_s.bytes.to_a}, string=#{string.bytes.to_a.inspect}"
|
191
|
+
messages << last_message if last_message
|
192
|
+
end
|
193
|
+
[messages, string]
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def field kind, name, size, description, validator, options
|
198
|
+
@_fields ||= []
|
199
|
+
@_fields << [kind, name, size, description, options]
|
200
|
+
self.class_eval do
|
201
|
+
define_method "#{name}=" do |val|
|
202
|
+
raise FieldValueException unless validator.call(val, size, options)
|
203
|
+
self.instance_variable_set("@__#{name}", val)
|
204
|
+
@_checksum_cached = false
|
205
|
+
end
|
206
|
+
end
|
207
|
+
unless checksum_field && checksum_field[0] == name
|
208
|
+
self.class_eval %{
|
209
|
+
def #{name}
|
210
|
+
@__#{name}
|
211
|
+
end
|
212
|
+
}
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Creates a new instance of the class. You can pass in field names to initialize to
|
218
|
+
# set their values.
|
219
|
+
# @example
|
220
|
+
# MyStructure.new(:field1 => 44, :field2 => 0x70, :field3 => "hello")
|
221
|
+
def initialize(values={})
|
222
|
+
values.each{|key, value|
|
223
|
+
self.send "#{key}=", value
|
224
|
+
}
|
225
|
+
@_checksum_cached = false
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns a binary string representation of the structure according to the fields defined
|
229
|
+
# and their current values.
|
230
|
+
# @return [String] bit string representing struct
|
231
|
+
def to_s
|
232
|
+
if self.class.checksum_field && !@_checksum_cached
|
233
|
+
self.calculate_checksum
|
234
|
+
end
|
235
|
+
to_s_without_checksum
|
236
|
+
end
|
237
|
+
|
238
|
+
# Calculates and sets the checksum bit according to the checksum field defined by #checksum
|
239
|
+
def calculate_checksum
|
240
|
+
if self.class.checksum_field
|
241
|
+
@_calculating_checksum = true
|
242
|
+
self.send("#{self.class.checksum_field[0]}=", 0)
|
243
|
+
checksum = self.class.checksum_field[1].call(self.to_s_without_checksum.bytes.to_a)
|
244
|
+
self.send("#{self.class.checksum_field[0]}=", checksum)
|
245
|
+
@_checksum_cached = true
|
246
|
+
@_calculating_checksum = false
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
protected
|
251
|
+
# Returns the input number with the specified bit set to the specified value
|
252
|
+
# @param byte [Fixnum] Number to be modified
|
253
|
+
# @param bit [Fixnum] Bit number to be set
|
254
|
+
# @param value [Fixnum: {0, 1}] Value to set (either 0 or 1)
|
255
|
+
# @return [Fixnum] byte with bit set to value
|
256
|
+
def set_bit(byte, bit, value)
|
257
|
+
#TODO: this can probably be made more efficient
|
258
|
+
byte & (1<<bit) > 0 == value > 0 ? byte : byte ^ (1<<bit)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns the value at position bit of byte
|
262
|
+
# @param number [Fixnum] Number to be queried
|
263
|
+
# @param bit [Fixnum] bit of interest
|
264
|
+
# @return [Fixnum: {0, 1}] 0 or 1, depending on the value of the bit at position bit of number
|
265
|
+
def get_bit(number, bit)
|
266
|
+
number & (1<<bit) > 0 ? 1 : 0
|
267
|
+
end
|
268
|
+
|
269
|
+
def to_s_without_checksum
|
270
|
+
offset = 0
|
271
|
+
buffer = []
|
272
|
+
# This method works by iterating through each bit of each field and setting the bits in
|
273
|
+
# the current output byte appropriately.
|
274
|
+
self.class.fields.each{|field|
|
275
|
+
kind, name, size, description, options = field
|
276
|
+
data = self.send(name)
|
277
|
+
options ||= {}
|
278
|
+
case kind
|
279
|
+
when :variable
|
280
|
+
data ||= ""
|
281
|
+
size = options[:length] && self.send(options[:length]) ? self.send(options[:length]) : data.size
|
282
|
+
size *= 8 if options[:unit] == :byte
|
283
|
+
byte_iter = data.bytes
|
284
|
+
if offset % 8 == 0
|
285
|
+
buffer += data.bytes.to_a + [0] * (size - data.size)
|
286
|
+
else
|
287
|
+
size.times{|i|
|
288
|
+
byte = byte_iter.next rescue 0
|
289
|
+
8.times{|bit|
|
290
|
+
buffer << 0 if offset % 8 == 0
|
291
|
+
buffer[-1] |= get_bit(byte, 7-bit) << 7-(offset % 8)
|
292
|
+
offset += 1
|
293
|
+
}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
else
|
297
|
+
data ||= 0
|
298
|
+
size.times do |bit|
|
299
|
+
buffer << 0 if offset % 8 == 0
|
300
|
+
buffer[-1] |= get_bit(data, size-bit-1) << 7-(offset % 8)
|
301
|
+
offset += 1
|
302
|
+
end
|
303
|
+
end
|
304
|
+
}
|
305
|
+
buffer.pack("c*")
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
data/rubybits.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rubybits}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Micah Wylde"]
|
12
|
+
s.date = %q{2010-11-27}
|
13
|
+
s.description = %q{RubyBits simplifies the task of parsing and generating binary strings in particular formats.}
|
14
|
+
s.email = %q{mwylde@wesleyan.edu}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/rubybits.rb",
|
29
|
+
"rubybits.gemspec",
|
30
|
+
"spec/rubybits_spec.rb",
|
31
|
+
"spec/spec_helper.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/mwylde/rubybits}
|
34
|
+
s.licenses = ["MIT"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.7}
|
37
|
+
s.summary = %q{A library that makes dealing with bit strings and binary formats easier, inspired by BitStruct}
|
38
|
+
s.test_files = [
|
39
|
+
"spec/rubybits_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.1.0"])
|
49
|
+
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
50
|
+
s.add_development_dependency(%q<bluecloth>, ["~> 2.0.9"])
|
51
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
52
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
53
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, ["~> 2.1.0"])
|
56
|
+
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
57
|
+
s.add_dependency(%q<bluecloth>, ["~> 2.0.9"])
|
58
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
60
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
61
|
+
end
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<rspec>, ["~> 2.1.0"])
|
64
|
+
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
65
|
+
s.add_dependency(%q<bluecloth>, ["~> 2.0.9"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
68
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Structure" do
|
4
|
+
it "should allow definition of a format with unsigned fields" do
|
5
|
+
class TestFormat1 < RubyBits::Structure
|
6
|
+
unsigned :field1, 8, "Field1"
|
7
|
+
unsigned :field2, 4, "Field2"
|
8
|
+
unsigned :flag, 1, "Flag"
|
9
|
+
unsigned :field3, 16, "Field3"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should allow accessing fields created" do
|
14
|
+
class TestFormat2 < RubyBits::Structure
|
15
|
+
unsigned :field1, 8, "Field1"
|
16
|
+
unsigned :field2, 4, "Field2"
|
17
|
+
unsigned :flag, 1, "Flag"
|
18
|
+
unsigned :field3, 16, "Field3"
|
19
|
+
end
|
20
|
+
|
21
|
+
tf = TestFormat2.new
|
22
|
+
tf.field1 = 0x40
|
23
|
+
tf.field1.should == 0x40
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow initialization with data" do
|
27
|
+
class TestFormat3 < RubyBits::Structure
|
28
|
+
unsigned :field1, 8, "Field1"
|
29
|
+
unsigned :field2, 4, "Field2"
|
30
|
+
unsigned :field3, 4, "Flag"
|
31
|
+
unsigned :field4, 16, "Field3"
|
32
|
+
end
|
33
|
+
|
34
|
+
tf = TestFormat3.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => 0x726b)
|
35
|
+
tf.field1.should == 0x77
|
36
|
+
tf.field2.should == 0x06
|
37
|
+
tf.field3.should == 0x0F
|
38
|
+
tf.field4.should == 0x726b
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow creation of bitstrings from structure spec" do
|
42
|
+
class TestFormat4 < RubyBits::Structure
|
43
|
+
unsigned :field1, 8, "Field1"
|
44
|
+
unsigned :field2, 4, "Field2"
|
45
|
+
unsigned :field3, 4, "Flag"
|
46
|
+
unsigned :field4, 16, "Field3"
|
47
|
+
end
|
48
|
+
|
49
|
+
tf = TestFormat4.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => 0x726b)
|
50
|
+
tf.to_s.should == "work"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should allow weird field sizes" do
|
54
|
+
class TestFormat5 < RubyBits::Structure
|
55
|
+
unsigned :field1, 5, "Field1"
|
56
|
+
unsigned :field2, 3, "Field2"
|
57
|
+
unsigned :field3, 6, "Flag"
|
58
|
+
unsigned :field4, 4, "Field3"
|
59
|
+
unsigned :field5, 11, "Field5"
|
60
|
+
unsigned :field6, 2, "Field6"
|
61
|
+
end
|
62
|
+
|
63
|
+
tf = TestFormat5.new(:field1 => 0b11010, :field2 => 0b001, :field3 => 0b101010, :field4 => 0b1011, :field5 => 0b11101010001, :field6 => 0b11)
|
64
|
+
tf.to_s.bytes.to_a.should == [0b11010001, 0b10101010, 0b11111010, 0b10001110]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow signed integers" do
|
68
|
+
class TestFormat6 < RubyBits::Structure
|
69
|
+
signed :field1, 8, "Field1"
|
70
|
+
signed :field2, 4, "Field2"
|
71
|
+
signed :field3, 4, "Field3"
|
72
|
+
end
|
73
|
+
|
74
|
+
tf = TestFormat6.new(:field1 => -10, :field2 => -4, :field3 => -7)
|
75
|
+
tf.to_s.bytes.to_a.should == [0b11110110, 0b11001001]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should calculate checksum correctly" do
|
79
|
+
class TestFormat7 < RubyBits::Structure
|
80
|
+
unsigned :field1, 8, "Field1"
|
81
|
+
unsigned :field2, 4, "Field2"
|
82
|
+
unsigned :field3, 4, "Flag"
|
83
|
+
unsigned :field4, 16, "Field3"
|
84
|
+
unsigned :checksum, 8, "Checksum (sum of all previous fields)"
|
85
|
+
|
86
|
+
checksum :checksum do |bytes|
|
87
|
+
bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
tf = TestFormat7.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => 0x726b)
|
92
|
+
tf.to_s.bytes.to_a.should == "work".bytes.to_a << ((0x77 + 0x6F + 0x72 + 0x6B) & 255)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should calculate checksum when accessed" do
|
96
|
+
class TestFormat8 < RubyBits::Structure
|
97
|
+
unsigned :field1, 8, "Field1"
|
98
|
+
unsigned :field2, 4, "Field2"
|
99
|
+
unsigned :field3, 4, "Flag"
|
100
|
+
unsigned :field4, 16, "Field3"
|
101
|
+
unsigned :checksum, 8, "Checksum (sum of all previous fields)"
|
102
|
+
|
103
|
+
checksum :checksum do |bytes|
|
104
|
+
bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
tf = TestFormat8.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => 0x726b)
|
109
|
+
tf.checksum.should == (0x77 + 0x6F + 0x72 + 0x6B) & 255
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should allow variable length fields" do
|
113
|
+
class TestFormat9 < RubyBits::Structure
|
114
|
+
unsigned :field1, 8, "Field1"
|
115
|
+
unsigned :field2, 4, "Field2"
|
116
|
+
unsigned :field3, 4, "Flag"
|
117
|
+
variable :field4, "text"
|
118
|
+
unsigned :checksum, 8, "Checksum (sum of all previous fields)"
|
119
|
+
|
120
|
+
checksum :checksum do |bytes|
|
121
|
+
bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
tf = TestFormat9.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => "hello")
|
126
|
+
checksum = (0x77 + 0x6F + "hello".bytes.to_a.reduce(:+)) & 255
|
127
|
+
tf.checksum.should == checksum
|
128
|
+
|
129
|
+
tf.to_s.bytes.to_a.should == [0x77, 0x6F] + "hello".bytes.to_a << checksum
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should allow variable length fields that are not byte aligned" do
|
133
|
+
class TestFormat10 < RubyBits::Structure
|
134
|
+
unsigned :field1, 8, "Field1"
|
135
|
+
unsigned :field2, 4, "Field2"
|
136
|
+
unsigned :field3, 4, "Flag"
|
137
|
+
unsigned :field4, 6, "Not byte aligned"
|
138
|
+
variable :text, "text"
|
139
|
+
unsigned :checksum, 8, "Checksum (sum of all previous fields)"
|
140
|
+
|
141
|
+
checksum :checksum do |bytes|
|
142
|
+
bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
string = [119, 111, 201, 133, 137, 140]
|
147
|
+
tf = TestFormat10.new(:field1 => 0x77, :field2 => 0x06, :field3 => 0x0F, :field4 => 0x32, :text => "abc")
|
148
|
+
checksum = string.reduce(:+) & 255
|
149
|
+
tf.checksum.should == checksum
|
150
|
+
|
151
|
+
tf.to_s.bytes.to_a.should == [119, 111, 201, 133, 137, 141, 36]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should allow variable length fields whose lengths are specified by another field" do
|
155
|
+
class TestFormat11 < RubyBits::Structure
|
156
|
+
unsigned :field1, 8, "Field1"
|
157
|
+
unsigned :field2, 4, "Field2"
|
158
|
+
unsigned :field3, 4, "Flag"
|
159
|
+
variable :text, "text", :length => :field2
|
160
|
+
unsigned :checksum, 8, "Checksum (sum of all previous fields)"
|
161
|
+
|
162
|
+
checksum :checksum do |bytes|
|
163
|
+
bytes[0..-2].inject{|sum, byte| sum += byte} & 255
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
tf = TestFormat11.new(:field1 => 0x77, :field2 => 0x04, :field3 => 0x0F, :text => "abc")
|
168
|
+
checksum = (0x77 + 0x4F + "abc".bytes.to_a.reduce(:+)) & 255
|
169
|
+
tf.checksum.should == checksum
|
170
|
+
|
171
|
+
tf.to_s.bytes.to_a.should == [0x77, 0x4F, 0x61, 0x62, 0x63, 0, checksum]
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should fail when setting an invalid value for a field" do
|
175
|
+
class TestFormat12 < RubyBits::Structure
|
176
|
+
unsigned :field1, 8, "Field1"
|
177
|
+
unsigned :field2, 4, "Field2"
|
178
|
+
unsigned :field3, 16, "Flag"
|
179
|
+
variable :text, "text"
|
180
|
+
end
|
181
|
+
|
182
|
+
expect {TestFormat12.new(:field1 => 257, :field2 => 0x4, :field3 => 500)}.to raise_error(RubyBits::FieldValueException)
|
183
|
+
tf = TestFormat12.new
|
184
|
+
expect {tf.field2 = 0x44}.to raise_error(RubyBits::FieldValueException)
|
185
|
+
expect {tf.field3 = 0x44122}.to raise_error(RubyBits::FieldValueException)
|
186
|
+
expect {tf.text = 55}.to raise_error(RubyBits::FieldValueException)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "parsing" do
|
191
|
+
it "should correctly determine a valid message" do
|
192
|
+
class TestFormat13 < RubyBits::Structure
|
193
|
+
unsigned :field1, 8, "Field1"
|
194
|
+
unsigned :field2, 4, "Field2"
|
195
|
+
unsigned :field3, 4, "Field3"
|
196
|
+
signed :field4, 8, "Field4"
|
197
|
+
signed :field5, 8, "Field5"
|
198
|
+
unsigned :field6, 16, "Short field"
|
199
|
+
end
|
200
|
+
TestFormat13.valid_message?([0x34, 0x41, 0b11001001, 0x24, 0x44, 0x55].pack("c*")).should == true
|
201
|
+
TestFormat13.valid_message?([0x34, 0x41, 0b11001001, 0x24, 0x44, 0x55, 0x44].pack("c*")).should == true
|
202
|
+
TestFormat13.valid_message?([0x11, 0x11, 0x44, 0x24, 0x11].pack("c*")).should == false
|
203
|
+
TestFormat13.valid_message?("").should == false
|
204
|
+
|
205
|
+
tf, string = TestFormat13.from_string([0x34, 0x41, 0b11001001, 0b00110011, 0x55, 0x11].pack("c*") + "ab")
|
206
|
+
tf.field1.should == 0x34
|
207
|
+
tf.field2.should == 0x04
|
208
|
+
tf.field3.should == 0x01
|
209
|
+
tf.field4.should == -55
|
210
|
+
tf.field5.should == 0b00110011
|
211
|
+
tf.field6.should == 0x5511
|
212
|
+
string.should == "ab"
|
213
|
+
end
|
214
|
+
it "should correctly determine a valid message with variable length fields" do
|
215
|
+
class TestFormat14 < RubyBits::Structure
|
216
|
+
unsigned :field1, 8, "Field1"
|
217
|
+
unsigned :size, 4, "Length"
|
218
|
+
unsigned :field3, 4, "Field3"
|
219
|
+
variable :text, "text", :length => :size, :unit => :byte
|
220
|
+
end
|
221
|
+
TestFormat14.valid_message?([0x44, 0x3F].pack("cc") + "abc").should == true
|
222
|
+
TestFormat14.valid_message?([0x33, 0x1C].pack("cc") + "ab").should == true
|
223
|
+
TestFormat14.valid_message?([0x11, 0x5C].pack("cc") + "abc").should == false
|
224
|
+
|
225
|
+
tf, string = TestFormat14.from_string([0x34, 0x3F].pack("c*") + "abcdefg")
|
226
|
+
tf.field1.should == 0x34
|
227
|
+
tf.size.should == 0x03
|
228
|
+
tf.field3.should == 0x0F
|
229
|
+
tf.text.should == "abc"
|
230
|
+
string.should == "defg"
|
231
|
+
end
|
232
|
+
it "should correctly determine a valid message with variable length fields and checksum" do
|
233
|
+
class TestFormat15 < RubyBits::Structure
|
234
|
+
unsigned :field1, 8, "Field1"
|
235
|
+
unsigned :size, 4, "size"
|
236
|
+
variable :text, "text", :length => :size, :unit => :byte
|
237
|
+
unsigned :checksum, 8, "checksum"
|
238
|
+
|
239
|
+
checksum :checksum do |bytes|
|
240
|
+
bytes.reduce(:+) & 255
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
TestFormat15.valid_message?([0x5C, 0x36, 0x16, 0x26, 0x3F, 0xE0].pack("c*")).should == true
|
245
|
+
TestFormat15.valid_message?([0x5C, 0x36, 0x16, 0x26, 0x3F, 0xD0].pack("c*")).should == false
|
246
|
+
TestFormat15.valid_message?([0x5C, 0x36, 0x16, 0x26, 0x30, 0xE0].pack("c*")).should == false
|
247
|
+
|
248
|
+
tf, string = TestFormat15.from_string([0x5C, 0x36, 0x16, 0x26, 0x3F, 0xE0].pack("c*") + "BC")
|
249
|
+
tf.field1.should == 0x5C
|
250
|
+
tf.size.should == 3
|
251
|
+
tf.text.should == "abc"
|
252
|
+
tf.checksum.should == 254
|
253
|
+
string.should == "BC"
|
254
|
+
end
|
255
|
+
it "should parse fix-width format" do
|
256
|
+
class TestFormat16 < RubyBits::Structure
|
257
|
+
unsigned :field1, 8, "Field1"
|
258
|
+
unsigned :field2, 4, "Field2"
|
259
|
+
unsigned :field3, 4, "Field3"
|
260
|
+
signed :field4, 8, "Field4"
|
261
|
+
end
|
262
|
+
|
263
|
+
messages, string = TestFormat16.parse([0x34, 0x41, 0b11001001, 0x55, 0xCF, 0b00110110].pack("c*") + "ab")
|
264
|
+
messages[0].field1.should == 0x34
|
265
|
+
messages[0].field2.should == 0x04
|
266
|
+
messages[0].field3.should == 0x01
|
267
|
+
messages[0].field4.should == -55
|
268
|
+
|
269
|
+
messages[1].field1.should == 0x55
|
270
|
+
messages[1].field2.should == 0xC
|
271
|
+
messages[1].field3.should == 0xF
|
272
|
+
messages[1].field4.should == 0b00110110
|
273
|
+
|
274
|
+
string.should == "ab"
|
275
|
+
end
|
276
|
+
it "should parse variable-width format" do
|
277
|
+
class TestFormat17 < RubyBits::Structure
|
278
|
+
unsigned :field1, 8, "Field1"
|
279
|
+
unsigned :size, 4, "Length"
|
280
|
+
unsigned :field3, 4, "Field3"
|
281
|
+
variable :text, "text", :length => :size, :unit => :byte
|
282
|
+
end
|
283
|
+
|
284
|
+
messages, string = TestFormat17.parse([0x44, 0x24].pack("c*") + "hi" + [0x55, 0x62].pack("c*") + "hello! friend")
|
285
|
+
messages[0].field1.should == 0x44
|
286
|
+
messages[0].size.should == 0x02
|
287
|
+
messages[0].field3.should == 0x04
|
288
|
+
messages[0].text.should == "hi"
|
289
|
+
|
290
|
+
messages[1].field1.should == 0x55
|
291
|
+
messages[1].size.should == 0x6
|
292
|
+
messages[1].field3.should == 0x2
|
293
|
+
messages[1].text.should == "hello!"
|
294
|
+
|
295
|
+
string.should == " friend"
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'rubybits'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubybits
|
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
|
+
- Micah Wylde
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-27 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
version: 2.1.0
|
32
|
+
type: :development
|
33
|
+
prerelease: false
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
- 6
|
45
|
+
- 0
|
46
|
+
version: 0.6.0
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: bluecloth
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 2
|
59
|
+
- 0
|
60
|
+
- 9
|
61
|
+
version: 2.0.9
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: bundler
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ~>
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 1
|
74
|
+
- 0
|
75
|
+
- 0
|
76
|
+
version: 1.0.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: jeweler
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 1
|
89
|
+
- 5
|
90
|
+
- 1
|
91
|
+
version: 1.5.1
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rcov
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: *id006
|
108
|
+
description: RubyBits simplifies the task of parsing and generating binary strings in particular formats.
|
109
|
+
email: mwylde@wesleyan.edu
|
110
|
+
executables: []
|
111
|
+
|
112
|
+
extensions: []
|
113
|
+
|
114
|
+
extra_rdoc_files:
|
115
|
+
- LICENSE.txt
|
116
|
+
- README.md
|
117
|
+
files:
|
118
|
+
- .document
|
119
|
+
- .rspec
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- VERSION
|
126
|
+
- lib/rubybits.rb
|
127
|
+
- rubybits.gemspec
|
128
|
+
- spec/rubybits_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
has_rdoc: true
|
131
|
+
homepage: http://github.com/mwylde/rubybits
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
post_install_message:
|
135
|
+
rdoc_options: []
|
136
|
+
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
hash: 1583675054934852324
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
version: "0"
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
segments:
|
154
|
+
- 0
|
155
|
+
version: "0"
|
156
|
+
requirements: []
|
157
|
+
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 1.3.7
|
160
|
+
signing_key:
|
161
|
+
specification_version: 3
|
162
|
+
summary: A library that makes dealing with bit strings and binary formats easier, inspired by BitStruct
|
163
|
+
test_files:
|
164
|
+
- spec/rubybits_spec.rb
|
165
|
+
- spec/spec_helper.rb
|