nanaimo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module Nanaimo
2
+ VERSION = '0.1.0'.freeze
3
+ 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
@@ -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