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,101 @@
1
+
2
+ require 'plist4r/mixin/haml4r/table_cells'
3
+
4
+ module Haml4r
5
+
6
+ # A TableSection is a container object. Its responsibility is to forward messages to
7
+ # the TableCells instance object. TableSection also holds css attributes for that
8
+ # sub-section (if applicable, these will be applied to thead, and tbody html tags).
9
+ #
10
+ # The TableSection contains other instance objects.
11
+ #
12
+ # :----------------------------------------------------:
13
+ # | |
14
+ # | :--------------------------------------------: |
15
+ # | | | |
16
+ # | | :-------------------:------------------: | |
17
+ # | | | | | | |
18
+ # | | | | | | |
19
+ # | | | TableCell | TableCell | | |
20
+ # | | | | | | |
21
+ # | | | | | | |
22
+ # | | :-------------------:------------------: | |
23
+ # | | | | | | |
24
+ # | | | | | | |
25
+ # | | | TableCell | TableCell | | |
26
+ # | | | | | | |
27
+ # | | | | | | |
28
+ # | | :- @array ----------:------------------: | |
29
+ # | | TableCells | |
30
+ # | :- @cells -----------------------------------: |
31
+ # | TableSection |
32
+ # :----------------------------------------------------:
33
+ #
34
+ class TableSection
35
+
36
+ def haml
37
+ @haml ||= <<-'EOC'
38
+ %table
39
+ %tbody{:class => self.css_class, :id => self.css_id, :style => self.css_style}
40
+ - @cells.row_range.each do |row|
41
+ %tr
42
+ - @cells.col_range.each do |col|
43
+ - c = @cells.cell col, row
44
+ - unless c.spanee
45
+ %td{:class => c.css_class, :id => c.css_id, :style => c.css_style, :colspan => @cells.colspan(col,row), :rowspan => @cells.rowspan(col,row)} #{c.content}
46
+ EOC
47
+ end
48
+
49
+ def to_s
50
+ require 'haml'
51
+ engine = ::Haml::Engine.new self.haml
52
+ rendered_html_output = engine.render self
53
+ end
54
+
55
+ Attributes = Haml4r::CssAttributes
56
+
57
+ def initialize *args, &blk
58
+ @cells ||= TableCells.new *args, &blk
59
+ end
60
+
61
+ def method_missing method_sym, *args, &blk
62
+ if Attributes.include? method_sym.to_s.chomp('=')
63
+ set_or_return method_sym.to_s, *args, &blk
64
+
65
+ elsif @cells.respond_to? method_sym
66
+ @cells.send method_sym, *args, &blk
67
+
68
+ else
69
+ super
70
+ end
71
+ end
72
+
73
+ def respond_to? method_sym
74
+ return true if Attributes.include? method_sym.to_s.chomp('=')
75
+ return true if @cells.respond_to? method_sym
76
+ super
77
+ end
78
+
79
+ def set attribute, value
80
+ eval "@#{attribute} = value"
81
+ end
82
+
83
+ def value_for attribute
84
+ eval "@#{attribute}"
85
+ end
86
+
87
+ def set_or_return attribute, value=nil
88
+ case attribute
89
+ when /\=$/
90
+ set attribute.to_s.chomp('='), value
91
+ else
92
+ value_for attribute.to_s
93
+ end
94
+ end
95
+
96
+ def inspect start_col=0
97
+ @cells.inspect start_col
98
+ end
99
+ end
100
+
101
+ end
@@ -6,8 +6,11 @@ module Plist4r
6
6
  # {ActiveSupport::OrderedHash}
7
7
  #
8
8
  # Copyright (c) 2005 David Hansson,
9
+ #
9
10
  # Copyright (c) 2007 Mauricio Fernandez, Sam Stephenson
11
+ #
10
12
  # Copyright (c) 2008 Steve Purcell, Josh Peek
13
+ #
11
14
  # Copyright (c) 2009 Christoffer Sawicki
12
15
  #
13
16
  # Permission is hereby granted, free of charge, to any person obtaining
@@ -65,6 +68,11 @@ module Plist4r
65
68
  @keys = other.keys
66
69
  end
67
70
 
71
+ def store(key, value)
72
+ @keys << key if !has_key?(key)
73
+ super
74
+ end
75
+
68
76
  def []=(key, value)
69
77
  @keys << key if !has_key?(key)
70
78
  super
@@ -77,7 +85,7 @@ module Plist4r
77
85
  end
78
86
  super
79
87
  end
80
-
88
+
81
89
  def delete_if
82
90
  super
83
91
  sync_keys!
@@ -15,7 +15,7 @@ module Plist4r
15
15
  #
16
16
  # Don't use the "Block form" calling method. It screws up on the pipes IO.
17
17
  #
18
- # Use "Simple form", always. Simple form == more robust IO handling.
18
+ # Use "Simple form", always. Simple form = more robust IO handling.
19
19
  #
20
20
  # @example Simple form
21
21
  # def popen4_exec stdin_str, cmd, *args
@@ -16,14 +16,158 @@ class Object
16
16
  end
17
17
  nil
18
18
  end
19
+
20
+ # Make a deep copy of an object. Including a deep copy of all the object's instance data.
21
+ # @example
22
+ # copy_of_obj = obj.deep_clone
23
+ # @return [Object] A new copy of the object
24
+ def deep_clone; Marshal::load(Marshal.dump(self)); end
25
+ end
26
+
27
+ class Array
28
+ # And array is considered multi-dimensional if all of the first-order elements are also arrays.
29
+ # @example
30
+ # [[1],[2],[3]].multidim?
31
+ # => true
32
+ #
33
+ # [[1],2,[3]].multidim?
34
+ # => false
35
+ # @return [true,false] true for a Multi-Dimensional array, false otherwise
36
+ def multidim?
37
+ case self.size
38
+ when 0
39
+ false
40
+ else
41
+ each do |e|
42
+ return false unless e.class == Array
43
+ end
44
+ true
45
+ end
46
+ end
47
+
48
+ # Converts an array of values (which must respond to #succ) to an array of ranges. For example,
49
+ # @example
50
+ # [3,4,5,1,6,9,8].to_ranges => [1,3..6,8..9]
51
+ def to_ranges
52
+ array = self.compact.uniq.sort
53
+ ranges = []
54
+ if !array.empty?
55
+ # Initialize the left and right endpoints of the range
56
+ left, right = array.first, nil
57
+ array.each do |obj|
58
+ # If the right endpoint is set and obj is not equal to right's successor
59
+ # then we need to create a range.
60
+ if right && obj != right.succ
61
+ ranges << Range.new(left,right)
62
+ left = obj
63
+ end
64
+ right = obj
65
+ end
66
+ ranges << Range.new(left,right)
67
+ end
68
+ ranges
69
+ end
70
+ end
71
+
72
+ class Hash
73
+ def merge_array_of_hashes_of_arrays array_of_hashes_of_arrays
74
+ a = array_of_hashes_of_arrays
75
+ raise "not an array_of_hashes_of_arrays" unless a.is_a? Array
76
+ if a[0].is_a? Hash
77
+ h = self.deep_clone
78
+ a.each_index do |i|
79
+ raise "not an array_of_hashes_of_arrays" unless a[i].is_a? Hash
80
+ a[i].each do |k,v|
81
+ raise "not an array_of_hashes_of_arrays" unless v.is_a? Array
82
+ h[k] = [h[k]].flatten.compact + v
83
+ end
84
+ end
85
+ else
86
+ raise "not an array_of_hashes_of_arrays"
87
+ end
88
+ h
89
+ end
90
+
91
+ def merge_array_of_hashes_of_arrays! array_of_hashes_of_arrays
92
+ h = merge_array_of_hashes_of_arrays array_of_hashes_of_arrays
93
+ self.replace h
94
+ self
95
+ end
96
+ end
97
+
98
+ class Range
99
+ # The Range's computed size, ie the number of elements in range.
100
+ # @example
101
+ # (3..3).size
102
+ # => 1
103
+ #
104
+ # (0..9).size
105
+ # => 10
106
+ # @return The size of the range
107
+ def size
108
+ last - first + 1
109
+ end
110
+
111
+ # The intersection of 2 ranges. Returns nil if there are no common elements.
112
+ # @example
113
+ # 1..10 & 5..15 => 5..10
114
+ # @return [Range, nil] The intesection between 2 overlapping ranges, or zero
115
+ def & other_range
116
+ case other_range
117
+ when Range
118
+ intersection = []
119
+ each do |i|
120
+ intersection << i if other_range.include? i
121
+ end
122
+ result = intersection.to_ranges
123
+ case result[0]
124
+ when Integer
125
+ return (result[0])..(result[0])
126
+ when Range, nil
127
+ return result[0]
128
+ end
129
+ else
130
+ raise "unsupported type"
131
+ end
132
+ end
133
+
134
+ # Does this range wholely include other_range? (true or false)
135
+ # @example
136
+ # (3..5).include_range? (3..3)
137
+ # => true
138
+ #
139
+ # (0..4).include_range? (6..7)
140
+ # => false
141
+ # @return [true, false]
142
+ def include_range? other_range
143
+ case other_range
144
+ when Range
145
+ if other_range.first >= self.first && other_range.last <= self.last
146
+ return true
147
+ else
148
+ return false
149
+ end
150
+ else
151
+ raise "unsupported type"
152
+ end
153
+ end
19
154
  end
20
155
 
21
156
  class String
157
+ # The blob status of this string (set this to true if a binary string)
158
+ attr_accessor :blob
159
+
160
+ # Returns whether or not +str+ is a blob.
161
+ # @return [true,false] If true, this string contains binary data. If false, its a regular string
162
+ def blob?
163
+ @blob
164
+ end
165
+
22
166
  # A Camel-ized string. The reverse of {#snake_case}
23
167
  # @example
24
168
  # "my_plist_key".camelcase => "MyPlistKey"
25
169
  def camelcase
26
- str = self.dup.capitalize.gsub(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase } \
170
+ str = self.dup.gsub(/(^|[-_.\s])([a-zA-Z0-9])/) { $2.upcase } \
27
171
  .gsub('+', 'x')
28
172
  end
29
173
 
@@ -36,3 +180,12 @@ class String
36
180
  end
37
181
  end
38
182
 
183
+ class Float
184
+ alias_method :round_orig, :round
185
+ # Round to nearest n decimal places
186
+ # @example
187
+ # 16.347.round(2) => 16.35
188
+ def round(n=0)
189
+ (self * (10.0 ** n)).round_orig * (10.0 ** (-n))
190
+ end
191
+ end
@@ -0,0 +1,133 @@
1
+ module Plist4r
2
+ # A module which is an instance of the Script class encapsulates in its scope
3
+ # the top-level methods, top-level constants, and instance variables defined in
4
+ # a ruby script file (and its subfiles) loaded by a ruby program. This allows
5
+ # use of script files to define objects that can be loaded into a program in
6
+ # much the same way that objects can be loaded from YAML or Marshal files.
7
+ #
8
+ # See intro.txt[link:files/intro_txt.html] for an overview.
9
+ #
10
+ # Usable under the Ruby license. Copyright (C)2004 Joel VanderWerf. Questions to
11
+ # mailto:vjoel@users.sourceforge.net.
12
+ class Script < Module
13
+ # The file with which the Script was instantiated.
14
+ attr_reader :__main_file
15
+
16
+ # The directory in which main_file is located, and relative to which
17
+ # #load searches for files before falling back to Kernel#load.
18
+ attr_reader :__dir
19
+
20
+ # A hash that maps <tt>filename=>true</tt> for each file that has been
21
+ # required locally by the script. This has the same semantics as <tt>$"</tt>,
22
+ # alias <tt>$LOADED_FEATURES</tt>, except that it is local to this script.
23
+ attr_reader :__loaded_features
24
+
25
+ class << self
26
+ alias load new
27
+ end
28
+
29
+ # Creates new Script, and loads _main_file_ in the scope of the Script. If a
30
+ # block is given, the script is passed to it before loading from the file, and
31
+ # constants can be defined as inputs to the script.
32
+
33
+ def initialize(main_file) # :yields: self
34
+ extend ScriptModuleMethods
35
+ @__main_file = File.expand_path(main_file)
36
+ @__dir = File.dirname(@__main_file)
37
+ @__loaded_features = {}
38
+
39
+ yield self if block_given?
40
+ load_in_module(@__main_file)
41
+ end
42
+
43
+ # Loads _file_ into this Script. Searches relative to the local dir, that is,
44
+ # the dir of the file given in the original call to
45
+ # <tt>Script.load(file)</tt>, loads the file, if found, into this Script's
46
+ # scope, and returns true. If the file is not found, falls back to
47
+ # <tt>Kernel.load</tt>, which searches on <tt>$LOAD_PATH</tt>, loads the file,
48
+ # if found, into global scope, and returns true. Otherwise, raises
49
+ # <tt>LoadError</tt>.
50
+ #
51
+ # The _wrap_ argument is passed to <tt>Kernel.load</tt> in the fallback case,
52
+ # when the file is not found locally.
53
+ #
54
+ # Typically called from within the main file to load additional sub files, or
55
+ # from those sub files.
56
+
57
+ def load(file, wrap = false)
58
+ load_in_module(file)
59
+ true
60
+ rescue MissingFile
61
+ super
62
+ end
63
+
64
+ # Analogous to <tt>Kernel#require</tt>. First tries the local dir, then falls
65
+ # back to <tt>Kernel#require</tt>. Will load a given _feature_ only once.
66
+ #
67
+ # Note that extensions (*.so, *.dll) can be required in the global scope, as
68
+ # usual, but not in the local scope. (This is not much of a limitation in
69
+ # practice--you wouldn't want to load an extension more than once.) This
70
+ # implementation falls back to <tt>Kernel#require</tt> when the argument is an
71
+ # extension or is not found locally.
72
+
73
+ def require(feature)
74
+ unless @__loaded_features[feature]
75
+ @__loaded_features[feature] = true
76
+ file = feature
77
+ file += ".rb" unless /\.rb$/ =~ file
78
+ load_in_module(file)
79
+ end
80
+ rescue MissingFile
81
+ @__loaded_features[feature] = false
82
+ super
83
+ end
84
+
85
+ # Raised by #load_in_module, caught by #load and #require.
86
+ class MissingFile < LoadError; end
87
+
88
+ # Loads _file_ in this module's context. Note that <tt>\_\_FILE\_\_</tt> and
89
+ # <tt>\_\_LINE\_\_</tt> work correctly in _file_.
90
+ # Called by #load and #require; not normally called directly.
91
+
92
+ def load_in_module(__file__)
93
+ __file__ = File.expand_path(__file__, @__dir)
94
+ module_eval("@__script_scope ||= binding\n" + IO.read(__file__),
95
+ __file__, 0)
96
+ # start numbering at 0 because of the extra line.
97
+ # The extra line does nothing in sub-script files.
98
+ rescue Errno::ENOENT
99
+ if /#{__file__}$/ =~ $!.message # No extra locals in this scope.
100
+ raise MissingFile, $!.message
101
+ else
102
+ raise
103
+ end
104
+ end
105
+
106
+ def to_s # :nodoc:
107
+ "#<#{self.class}:#{File.join(__dir, File.basename(__main_file))}>"
108
+ end
109
+
110
+ module ScriptModuleMethods
111
+ # This is so that <tt>def meth...</tt> behaves like in Ruby's top-level
112
+ # context. The implementation simply calls
113
+ # <tt>Module#module_function(name)</tt>.
114
+ def method_added(name) # :nodoc:
115
+ module_function(name)
116
+ end
117
+
118
+ attr_reader :__script_scope
119
+
120
+ # Gets list of local vars in the script. Does not see local vars in files
121
+ # loaded or required by that script.
122
+ def __local_variables
123
+ eval("local_variables", __script_scope)
124
+ end
125
+
126
+ # Gets value of local var in the script. Does not see local vars in files
127
+ # loaded or required by that script.
128
+ def __local_variable_get(name)
129
+ eval(name.to_s, __script_scope)
130
+ end
131
+ end
132
+ end
133
+ end