sj-plist 3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2006, Ben Bleything <ben@bleything.net>
2
+ and Patrick May <patrick@hexane.org>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included
13
+ in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,40 @@
1
+ = All-purpose Property List manipulation library (w/ binary support)
2
+
3
+ Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects.
4
+
5
+ This fork includes support for binary plists.
6
+
7
+ == Usage
8
+
9
+ gem install 'sj-plist'
10
+
11
+ See USAGE[link:files/docs/USAGE.html].
12
+
13
+ == Links
14
+
15
+ [<b>Project Page</b>] http://plist.rubyforge.org
16
+ [<b>Subversion repository</b>] svn://rubyforge.org//var/svn/plist
17
+ [<b>RDoc (on RubyForge)</b>] http://plist.rubyforge.org
18
+
19
+ == Credits
20
+
21
+ plist is maintained by Ben Bleything <mailto:ben@bleything.net> and Patrick May <mailto:patrick@hexane.org>. Patrick wrote most of the code; Ben is a recent addition to the project, having merged in his plist generation library.
22
+
23
+ Other folks who have helped along the way:
24
+
25
+ [<b>Martin Dittus</b>] who pointed out that +Time+ wasn't enough for plist <tt>Dates</tt>, especially those in <tt>~/Library/Cookies/Cookies.plist</tt>
26
+ [<b>Chuck Remes</b>] who pushed Patrick towards implementing <tt>#to_plist</tt>
27
+ [<b>Mat Schaffer</b>] who supplied code and test cases for <tt><data></tt> elements
28
+ [<b>Michael Granger</b>] for encouragement and help
29
+
30
+ == License and Copyright
31
+
32
+ plist is released under the MIT License.
33
+
34
+ Portions of the code (notably the Rakefile) contain code pulled and/or adapted from other projects. These files contain a comment at the top describing what was used.
35
+
36
+ === MIT License
37
+
38
+ :include: MIT-LICENSE
39
+
40
+
@@ -0,0 +1,135 @@
1
+ ##############################################################
2
+ # Copyright 2006, Ben Bleything <ben@bleything.net> and #
3
+ # Patrick May <patrick@hexane.org> #
4
+ # #
5
+ # Based heavily on Geoffrey Grosenbach's Rakefile for gruff. #
6
+ # Includes whitespace-fixing task based on code from Typo. #
7
+ # #
8
+ # Distributed under the MIT license. #
9
+ ##############################################################
10
+
11
+ require 'fileutils'
12
+ require 'rubygems'
13
+ require 'rake'
14
+ require 'rake/testtask'
15
+ require 'rake/packagetask'
16
+ require 'rdoc/task'
17
+ require 'rubygems/package_task'
18
+
19
+ $:.unshift(File.dirname(__FILE__) + "/lib")
20
+ require 'plist'
21
+
22
+ PKG_NAME = 'sj-plist'
23
+ PKG_VERSION = Plist::VERSION
24
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
25
+
26
+ RELEASE_NAME = "REL #{PKG_VERSION}"
27
+
28
+ TEST_FILES = Dir.glob('test/test_*').delete_if { |item| item.include?( "\.svn" ) }
29
+ TEST_ASSETS = Dir.glob('test/assets/*').delete_if { |item| item.include?( "\.svn" ) }
30
+ LIB_FILES = Dir.glob('lib/**/*').delete_if { |item| item.include?( "\.svn" ) }
31
+ RELEASE_FILES = [ "Rakefile", "README", "MIT-LICENSE", "docs/USAGE" ] + LIB_FILES + TEST_FILES + TEST_ASSETS
32
+
33
+ task :default => [ :test ]
34
+ # Run the unit tests
35
+ Rake::TestTask.new { |t|
36
+ t.libs << "test"
37
+ t.pattern = 'test/test_*.rb'
38
+ t.verbose = true
39
+ }
40
+
41
+ desc "Clean pkg, coverage, and rdoc; remove .bak files"
42
+ task :clean => [ :clobber_rdoc, :clobber_package, :clobber_coverage ] do
43
+ puts cmd = "find . -type f -name *.bak -delete"
44
+ `#{cmd}`
45
+ end
46
+
47
+ task :clobber_coverage do
48
+ puts cmd = "rm -rf coverage"
49
+ `#{cmd}`
50
+ end
51
+
52
+ desc "Generate coverage analysis with rcov (requires rcov to be installed)"
53
+ task :rcov => [ :clobber_coverage ] do
54
+ puts cmd = "rcov -Ilib --xrefs -T test/*.rb"
55
+ puts `#{cmd}`
56
+ end
57
+
58
+ desc "Strip trailing whitespace and fix newlines for all release files"
59
+ task :fix_whitespace => [ :clean ] do
60
+ RELEASE_FILES.reject {|i| i =~ /assets/}.each do |filename|
61
+ next if File.directory? filename
62
+
63
+ File.open(filename) do |file|
64
+ newfile = ''
65
+ needs_love = false
66
+
67
+ file.readlines.each_with_index do |line, lineno|
68
+ if line =~ /[ \t]+$/
69
+ needs_love = true
70
+ puts "#{filename}: trailing whitespace on line #{lineno}"
71
+ line.gsub!(/[ \t]*$/, '')
72
+ end
73
+
74
+ if line.chomp == line
75
+ needs_love = true
76
+ puts "#{filename}: no newline on line #{lineno}"
77
+ line << "\n"
78
+ end
79
+
80
+ newfile << line
81
+ end
82
+
83
+ if needs_love
84
+ tempname = "#{filename}.new"
85
+
86
+ File.open(tempname, 'w').write(newfile)
87
+ File.chmod(File.stat(filename).mode, tempname)
88
+
89
+ FileUtils.ln filename, "#{filename}.bak"
90
+ FileUtils.ln tempname, filename, :force => true
91
+ File.unlink(tempname)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Genereate the RDoc documentation
98
+ RDoc::Task.new { |rdoc|
99
+ rdoc.rdoc_dir = 'rdoc'
100
+ rdoc.title = "All-purpose Property List manipulation library"
101
+ rdoc.options << '-SNmREADME'
102
+ rdoc.template = "docs/jamis-template.rb"
103
+ rdoc.rdoc_files.include('README', 'MIT-LICENSE', 'CHANGELOG')
104
+ rdoc.rdoc_files.include Dir.glob('docs/**').delete_if {|f| f.include? 'jamis' }
105
+ rdoc.rdoc_files.include('lib/**')
106
+ }
107
+
108
+ # Create compressed packages
109
+ spec = Gem::Specification.new do |s|
110
+ s.name = PKG_NAME
111
+ s.version = PKG_VERSION
112
+
113
+ s.summary = "All-purpose Property List manipulation library (w/ binary support)."
114
+ s.description = <<-EOD
115
+ Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects.
116
+
117
+ This fork includes support for binary plists.
118
+ EOD
119
+
120
+ s.authors = "Ben Bleything and Patrick May. Binary support merged by Sijmen Mulder."
121
+ s.homepage = "http://github.com/sjmulder/plist"
122
+
123
+ s.has_rdoc = true
124
+
125
+ s.files = RELEASE_FILES
126
+ s.test_files = TEST_FILES
127
+
128
+ s.autorequire = 'plist'
129
+ end
130
+
131
+ Gem::PackageTask.new(spec) do |p|
132
+ p.gem_spec = spec
133
+ p.need_tar = true
134
+ p.need_zip = true
135
+ end
@@ -0,0 +1,104 @@
1
+ == Parsing
2
+
3
+ result = Plist::parse_xml('path/to/example.plist')
4
+
5
+ result.class
6
+ => Hash
7
+
8
+ "#{result['FirstName']} #{result['LastName']}"
9
+ => "John Public"
10
+
11
+ result['ZipPostal']
12
+ => "12345"
13
+
14
+ ==== Example Property List
15
+
16
+ <?xml version="1.0" encoding="UTF-8"?>
17
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18
+ <plist version="1.0">
19
+ <dict>
20
+ <key>FirstName</key>
21
+ <string>John</string>
22
+
23
+ <key>LastName</key>
24
+ <string>Public</string>
25
+
26
+ <key>StreetAddr1</key>
27
+ <string>123 Anywhere St.</string>
28
+
29
+ <key>StateProv</key>
30
+ <string>CA</string>
31
+
32
+ <key>City</key>
33
+ <string>Some Town</string>
34
+
35
+ <key>CountryName</key>
36
+ <string>United States</string>
37
+
38
+ <key>AreaCode</key>
39
+ <string>555</string>
40
+
41
+ <key>LocalPhoneNumber</key>
42
+ <string>5551212</string>
43
+
44
+ <key>ZipPostal</key>
45
+ <string>12345</string>
46
+ </dict>
47
+ </plist>
48
+
49
+ == Generation
50
+
51
+ plist also provides the ability to generate plists from Ruby objects. The following Ruby classes are converted into native plist types:
52
+ Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false
53
+
54
+ * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively).
55
+ * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element.
56
+ * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element. See below for more details.
57
+
58
+ ==== Creating a plist
59
+
60
+ There are two ways to generate complete plists. Given an object:
61
+
62
+ obj = [1, :two, {'c' => 0xd}]
63
+
64
+ If you've mixed in <tt>Plist::Emit</tt> (which is already done for +Array+ and +Hash+), you can simply call +to_plist+:
65
+
66
+ obj.to_plist
67
+
68
+ This is equivalent to calling <tt>Plist::Emit.dump(obj)</tt>. Either one will yield:
69
+
70
+ <?xml version="1.0" encoding="UTF-8"?>
71
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
72
+ <plist version="1.0">
73
+ <array>
74
+ <integer>1</integer>
75
+ <string>two</string>
76
+ <dict>
77
+ <key>c</key>
78
+ <integer>13</integer>
79
+ </dict>
80
+ </array>
81
+ </plist>
82
+
83
+ You can also dump plist fragments by passing +false+ as the second parameter:
84
+
85
+ Plist::Emit.dump('holy cow!', false)
86
+ => "<string>holy cow!</string>"
87
+
88
+ ==== Custom serialization
89
+
90
+ If your class can be safely coerced into a native plist datatype, you can implement +to_plist_node+. Upon encountering an object of a class it doesn't recognize, the plist library will check to see if it responds to +to_plist_node+, and if so, insert the result of that call into the plist output.
91
+
92
+ An example:
93
+
94
+ class MyFancyString
95
+ ...
96
+
97
+ def to_plist_node
98
+ return "<string>#{self.defancify}</string>"
99
+ end
100
+ end
101
+
102
+ When you attempt to serialize a +MyFancyString+ object, the +to_plist_node+ method will be called and the object's contents will be defancified and placed in the plist.
103
+
104
+ If for whatever reason you can't add this method, your object will be serialized with <tt>Marshal.dump</tt> instead.
@@ -0,0 +1,22 @@
1
+ #--
2
+ ##############################################################
3
+ # Copyright 2006, Ben Bleything <ben@bleything.net> and #
4
+ # Patrick May <patrick@hexane.org> #
5
+ # #
6
+ # Distributed under the MIT license. #
7
+ ##############################################################
8
+ #++
9
+ # = Plist
10
+ #
11
+ # This is the main file for plist. Everything interesting happens in Plist and Plist::Emit.
12
+
13
+ require 'base64'
14
+ require 'cgi'
15
+ require 'stringio'
16
+
17
+ require 'plist/generator'
18
+ require 'plist/parser'
19
+
20
+ module Plist
21
+ VERSION = '3.2'
22
+ end
@@ -0,0 +1,251 @@
1
+ require 'strscan'
2
+ require "time"
3
+ require "stringio"
4
+
5
+ module Plist
6
+
7
+ #
8
+ # string - the plist string to parse
9
+ # opts - options (see +AsciiParser.new+)
10
+ #
11
+
12
+ def self.parse_ascii(string, opts = {})
13
+ AsciiParser.new(string, opts).parse
14
+ end
15
+
16
+ #
17
+ # Plist::AsciiParser
18
+ #
19
+ # Parser for the old style ASCII/NextSTEP property lists.
20
+ #
21
+ # Created by Jari Bakken on 2008-08-13.
22
+ #
23
+ class AsciiParser < StringScanner
24
+
25
+ class ParseError < StandardError; end
26
+
27
+ SPACE_REGEXP = %r{ ( /\*.*?\*/ | # block comments
28
+ //.*?$\n? | # single-line comments
29
+ \s* )+ # space
30
+ }mx
31
+
32
+ CONTROL_CHAR = {
33
+ "a" => "\a",
34
+ "b" => "\b",
35
+ "n" => "\n",
36
+ "f" => "\f",
37
+ "t" => "\t",
38
+ "r" => "\r",
39
+ "v" => "\v",
40
+ }
41
+
42
+ BOOLS = {true => "1", false => "0"}
43
+
44
+ #
45
+ # string - the plist string to parse
46
+ #
47
+ # options hash:
48
+ #
49
+ # :parse_numbers => true/false : Set this to true if you numeric values (float/ints) as the correct Ruby type
50
+ # :parse_booleans => true/false : Set this to true if you want "true" and "false" to return the boolean Ruby types
51
+ #
52
+ # Note: Apple's parsers return strings for all old-style plist types.
53
+ #
54
+
55
+ def initialize(string, opts = {})
56
+ string = case string
57
+ when StringIO
58
+ string.string
59
+ when IO
60
+ string.read
61
+ else
62
+ string
63
+ end
64
+
65
+ @parse_numbers = opts.delete(:parse_numbers)
66
+ @parse_bools = opts.delete(:parse_booleans)
67
+ @debug = $VERBOSE == true # ruby -W3
68
+
69
+ raise ArgumentError, "unknown option #{opts.inspect}" unless opts.empty?
70
+
71
+ super(string)
72
+ end
73
+
74
+ def parse
75
+ res = object
76
+
77
+ skip_space
78
+ error "junk after plist" unless eos?
79
+
80
+ res
81
+ end
82
+
83
+ private
84
+
85
+ def object
86
+ skip_space
87
+
88
+ if scan(/\{/) then dictionary
89
+ elsif scan(/\(/) then array
90
+ elsif scan(/</) then data
91
+ elsif scan(/"/) then quoted_string
92
+ else unquoted_string
93
+ end
94
+ end
95
+
96
+ def quoted_string
97
+ puts "creating quoted string #{inspect}" if @debug
98
+
99
+ result = ''
100
+
101
+ loop do
102
+ if scan(/\\/)
103
+ result << escaped
104
+ elsif scan(/"/)
105
+ break
106
+ elsif eos?
107
+ error("unterminated quoted string")
108
+ else scan(/./)
109
+ error("unterminated quoted string") unless matched
110
+ result << matched
111
+ end
112
+ end
113
+
114
+ result
115
+ end
116
+
117
+ def escaped
118
+ if scan(/"|\\|\\\//)
119
+ matched
120
+ elsif scan(/a|b|f|n|v|r|t/)
121
+ CONTROL_CHAR[matched]
122
+ elsif scan(/u[0-9a-f]{4}/i)
123
+ [ matched[1..-1].to_i(16) ].pack("U")
124
+ elsif scan(/\d{1,3}/)
125
+ [ matched.to_i(8) ].pack("C")
126
+ end
127
+ end
128
+
129
+ def unquoted_string
130
+ puts "creating unquoted string #{inspect}" if @debug
131
+
132
+ if scan(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ((\+|-)\d{4})?/)
133
+ puts "returning time" if @debug
134
+ require 'date'
135
+ DateTime.parse(matched)
136
+ elsif scan(/-?\d+?\.\d+\b/)
137
+ puts "returning float" if @debug
138
+ @parse_numbers ? matched.to_f : matched
139
+ elsif scan(/-?\d+\b/)
140
+ puts "returning int" if @debug
141
+ @parse_numbers ? matched.to_i : matched
142
+ elsif scan(/\b(true|false)\b/)
143
+ val = matched == 'true'
144
+ if @parse_bools
145
+ val
146
+ else
147
+ @parse_numbers ? BOOLS[val].to_i : BOOLS[val]
148
+ end
149
+ elsif eos?
150
+ error("unexpected end-of-string")
151
+ else
152
+ puts "returning string" if @debug
153
+ scan(/\w+/)
154
+ end
155
+ end
156
+
157
+ def data
158
+ puts "creating data #{inspect}" if @debug
159
+
160
+ scan(/(.+?)>/)
161
+
162
+ hex = self[1].delete(" ")
163
+ [hex].pack("H*")
164
+ end
165
+
166
+ def array
167
+ puts "creating array #{inspect}" if @debug
168
+
169
+ skip_space
170
+
171
+ arr = []
172
+ until scan(/\)/)
173
+ val = object()
174
+
175
+ return nil unless val
176
+ skip_space
177
+
178
+ unless skip(/,\s*/)
179
+ skip_space
180
+ if scan(/\)/)
181
+ return arr << val
182
+ else
183
+ error "missing ',' or ')' for array"
184
+ end
185
+ end
186
+
187
+ arr << val
188
+ error "unexpected end-of-string when parsing array" if eos?
189
+ end
190
+
191
+ arr
192
+ end
193
+
194
+ def dictionary
195
+ puts "creating dict #{inspect}" if @debug
196
+
197
+ skip_space
198
+
199
+ dict = {}
200
+ until scan(/\}/)
201
+ key = object()
202
+ p :key => key if @debug
203
+
204
+ error "expected terminating '}' for dictionary" unless key
205
+
206
+ # dictionary keys must be strings, even if represented as 12345 in the plist
207
+ if key.is_a?(Integer) && key >= 0
208
+ key = key.to_s
209
+ end
210
+ error "dictionary key must be string (\"quoted\" or alphanumeric)" unless key.is_a? String
211
+
212
+ skip_space
213
+ error "missing '=' in dictionary" unless scan(/=/)
214
+ skip_space
215
+
216
+ val = object()
217
+ p :val => val if @debug
218
+
219
+ skip_space
220
+ error "missing ';' in dictionary" unless skip(/;/)
221
+ skip_space
222
+
223
+ dict[key] = val
224
+ error "unexpected end-of-string when parsing dictionary" if eos?
225
+ end
226
+
227
+ dict
228
+ end
229
+
230
+ def error(msg)
231
+ line = 1
232
+ string.split(//).each_with_index do |e, i|
233
+ line += 1 if e == "\n"
234
+ break if i == pos
235
+ end
236
+
237
+ context = (pos - 10) < 0 ? 0 : pos - 10
238
+ err = "#{msg} at line #{line}\n"
239
+ err << "#{string[context..pos+10]}".inspect << "\n"
240
+
241
+ err << "\n#{inspect}" if @debug
242
+ raise ParseError, err
243
+ end
244
+
245
+ def skip_space
246
+ puts "skipping whitespace" if @debug
247
+ skip SPACE_REGEXP
248
+ end
249
+
250
+ end
251
+ end