property-list 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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