nanaimo 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 037701ffb338db5f3836b57e4537fd589c8dccff
4
- data.tar.gz: ab581715b5317779e1873d7840c85d8b485eb56d
3
+ metadata.gz: 14c576818f92d08020d63bfcc8d90840f37ec30d
4
+ data.tar.gz: bca04f1ce7e922722502091d131a6000285352cc
5
5
  SHA512:
6
- metadata.gz: 031ff566d32a070b3dcace79926ba7cbb9efb214f20a733fc753d591ed668e35289f72b9c85197d0ccac77237543073669d8b8564c5f939aca1d3b85929cfd6a
7
- data.tar.gz: 95e44f16c8b0724e16096f364d591762082c9aec7521a5c379cb6a69807031b08c88ee1b45ea8c04f0baf2669afd321af2a1e1b27b890a3954c21efd442b8f1b
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nanaimo (0.1.4)
4
+ nanaimo (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/lib/nanaimo.rb CHANGED
@@ -17,5 +17,4 @@ module Nanaimo
17
17
  require 'nanaimo/reader'
18
18
  require 'nanaimo/unicode'
19
19
  require 'nanaimo/writer'
20
- require 'nanaimo/xcode_project_writer'
21
20
  end
@@ -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, "unrecognized characters #{@scanner.rest.inspect} after parsing" unless @scanner.eos?
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 eos while parsing' if @scanner.eos?
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/.$-]+}o)
116
- raise_parser_error ParseError, "not a valid string at index #{@scanner.pos} (char is #{current_character.inspect})"
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, "unterminated quoted string started at #{@scanner.pos}, expected #{quote} but never found it"
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 #{objects} missing ',' in between objects"
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 after key #{key.inspect} at index #{@scanner.pos}, expected '=' and got #{current_character.inspect}"
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 (#{objects}) missing ';' after key-value pair (#{key} = #{value}) at index #{@scanner.pos} (got #{current_character})"
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, "failed to terminate single line comment #{@scanner.rest.inspect}"
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, "#{@scanner.rest.inspect} failed to terminate multiline comment"
237
+ raise_parser_error ParseError, 'Failed to terminate multiline comment'
212
238
  end
213
239
  @scanner.skip(%r{\*/})
214
240
 
@@ -1,3 +1,3 @@
1
1
  module Nanaimo
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -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 = true, output = ::String.new)
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 %r{[^\w\./]}, QuotedString, ''
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 "Cannot write #{object} to an ascii plist"
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
@@ -17,6 +17,10 @@ module Nanaimo
17
17
 
18
18
  private
19
19
 
20
+ def plist_format
21
+ :xml
22
+ end
23
+
20
24
  def write_object(object)
21
25
  case object
22
26
  when Float, Integer
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.1.4
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-01 00:00:00.000000000 Z
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