plist4r 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +11 -0
  3. data/LICENSE +3 -1
  4. data/README.rdoc +25 -122
  5. data/Rakefile +14 -0
  6. data/VERSION +1 -1
  7. data/bin/plist4r +2 -0
  8. data/ext/osx_plist/Makefile +157 -0
  9. data/ext/osx_plist/extconf.rb +9 -0
  10. data/ext/osx_plist/plist.c +606 -0
  11. data/ext/osx_plist/plist.o +0 -0
  12. data/lib/plist4r.rb +6 -3
  13. data/lib/plist4r/application.rb +1 -2
  14. data/lib/plist4r/backend.rb +102 -34
  15. data/lib/plist4r/backend/c_f_property_list.rb +65 -0
  16. data/lib/plist4r/backend/c_f_property_list/LICENSE +19 -0
  17. data/lib/plist4r/backend/c_f_property_list/README +34 -0
  18. data/lib/plist4r/backend/c_f_property_list/cfpropertylist.rb +6 -0
  19. data/lib/plist4r/backend/c_f_property_list/rbBinaryCFPropertyList.rb +663 -0
  20. data/lib/plist4r/backend/c_f_property_list/rbCFPlistError.rb +26 -0
  21. data/lib/plist4r/backend/c_f_property_list/rbCFPropertyList.rb +348 -0
  22. data/lib/plist4r/backend/c_f_property_list/rbCFTypes.rb +241 -0
  23. data/lib/plist4r/backend/c_f_property_list/rbXMLCFPropertyList.rb +116 -0
  24. data/lib/plist4r/backend/example.rb +37 -52
  25. data/lib/plist4r/backend/haml.rb +47 -36
  26. data/lib/plist4r/backend/libxml4r.rb +24 -20
  27. data/lib/plist4r/backend/osx_plist.rb +82 -0
  28. data/lib/plist4r/backend/ruby_cocoa.rb +172 -54
  29. data/lib/plist4r/backend/test/data_types.rb +163 -0
  30. data/lib/plist4r/backend/test/harness.rb +255 -0
  31. data/lib/plist4r/backend/test/output.rb +47 -0
  32. data/lib/plist4r/backend_base.rb +4 -2
  33. data/lib/plist4r/{options.rb → cli.rb} +2 -1
  34. data/lib/plist4r/commands.rb +13 -8
  35. data/lib/plist4r/config.rb +36 -9
  36. data/lib/plist4r/docs/Backends.html +59 -0
  37. data/lib/plist4r/docs/DeveloperGuide.rdoc +53 -0
  38. data/lib/plist4r/docs/EditingPlistFiles.rdoc +88 -0
  39. data/lib/plist4r/docs/InfoPlistExample.rdoc +33 -0
  40. data/lib/plist4r/docs/LaunchdPlistExample.rdoc +33 -0
  41. data/lib/plist4r/docs/PlistKeyNames.rdoc +47 -0
  42. data/lib/plist4r/mixin/array_dict.rb +61 -0
  43. data/lib/plist4r/mixin/data_methods.rb +178 -54
  44. data/lib/plist4r/mixin/haml4r.rb +4 -0
  45. data/lib/plist4r/mixin/haml4r/css_attributes.rb +19 -0
  46. data/lib/plist4r/mixin/haml4r/examples.rb +261 -0
  47. data/lib/plist4r/mixin/haml4r/haml_table_example.rb +79 -0
  48. data/lib/plist4r/mixin/haml4r/table.rb +157 -0
  49. data/lib/plist4r/mixin/haml4r/table_cell.rb +160 -0
  50. data/lib/plist4r/mixin/haml4r/table_cells.rb +485 -0
  51. data/lib/plist4r/mixin/haml4r/table_section.rb +101 -0
  52. data/lib/plist4r/mixin/ordered_hash.rb +9 -1
  53. data/lib/plist4r/mixin/popen4.rb +1 -1
  54. data/lib/plist4r/mixin/ruby_stdlib.rb +154 -1
  55. data/lib/plist4r/mixin/script.rb +133 -0
  56. data/lib/plist4r/mixin/table.rb +435 -0
  57. data/lib/plist4r/plist.rb +272 -94
  58. data/lib/plist4r/plist_cache.rb +42 -43
  59. data/lib/plist4r/plist_type.rb +31 -74
  60. data/lib/plist4r/plist_type/info.rb +157 -3
  61. data/lib/plist4r/plist_type/launchd.rb +54 -48
  62. data/lib/plist4r/plist_type/plist.rb +1 -3
  63. data/plist4r.gemspec +74 -14
  64. data/spec/{examples.rb → launchd_examples.rb} +131 -139
  65. data/spec/plist4r/application_spec.rb +37 -0
  66. data/spec/plist4r/backend_spec.rb +256 -0
  67. data/spec/plist4r/cli_spec.rb +25 -0
  68. data/spec/plist4r/commands_spec.rb +20 -0
  69. data/spec/plist4r/config_spec.rb +38 -0
  70. data/spec/plist4r/mixin/array_dict_spec.rb +120 -0
  71. data/spec/plist4r/mixin/data_methods_spec.rb +96 -0
  72. data/spec/plist4r/mixin/haml4r/examples.rb +261 -0
  73. data/spec/plist4r/mixin/ruby_stdlib_spec.rb +228 -0
  74. data/spec/plist4r/plist_cache_spec.rb +261 -0
  75. data/spec/plist4r/plist_spec.rb +841 -23
  76. data/spec/plist4r/plist_type_spec.rb +126 -0
  77. data/spec/plist4r_spec.rb +53 -27
  78. data/spec/scratchpad.rb +226 -0
  79. data/spec/spec_helper.rb +5 -1
  80. metadata +109 -23
  81. data/lib/plist4r/backend/plutil.rb +0 -25
  82. data/lib/plist4r/mixin.rb +0 -7
  83. data/plists/array_mini.xml +0 -14
  84. data/plists/example_big_binary.plist +0 -0
  85. data/plists/example_medium_binary_launchd.plist +0 -0
  86. data/plists/example_medium_launchd.xml +0 -53
  87. data/plists/mini.xml +0 -12
  88. data/test.rb +0 -40
@@ -0,0 +1,435 @@
1
+
2
+ require 'plist4r/mixin/ruby_stdlib'
3
+
4
+ module Plist4r
5
+
6
+ # A data type representation for tables. The underlying store is an array of arrays (2d).
7
+ # The addressing scheme is based on (column, row) order, with Range objects to specify
8
+ # the bounds of any rectangle of elements withing the table.
9
+ #
10
+ # A variety of methods are provided for manipulating the table data, including flipping,
11
+ # inserting, replacing and deleting. Operations can be column-based, row-based, or both.
12
+ class Table
13
+ class << self
14
+ # This class only understands a range addressing scheme, which is used to specify
15
+ # table locations in a [columns, rows] caresian system starting at col 0, row 0.
16
+ #
17
+ # Convert any Integer numbers into range objects. Check that all input ranges are
18
+ # positive, starting (and including) zero as the first index.
19
+ def sanitize_ranges *ranges
20
+ sanitized_ranges = []
21
+ ranges.flatten.each do |range|
22
+ case range
23
+ when Range
24
+ if range.exclude_end?
25
+ range = range.first..(range.last - 1)
26
+ end
27
+ when Integer
28
+ range = (range..range)
29
+ else
30
+ raise "Unsupported type"
31
+ end
32
+ if range.first < 0 || range.last < 0
33
+ raise "range cannot cover negative values"
34
+ end
35
+ sanitized_ranges << range
36
+ end
37
+ sanitized_ranges
38
+ end
39
+ end
40
+
41
+ # The value to assign to an empty cell
42
+ EmptyCell = nil
43
+
44
+ # The range of valid values for which match the empty cell criteria (for which the cell are ignored)
45
+ EmptyCells = [nil, false]
46
+
47
+ # When allocating the arrays for a new table, the minimum size to pad around with empty cells.
48
+ MinPadSize = 10
49
+
50
+ OptionsHash = %w[ size array pad_all fill_all ]
51
+
52
+ def initialize *args, &blk
53
+ @array = []
54
+
55
+ case args[0]
56
+ when nil
57
+ resize 0..0, 0..0
58
+
59
+ when Hash
60
+ parse_opts args[0]
61
+
62
+ when Plist4r::Table
63
+ %w[ col_range row_range array ].each do |a|
64
+ self.send a.to_sym, args[0].send(a.to_sym).deep_clone
65
+ end
66
+
67
+ else
68
+ raise "unsupported type"
69
+ end
70
+
71
+ resize 0..0, 0..0 unless @cr && @rr
72
+ end
73
+
74
+ # Sets up those valid (settable) attributes as found the options hash.
75
+ # Normally we dont call this method directly. Called from {#initialize}.
76
+ # @param [Hash <OptionsHash>] opts The options hash, containing keys of {OptionsHash}
77
+ # @see #initialize
78
+ def parse_opts opts
79
+ self.class::OptionsHash.each do |opt|
80
+ if opts[opt.to_sym]
81
+ value = opts[opt.to_sym]
82
+ eval "self.#{opt}(value)"
83
+ end
84
+ end
85
+ end
86
+
87
+ def ascii_col_width
88
+ lines = self.inspect.split "\n"
89
+ lines[0].length
90
+ end
91
+
92
+ def inspect start_col=0
93
+ cell_width = Plist4r::Table.new :size => [@cr, 0..0], :fill_all => 5
94
+ @cr.each do |col|
95
+ @rr.each do |row|
96
+ cell_size = @array[col][row].inspect.size
97
+ cell_width.cell(col, 0, cell_size) if cell_size > cell_width.cell(col,0)
98
+ end
99
+ end
100
+
101
+ col_pad_pre = " "
102
+ col_pad_post = " "
103
+
104
+ vert_border = "|"
105
+ horz_border = "-"
106
+
107
+ row_sep = ""
108
+ row_sep << " " * start_col
109
+ @cr.each do |col|
110
+ row_sep << vert_border + horz_border * (col_pad_pre.size + cell_width.cell(col,0) + col_pad_post.size)
111
+ end
112
+ row_sep << vert_border
113
+
114
+ o = ""
115
+ o << row_sep << "\n"
116
+ @rr.each do |row|
117
+ o << " " * start_col
118
+ @cr.each do |col|
119
+ cell_str = vert_border + col_pad_pre + " " * cell_width.cell(col,0) + col_pad_post
120
+ cell_str[vert_border.size+col_pad_pre.size,@array[col][row].inspect.size] = @array[col][row].inspect
121
+ o << cell_str
122
+ end
123
+ o << vert_border
124
+ o << "\n"
125
+ o << row_sep << "\n"
126
+ end
127
+ return o
128
+ end
129
+
130
+ def col_range
131
+ @cr
132
+ end
133
+
134
+ def row_range
135
+ @rr
136
+ end
137
+
138
+ def cell col, row, value=nil
139
+ raise "unsupported type" unless col.is_a?(Integer) && row.is_a?(Integer)
140
+ return nil if col < 0 || row < 0
141
+
142
+ case value
143
+ when nil
144
+ @array[col][row]
145
+ else
146
+ if value.is_a? Plist4r::Table
147
+ @array[col][row] = value.cell col, row
148
+ else
149
+ @array[col][row] = value
150
+ end
151
+ end
152
+ end
153
+
154
+ def first value=nil
155
+ cell @cr.first, @rr.first, value
156
+ end
157
+
158
+ def map col_range=nil, row_range=nil, &blk
159
+ col_range = @cr if col_range.nil? ; row_range = @rr if row_range.nil?
160
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
161
+
162
+ t = Plist4r::Table.new :size => [col_range, row_range]
163
+ row_range.each do |row|
164
+ col_range.each do |col|
165
+ t.cell col, row, yield(cell(col, row))
166
+ end
167
+ end
168
+ t
169
+ end
170
+
171
+ def each col_range=nil, row_range=nil, &blk
172
+ col_range = @cr if col_range.nil? ; row_range = @rr if row_range.nil?
173
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
174
+ row_range.each do |row|
175
+ col_range.each do |col|
176
+ yield cell(col, row)
177
+ end
178
+ end
179
+ end
180
+
181
+ def array array=nil
182
+ case array
183
+ when nil
184
+ @array
185
+ when Array
186
+ if array.multidim?
187
+ @array = array
188
+ auto_size unless @cr && @rr
189
+ else
190
+ raise "array type not supported"
191
+ end
192
+ else
193
+ raise "Unsupported type"
194
+ end
195
+ end
196
+
197
+ def size *args
198
+ if args.empty?
199
+ [@cr, @rr]
200
+ else
201
+ col_range, row_range = args.flatten
202
+ resize col_range, row_range
203
+ end
204
+ end
205
+
206
+ def resize col_range, row_range
207
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
208
+
209
+ @cr = col_range
210
+ @rr = row_range
211
+ pad @cr, @rr, EmptyCell
212
+ end
213
+
214
+ def auto_size
215
+ ce = @array.size - 1
216
+ unless @cr && (0..ce).include_range?(@cr)
217
+ @cr = 0..ce
218
+ end
219
+
220
+ re = 0
221
+ @array.each do |col|
222
+ re = col.size - 1 if col.size - 1 > re
223
+ end
224
+
225
+ unless @rr && (0..re).include_range?(@rr)
226
+ @rr = 0..re
227
+ end
228
+ end
229
+
230
+ def pad col_range, row_range, data
231
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
232
+
233
+ if EmptyCells.include? data
234
+ if col_range.end < MinPadSize - 1
235
+ col_range = 0..(MinPadSize - 1)
236
+ else
237
+ col_range = 0..(col_range.end)
238
+ end
239
+
240
+ if row_range.end < MinPadSize - 1
241
+ row_range = 0..(MinPadSize - 1)
242
+ else
243
+ row_range = 0..(row_range.end)
244
+ end
245
+ end
246
+
247
+ (col_range.end - @array.size + 1).times do
248
+ @array << []
249
+ end
250
+
251
+ col_range.each do |col|
252
+ row_range.each do |row|
253
+ @array[col][row] = data.deep_clone if EmptyCells.include? @array[col][row]
254
+ end
255
+ end
256
+ end
257
+
258
+ def pad_all data
259
+ pad @cr, @rr, data
260
+ end
261
+
262
+ def crop col_range, row_range
263
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
264
+
265
+ pad col_range, row_range, EmptyCell
266
+ crop_obj = self.class.new :size => [0..(col_range.size - 1), 0..(row_range.size - 1)]
267
+
268
+ col_range.each do |col|
269
+ row_range.each do |row|
270
+ crop_obj.array[col-col_range.first][row-row_range.first] = @array[col][row].deep_clone
271
+ end
272
+ end
273
+ crop_obj
274
+ end
275
+
276
+ def fill col_range, row_range, data=nil
277
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
278
+ data = EmptyCell unless data
279
+
280
+ pad col_range, row_range, EmptyCell
281
+ col_range.each do |c|
282
+ row_range.each do |r|
283
+ @array[c][r] = data.deep_clone
284
+ end
285
+ end
286
+ end
287
+
288
+ def fill_all data
289
+ fill @cr, @rr, data
290
+ end
291
+
292
+ def inverse_fill col_range, row_range, data=nil
293
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
294
+ data = EmptyCell unless data
295
+
296
+ crop_obj = crop col_range, row_range
297
+ fill @cr, @rr, data
298
+ replace col_range, row_range, crop_obj
299
+ end
300
+
301
+ def transpose col_range=nil, row_range=nil, keep_bounds=false
302
+ col_range = @cr if col_range.nil? ; row_range = @rr if row_range.nil?
303
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
304
+
305
+ if col_range == @cr && row_range == @rr && @cr.first == @rr.first
306
+ @array = @array.transpose
307
+ if keep_bounds
308
+ @cr,@rr = [[@cr,@rr].min,[@cr,@rr].min]
309
+ else
310
+ @cr,@rr = [@rr, @cr]
311
+ end
312
+ self
313
+ else
314
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
315
+
316
+ crop_obj = crop col_range, row_range
317
+ crop_obj.transpose
318
+ fill col_range, row_range, EmptyCell
319
+
320
+ ocr,orr = [col_range,row_range]
321
+ col_range = (ocr.begin)..(ocr.begin+orr.size-1)
322
+ row_range = (orr.begin)..(orr.begin+ocr.size-1)
323
+
324
+ if keep_bounds && col_range.size != row_range.size
325
+ if ocr.size > orr.size
326
+ row_range = (row_range.begin)..(orr.end)
327
+ else
328
+ col_range = (col_range.begin)..(ocr.end)
329
+ end
330
+ end
331
+ replace col_range, row_range, crop_obj
332
+ end
333
+ self
334
+ end
335
+
336
+ def data_replace col_range, row_range, other_data
337
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
338
+
339
+ self.pad col_range, row_range, EmptyCell
340
+ o = other_data.deep_clone
341
+
342
+ if o.col_range.size < col_range.size
343
+ col_range = (col_range.first)..(row_range.first + o.col_range.size - 1)
344
+ end
345
+
346
+ if o.row_range.size < row_range.size
347
+ row_range = (row_range.first)..(row_range.first + o.row_range.size - 1)
348
+ end
349
+
350
+ col_range.each do |col|
351
+ row_range.each do |row|
352
+ # @array[col][row] = o.array[col-col_range.first+o.col_range.first][row-row_range.first+o.row_range.first].deep_clone
353
+ cell col, row, o.cell(col-col_range.first+o.col_range.first,row-row_range.first+o.row_range.first).deep_clone
354
+ end
355
+ end
356
+ end
357
+
358
+ def replace col_range, row_range, data
359
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
360
+
361
+ case data
362
+ when Plist4r::Table
363
+ data_replace col_range, row_range, data
364
+ else
365
+ fill col_range, row_range, data
366
+ end
367
+ self
368
+ end
369
+
370
+ def col_replace col_range, data
371
+ col_range = Plist4r::Table.sanitize_ranges col_range
372
+ replace col_range, @rr.first..@rr.last, data
373
+ end
374
+
375
+ def row_replace row_range, data
376
+ row_range = Plist4r::Table.sanitize_ranges row_range
377
+ replace @cr.first..@cr.last, row_range, data
378
+ end
379
+
380
+ def translate col_range, row_range, vector
381
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
382
+
383
+ crop_obj = crop col_range, row_range
384
+ fill col_range, row_range, EmptyCell
385
+ col_range = (col_range.begin+vector[0])..(col_range.end+vector[0])
386
+ row_range = (row_range.begin+vector[1])..(row_range.end+vector[1])
387
+ replace col_range, row_range, crop_obj
388
+ end
389
+
390
+ def col_insert col_range, row_range, other_data, resize=true
391
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
392
+ translate col_range.first..@cr.last, @rr, [col_range.size, 0]
393
+ replace col_range, row_range, other_data
394
+ @cr = @cr.first..(@cr.last + col_range.size) if resize
395
+ end
396
+
397
+ def row_insert col_range, row_range, other_data, resize=true
398
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
399
+ translate @cr, row_range.first..@rr.last, [0, row_range.size]
400
+ replace col_range, row_range, other_data
401
+ @rr = @rr.first..(@rr.last + row_range.size) if resize
402
+ end
403
+
404
+ def cells col_range=nil, row_range=nil, value=nil
405
+ col_range = @cr if col_range.nil? ; row_range = @rr if row_range.nil?
406
+ col_range, row_range = Plist4r::Table.sanitize_ranges col_range, row_range
407
+
408
+ replace col_range, row_range, value if value
409
+ self.class.new :array => @array, :size => [col_range, row_range]
410
+ end
411
+
412
+ def col col_range, value=nil
413
+ col_range = Plist4r::Table.sanitize_ranges col_range
414
+
415
+ case value
416
+ when nil
417
+ cells col_range, @rr
418
+ else
419
+ col_replace col_range, value
420
+ end
421
+ end
422
+
423
+ def row row_range, value=nil
424
+ row_range = Plist4r::Table.sanitize_ranges row_range
425
+
426
+ case value
427
+ when nil
428
+ cells @cr, row_range
429
+ else
430
+ row_replace row_range, value
431
+ end
432
+ end
433
+
434
+ end
435
+ end
data/lib/plist4r/plist.rb CHANGED
@@ -3,18 +3,20 @@ require 'plist4r/mixin/ordered_hash'
3
3
  require 'plist4r/mixin/ruby_stdlib'
4
4
  require 'plist4r/plist_cache'
5
5
  require 'plist4r/plist_type'
6
- Dir.glob(File.dirname(__FILE__) + "/plist_type/**/*.rb").each {|t| require File.expand_path t}
7
- require 'plist4r/backend'
6
+ Dir.glob(File.dirname(__FILE__) + "/plist_type/*.rb").each {|t| require File.expand_path t}
7
+ # require 'plist4r/backend'
8
8
 
9
9
  module Plist4r
10
+ # See {file:README} and {file:InfoPlistExample} for usage examples. Also see {file:EditingPlistFiles}
10
11
  class Plist
11
12
  # Recognised keys of the options hash. Passed when instantiating a new Plist Object
12
13
  # @see #initialize
13
14
  # @see #parse_opts
14
- PlistOptionsHash = %w[filename path file_format plist_type strict_keys backends from_string]
15
- # The plist file formats, written as symbols.
15
+ OptionsHash = %w[filename path file_format plist_type strict_keys backends from_string]
16
+
17
+ # The plist file formats, written as symbols.
16
18
  # @see #file_format
17
- FileFormats = %w[binary xml next_step]
19
+ FileFormats = %w[binary xml gnustep]
18
20
 
19
21
  # Instantiate a new Plist4r::Plist object. We usually set our per-application defaults in {Plist4r::Config} beforehand.
20
22
  #
@@ -33,9 +35,10 @@ module Plist4r
33
35
  # Plist4r::Plist.new({ :filename => "example.plist", :path => plist_working_dir, :backends => ["libxml4r","ruby_cocoa"]})
34
36
  # => #<Plist4r::Plist:0x111546c @file_format=nil, ...>
35
37
  # @return [Plist4r::Plist] The new Plist object
38
+ # @yield An optional block to instance_eval &blk, and apply an edit on creation
36
39
  def initialize *args, &blk
37
40
  @hash = ::Plist4r::OrderedHash.new
38
- @plist_type = plist_type :plist
41
+ plist_type :plist
39
42
 
40
43
  @strict_keys = Config[:strict_keys]
41
44
  @backends = Config[:backends]
@@ -57,6 +60,8 @@ module Plist4r
57
60
  end
58
61
 
59
62
  @plist_cache ||= PlistCache.new self
63
+
64
+ edit(&blk) if block_given?
60
65
  end
61
66
 
62
67
  # Reinitialize plist object from string (overwrites the current contents). Usually called from {Plist#initialize}
@@ -64,7 +69,7 @@ module Plist4r
64
69
  # plist = Plist4r::Plist.new
65
70
  # => #<Plist4r::Plist:0x11e161c @file_format=nil, ...>
66
71
  # plist.from_string "{ \"key1\" = \"value1\"; \"key2\" = \"value2\"; }"
67
- # => #<Plist4r::Plist:0x11e161c @file_format="next_step", ...>
72
+ # => #<Plist4r::Plist:0x11e161c @file_format="gnustep", ...>
68
73
  def from_string string=nil
69
74
  case string
70
75
  when String
@@ -83,7 +88,7 @@ module Plist4r
83
88
  end
84
89
  end
85
90
 
86
- # Set or return the filename attribute of the plist object.
91
+ # Set or return the filename attribute of the plist object. Used in cojunction with the {#path} attribute
87
92
  # @param [String] filename either a relative path or absolute
88
93
  # @return The plist's filename
89
94
  # @see Plist::Plist#open
@@ -122,7 +127,7 @@ module Plist4r
122
127
  # @see filename
123
128
  # @see path
124
129
  def filename_path filename_path=nil
125
- case path
130
+ case filename_path
126
131
  when String
127
132
  @filename = File.basename filename_path
128
133
  @path = File.dirname filename_path
@@ -135,7 +140,7 @@ module Plist4r
135
140
 
136
141
  # The file format of the plist file we are loading / saving. Written as a symbol.
137
142
  # One of {Plist4r::Plist.FileFormats}. Defaults to :xml
138
- # @param [Symbol, String] file_format Can be :binary, :xml, :next_step
143
+ # @param [Symbol, String] file_format Can be :binary, :xml, :gnustep
139
144
  # @return The file format associated to this current plist object
140
145
  # @see Plist4r::Plist.FileFormats
141
146
  def file_format file_format=nil
@@ -157,7 +162,7 @@ module Plist4r
157
162
  # using an algorithm that stats the plist data. The plist types with the highest stat (score)
158
163
  # is chosen to be the object's "Plist Type".
159
164
  # @see Plist4r::PlistType
160
- # @return [true] always
165
+ # @return The plist's known type, written as a symbol. Will be a sublcass of Plist4r::PlistType. Defaults to :plist
161
166
  def detect_plist_type
162
167
  stat_m = {}
163
168
  stat_r = {}
@@ -167,6 +172,7 @@ module Plist4r
167
172
  t = eval "::Plist4r::PlistType::#{t.to_s.camelcase}"
168
173
  when Class
169
174
  t = t
175
+ when nil
170
176
  else
171
177
  raise "Unrecognized plist type: #{t.inspect}"
172
178
  end
@@ -190,7 +196,6 @@ module Plist4r
190
196
  else
191
197
  plist_type stat_m[most_matches]
192
198
  end
193
- return true
194
199
  end
195
200
 
196
201
  # Set or return the plist_type of the current object. We can use this to override the automatic type detection.
@@ -202,7 +207,8 @@ module Plist4r
202
207
  begin
203
208
  case plist_type
204
209
  when Class
205
- unless plist_type.is_a? ::Plist4r::PlistType
210
+ # unless plist_type.is_a? ::Plist4r::PlistType # .is_a? returns false in spec
211
+ unless plist_type.ancestors.include? Plist4r::PlistType
206
212
  raise "Unrecognized Plist type. Class #{plist_type.inspect} isnt inherited from ::Plist4r::PlistType"
207
213
  end
208
214
  when Symbol, String
@@ -236,16 +242,26 @@ module Plist4r
236
242
 
237
243
  # An array of strings, symbols or class names which correspond to the active Plist4r::Backends for this object.
238
244
  # The priority order in which backends are executed is determined by the in sequence array order.
239
- # @param [Array] backends Inherited from {Plist4r::Config}[:backends]
240
- # @return The backends for this object
241
- # @example Execute haml before resorting to RubyCocoa
245
+ # @param [Array <Symbol,String>] A new list of backends to use, in Priority order
246
+ # @return [Array <Symbol>] The plist's backends, each written as a symbol. Must be a sublcass of Plist4r::Backend
247
+ # Defaults to {Plist4r::Config}[:backends]
248
+ # @example
242
249
  # plist.backends [:haml, :ruby_cocoa]
243
250
  # @see Plist4r::Backend
244
251
  # @see Plist4r::Backend::Example
245
252
  def backends backends=nil
246
253
  case backends
247
254
  when Array
248
- @backends = backends
255
+ @backends = backends.collect do |b|
256
+ case b
257
+ when Symbol, String
258
+ eval "Plist4r::Backend::#{b.to_s.camelcase}"
259
+ b.to_sym
260
+ when nil
261
+ else
262
+ raise "Backend #{b.inspect} is of unsupported type: #{b.class}"
263
+ end
264
+ end
249
265
  when nil
250
266
  @backends
251
267
  else
@@ -254,21 +270,21 @@ module Plist4r
254
270
  end
255
271
 
256
272
  # Sets up those valid (settable) plist attributes as found the options hash.
257
- # Normally we dont call this method ourselves. Called from {#initialize}.
258
- # @param [Hash <PlistOptionsHash>] opts The options hash, containing keys of {PlistOptionsHash}
273
+ # Normally we dont call this method directly. Called from {#initialize}.
274
+ # @param [Hash <OptionsHash>] opts The options hash, containing keys of {OptionsHash}
259
275
  # @see #initialize
260
276
  def parse_opts opts
261
- PlistOptionsHash.each do |opt|
277
+ OptionsHash.each do |opt|
262
278
  if opts[opt.to_sym]
263
279
  value = opts[opt.to_sym]
264
- eval "self.#{opt}(value)"
280
+ self.send opt, value
265
281
  end
266
282
  end
267
283
  end
268
284
 
269
285
  # Opens a plist file
270
286
  #
271
- # @param [String] filename plist file to load. Uses the @filename attribute when nil
287
+ # @param [String] filename plist file to load. Uses the {#filename} attribute when nil
272
288
  # @return [Plist4r::Plist] The loaded Plist object
273
289
  # @example Load from file
274
290
  # plist = Plist4r.new
@@ -282,7 +298,7 @@ module Plist4r
282
298
  # An alias of {#edit}
283
299
  # @example
284
300
  # plist.<< do
285
- # set "PFReleaseVersion" "0.1.1"
301
+ # store "PFReleaseVersion" "0.1.1"
286
302
  # end
287
303
  # @see #edit
288
304
  def << *args, &blk
@@ -291,36 +307,35 @@ module Plist4r
291
307
 
292
308
  # Edit a plist object. Set or return plist keys. Add or remove a selection of keys.
293
309
  # Plist key accessor methods are snake-cased versions of the key string.
294
- # @example Set some arbitrary keys and values via {DataMethods#set}
310
+ # @example Edit some keys and values with {#[]} and {#store}
295
311
  # plist.edit do
296
- # set "PFInstance" "4982394823"
297
- # set "PFReleaseVersion" "0.1.1"
312
+ # store "PFInstance" "4982394823"
313
+ # store "PFReleaseVersion" "0.1.1"
298
314
  # end
299
- # @example Retrieve, and modify a plist key, value pair, with {DataMethods#set} and {DataMethods#value_of}
315
+ #
300
316
  # plist.edit do
301
- # new_ver = value_of("PFReleaseVersion") + 0.1
302
- # set "PFReleaseVersion" new_ver
317
+ # new_ver = self["PFReleaseVersion"] + 0.1
318
+ # store "PFReleaseVersion" new_ver
303
319
  # end
304
- # @example Modify a Launchd plist, by camel-cased accessor methods
320
+ # @example Edit with implicit methods. Calls method_missing()
305
321
  # plist.edit do
306
- # new_ver = value_of("PFReleaseVersion") + 0.1
307
- # set "PFReleaseVersion" new_ver
322
+ # new_ver = p_f_release_version + 0.1
323
+ # p_f_release_version(new_ver)
308
324
  # end
309
325
  def edit *args, &blk
310
326
  @plist_type.hash @hash
311
327
  instance_eval *args, &blk
312
- detect_plist_type
313
- @plist_cache.update_checksum
328
+ detect_plist_type if plist_type == :plist
314
329
  end
315
330
 
316
331
  # Pass down unknown method calls to the selected plist_type, to set or return plist keys.
317
332
  # All plist data manipulation API is called through method_missing -> PlistType -> DataMethods.
318
- # @example This will actually call {DataMethods#set}
319
- # plist.set "CFBundleVersion" "0.1.0"
333
+ # @example This will actually call {DataMethods#method_missing}
334
+ # plist.store "CFBundleVersion" "0.1.0"
320
335
  # @see Plist4r::DataMethods#method_missing
321
336
  # @see #plist_type
322
337
  def method_missing method_sym, *args, &blk
323
- @plist_type.send method_sym, *args, &blk
338
+ @plist_type.method_missing method_sym, *args, &blk
324
339
  end
325
340
 
326
341
  # Backend method to set or return all new plist data resulting from a backend API. Used in load operations.
@@ -337,11 +352,204 @@ module Plist4r
337
352
  raise "Please use ::Plist4r::OrderedHash.new for your hashes"
338
353
  end
339
354
  end
340
-
341
- # The primary storage object for plist data
355
+
356
+ # Element Reference Retrieve the value object corresponding to the key object. If not found, returns nil
357
+ # @param [Symbol, String] key The plist key name, either a snake-cased symbol, or literal string
358
+ # @return The value associated with the plist key
359
+ # @example
360
+ # plist["CFBundleIdentifier"] # => "com.apple.myapp"
361
+ # @example
362
+ # plist[:c_f_bundle_identifier] # => "com.apple.myapp"
363
+ def [] key
364
+ @plist_type.set_or_return key
365
+ end
366
+
367
+
368
+ # Element Assignment — Assign a value to the given plist key
369
+ # @param [Symbol, String] key The plist key name, either a snake-cased symbol, or literal string
370
+ # @param value The value to store under the plist key name
371
+ # @example
372
+ # plist["CFBundleIdentifier"] = "com.apple.myapp"
373
+ # @example
374
+ # plist[:c_f_bundle_identifier] = "com.apple.myapp"
375
+ def []= key, value
376
+ store key, value
377
+ end
378
+
379
+ # Element Assignment — Assign a value to the given plist key
380
+ # @param [Symbol, String] key The plist key name, either a snake-cased symbol, or literal string
381
+ # @param value The value to store under the plist key name
382
+ # @example
383
+ # plist.store "CFBundleIdentifier", "com.apple.myapp"
384
+ # @example
385
+ # plist.store :c_f_bundle_identifier, "com.apple.myapp"
386
+ def store key, value
387
+ @plist_type.set_or_return key, value
388
+ end
389
+
390
+ # Element selection - Keep selected plist keys and discard others.
391
+ # @param [Array, *args] keys List of Plist Keys to keep. Can be an array, or method argument list
392
+ # @yield Keep every key-value pair for which the passed block evaluates to true. Works as per the ruby core classes Hash#select method
393
+ def select *keys, &blk
394
+ if block_given?
395
+ a = @hash.select &blk
396
+ old_hash = @hash.deep_clone
397
+ clear
398
+ a.each do |pair|
399
+ store pair[0], pair[1]
400
+ end
401
+ keys.each do |k|
402
+ store k, old_hash[k]
403
+ end
404
+ else
405
+ @plist_type.array_dict :select, *keys
406
+ end
407
+ end
408
+
409
+ # Invokes block &blk once for each key-value pair in plist. Similar to the ruby core classes Array#map.
410
+ # Replaces the plist keys and values with the [key,value] pairs returned by &blk.
411
+ # @yield For each iteration of the block, must return a 2-element Array which is a [key,value] pair to replace the original [key,value] pair from the plist.
412
+ # Key names can be given as either snake_case'd Symbol or camelcased String
413
+ def map &blk
414
+ if block_given?
415
+ old_hash = @hash.deep_clone
416
+ clear
417
+
418
+ old_hash.each do |k,v|
419
+ pair = yield k,v
420
+ case pair
421
+ when Array
422
+ store pair[0], pair[1]
423
+ when nil
424
+ else
425
+ raise "The supplied block must return plist [key, value] pairs, or nil"
426
+ end
427
+ end
428
+ else
429
+ raise "No block given"
430
+ end
431
+ end
432
+
433
+ # Alias for {#map}
434
+ def collect &blk
435
+ map &blk
436
+ end
437
+
438
+ # Alias for {#delete}
439
+ def unselect *keys
440
+ delete *keys
441
+ end
442
+
443
+ # Delete plist keys from the object.
444
+ # @param [Array, *args] keys The list of Plist Keys to delete unconditionally. Can be an array, or argument list
445
+ # Key names can be given as either snake_case'd Symbol or camelcased String
446
+ # @example
447
+ # plist.delete :c_f_bundle_identifier
448
+ def delete *keys
449
+ @plist_type.array_dict :unselect, *keys
450
+ end
451
+
452
+ # Conditionally delete plist keys from the object.
453
+ # @param [Array, *args] keys The list of Plist Keys to delete unconditionally. Can be an array, or argument list
454
+ # @yield Delete a key-value pair if block evaluates to true.
455
+ # @example
456
+ # plist.delete_if "CFBundleIdentifier"
457
+ # @example
458
+ # plist.delete_if { |k,v| k.length > 20 }
459
+ # @example
460
+ # plist.delete_if { |k,v| k =~ /Identifier/ }
461
+ def delete_if *keys, &blk
462
+ delete *keys
463
+ @hash.delete_if &blk
464
+ @plist_type.hash @hash
465
+ end
466
+
467
+ # Clears all plist keys and their contents
468
+ # @example
469
+ # plist.clear
470
+ # plist.size # => 0
471
+ def clear
472
+ @plist_type.array_dict :unselect_all
473
+ end
474
+
475
+ # Merge together plist objects.
476
+ # Adds the contents of other_plist to the current object, overwriting any entries of the same key name with those from other_plist.
477
+ # Other attributes (filename, plist_type, file_format, etc) remain unaffected
478
+ # @param [Plist4r::Plist] other_plist The other plist to merge with
479
+ def merge! other_plist
480
+ if plist_type == other_plist.plist_type
481
+ @hash.merge! other_plist.to_hash
482
+ @plist_type.hash @hash
483
+ else
484
+ raise "plist_type differs, one is #{plist_type.inspect}, and the other is #{plist.plist_type.inspect}"
485
+ end
486
+ self
487
+ end
488
+
489
+ # Check if key exists in plist
490
+ # This is equivalent to the ruby core classes method Hash#include?
491
+ # @param [String, Symbol] key The plist key name
492
+ # @return [true,false] True if the plist has the specified key
493
+ def include? key
494
+ key.to_s.camelcase if key.class == Symbol
495
+ @hash.include? key
496
+ end
497
+
498
+ # Alias of {#include?}
499
+ # @param [String, Symbol] key The plist key name
500
+ # @return [true,false] True if the plist has the specified key
501
+ def has_key? key
502
+ key.to_s.camelcase if key.class == Symbol
503
+ @hash.has_key? key
504
+ end
505
+
506
+ # This is equivalent to the ruby core classes method Array#empty?
507
+ def empty?
508
+ @hash.empty?
509
+ end
510
+
511
+ # This is equivalent to the ruby core classes method Hash#each
512
+ # @yield A block to execute for each key, value pair in plist
513
+ # @example
514
+ # plist.each do |k,v|
515
+ # puts "key = #{k.inspect}, value = #{v.inspect}"
516
+ # end
517
+ def each &blk
518
+ @hash.each &blk
519
+ end
520
+
521
+ # This is equivalent to the ruby core classes method Array#length
522
+ # @example
523
+ # plist.length # => 14
524
+ def length
525
+ @hash.length
526
+ end
527
+
528
+ # This is equivalent to the ruby core classes method Array#size
529
+ # plist.size # => 14
530
+ def size
531
+ @hash.size
532
+ end
533
+
534
+ # This is equivalent to the ruby core classes method Hash#keys
535
+ # @example
536
+ # plist.keys # => ["Key1", "Key2", "Key3", "etc.."]
537
+ # @return [Array <String, Symbol>] The keys of the plist
538
+ def keys
539
+ @hash.keys
540
+ end
541
+
542
+ # The internal data storage object for the plist data
543
+ #
544
+ # This is a pretty standard (either ActiveSupport or Ruby 1.9) ordered hash.
545
+ # Key names - regular ruby strings of arbitrary length.
342
546
  #
343
- # @return [::Plist4r::OrderedHash] Nested hash of ruby objects. The raw Plist data
344
- # @see ::Plist4r::OrderedHash
547
+ # Values - Must only store generic Ruby objects data such as TrueClass, FalseClass, Integer, Float, String, Time, Array, Hash, and Data
548
+ #
549
+ # Data (NSData / CFData) - see {file:EditingPlistFiles}
550
+ #
551
+ # @return [Plist4r::OrderedHash] Nested hash of ruby objects. The raw Plist data
552
+ # @see Plist4r::OrderedHash
345
553
  # @example
346
554
  # plist = "{ \"key1\" = \"value1\"; \"key2\" = \"value2\"; }".to_plist
347
555
  # plist.to_hash => {"key1"=>"value1", "key2"=>"value2"}
@@ -349,7 +557,17 @@ module Plist4r
349
557
  @hash
350
558
  end
351
559
 
352
- # Calls out to the plist cache
560
+ # # The internal ruby data representation for the plist data.
561
+ # # This is a pretty standard (either ActiveSupport or Ruby 1.9) ordered hash.
562
+ # # Key names - regular ruby strings of arbitrary length.
563
+ # # Values - Must only store generic Ruby objects data such as TrueClass, FalseClass, Integer, Float, String, Time, Array, Hash, and Data
564
+ # # @return [Plist4r::OrderedHash] Nested hash of ruby objects. The raw Plist data
565
+ # def hash
566
+ # @hash
567
+ # end
568
+
569
+ # Export the plist to xml string representation.
570
+ # Calls through the plist cache
353
571
  #
354
572
  # @return [String] An xml string which represents the entire plist, as would be the plist xml file
355
573
  # @example
@@ -359,8 +577,12 @@ module Plist4r
359
577
  def to_xml
360
578
  @plist_cache.to_xml
361
579
  end
362
-
363
- # @example
580
+
581
+ # Write out a binary string representation of the plist
582
+ #
583
+ # Looking for how to store a bytestream in CFData / NSData? See {file:EditingPlistFiles}
584
+ #
585
+ # @example
364
586
  # plist = "{ \"key1\" = \"value1\"; \"key2\" = \"value2\"; }".to_plist
365
587
  # plist.to_binary
366
588
  # => "bplist00\322\001\002\003\004Tkey2Tkey1Vvalue2Vvalue1\b\r\022\027\036\000\000\000\000\000\000\001\001\000\000\000\000\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000%"
@@ -368,23 +590,21 @@ module Plist4r
368
590
  @plist_cache.to_binary
369
591
  end
370
592
 
371
- # @todo Needs a backend
372
- # We are missing a backend for writing out :next_step strings and saving :next_step files to disk
373
- def to_next_step
374
- @plist_cache.to_next_step
593
+ # We are missing a backend for writing out plist strings in Gnustep / Nextstep / Openstep format. Contributions appreciated.
594
+ def to_gnustep
595
+ @plist_cache.to_gnustep
375
596
  end
376
597
 
377
- # Save plist to #filename
598
+ # Save plist to {#filename_path}
378
599
  # @raise [RuntimeError] if the {#filename} attribute is nil
379
- # @see #filename
380
- # @see #path
600
+ # @see #filename_path
381
601
  def save
382
602
  raise "No filename specified" unless @filename
383
603
  @plist_cache.save
384
604
  end
385
605
 
386
606
  # Save the plist under a new filename
387
- # @param [String] filename The new file name to save as. If a relative path, will be concactenated to plist.path
607
+ # @param [String] filename The new file name to save as. If relative, will be appended to {#path}
388
608
  # @see #save
389
609
  def save_as filename
390
610
  @filename = filename
@@ -393,47 +613,5 @@ module Plist4r
393
613
  end
394
614
  end
395
615
 
396
- module Plist4r
397
- # @private
398
- class OldPlist
399
-
400
- def initialize path_prefix, plist_str, &blk
401
- plist_str << ".plist" unless plist_str =~ /\.plist$/
402
-
403
- @filename = nil
404
- if plist_str =~ /^\//
405
- @filename = plist_str
406
- else
407
- @filename = "#{path_prefix}/#{plist_str}"
408
- end
409
-
410
- @label = @filename.match(/^.*\/(.*)\.plist$/)[1]
411
- @shortname = @filename.match(/^.*\.(.*)$/)[1]
412
-
413
- @block = blk
414
- @hash = @orig = ::Plist4r::OrderedHash.new
415
-
416
- instance_eval(&@block) if @block
417
- end
418
-
419
- def override_plist_keys?
420
- return true unless @label == @filename.match(/^.*\/(.*)\.plist$/)[1]
421
- vars = self.instance_variables - ["@filename","@label","@shortname","@block","@hash","@obj"]
422
- return true unless vars.empty?
423
- end
424
-
425
- def finalize
426
- if File.exists? @filename
427
- if override_plist_keys?
428
- # @hash = @obj = ::LibxmlLaunchdPlistParser.new(@filename).plist_struct
429
- # eval_plist_block(&@block) if @block
430
- # write_plist
431
- end
432
- else
433
- # write_plist
434
- end
435
- end
436
- end
437
- end
438
616
 
439
617