ruber 0.0.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 (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. metadata +284 -0
@@ -0,0 +1,414 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'delegate'
22
+ require 'enumerator'
23
+ require 'forwardable'
24
+ require 'pathname'
25
+ require 'shellwords'
26
+ require 'ostruct'
27
+
28
+ require 'dictionary'
29
+ require 'facets/boolean'
30
+ require 'facets/enumerable/mash'
31
+
32
+ module Ruber
33
+
34
+ =begin rdoc
35
+ The directory where the Ruber core files are
36
+ =end
37
+ RUBER_LIB_DIR = File.dirname(File.expand_path(__FILE__))
38
+
39
+ =begin rdoc
40
+ The Ruber @data@ directory
41
+ =end
42
+ RUBER_DATA_DIR = File.expand_path File.join(RUBER_LIB_DIR, '..', 'data')
43
+ end
44
+
45
+ module Kernel
46
+
47
+ =begin rdoc
48
+ Whether an object is _similar_ to a string
49
+ @return [Boolean] *true* if the object is similar to a string and *false* otherwise. In particular
50
+ currently it returns *true* for strings and symbol and *false* for all other objects.
51
+ =end
52
+ def string_like?
53
+ false
54
+ end
55
+
56
+ =begin rdoc
57
+ @return [Binding] the binding of the object
58
+ =end
59
+ def obj_binding
60
+ binding
61
+ end
62
+
63
+ alias :same? :equal?
64
+
65
+ =begin rdoc
66
+ Executes the body of the block without emitting warnings
67
+
68
+ This method sets the @$VERBOSE@ global variable to *nil* (which disables warnings)
69
+ before calling the block and restores it to the original value after executing the
70
+ block (even if the block raises an exception)
71
+ @yield
72
+ @return [Object] the value returned by the block
73
+ =end
74
+ def silently
75
+ old_verbose, $VERBOSE = $VERBOSE, nil
76
+ begin yield
77
+ ensure
78
+ $VERBOSE = old_verbose
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ class Object
85
+
86
+ =begin rdoc
87
+ Prints the given object and the position from which the method was called
88
+ @param [Object] the object to print on screen (its @inspect@ method will be used)
89
+ @return [nil]
90
+ =end
91
+ def debug obj
92
+ $stderr.puts "DEBUG(#{caller[0][/^[^:]+:\d+/]}): #{obj.inspect}\n"
93
+ end
94
+ alias_method :d, :debug
95
+
96
+ end
97
+
98
+ class OpenStruct
99
+ #This is needed for compatibility with ruby 1.8, where there's an Object#type
100
+ #deprecated method which forbids OpenStruct to define its own, thus breaking
101
+ #the type entry of Options objects
102
+ undef_method :type rescue nil
103
+ end
104
+
105
+ class Object
106
+
107
+ =begin rdoc
108
+ Encloses *self* in an array unless it's already an array
109
+
110
+ Unlike @Kernel#Array@, this doesn't use the @to_ary@ or @to_a@ methods
111
+
112
+ @return [Array] *self* enclosed in an array
113
+ =end
114
+ def to_array
115
+ [self]
116
+ end
117
+
118
+ end
119
+
120
+ class Array
121
+
122
+ =begin rdoc
123
+ Override of {Object#to_array}
124
+
125
+ @return [Array] *self*
126
+ =end
127
+ def to_array
128
+ self
129
+ end
130
+
131
+ if RUBY_VERSION.match /8/
132
+ =begin rdoc
133
+ Choose a random element or n random elements
134
+
135
+ It's a version for ruby 1.8.7 of Array#sample from ruby 1.9. It uses Array#choice
136
+ which only exists in ruby 1.8.7 but this doesn't matter since this method is only
137
+ defined in ruby 1.8.7
138
+
139
+ *Note:* ruby 1.9 already defines this method, so the original version is kept
140
+
141
+ @param [Integer] n the number of elements to sample
142
+ @return [<Object>,Object] if _n_ is 1, then a single element is returned. If _n_
143
+ is greater than one, then an array of _n_ elements randomly chosen from those in
144
+ the array are returned (without duplicates). If _n_ is greater than the size
145
+ of the array, then the returned array will have the size of the array
146
+ =end
147
+ def sample n = 1
148
+ if n == 1 then choice
149
+ else
150
+ a = dup
151
+ n = [a.size, n].min
152
+ res = []
153
+ n.times do
154
+ res << a.delete(rand(a.size))
155
+ end
156
+ res
157
+ end
158
+ end
159
+ end
160
+
161
+ =begin rdoc
162
+ Converts an array to a hash
163
+
164
+ Depending on the value of the argument, this method can work as the reverse of
165
+ @Hash#to_a@ or as @Hash.[]@.
166
+
167
+ In the first case, each element of the array must be an array of size 2. The first
168
+ element of each inner array will be used as key, while the second will be used as
169
+ the corresponding value.
170
+
171
+ In the second case, this method works exactly as @Hash.[]@
172
+ @param [Boolean] pairs whether to work as the inverse of @Hash#to_a@ or as @Hash.[]@
173
+ @return [Hash] a hash built as described above
174
+ =end
175
+ def to_h pairs = true
176
+ if pairs then self.inject({}){|res, i| res[i[0]] = i[1]; res}
177
+ else Hash[*self]
178
+ end
179
+ end
180
+
181
+ =begin rdoc
182
+ Whether the array contains a single element and that element is equal to a given value
183
+ @param [Object] value the object to compare the only element of the array
184
+ @return [Boolean] *true* if the array has size 1 and its element is equal (according
185
+ to @==@) to _value_ and *false* otherwise.
186
+ =end
187
+ def only? value
188
+ self.size == 1 and self[0] == value
189
+ end
190
+
191
+ end
192
+
193
+ class String
194
+
195
+ =begin rdoc
196
+ Splits the string in lines.
197
+
198
+ It's a shortcut for str.split("\n")
199
+
200
+ @return [<String>] an array containing the lines which make up the string
201
+ =end
202
+ def split_lines
203
+ split "\n"
204
+ end
205
+
206
+ =begin rdoc
207
+ Overryde of {Kernel#string_like?}
208
+
209
+ @return [Boolean] *true*
210
+ =end
211
+ def string_like?
212
+ true
213
+ end
214
+
215
+ end
216
+
217
+ class Symbol
218
+
219
+ =begin rdoc
220
+ Overryde of {Kernel#string_like?}
221
+
222
+ @return [Boolean] *true*
223
+ =end
224
+ def string_like?
225
+ true
226
+ end
227
+
228
+ end
229
+
230
+ class Pathname
231
+
232
+ =begin rdoc
233
+ Whether the pathname represents a file or directory under the directory represented
234
+ by another pathname
235
+
236
+ @param [Pathname,String] the string or pathname representing the directory which
237
+ can be parent of *self*
238
+ =end
239
+ def child_of? other
240
+ other = Pathname.new(other) if other.is_a? String
241
+ self.relative_path_from(other).to_s[0..2]!= '../'
242
+ end
243
+
244
+ end
245
+
246
+ class Dictionary
247
+
248
+ # In ruby 1.9, it seems that passing a lambda to one of the Array#each or similar
249
+ # methods doesn't unpack the elements of the array, so the call to the lambda
250
+ # fails because of wrong number of arguments
251
+ if RUBY_VERSION.match /9/
252
+
253
+ =begin rdoc
254
+ @private
255
+ Works as the method with the same name in facets but works around a bug with facets
256
+ and ruby 1.9
257
+ =end
258
+ def order_by_value
259
+ @order_by = lambda { |i| i[0] }
260
+ order
261
+ self
262
+ end
263
+
264
+ =begin rdoc
265
+ @private
266
+ Works as the method with the same name in facets but works around a bug with facets
267
+ and ruby 1.9
268
+ =end
269
+ def order_by_key
270
+ @order_by = lambda { |i| i[0] }
271
+ order
272
+ self
273
+ end
274
+
275
+ end
276
+
277
+ =begin rdoc
278
+ Calls the block passing it each key and the corresponding value starting from the
279
+ last
280
+ @yield key, value
281
+ @return [Dictionary] *self*
282
+ =end
283
+ def reverse_each
284
+ order.reverse_each{|k| yield k, @hash[k] }
285
+ self
286
+ end
287
+
288
+ end
289
+
290
+ module Enumerable
291
+
292
+ =begin rdoc
293
+ Finds the first element for which the block returns a true value and returns the
294
+ value of the block
295
+
296
+ It works like @Enumerable#find@, but, instead of returning the element for which
297
+ the block returned a true value, it returns the value returned by the block.
298
+ @yield obj
299
+ @return [Object, nil] the first non-false value returned by the block or *nil*
300
+ if the block returns *nil* or *false* for all elements
301
+
302
+ @example
303
+ [1, 2, 3, 4].find!{|i| 2*i if i > 2}
304
+ => 6 #(3*2)
305
+ =end
306
+ def find!
307
+ each do |obj|
308
+ res = yield obj
309
+ return res if res
310
+ end
311
+ nil
312
+ end
313
+ alias_method :find_and_map, :find!
314
+
315
+ alias_method :map_hash, :mash
316
+ end
317
+
318
+ module Ruber
319
+
320
+ =begin rdoc
321
+ Module for objects which can be activated and disactivated. It provides methods
322
+ for inquiring the active state and to change it. If the object is a @Qt::Object@,
323
+ and has an @activated()@ and a @deactivated@ signals, they will be
324
+ emitted when the state change. If the object is not a @Qt::Object@, or doesn't have
325
+ those signals, everything else will still work (in the rest of the documentation,
326
+ every time emitting a signal is mentioned, it's understood that the signal won't
327
+ be emitted if it doesn't exist).
328
+
329
+ Classes mixing-in this module should initialize an instance variable called
330
+ <tt>@active</tt>. If they don't, one initialized to *nil* will be created the first
331
+ time it'll be needed (possibly with a warning).
332
+ =end
333
+ module Activable
334
+
335
+ =begin rdoc
336
+ @return [Boolean] whether the object is active or not
337
+ =end
338
+ def active?
339
+ @active
340
+ end
341
+
342
+ =begin rdoc
343
+ Makes the object inactive
344
+
345
+ If previously the object was active, emits the @deactivated@ signal
346
+ @return [nil]
347
+ =end
348
+ def deactivate
349
+ self.active = false
350
+ nil
351
+ end
352
+
353
+ =begin rdoc
354
+ Makes the object active
355
+
356
+ If previously the object was inactive, emits the @activated@ signal.
357
+ @return [nil]
358
+ =end
359
+ def activate
360
+ self.active = true
361
+ nil
362
+ end
363
+
364
+ =begin rdoc
365
+ Enables or disables the object
366
+
367
+ If the state of the object changes, the @activated@ or @deactivated@ signal is
368
+ emitted.
369
+
370
+ @param [Object] val whether the object should be activated or deactivated. If the
371
+ object is a true value, the object will be activated, otherwise it will be deactivated
372
+ @return [Object] _val_
373
+ =end
374
+ def active= val
375
+ old = @active
376
+ @active = val.to_bool
377
+ if old != @active
378
+ emit(@active ? activated : deactivated) rescue NameError
379
+ end
380
+ end
381
+
382
+ end
383
+
384
+ end
385
+
386
+ module Shellwords
387
+
388
+ =begin rdoc
389
+ Similar to @Shellwords.split@, but attempts to include quotes in the returned
390
+ array for strings containing spaces.
391
+
392
+ Since it's impossible to find out from the output of @Shellwords#split@ whether a string was
393
+ quoted with signle or double quotes, the following approach is taken: if the
394
+ string contains double quotes, it's sourrounded with single quotes; otherwise
395
+ double quotes are used.
396
+
397
+ TODO: improve on the above algorithm.
398
+ @param [String] str the string to split
399
+ @return [<String>] an array as with @Shellwords#split@ but with strings containing
400
+ whitespaces quoted according to the above algorithm
401
+ =end
402
+ def self.split_with_quotes str
403
+ res = split str
404
+ res.map! do |s|
405
+ unless s.include? ' ' then s
406
+ else
407
+ quote = s.include?('"') ? "'" : '"'
408
+ quote + s + quote
409
+ end
410
+ end
411
+ res
412
+ end
413
+
414
+ end
@@ -0,0 +1,159 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'facets/kernel/deep_copy'
22
+
23
+ module Ruber
24
+
25
+ =begin rdoc
26
+ Backend for <tt>SettingsContainer</tt> which writes the options to a YAML file,
27
+ as a nested hash, where the outer hash contains the groups and each inner hash,
28
+ which represents a group, contains the options. Here's an example of the YAML
29
+ file produced by this class.
30
+
31
+ :group1:
32
+ :opt1: value1
33
+ :opt2: value2
34
+ :opt3: value3
35
+ :group2:
36
+ :opt1: value4
37
+ :opt4: value5
38
+ :group3:
39
+ :opt5: value6
40
+ =end
41
+ class YamlSettingsBackend
42
+
43
+ =begin rdoc
44
+ Exception raised when attempted to parse an invalid YAML file
45
+ =end
46
+ class InvalidSettingsFile < StandardError
47
+ end
48
+
49
+ =begin rdoc
50
+ Creates a new YamlSettingsBackend corresponding to the YAML file with name _file_.
51
+ If the file exists, it's parsed and the content is stored internally in a hash.
52
+ If it doesn't exist, the object is initialized with a new hash.
53
+
54
+ If the file exists but isn't a valid YAML file, or if it doesn't contain a toplevel
55
+ hash, InvalidSettingsFile is raised. In this case, however, the object will be
56
+ fully initialized (with an empty hash) before the exception is raised. This means
57
+ that a class deriving from YamlSettingsBackend which wants to ignore errors due to
58
+ an invalid project file can simply do the following:
59
+
60
+ class CustomSettingsBackend < Ruber::Yaml::SettingsBackend
61
+
62
+ def initialize file
63
+ begin super
64
+ rescue Ruber::YamlSettingsBackend::InvalidSettingsFile
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ This way
71
+
72
+ CustomSettingsBackend.new('/path/to/invalid_file')
73
+
74
+ will return an option backend fully initialized. (This happens because of how
75
+ Class.new works: the object is first allocated, then its initialize method is called
76
+ and the object is returned. If an exception raised by initialize is rescued within
77
+ the initialize method, Class.new will never notice something went wrong and still
78
+ return the allocated object)
79
+ =end
80
+ def initialize file
81
+ @filename = file
82
+ if File.exist? file
83
+ @data = begin YAML.load(File.read(file))
84
+ rescue ArgumentError => e
85
+ @data = {}
86
+ raise InvalidSettingsFile, e.message
87
+ end
88
+ unless @data.is_a? Hash
89
+ @data = {}
90
+ raise InvalidSettingsFile, "The file #{file} isn\'t a valid option file"
91
+ end
92
+ else @data = {}
93
+ end
94
+ end
95
+
96
+ =begin rdoc
97
+ The name of the file associated with the backend. Note that there's no warranty
98
+ the file actually exists.
99
+ =end
100
+ def file
101
+ @filename
102
+ end
103
+
104
+ =begin rdoc
105
+ Returns the option corresponding to _opt_. _opt_ is an option object with
106
+ the characteristics specified in SettingsContainer#add_option. If an option with
107
+ the same name and value of _opt_ isn't included in the internal hash, the option
108
+ default value will be returned
109
+ =end
110
+ def [] opt
111
+ grp = @data.fetch(opt.group){return opt.default.deep_copy}
112
+ grp.fetch(opt.name, opt.default)
113
+ end
114
+
115
+ =begin rdoc
116
+ Writes the options back to the YAML file (creating it if needed). _options_ is a
117
+ hash with option objects as keys and the corresponding values as entries. There
118
+ are two issues to be aware of:
119
+ * if one of the options in _options_ has a value which is equal to its default
120
+ value, it won't be written to file
121
+ * _options_ is interpreted as only containing the options which might have changed:
122
+ any option contained in the internal hash but not in _options_ is written back
123
+ to the file unchanged.
124
+
125
+ After having written the new options back to file, the internal hash is updated
126
+ =end
127
+ def write options
128
+ new_data = compute_data options
129
+ File.open(@filename, 'w'){|f| f.write YAML.dump(new_data)}
130
+ @data = new_data
131
+ end
132
+
133
+ private
134
+
135
+ =begin rdoc
136
+ Creates a hash with the options to be written to file. See +write+ for a more detailed
137
+ description of how this happens
138
+ =end
139
+ def compute_data options
140
+ new_data = Hash.new{|h, k| h[k] = {}}
141
+ removed = []
142
+ options.each_pair do |k, v|
143
+ if v != k.default then new_data[k.group][k.name] = v
144
+ else removed << [k.group, k.name]
145
+ end
146
+ end
147
+ @data.each_pair do |grp, data|
148
+ data.each_pair do |opt, val|
149
+ unless removed.include?([grp, opt])
150
+ new_data[grp][opt] ||= val
151
+ end
152
+ end
153
+ end
154
+ new_data
155
+ end
156
+
157
+ end
158
+
159
+ end