Class: Ruckus::Parsel
- Defined in:
- lib/ruckus/parsel.rb,
lib/ruckus/selector.rb
Overview
A parsel is an object that supports the following three methods:
-
An
initialize
that accepts and passes through an opts hash -
A <tt>to_s<tt> that renders binary, and takes an optional “offset” arg
-
A
capture
that parses a binary string and returns whatever is left of the strin.
You assemble a tree of Parsel objects to make a packet. The parsel tree winds up looking a lot like the HTML DOM:
-
There’s a tree root you can get to from anywhere by calling
root
-
Nodes can have DOM-style “classes” (we call them “names”)
-
Nodes can have DOM-style “ids” (we call them “tags”)
As the tree is rendered (by calling to_s on every node), the offset in the final string is stored in @rendered_offset.
All this is useful for instance with binary formats that required padded offsets from headers — tag the header base, look it up from anywhere, compare rendered offsets, and you know how much padding you need.
Any attribute of a Parsel can be replaced with:
-
A method to call on a sibling node to get the value (for instance, the size of a string)
-
A complex specification of which node to query, what method to use, and how to modify it
-
A block
Constant Summary collapse
- VERBOTEN =
[:size, :capture]
Instance Attribute Summary collapse
-
#name ⇒ Object
Returns the value of attribute name.
-
#parent ⇒ Object
Returns the value of attribute parent.
-
#rendered_offset ⇒ Object
Returns the value of attribute rendered_offset.
-
#rendering ⇒ Object
Returns the value of attribute rendering.
-
#tag ⇒ Object
Returns the value of attribute tag.
-
#value ⇒ Object
Returns the value of attribute value.
Class Method Summary collapse
-
.bytes_for_bits(b) ⇒ Object
How many bytes, rounded up, does it take to represent this many bits?.
-
.coerce(val) ⇒ Object
Coerce native types to their equivalent parsels, so you can assign “1” to a Number field, etc.
-
.endian? ⇒ Boolean
What’s our native endianness? :big or :little.
- .factory? ⇒ Boolean
-
.native?(endian) ⇒ Boolean
Is this endianness native?.
Instance Method Summary collapse
- #each_matching_selector(sel) ⇒ Object
-
#find_containing(klass) ⇒ Object
Walk up parents until you find one of type
klass
; so, if you have a FooHeader containing lots of FooRecords, and you have a handle on a FooElement inside a FooRecord, you can callx.find_containing(FooRecord)
to jump right back to the header. -
#find_tag(t) ⇒ Object
Find any node by its tag.
-
#find_tag_struct(t) ⇒ Object
Find a node by its tag, but return its enclosing structure, not the node itself.
-
#fixup ⇒ Object
Stubbed.
- #in(*args) ⇒ Object
- #incomplete! ⇒ Object
- #index_for_selectors ⇒ Object
-
#initialize(opts = {}) ⇒ Parsel
constructor
Note: all opts become instance variables.
-
#inspect ⇒ Object
This got insane following links, so cut down what ‘pp’ gives you, mostly by not recursively rendering children.
- #matches_selector?(sel) ⇒ Boolean
-
#method_missing(meth, *args, &block) ⇒ Object
Parsel decorates native types.
-
#native? ⇒ Boolean
Is this parsel in native byte order?.
-
#next ⇒ Object
Get to the next node (at this level of the tree — does not traverse back through parent).
-
#out(*args) ⇒ Object
phase in the new names.
-
#parent_structure(p = self) ⇒ Object
(also: #parent_struct)
Walk up the parents of this node until we find a containing structure.
-
#permute(all = nil) ⇒ Object
This kind of doesn’t belong here.
-
#prev ⇒ Object
Opposite of Parsel#next.
-
#resolve(val) ⇒ Object
Read Blob first.
- #respond_to?(symbol, include_priv = false) ⇒ Boolean
-
#root(p = self) {|p| ... } ⇒ Object
Traverse all the way to the root of the tree.
-
#size ⇒ Object
How big is the rendered output in bytes? By default, the worst possible impl: render and take size of result.
-
#visit(&block) ⇒ Object
Wrap tree-traversal;
&block
is called for each element of the tree. -
#where_am_i? ⇒ Boolean
See Blob.
Constructor Details
#initialize(opts = {}) ⇒ Parsel
Note: all opts become instance variables. Never created directly.
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/ruckus/parsel.rb', line 190 def initialize(opts={}) (rec = lambda do |k| begin k.const_get(:OPTIONS).each do |key, v| opts[key] ||= v end rescue ensure rec.call(k.superclass) if k.inherits_from? Parsel end end).call(self.class) opts.each do |k, v| next if VERBOTEN.member? k instance_variable_set "@#{ k }".intern, v (class << self; self; end).instance_eval { attr_accessor k } end capture(opts[:capture]) if opts[:capture] end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args, &block) ⇒ Object
Parsel decorates native types.
225 226 227 |
# File 'lib/ruckus/parsel.rb', line 225 def method_missing(meth, *args, &block) @value.send meth, *args, &block end |
Instance Attribute Details
#name ⇒ Object
Returns the value of attribute name.
37 38 39 |
# File 'lib/ruckus/parsel.rb', line 37 def name @name end |
#parent ⇒ Object
Returns the value of attribute parent.
34 35 36 |
# File 'lib/ruckus/parsel.rb', line 34 def parent @parent end |
#rendered_offset ⇒ Object
Returns the value of attribute rendered_offset.
35 36 37 |
# File 'lib/ruckus/parsel.rb', line 35 def rendered_offset @rendered_offset end |
#rendering ⇒ Object
Returns the value of attribute rendering.
38 39 40 |
# File 'lib/ruckus/parsel.rb', line 38 def rendering @rendering end |
#tag ⇒ Object
Returns the value of attribute tag.
36 37 38 |
# File 'lib/ruckus/parsel.rb', line 36 def tag @tag end |
#value ⇒ Object
Returns the value of attribute value.
33 34 35 |
# File 'lib/ruckus/parsel.rb', line 33 def value @value end |
Class Method Details
.bytes_for_bits(b) ⇒ Object
How many bytes, rounded up, does it take to represent this many bits?
61 62 63 |
# File 'lib/ruckus/parsel.rb', line 61 def self.bytes_for_bits(b) (b / 8) + ((b % 8) != 0 ? 1 : 0) end |
.coerce(val) ⇒ Object
Coerce native types to their equivalent parsels, so you can assign “1” to a Number field, etc
68 69 70 71 72 73 74 |
# File 'lib/ruckus/parsel.rb', line 68 def self.coerce(val) if val.kind_of? Numeric Number.new :value => val elsif val.kind_of? String Str.new :value => val end end |
.endian? ⇒ Boolean
What’s our native endianness? :big or :little
48 49 50 |
# File 'lib/ruckus/parsel.rb', line 48 def self.endian? @endianness ||= ([1].pack("I") == "\x01\x00\x00\x00" ? :little : :big) end |
.factory? ⇒ Boolean
363 |
# File 'lib/ruckus/parsel.rb', line 363 def self.factory?; false; end |
.native?(endian) ⇒ Boolean
Is this endianness native?
54 55 56 |
# File 'lib/ruckus/parsel.rb', line 54 def self.native?(endian) endian? == endian end |
Instance Method Details
#each_matching_selector(sel) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/ruckus/selector.rb', line 37 def each_matching_selector(sel) index_for_selectors if not @selector_index sels = sel.split.reverse.map {|x| Selector.new(x)} first = sels.shift ipool = first.rid ? @selector_index[0][first.rid] : nil cpool = first.rclass ? @selector_index[1][first.rclass] : nil kpool = first.rkind ? @selector_index[2][first.rkind] : nil pool = ipool || cpool || kpool pool = (pool & ipool) if ipool pool = (pool & cpool) if cpool pool = (pool & kpool) if kpool # XXX this loop is fucked: # outer loop needs to be pool # will capture parent nodes in arbitrary order # sels.each do |s| # pool.each do |victim| # found = false # victim.root {|n| found = true if n.matches_selector? s} # pool.delete(victim) if not found # end # end pool.each do |victim| tmpsels = sels.clone cur = sels.shift victim.root do |parent| break if not cur if parent.matches_selector? cur cur = sels.shift end end pool.delete(victim) if cur end pool.each do |n| yield n end pool.size end |
#find_containing(klass) ⇒ Object
Walk up parents until you find one of type klass
; so, if you have a FooHeader containing lots of FooRecords, and you have a handle on a FooElement inside a FooRecord, you can call x.find_containing(FooRecord)
to jump right back to the header.
288 289 290 291 292 293 294 |
# File 'lib/ruckus/parsel.rb', line 288 def find_containing(klass) p = parent while p and not p.kind_of? klass p = p.parent end return p end |
#find_tag(t) ⇒ Object
Find any node by its tag. This isn’t indexed in any way, so it’s slow, but who cares?
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/ruckus/parsel.rb', line 252 def find_tag(t) r = nil if @tag == t r = self elsif begin each do |it| r = it.find_tag(t) break if r end rescue end end return r end |
#find_tag_struct(t) ⇒ Object
Find a node by its tag, but return its enclosing structure, not the node itself. This is usually what you want; the first field of a header might be tagged, but you just wanted a reference to the header entire.
273 274 275 276 277 278 279 280 |
# File 'lib/ruckus/parsel.rb', line 273 def find_tag_struct(t) p = find_tag(t) if(p) return p.parent_struct else return nil end end |
#in(*args) ⇒ Object
361 |
# File 'lib/ruckus/parsel.rb', line 361 def in(*args); capture(*args); end |
#incomplete! ⇒ Object
365 |
# File 'lib/ruckus/parsel.rb', line 365 def incomplete!; raise IncompleteCapture; end |
#index_for_selectors ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/ruckus/selector.rb', line 19 def index_for_selectors b = lambda {|h, k| h[k] = []} ids, classes, kinds = [Hash.new(&b), Hash.new(&b), Hash.new(&b)] visit do |n| k = n.class while k != Ruckus::Parsel and k != Object kinds[k.to_s] << n k = k.superclass end (ids[n.tag.to_s] << n) if n.tag (classes[n.name.to_s] << n) if n.name end @selector_index = [ids, classes, kinds] end |
#inspect ⇒ Object
This got insane following links, so cut down what ‘pp’ gives you, mostly by not recursively rendering children.
349 350 351 352 353 354 355 356 357 |
# File 'lib/ruckus/parsel.rb', line 349 def inspect if @value.kind_of? Parsel val = "#<#{ @value.class }:#{ @value.object_id }: @name=\"#{ @name }\">" else val = @value.inspect end "#<#{ self.class }:#{ self.object_id }: @name=\"#{ @name }\" @value=#{ val }>" end |
#matches_selector?(sel) ⇒ Boolean
84 85 86 87 88 89 90 |
# File 'lib/ruckus/selector.rb', line 84 def matches_selector?(sel) index_for_selectors if not @selector_index return false if sel.rid and sel.rid != self.tag return false if sel.rclass and sel.rclass != self.name return false if sel.rkind and not @selector_index[2][sel.rkind].include? self return true end |
#native? ⇒ Boolean
Is this parsel in native byte order?
42 43 44 |
# File 'lib/ruckus/parsel.rb', line 42 def native? self.class.native @endian || :little end |
#next ⇒ Object
Get to the next node (at this level of the tree — does not traverse back through parent)
163 164 165 |
# File 'lib/ruckus/parsel.rb', line 163 def next parent[parent.place(self) + 1] end |
#out(*args) ⇒ Object
phase in the new names
360 |
# File 'lib/ruckus/parsel.rb', line 360 def out(*args); to_s(*args); end |
#parent_structure(p = self) ⇒ Object Also known as: parent_struct
Walk up the parents of this node until we find a containing structure.
176 177 178 179 180 181 182 |
# File 'lib/ruckus/parsel.rb', line 176 def parent_structure(p = self) while p.parent p = p.parent break if p.kind_of? Ruckus::Structure end return p end |
#permute(all = nil) ⇒ Object
This kind of doesn’t belong here. XXX factor out into module.
Use Parsel#visit to permute every permutable Parsel in a message, by calling value.permute (Mutator objects respond to this message). See Mutate.rb.
319 320 321 322 323 324 325 |
# File 'lib/ruckus/parsel.rb', line 319 def permute(all=nil) visit {|o| o.permute(nil)} and return if all begin @value.permute rescue end end |
#prev ⇒ Object
Opposite of Parsel#next
169 170 171 |
# File 'lib/ruckus/parsel.rb', line 169 def prev parent[parent.place(self) - 1] end |
#resolve(val) ⇒ Object
Read Blob first.
Parsels can have nonscalar values, including blocks and references to the values of other fields. This is a bit of a mess. Takes:
- all
-
Rebind all instance variables, not just @value
- meth
-
Method to call on target object to extract value, but note that you can just pass “val” as a sym for same effect. (Default: :size)
- source
-
Source Parsel to extract object, but you’ll never specify this directly. One exception: :source => :rest means, “apply the method to all subsequent elements of the blob or structure”
- offset
-
(Default: 1) which neighboring Parsel should we extract from — can also be
:this
,:prev
, and:next
. - block
-
A Proc to call with the object we’re extracting.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/ruckus/parsel.rb', line 96 def resolve(val) return nil if not val return val if [String, Integer, Ruckus::Mutator::Mutator].kind_of_these? val return val if val == true return val if val == false o = {} if val.kind_of? Symbol o[:meth] = val elsif val.kind_of? Hash o = o.merge(val) end if (t = @from_tag) || (t = o[:from_tag]) o[:source] = root.find_tag(t) raise "can't find" if not o[:source] end if (f = @from_field) || (f = o[:from_field]) o[:source] = parent_struct.send f raise "can't find field" if not o[:source] end place = parent.place(self) if not o[:source] raise "unparented" if not parent o[:offset] ||= 1 o[:offset] = 0 if o[:offset] == :this o[:offset] = -1 if o[:offset] == :prev o[:offset] = 1 if o[:offset] == :next loc = place + o[:offset] o[:source] = parent[loc] raise "can't resolve #{ o } for #{ @name }" if not o[:source] end if not o[:block] o[:meth] ||= :size if o[:source] == :rest r = 0 ((place+1)..(parent.size)).each do |i| r += parent[i].send(o[:meth]) if parent[i] end else r = o[:source].send o[:meth] end else r = o[:block].call o[:source] end # cheat: if resolution returns a symbol --- which happens # with len/string pairs, because they depend on each other --- # return nil. This effectively unbounds the string during to_s. r = nil if r.kind_of? Symbol r = @modifier.call(self, r) if @modifier return r end |
#respond_to?(symbol, include_priv = false) ⇒ Boolean
229 230 231 |
# File 'lib/ruckus/parsel.rb', line 229 def respond_to?(symbol, include_priv = false) !!(super(symbol, include_priv) || @value.respond_to?(symbol, include_priv)) end |
#root(p = self) {|p| ... } ⇒ Object
Traverse all the way to the root of the tree
235 236 237 238 239 240 241 242 |
# File 'lib/ruckus/parsel.rb', line 235 def root(p = self, &block) yield p if block_given? while p.parent p = p.parent yield p if block_given? end return p end |
#size ⇒ Object
How big is the rendered output in bytes? By default, the worst possible impl: render and take size of result. You can override to make this more reasonable.
218 219 220 221 |
# File 'lib/ruckus/parsel.rb', line 218 def size return nil if not @value return to_s.size end |
#visit(&block) ⇒ Object
Wrap tree-traversal; &block
is called for each element of the tree.
299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/ruckus/parsel.rb', line 299 def visit(&block) raise "need block" if not block_given? block.call(self) begin @value.each do |o| o.visit(&block) end rescue end end |
#where_am_i? ⇒ Boolean
See Blob.
What’s our position in the parent? This works for any enumerable parent.
332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/ruckus/parsel.rb', line 332 def where_am_i? return @where_am_i if @where_am_i raise "not parented" if not parent parent.each_with_index do |o, i| if o == self @where_am_i = i return i end end nil end |