property-list 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cccb65a32fea2d625572d56e271741ad40ed0e57
4
+ data.tar.gz: 321a139923120337ab64a16c9836386e3ab5feaa
5
+ SHA512:
6
+ metadata.gz: e7fe9ed9590b7a55622509048b7d51f44a011ca8dc223acd717e3cdcbcad0802627bb9075be536f511a27cf7245a2bdc5962a71d9e82dd1f87a165025f2a5777
7
+ data.tar.gz: 4ef25397d9e969d28bc97943f11c9e075c00b12b36479e5cad80c47c6eec9a2fbdc94ad5c7c454c65ae1fab4a13550e8e747a425a9406c7f7ce21dfd80bb06aa
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2017, luikore
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ This is a full-featured property list library which supports XML/Binary/ASCII format plists, and performance is tuned for large property list files.
2
+
3
+ The code is as clean and complete as possible. There is no runtime dependency to any other gems or libplist or other libraries.
4
+
5
+ ## Install
6
+
7
+ Requires Ruby 1.9+
8
+
9
+ gem ins property-list
10
+
11
+ ## Usage
12
+
13
+ Load a plist file
14
+
15
+ PropertyList.load_xml File.read "some_plist.xml"
16
+ PropertyList.load_binary File.binread "some_binary.plist"
17
+ PropertyList.load_ascii File.read "some_ascii.strings"
18
+ PropertyList.load File.binread "unknown_format.plist"
19
+
20
+ Generate a plist file data
21
+
22
+ PropertyList.dump_xml obj
23
+ PropertyList.dump_binary obj
24
+ PropertyList.dump_ascii obj
25
+
26
+ ## Data type mapping
27
+
28
+ Data type mapping in `PropertyList.load`:
29
+
30
+ real: Float
31
+ string: String
32
+ integer: Integer
33
+ data: StringIO
34
+ date: DateTime
35
+ true: true
36
+ false: false
37
+ uid: PropertyList::Uid # obj.uid is the integer index
38
+
39
+ Reverse mapping in `PropertyList.dump_*`:
40
+
41
+ Float: real
42
+ String, Symbol: string
43
+ Integer: integer
44
+ StringIO, IO: data
45
+ Time, DateTime, Date: date
46
+ true: true
47
+ false: false
48
+ PropertyList::Uid: uid
49
+
50
+ ## Credits
51
+
52
+ The binary generating code is modified from https://github.com/jarib/plist with bug fixes and performance tuning.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ desc "run tests"
2
+ task :test do
3
+ Dir.glob './test/test_*.rb' do |f|
4
+ p f
5
+ require f
6
+ end
7
+ end
data/gems.locked ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ property-list (1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.1)
10
+ docile (1.1.5)
11
+ hoe (3.16.1)
12
+ rake (>= 0.8, < 13.0)
13
+ json (2.1.0)
14
+ method_source (0.8.2)
15
+ pry (0.10.4)
16
+ coderay (~> 1.1.0)
17
+ method_source (~> 0.8.1)
18
+ slop (~> 3.4)
19
+ rake (12.0.0)
20
+ simplecov (0.14.1)
21
+ docile (~> 1.1.0)
22
+ json (>= 1.8, < 3)
23
+ simplecov-html (~> 0.10.0)
24
+ simplecov-html (0.10.2)
25
+ slop (3.6.0)
26
+ test-unit (1.2.3)
27
+ hoe (>= 1.5.1)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.15.4)
34
+ property-list!
35
+ pry (~> 0.10.4)
36
+ rake (~> 12.0.0)
37
+ simplecov (~> 0.14.1)
38
+ test-unit (~> 1.2.3)
39
+
40
+ BUNDLED WITH
41
+ 1.15.4
data/gems.rb ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,83 @@
1
+ require 'date'
2
+ require 'set'
3
+ require 'base64'
4
+ require 'cgi'
5
+ require 'stringio'
6
+ require 'strscan'
7
+
8
+ require_relative 'property-list/xml_generator'
9
+ require_relative 'property-list/xml_parser'
10
+ require_relative 'property-list/ascii_generator'
11
+ require_relative 'property-list/ascii_parser'
12
+ require_relative 'property-list/binary_markers'
13
+ require_relative 'property-list/binary_generator'
14
+ require_relative 'property-list/binary_parser'
15
+ require_relative 'property-list/version'
16
+
17
+ # === Load a plist file
18
+ #
19
+ # PropertyList.load_xml File.read "some_plist.xml"
20
+ # PropertyList.load_binary File.binread "some_binary.plist"
21
+ # PropertyList.load_ascii File.read "some_ascii.strings"
22
+ # PropertyList.load File.binread "unknown_format.plist"
23
+ #
24
+ # === Generate a plist file data
25
+ #
26
+ # PropertyList.dump_xml obj
27
+ # PropertyList.dump_binary obj
28
+ # PropertyList.dump_ascii obj
29
+ #
30
+ module PropertyList
31
+ # load plist (binary or xml or ascii) into a ruby object
32
+ # auto detect the format
33
+ def self.load data
34
+ case data.byteslice(0, 8)
35
+ when /\Abplist\d\d/n
36
+ load_binary data.force_encoding('binary')
37
+ when /\A<\?xml\ /n
38
+ load_xml data.force_encoding('utf-8')
39
+ else
40
+ load_ascii data.force_encoding('utf-8')
41
+ end
42
+ end
43
+
44
+ class SyntaxError < RuntimeError
45
+ end
46
+
47
+ # binary plist v0x elements:
48
+
49
+ class UID
50
+ def initialize uid
51
+ @uid = uid
52
+ end
53
+ attr_reader :uid
54
+ end
55
+
56
+ # binary plist v1x elements:
57
+
58
+ class URL
59
+ def initialize url
60
+ @url = url
61
+ end
62
+ attr_reader :url
63
+ end
64
+
65
+ class UUID
66
+ def initialize uuid
67
+ @uuid = uuid
68
+ end
69
+ attr_reader :uuid
70
+ end
71
+
72
+ class OrderedSet
73
+ def initialize elements
74
+ @elements = elements.uniq
75
+ end
76
+
77
+ def each
78
+ @elements.each do |e|
79
+ yield e
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,148 @@
1
+ module PropertyList
2
+ # options can be:
3
+ #
4
+ # [:indent_unit] the indent unit, default value is <code>"\t"</code>, set to <code>''</code> if you don't need indent
5
+ #
6
+ # [:initial_indent] initial indent space, default is <code>''</code>, the indentation per line equals to <code>initial_indent + indent * current_indent_level</code>
7
+ #
8
+ # [:wrap] wrap the top level output with '{}' when obj is a Hash, default is true.
9
+ #
10
+ # [:encoding_comment] add encoding comment '!$*UTF8*$!' on top of file, default is false
11
+ #
12
+ # [:sort_keys] sort dict keys, default is true
13
+ #
14
+ def self.dump_ascii obj, indent_unit: "\t", initial_indent: '', wrap: true, encoding_comment: false, sort_keys: true
15
+ generator = AsciiGenerator.new indent_unit: indent_unit, initial_indent: initial_indent, sort_keys: sort_keys
16
+ generator.output << "// !$*UTF8*$!\n" if encoding_comment
17
+ generator.generate obj, wrap
18
+ generator.output << "\n" if wrap and obj.is_a?(Hash)
19
+ generator.output.join
20
+ end
21
+
22
+ class AsciiGenerator #:nodoc:
23
+ def initialize indent_unit: "\t", initial_indent: '', sort_keys: true
24
+ @indent_unit = indent_unit
25
+ @indent_level = 0
26
+ @initial_indent = initial_indent
27
+ @indent = @initial_indent + @indent_unit * @indent_level
28
+ @sort_keys = sort_keys
29
+ @output = []
30
+ end
31
+ attr_reader :output
32
+
33
+ def generate object, wrap=true
34
+ # See also
35
+ # http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html
36
+ # The <...> extensions are from GNUStep
37
+
38
+ case object
39
+ when Array
40
+ ascii_collection '(', ')' do
41
+ object.each do |e|
42
+ generate e
43
+ @output << ",\n"
44
+ end
45
+ end
46
+ when Hash
47
+ if wrap
48
+ ascii_collection '{', '}' do
49
+ ascii_hash_content object
50
+ end
51
+ else
52
+ ascii_hash_content object
53
+ end
54
+ when true
55
+ ascii_value "<*BY>"
56
+ when false
57
+ ascii_value "<*BN>"
58
+ when Float
59
+ if object.to_i == object
60
+ object = object.to_i
61
+ end
62
+ ascii_value "<*R#{object}>"
63
+ when Integer
64
+ ascii_value "<*I#{object}>"
65
+ when Time, Date # also covers DateTime
66
+ ascii_value "<*D#{object.strftime '%Y-%m-%d %H:%M:%S %z'}>"
67
+ when String
68
+ ascii_string object
69
+ when Symbol
70
+ ascii_string object.to_s
71
+ when IO, StringIO
72
+ object.rewind
73
+ contents = object.read
74
+ ascii_data contents
75
+ else
76
+ raise "Generating of this class is not supported"
77
+ end
78
+ end
79
+
80
+ def ascii_value v
81
+ @output << @indent
82
+ @output << v
83
+ end
84
+
85
+ TABLE_FOR_ASCII_STRING_ESCAPE = {
86
+ "\\".ord => "\\\\",
87
+ '"'.ord => '\\"',
88
+ "\b".ord => '\b',
89
+ "\n".ord => "\\n",
90
+ "\r".ord => "\\r",
91
+ "\t".ord => "\\t"
92
+ }.freeze
93
+ def ascii_string s
94
+ @output << @indent
95
+
96
+ if s =~ /\A[\w\.\/]+\z/
97
+ @output << s
98
+ return
99
+ end
100
+
101
+ # there is also single quote string, in which we can use new lines and '' for single quote
102
+ @output << '"'
103
+ s.unpack('U*').each do |c|
104
+ if c > 127
105
+ # there is also a \OOO format, but we only generate the \U format
106
+ @output << "\\U#{c.to_s(16).rjust 4, '0'}"
107
+ elsif (escaped_c = TABLE_FOR_ASCII_STRING_ESCAPE[c])
108
+ @output << escaped_c
109
+ else
110
+ @output << c.chr
111
+ end
112
+ end
113
+ @output << '"'
114
+ end
115
+
116
+ def ascii_collection start_delim, end_delim
117
+ @output << @indent
118
+ @output << start_delim
119
+ @output << "\n"
120
+ @indent = @initial_indent + @indent_unit * (@indent_level += 1)
121
+ yield
122
+ @indent = @initial_indent + @indent_unit * (@indent_level -= 1)
123
+ @output << @indent
124
+ @output << end_delim
125
+ end
126
+
127
+ def ascii_hash_content object
128
+ keys = object.keys
129
+ keys.sort_by! &:to_s if @sort_keys
130
+ keys.each do |k|
131
+ v = object[k]
132
+ reset_indent = @indent
133
+ ascii_string k.to_s
134
+ @output << ' = '
135
+ @indent = '' # ignores the indent for first line
136
+ generate v
137
+ @output << ";\n"
138
+ @indent = reset_indent
139
+ end
140
+ end
141
+
142
+ def ascii_data content
143
+ hex, _ = content.unpack 'H*'
144
+ hex.gsub! /(.{2})/, "\\1 "
145
+ @output << "< #{hex}>"
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,217 @@
1
+ module PropertyList
2
+ def self.load_ascii(data)
3
+ AsciiParser.new(data).parse
4
+ end
5
+
6
+ class AsciiParser
7
+ def initialize src
8
+ @lexer = StringScanner.new src.strip
9
+ end
10
+
11
+ def parse
12
+ res = parse_object
13
+ skip_space_and_comment
14
+ if !@lexer.eos? or res.nil?
15
+ syntax_error "Unrecognized token"
16
+ end
17
+ res
18
+ end
19
+
20
+ def skip_space_and_comment
21
+ @lexer.skip(%r{(?:
22
+ [\x0A\x0D\u2028\u2029\x09\x0B\x0C\x20]+ # newline and space
23
+ |
24
+ //[^\x0A\x0D\u2028\u2029]* # one-line comment
25
+ |
26
+ /\*(?:.*?)\*/ # multi-line comment
27
+ )+}mx)
28
+ end
29
+
30
+ def parse_object
31
+ skip_space_and_comment
32
+ case @lexer.peek(1)
33
+ when '{'
34
+ parse_dict
35
+ when '('
36
+ parse_array
37
+ when '"'
38
+ parse_string '"'
39
+ when "'"
40
+ parse_string "'" # NOTE: not in GNU extension
41
+ when '<'
42
+ parse_extension_value
43
+ when /[\w\.\/]/
44
+ parse_unquoted_string
45
+ end
46
+ end
47
+
48
+ def parse_dict
49
+ @lexer.pos += 1
50
+ hash = {}
51
+ while (skip_space_and_comment; @lexer.peek(1) != '}')
52
+ k = \
53
+ case @lexer.peek(1)
54
+ when '"'
55
+ parse_string '"'
56
+ when "'"
57
+ parse_string "'"
58
+ when /\w/
59
+ parse_unquoted_string
60
+ end
61
+ if !k
62
+ syntax_error "Expect dictionary key"
63
+ end
64
+
65
+ skip_space_and_comment
66
+ if !@lexer.scan(/=/)
67
+ syntax_error "Expect '=' after dictionary key"
68
+ end
69
+ skip_space_and_comment
70
+
71
+ v = parse_object
72
+ if v.nil?
73
+ syntax_error "Expect dictionary value"
74
+ end
75
+
76
+ skip_space_and_comment
77
+ if !@lexer.scan(/;/)
78
+ syntax_error "Expect ';' after dictionary value"
79
+ end
80
+ skip_space_and_comment
81
+
82
+ hash[k] = v
83
+ end
84
+ if @lexer.getch != '}'
85
+ syntax_error "Unclosed hash"
86
+ end
87
+ hash
88
+ end
89
+
90
+ def parse_array
91
+ @lexer.pos += 1
92
+ array = []
93
+ while (skip_space_and_comment; @lexer.peek(1) != ')')
94
+ obj = parse_object
95
+ if obj.nil?
96
+ syntax_error "Failed to parse array element"
97
+ end
98
+ array << obj
99
+ skip_space_and_comment
100
+ @lexer.scan(/,/)
101
+ end
102
+ if @lexer.getch != ')'
103
+ syntax_error "Unclosed array"
104
+ end
105
+ array
106
+ end
107
+
108
+ def parse_string delim
109
+ @lexer.pos += 1
110
+
111
+ # TODO (TextMate only, xcode cannot parse it) when delim is ', '' is the escape
112
+
113
+ chars = []
114
+ while (ch = @lexer.getch) != delim
115
+ case ch
116
+ when '\\'
117
+ case @lexer.getch
118
+ when '\\'
119
+ chars << '\\'
120
+ when '"'
121
+ chars << '"'
122
+ when "'"
123
+ chars << "'"
124
+ when 'b'
125
+ chars << "\b"
126
+ when 'n'
127
+ chars << "\n"
128
+ when 'r'
129
+ chars << "\r"
130
+ when 't'
131
+ chars << "\t"
132
+ when 'U'
133
+ if (hex = @lexer.scan /[0-9a-h]{4}/i)
134
+ chars << [hex].pack('U')
135
+ else
136
+ syntax_error "Expect 4 digit hex code"
137
+ end
138
+ else
139
+ if (oct = @lexer.scan /[0-7]{3}/)
140
+ chars << [oct.to_i(8)].pack('U')
141
+ else
142
+ syntax_error "Expect 3 digit oct code"
143
+ end
144
+ end
145
+ else
146
+ chars << ch
147
+ end
148
+ end
149
+ chars.join
150
+ end
151
+
152
+ def parse_unquoted_string
153
+ @lexer.scan /[\w\.\/]+/
154
+ end
155
+
156
+ def parse_extension_value
157
+ @lexer.pos += 1
158
+
159
+ case @lexer.peek(2)
160
+ when '*D' # date
161
+ @lexer.pos += 2
162
+ if (d = @lexer.scan /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [+\-]\d{4}\>/)
163
+ Date.strptime d.chop, "%Y-%m-%d %H:%M:%S %z"
164
+ else
165
+ syntax_error "Expect date value"
166
+ end
167
+
168
+ when '*I' # integer
169
+ @lexer.pos += 2
170
+ if (i = @lexer.scan /[\+\-]?\d+\>/)
171
+ i.chop.to_i
172
+ else
173
+ syntax_error "Expect integer value"
174
+ end
175
+
176
+ when '*R' # real
177
+ @lexer.pos += 2
178
+ if (r = @lexer.scan /[\+\-]?\d+(\.\d+)?([eE][\+\-]?\d+)?\>/)
179
+ r.chop.to_f
180
+ else
181
+ syntax_error "Expect real value"
182
+ end
183
+
184
+ when '*B' # boolean
185
+ @lexer.pos += 2
186
+ case @lexer.scan(/[YN]\>/)
187
+ when 'Y>'
188
+ true
189
+ when 'N>'
190
+ false
191
+ else
192
+ syntax_error "Expect boolean value"
193
+ end
194
+
195
+ else
196
+ parse_data
197
+ end
198
+ end
199
+
200
+ def parse_data
201
+ if (h = @lexer.scan /[0-9a-f\s]*\>/i)
202
+ h = h.gsub /[\s\>]/, ''
203
+ data = [h].pack 'H*'
204
+ StringIO.new data
205
+ else
206
+ syntax_error "Expect hex value"
207
+ end
208
+ end
209
+
210
+ def syntax_error msg
211
+ pre = @lexer.string[0...@lexer.pos]
212
+ line = pre.count("\n") + 1
213
+ col = pre.size - pre.rindex("\n")
214
+ raise SyntaxError, msg + " at line: #{line} col: #{col} #{@lexer.inspect}", caller
215
+ end
216
+ end
217
+ end