nanaimo 0.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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +72 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nanaimo.rb +21 -0
- data/lib/nanaimo/object.rb +100 -0
- data/lib/nanaimo/plist.rb +33 -0
- data/lib/nanaimo/reader.rb +252 -0
- data/lib/nanaimo/unicode.rb +88 -0
- data/lib/nanaimo/unicode/next_step_mapping.rb +136 -0
- data/lib/nanaimo/unicode/quote_maps.rb +56 -0
- data/lib/nanaimo/version.rb +3 -0
- data/lib/nanaimo/writer.rb +174 -0
- data/lib/nanaimo/writer/xml.rb +131 -0
- data/lib/nanaimo/xcode_project_writer.rb +76 -0
- data/nanaimo.gemspec +24 -0
- metadata +114 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
module Nanaimo
|
3
|
+
module Unicode
|
4
|
+
QUOTE_MAP = {
|
5
|
+
"\a" => "\\a",
|
6
|
+
"\b" => "\\b",
|
7
|
+
"\f" => "\\f",
|
8
|
+
"\r" => "\\r",
|
9
|
+
"\t" => "\\t",
|
10
|
+
"\v" => "\\v",
|
11
|
+
"\n" => "\\n",
|
12
|
+
"'" => "\\'",
|
13
|
+
"\"" => "\\\"",
|
14
|
+
"\x00" => "\\U0000",
|
15
|
+
"\x01" => "\\U0001",
|
16
|
+
"\x02" => "\\U0002",
|
17
|
+
"\x03" => "\\U0003",
|
18
|
+
"\x04" => "\\U0004",
|
19
|
+
"\x05" => "\\U0005",
|
20
|
+
"\x06" => "\\U0006",
|
21
|
+
"\x0E" => "\\U000e",
|
22
|
+
"\x0F" => "\\U000f",
|
23
|
+
"\x10" => "\\U0010",
|
24
|
+
"\x11" => "\\U0011",
|
25
|
+
"\x12" => "\\U0012",
|
26
|
+
"\x13" => "\\U0013",
|
27
|
+
"\x14" => "\\U0014",
|
28
|
+
"\x15" => "\\U0015",
|
29
|
+
"\x16" => "\\U0016",
|
30
|
+
"\x17" => "\\U0017",
|
31
|
+
"\x18" => "\\U0018",
|
32
|
+
"\x19" => "\\U0019",
|
33
|
+
"\x1A" => "\\U001a",
|
34
|
+
"\e" => "\\U001b",
|
35
|
+
"\x1C" => "\\U001c",
|
36
|
+
"\x1D" => "\\U001d",
|
37
|
+
"\x1E" => "\\U001e",
|
38
|
+
"\x1F" => "\\U001f",
|
39
|
+
}.freeze
|
40
|
+
|
41
|
+
UNQUOTE_MAP = {
|
42
|
+
"\n" => "\n",
|
43
|
+
"a" => "\a",
|
44
|
+
"b" => "\b",
|
45
|
+
"f" => "\f",
|
46
|
+
"r" => "\r",
|
47
|
+
"t" => "\t",
|
48
|
+
"v" => "\v",
|
49
|
+
"n" => "\n",
|
50
|
+
"'" => "'",
|
51
|
+
"\"" => "\"",
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
QUOTE_REGEXP = /\x07|\x08|\f|\r|\t|\v|\n|'|"|\x00|\x01|\x02|\x03|\x04|\x05|\x06|\x0E|\x0F|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1A|\x1B|\x1C|\x1D|\x1E|\x1F/
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Nanaimo
|
2
|
+
# Transforms native ruby objects or Plist objects into their ASCII Plist
|
3
|
+
# string representation.
|
4
|
+
#
|
5
|
+
class Writer
|
6
|
+
autoload :XMLWriter, 'nanaimo/writer/xml'
|
7
|
+
|
8
|
+
# The magic comment that denotes a UTF8-encoded plist.
|
9
|
+
#
|
10
|
+
UTF8 = "// !$*UTF8*$!\n".freeze
|
11
|
+
|
12
|
+
# @param plist [Plist,String,Hash,Array] The plist obejct to write
|
13
|
+
# @param pretty [Boolean] Whether to serialize annotations and add
|
14
|
+
# spaces and newlines to make output more legible
|
15
|
+
# @param output [#<<] The output stream to write the plist to
|
16
|
+
#
|
17
|
+
def initialize(plist, pretty = true, output = ::String.new)
|
18
|
+
@plist = plist
|
19
|
+
@pretty = pretty
|
20
|
+
@output = output
|
21
|
+
@indent = 0
|
22
|
+
@newlines = true
|
23
|
+
end
|
24
|
+
|
25
|
+
# Writes the plist to the given output.
|
26
|
+
#
|
27
|
+
def write
|
28
|
+
write_utf8
|
29
|
+
write_object(@plist.root_object)
|
30
|
+
write_newline
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :indent, :pretty, :output, :newlines
|
34
|
+
private :indent, :pretty, :output, :newlines
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def write_utf8
|
39
|
+
output << UTF8
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_newline
|
43
|
+
output << if newlines
|
44
|
+
"\n"
|
45
|
+
else
|
46
|
+
' '
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_object(object)
|
51
|
+
case object
|
52
|
+
when Array, ::Array
|
53
|
+
write_array(object)
|
54
|
+
when Dictionary, ::Hash
|
55
|
+
write_dictionary(object)
|
56
|
+
when %r{[^\w\./]}, QuotedString, ''
|
57
|
+
write_quoted_string(object)
|
58
|
+
when String, ::String, Symbol
|
59
|
+
write_string(object)
|
60
|
+
when Data
|
61
|
+
write_data(object)
|
62
|
+
else
|
63
|
+
raise "Cannot write #{object} to an ascii plist"
|
64
|
+
end
|
65
|
+
write_annotation(object) if pretty
|
66
|
+
output
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_string(object)
|
70
|
+
output << value_for(object).to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_quoted_string(object)
|
74
|
+
output << '"' << Unicode.quotify_string(value_for(object)) << '"'
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_data(object)
|
78
|
+
output << '<'
|
79
|
+
value_for(object).unpack('H*').first.chars.each_with_index do |c, i|
|
80
|
+
output << "\n" if i > 0 && (i % 16).zero?
|
81
|
+
output << ' ' if i > 0 && (i % 4).zero?
|
82
|
+
output << c
|
83
|
+
end
|
84
|
+
output << '>'
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_array(object)
|
88
|
+
write_array_start
|
89
|
+
value = value_for(object)
|
90
|
+
value.each do |v|
|
91
|
+
write_array_element(v)
|
92
|
+
end
|
93
|
+
write_array_end
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_array_start
|
97
|
+
output << '('
|
98
|
+
write_newline if newlines
|
99
|
+
push_indent!
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_array_end
|
103
|
+
pop_indent!
|
104
|
+
write_indent
|
105
|
+
output << ')'
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_array_element(object)
|
109
|
+
write_indent
|
110
|
+
write_object(object)
|
111
|
+
output << ','
|
112
|
+
write_newline
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_dictionary(object)
|
116
|
+
write_dictionary_start
|
117
|
+
value = value_for(object)
|
118
|
+
value.each do |key, val|
|
119
|
+
write_dictionary_key_value_pair(key, val)
|
120
|
+
end
|
121
|
+
write_dictionary_end
|
122
|
+
end
|
123
|
+
|
124
|
+
def write_dictionary_start
|
125
|
+
output << '{'
|
126
|
+
write_newline if newlines
|
127
|
+
push_indent!
|
128
|
+
end
|
129
|
+
|
130
|
+
def write_dictionary_end
|
131
|
+
pop_indent!
|
132
|
+
write_indent
|
133
|
+
output << '}'
|
134
|
+
end
|
135
|
+
|
136
|
+
def write_dictionary_key_value_pair(key, value)
|
137
|
+
write_indent
|
138
|
+
write_object(key)
|
139
|
+
output << ' = '
|
140
|
+
write_object(value)
|
141
|
+
output << ';'
|
142
|
+
write_newline
|
143
|
+
end
|
144
|
+
|
145
|
+
def write_annotation(object)
|
146
|
+
return output unless object.is_a?(Nanaimo::Object)
|
147
|
+
annotation = object.annotation
|
148
|
+
return output unless annotation && !annotation.empty?
|
149
|
+
output << " /*#{annotation}*/"
|
150
|
+
end
|
151
|
+
|
152
|
+
def value_for(object)
|
153
|
+
if object.is_a?(Nanaimo::Object)
|
154
|
+
object.value
|
155
|
+
else
|
156
|
+
object
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def push_indent!
|
161
|
+
@indent += 1
|
162
|
+
end
|
163
|
+
|
164
|
+
def pop_indent!
|
165
|
+
@indent -= 1
|
166
|
+
@indent = 0 if @indent < 0
|
167
|
+
end
|
168
|
+
|
169
|
+
def write_indent
|
170
|
+
output << "\t" * indent if newlines
|
171
|
+
output
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Nanaimo
|
2
|
+
class Writer
|
3
|
+
# Transforms native ruby objects or Plist objects into their XML Plist
|
4
|
+
# string representation.
|
5
|
+
#
|
6
|
+
class XMLWriter < Writer
|
7
|
+
autoload :Base64, 'base64'
|
8
|
+
|
9
|
+
def write
|
10
|
+
write_xml_header
|
11
|
+
write_object(@plist.root_object)
|
12
|
+
write_newline
|
13
|
+
write_xml_footer
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def write_object(object)
|
19
|
+
case object
|
20
|
+
when Float, Integer
|
21
|
+
write_number(object)
|
22
|
+
when Time, Date, DateTime
|
23
|
+
write_date(object)
|
24
|
+
when true, false
|
25
|
+
write_boolean(object)
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_xml_header
|
32
|
+
output << <<-EOS
|
33
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
34
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
35
|
+
<plist version="1.0">
|
36
|
+
EOS
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_xml_footer
|
40
|
+
output << <<-EOS
|
41
|
+
</plist>
|
42
|
+
EOS
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_annotation(_)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_number(object)
|
49
|
+
type = object.integer? ? 'integer' : 'real'
|
50
|
+
output << "<#{type}>#{object}</#{type}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_boolean(object)
|
54
|
+
output << "<#{object}/>"
|
55
|
+
end
|
56
|
+
|
57
|
+
def write_date(object)
|
58
|
+
output << '<date>' << object.iso8601 << '</date>'
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_string(object)
|
62
|
+
output << '<string>' << Unicode.xml_escape_string(value_for(object)) << '</string>'
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_quoted_string(object)
|
66
|
+
write_string(object)
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_data(object)
|
70
|
+
output << '<data>'
|
71
|
+
data = Base64.encode64(value_for(object)).delete("\n")
|
72
|
+
data = data.scan(/.{1,76}/).join("\n") if pretty
|
73
|
+
output << data << '</data>'
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_array(object)
|
77
|
+
return output << '<array/>' if value_for(object).empty?
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
def write_array_start
|
82
|
+
output << '<array>'
|
83
|
+
write_newline if newlines
|
84
|
+
push_indent!
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_array_end
|
88
|
+
pop_indent!
|
89
|
+
write_indent
|
90
|
+
output << '</array>'
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_array_element(object)
|
94
|
+
write_indent
|
95
|
+
write_object(object)
|
96
|
+
write_newline
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_dictionary(object)
|
100
|
+
object = value_for(object)
|
101
|
+
return output << '<dict/>' if object.empty?
|
102
|
+
keys = object.keys.sort_by(&:to_s)
|
103
|
+
object = keys.each_with_object({}) do |key, hash|
|
104
|
+
hash[key.to_s] = object[key]
|
105
|
+
end
|
106
|
+
super(object)
|
107
|
+
end
|
108
|
+
|
109
|
+
def write_dictionary_start
|
110
|
+
output << '<dict>'
|
111
|
+
write_newline if newlines
|
112
|
+
push_indent!
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_dictionary_end
|
116
|
+
pop_indent!
|
117
|
+
write_indent
|
118
|
+
output << '</dict>'
|
119
|
+
end
|
120
|
+
|
121
|
+
def write_dictionary_key_value_pair(key, value)
|
122
|
+
write_indent
|
123
|
+
output << '<key>' << Unicode.xml_escape_string(value_for(key)) << '</key>'
|
124
|
+
write_newline
|
125
|
+
write_indent
|
126
|
+
write_object(value)
|
127
|
+
write_newline
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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
|
data/nanaimo.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nanaimo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'nanaimo'
|
8
|
+
spec.version = Nanaimo::VERSION
|
9
|
+
spec.authors = ['Danielle Tomlinson', 'Samuel Giddins']
|
10
|
+
spec.email = ['dan@tomlinson.io', 'segiddins@segiddins.me']
|
11
|
+
|
12
|
+
spec.summary = 'A library for (de)serialization of ASCII Plists.'
|
13
|
+
spec.homepage = 'https://github.com/CocoaPods/Nanaimo'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
end
|