thorero 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
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