databrick 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/InitIRB.rb +10 -0
  2. data/README +1 -0
  3. data/databrick.rb +216 -0
  4. data/test.rb +34 -0
  5. metadata +70 -0
@@ -0,0 +1,10 @@
1
+ require 'DataBrick'
2
+
3
+ class CatPeople < DataBrick
4
+ define_piece :next, :pointer
5
+ define_piece :friend, :string
6
+ define_piece :cat, :string
7
+ end
8
+
9
+ Cat = CatPeople.new(open('cats.db', 'a+'), 0)
10
+
data/README ADDED
@@ -0,0 +1 @@
1
+ See comments in DataBrick.rb for now. :)
@@ -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