databrick 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/InitIRB.rb +10 -0
- data/README +1 -0
- data/databrick.rb +216 -0
- data/test.rb +34 -0
- metadata +70 -0
data/InitIRB.rb
ADDED
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
See comments in DataBrick.rb for now. :)
|
data/databrick.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
#
|
2
|
+
# -=- The Story of DataBrick -=-
|
3
|
+
# Not all things in this world may be blessed with lovely
|
4
|
+
# ascii art, json, yaml, and other funky formats.
|
5
|
+
# Sometimes you need to get your arms dirty with some raw
|
6
|
+
# unadulterated binary! Binary needn't scar you for life
|
7
|
+
# though - so here's my little ORM for bits of binary.
|
8
|
+
#
|
9
|
+
# Much Love!
|
10
|
+
# <3 Bluebie <3
|
11
|
+
|
12
|
+
class DataBrick
|
13
|
+
class << self; # Metaclass Madness!
|
14
|
+
# Defines a piece of the Brick specification
|
15
|
+
def define_piece name, type, options = {}
|
16
|
+
name = name.to_sym
|
17
|
+
@parts ||= Array.new
|
18
|
+
@part_types ||= Array.new
|
19
|
+
@part_opts ||= Array.new
|
20
|
+
|
21
|
+
# if there's some smarter thing, do that!
|
22
|
+
return send("define_one_#{type}", name, options) if respond_to?("define_one_#{type}")
|
23
|
+
# otherwise do the default thing
|
24
|
+
|
25
|
+
# add to ordered lists to keep everything orderly
|
26
|
+
@parts.push name
|
27
|
+
@part_types.push type
|
28
|
+
@part_opts.push options
|
29
|
+
|
30
|
+
# make a getter method!
|
31
|
+
define_method(name) do
|
32
|
+
begin; was_at = @source.tell
|
33
|
+
@source.seek @position + offset_for(name)
|
34
|
+
send("read_#{type}", @source, options)
|
35
|
+
ensure; @source.seek was_at; end
|
36
|
+
end
|
37
|
+
|
38
|
+
# and a setter would be great too!
|
39
|
+
case type.to_sym # Numbers always take the same number of bytes, so we can update them smartly!
|
40
|
+
when :raw_string # for variable width things...
|
41
|
+
define_method("#{name}=") do |value|
|
42
|
+
begin; was_at = @source.tell
|
43
|
+
@source.seek @position
|
44
|
+
update({name => value}, @source);
|
45
|
+
ensure; @source.seek was_at; end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
define_method("#{name}=") do |value|
|
49
|
+
begin; was_at = @source.tell
|
50
|
+
@source.seek @position + offset_for(name)
|
51
|
+
@source.write self.class.send("blob_#{type}", value, {}, options)
|
52
|
+
@source.flush
|
53
|
+
ensure; @source.seek was_at; end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
define_method("length_for_#{name}") { send("type_length_#{type}", options) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# creates a blob of binary from some properties in a hash, like the ones #to_h gives you
|
61
|
+
# If you give it an IO as it's second param, it'll return an instance of this instead of
|
62
|
+
# the blob, and write the blob straight to your IO for you right where you left it. :)
|
63
|
+
def create properties, write_to = ''
|
64
|
+
original_position = write_to.pos if write_to.respond_to?(:pos)
|
65
|
+
@parts.each_with_index do |part, i|
|
66
|
+
write_to << send("blob_#{@part_types[i]}", properties[part], properties, @part_opts[i])
|
67
|
+
end
|
68
|
+
write_to.flush if write_to.respond_to?(:flush)
|
69
|
+
return write_to.respond_to?(:seek) ? self.new(write_to, original_position) : write_to
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
attr_reader :source, :position
|
74
|
+
def initialize(source, position = false)
|
75
|
+
@source = source
|
76
|
+
@position = position || source.pos
|
77
|
+
end
|
78
|
+
|
79
|
+
def update properties
|
80
|
+
blob = self.class.create(to_h.merge(properties))
|
81
|
+
raise 'Length of brick is longer! cannot safely update without overflowing! Aborting!' if length < blob.length
|
82
|
+
@source.seek @position
|
83
|
+
@source.write updated
|
84
|
+
@source.flush
|
85
|
+
end
|
86
|
+
|
87
|
+
# returns the byte length of this block serialized
|
88
|
+
def length; parts.inject(0) { |sofar, part| sofar + send("length_for_#{part}") }; end
|
89
|
+
|
90
|
+
# add all the lengths for the bits before the specified one, to figure out the byte offset
|
91
|
+
def offset_for(part)
|
92
|
+
parts[0 ... parts.index(part)].inject(0) do |sofar, piece|
|
93
|
+
sofar + self.send("length_for_#{piece}")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# returns a hash version of this DataBrick
|
98
|
+
def to_h
|
99
|
+
hash = Hash.new
|
100
|
+
parts.each { |part| hash[part] = send(part) }
|
101
|
+
return hash
|
102
|
+
end
|
103
|
+
|
104
|
+
# returns the string of this thingy
|
105
|
+
def to_s
|
106
|
+
@source.seek @position
|
107
|
+
@source.read(length)
|
108
|
+
end
|
109
|
+
|
110
|
+
# returns a stringy representation of this DataBrick's unique content
|
111
|
+
def inspect
|
112
|
+
"<#{self.class.name}##{position}: #{parts.map {|p| val = self.send(p); ".#{p}: #{val.is_a?(DataBrick) ? val.micro_inspect : val.inspect}" }.join(', ')}>"
|
113
|
+
end
|
114
|
+
|
115
|
+
def micro_inspect; "<#{self.class.name}##{position}>"; end
|
116
|
+
|
117
|
+
protected
|
118
|
+
def parts; self.class.instance_variable_get(:@parts); end
|
119
|
+
PointerDefaults = {:bits => 32, :nil_if => 0xFFFFFFFF}
|
120
|
+
|
121
|
+
# defines a simple string - defaults 8 bit length, :bits => 16 or 32 for longer strings!
|
122
|
+
def self.define_one_string(name, opts = {})
|
123
|
+
define_piece "#{name}_length", :string_length, {:string_name => name}.merge(opts)
|
124
|
+
define_piece name, :raw_string, {:length_from => "#{name}_length".to_sym}.merge(opts)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Thing readers!
|
128
|
+
IntPacker = {8 => 'C', 16 => 'n', 32 => 'N', 64 => 'Q'}
|
129
|
+
def read_integer(io, options = {}); options = {:bits => 8}.merge(options)
|
130
|
+
source.read(options[:bits] / 8).unpack(IntPacker[options[:bits]]).first
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_raw_string(io, options = {}); io.read(send(options[:length_from])); end
|
134
|
+
alias_method :read_string_length, :read_integer
|
135
|
+
|
136
|
+
def read_pointer(io, options = {})
|
137
|
+
options = PointerDefaults.merge(options)
|
138
|
+
ref = read_integer(io, options)
|
139
|
+
return nil if ref == options[:nil_if]
|
140
|
+
(options[:type] || self.class).new(io, ref)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Thing blobbers!
|
145
|
+
def self.blob_integer(int, props, options = {})
|
146
|
+
[int.to_i].pack(IntPacker[options[:bits] || 8])
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.blob_raw_string(str, props, options = {})
|
150
|
+
str.to_s
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.blob_pointer(pointee, props, options = {})
|
154
|
+
options = PointerDefaults.merge(options)
|
155
|
+
val = (pointee.position if pointee.respond_to?(:position)) || pointee
|
156
|
+
val = options[:nil_if] if val == nil && options[:nil_if]
|
157
|
+
self.blob_integer(val, props, options)
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.blob_string_length(length, props, options = {})
|
161
|
+
self.blob_integer(props[options[:string_name]].to_s.length, props, options)
|
162
|
+
end
|
163
|
+
|
164
|
+
# lengths for offset calculation
|
165
|
+
def type_length_integer(opts); (opts[:bits] || 8) / 8; end
|
166
|
+
def type_length_pointer(opts); (opts[:bits] || 32) / 8; end
|
167
|
+
def type_length_raw_string(opts); send(opts[:length_from]); end
|
168
|
+
alias_method :type_length_string_length, :type_length_integer
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
# -=- BrickFu Lessons! -=-
|
173
|
+
#### Here's how to remember your friend's cats names:
|
174
|
+
# class CatPeople < DataBrick
|
175
|
+
# define_piece :next, :pointer
|
176
|
+
# define_piece :friend, :string
|
177
|
+
# define_piece :cat, :string
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
#### And here's how you'd use that to make a little database:
|
181
|
+
# people = ["Bluebie", "Radar", "Judofyr's Sister"]
|
182
|
+
# pets = ["Phoenix", "Mr. Purrr", "Sibelius"]
|
183
|
+
# database = File.open('cats.db', 'w+')
|
184
|
+
#
|
185
|
+
# last = false; # make this variable stay out here so it lasts forever!
|
186
|
+
# people.each_with_index do |person, index|
|
187
|
+
# # go to end of file to add new CatPeople without overwriting any!
|
188
|
+
# database.seek 0, File::SEEK_END
|
189
|
+
# # add it right there
|
190
|
+
# cat = CatPeople.create({
|
191
|
+
# :friend => person,
|
192
|
+
# :cat => pets[index]
|
193
|
+
# }, database)
|
194
|
+
# # link it up in to a list. :)
|
195
|
+
# last.next = cat if last
|
196
|
+
# last = cat
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
#### So that program put all your cat people in to the file, now in
|
200
|
+
#### another program we can read it like this!
|
201
|
+
# catman = CatPeople.new(open('cats.db'))
|
202
|
+
# puts "#{catman.friend} has awesome cat #{catman.cat}."
|
203
|
+
# begin
|
204
|
+
# puts "Also, #{catman.friend} has a cat: #{catman.cat}."
|
205
|
+
# catman = catman.next
|
206
|
+
# end until catman == nil
|
207
|
+
#
|
208
|
+
|
209
|
+
#### TODO:
|
210
|
+
# @ Add float types
|
211
|
+
# @ Maybe something for signed numbers?
|
212
|
+
# @ An Array type?
|
213
|
+
# @ Booleans type! (length = bools / 8)
|
214
|
+
#
|
215
|
+
|
216
|
+
|
data/test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'DataBrick'
|
3
|
+
|
4
|
+
class CatPeople < DataBrick
|
5
|
+
define_piece :next, :pointer
|
6
|
+
define_piece :friend, :string
|
7
|
+
define_piece :cat, :string
|
8
|
+
end
|
9
|
+
|
10
|
+
# And here's how you'd use that to make a little database:
|
11
|
+
people = ["Bluebie", "Radar", "Judofyr's Sister", "Steve K.", "Steve K.", "Elliott"]
|
12
|
+
pets = ["Phoenix", "Mr. Purrr", "Sibelius", "Scuzzy", "Lady", "Tucker (actually a dog)"]
|
13
|
+
database = File.open('cats.db', 'w+')
|
14
|
+
database.sync = true
|
15
|
+
|
16
|
+
last = false; # make this variable stay out here so it lasts forever!
|
17
|
+
people.each_with_index do |person, index|
|
18
|
+
database.seek 0, File::SEEK_END
|
19
|
+
cat = CatPeople.create({
|
20
|
+
:friend => person,
|
21
|
+
:cat => pets[index]
|
22
|
+
}, database)
|
23
|
+
last.next = cat if last # link it up!
|
24
|
+
last = cat
|
25
|
+
end
|
26
|
+
|
27
|
+
# So that program put all your cat people in to the file, now in
|
28
|
+
# another program we can read it like this!
|
29
|
+
catman = CatPeople.new(open('cats.db'))
|
30
|
+
until catman == nil
|
31
|
+
puts "#{catman.friend} has a cat: #{catman.cat}."
|
32
|
+
catman = catman.next
|
33
|
+
end
|
34
|
+
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: databrick
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bluebie
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-25 00:00:00 +10:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Not all things in this world may be blessed with lovely ascii art, json, yaml, and other funky formats. Sometimes you need to get your arms dirty with some raw unadulterated binary! Binary needn't scar you for life though - so here's my little ORM for bits of binary.
|
23
|
+
email: a@creativepony.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- databrick.rb
|
32
|
+
- InitIRB.rb
|
33
|
+
- test.rb
|
34
|
+
- README
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/Bluebie/DataBrick
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- .
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
hash: 3
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.7
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: A tiny binary ORM, to save you from pack and unpack heck!
|
69
|
+
test_files:
|
70
|
+
- test.rb
|