thorero 0.9.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.
Files changed (47) hide show
  1. data/History.txt +1 -0
  2. data/LICENSE +20 -0
  3. data/Manifest +45 -0
  4. data/Manifest.txt +29 -0
  5. data/README.txt +3 -0
  6. data/Rakefile +180 -0
  7. data/lib/extlib.rb +32 -0
  8. data/lib/extlib/assertions.rb +8 -0
  9. data/lib/extlib/blank.rb +42 -0
  10. data/lib/extlib/class.rb +175 -0
  11. data/lib/extlib/hash.rb +410 -0
  12. data/lib/extlib/hook.rb +366 -0
  13. data/lib/extlib/inflection.rb +141 -0
  14. data/lib/extlib/lazy_array.rb +106 -0
  15. data/lib/extlib/logger.rb +202 -0
  16. data/lib/extlib/mash.rb +143 -0
  17. data/lib/extlib/module.rb +37 -0
  18. data/lib/extlib/object.rb +165 -0
  19. data/lib/extlib/object_space.rb +13 -0
  20. data/lib/extlib/pathname.rb +5 -0
  21. data/lib/extlib/pooling.rb +233 -0
  22. data/lib/extlib/rubygems.rb +38 -0
  23. data/lib/extlib/simple_set.rb +39 -0
  24. data/lib/extlib/string.rb +132 -0
  25. data/lib/extlib/struct.rb +8 -0
  26. data/lib/extlib/tasks/release.rb +11 -0
  27. data/lib/extlib/time.rb +12 -0
  28. data/lib/extlib/version.rb +3 -0
  29. data/lib/extlib/virtual_file.rb +10 -0
  30. data/spec/blank_spec.rb +85 -0
  31. data/spec/hash_spec.rb +524 -0
  32. data/spec/hook_spec.rb +1198 -0
  33. data/spec/inflection_spec.rb +50 -0
  34. data/spec/lazy_array_spec.rb +896 -0
  35. data/spec/mash_spec.rb +244 -0
  36. data/spec/module_spec.rb +58 -0
  37. data/spec/object_space_spec.rb +9 -0
  38. data/spec/object_spec.rb +98 -0
  39. data/spec/pooling_spec.rb +486 -0
  40. data/spec/simple_set_spec.rb +26 -0
  41. data/spec/spec_helper.rb +8 -0
  42. data/spec/string_spec.rb +200 -0
  43. data/spec/struct_spec.rb +12 -0
  44. data/spec/time_spec.rb +16 -0
  45. data/spec/virtual_file_spec.rb +21 -0
  46. data/thorero.gemspec +147 -0
  47. metadata +146 -0
@@ -0,0 +1,38 @@
1
+ # this is a temporary workaround until rubygems Does the Right thing here
2
+ require 'rubygems'
3
+ module Gem
4
+ class SourceIndex
5
+
6
+ # This is resolved in 1.1
7
+ if Version.new(RubyGemsVersion) < Version.new("1.1")
8
+
9
+ # Overwrite this so that a gem of the same name and version won't push one
10
+ # from the gems directory out entirely.
11
+ #
12
+ # @param gem_spec<Gem::Specification> The specification of the gem to add.
13
+ def add_spec(gem_spec)
14
+ unless gem_spec.instance_variable_get("@loaded_from") &&
15
+ @gems[gem_spec.full_name].is_a?(Gem::Specification) &&
16
+ @gems[gem_spec.full_name].installation_path ==
17
+ File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems")
18
+
19
+ @gems[gem_spec.full_name] = gem_spec
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ class Specification
28
+
29
+ # Overwrite this so that gems in the gems directory get preferred over gems
30
+ # from any other location. If there are two gems of different versions in
31
+ # the gems directory, the later one will load as usual.
32
+ #
33
+ # @return <Array[Array]> The object used for sorting gem specs.
34
+ def sort_obj
35
+ [@name, installation_path == File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems") ? 1 : -1, @version.to_ints, @new_platform == Gem::Platform::RUBY ? -1 : 1]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module Extlib
2
+ # Simple set implementation
3
+ # on top of Hash with merging support.
4
+ #
5
+ # In particular this is used to store
6
+ # a set of callable actions of controller.
7
+ class SimpleSet < Hash
8
+
9
+ # @param arr<Array> Initial set values.
10
+ #
11
+ # @return <Array> The array the Set was initialized with
12
+ def initialize(arr = [])
13
+ arr.each {|x| self[x] = true}
14
+ end
15
+
16
+ # @param value<Object> Value to add to set.
17
+ #
18
+ # @return <TrueClass>
19
+ def <<(value)
20
+ self[value] = true
21
+ end
22
+
23
+ # @param arr<Array> Values to merge with set.
24
+ #
25
+ # @return <SimpleSet> The set after the Array was merged in.
26
+ def merge(arr)
27
+ super(arr.inject({}) {|s,x| s[x] = true; s })
28
+ end
29
+
30
+ # @return <String> A human readable version of the set.
31
+ def inspect
32
+ "#<SimpleSet: {#{keys.map {|x| x.inspect}.join(", ")}}>"
33
+ end
34
+
35
+ # def to_a
36
+ alias_method :to_a, :keys
37
+
38
+ end # SimpleSet
39
+ end # Merb
@@ -0,0 +1,132 @@
1
+ require "pathname"
2
+
3
+ class String
4
+ ##
5
+ # @return <String> The string with all regexp special characters escaped.
6
+ #
7
+ # @example
8
+ # "*?{}.".escape_regexp #=> "\\*\\?\\{\\}\\."
9
+ def escape_regexp
10
+ Regexp.escape self
11
+ end
12
+
13
+ ##
14
+ # @return String The string with all regexp special characters unescaped.
15
+ #
16
+ # @example
17
+ # "\\*\\?\\{\\}\\.".unescape_regexp #=> "*?{}."
18
+ def unescape_regexp
19
+ self.gsub(/\\([\.\?\|\(\)\[\]\{\}\^\$\*\+\-])/, '\1')
20
+ end
21
+
22
+ ##
23
+ # @return <String> The string converted to snake case.
24
+ #
25
+ # @example
26
+ # "FooBar".snake_case #=> "foo_bar"
27
+ # @example
28
+ # "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
29
+ # @example
30
+ # "CNN".snake_case #=> "cnn"
31
+ def snake_case
32
+ return self.downcase if self =~ /^[A-Z]+$/
33
+ self.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
34
+ return $+.downcase
35
+ end
36
+
37
+ ##
38
+ # @return <String> The string converted to camel case.
39
+ #
40
+ # @example
41
+ # "foo_bar".camel_case #=> "FooBar"
42
+ def camel_case
43
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
44
+ split('_').map{|e| e.capitalize}.join
45
+ end
46
+
47
+ ##
48
+ # @return <String> The path string converted to a constant name.
49
+ #
50
+ # @example
51
+ # "merb/core_ext/string".to_const_string #=> "Merb::CoreExt::String"
52
+ def to_const_string
53
+ gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
54
+ end
55
+
56
+ ##
57
+ # @return <String>
58
+ # The path that is associated with the constantized string, assuming a
59
+ # conventional structure.
60
+ #
61
+ # @example
62
+ # "FooBar::Baz".to_const_path # => "foo_bar/baz"
63
+ def to_const_path
64
+ snake_case.gsub(/::/, "/")
65
+ end
66
+
67
+ ##
68
+ # @param o<String> The path component to join with the string.
69
+ #
70
+ # @return <String> The original path concatenated with o.
71
+ #
72
+ # @example
73
+ # "merb"/"core_ext" #=> "merb/core_ext"
74
+ def /(o)
75
+ File.join(self, o.to_s)
76
+ end
77
+
78
+ ##
79
+ # @param other<String> Base path to calculate against
80
+ #
81
+ # @return <String> Relative path from between the two
82
+ #
83
+ # @example
84
+ # "/opt/local/lib".relative_path_from("/opt/local/lib/ruby/site_ruby") # => "../.."
85
+ def relative_path_from(other)
86
+ Pathname.new(self).relative_path_from(Pathname.new(other)).to_s
87
+ end
88
+
89
+ # Overwrite this method to provide your own translations.
90
+ def self.translate(value)
91
+ translations[value] || value
92
+ end
93
+
94
+ def self.translations
95
+ @translations ||= {}
96
+ end
97
+
98
+ # Matches any whitespace (including newline) and replaces with a single space
99
+ #
100
+ # @example
101
+ # <<QUERY.compress_lines
102
+ # SELECT name
103
+ # FROM users
104
+ # QUERY
105
+ # => "SELECT name FROM users"
106
+ def compress_lines(spaced = true)
107
+ split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
108
+ end
109
+
110
+ # Useful for heredocs - removes whitespace margin.
111
+ def margin(indicator = nil)
112
+ lines = self.dup.split($/)
113
+
114
+ min_margin = 0
115
+ lines.each do |line|
116
+ if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
117
+ min_margin = $1.size
118
+ end
119
+ end
120
+ lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
121
+ end
122
+
123
+ # Formats String for easy translation. Replaces an arbitrary number of
124
+ # values using numeric identifier replacement.
125
+ #
126
+ # @example
127
+ # "%s %s %s" % %w(one two three) #=> "one two three"
128
+ # "%3$s %2$s %1$s" % %w(one two three) #=> "three two one"
129
+ def t(*values)
130
+ self.class::translate(self) % values
131
+ end
132
+ end # class String
@@ -0,0 +1,8 @@
1
+ class Struct
2
+ # Returns a hash containing the names and values for all instance variables in the Struct.
3
+ def attributes
4
+ h = {}
5
+ each_pair { |k,v| h[k] = v }
6
+ h
7
+ end
8
+ end # class Struct
@@ -0,0 +1,11 @@
1
+ desc "Publish the release files to RubyForge."
2
+ task :release => [ :package ] do
3
+ require 'rubyforge'
4
+ require 'rake/contrib/rubyforgepublisher'
5
+
6
+ packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
7
+
8
+ rubyforge = RubyForge.new
9
+ rubyforge.login
10
+ rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
11
+ end
@@ -0,0 +1,12 @@
1
+ class Time
2
+
3
+ # @return <String>
4
+ # ISO 8601 compatible rendering of the Time object's properties.
5
+ #
6
+ # @example
7
+ # Time.now.to_json # => "\"2008-03-28T17:54:20-05:00\""
8
+ def to_json
9
+ self.xmlschema.to_json
10
+ end
11
+
12
+ end
@@ -0,0 +1,3 @@
1
+ module Extlib
2
+ VERSION = "0.9.4"
3
+ end
@@ -0,0 +1,10 @@
1
+ require "stringio"
2
+
3
+ # To use as a parameter to Merb::Template.inline_template
4
+ class VirtualFile < StringIO
5
+ attr_accessor :path
6
+ def initialize(string, path)
7
+ super(string)
8
+ @path = path
9
+ end
10
+ end
@@ -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/hash_spec.rb ADDED
@@ -0,0 +1,524 @@
1
+ require 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
+ end
55
+
56
+
57
+ describe Hash, "from_xml" do
58
+ it "should transform a simple tag with content" do
59
+ xml = "<tag>This is the contents</tag>"
60
+ Hash.from_xml(xml).should == { 'tag' => 'This is the contents' }
61
+ end
62
+
63
+ it "should work with cdata tags" do
64
+ xml = <<-END
65
+ <tag>
66
+ <![CDATA[
67
+ text inside cdata
68
+ ]]>
69
+ </tag>
70
+ END
71
+ Hash.from_xml(xml)["tag"].strip.should == "text inside cdata"
72
+ end
73
+
74
+ it "should transform a simple tag with attributes" do
75
+ xml = "<tag attr1='1' attr2='2'></tag>"
76
+ hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
77
+ Hash.from_xml(xml).should == hash
78
+ end
79
+
80
+ it "should transform repeating siblings into an array" do
81
+ xml =<<-XML
82
+ <opt>
83
+ <user login="grep" fullname="Gary R Epstein" />
84
+ <user login="stty" fullname="Simon T Tyson" />
85
+ </opt>
86
+ XML
87
+
88
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Array)
89
+
90
+ hash = {
91
+ 'opt' => {
92
+ 'user' => [{
93
+ 'login' => 'grep',
94
+ 'fullname' => 'Gary R Epstein'
95
+ },{
96
+ 'login' => 'stty',
97
+ 'fullname' => 'Simon T Tyson'
98
+ }]
99
+ }
100
+ }
101
+
102
+ Hash.from_xml(xml).should == hash
103
+ end
104
+
105
+ it "should not transform non-repeating siblings into an array" do
106
+ xml =<<-XML
107
+ <opt>
108
+ <user login="grep" fullname="Gary R Epstein" />
109
+ </opt>
110
+ XML
111
+
112
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Hash)
113
+
114
+ hash = {
115
+ 'opt' => {
116
+ 'user' => {
117
+ 'login' => 'grep',
118
+ 'fullname' => 'Gary R Epstein'
119
+ }
120
+ }
121
+ }
122
+
123
+ Hash.from_xml(xml).should == hash
124
+ end
125
+
126
+ it "should typecast an integer" do
127
+ xml = "<tag type='integer'>10</tag>"
128
+ Hash.from_xml(xml)['tag'].should == 10
129
+ end
130
+
131
+ it "should typecast a true boolean" do
132
+ xml = "<tag type='boolean'>true</tag>"
133
+ Hash.from_xml(xml)['tag'].should be_true
134
+ end
135
+
136
+ it "should typecast a false boolean" do
137
+ ["false"].each do |w|
138
+ Hash.from_xml("<tag type='boolean'>#{w}</tag>")['tag'].should be_false
139
+ end
140
+ end
141
+
142
+ it "should typecast a datetime" do
143
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
144
+ Hash.from_xml(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
145
+ end
146
+
147
+ it "should typecast a date" do
148
+ xml = "<tag type='date'>2007-12-31</tag>"
149
+ Hash.from_xml(xml)['tag'].should == Date.parse('2007-12-31')
150
+ end
151
+
152
+ it "should unescape html entities" do
153
+ values = {
154
+ "<" => "&lt;",
155
+ ">" => "&gt;",
156
+ '"' => "&quot;",
157
+ "'" => "&apos;",
158
+ "&" => "&amp;"
159
+ }
160
+ values.each do |k,v|
161
+ xml = "<tag>Some content #{v}</tag>"
162
+ Hash.from_xml(xml)['tag'].should match(Regexp.new(k))
163
+ end
164
+ end
165
+
166
+ it "should undasherize keys as tags" do
167
+ xml = "<tag-1>Stuff</tag-1>"
168
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
169
+ end
170
+
171
+ it "should undasherize keys as attributes" do
172
+ xml = "<tag1 attr-1='1'></tag1>"
173
+ Hash.from_xml(xml)['tag1'].keys.should include( 'attr_1')
174
+ end
175
+
176
+ it "should undasherize keys as tags and attributes" do
177
+ xml = "<tag-1 attr-1='1'></tag-1>"
178
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
179
+ Hash.from_xml(xml)['tag_1'].keys.should include( 'attr_1')
180
+ end
181
+
182
+ it "should render nested content correctly" do
183
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
184
+ Hash.from_xml(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
185
+ end
186
+
187
+ it "should render nested content with split text nodes correctly" do
188
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
189
+ Hash.from_xml(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
190
+ end
191
+
192
+ it "should ignore attributes when a child is a text node" do
193
+ xml = "<root attr1='1'>Stuff</root>"
194
+ Hash.from_xml(xml).should == { "root" => "Stuff" }
195
+ end
196
+
197
+ it "should ignore attributes when any child is a text node" do
198
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
199
+ Hash.from_xml(xml).should == { "root" => "Stuff <em>in italics</em>" }
200
+ end
201
+
202
+ it "should correctly transform multiple children" do
203
+ xml = <<-XML
204
+ <user gender='m'>
205
+ <age type='integer'>35</age>
206
+ <name>Home Simpson</name>
207
+ <dob type='date'>1988-01-01</dob>
208
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
209
+ <is-cool type='boolean'>true</is-cool>
210
+ </user>
211
+ XML
212
+
213
+ hash = {
214
+ "user" => {
215
+ "gender" => "m",
216
+ "age" => 35,
217
+ "name" => "Home Simpson",
218
+ "dob" => Date.parse('1988-01-01'),
219
+ "joined_at" => Time.parse("2000-04-28 23:01"),
220
+ "is_cool" => true
221
+ }
222
+ }
223
+
224
+ Hash.from_xml(xml).should == hash
225
+ end
226
+
227
+ it "should properly handle nil values (ActiveSupport Compatible)" do
228
+ topic_xml = <<-EOT
229
+ <topic>
230
+ <title></title>
231
+ <id type="integer"></id>
232
+ <approved type="boolean"></approved>
233
+ <written-on type="date"></written-on>
234
+ <viewed-at type="datetime"></viewed-at>
235
+ <content type="yaml"></content>
236
+ <parent-id></parent-id>
237
+ </topic>
238
+ EOT
239
+
240
+ expected_topic_hash = {
241
+ 'title' => nil,
242
+ 'id' => nil,
243
+ 'approved' => nil,
244
+ 'written_on' => nil,
245
+ 'viewed_at' => nil,
246
+ 'content' => nil,
247
+ 'parent_id' => nil
248
+ }
249
+ Hash.from_xml(topic_xml)["topic"].should == expected_topic_hash
250
+ end
251
+
252
+ it "should handle a single record from xml (ActiveSupport Compatible)" do
253
+ topic_xml = <<-EOT
254
+ <topic>
255
+ <title>The First Topic</title>
256
+ <author-name>David</author-name>
257
+ <id type="integer">1</id>
258
+ <approved type="boolean"> true </approved>
259
+ <replies-count type="integer">0</replies-count>
260
+ <replies-close-in type="integer">2592000000</replies-close-in>
261
+ <written-on type="date">2003-07-16</written-on>
262
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
263
+ <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>
264
+ <author-email-address>david@loudthinking.com</author-email-address>
265
+ <parent-id></parent-id>
266
+ <ad-revenue type="decimal">1.5</ad-revenue>
267
+ <optimum-viewing-angle type="float">135</optimum-viewing-angle>
268
+ <resident type="symbol">yes</resident>
269
+ </topic>
270
+ EOT
271
+
272
+ expected_topic_hash = {
273
+ 'title' => "The First Topic",
274
+ 'author_name' => "David",
275
+ 'id' => 1,
276
+ 'approved' => true,
277
+ 'replies_count' => 0,
278
+ 'replies_close_in' => 2592000000,
279
+ 'written_on' => Date.new(2003, 7, 16),
280
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
281
+ # Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify
282
+ # The line in ActiveSupport is
283
+ # 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
284
+ 'content' => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
285
+ 'author_email_address' => "david@loudthinking.com",
286
+ 'parent_id' => nil,
287
+ 'ad_revenue' => BigDecimal("1.50"),
288
+ 'optimum_viewing_angle' => 135.0,
289
+ 'resident' => :yes
290
+ }
291
+
292
+ Hash.from_xml(topic_xml)["topic"].each do |k,v|
293
+ v.should == expected_topic_hash[k]
294
+ end
295
+ end
296
+
297
+ it "should handle multiple records (ActiveSupport Compatible)" do
298
+ topics_xml = <<-EOT
299
+ <topics type="array">
300
+ <topic>
301
+ <title>The First Topic</title>
302
+ <author-name>David</author-name>
303
+ <id type="integer">1</id>
304
+ <approved type="boolean">false</approved>
305
+ <replies-count type="integer">0</replies-count>
306
+ <replies-close-in type="integer">2592000000</replies-close-in>
307
+ <written-on type="date">2003-07-16</written-on>
308
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
309
+ <content>Have a nice day</content>
310
+ <author-email-address>david@loudthinking.com</author-email-address>
311
+ <parent-id nil="true"></parent-id>
312
+ </topic>
313
+ <topic>
314
+ <title>The Second Topic</title>
315
+ <author-name>Jason</author-name>
316
+ <id type="integer">1</id>
317
+ <approved type="boolean">false</approved>
318
+ <replies-count type="integer">0</replies-count>
319
+ <replies-close-in type="integer">2592000000</replies-close-in>
320
+ <written-on type="date">2003-07-16</written-on>
321
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
322
+ <content>Have a nice day</content>
323
+ <author-email-address>david@loudthinking.com</author-email-address>
324
+ <parent-id></parent-id>
325
+ </topic>
326
+ </topics>
327
+ EOT
328
+
329
+ expected_topic_hash = {
330
+ 'title' => "The First Topic",
331
+ 'author_name' => "David",
332
+ 'id' => 1,
333
+ 'approved' => false,
334
+ 'replies_count' => 0,
335
+ 'replies_close_in' => 2592000000,
336
+ 'written_on' => Date.new(2003, 7, 16),
337
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
338
+ 'content' => "Have a nice day",
339
+ 'author_email_address' => "david@loudthinking.com",
340
+ 'parent_id' => nil
341
+ }
342
+ # puts Hash.from_xml(topics_xml)['topics'].first.inspect
343
+ Hash.from_xml(topics_xml)["topics"].first.each do |k,v|
344
+ v.should == expected_topic_hash[k]
345
+ end
346
+ end
347
+
348
+ it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do
349
+ topic_xml = <<-EOT
350
+ <rsp stat="ok">
351
+ <photos page="1" pages="1" perpage="100" total="16">
352
+ <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
353
+ </photos>
354
+ </rsp>
355
+ EOT
356
+
357
+ expected_topic_hash = {
358
+ 'id' => "175756086",
359
+ 'owner' => "55569174@N00",
360
+ 'secret' => "0279bf37a1",
361
+ 'server' => "76",
362
+ 'title' => "Colored Pencil PhotoBooth Fun",
363
+ 'ispublic' => "1",
364
+ 'isfriend' => "0",
365
+ 'isfamily' => "0",
366
+ }
367
+ Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"].each do |k,v|
368
+ v.should == expected_topic_hash[k]
369
+ end
370
+ end
371
+
372
+ it "should handle an emtpy array (ActiveSupport Compatible)" do
373
+ blog_xml = <<-XML
374
+ <blog>
375
+ <posts type="array"></posts>
376
+ </blog>
377
+ XML
378
+ expected_blog_hash = {"blog" => {"posts" => []}}
379
+ Hash.from_xml(blog_xml).should == expected_blog_hash
380
+ end
381
+
382
+ it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do
383
+ blog_xml = <<-XML
384
+ <blog>
385
+ <posts type="array">
386
+ </posts>
387
+ </blog>
388
+ XML
389
+ expected_blog_hash = {"blog" => {"posts" => []}}
390
+ Hash.from_xml(blog_xml).should == expected_blog_hash
391
+ end
392
+
393
+ it "should handle array with one entry from_xml (ActiveSupport Compatible)" do
394
+ blog_xml = <<-XML
395
+ <blog>
396
+ <posts type="array">
397
+ <post>a post</post>
398
+ </posts>
399
+ </blog>
400
+ XML
401
+ expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
402
+ Hash.from_xml(blog_xml).should == expected_blog_hash
403
+ end
404
+
405
+ it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do
406
+ blog_xml = <<-XML
407
+ <blog>
408
+ <posts type="array">
409
+ <post>a post</post>
410
+ <post>another post</post>
411
+ </posts>
412
+ </blog>
413
+ XML
414
+ expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
415
+ Hash.from_xml(blog_xml).should == expected_blog_hash
416
+ end
417
+
418
+ it "should handle file types (ActiveSupport Compatible)" do
419
+ blog_xml = <<-XML
420
+ <blog>
421
+ <logo type="file" name="logo.png" content_type="image/png">
422
+ </logo>
423
+ </blog>
424
+ XML
425
+ hash = Hash.from_xml(blog_xml)
426
+ hash.should have_key('blog')
427
+ hash['blog'].should have_key('logo')
428
+
429
+ file = hash['blog']['logo']
430
+ file.original_filename.should == 'logo.png'
431
+ file.content_type.should == 'image/png'
432
+ end
433
+
434
+ it "should handle file from xml with defaults (ActiveSupport Compatible)" do
435
+ blog_xml = <<-XML
436
+ <blog>
437
+ <logo type="file">
438
+ </logo>
439
+ </blog>
440
+ XML
441
+ file = Hash.from_xml(blog_xml)['blog']['logo']
442
+ file.original_filename.should == 'untitled'
443
+ file.content_type.should == 'application/octet-stream'
444
+ end
445
+
446
+ it "should handle xsd like types from xml (ActiveSupport Compatible)" do
447
+ bacon_xml = <<-EOT
448
+ <bacon>
449
+ <weight type="double">0.5</weight>
450
+ <price type="decimal">12.50</price>
451
+ <chunky type="boolean"> 1 </chunky>
452
+ <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
453
+ <notes type="string"></notes>
454
+ <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
455
+ </bacon>
456
+ EOT
457
+
458
+ expected_bacon_hash = {
459
+ 'weight' => 0.5,
460
+ 'chunky' => true,
461
+ 'price' => BigDecimal("12.50"),
462
+ 'expires_at' => Time.utc(2007,12,25,12,34,56),
463
+ 'notes' => "",
464
+ 'illustration' => "babe.png"
465
+ }
466
+
467
+ Hash.from_xml(bacon_xml)["bacon"].should == expected_bacon_hash
468
+ end
469
+
470
+ it "should let type trickle through when unknown (ActiveSupport Compatible)" do
471
+ product_xml = <<-EOT
472
+ <product>
473
+ <weight type="double">0.5</weight>
474
+ <image type="ProductImage"><filename>image.gif</filename></image>
475
+
476
+ </product>
477
+ EOT
478
+
479
+ expected_product_hash = {
480
+ 'weight' => 0.5,
481
+ 'image' => {'type' => 'ProductImage', 'filename' => 'image.gif' },
482
+ }
483
+
484
+ Hash.from_xml(product_xml)["product"].should == expected_product_hash
485
+ end
486
+
487
+ it "should handle unescaping from xml (ActiveResource Compatible)" do
488
+ xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
489
+ expected_hash = {
490
+ 'bare_string' => 'First & Last Name',
491
+ 'pre_escaped_string' => 'First &amp; Last Name'
492
+ }
493
+
494
+ Hash.from_xml(xml_string)['person'].should == expected_hash
495
+ end
496
+ end
497
+
498
+
499
+ describe Hash, 'to_params' do
500
+ before do
501
+ @hash = { :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }
502
+ end
503
+
504
+ it 'should convert correctly into query parameters' do
505
+ @hash.to_params.split('&').sort.should ==
506
+ 'name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave.'.split('&').sort
507
+ end
508
+
509
+ it 'should not leave a trailing &' do
510
+ @hash.to_params.should_not match(/&$/)
511
+ end
512
+ end
513
+
514
+
515
+ describe Hash, 'to_mash' do
516
+ before :each do
517
+ @hash = Hash.new(10)
518
+ end
519
+
520
+ it "copies default Hash value to Mash" do
521
+ @mash = @hash.to_mash
522
+ @mash[:merb].should == 10
523
+ end
524
+ end