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 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