ruby-web 1.1.1

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 (190) hide show
  1. data/ChangeLog +474 -0
  2. data/INSTALL.txt +9 -0
  3. data/InstalledFiles +180 -0
  4. data/LICENSE.txt +74 -0
  5. data/Rakefile +529 -0
  6. data/TODO +65 -0
  7. data/doc/additional.xml +149 -0
  8. data/doc/core.xml +652 -0
  9. data/doc/credits/index.xml +52 -0
  10. data/doc/credits/php.contributors.xml +118 -0
  11. data/doc/credits/php.language-snippets.ent +622 -0
  12. data/doc/install/index.xml +136 -0
  13. data/doc/install/mac/index.xml +21 -0
  14. data/doc/install/ruby-web.install.rb.instructions.xml +7 -0
  15. data/doc/install/unix/index.xml +46 -0
  16. data/doc/install/win/apache1.xml +166 -0
  17. data/doc/install/win/apache2.xml +141 -0
  18. data/doc/install/win/iis.xml +162 -0
  19. data/doc/install/win/index.xml +24 -0
  20. data/doc/install/win/installer.xml +31 -0
  21. data/doc/install/win/manual.xml +43 -0
  22. data/doc/manual.xml +69 -0
  23. data/doc/old/apache_cgi.txt +23 -0
  24. data/doc/old/fastcgi.txt +23 -0
  25. data/doc/old/mod_ruby.txt +21 -0
  26. data/doc/old/snippets.rdoc +183 -0
  27. data/doc/old/webrick.txt +23 -0
  28. data/doc/old/windows_cgi.txt +9 -0
  29. data/doc/tutorial.xml +14 -0
  30. data/doc/xsl/manual-multi.xsl +10 -0
  31. data/doc/xsl/manual-pdf.xsl +6 -0
  32. data/doc/xsl/manual-single.xsl +6 -0
  33. data/doc/xsl/manual.css +22 -0
  34. data/install.rb +1022 -0
  35. data/lib/formatter.rb +314 -0
  36. data/lib/html-parser.rb +429 -0
  37. data/lib/htmlrepair.rb +113 -0
  38. data/lib/htmlsplit.rb +842 -0
  39. data/lib/sgml-parser.rb +332 -0
  40. data/lib/web.rb +68 -0
  41. data/lib/web/assertinclude.rb +129 -0
  42. data/lib/web/config.rb +50 -0
  43. data/lib/web/connection.rb +1070 -0
  44. data/lib/web/convenience.rb +154 -0
  45. data/lib/web/formreader.rb +318 -0
  46. data/lib/web/htmlparser/html-parser.rb +429 -0
  47. data/lib/web/htmlparser/sgml-parser.rb +332 -0
  48. data/lib/web/htmltools/element.rb +296 -0
  49. data/lib/web/htmltools/stparser.rb +276 -0
  50. data/lib/web/htmltools/tags.rb +286 -0
  51. data/lib/web/htmltools/tree.rb +139 -0
  52. data/lib/web/htmltools/xmltree.rb +160 -0
  53. data/lib/web/htmltools/xpath.rb +71 -0
  54. data/lib/web/info.rb +63 -0
  55. data/lib/web/load.rb +210 -0
  56. data/lib/web/mime.rb +87 -0
  57. data/lib/web/phprb.rb +340 -0
  58. data/lib/web/resources/test/cookie.rb +33 -0
  59. data/lib/web/resources/test/counter.rb +20 -0
  60. data/lib/web/resources/test/multipart.rb +14 -0
  61. data/lib/web/resources/test/redirect.rb +8 -0
  62. data/lib/web/resources/test/stock.rb +33 -0
  63. data/lib/web/sapi/apache.rb +129 -0
  64. data/lib/web/sapi/fastcgi.rb +22 -0
  65. data/lib/web/sapi/install/apache.rb +180 -0
  66. data/lib/web/sapi/install/iis.rb +93 -0
  67. data/lib/web/sapi/install/macosx.rb +90 -0
  68. data/lib/web/sapi/webrick.rb +86 -0
  69. data/lib/web/session.rb +83 -0
  70. data/lib/web/shim/cgi.rb +129 -0
  71. data/lib/web/shim/rails.rb +175 -0
  72. data/lib/web/stringio.rb +78 -0
  73. data/lib/web/strscanparser.rb +24 -0
  74. data/lib/web/tagparser.rb +96 -0
  75. data/lib/web/testing.rb +666 -0
  76. data/lib/web/traceoutput.rb +75 -0
  77. data/lib/web/unit.rb +56 -0
  78. data/lib/web/upload.rb +59 -0
  79. data/lib/web/validate.rb +52 -0
  80. data/lib/web/wiki.rb +557 -0
  81. data/lib/web/wiki/linker.rb +72 -0
  82. data/lib/web/wiki/page.rb +201 -0
  83. data/lib/webunit.rb +27 -0
  84. data/lib/webunit/assert.rb +152 -0
  85. data/lib/webunit/converter.rb +154 -0
  86. data/lib/webunit/cookie.rb +118 -0
  87. data/lib/webunit/domwalker.rb +185 -0
  88. data/lib/webunit/exception.rb +14 -0
  89. data/lib/webunit/form.rb +116 -0
  90. data/lib/webunit/frame.rb +37 -0
  91. data/lib/webunit/htmlelem.rb +122 -0
  92. data/lib/webunit/image.rb +26 -0
  93. data/lib/webunit/jscript.rb +31 -0
  94. data/lib/webunit/link.rb +33 -0
  95. data/lib/webunit/params.rb +321 -0
  96. data/lib/webunit/parser.rb +229 -0
  97. data/lib/webunit/response.rb +464 -0
  98. data/lib/webunit/runtest.rb +41 -0
  99. data/lib/webunit/table.rb +148 -0
  100. data/lib/webunit/testcase.rb +45 -0
  101. data/lib/webunit/ui/cui/testrunner.rb +50 -0
  102. data/lib/webunit/utils.rb +68 -0
  103. data/lib/webunit/webunit.rb +28 -0
  104. data/test/dev/action.rb +83 -0
  105. data/test/dev/forms.rb +104 -0
  106. data/test/dev/forms2.rb +104 -0
  107. data/test/dev/parser.rb +17 -0
  108. data/test/dev/scripts/dump.rb +24 -0
  109. data/test/dev/scripts/makedist.rb +62 -0
  110. data/test/dev/scripts/uri.rb +41 -0
  111. data/test/dev/scripts/uri/common.rb +432 -0
  112. data/test/dev/scripts/uri/ftp.rb +149 -0
  113. data/test/dev/scripts/uri/generic.rb +1106 -0
  114. data/test/dev/scripts/uri/http.rb +76 -0
  115. data/test/dev/scripts/uri/https.rb +26 -0
  116. data/test/dev/scripts/uri/ldap.rb +238 -0
  117. data/test/dev/scripts/uri/mailto.rb +260 -0
  118. data/test/dev/scripts/urireg.rb +174 -0
  119. data/test/dev/simpledispatcher.rb +156 -0
  120. data/test/dev/test.action.rb +146 -0
  121. data/test/dev/test.formreader.rb +463 -0
  122. data/test/dev/test.simpledispatcher.rb +186 -0
  123. data/test/dev/webunit/conv/digit-0.rb +21 -0
  124. data/test/dev/webunit/conv/digit-1.rb +17 -0
  125. data/test/dev/webunit/conv/digit.rb +23 -0
  126. data/test/dev/webunit/conv/test_digit-0.rb +16 -0
  127. data/test/dev/webunit/conv/test_digit-1.rb +19 -0
  128. data/test/dev/webunit/conv/test_digit.rb +26 -0
  129. data/test/dev/webunit/conv/test_digit_view-0.rb +76 -0
  130. data/test/dev/webunit/conv/test_digit_view-1.rb +102 -0
  131. data/test/dev/webunit/conv/test_digit_view.rb +134 -0
  132. data/test/installation/htdocs/cgi_test.rb +296 -0
  133. data/test/installation/htdocs/test_install.rb +4 -0
  134. data/test/installation/runwebtest.rb +5 -0
  135. data/test/installation/test_cookie.rb +128 -0
  136. data/test/installation/test_form.rb +47 -0
  137. data/test/installation/test_multipart.rb +51 -0
  138. data/test/installation/test_request.rb +24 -0
  139. data/test/installation/test_response.rb +35 -0
  140. data/test/unit/htdocs/cookie.rb +32 -0
  141. data/test/unit/htdocs/multipart.rb +28 -0
  142. data/test/unit/htdocs/redirect.rb +12 -0
  143. data/test/unit/htdocs/simple.rb +13 -0
  144. data/test/unit/htdocs/stock.rb +33 -0
  145. data/test/unit/test_assert.rb +162 -0
  146. data/test/unit/test_cookie.rb +114 -0
  147. data/test/unit/test_domwalker.rb +77 -0
  148. data/test/unit/test_form.rb +42 -0
  149. data/test/unit/test_frame.rb +40 -0
  150. data/test/unit/test_htmlelem.rb +74 -0
  151. data/test/unit/test_image.rb +45 -0
  152. data/test/unit/test_jscript.rb +57 -0
  153. data/test/unit/test_link.rb +85 -0
  154. data/test/unit/test_multipart.rb +51 -0
  155. data/test/unit/test_params.rb +210 -0
  156. data/test/unit/test_parser.rb +53 -0
  157. data/test/unit/test_response.rb +150 -0
  158. data/test/unit/test_table.rb +70 -0
  159. data/test/unit/test_utils.rb +106 -0
  160. data/test/unit/test_webunit.rb +28 -0
  161. data/test/web/mod_ruby_stub.rb +39 -0
  162. data/test/web/test.assertinclude.rb +109 -0
  163. data/test/web/test.buffer.rb +182 -0
  164. data/test/web/test.code.loader.rb +78 -0
  165. data/test/web/test.config.rb +31 -0
  166. data/test/web/test.error.handling.rb +91 -0
  167. data/test/web/test.formreader-2.0.rb +352 -0
  168. data/test/web/test.load.rb +125 -0
  169. data/test/web/test.mime-type.rb +23 -0
  170. data/test/web/test.narf.cgi.rb +106 -0
  171. data/test/web/test.phprb.rb +239 -0
  172. data/test/web/test.request.rb +368 -0
  173. data/test/web/test.response.rb +637 -0
  174. data/test/web/test.ruby-web.rb +10 -0
  175. data/test/web/test.session.rb +50 -0
  176. data/test/web/test.shim.cgi.rb +96 -0
  177. data/test/web/test.tagparser.rb +65 -0
  178. data/test/web/test.template2.rb +297 -0
  179. data/test/web/test.testing2.rb +318 -0
  180. data/test/web/test.upload.rb +45 -0
  181. data/test/web/test.validate.rb +46 -0
  182. data/test/web/test.web.test.rb +495 -0
  183. data/test/wiki/test.history.rb +297 -0
  184. data/test/wiki/test.illustration_page.rb +287 -0
  185. data/test/wiki/test.linker.rb +197 -0
  186. data/test/wiki/test.tarpit.rb +56 -0
  187. data/test/wiki/test.wiki.rb +300 -0
  188. data/test/wikitestroot/admin.rb +7 -0
  189. data/test/wikitestroot/wiki.rb +6 -0
  190. metadata +234 -0
@@ -0,0 +1,154 @@
1
+ module Web
2
+ def Web::lib_filename( resource, dirname="missing" )
3
+ if dirname == "missing"
4
+ dirname = File.dirname( Web::parse_trace( caller[1] ).shift )
5
+ end
6
+ File.join( dirname, resource )
7
+ end
8
+
9
+ def Web::lib_file_contents( filename, dirname="missing" )
10
+ File.open( lib_filename( filename, dirname ), 'r' ) { |f|
11
+ f.read
12
+ }
13
+ end
14
+
15
+ def Web::send_lib_file( filename, dirname = "missing" )
16
+ Web::connection::send_file( Web::lib_filename( filename, dirname ) )
17
+ end
18
+
19
+ def Web::escape(string)
20
+ return nil unless string
21
+ string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
22
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
23
+ end.tr(' ', '+')
24
+ end
25
+
26
+
27
+ def Web::unescape(string)
28
+ return nil unless string
29
+ string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
30
+ [$1.delete('%')].pack('H*')
31
+ end
32
+ end
33
+
34
+
35
+ def Web::escape_html(string)
36
+ return nil unless string
37
+ string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;')
38
+ end
39
+
40
+ # include these methods with Web
41
+ def puts(*args)
42
+ Web::connection.puts(args)
43
+ end
44
+ def print(*args)
45
+ Web::connection.print(*args)
46
+ end
47
+ def write(*args)
48
+ Web::connection.write(*args)
49
+ end
50
+ def escape_html(string)
51
+ Web::escape_html(string)
52
+ end
53
+ def unescape_html(string)
54
+ Web::unescape_html(string)
55
+ end
56
+ def escape_element(string, *elements)
57
+ Web::escape_element(string, *elements)
58
+ end
59
+ def unescape_element(string, *elements)
60
+ Web::unescape_element(string, *elements)
61
+ end
62
+ alias escapeHTML escape_html
63
+ alias html_encode escape_html
64
+ alias unescapeHTML unescape_html
65
+ alias escapeElement escape_element
66
+ alias unescapeElement unescape_element
67
+ def rfc1123_date(time)
68
+ Web::rfc1123_date(time)
69
+ end
70
+
71
+ def Web::unescape_html(string)
72
+ return nil unless string
73
+ string.gsub(/&(.*?);/n) do
74
+ match = $1.dup
75
+ case match
76
+ when /\Aamp\z/ni then '&'
77
+ when /\Aquot\z/ni then '"'
78
+ when /\Agt\z/ni then '>'
79
+ when /\Alt\z/ni then '<'
80
+ when /\A#0*(\d+)\z/n then
81
+ if Integer($1) < 256
82
+ Integer($1).chr
83
+ else
84
+ if Integer($1) < 65536 and ($KCODE[0] == ?u or $KCODE[0] == ?U)
85
+ [Integer($1)].pack("U")
86
+ else
87
+ "&##{$1};"
88
+ end
89
+ end
90
+ when /\A#x([0-9a-f]+)\z/ni then
91
+ if $1.hex < 256
92
+ $1.hex.chr
93
+ else
94
+ if $1.hex < 65536 and ($KCODE[0] == ?u or $KCODE[0] == ?U)
95
+ [$1.hex].pack("U")
96
+ else
97
+ "&#x#{$1};"
98
+ end
99
+ end
100
+ else
101
+ "&#{match};"
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ def Web::escape_element(string, *elements)
108
+ return nil unless string
109
+ elements = elements[0] if elements[0].kind_of?(Array)
110
+ unless elements.empty?
111
+ string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/ni) do
112
+ Web::escape_html($&)
113
+ end
114
+ else
115
+ string
116
+ end
117
+ end
118
+
119
+
120
+ def Web::unescape_element(string, *elements)
121
+ return nil unless string
122
+ elements = elements[0] if elements[0].kind_of?(Array)
123
+ unless elements.empty?
124
+ string.gsub(/&lt;\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?&gt;/ni) do
125
+ Web::unescape_html($&)
126
+ end
127
+ else
128
+ string
129
+ end
130
+ end
131
+
132
+ class << self
133
+ alias escapeHTML escape_html
134
+ alias html_encode escape_html
135
+ alias unescapeHTML unescape_html
136
+ alias escapeElement escape_element
137
+ alias unescapeElement unescape_element
138
+ end
139
+
140
+ RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
141
+ RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
142
+ def Web::rfc1123_date(time)
143
+ t = time.clone.gmtime
144
+ return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
145
+ RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year,
146
+ t.hour, t.min, t.sec)
147
+ end
148
+
149
+ def Web::parse_trace(line)
150
+ line =~ /^((\w:){0,1}.*?):(\d+)(:?(.*))$/
151
+ [ $1, $3, $5 ]
152
+ end
153
+
154
+ end
@@ -0,0 +1,318 @@
1
+ class FormReader #:nodoc: all
2
+ attr_accessor :html
3
+ alias :body :html
4
+ attr_reader :url
5
+ def initialize( html )
6
+ @html = html
7
+ @html_tree = nil
8
+ @parsed = nil
9
+ @url = ""
10
+ end
11
+
12
+ def get_content
13
+ html
14
+ end
15
+
16
+ attr_writer :forms
17
+ def forms
18
+ @forms ||= { }
19
+ end
20
+
21
+ def [] (key)
22
+ # ugly ugly ugly
23
+ form_elements( key )
24
+ forms[key]
25
+ end
26
+
27
+ def form_elements( name )
28
+ unless( forms[name] )
29
+ html_tree.get_elements("form").each{ |form|
30
+ if (name == form.attribute("name"))
31
+ forms[name] ||= FormElements.new
32
+
33
+ forms[name].action = form.attribute("action")
34
+
35
+ form.get_elements("input").each{ |e|
36
+ if ["text","hidden","submit","password","file"].include? e.attribute("type") or e.attribute("type").nil?
37
+ forms[name].push_text( e.attribute("name"),
38
+ Web.unescape(e.attribute("value")) || "" )
39
+ elsif e.attribute("type") == "checkbox"
40
+ # boolean checkboxes
41
+ # - assumed if there is no value
42
+ if (e.attribute("value") == "" || e.attribute("value").nil?)
43
+ forms[name].push( e.attribute("name"),
44
+ if (e.attribute("checked"))
45
+ true
46
+ else
47
+ false
48
+ end )
49
+ else
50
+ # multi select checkboxes
51
+ select = forms[name].find_select( e.attribute("name") )
52
+ select.multiple = true
53
+ select.push_option(e)
54
+ end
55
+ elsif e.attribute("type") == "radio"
56
+ select = forms[name].find_select( e.attribute("name") )
57
+ select.multiple = false
58
+ select.push_option(e)
59
+ elsif e.attribute("type") == "file"
60
+ end
61
+ }
62
+ form.get_elements("textarea").each{ |e|
63
+ forms[name].push_text e.attribute("name"), e.content.to_s.strip
64
+ }
65
+ form.get_elements("select").each { |e|
66
+ select = Select.new
67
+ select.multiple = e.attribute("multiple") != nil
68
+ e.get_elements("option").each{ |option|
69
+ select.shift_option( option )
70
+ }
71
+ forms[name].push_select e.attribute("name"), select
72
+ }
73
+ end
74
+ }
75
+ end
76
+ forms[name]
77
+ end
78
+
79
+ def get_fields( name )
80
+ form_elements(name).get_fields
81
+ end
82
+
83
+ def get_options( name )
84
+ form_elements(name).get_options
85
+ end
86
+
87
+ def html_tree
88
+ unless (@html_tree)
89
+ p = HTMLTree::Parser.new(true, false)
90
+ p.feed get_content
91
+ @html_tree = p.tree
92
+ end
93
+ @html_tree
94
+ end
95
+
96
+
97
+ def merge_fields( name, edited_fields )
98
+ form_elements(name).merge_fields(edited_fields)
99
+ end
100
+
101
+ class Option
102
+ attr_accessor :value, :selected
103
+ def initialize( value, selected )
104
+ @value = value; @selected = selected
105
+ end
106
+ end
107
+
108
+ class Select
109
+ attr_accessor :options, :multiple
110
+ def initialize
111
+ @options = Array.new
112
+ @multiple = false
113
+ end
114
+
115
+ def shift_option( option )
116
+ options.unshift(make_option_from_element(option))
117
+ end
118
+
119
+ def push_option( option )
120
+ options.push(make_option_from_element(option))
121
+ end
122
+
123
+ def make_option_from_element( element )
124
+ value = element.attribute("value")
125
+ unless value
126
+ value = element.content.to_s
127
+ value.gsub!( /<[^>]*>/, "" )
128
+ value.strip!
129
+ end
130
+ Option.new( value,
131
+ element.attribute("selected") || \
132
+ element.attribute("checked") )
133
+ end
134
+
135
+ def each
136
+ options.each{ |option|
137
+ yield option.value if option.selected
138
+ }
139
+ end
140
+
141
+ include Enumerable
142
+
143
+ def == other
144
+ if other.kind_of?(String) && self.to_a.length == 1
145
+ other == self.to_a[0]
146
+ else
147
+ other == self.to_a
148
+ end
149
+ end
150
+
151
+ # these functions are the interface for assert_includes
152
+ # we delegate to the array... but also answer to the options hash
153
+ def has_key?(index)
154
+ self.to_a.has_key?(index)
155
+ end
156
+
157
+ def __index(key)
158
+ self.to_a.__index(key)
159
+ end
160
+
161
+ def compare_includes? haystack, prefix=[]
162
+ self.to_a.compare_includes? haystack, prefix
163
+ end
164
+
165
+ end
166
+
167
+ class FormElements
168
+ attr_reader :fields
169
+ attr_accessor :action
170
+
171
+ def initialize
172
+ @fields = {}
173
+ end
174
+
175
+ def get_fields
176
+ crushedFields = { }
177
+ @fields.each{ |k,v|
178
+ v = v.collect { |v1|
179
+ if v1.kind_of? Select
180
+ v1.to_a.sort
181
+ else
182
+ v1
183
+ end
184
+ }.flatten
185
+ crushedFields[k] = v
186
+ }
187
+ Web::Testing::MultiHashTree.new(crushedFields).fields
188
+ #crushedFields
189
+ end
190
+
191
+ def get_options
192
+ all_options = { }
193
+ @fields.each{ |k,v|
194
+ v.each { |field|
195
+ if (field.kind_of? Select)
196
+ all_options[k] = field.options.collect{ |o|
197
+ o.value
198
+ }
199
+ end
200
+ }
201
+ }
202
+ all_options
203
+ end
204
+
205
+ def merge_fields( edited_fields )
206
+ merged = Marshal.load( Marshal.dump( @fields ) )
207
+
208
+ edited_fields.each{ |k,v|
209
+ # normalize inputs
210
+ k = k.to_s
211
+ v = [v] unless v.kind_of? Array
212
+
213
+ # guard against errors
214
+ if merged[k].nil?
215
+ raise Web::Testing::FieldNotFoundException.new( "Element named #{k} not present on form" )
216
+ end
217
+
218
+ if (v.size > merged[k].size)
219
+ raise FormMergeError.new( "Only #{merged[k].size} elements on form; " +
220
+ "Can't merge with #{v.size} new values" )
221
+ end
222
+
223
+ v.each_with_index{ |v, i|
224
+ pristine = @fields[k][i]
225
+ target = merged[k][i]
226
+
227
+ if (pristine.kind_of? Select)
228
+ # guard against improperly submitted values
229
+ unless v.kind_of? Hash
230
+ raise FormMergeError.new( "Values for select elements must be merged in by using merge('#{k}'=>select('#{v}')" )
231
+ end
232
+
233
+ # guard against merging multiple
234
+ # values into single select
235
+ if (pristine.multiple == false) & (v.keys.length > 1 )
236
+ raise FormMergeError.new( "Trying to merge multiple [#{ v.inspect }] into single select[#{ k }]" )
237
+ end
238
+
239
+ # selects must be merged with a hash
240
+ v.each{ |value, flag|
241
+ # guard against merging nonexistent option
242
+ unless pristine.options.find{ |e|
243
+ e.value == value
244
+ }
245
+ raise FormMergeError. \
246
+ new("Form doesn't have an option for #{value}")
247
+ end
248
+
249
+
250
+ # turn off everything else for single selects
251
+
252
+ unless target.multiple || flag == false
253
+ target.options.each { |o|
254
+ o.selected = false
255
+ }
256
+ end
257
+
258
+ # merge select fields
259
+ target.options.find{ |e|
260
+ e.value == value
261
+ }.selected = flag
262
+ }
263
+
264
+ else
265
+ merged[k][i] = v
266
+ end
267
+ }
268
+ }
269
+
270
+ # delete unchecked checkboxes
271
+ merged.delete_if{ |k,v|
272
+ v.delete_if{ |e|
273
+ e == false
274
+ }
275
+ v.size == 0
276
+ }
277
+
278
+
279
+
280
+ # flatten out select elements
281
+ merged.each{ |k,v|
282
+ merged[k] = v.collect{ |e|
283
+ if (e.kind_of? Select)
284
+ e.to_a
285
+ else
286
+ e
287
+ end
288
+ }.flatten
289
+ }
290
+ merged
291
+ end
292
+
293
+ def push( name, value )
294
+ @fields[name] ||= []
295
+ @fields[name].push value
296
+ end
297
+
298
+ def find_select( name )
299
+ select = if (fields[name])
300
+ fields[name].find{ |e|
301
+ e.kind_of? Select
302
+ }
303
+ end
304
+ unless select
305
+ select = Select.new
306
+ push name, select
307
+ end
308
+ select
309
+ end
310
+
311
+ alias :push_text :push
312
+ alias :push_select :push
313
+
314
+ end
315
+ end
316
+
317
+ class FormMergeError < Exception #:nodoc: all
318
+ end