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,59 @@
1
+ <h1>Backends</h1>
2
+ <div>
3
+ <p>Documentation for the Plist4r backend modules - please see <tt><a href="Plist4r/Backend.html" target="_self" title="Plist4r::Backend (class)">Plist4r::Backend</a></tt></p>
4
+ <h3>Performance</h3>
5
+ <p>Time elapsed for encoding / decoding a non-flat (nested) plist structure of 1024 keys</p>
6
+ <p>Real elapsed time based on - 2GHz Intel Core Duo with 2GB Ram</p>
7
+ <p>Ruby Enterprise Edition (REE) 1.8.7 p248, Mac OS-X 10.6.3</p>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>&nbsp;</th>
12
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: center'>:from_xml</th>
13
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: center'>:to_xml</th>
14
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: center'>:from_binary</th>
15
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: center'>:to_binary</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <tr>
20
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: right'>c_f_property_list</th>
21
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>18.2 ms</td>
22
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>19.5 ms</td>
23
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>33.4 ms</td>
24
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>34.9 ms</td>
25
+ </tr>
26
+ <tr>
27
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: right'>haml</th>
28
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
29
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>126.8 ms</td>
30
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
31
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
32
+ </tr>
33
+ <tr>
34
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: right'>libxml4r</th>
35
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>41.4 ms</td>
36
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
37
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
38
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>n/a</td>
39
+ </tr>
40
+ <tr>
41
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: right'>osx_plist</th>
42
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>4.8 ms</td>
43
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>5.0 ms</td>
44
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>4.6 ms</td>
45
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>4.6 ms</td>
46
+ </tr>
47
+ <tr>
48
+ <th style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #F5F5FF; text-align: right'>ruby_cocoa</th>
49
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>320.4 ms</td>
50
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>318.8 ms</td>
51
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>319.7 ms</td>
52
+ <td style='padding: 5px; padding-left: 15px; padding-right: 15px; background-color: #FAFAFA; text-align: center'>319.2 ms</td>
53
+ </tr>
54
+ </tbody>
55
+ </table>
56
+ <p>To re-run the backend tests</p>
57
+ <pre class='code'>$ cd plist4r && rake backend:tests</pre>
58
+ </div>
59
+ <p></p>
@@ -0,0 +1,53 @@
1
+ == Plist4r Backends
2
+
3
+ There are now a number of ruby libraries which can read / write plist files. The aim of plist4r is to utilize the individual best features from all of those libraries, as a series of "backends". And hide those behind a "frontend" that is easy to work with.
4
+
5
+ Backends often only need to be a single ruby file, which implements the Plist4r API methods and calls out to other (existing) ruby code. No single backend has to provide the whole API. Instead, Plist4r simply iterates over all of the backends it knows about, and then calls the first backend that can responds to the API method.
6
+
7
+ There are only 3 generally recognized Plist file formats. (the gnustep plist format also goes by the names openstep, and nextstep)
8
+
9
+ FileFormats = %w[ binary xml gnustep ]
10
+
11
+ A plist4r backend can implement any number of these 6 supported API methods
12
+
13
+ ApiMethods = %w[ from_xml from_binary from_gnustep to_xml to_binary to_gnustep ]
14
+
15
+ Other API methods are "generated" methods, which translate the call onto real backend API methods. A good example is +from_string()+, which gets resolved to +from_binary+ or +from_xml+. Generally speaking, those {Plist4r::Backend.PrivateApiMethods} methods are cached and should not be provided by a Plist4r backend
16
+
17
+ PrivateApiMethods = %w[ from_string open save ]
18
+
19
+ For backends performance data see {file:Backends}.
20
+
21
+ == Plist4r Types
22
+
23
+ A Plist type can be one of +%w[plist info launchd]+, and is the data type for the whole plist file. A PlistType can provide special convenience methods for its Type-specific data structures. For example {Plist4r::PlistType::Launchd#socket}.
24
+
25
+ We re-use common support objects when writing a new PlistType
26
+ * {Plist4r::PlistType}
27
+ * {Plist4r::ArrayDict}
28
+ * {Plist4r::DataMethods}
29
+
30
+ == Contributing to Plist4r
31
+
32
+ * Fork the project, and create a topic branch as per {these instructions}[http://wiki.opscode.com/display/opscode/Working+with+Git]
33
+ * Make the appropriate source code changes
34
+ * Raise a new issue in Github Issues, with a name that corresponds to the topic branch name
35
+
36
+ For a change in the {Plist4r} Core library
37
+
38
+ * Please update the inline source code documentation, generate locally with +rake yard+
39
+ * Please run the regression tests with +rake spec+ and +rake backend:tests+
40
+ * Please update the existing regression tests (in RSpec) or write new ones
41
+
42
+ For a {Plist4r::Backend}
43
+
44
+ * Please include attribution with an @author tag in the yard inline comments
45
+ * If requires new gem dependencies, be add to the Rakefile and run +rake gemspec+
46
+
47
+ For a {Plist4r::PlistType}
48
+
49
+ * Conform to the conventions used by {Plist4r::PlistType::Info} and {Plist4r::PlistType::Launchd}
50
+ * Use {Plist4r::OrderedHash} instead of +Hash+ or +{}+
51
+ * Try to use {Plist4r::DataMethods} and {Plist4r::ArrayDict} for storing the data
52
+ * Use yard to help document any custom key functions and +rake yard+ afterward
53
+
@@ -0,0 +1,88 @@
1
+ = Editing Plist Files
2
+
3
+ == Editing
4
+
5
+ In this example, we will be editing a Launchd plist file.
6
+
7
+ When we wish to perform an edit operation on a plist object, we (almost always) are calling an accessor method on the Plist Object. We may call the method directly on the object, like this
8
+
9
+ launchd_plist.watch_paths ["/path1", "/path2", ...]
10
+
11
+ However it gets a bit repetitive when there are many such plist keys to set
12
+
13
+ launchd_plist.label "com.mydomain.myapp"
14
+ launchd_plist.program "/path/to/myapp"
15
+ launchd_plist.launch_only_once true
16
+ # etc...
17
+ launchd_plist.save
18
+
19
+ === plist.edit do
20
+
21
+ So instead we can invoke an convenience edit block on our plist object, which will just instance_eval(&blk) the block.
22
+
23
+ launchd_plist.edit do
24
+ label "com.mydomain.myapp"
25
+ program "/path/to/myapp"
26
+ launch_only_once true
27
+ # etc...
28
+ save
29
+ end
30
+
31
+ === plist.<< do
32
+
33
+ The << operator can alternatively be used, interchangeably. Its just another way of writing plist.edit.
34
+
35
+ launchd_plist.<< do
36
+ label "com.mydomain.myapp"
37
+ program "/path/to/myapp"
38
+ launch_only_once true
39
+ end
40
+ launchd_plist.save
41
+
42
+ == Editing operations
43
+
44
+ Certain kinds of edit operation are available on the plist keys. These are useful when we want to treat the plist keys like we would an Array object or a Hash object. Methods like {Plist4r::Plist#select}, {Plist4r::Plist#map}, {Plist4r::Plist#delete_if}, {Plist4r::Plist#clear} and similar. Such methods are all documented in {Plist4r::Plist}.
45
+
46
+ == Plist Data (CFData / NSData)
47
+
48
+ A plist file supports key-value pairs in several types, including Base64 encoded binary data. For example, a plist key that stores binary data might look something like this
49
+
50
+ <data>
51
+ PEKBpYGlmYFCPA==
52
+ </data>
53
+
54
+ When its decoded, this is a byte stream (8-bit bytes), of some finite length. In Ruby we have no dedicated class to represent this, but instead can use a ruby +String+ as a binary string.
55
+
56
+ In Plist4r, we extend {String} with 2 methods, {String#blob=} and {String#blob?}. This allows us to identify and differentiate a binary string from a regular string object.
57
+
58
+ So to store binary data into a plist data key...
59
+
60
+ @plist = Plist4r.new
61
+ bstr = "my_binary_data"
62
+ bstr.blob = true # mark as a binary string
63
+ @plist.store "MyData", bstr
64
+
65
+ ...and to read or inspect the binary data in Ruby from a {Plist4r::Plist} object
66
+
67
+ @plist.my_data
68
+ => "my_binary_data"
69
+ @plist.my_data.blob?
70
+ => true
71
+
72
+ If we forget the +blob?+ and +blob=+ methods, then we simple are storing and reading our data as a regular textual +String+. So heh, just remember to set +blob=+ true at some point.
73
+
74
+ If we want to perform byte stream IO operations on our binary string...
75
+
76
+ # wrap it in a StringIO object
77
+ stream = StringIO.new(@plist.my_data)
78
+ next_byte = stream.getc # or stream.putc(int) to write a byte
79
+
80
+ See http://www.ruby-doc.org/core/classes/IO.html and http://www.ruby-doc.org/core/classes/StringIO.html for more information about the IO object classes.
81
+
82
+ == Plist Types
83
+
84
+ The class {Plist4r::PlistType} allows you to save those more complex data structures to specific plist keys in a plist file. For example the method {Plist4r::PlistType::Launchd#socket} will construct a Socket entry for a launchd plist file.
85
+
86
+ If you are developing a custom application, and intend to exchange data in a custom plist file format, it may be worth writing a custom Plist Type. In which case please see the {file:DeveloperGuide} for more info.
87
+
88
+
@@ -0,0 +1,33 @@
1
+ = Info Plist Example
2
+
3
+ In this example, we edit an Apple Info.plist, to modify a couple of the keys and write them back to the same file. For setting the plist file attributes see {Plist4r::Plist}. For more information specifically about Info Plists, see {Plist4r::PlistType::Info}.
4
+
5
+ # standard method
6
+ info_plist = Plist4r.open "/Applications/MyApp.app/Contents/Info.plist"
7
+ info_plist[:c_f_bundle_signature] # => "????"
8
+ info_plist["LSUIElement"] = true
9
+ info_plist.save
10
+
11
+ # block method
12
+ info_plist = Plist4r.open "/Applications/MyApp.app/Contents/Info.plist" do
13
+
14
+ # plist attributes
15
+ filename # => "/Applications/MyApp.app/Contents/Info.plist"
16
+ file_format # => :xml
17
+ plist_type # => :info
18
+
19
+ # read
20
+ c_f_bundle_executable # => "MyApp"
21
+ c_f_bundle_icon_file # => "Icon"
22
+ c_f_bundle_identifier # => "com.mydomain.MyApp"
23
+ c_f_bundle_name # => "MyApp"
24
+ c_f_bundle_signature # => "????"
25
+
26
+ # write
27
+ c_f_bundle_signature "MYAP"
28
+ store "LSUIElement" true
29
+
30
+ # call info_plist.save
31
+ save
32
+ end
33
+
@@ -0,0 +1,33 @@
1
+ = Launchd Plist Example
2
+
3
+ In this example, we edit an Apple Launchd plist, to modify a couple of the keys and write them back to the same file. For setting the plist file attributes see {Plist4r::Plist}. For more information specifically about Launchd Plists, see {Plist4r::PlistType::Launchd}.
4
+
5
+ # standard method
6
+ launchd_plist = Plist4r.open "/Library/LaunchDaemons/com.mydomain.MyApp.plist"
7
+ launchd_plist[:label] # => "com.mydomain.Myapp"
8
+ launchd_plist[:start_on_mount] = true
9
+ launchd_plist.save
10
+
11
+ # block method
12
+ launchd_plist = Plist4r.open "/Library/LaunchDaemons/com.mydomain.MyApp.plist" do
13
+
14
+ # plist attributes
15
+ filename # => "/Library/LaunchDaemons/com.mydomain.MyApp.plist"
16
+ file_format # => :xml
17
+ plist_type # => :launchd
18
+
19
+ # read
20
+ label # => "com.mydomain.Myapp"
21
+ program_arguments # => ["/Applications/MyApp.app/Contents/MacOS/MyApp"]
22
+ queue_directories # => ["/dir/to/watch/1","/dir/to/watch/2","etc..."]
23
+ run_at_load # => false
24
+ start_on_mount # => false
25
+
26
+ # write
27
+ run_at_load true
28
+ store "StartOnMount" true
29
+
30
+ # call launchd_plist.save
31
+ save
32
+ end
33
+
@@ -0,0 +1,47 @@
1
+ = Plist Key Names
2
+
3
+ To read from or write to a plist key, we must specify the key name, plus an optional value. There are several ways we can specify key names in Plist4r. The precise method syntax you choose will depend mostly upon convenience. However there are certain limitations to method names in Ruby in regards to uppercase / lowercase. And plist key names are usually capitalized (such as "WatchPaths", and "CFBundleSignature"). To get around this we "CamelCase" and "snake_case" the key accessor methods, by default. Dont forget that many Info plist keys start with 2 capital letters. So "CFBundleSignature" should be written with an extra underscore between the "c" and the "f"
4
+
5
+ === CF/NSKeyNames
6
+
7
+ info_plist.<< do
8
+ c_f_bundle_signature "com.mydomain.myapp"
9
+ end
10
+ info_plist.to_hash
11
+ # => { "CFBundleSignature" => "com.mydomain.myapp" }
12
+
13
+ === Corrected Key Names
14
+
15
+ Sometimes, the associated PlistType will come with a "tweaked" method name, is the true keyname cannot be expressed in terms of snake_case. For example
16
+
17
+ launchd_plist.<< do
18
+ inetd_compatibility :wait => true
19
+ end
20
+ launchd_plist.to_hash
21
+ # => { "inetdCompatability" => { "Wait" => true } }
22
+ # (the CamelCased key name would otherwise start with a capital letter, "InetdCompatability")
23
+
24
+ === CamelCasing of Key Names
25
+
26
+ For an unknown key name which isnt specifically supported, these are all passed through method_missing, and automatically CamelCased. This is the default behaviour. Therefore
27
+
28
+ plist.<< do
29
+ some_arbitrary_key ["some","arbitrary","data"]
30
+ end
31
+ plist.to_hash
32
+ # => { "SomeArbitraryKey" => ["some", "arbitrary", "data"] }
33
+
34
+ === store(), [] and []=
35
+
36
+ Sometimes a plist key name will be in lowercase, and we cannot use method_missing(). To store or read the plist key we call the regular Hash accessor methods with the literal key name. See {Plist4r::Plist#store}, {Plist4r::Plist#[]}, and {Plist4r::Plist#[]=}.
37
+
38
+ # writing with plist.store("key",value)
39
+ plist.<< do
40
+ store "keyname" "value"
41
+ end
42
+
43
+ # reading and writing with plist["key"], and plist["key"]="value"
44
+ plist["keyname"] = "value"
45
+ plist["keyname"] # => "value"
46
+
47
+
@@ -0,0 +1,61 @@
1
+
2
+ require 'plist4r/mixin/data_methods'
3
+
4
+ module Plist4r
5
+ # Abstract Base class. Represents some nested data structure within an open {Plist4r::Plist}.
6
+ # Typically, a {Plist4r::PlistType} will create and build upon nested instances of this class.
7
+ class ArrayDict
8
+ include Plist4r::DataMethods
9
+
10
+ # The initializer for this object. Here we set a reference to our raw data structure,
11
+ # which typically is a nested hash within the plist root hash object.
12
+ # Or an Array type structure if index is set.
13
+ # @param [OrderedHash] orig The nested hash object which this structure represents.
14
+ # @param [Fixnum] index The Array index (if representing an Array structure)
15
+ def initialize orig, index=nil, &blk
16
+ @orig = orig
17
+ @orig = @orig[index] if index
18
+ @hash = ::Plist4r::OrderedHash.new
19
+
20
+ @block = blk
21
+ instance_eval(&@block) if block_given?
22
+ end
23
+
24
+ # The raw data object
25
+ # @return [Plist4r::OrderedHash] @hash
26
+ def hash
27
+ @hash
28
+ end
29
+
30
+ # Select (keep) plist keys from the object.
31
+ # Copy them to the resultant object moving forward.
32
+ # @param [Array, *args] keys The list of Plist Keys to keep
33
+ def select *keys
34
+ keys.flatten.each do |k|
35
+ k = k.to_s.camelcase if k.class == Symbol
36
+ @hash[k] = @orig[k] if @orig[k]
37
+ end
38
+ end
39
+
40
+ # Unselect (delete) plist keys from the object.
41
+ # @param [Array, *args] keys The list of Plist Keys to delete
42
+ def unselect *keys
43
+ @hash = @orig
44
+ keys.flatten.each do |k|
45
+ k = k.to_s.camelcase if k.class == Symbol
46
+ @hash.delete k
47
+ end
48
+ end
49
+
50
+ # Unselect (delete) all plist keys from the object.
51
+ def unselect_all
52
+ @hash = Plist4r::OrderedHash.new
53
+ end
54
+
55
+ # Select (keep) all plist keys from the object.
56
+ # Copy them to the resultant object moving forward.
57
+ def select_all
58
+ @hash = @orig
59
+ end
60
+ end
61
+ end
@@ -5,99 +5,223 @@ require 'plist4r/mixin/ordered_hash'
5
5
  module Plist4r
6
6
  module DataMethods
7
7
 
8
- def classes_for_key_type
9
- {
10
- :string => [String],
11
- :bool => [TrueClass,FalseClass],
12
- :integer => [Fixnum],
13
- :array_of_strings => [Array],
14
- :hash_of_bools => [Hash],
15
- :hash => [Hash],
16
- :bool_or_string_or_array_of_strings => [TrueClass,FalseClass,String,Array]
17
- }
18
- end
8
+ # Return those Class constants which the value of a Plist Key must conform to.
9
+ # This is class-based data validation.
10
+ ClassesForKeyType =
11
+ {
12
+ :string => [String],
13
+ :bool => [TrueClass,FalseClass],
14
+ :bool_or_string => [TrueClass,FalseClass,String],
15
+ :integer => [Fixnum],
16
+ :array_of_strings => [Array],
17
+ :array_of_hashes => [Array],
18
+ :array => [Array],
19
+ :array_or_integer => [Array,Fixnum],
20
+ :array_or_hash => [Array, Hash],
21
+ :hash_of_bools => [Hash],
22
+ :hash_of_strings => [Hash],
23
+ :hash_of_arrays => [Hash],
24
+ :hash_of_arrays_of_strings => [Hash],
25
+ :hash => [Hash],
26
+ :bool_or_string_or_array_of_strings => [TrueClass,FalseClass,String,Array]
27
+ }
28
+
29
+ # A Hash Array of the supported plist keys for this type. These are only those plist keys
30
+ # recognized as belonging to a specific plist datatype. Used in validation, part of DataMethods.
31
+ # We usually overload this method in subclasses. To see how to use these Plist Keys, go to {file:PlistKeyNames}
32
+ # @example
33
+ # class Plist4r::PlistType::MyPlistType < PlistType
34
+ # ValidKeys =
35
+ # {
36
+ # :string => %w[PlistKeyS1 PlistKeyS2 ...],
37
+ # :bool => %w[PlistKeyB1 PlistKeyB2 ...],
38
+ # :integer => %w[PlistKeyI1 PlistKeyI2 ...],
39
+ # :method_defined => %w[CustomPlistKey1 CustomPlistKey2 ...]
40
+ # }
41
+ # end
42
+ #
43
+ # plist.plist_type :my_plist_type
44
+ # plist.plist_key_s1 "some string"
45
+ # plist.plist_key_b1 true
46
+ # plist.plist_key_i1 08
47
+ # plist.custom_plist_key1 MyClass.new(opts)
48
+ # @see ClassesForKeyType
49
+ ValidKeys = {}
19
50
 
20
- def valid_keys
21
- {}
51
+ # A template for creating new plist types
52
+ ValidKeysTemplate =
53
+ {
54
+ :string => %w[
55
+
56
+ ],
57
+ :bool => %w[
58
+
59
+ ],
60
+ :integer => %w[
61
+
62
+ ],
63
+ :array_of_strings => %w[
64
+
65
+ ],
66
+ :array_of_hashes => %w[
67
+
68
+ ],
69
+ :array => %w[
70
+
71
+ ],
72
+ :hash_of_bools => %w[
73
+
74
+ ],
75
+ :hash_of_strings => %w[
76
+
77
+ ],
78
+ :hash_of_arrays => %w[
79
+
80
+ ],
81
+ :hash_of_arrays_of_strings => %w[
82
+
83
+ ],
84
+ :hash => %w[
85
+
86
+ ],
87
+ :could_be_method_defined => %w[
88
+
89
+ ]
90
+ }
91
+
92
+ # Defining respond_to? conflicts with rspec 1.3.0. @object.stub() blows up.
93
+ # To as a consequence we dont implement respond_to? for method_missing()
94
+ # @return true
95
+ def _respond_to? method_sym
96
+ true
22
97
  end
23
98
 
99
+ # Call {#set_or_return} with the appropriate arguments. If {Plist4r::Plist#strict_keys} is enabled,
100
+ # then raise an error on any unrecognised Plist Keys.
24
101
  def method_missing method_symbol, *args, &blk
25
- # puts "method_missing: #{method_symbol.inspect}, args: #{args.inspect}"
26
- # puts "@hash = #{@hash.inspect}"
27
- valid_keys.each do |key_type, valid_keys_of_those_type|
28
- if valid_keys_of_those_type.include?(method_symbol.to_s.camelcase)
29
- puts "key_type = #{key_type}, method_symbol.to_s.camelcase = #{method_symbol.to_s.camelcase}, args = #{args.inspect}"
30
- # return eval("set_or_return key_type, method_symbol.to_s.camelcase, *args, &blk")
31
- return set_or_return key_type, method_symbol.to_s.camelcase, *args, &blk
102
+ set_or_return method_symbol.to_s.camelcase, *args, &blk
103
+ end
104
+
105
+ # Set or return value data
106
+ # @param [Symbol, String] key The plist key name, either a snake-cased symbol, or literal string
107
+ # @param [nil, Object] value If present, store Object under the plist key "key". If nil, return the stored object
108
+ # @return The Object stored under key
109
+ def set_or_return key, value=nil
110
+ key = key.to_s.camelcase if key.class == Symbol
111
+
112
+ self.class::ValidKeys.each do |key_type, valid_keys_of_those_type|
113
+ if valid_keys_of_those_type.include?(key)
114
+ return set_or_return_of_type key_type, key, value
32
115
  end
33
116
  end
34
- # puts @plist.inspect
117
+
35
118
  unless @plist.strict_keys
36
119
  key_type = nil
37
- # return eval("set_or_return key_type, method_symbol.to_s.camelcase, *args, &blk")
38
- return set_or_return key_type, method_symbol.to_s.camelcase, *args, &blk
120
+ return set_or_return_of_type key_type, key, value
39
121
  else
40
- raise "Unrecognized key for class: #{self.class.inspect}. Tried to set_or_return #{method_symbol.inspect}, with: #{args.inspect}"
122
+ raise "Unrecognized key for class: #{self.class.inspect}. Tried to set_or_return #{key.inspect}, with: #{value.inspect}"
41
123
  end
42
- # puts "bob"
43
124
  end
44
125
 
126
+ # This method is called when setting a value to a plist key. (or some value within a nested plist sub-structure).
127
+ # @param [Symbol] key_type This Symbol is resolved to a class constant, by looking it up in {ClassesForKeyType}
128
+ # @param value The value to validate. We just check that the value conforms to key_type.
129
+ # @see ClassesForKeyType
130
+ # @raise Class mistmatch
131
+ # @example
132
+ # plist.validate_value :string, "CFBundleIdentifier", "com.apple.myapp"
133
+ # # => Okay, no error raised
134
+ # plist.validate_value :string, "CFBundleIdentifier", ["com.apple.myapp"]
135
+ # # => Raises Class mismatch. Value is of type Array, should be String
45
136
  def validate_value key_type, key, value
46
- unless classes_for_key_type[key_type].include? value.class
47
- raise "Key: #{key}, value: #{value.inspect} is of type #{value.class}. Should be: #{classes_for_key_type[key_type].join ", "}"
137
+ unless ClassesForKeyType[key_type].include? value.class
138
+ raise "Key: #{key}, value: #{value.inspect} is of type #{value.class}. Should be: #{ClassesForKeyType[key_type].join ", "}"
48
139
  end
49
140
  case key_type
50
141
  when :array_of_strings, :bool_or_string_or_array_of_strings
51
142
  if value.class == Array
52
143
  value.each_index do |i|
53
144
  unless value[i].class == String
54
- raise "Element: #{key}[#{i}], value: #{value[i].inspect} is of type #{value[i].class}. Should be: #{classes_for_key_type[:string].join ", "}"
145
+ raise "Element: #{key}[#{i}], value: #{value[i].inspect} is of type #{value[i].class}. Should be: #{ClassesForKeyType[:string].join ", "}"
55
146
  end
56
147
  end
57
148
  end
149
+ when :array_of_hashes
150
+ value.each_index do |i|
151
+ unless value[i].class == Hash
152
+ raise "Element: #{key}[#{i}], value: #{value[i].inspect} is of type #{value[i].class}. Should be: #{ClassesForKeyType[:hash].join ", "}"
153
+ end
154
+ end
58
155
  when :hash_of_bools
59
156
  value.each do |k,v|
60
157
  unless [TrueClass,FalseClass].include? v.class
61
- raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{classes_for_key_type[:bool].join ", "}"
158
+ raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{ClassesForKeyType[:bool].join ", "}"
159
+ end
160
+ end
161
+ when :hash_of_strings
162
+ value.each do |k,v|
163
+ unless v.class == String
164
+ raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{ClassesForKeyType[:string].join ", "}"
165
+ end
166
+ end
167
+ when :hash_of_arrays
168
+ value.each do |k,v|
169
+ unless v.class == Array
170
+ raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{ClassesForKeyType[:array].join ", "}"
171
+ end
172
+ end
173
+ when :hash_of_arrays_of_strings
174
+ value.each do |k,v|
175
+ unless v.class == Array
176
+ raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{ClassesForKeyType[:array].join ", "}"
177
+ end
178
+ v.each_index do |i|
179
+ unless v[i].class == String
180
+ raise "Element: #{key}[#{k}][#{i}], value: #{v[i].inspect} is of type #{v[i].class}. Should be: #{ClassesForKeyType[:string].join ", "}"
181
+ end
62
182
  end
63
183
  end
64
184
  end
65
185
  end
66
186
 
67
- # Set a plist key to a specific value
68
- # @param [String] The Plist key, as-is
69
- # @param value A ruby object (Hash, String, Array, etc) to set as the value of the plist key
70
- # @example
71
- # plist.set "CFBundleIdentifier", "com.apple.myapp"
72
- # @see #set_or_return
73
- def set key, value
74
- set_or_return nil, key, value
75
- end
187
+ # # Set a plist key to a specific value
188
+ # # @param [String] The Plist key, as-is
189
+ # # @param value A ruby object (Hash, String, Array, etc) to set as the value of the plist key
190
+ # # @see #set_or_return
191
+ # # @example
192
+ # # plist.set "CFBundleIdentifier", "com.apple.myapp"
193
+ # def set key, value
194
+ # set_or_return key, value
195
+ # end
76
196
 
77
- # Return the value of an existing plist key
78
- # @return The key's current value, at the time this method was called
79
- # @see #set_or_return
80
- def value_of key
81
- set_or_return nil, key
82
- end
197
+ # # Return the value of an existing plist key
198
+ # # @return The key's current value, at the time this method was called
199
+ # # @see #set_or_return
200
+ # # @example
201
+ # # plist.value_of "CFBundleIdentifier"
202
+ # # # => "com.apple.myapp"
203
+ # def value_of key
204
+ # set_or_return key
205
+ # end
83
206
 
84
- # Set or return a plist key, value pair
207
+ # Set or return a plist key, value pair, and verify the value type
85
208
  # @param [Symbol, nil] key_type The type of class which the value of the key must belong to. Used for validity check.
86
209
  # If key_type is set to nil, then skip value data check
87
210
  # @return the key's value
88
211
  # @see #validate_value
89
- def set_or_return key_type, key, value=nil
90
- # puts "#{method_name}, key_type: #{key_type.inspect}, key: #{key.inspect}, value: #{value.inspect}"
91
- if value
212
+ # @example
213
+ # plist.set_or_return_of_type :string, "CFBundleIdentifier", "com.apple.myapp"
214
+ #
215
+ # plist.set_or_return_of_type nil, "SomeUnknownKey", [[0],1,2,{ 3 => true}]
216
+ # # Skips validation
217
+ def set_or_return_of_type key_type, key, value=nil
218
+ case value
219
+ when nil
220
+ @orig[key]
221
+ else
92
222
  validate_value key_type, key, value unless key_type == nil
93
223
  @hash[key] = value
94
- else
95
- @orig[key]
96
224
  end
97
225
  end
98
226
  end
99
227
  end
100
-
101
-
102
-
103
-