nanaimo 0.1.4 → 0.2.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/lib/nanaimo.rb +0 -1
- data/lib/nanaimo/reader.rb +36 -10
- data/lib/nanaimo/version.rb +1 -1
- data/lib/nanaimo/writer.rb +35 -5
- data/lib/nanaimo/writer/pbxproj.rb +78 -0
- data/lib/nanaimo/writer/xml.rb +4 -0
- metadata +3 -3
- data/lib/nanaimo/xcode_project_writer.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14c576818f92d08020d63bfcc8d90840f37ec30d
|
4
|
+
data.tar.gz: bca04f1ce7e922722502091d131a6000285352cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e45beaaddb84a3ec4b076f95429fd2ae3040d48ea1b2d066e31990af2121cc0575faeddb1972d124c8a66bc08444547eb0f36dc87da53f32165d41fe34e60877
|
7
|
+
data.tar.gz: 1600ba9301952b7aac138af098c7caba4157609e8715e8189d167827682f6546f39e7d2ee105f87b0aaf7e4dd9c4f748d4e0abbb2a56e7f2f94d37c582298912
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Nanaimo Changelog
|
2
2
|
|
3
|
+
## 0.2.0 (2016-11-02)
|
4
|
+
|
5
|
+
##### Enhancements
|
6
|
+
|
7
|
+
* Add context to parse errors.
|
8
|
+
[Samuel Giddins](https://github.com/segiddins)
|
9
|
+
[#5](https://github.com/CocoaPods/Nanaimo/issues/5)
|
10
|
+
|
11
|
+
* Allow disabling 'strict' mode when writing plists, allowing unknown object
|
12
|
+
types to be serialized as their string representations.
|
13
|
+
[Samuel Giddins](https://github.com/segiddins)
|
14
|
+
|
15
|
+
##### Bug Fixes
|
16
|
+
|
17
|
+
* None.
|
18
|
+
|
19
|
+
|
3
20
|
## 0.1.4 (2016-11-01)
|
4
21
|
|
5
22
|
##### Enhancements
|
data/Gemfile.lock
CHANGED
data/lib/nanaimo.rb
CHANGED
data/lib/nanaimo/reader.rb
CHANGED
@@ -32,6 +32,32 @@ module Nanaimo
|
|
32
32
|
# @return [String] The contents of the plist.
|
33
33
|
#
|
34
34
|
attr_accessor :plist_string
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"[!] #{super}#{context}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def context(n = 2)
|
41
|
+
line_number, column = location
|
42
|
+
line_number -= 1
|
43
|
+
lines = plist_string.split(NEWLINE)
|
44
|
+
|
45
|
+
s = line_number.succ.to_s.size
|
46
|
+
indent = "#{' ' * s}# "
|
47
|
+
indicator = "#{line_number.succ}> "
|
48
|
+
|
49
|
+
m = ::String.new("\n")
|
50
|
+
m << "#{indent}-------------------------------------------\n"
|
51
|
+
m << lines[[line_number - n, 0].max...line_number].map do |l|
|
52
|
+
"#{indent}#{l}\n"
|
53
|
+
end.join
|
54
|
+
m << "#{indicator}#{lines[line_number]}\n"
|
55
|
+
m << ' ' * (column + s + 2) << "^\n"
|
56
|
+
m << Array(lines[line_number.succ..[lines.count.pred, line_number + n].min]).map do |l|
|
57
|
+
l.strip.empty? ? '' : "#{indent}#{l}\n"
|
58
|
+
end.join
|
59
|
+
m << "#{indent}-------------------------------------------\n"
|
60
|
+
end
|
35
61
|
end
|
36
62
|
|
37
63
|
# @param plist_contents [String]
|
@@ -73,7 +99,7 @@ module Nanaimo
|
|
73
99
|
root_object = parse_object
|
74
100
|
|
75
101
|
eat_whitespace!
|
76
|
-
raise_parser_error ParseError,
|
102
|
+
raise_parser_error ParseError, 'Found additional characters after parsing the root plist object' unless @scanner.eos?
|
77
103
|
|
78
104
|
Nanaimo::Plist.new(root_object, plist_format)
|
79
105
|
end
|
@@ -93,7 +119,7 @@ module Nanaimo
|
|
93
119
|
def parse_object
|
94
120
|
_comment = skip_to_non_space_matching_annotations
|
95
121
|
start_pos = @scanner.pos
|
96
|
-
raise_parser_error ParseError, 'Unexpected
|
122
|
+
raise_parser_error ParseError, 'Unexpected end of string while parsing' if @scanner.eos?
|
97
123
|
if @scanner.skip(/\{/)
|
98
124
|
parse_dictionary
|
99
125
|
elsif @scanner.skip(/\(/)
|
@@ -112,15 +138,15 @@ module Nanaimo
|
|
112
138
|
|
113
139
|
def parse_string
|
114
140
|
eat_whitespace!
|
115
|
-
unless match = @scanner.scan(%r{[\w
|
116
|
-
raise_parser_error ParseError, "
|
141
|
+
unless match = @scanner.scan(%r{[\w/.$]+})
|
142
|
+
raise_parser_error ParseError, "Invalid character #{current_character.inspect} in unquoted string"
|
117
143
|
end
|
118
144
|
Nanaimo::String.new(match, nil)
|
119
145
|
end
|
120
146
|
|
121
147
|
def parse_quotedstring(quote)
|
122
148
|
unless string = @scanner.scan(/(?:([^#{quote}\\]|\\.)*)#{quote}/)
|
123
|
-
raise_parser_error ParseError, "
|
149
|
+
raise_parser_error ParseError, "Unterminated quoted string, expected #{quote} but never found it"
|
124
150
|
end
|
125
151
|
string = Unicode.unquotify_string(string.chomp!(quote))
|
126
152
|
Nanaimo::QuotedString.new(string, nil)
|
@@ -137,7 +163,7 @@ module Nanaimo
|
|
137
163
|
eat_whitespace!
|
138
164
|
break if @scanner.skip(/\)/)
|
139
165
|
unless @scanner.skip(/,/)
|
140
|
-
raise_parser_error ParseError, "Array
|
166
|
+
raise_parser_error ParseError, "Array missing ',' in between objects"
|
141
167
|
end
|
142
168
|
end
|
143
169
|
|
@@ -153,7 +179,7 @@ module Nanaimo
|
|
153
179
|
key = parse_object
|
154
180
|
eat_whitespace!
|
155
181
|
unless @scanner.skip(/=/)
|
156
|
-
raise_parser_error ParseError, "Dictionary missing value
|
182
|
+
raise_parser_error ParseError, "Dictionary missing value for key #{key.as_ruby.inspect}, expected '=' and found #{current_character.inspect}"
|
157
183
|
end
|
158
184
|
|
159
185
|
value = parse_object
|
@@ -162,7 +188,7 @@ module Nanaimo
|
|
162
188
|
eat_whitespace!
|
163
189
|
break if @scanner.skip(/}/)
|
164
190
|
unless @scanner.skip(/;/)
|
165
|
-
raise_parser_error ParseError, "Dictionary
|
191
|
+
raise_parser_error ParseError, "Dictionary missing ';' after key-value pair for #{key.as_ruby.inspect}, found #{current_character.inspect}"
|
166
192
|
end
|
167
193
|
end
|
168
194
|
|
@@ -189,7 +215,7 @@ module Nanaimo
|
|
189
215
|
|
190
216
|
def read_singleline_comment
|
191
217
|
unless comment = @scanner.scan_until(NEWLINE)
|
192
|
-
raise_parser_error ParseError,
|
218
|
+
raise_parser_error ParseError, 'Failed to terminate single line comment'
|
193
219
|
end
|
194
220
|
comment
|
195
221
|
end
|
@@ -208,7 +234,7 @@ module Nanaimo
|
|
208
234
|
|
209
235
|
def read_multiline_comment
|
210
236
|
unless annotation = @scanner.scan(%r{(?:.+?)(?=\*/)}m)
|
211
|
-
raise_parser_error ParseError,
|
237
|
+
raise_parser_error ParseError, 'Failed to terminate multiline comment'
|
212
238
|
end
|
213
239
|
@scanner.skip(%r{\*/})
|
214
240
|
|
data/lib/nanaimo/version.rb
CHANGED
data/lib/nanaimo/writer.rb
CHANGED
@@ -3,8 +3,24 @@ module Nanaimo
|
|
3
3
|
# string representation.
|
4
4
|
#
|
5
5
|
class Writer
|
6
|
+
autoload :PBXProjWriter, 'nanaimo/writer/pbxproj'
|
6
7
|
autoload :XMLWriter, 'nanaimo/writer/xml'
|
7
8
|
|
9
|
+
# Raised when attempting to write a plist containing an object of an
|
10
|
+
# unsupported type.
|
11
|
+
#
|
12
|
+
class UnsupportedPlistTypeError < Error
|
13
|
+
def initialize(plist_format, object)
|
14
|
+
@plist_format = plist_format
|
15
|
+
@object = object
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"Unable to write #{@object.inspect}. " \
|
20
|
+
"`#{@object.class}` is an invalid object type to serialize in a #{@plist_format} plist."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
8
24
|
# The magic comment that denotes a UTF8-encoded plist.
|
9
25
|
#
|
10
26
|
UTF8 = "// !$*UTF8*$!\n".freeze
|
@@ -14,12 +30,13 @@ module Nanaimo
|
|
14
30
|
# spaces and newlines to make output more legible
|
15
31
|
# @param output [#<<] The output stream to write the plist to
|
16
32
|
#
|
17
|
-
def initialize(plist, pretty
|
33
|
+
def initialize(plist, pretty: true, output: ::String.new, strict: true)
|
18
34
|
@plist = plist
|
19
35
|
@pretty = pretty
|
20
36
|
@output = output
|
21
37
|
@indent = 0
|
22
38
|
@newlines = true
|
39
|
+
@strict = strict
|
23
40
|
end
|
24
41
|
|
25
42
|
# Writes the plist to the given output.
|
@@ -30,11 +47,15 @@ module Nanaimo
|
|
30
47
|
write_newline
|
31
48
|
end
|
32
49
|
|
33
|
-
attr_reader :indent, :pretty, :output, :newlines
|
34
|
-
private :indent, :pretty, :output, :newlines
|
50
|
+
attr_reader :indent, :pretty, :output, :newlines, :strict
|
51
|
+
private :indent, :pretty, :output, :newlines, :strict
|
35
52
|
|
36
53
|
private
|
37
54
|
|
55
|
+
def plist_format
|
56
|
+
:ascii
|
57
|
+
end
|
58
|
+
|
38
59
|
def write_utf8
|
39
60
|
output << UTF8
|
40
61
|
end
|
@@ -53,19 +74,28 @@ module Nanaimo
|
|
53
74
|
write_array(object)
|
54
75
|
when Dictionary, ::Hash
|
55
76
|
write_dictionary(object)
|
56
|
-
when
|
77
|
+
when QUOTED_STRING_REGEXP, QuotedString, ''
|
57
78
|
write_quoted_string(object)
|
58
79
|
when String, ::String, Symbol
|
59
80
|
write_string(object)
|
60
81
|
when Data
|
61
82
|
write_data(object)
|
62
83
|
else
|
63
|
-
raise
|
84
|
+
raise UnsupportedPlistTypeError.new(plist_format, object) if strict
|
85
|
+
write_string_quoted_if_necessary(object)
|
64
86
|
end
|
65
87
|
write_annotation(object) if pretty
|
66
88
|
output
|
67
89
|
end
|
68
90
|
|
91
|
+
QUOTED_STRING_REGEXP = %r{[^\w\./]}
|
92
|
+
private_constant :QUOTED_STRING_REGEXP
|
93
|
+
|
94
|
+
def write_string_quoted_if_necessary(object)
|
95
|
+
string = object.to_s
|
96
|
+
string =~ QUOTED_STRING_REGEXP ? write_quoted_string(string) : write_string(string)
|
97
|
+
end
|
98
|
+
|
69
99
|
def write_string(object)
|
70
100
|
output << value_for(object).to_s
|
71
101
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Nanaimo
|
2
|
+
class Writer
|
3
|
+
# Transforms native ruby objects or Plist objects into their ASCII Plist
|
4
|
+
# string representation, formatted as Xcode writes Xcode projects.
|
5
|
+
#
|
6
|
+
class PBXProjWriter < Writer
|
7
|
+
ISA = String.new('isa', '')
|
8
|
+
private_constant :ISA
|
9
|
+
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@objects_section = false
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def write_dictionary(object)
|
18
|
+
n = newlines
|
19
|
+
@newlines = false if flat_dictionary?(object)
|
20
|
+
return super(sort_dictionary(object)) unless @objects_section
|
21
|
+
@objects_section = false
|
22
|
+
write_dictionary_start
|
23
|
+
value = value_for(object)
|
24
|
+
objects_by_isa = value.group_by { |_k, v| isa_for(v) }
|
25
|
+
objects_by_isa.each do |isa, kvs|
|
26
|
+
write_newline
|
27
|
+
output << "/* Begin #{isa} section */"
|
28
|
+
write_newline
|
29
|
+
sort_dictionary(kvs).each do |k, v|
|
30
|
+
write_dictionary_key_value_pair(k, v)
|
31
|
+
end
|
32
|
+
output << "/* End #{isa} section */"
|
33
|
+
write_newline
|
34
|
+
end
|
35
|
+
write_dictionary_end
|
36
|
+
ensure
|
37
|
+
@newlines = n
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_dictionary_key_value_pair(k, v)
|
41
|
+
@objects_section = true if value_for(k) == 'objects'
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def sort_dictionary(dictionary)
|
46
|
+
hash = value_for(dictionary)
|
47
|
+
hash.to_a.sort do |(k1, v1), (k2, v2)|
|
48
|
+
v2_isa = isa_for(v2)
|
49
|
+
v1_isa = v2_isa && isa_for(v1)
|
50
|
+
comp = v1_isa <=> v2_isa
|
51
|
+
next comp if !comp.zero? && v1_isa
|
52
|
+
|
53
|
+
key1 = value_for(k1)
|
54
|
+
key2 = value_for(k2)
|
55
|
+
next -1 if key1 == 'isa'
|
56
|
+
next 1 if key2 == 'isa'
|
57
|
+
key1 <=> key2
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def isa_for(dictionary)
|
62
|
+
dictionary = value_for(dictionary)
|
63
|
+
return unless dictionary.is_a?(Hash)
|
64
|
+
isa = dictionary.values_at('isa', ISA).map(&method(:value_for)).compact.first
|
65
|
+
isa && value_for(isa)
|
66
|
+
end
|
67
|
+
|
68
|
+
def flat_dictionary?(dictionary)
|
69
|
+
case isa_for(dictionary)
|
70
|
+
when 'PBXBuildFile', 'PBXFileReference'
|
71
|
+
true
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/nanaimo/writer/xml.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nanaimo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danielle Tomlinson
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-11-
|
12
|
+
date: 2016-11-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -84,8 +84,8 @@ files:
|
|
84
84
|
- lib/nanaimo/unicode/quote_maps.rb
|
85
85
|
- lib/nanaimo/version.rb
|
86
86
|
- lib/nanaimo/writer.rb
|
87
|
+
- lib/nanaimo/writer/pbxproj.rb
|
87
88
|
- lib/nanaimo/writer/xml.rb
|
88
|
-
- lib/nanaimo/xcode_project_writer.rb
|
89
89
|
- nanaimo.gemspec
|
90
90
|
homepage: https://github.com/CocoaPods/Nanaimo
|
91
91
|
licenses:
|
@@ -1,76 +0,0 @@
|
|
1
|
-
module Nanaimo
|
2
|
-
# Transforms native ruby objects or Plist objects into their ASCII Plist
|
3
|
-
# string representation, formatted as Xcode writes Xcode projects.
|
4
|
-
#
|
5
|
-
class XcodeProjectWriter < Writer
|
6
|
-
ISA = String.new('isa', '')
|
7
|
-
private_constant :ISA
|
8
|
-
|
9
|
-
def initialize(*)
|
10
|
-
super
|
11
|
-
@objects_section = false
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def write_dictionary(object)
|
17
|
-
n = newlines
|
18
|
-
@newlines = false if flat_dictionary?(object)
|
19
|
-
return super(sort_dictionary(object)) unless @objects_section
|
20
|
-
@objects_section = false
|
21
|
-
write_dictionary_start
|
22
|
-
value = value_for(object)
|
23
|
-
objects_by_isa = value.group_by { |_k, v| isa_for(v) }
|
24
|
-
objects_by_isa.each do |isa, kvs|
|
25
|
-
write_newline
|
26
|
-
output << "/* Begin #{isa} section */"
|
27
|
-
write_newline
|
28
|
-
sort_dictionary(kvs).each do |k, v|
|
29
|
-
write_dictionary_key_value_pair(k, v)
|
30
|
-
end
|
31
|
-
output << "/* End #{isa} section */"
|
32
|
-
write_newline
|
33
|
-
end
|
34
|
-
write_dictionary_end
|
35
|
-
ensure
|
36
|
-
@newlines = n
|
37
|
-
end
|
38
|
-
|
39
|
-
def write_dictionary_key_value_pair(k, v)
|
40
|
-
@objects_section = true if value_for(k) == 'objects'
|
41
|
-
super
|
42
|
-
end
|
43
|
-
|
44
|
-
def sort_dictionary(dictionary)
|
45
|
-
hash = value_for(dictionary)
|
46
|
-
hash.to_a.sort do |(k1, v1), (k2, v2)|
|
47
|
-
v2_isa = isa_for(v2)
|
48
|
-
v1_isa = v2_isa && isa_for(v1)
|
49
|
-
comp = v1_isa <=> v2_isa
|
50
|
-
next comp if !comp.zero? && v1_isa
|
51
|
-
|
52
|
-
key1 = value_for(k1)
|
53
|
-
key2 = value_for(k2)
|
54
|
-
next -1 if key1 == 'isa'
|
55
|
-
next 1 if key2 == 'isa'
|
56
|
-
key1 <=> key2
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def isa_for(dictionary)
|
61
|
-
dictionary = value_for(dictionary)
|
62
|
-
return unless dictionary.is_a?(Hash)
|
63
|
-
isa = dictionary.values_at('isa', ISA).map(&method(:value_for)).compact.first
|
64
|
-
isa && value_for(isa)
|
65
|
-
end
|
66
|
-
|
67
|
-
def flat_dictionary?(dictionary)
|
68
|
-
case isa_for(dictionary)
|
69
|
-
when 'PBXBuildFile', 'PBXFileReference'
|
70
|
-
true
|
71
|
-
else
|
72
|
-
false
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|