plist4r 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.yardopts +11 -0
- data/LICENSE +3 -1
- data/README.rdoc +25 -122
- data/Rakefile +14 -0
- data/VERSION +1 -1
- data/bin/plist4r +2 -0
- data/ext/osx_plist/Makefile +157 -0
- data/ext/osx_plist/extconf.rb +9 -0
- data/ext/osx_plist/plist.c +606 -0
- data/ext/osx_plist/plist.o +0 -0
- data/lib/plist4r.rb +6 -3
- data/lib/plist4r/application.rb +1 -2
- data/lib/plist4r/backend.rb +102 -34
- data/lib/plist4r/backend/c_f_property_list.rb +65 -0
- data/lib/plist4r/backend/c_f_property_list/LICENSE +19 -0
- data/lib/plist4r/backend/c_f_property_list/README +34 -0
- data/lib/plist4r/backend/c_f_property_list/cfpropertylist.rb +6 -0
- data/lib/plist4r/backend/c_f_property_list/rbBinaryCFPropertyList.rb +663 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFPlistError.rb +26 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFPropertyList.rb +348 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFTypes.rb +241 -0
- data/lib/plist4r/backend/c_f_property_list/rbXMLCFPropertyList.rb +116 -0
- data/lib/plist4r/backend/example.rb +37 -52
- data/lib/plist4r/backend/haml.rb +47 -36
- data/lib/plist4r/backend/libxml4r.rb +24 -20
- data/lib/plist4r/backend/osx_plist.rb +82 -0
- data/lib/plist4r/backend/ruby_cocoa.rb +172 -54
- data/lib/plist4r/backend/test/data_types.rb +163 -0
- data/lib/plist4r/backend/test/harness.rb +255 -0
- data/lib/plist4r/backend/test/output.rb +47 -0
- data/lib/plist4r/backend_base.rb +4 -2
- data/lib/plist4r/{options.rb → cli.rb} +2 -1
- data/lib/plist4r/commands.rb +13 -8
- data/lib/plist4r/config.rb +36 -9
- data/lib/plist4r/docs/Backends.html +59 -0
- data/lib/plist4r/docs/DeveloperGuide.rdoc +53 -0
- data/lib/plist4r/docs/EditingPlistFiles.rdoc +88 -0
- data/lib/plist4r/docs/InfoPlistExample.rdoc +33 -0
- data/lib/plist4r/docs/LaunchdPlistExample.rdoc +33 -0
- data/lib/plist4r/docs/PlistKeyNames.rdoc +47 -0
- data/lib/plist4r/mixin/array_dict.rb +61 -0
- data/lib/plist4r/mixin/data_methods.rb +178 -54
- data/lib/plist4r/mixin/haml4r.rb +4 -0
- data/lib/plist4r/mixin/haml4r/css_attributes.rb +19 -0
- data/lib/plist4r/mixin/haml4r/examples.rb +261 -0
- data/lib/plist4r/mixin/haml4r/haml_table_example.rb +79 -0
- data/lib/plist4r/mixin/haml4r/table.rb +157 -0
- data/lib/plist4r/mixin/haml4r/table_cell.rb +160 -0
- data/lib/plist4r/mixin/haml4r/table_cells.rb +485 -0
- data/lib/plist4r/mixin/haml4r/table_section.rb +101 -0
- data/lib/plist4r/mixin/ordered_hash.rb +9 -1
- data/lib/plist4r/mixin/popen4.rb +1 -1
- data/lib/plist4r/mixin/ruby_stdlib.rb +154 -1
- data/lib/plist4r/mixin/script.rb +133 -0
- data/lib/plist4r/mixin/table.rb +435 -0
- data/lib/plist4r/plist.rb +272 -94
- data/lib/plist4r/plist_cache.rb +42 -43
- data/lib/plist4r/plist_type.rb +31 -74
- data/lib/plist4r/plist_type/info.rb +157 -3
- data/lib/plist4r/plist_type/launchd.rb +54 -48
- data/lib/plist4r/plist_type/plist.rb +1 -3
- data/plist4r.gemspec +74 -14
- data/spec/{examples.rb → launchd_examples.rb} +131 -139
- data/spec/plist4r/application_spec.rb +37 -0
- data/spec/plist4r/backend_spec.rb +256 -0
- data/spec/plist4r/cli_spec.rb +25 -0
- data/spec/plist4r/commands_spec.rb +20 -0
- data/spec/plist4r/config_spec.rb +38 -0
- data/spec/plist4r/mixin/array_dict_spec.rb +120 -0
- data/spec/plist4r/mixin/data_methods_spec.rb +96 -0
- data/spec/plist4r/mixin/haml4r/examples.rb +261 -0
- data/spec/plist4r/mixin/ruby_stdlib_spec.rb +228 -0
- data/spec/plist4r/plist_cache_spec.rb +261 -0
- data/spec/plist4r/plist_spec.rb +841 -23
- data/spec/plist4r/plist_type_spec.rb +126 -0
- data/spec/plist4r_spec.rb +53 -27
- data/spec/scratchpad.rb +226 -0
- data/spec/spec_helper.rb +5 -1
- metadata +109 -23
- data/lib/plist4r/backend/plutil.rb +0 -25
- data/lib/plist4r/mixin.rb +0 -7
- data/plists/array_mini.xml +0 -14
- data/plists/example_big_binary.plist +0 -0
- data/plists/example_medium_binary_launchd.plist +0 -0
- data/plists/example_medium_launchd.xml +0 -53
- data/plists/mini.xml +0 -12
- 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> </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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
117
|
+
|
35
118
|
unless @plist.strict_keys
|
36
119
|
key_type = nil
|
37
|
-
|
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 #{
|
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
|
47
|
-
raise "Key: #{key}, value: #{value.inspect} is of type #{value.class}. Should be: #{
|
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: #{
|
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: #{
|
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
|
-
# @
|
71
|
-
#
|
72
|
-
#
|
73
|
-
def set key, value
|
74
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|