ruckus 0.5.4
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/.gitignore +22 -0
- data/README +0 -0
- data/README.markdown +51 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/ruckus.rb +70 -0
- data/lib/ruckus/blob.rb +113 -0
- data/lib/ruckus/choice.rb +55 -0
- data/lib/ruckus/dfuzz.rb +231 -0
- data/lib/ruckus/dictionary.rb +119 -0
- data/lib/ruckus/enum.rb +39 -0
- data/lib/ruckus/extensions/array.rb +33 -0
- data/lib/ruckus/extensions/class.rb +168 -0
- data/lib/ruckus/extensions/duplicable.rb +37 -0
- data/lib/ruckus/extensions/file.rb +24 -0
- data/lib/ruckus/extensions/fixnum.rb +26 -0
- data/lib/ruckus/extensions/hash.rb +10 -0
- data/lib/ruckus/extensions/integer.rb +26 -0
- data/lib/ruckus/extensions/ipaddr.rb +155 -0
- data/lib/ruckus/extensions/irb.rb +30 -0
- data/lib/ruckus/extensions/math.rb +6 -0
- data/lib/ruckus/extensions/module.rb +37 -0
- data/lib/ruckus/extensions/numeric.rb +117 -0
- data/lib/ruckus/extensions/object.rb +22 -0
- data/lib/ruckus/extensions/range.rb +16 -0
- data/lib/ruckus/extensions/socket.rb +20 -0
- data/lib/ruckus/extensions/string.rb +327 -0
- data/lib/ruckus/filter.rb +16 -0
- data/lib/ruckus/human_display.rb +79 -0
- data/lib/ruckus/ip.rb +38 -0
- data/lib/ruckus/mac_addr.rb +31 -0
- data/lib/ruckus/mutator.rb +318 -0
- data/lib/ruckus/null.rb +24 -0
- data/lib/ruckus/number.rb +360 -0
- data/lib/ruckus/parsel.rb +363 -0
- data/lib/ruckus/selector.rb +92 -0
- data/lib/ruckus/str.rb +164 -0
- data/lib/ruckus/structure.rb +263 -0
- data/lib/ruckus/structure/atcreate.rb +23 -0
- data/lib/ruckus/structure/beforebacks.rb +28 -0
- data/lib/ruckus/structure/defaults.rb +57 -0
- data/lib/ruckus/structure/factory.rb +42 -0
- data/lib/ruckus/structure/fixupfields.rb +16 -0
- data/lib/ruckus/structure/initializers.rb +14 -0
- data/lib/ruckus/structure/relate.rb +34 -0
- data/lib/ruckus/structure/replacement.rb +30 -0
- data/lib/ruckus/structure/searchmods.rb +33 -0
- data/lib/ruckus/structure/structureproxies.rb +28 -0
- data/lib/ruckus/structure/validate.rb +12 -0
- data/lib/ruckus/structure_shortcuts.rb +109 -0
- data/lib/ruckus/time_t.rb +26 -0
- data/lib/ruckus/vector.rb +97 -0
- data/ruckus.gemspec +104 -0
- data/test/test_decides.rb +61 -0
- data/test/test_mutator.rb +29 -0
- data/test/test_override.rb +23 -0
- data/test/test_replace.rb +39 -0
- metadata +138 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureBeforeCallbacks
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(ClassMethods)
|
5
|
+
|
6
|
+
klass.class_eval {
|
7
|
+
class_inheritable_array :before_callbacks
|
8
|
+
write_inheritable_array :before_callbacks, []
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_render_hook
|
13
|
+
self.class.before_callbacks.each {|cb| self.instance_eval(&cb)}
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def before_render(arg=nil, &block)
|
18
|
+
if not block_given?
|
19
|
+
raise "need a callback function" if not arg
|
20
|
+
arg = arg.intern if not arg.kind_of? Symbol
|
21
|
+
block = lambda { send(arg) }
|
22
|
+
end
|
23
|
+
|
24
|
+
self.before_callbacks << block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureDefaultValues
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
def final_initialization_hook
|
8
|
+
instance_variables.grep(/^@with_.*/).each do |v|
|
9
|
+
v =~ /@with_(.*)/
|
10
|
+
var = $1
|
11
|
+
send((var + "=").intern, instance_variable_get(v.intern))
|
12
|
+
end
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# XXX probably not needed
|
17
|
+
# def template_entry_added_hook(obj)
|
18
|
+
# obj.instance_variables.grep(/^@with_.*/).each do |v|
|
19
|
+
# v =~ /@with_(.*)/
|
20
|
+
# var = $1
|
21
|
+
# obj.send((var + "=").intern, obj.instance_variable_get(v.intern))
|
22
|
+
# end
|
23
|
+
# super
|
24
|
+
# end
|
25
|
+
|
26
|
+
def method_missing_hook(meth, args)
|
27
|
+
m = meth.to_s
|
28
|
+
setter = (m[-1].chr == "=") ? true : false
|
29
|
+
m = m[0..-2] if setter
|
30
|
+
if setter and (field = self[m.intern])
|
31
|
+
if(field.kind_of? Structure)
|
32
|
+
if args[0].kind_of? field.class
|
33
|
+
args[0].each_field do |name, f|
|
34
|
+
field.value = f.value
|
35
|
+
end
|
36
|
+
else
|
37
|
+
if((deft = field.instance_variable_get :@default_field))
|
38
|
+
field.send(deft.to_s + "=", args[0])
|
39
|
+
return false
|
40
|
+
else
|
41
|
+
puts "WARNING: attempt to set structure field with no default_field declared"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
def default_field(f)
|
51
|
+
self.initializers << lambda do
|
52
|
+
@default_field = f
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureDetectFactory
|
3
|
+
def factory?
|
4
|
+
self.respond_to? :factory
|
5
|
+
end
|
6
|
+
|
7
|
+
def structure_field_def_hook(*a)
|
8
|
+
args = a[0]
|
9
|
+
opts = args[0].respond_to?(:has_key?) ? args[0] : args[1]
|
10
|
+
include StructureFactory if opts.try(:has_key?, :decides)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module StructureFactory
|
16
|
+
def self.included(klass)
|
17
|
+
klass.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def factory(str)
|
22
|
+
orig = str.clone
|
23
|
+
(tmp = self.new).capture(str)
|
24
|
+
tmp.each_field do |n, f|
|
25
|
+
if (m = f.try(:decides))
|
26
|
+
klass = m[f.value]
|
27
|
+
if klass
|
28
|
+
o = derive_search_module.const_get(klass.to_s.class_name).new
|
29
|
+
if o.class.factory?
|
30
|
+
o, orig = o.class.factory(orig)
|
31
|
+
else
|
32
|
+
orig = o.capture(orig)
|
33
|
+
end
|
34
|
+
return o, orig
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureFixupFieldNames
|
3
|
+
def structure_field_def_hook(*a)
|
4
|
+
args = a[0]
|
5
|
+
if args[0] and args[0].kind_of? Symbol
|
6
|
+
if args[1]
|
7
|
+
args[1][:name] = args[0]
|
8
|
+
else
|
9
|
+
args[1] = { :name => args[0] }
|
10
|
+
end
|
11
|
+
args.shift
|
12
|
+
end
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureInitializers
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval {
|
5
|
+
class_inheritable_array :initializers
|
6
|
+
write_inheritable_array :initializers, []
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def final_initialization_hook
|
11
|
+
self.initializers.each {|cb| self.instance_eval(&cb) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureRelateDeclaration
|
3
|
+
def class_method_missing_hook(meth, *args)
|
4
|
+
if meth.to_s =~ /^relate_(.*)/
|
5
|
+
relate($1.intern, *args)
|
6
|
+
return false
|
7
|
+
end
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def relate(attr, field, opts={})
|
12
|
+
opts[:through] ||= :value
|
13
|
+
raise "need a valid field to relate" if not field
|
14
|
+
raise "need :to argument" if not opts[:to]
|
15
|
+
|
16
|
+
self.initializers << lambda do
|
17
|
+
f = send(field)
|
18
|
+
|
19
|
+
case attr
|
20
|
+
when :value
|
21
|
+
f.value = opts[:through]
|
22
|
+
f.instance_eval { @from_field = opts[:to] }
|
23
|
+
when :size
|
24
|
+
f.instance_eval {
|
25
|
+
@in_size = {
|
26
|
+
:meth => opts[:through],
|
27
|
+
:from_field => opts[:to]
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureAllowFieldReplacement
|
3
|
+
def template_entry_added_hook(obj)
|
4
|
+
## -------------------------------------------
|
5
|
+
## a quick dance to allow fields to replace other
|
6
|
+
## fields
|
7
|
+
|
8
|
+
# check to see if it already exists, in which
|
9
|
+
# case we want to replace the previous definition
|
10
|
+
found = false
|
11
|
+
@value.each_with_index do |x,i|
|
12
|
+
if x.try(:name) == obj.try(:name)
|
13
|
+
found = i
|
14
|
+
break
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# it didn't exist, so add it to the structure
|
19
|
+
if found
|
20
|
+
# it did exist, so update the names index
|
21
|
+
# and the previous entry in the structure
|
22
|
+
|
23
|
+
self.class.structure_field_names[obj.try(:name)] = found
|
24
|
+
@value[found] = obj
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureSearchModules
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval {
|
5
|
+
class_inheritable_array :search_modules
|
6
|
+
write_inheritable_array :search_modules, []
|
7
|
+
}
|
8
|
+
|
9
|
+
klass.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def derive_search_module
|
14
|
+
if self.search_modules.empty?
|
15
|
+
return Ruckus
|
16
|
+
else
|
17
|
+
mod = Module.new
|
18
|
+
self.search_modules.each do |m|
|
19
|
+
mod.module_eval "include #{ m.to_s.class_name }"
|
20
|
+
end
|
21
|
+
mod.module_eval "include Ruckus"
|
22
|
+
return mod
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def search_module(*args)
|
27
|
+
args.each do |m|
|
28
|
+
self.search_modules << m
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureProxies
|
3
|
+
class ValueProxy
|
4
|
+
def initialize(target); @t = target; end
|
5
|
+
def method_missing(meth, *args)
|
6
|
+
if meth.to_s.ends_with? "="
|
7
|
+
@t.send(meth, *args)
|
8
|
+
else
|
9
|
+
@t.send(meth).send(:value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NodeProxy
|
15
|
+
def initialize(target); @t = target; end
|
16
|
+
def method_missing(meth, *args)
|
17
|
+
if meth.to_s.ends_with? "="
|
18
|
+
@t.send(meth, *args)
|
19
|
+
else
|
20
|
+
@t[meth]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def v; ValueProxy.new(self); end
|
26
|
+
def n; NodeProxy.new(self); end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Ruckus
|
4
|
+
module StructureValidateField
|
5
|
+
def structure_field_def_hook(*a)
|
6
|
+
args = a[0]
|
7
|
+
if [ "value", "name", "size" ].include?(nm = args[0][:name] && args[0][:name].to_s) or (nm and nm.starts_with? "relate_")
|
8
|
+
raise "can't have fields named #{ nm }, because we suck; rename the field"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# === A sort of half-baked DSL for defining packet fields.
|
2
|
+
# These are all classmethods of Structure.
|
3
|
+
module Ruckus
|
4
|
+
class Structure
|
5
|
+
# Any number, ie
|
6
|
+
# num :len, :width => 32, :endianness => :little
|
7
|
+
#
|
8
|
+
def self.num(name, opts)
|
9
|
+
add(Number, opts.merge(:name => name))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.char(*args); with_args(*args) {|name, opts| str opts.merge(:name => name, :size => 1)}; end
|
13
|
+
|
14
|
+
def self.decimal(*args); with_args(*args) {|name, opts| num name, opts.merge(:ascii => true, :radix => 10)}; end
|
15
|
+
def self.hex_number(*args); with_args(*args) {|name, opts| num name, opts.merge(:ascii => true, :radix => 16)}; end
|
16
|
+
|
17
|
+
def self.tag_bit(*args); with_args(*args) {|name, opts| num name, opts.merge(:width => 1, :tag => name)}; end
|
18
|
+
|
19
|
+
# A string (ie, multiple of 8 bits wide) containing all zeroes.
|
20
|
+
# You could also just use
|
21
|
+
# num :width => whatever, :value => 0
|
22
|
+
#
|
23
|
+
def self.zero_pad(*args)
|
24
|
+
with_args(*args) do |name, opts|
|
25
|
+
str opts.merge(:name => name, :padding => "\x00")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# A bounded string, takes its size from the preceding element
|
30
|
+
#
|
31
|
+
def self.bounded(*args)
|
32
|
+
with_args(*args) do |name, opts|
|
33
|
+
opts[:size] ||= :value
|
34
|
+
opts[:offset] ||= -1
|
35
|
+
str name, opts
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# A string.
|
40
|
+
def self.string(*args)
|
41
|
+
with_args(*args) do |name, opts|
|
42
|
+
str opts.merge(:name => name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# A Null byte
|
47
|
+
def self.mark(*args)
|
48
|
+
with_args(*args) do |name, opts|
|
49
|
+
null opts.merge(:name => name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# 4-byte IP address (IPv4)
|
54
|
+
def self.ipv4(*args)
|
55
|
+
with_args(*args) do |name, opts|
|
56
|
+
add(Ruckus::IP, opts.merge(:name => name))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.choose(name, tag=nil, &block)
|
61
|
+
add(Ruckus::Choice, :name => name, :block => block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.base_pad(name, tag=nil)
|
65
|
+
string name, :value => {:offset => :this, :block => lambda do |this|
|
66
|
+
r = this.root
|
67
|
+
r = r.find_tag_struct(tag) if tag
|
68
|
+
if ((k = this.rendered_offset - r.rendered_offset) % 4) != 0
|
69
|
+
pad = 4 - ((this.rendered_offset - r.rendered_offset) % 4)
|
70
|
+
else
|
71
|
+
pad = 0
|
72
|
+
end
|
73
|
+
"\x00" * pad
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.word_len(*args); with_args(*args) do |name, opts|
|
79
|
+
opts[:value] ||= :size
|
80
|
+
opts[:width] ||= 16
|
81
|
+
opts[:modifier] = lambda {|o, s| s/=2}
|
82
|
+
num name, opts
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.msg_len(*args); with_args(*args) do |name, opts|
|
87
|
+
opts[:value] = { :block => lambda do |n|
|
88
|
+
if not n.rendering
|
89
|
+
begin
|
90
|
+
n.rendering = true
|
91
|
+
n.parent_struct.size
|
92
|
+
ensure
|
93
|
+
n.rendering = false
|
94
|
+
end
|
95
|
+
else
|
96
|
+
4
|
97
|
+
end
|
98
|
+
end
|
99
|
+
}
|
100
|
+
opts[:width] ||= 32
|
101
|
+
num name, opts
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Padding < Structure
|
107
|
+
base_pad :pad
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module Ruckus
|
3
|
+
class TimeT < Number
|
4
|
+
def initialize(opts={})
|
5
|
+
opts[:width] ||= 32
|
6
|
+
super(opts)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_human(indent="")
|
10
|
+
"#{indent}#{@name} = #{@value.to_hex} (#{time})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def utc(*args)
|
14
|
+
@value=( Time.utc(*args) ).to_i
|
15
|
+
end
|
16
|
+
alias :gm :utc
|
17
|
+
|
18
|
+
def local(*args)
|
19
|
+
@value=( t=Time.local(*args)).to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
def time
|
23
|
+
Time.at(@value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Ruckus
|
2
|
+
|
3
|
+
# A vector of count elements of the same class, as in
|
4
|
+
# [elt0] [elt1] ... [eltN]
|
5
|
+
class Vector < Parsel
|
6
|
+
|
7
|
+
# Options include:
|
8
|
+
# * <tt>:class</tt> the class of each element
|
9
|
+
# * <tt>:e_opts</tt> opts to pass when creating each element (:name is always deleted)
|
10
|
+
# * <tt>:count</tt> the number of elements of class :class in the vector. Can be a reference to another field
|
11
|
+
# via :from_field
|
12
|
+
def initialize(opts={})
|
13
|
+
opts[:count] = 0x1fffffff if opts[:count] == :unlimited # grotesque hack XXX
|
14
|
+
opts[:e_opts] ||= {}
|
15
|
+
|
16
|
+
raise "need a class" if not opts[:class] and not opts[:classes_from]
|
17
|
+
|
18
|
+
if opts[:classes_from]
|
19
|
+
if not opts[:keys_from]
|
20
|
+
raise "need a module to pull keys from (protocol numbers, command IDs, whatever) as :keys_from"
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
@keys = (m = opts[:keys_from]).constants.
|
25
|
+
select {|x| m.const_get(x).kind_of? Numeric}.
|
26
|
+
map {|x| [m.const_get(x), x]}.
|
27
|
+
to_hash
|
28
|
+
rescue => e
|
29
|
+
raise "can't look up keys:\n #{ e }"
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
@classes = (m = opts[:classes_from]).constants.
|
34
|
+
select {|x| m.const_get(x).kind_of? Class}.
|
35
|
+
map do |x|
|
36
|
+
name = (klass = m.const_get(x)).
|
37
|
+
to_s.
|
38
|
+
underscore.
|
39
|
+
upcase
|
40
|
+
name = name[name.rindex(":")+1..-1]
|
41
|
+
[ name, klass ]
|
42
|
+
end.to_hash
|
43
|
+
rescue => e
|
44
|
+
raise "can't generate class dictionary:\n #{ e }"
|
45
|
+
end
|
46
|
+
|
47
|
+
raise "need a :key_field or :key_finder" if not opts[:key_field] and not opts[:key_finder]
|
48
|
+
end
|
49
|
+
|
50
|
+
super(opts)
|
51
|
+
@value = Blob.new
|
52
|
+
@value.parent = self
|
53
|
+
end
|
54
|
+
|
55
|
+
def capture(str)
|
56
|
+
count = resolve(@count)
|
57
|
+
if not count
|
58
|
+
raise "You need to provide a :count value to parse a vector; did you give it :size by mistake?"
|
59
|
+
end
|
60
|
+
|
61
|
+
count.downto(1) do |i|
|
62
|
+
break if not str or str.empty?
|
63
|
+
|
64
|
+
if @class
|
65
|
+
o = @class.new(@e_opts.merge(:parent => self))
|
66
|
+
str = o.capture(str)
|
67
|
+
@value << o
|
68
|
+
else
|
69
|
+
if @key_field
|
70
|
+
key = parent_struct.send(@key_field)
|
71
|
+
end
|
72
|
+
|
73
|
+
if @key_finder
|
74
|
+
key = @key_finder.call(str)
|
75
|
+
end
|
76
|
+
|
77
|
+
begin
|
78
|
+
o = @classes[@keys[key]].new(@e_opts.merge(:parent => self))
|
79
|
+
str = o.capture(str)
|
80
|
+
@value << o
|
81
|
+
rescue => e
|
82
|
+
raise "couldn't create an object from key:\n#{ e }"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@count = @value.count
|
87
|
+
str
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s(off=nil)
|
91
|
+
@rendered_offset = off || 0
|
92
|
+
(@value)? @value.to_s(off) : ""
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|