sundbp-extlib 0.9.14
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/.autotest +21 -0
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +47 -0
- data/README.rdoc +17 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/extlib.gemspec +146 -0
- data/lib/extlib.rb +50 -0
- data/lib/extlib/array.rb +36 -0
- data/lib/extlib/assertions.rb +8 -0
- data/lib/extlib/blank.rb +89 -0
- data/lib/extlib/boolean.rb +11 -0
- data/lib/extlib/byte_array.rb +6 -0
- data/lib/extlib/class.rb +177 -0
- data/lib/extlib/datetime.rb +29 -0
- data/lib/extlib/dictionary.rb +433 -0
- data/lib/extlib/hash.rb +442 -0
- data/lib/extlib/hook.rb +403 -0
- data/lib/extlib/inflection.rb +440 -0
- data/lib/extlib/lazy_array.rb +451 -0
- data/lib/extlib/lazy_module.rb +18 -0
- data/lib/extlib/logger.rb +198 -0
- data/lib/extlib/mash.rb +155 -0
- data/lib/extlib/module.rb +47 -0
- data/lib/extlib/nil.rb +5 -0
- data/lib/extlib/numeric.rb +5 -0
- data/lib/extlib/object.rb +175 -0
- data/lib/extlib/object_space.rb +13 -0
- data/lib/extlib/pathname.rb +20 -0
- data/lib/extlib/pooling.rb +235 -0
- data/lib/extlib/rubygems.rb +38 -0
- data/lib/extlib/simple_set.rb +66 -0
- data/lib/extlib/string.rb +176 -0
- data/lib/extlib/struct.rb +17 -0
- data/lib/extlib/symbol.rb +21 -0
- data/lib/extlib/time.rb +43 -0
- data/lib/extlib/virtual_file.rb +10 -0
- data/spec/array_spec.rb +39 -0
- data/spec/blank_spec.rb +85 -0
- data/spec/byte_array_spec.rb +7 -0
- data/spec/class_spec.rb +157 -0
- data/spec/datetime_spec.rb +22 -0
- data/spec/hash_spec.rb +537 -0
- data/spec/hook_spec.rb +1234 -0
- data/spec/inflection/plural_spec.rb +564 -0
- data/spec/inflection/singular_spec.rb +497 -0
- data/spec/inflection_extras_spec.rb +110 -0
- data/spec/lazy_array_spec.rb +1957 -0
- data/spec/lazy_module_spec.rb +38 -0
- data/spec/mash_spec.rb +311 -0
- data/spec/module_spec.rb +70 -0
- data/spec/object_space_spec.rb +9 -0
- data/spec/object_spec.rb +114 -0
- data/spec/pooling_spec.rb +511 -0
- data/spec/rcov.opts +6 -0
- data/spec/simple_set_spec.rb +57 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/string_spec.rb +221 -0
- data/spec/struct_spec.rb +12 -0
- data/spec/symbol_spec.rb +8 -0
- data/spec/time_spec.rb +29 -0
- data/spec/try_call_spec.rb +73 -0
- data/spec/try_dup_spec.rb +45 -0
- data/spec/virtual_file_spec.rb +21 -0
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +36 -0
- data/tasks/spec.rake +25 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +180 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class Struct
|
2
|
+
##
|
3
|
+
# Get a hash with names and values of all instance variables.
|
4
|
+
#
|
5
|
+
# class Foo < Struct.new(:name, :age, :gender); end
|
6
|
+
# f = Foo.new("Jill", 50, :female)
|
7
|
+
# f.attributes #=> {:name => "Jill", :age => 50, :gender => :female}
|
8
|
+
#
|
9
|
+
# @return [Hash] Hash of instance variables in receiver, keyed by ivar name
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def attributes
|
13
|
+
h = {}
|
14
|
+
each_pair { |k,v| h[k] = v }
|
15
|
+
h
|
16
|
+
end
|
17
|
+
end # class Struct
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Symbol
|
2
|
+
|
3
|
+
def try_dup
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
##
|
8
|
+
# Join with _o_ as a file path
|
9
|
+
#
|
10
|
+
# :merb/"core_ext" #=> "merb/core_ext"
|
11
|
+
# :merb / :core_ext / :string #=> "merb/core_ext/string"
|
12
|
+
#
|
13
|
+
# @param [#to_s] o The path component(s) to append.
|
14
|
+
#
|
15
|
+
# @return [String] The receiver (as path string), concatenated with _o_.
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def /(o)
|
19
|
+
File.join(self.to_s, o.to_s)
|
20
|
+
end
|
21
|
+
end
|
data/lib/extlib/time.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
class Time
|
4
|
+
|
5
|
+
##
|
6
|
+
# Convert to ISO 8601 representation
|
7
|
+
#
|
8
|
+
# Time.now.to_json #=> "\"2008-03-28T17:54:20-05:00\""
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
# ISO 8601 compatible representation of the Time object.
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def to_json(*)
|
15
|
+
self.xmlschema.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Return receiver (for DateTime/Time conversion protocol).
|
20
|
+
#
|
21
|
+
# Time.now.to_time #=> Wed Nov 19 20:08:28 -0800 2008
|
22
|
+
#
|
23
|
+
# @return [Time] Receiver
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
remove_method :to_time if instance_methods(false).any? { |m| m.to_sym == :to_time }
|
27
|
+
def to_time
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Convert to DateTime (for DateTime/Time conversion protocol).
|
33
|
+
#
|
34
|
+
# Time.now.to_datetime #=> #<DateTime: 106046956823/43200,-1/3,2299161>
|
35
|
+
#
|
36
|
+
# @return [DateTime] DateTime object representing the same moment as receiver
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
remove_method :to_datetime if instance_methods(false).any? { |m| m.to_sym == :to_datetime }
|
40
|
+
def to_datetime
|
41
|
+
DateTime.new(year, month, day, hour, min, sec, Rational(gmt_offset, 24 * 3600))
|
42
|
+
end
|
43
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
before :all do
|
5
|
+
@array = [ [ :a, [ 1 ] ], [ :b, [ 2 ] ], [ :c, [ 3 ] ] ].freeze
|
6
|
+
end
|
7
|
+
|
8
|
+
it { @array.should respond_to(:to_hash) }
|
9
|
+
|
10
|
+
describe '#to_hash' do
|
11
|
+
before :all do
|
12
|
+
@return = @array.to_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return a Hash' do
|
16
|
+
@return.should be_kind_of(Hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return expected value' do
|
20
|
+
@return.should == { :a => [ 1 ], :b => [ 2 ], :c => [ 3 ] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it { @array.should respond_to(:to_mash) }
|
25
|
+
|
26
|
+
describe '#to_mash' do
|
27
|
+
before :all do
|
28
|
+
@return = @array.to_mash
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should return a Mash' do
|
32
|
+
@return.should be_kind_of(Mash)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should return expected value' do
|
36
|
+
@return.should == { 'a' => [ 1 ], 'b' => [ 2 ], 'c' => [ 3 ] }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/blank_spec.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe Object do
|
4
|
+
it 'should provide blank?' do
|
5
|
+
Object.new.should respond_to(:blank?)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should be blank if it is nil' do
|
9
|
+
object = Object.new
|
10
|
+
class << object
|
11
|
+
def nil?; true end
|
12
|
+
end
|
13
|
+
object.should be_blank
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should be blank if it is empty' do
|
17
|
+
{}.should be_blank
|
18
|
+
[].should be_blank
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should not be blank if not nil or empty' do
|
22
|
+
Object.new.should_not be_blank
|
23
|
+
[nil].should_not be_blank
|
24
|
+
{ nil => 0 }.should_not be_blank
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Numeric do
|
29
|
+
it 'should provide blank?' do
|
30
|
+
1.should respond_to(:blank?)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should never be blank' do
|
34
|
+
1.should_not be_blank
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe NilClass do
|
39
|
+
it 'should provide blank?' do
|
40
|
+
nil.should respond_to(:blank?)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should always be blank' do
|
44
|
+
nil.should be_blank
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe TrueClass do
|
49
|
+
it 'should provide blank?' do
|
50
|
+
true.should respond_to(:blank?)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should never be blank' do
|
54
|
+
true.should_not be_blank
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe FalseClass do
|
59
|
+
it 'should provide blank?' do
|
60
|
+
false.should respond_to(:blank?)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should always be blank' do
|
64
|
+
false.should be_blank
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe String do
|
69
|
+
it 'should provide blank?' do
|
70
|
+
'string'.should respond_to(:blank?)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should be blank if empty' do
|
74
|
+
''.should be_blank
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should be blank if it only contains whitespace' do
|
78
|
+
' '.should be_blank
|
79
|
+
" \r \n \t ".should be_blank
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should not be blank if it contains non-whitespace' do
|
83
|
+
' a '.should_not be_blank
|
84
|
+
end
|
85
|
+
end
|
data/spec/class_spec.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
class Grandparent
|
4
|
+
end
|
5
|
+
|
6
|
+
class Parent < Grandparent
|
7
|
+
end
|
8
|
+
|
9
|
+
class Child < Parent
|
10
|
+
end
|
11
|
+
|
12
|
+
class Parent
|
13
|
+
end
|
14
|
+
|
15
|
+
class Grandparent
|
16
|
+
class_inheritable_accessor :last_name, :_attribute
|
17
|
+
|
18
|
+
self._attribute = 1900
|
19
|
+
end
|
20
|
+
|
21
|
+
class ClassWithInheritableSymbolAccessor
|
22
|
+
class_inheritable_accessor :symbol
|
23
|
+
self.symbol = :foo
|
24
|
+
end
|
25
|
+
|
26
|
+
class ClassInheritingSymbolAccessor < ClassWithInheritableSymbolAccessor
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Class, "#inheritable_accessor" do
|
30
|
+
|
31
|
+
after :each do
|
32
|
+
Grandparent.send(:remove_instance_variable, "@last_name") rescue nil
|
33
|
+
Parent.send(:remove_instance_variable, "@last_name") rescue nil
|
34
|
+
Child.send(:remove_instance_variable, "@last_name") rescue nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'inherits from parent unless overriden' do
|
38
|
+
Parent._attribute.should == 1900
|
39
|
+
Child._attribute.should == 1900
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'inherits from grandparent unless overriden' do
|
43
|
+
Child._attribute.should == 1900
|
44
|
+
end
|
45
|
+
|
46
|
+
it "inherits even if the accessor is made after the inheritance" do
|
47
|
+
Grandparent.last_name = "Merb"
|
48
|
+
Parent.last_name.should == "Merb"
|
49
|
+
Child.last_name.should == "Merb"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "supports ||= to change a child" do
|
53
|
+
Parent.last_name ||= "Merb"
|
54
|
+
Grandparent.last_name.should == nil
|
55
|
+
Parent.last_name.should == "Merb"
|
56
|
+
Child.last_name.should == "Merb"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "supports << to change a child when the parent is an Array" do
|
60
|
+
Grandparent.last_name = ["Merb"]
|
61
|
+
Parent.last_name << "Core"
|
62
|
+
Grandparent.last_name.should == ["Merb"]
|
63
|
+
Parent.last_name.should == ["Merb", "Core"]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "supports ! methods on an Array" do
|
67
|
+
Grandparent.last_name = %w(Merb Core)
|
68
|
+
Parent.last_name.reverse!
|
69
|
+
Grandparent.last_name.should == %w(Merb Core)
|
70
|
+
Parent.last_name.should == %w(Core Merb)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "support modifying a parent Hash" do
|
74
|
+
Grandparent.last_name = {"Merb" => "name"}
|
75
|
+
Parent.last_name["Core"] = "name"
|
76
|
+
Parent.last_name.should == {"Merb" => "name", "Core" => "name"}
|
77
|
+
Grandparent.last_name.should == {"Merb" => "name"}
|
78
|
+
end
|
79
|
+
|
80
|
+
it "supports hard-merging a parent Hash" do
|
81
|
+
Grandparent.last_name = {"Merb" => "name"}
|
82
|
+
Parent.last_name.merge!("Core" => "name")
|
83
|
+
Parent.last_name.should == {"Merb" => "name", "Core" => "name"}
|
84
|
+
Grandparent.last_name.should == {"Merb" => "name"}
|
85
|
+
end
|
86
|
+
|
87
|
+
it "supports changes to the parent even if the child has already been read" do
|
88
|
+
Child.last_name
|
89
|
+
Grandparent.last_name = "Merb"
|
90
|
+
Child.last_name.should == "Merb"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "handles nil being set midstream" do
|
94
|
+
Child.last_name
|
95
|
+
Parent.last_name = nil
|
96
|
+
Grandparent.last_name = "Merb"
|
97
|
+
Child.last_name.should == nil
|
98
|
+
end
|
99
|
+
|
100
|
+
it "handles false being used in Parent" do
|
101
|
+
Child.last_name
|
102
|
+
Parent.last_name = false
|
103
|
+
Grandparent.last_name = "Merb"
|
104
|
+
Child.last_name.should == false
|
105
|
+
end
|
106
|
+
|
107
|
+
it "handles the grandparent changing the value (as long as the child isn't read first)" do
|
108
|
+
Grandparent.last_name = "Merb"
|
109
|
+
Grandparent.last_name = "Core"
|
110
|
+
Child.last_name.should == "Core"
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
describe Class, "#inheritable_accessor (of type Symbol)" do
|
116
|
+
|
117
|
+
it "should not raise" do
|
118
|
+
lambda { ClassInheritingSymbolAccessor.symbol }.should_not raise_error(TypeError)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# The bug that prompted this estoric spec was found in
|
125
|
+
# the wild when using dm-is-versioned with c_i_w.
|
126
|
+
#
|
127
|
+
|
128
|
+
module Plugin
|
129
|
+
def self.included(base)
|
130
|
+
base.class_eval do
|
131
|
+
class_inheritable_writer :plugin_options
|
132
|
+
class_inheritable_reader :plugin_options
|
133
|
+
self.plugin_options = :foo
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Model
|
139
|
+
def self.new
|
140
|
+
model = Class.new
|
141
|
+
model.send(:include, Plugin)
|
142
|
+
model
|
143
|
+
end
|
144
|
+
|
145
|
+
include Plugin
|
146
|
+
self.const_set("Version", Model.new)
|
147
|
+
end
|
148
|
+
|
149
|
+
describe Class, "#inheritable_accessor" do
|
150
|
+
it "uses object_id for comparison" do
|
151
|
+
Model.methods.map { |m| m.to_sym }.should be_include(:plugin_options)
|
152
|
+
Model.plugin_options.should == :foo
|
153
|
+
|
154
|
+
Model::Version.methods.map { |m| m.to_sym }.should be_include(:plugin_options)
|
155
|
+
Model::Version.plugin_options.should == :foo
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe DateTime, "#to_time" do
|
5
|
+
before do
|
6
|
+
@expected = Time.now.to_s
|
7
|
+
@datetime = DateTime.parse(@expected)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return a copy of time" do
|
11
|
+
time = @datetime.to_time
|
12
|
+
time.class.should == Time
|
13
|
+
time.to_s.should == @expected
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Time, "#to_datetime" do
|
18
|
+
it "should return a copy of its self" do
|
19
|
+
datetime = DateTime.now
|
20
|
+
datetime.to_datetime.should == datetime
|
21
|
+
end
|
22
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,537 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require "date"
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
describe Hash, "environmentize_keys!" do
|
6
|
+
it "should transform keys to uppercase text" do
|
7
|
+
{ :test_1 => 'test', 'test_2' => 'test', 1 => 'test' }.environmentize_keys!.should ==
|
8
|
+
{ 'TEST_1' => 'test', 'TEST_2' => 'test', '1' => 'test' }
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should only transform one level of keys" do
|
12
|
+
{ :test_1 => { :test2 => 'test'} }.environmentize_keys!.should ==
|
13
|
+
{ 'TEST_1' => { :test2 => 'test'} }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe Hash, "only" do
|
19
|
+
before do
|
20
|
+
@hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE', 4 => nil }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return a hash with only the given key(s)" do
|
24
|
+
@hash.only(:not_in_there).should == {}
|
25
|
+
@hash.only(4).should == {4 => nil}
|
26
|
+
@hash.only(:one).should == { :one => 'ONE' }
|
27
|
+
@hash.only(:one, 3).should == { :one => 'ONE', 3 => 'THREE' }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
describe Hash, "except" do
|
33
|
+
before do
|
34
|
+
@hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return a hash without only the given key(s)" do
|
38
|
+
@hash.except(:one).should == { 'two' => 'TWO', 3 => 'THREE' }
|
39
|
+
@hash.except(:one, 3).should == { 'two' => 'TWO' }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
describe Hash, "to_xml_attributes" do
|
45
|
+
before do
|
46
|
+
@hash = { :one => "ONE", "two" => "TWO" }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should turn the hash into xml attributes" do
|
50
|
+
attrs = @hash.to_xml_attributes
|
51
|
+
attrs.should match(/one="ONE"/m)
|
52
|
+
attrs.should match(/two="TWO"/m)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should preserve _ in hash keys' do
|
56
|
+
attrs = {
|
57
|
+
:some_long_attribute => "with short value",
|
58
|
+
:crash => :burn,
|
59
|
+
:merb => "uses extlib"
|
60
|
+
}.to_xml_attributes
|
61
|
+
|
62
|
+
attrs.should =~ /some_long_attribute="with short value"/
|
63
|
+
attrs.should =~ /merb="uses extlib"/
|
64
|
+
attrs.should =~ /crash="burn"/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
describe Hash, "from_xml" do
|
70
|
+
it "should transform a simple tag with content" do
|
71
|
+
xml = "<tag>This is the contents</tag>"
|
72
|
+
Hash.from_xml(xml).should == { 'tag' => 'This is the contents' }
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should work with cdata tags" do
|
76
|
+
xml = <<-END
|
77
|
+
<tag>
|
78
|
+
<![CDATA[
|
79
|
+
text inside cdata
|
80
|
+
]]>
|
81
|
+
</tag>
|
82
|
+
END
|
83
|
+
Hash.from_xml(xml)["tag"].strip.should == "text inside cdata"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should transform a simple tag with attributes" do
|
87
|
+
xml = "<tag attr1='1' attr2='2'></tag>"
|
88
|
+
hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
|
89
|
+
Hash.from_xml(xml).should == hash
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should transform repeating siblings into an array" do
|
93
|
+
xml =<<-XML
|
94
|
+
<opt>
|
95
|
+
<user login="grep" fullname="Gary R Epstein" />
|
96
|
+
<user login="stty" fullname="Simon T Tyson" />
|
97
|
+
</opt>
|
98
|
+
XML
|
99
|
+
|
100
|
+
Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Array)
|
101
|
+
|
102
|
+
hash = {
|
103
|
+
'opt' => {
|
104
|
+
'user' => [{
|
105
|
+
'login' => 'grep',
|
106
|
+
'fullname' => 'Gary R Epstein'
|
107
|
+
},{
|
108
|
+
'login' => 'stty',
|
109
|
+
'fullname' => 'Simon T Tyson'
|
110
|
+
}]
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
Hash.from_xml(xml).should == hash
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should not transform non-repeating siblings into an array" do
|
118
|
+
xml =<<-XML
|
119
|
+
<opt>
|
120
|
+
<user login="grep" fullname="Gary R Epstein" />
|
121
|
+
</opt>
|
122
|
+
XML
|
123
|
+
|
124
|
+
Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Hash)
|
125
|
+
|
126
|
+
hash = {
|
127
|
+
'opt' => {
|
128
|
+
'user' => {
|
129
|
+
'login' => 'grep',
|
130
|
+
'fullname' => 'Gary R Epstein'
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
Hash.from_xml(xml).should == hash
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should typecast an integer" do
|
139
|
+
xml = "<tag type='integer'>10</tag>"
|
140
|
+
Hash.from_xml(xml)['tag'].should == 10
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should typecast a true boolean" do
|
144
|
+
xml = "<tag type='boolean'>true</tag>"
|
145
|
+
Hash.from_xml(xml)['tag'].should be_true
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should typecast a false boolean" do
|
149
|
+
["false"].each do |w|
|
150
|
+
Hash.from_xml("<tag type='boolean'>#{w}</tag>")['tag'].should be_false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should typecast a datetime" do
|
155
|
+
xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
|
156
|
+
Hash.from_xml(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should typecast a date" do
|
160
|
+
xml = "<tag type='date'>2007-12-31</tag>"
|
161
|
+
Hash.from_xml(xml)['tag'].should == Date.parse('2007-12-31')
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should unescape html entities" do
|
165
|
+
values = {
|
166
|
+
"<" => "<",
|
167
|
+
">" => ">",
|
168
|
+
'"' => """,
|
169
|
+
"'" => "'",
|
170
|
+
"&" => "&"
|
171
|
+
}
|
172
|
+
values.each do |k,v|
|
173
|
+
xml = "<tag>Some content #{v}</tag>"
|
174
|
+
Hash.from_xml(xml)['tag'].should match(Regexp.new(k))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should undasherize keys as tags" do
|
179
|
+
xml = "<tag-1>Stuff</tag-1>"
|
180
|
+
Hash.from_xml(xml).should have_key('tag_1')
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should undasherize keys as attributes" do
|
184
|
+
xml = "<tag1 attr-1='1'></tag1>"
|
185
|
+
Hash.from_xml(xml)['tag1'].should have_key('attr_1')
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should undasherize keys as tags and attributes" do
|
189
|
+
xml = "<tag-1 attr-1='1'></tag-1>"
|
190
|
+
Hash.from_xml(xml).should have_key('tag_1' )
|
191
|
+
Hash.from_xml(xml)['tag_1'].should have_key('attr_1')
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should render nested content correctly" do
|
195
|
+
xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
|
196
|
+
Hash.from_xml(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should render nested content with split text nodes correctly" do
|
200
|
+
xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
|
201
|
+
Hash.from_xml(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should ignore attributes when a child is a text node" do
|
205
|
+
xml = "<root attr1='1'>Stuff</root>"
|
206
|
+
Hash.from_xml(xml).should == { "root" => "Stuff" }
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should ignore attributes when any child is a text node" do
|
210
|
+
xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
|
211
|
+
Hash.from_xml(xml).should == { "root" => "Stuff <em>in italics</em>" }
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should correctly transform multiple children" do
|
215
|
+
xml = <<-XML
|
216
|
+
<user gender='m'>
|
217
|
+
<age type='integer'>35</age>
|
218
|
+
<name>Home Simpson</name>
|
219
|
+
<dob type='date'>1988-01-01</dob>
|
220
|
+
<joined-at type='datetime'>2000-04-28 23:01</joined-at>
|
221
|
+
<is-cool type='boolean'>true</is-cool>
|
222
|
+
</user>
|
223
|
+
XML
|
224
|
+
|
225
|
+
hash = {
|
226
|
+
"user" => {
|
227
|
+
"gender" => "m",
|
228
|
+
"age" => 35,
|
229
|
+
"name" => "Home Simpson",
|
230
|
+
"dob" => Date.parse('1988-01-01'),
|
231
|
+
"joined_at" => Time.parse("2000-04-28 23:01"),
|
232
|
+
"is_cool" => true
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
Hash.from_xml(xml).should == hash
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should properly handle nil values (ActiveSupport Compatible)" do
|
240
|
+
topic_xml = <<-EOT
|
241
|
+
<topic>
|
242
|
+
<title></title>
|
243
|
+
<id type="integer"></id>
|
244
|
+
<approved type="boolean"></approved>
|
245
|
+
<written-on type="date"></written-on>
|
246
|
+
<viewed-at type="datetime"></viewed-at>
|
247
|
+
<content type="yaml"></content>
|
248
|
+
<parent-id></parent-id>
|
249
|
+
</topic>
|
250
|
+
EOT
|
251
|
+
|
252
|
+
expected_topic_hash = {
|
253
|
+
'title' => nil,
|
254
|
+
'id' => nil,
|
255
|
+
'approved' => nil,
|
256
|
+
'written_on' => nil,
|
257
|
+
'viewed_at' => nil,
|
258
|
+
'content' => nil,
|
259
|
+
'parent_id' => nil
|
260
|
+
}
|
261
|
+
Hash.from_xml(topic_xml)["topic"].should == expected_topic_hash
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should handle a single record from xml (ActiveSupport Compatible)" do
|
265
|
+
topic_xml = <<-EOT
|
266
|
+
<topic>
|
267
|
+
<title>The First Topic</title>
|
268
|
+
<author-name>David</author-name>
|
269
|
+
<id type="integer">1</id>
|
270
|
+
<approved type="boolean"> true </approved>
|
271
|
+
<replies-count type="integer">0</replies-count>
|
272
|
+
<replies-close-in type="integer">2592000000</replies-close-in>
|
273
|
+
<written-on type="date">2003-07-16</written-on>
|
274
|
+
<viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
|
275
|
+
<content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true\n</content>
|
276
|
+
<author-email-address>david@loudthinking.com</author-email-address>
|
277
|
+
<parent-id></parent-id>
|
278
|
+
<ad-revenue type="decimal">1.5</ad-revenue>
|
279
|
+
<optimum-viewing-angle type="float">135</optimum-viewing-angle>
|
280
|
+
<resident type="symbol">yes</resident>
|
281
|
+
</topic>
|
282
|
+
EOT
|
283
|
+
|
284
|
+
expected_topic_hash = {
|
285
|
+
'title' => "The First Topic",
|
286
|
+
'author_name' => "David",
|
287
|
+
'id' => 1,
|
288
|
+
'approved' => true,
|
289
|
+
'replies_count' => 0,
|
290
|
+
'replies_close_in' => 2592000000,
|
291
|
+
'written_on' => Date.new(2003, 7, 16),
|
292
|
+
'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
|
293
|
+
# Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify
|
294
|
+
# The line in ActiveSupport is
|
295
|
+
# 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
|
296
|
+
'content' => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
|
297
|
+
'author_email_address' => "david@loudthinking.com",
|
298
|
+
'parent_id' => nil,
|
299
|
+
'ad_revenue' => BigDecimal("1.50"),
|
300
|
+
'optimum_viewing_angle' => 135.0,
|
301
|
+
'resident' => :yes
|
302
|
+
}
|
303
|
+
|
304
|
+
Hash.from_xml(topic_xml)["topic"].each do |k,v|
|
305
|
+
v.should == expected_topic_hash[k]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should handle multiple records (ActiveSupport Compatible)" do
|
310
|
+
topics_xml = <<-EOT
|
311
|
+
<topics type="array">
|
312
|
+
<topic>
|
313
|
+
<title>The First Topic</title>
|
314
|
+
<author-name>David</author-name>
|
315
|
+
<id type="integer">1</id>
|
316
|
+
<approved type="boolean">false</approved>
|
317
|
+
<replies-count type="integer">0</replies-count>
|
318
|
+
<replies-close-in type="integer">2592000000</replies-close-in>
|
319
|
+
<written-on type="date">2003-07-16</written-on>
|
320
|
+
<viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
|
321
|
+
<content>Have a nice day</content>
|
322
|
+
<author-email-address>david@loudthinking.com</author-email-address>
|
323
|
+
<parent-id nil="true"></parent-id>
|
324
|
+
</topic>
|
325
|
+
<topic>
|
326
|
+
<title>The Second Topic</title>
|
327
|
+
<author-name>Jason</author-name>
|
328
|
+
<id type="integer">1</id>
|
329
|
+
<approved type="boolean">false</approved>
|
330
|
+
<replies-count type="integer">0</replies-count>
|
331
|
+
<replies-close-in type="integer">2592000000</replies-close-in>
|
332
|
+
<written-on type="date">2003-07-16</written-on>
|
333
|
+
<viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
|
334
|
+
<content>Have a nice day</content>
|
335
|
+
<author-email-address>david@loudthinking.com</author-email-address>
|
336
|
+
<parent-id></parent-id>
|
337
|
+
</topic>
|
338
|
+
</topics>
|
339
|
+
EOT
|
340
|
+
|
341
|
+
expected_topic_hash = {
|
342
|
+
'title' => "The First Topic",
|
343
|
+
'author_name' => "David",
|
344
|
+
'id' => 1,
|
345
|
+
'approved' => false,
|
346
|
+
'replies_count' => 0,
|
347
|
+
'replies_close_in' => 2592000000,
|
348
|
+
'written_on' => Date.new(2003, 7, 16),
|
349
|
+
'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
|
350
|
+
'content' => "Have a nice day",
|
351
|
+
'author_email_address' => "david@loudthinking.com",
|
352
|
+
'parent_id' => nil
|
353
|
+
}
|
354
|
+
# puts Hash.from_xml(topics_xml)['topics'].first.inspect
|
355
|
+
Hash.from_xml(topics_xml)["topics"].first.each do |k,v|
|
356
|
+
v.should == expected_topic_hash[k]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do
|
361
|
+
topic_xml = <<-EOT
|
362
|
+
<rsp stat="ok">
|
363
|
+
<photos page="1" pages="1" perpage="100" total="16">
|
364
|
+
<photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
|
365
|
+
</photos>
|
366
|
+
</rsp>
|
367
|
+
EOT
|
368
|
+
|
369
|
+
expected_topic_hash = {
|
370
|
+
'id' => "175756086",
|
371
|
+
'owner' => "55569174@N00",
|
372
|
+
'secret' => "0279bf37a1",
|
373
|
+
'server' => "76",
|
374
|
+
'title' => "Colored Pencil PhotoBooth Fun",
|
375
|
+
'ispublic' => "1",
|
376
|
+
'isfriend' => "0",
|
377
|
+
'isfamily' => "0",
|
378
|
+
}
|
379
|
+
Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"].each do |k,v|
|
380
|
+
v.should == expected_topic_hash[k]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should handle an emtpy array (ActiveSupport Compatible)" do
|
385
|
+
blog_xml = <<-XML
|
386
|
+
<blog>
|
387
|
+
<posts type="array"></posts>
|
388
|
+
</blog>
|
389
|
+
XML
|
390
|
+
expected_blog_hash = {"blog" => {"posts" => []}}
|
391
|
+
Hash.from_xml(blog_xml).should == expected_blog_hash
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do
|
395
|
+
blog_xml = <<-XML
|
396
|
+
<blog>
|
397
|
+
<posts type="array">
|
398
|
+
</posts>
|
399
|
+
</blog>
|
400
|
+
XML
|
401
|
+
expected_blog_hash = {"blog" => {"posts" => []}}
|
402
|
+
Hash.from_xml(blog_xml).should == expected_blog_hash
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should handle array with one entry from_xml (ActiveSupport Compatible)" do
|
406
|
+
blog_xml = <<-XML
|
407
|
+
<blog>
|
408
|
+
<posts type="array">
|
409
|
+
<post>a post</post>
|
410
|
+
</posts>
|
411
|
+
</blog>
|
412
|
+
XML
|
413
|
+
expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
|
414
|
+
Hash.from_xml(blog_xml).should == expected_blog_hash
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do
|
418
|
+
blog_xml = <<-XML
|
419
|
+
<blog>
|
420
|
+
<posts type="array">
|
421
|
+
<post>a post</post>
|
422
|
+
<post>another post</post>
|
423
|
+
</posts>
|
424
|
+
</blog>
|
425
|
+
XML
|
426
|
+
expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
|
427
|
+
Hash.from_xml(blog_xml).should == expected_blog_hash
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should handle file types (ActiveSupport Compatible)" do
|
431
|
+
blog_xml = <<-XML
|
432
|
+
<blog>
|
433
|
+
<logo type="file" name="logo.png" content_type="image/png">
|
434
|
+
</logo>
|
435
|
+
</blog>
|
436
|
+
XML
|
437
|
+
hash = Hash.from_xml(blog_xml)
|
438
|
+
hash.should have_key('blog')
|
439
|
+
hash['blog'].should have_key('logo')
|
440
|
+
|
441
|
+
file = hash['blog']['logo']
|
442
|
+
file.original_filename.should == 'logo.png'
|
443
|
+
file.content_type.should == 'image/png'
|
444
|
+
end
|
445
|
+
|
446
|
+
it "should handle file from xml with defaults (ActiveSupport Compatible)" do
|
447
|
+
blog_xml = <<-XML
|
448
|
+
<blog>
|
449
|
+
<logo type="file">
|
450
|
+
</logo>
|
451
|
+
</blog>
|
452
|
+
XML
|
453
|
+
file = Hash.from_xml(blog_xml)['blog']['logo']
|
454
|
+
file.original_filename.should == 'untitled'
|
455
|
+
file.content_type.should == 'application/octet-stream'
|
456
|
+
end
|
457
|
+
|
458
|
+
it "should handle xsd like types from xml (ActiveSupport Compatible)" do
|
459
|
+
bacon_xml = <<-EOT
|
460
|
+
<bacon>
|
461
|
+
<weight type="double">0.5</weight>
|
462
|
+
<price type="decimal">12.50</price>
|
463
|
+
<chunky type="boolean"> 1 </chunky>
|
464
|
+
<expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
|
465
|
+
<notes type="string"></notes>
|
466
|
+
<illustration type="base64Binary">YmFiZS5wbmc=</illustration>
|
467
|
+
</bacon>
|
468
|
+
EOT
|
469
|
+
|
470
|
+
expected_bacon_hash = {
|
471
|
+
'weight' => 0.5,
|
472
|
+
'chunky' => true,
|
473
|
+
'price' => BigDecimal("12.50"),
|
474
|
+
'expires_at' => Time.utc(2007,12,25,12,34,56),
|
475
|
+
'notes' => "",
|
476
|
+
'illustration' => "babe.png"
|
477
|
+
}
|
478
|
+
|
479
|
+
Hash.from_xml(bacon_xml)["bacon"].should == expected_bacon_hash
|
480
|
+
end
|
481
|
+
|
482
|
+
it "should let type trickle through when unknown (ActiveSupport Compatible)" do
|
483
|
+
product_xml = <<-EOT
|
484
|
+
<product>
|
485
|
+
<weight type="double">0.5</weight>
|
486
|
+
<image type="ProductImage"><filename>image.gif</filename></image>
|
487
|
+
|
488
|
+
</product>
|
489
|
+
EOT
|
490
|
+
|
491
|
+
expected_product_hash = {
|
492
|
+
'weight' => 0.5,
|
493
|
+
'image' => {'type' => 'ProductImage', 'filename' => 'image.gif' },
|
494
|
+
}
|
495
|
+
|
496
|
+
Hash.from_xml(product_xml)["product"].should == expected_product_hash
|
497
|
+
end
|
498
|
+
|
499
|
+
it "should handle unescaping from xml (ActiveResource Compatible)" do
|
500
|
+
xml_string = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>'
|
501
|
+
expected_hash = {
|
502
|
+
'bare_string' => 'First & Last Name',
|
503
|
+
'pre_escaped_string' => 'First & Last Name'
|
504
|
+
}
|
505
|
+
|
506
|
+
Hash.from_xml(xml_string)['person'].should == expected_hash
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
describe Hash, 'to_params' do
|
511
|
+
{
|
512
|
+
{ "foo" => "bar", "baz" => "bat" } => "foo=bar&baz=bat",
|
513
|
+
{ "foo" => [ "bar", "baz" ] } => "foo[]=bar&foo[]=baz",
|
514
|
+
{ "foo" => [ {"bar" => "1"}, {"bar" => 2} ] } => "foo[][bar]=1&foo[][bar]=2",
|
515
|
+
{ "foo" => { "bar" => [ {"baz" => 1}, {"baz" => "2"} ] } } => "foo[bar][][baz]=1&foo[bar][][baz]=2",
|
516
|
+
{ "foo" => {"1" => "bar", "2" => "baz"} } => "foo[1]=bar&foo[2]=baz"
|
517
|
+
}.each do |hash, params|
|
518
|
+
it "should covert hash: #{hash.inspect} to params: #{params.inspect}" do
|
519
|
+
hash.to_params.split('&').sort.should == params.split('&').sort
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
it 'should not leave a trailing &' do
|
524
|
+
{ :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }.to_params.should_not match(/&$/)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
describe Hash, 'to_mash' do
|
529
|
+
before :each do
|
530
|
+
@hash = Hash.new(10)
|
531
|
+
end
|
532
|
+
|
533
|
+
it "copies default Hash value to Mash" do
|
534
|
+
@mash = @hash.to_mash
|
535
|
+
@mash[:merb].should == 10
|
536
|
+
end
|
537
|
+
end
|