ruber 0.0.1.1

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