vendor 0.0.1
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.
- data/.gitignore +12 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +68 -0
- data/LICENCE +19 -0
- data/Rakefile +11 -0
- data/Readme.markdown +109 -0
- data/bin/vendor +62 -0
- data/lib/vendor/api.rb +49 -0
- data/lib/vendor/cli/auth.rb +38 -0
- data/lib/vendor/cli.rb +23 -0
- data/lib/vendor/config.rb +51 -0
- data/lib/vendor/extensions/array.rb +7 -0
- data/lib/vendor/extensions/fixnum.rb +7 -0
- data/lib/vendor/extensions/hash.rb +10 -0
- data/lib/vendor/extensions/string.rb +11 -0
- data/lib/vendor/plist.rb +255 -0
- data/lib/vendor/vendor_spec/builder.rb +83 -0
- data/lib/vendor/vendor_spec/dsl.rb +39 -0
- data/lib/vendor/vendor_spec/loader.rb +23 -0
- data/lib/vendor/version.rb +5 -0
- data/lib/vendor/xcode/object.rb +102 -0
- data/lib/vendor/xcode/objects/pbx_build_file.rb +9 -0
- data/lib/vendor/xcode/objects/pbx_container_item_proxy.rb +8 -0
- data/lib/vendor/xcode/objects/pbx_file_reference.rb +21 -0
- data/lib/vendor/xcode/objects/pbx_frameworks_build_phase.rb +9 -0
- data/lib/vendor/xcode/objects/pbx_group.rb +13 -0
- data/lib/vendor/xcode/objects/pbx_native_target.rb +11 -0
- data/lib/vendor/xcode/objects/pbx_project.rb +12 -0
- data/lib/vendor/xcode/objects/pbx_resources_build_phase.rb +7 -0
- data/lib/vendor/xcode/objects/pbx_shell_script_build_phase.rb +7 -0
- data/lib/vendor/xcode/objects/pbx_sources_build_phase.rb +9 -0
- data/lib/vendor/xcode/objects/pbx_target_dependency.rb +7 -0
- data/lib/vendor/xcode/objects/pbx_variant_group.rb +7 -0
- data/lib/vendor/xcode/objects/xc_build_configuration.rb +7 -0
- data/lib/vendor/xcode/objects/xc_configuration_list.rb +9 -0
- data/lib/vendor/xcode/project.rb +130 -0
- data/lib/vendor.rb +33 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/api_stubs.rb +12 -0
- data/spec/support/resources/files/SecondViewController.h +13 -0
- data/spec/support/resources/files/SecondViewController.m +43 -0
- data/spec/support/resources/projects/ProjectWithSpecs/ProjectWithSpecs.xcodeproj/project.pbxproj +320 -0
- data/spec/support/resources/projects/TabBarWithUnitTests/TabBarWithUnitTests.xcodeproj/project.pbxproj +473 -0
- data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.h +18 -0
- data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.m +73 -0
- data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.vendorspec +11 -0
- data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.h +18 -0
- data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.m +73 -0
- data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.vendorspec +11 -0
- data/spec/support/temp_project.rb +21 -0
- data/spec/vendor/api_spec.rb +25 -0
- data/spec/vendor/cli/auth_spec.rb +54 -0
- data/spec/vendor/config_spec.rb +62 -0
- data/spec/vendor/vendor_spec/builder_spec.rb +86 -0
- data/spec/vendor/vendor_spec/dsl_spec.rb +67 -0
- data/spec/vendor/vendor_spec/loader_spec.rb +41 -0
- data/spec/vendor/xcode/object_spec.rb +76 -0
- data/spec/vendor/xcode/objects/pbx_project_spec.rb +26 -0
- data/spec/vendor/xcode/project_spec.rb +211 -0
- data/vendor.gemspec +33 -0
- metadata +234 -0
data/lib/vendor/plist.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
module Vendor
|
2
|
+
|
3
|
+
module Plist
|
4
|
+
|
5
|
+
require "strscan"
|
6
|
+
require "time"
|
7
|
+
require "stringio"
|
8
|
+
|
9
|
+
#
|
10
|
+
# string - the plist string to parse
|
11
|
+
# opts - options (see +AsciiParser.new+)
|
12
|
+
#
|
13
|
+
|
14
|
+
def self.parse_ascii(string, opts = {})
|
15
|
+
AsciiParser.new(string, opts).parse
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Plist::AsciiParser
|
20
|
+
#
|
21
|
+
# Parser for the old style ASCII/NextSTEP property lists.
|
22
|
+
#
|
23
|
+
# Created by Jari Bakken on 2008-08-13.
|
24
|
+
#
|
25
|
+
class AsciiParser < StringScanner
|
26
|
+
|
27
|
+
class ParseError < StandardError; end
|
28
|
+
|
29
|
+
SPACE_REGEXP = %r{ ( /\*.*?\*/ | # block comments
|
30
|
+
//.*?$\n? | # single-line comments
|
31
|
+
\s* )+ # space
|
32
|
+
}mx
|
33
|
+
|
34
|
+
CONTROL_CHAR = {
|
35
|
+
"a" => "\a",
|
36
|
+
"b" => "\b",
|
37
|
+
"n" => "\n",
|
38
|
+
"f" => "\f",
|
39
|
+
"t" => "\t",
|
40
|
+
"r" => "\r",
|
41
|
+
"v" => "\v",
|
42
|
+
}
|
43
|
+
|
44
|
+
BOOLS = {true => "1", false => "0"}
|
45
|
+
|
46
|
+
#
|
47
|
+
# string - the plist string to parse
|
48
|
+
#
|
49
|
+
# options hash:
|
50
|
+
#
|
51
|
+
# :parse_numbers => true/false : Set this to true if you numeric values (float/ints) as the correct Ruby type
|
52
|
+
# :parse_booleans => true/false : Set this to true if you want "true" and "false" to return the boolean Ruby types
|
53
|
+
#
|
54
|
+
# Note: Apple's parsers return strings for all old-style plist types.
|
55
|
+
#
|
56
|
+
|
57
|
+
def initialize(string, opts = {})
|
58
|
+
string = case string
|
59
|
+
when StringIO
|
60
|
+
string.string
|
61
|
+
when IO
|
62
|
+
string.read
|
63
|
+
else
|
64
|
+
string
|
65
|
+
end
|
66
|
+
|
67
|
+
@parse_numbers = opts.delete(:parse_numbers)
|
68
|
+
@parse_bools = opts.delete(:parse_booleans)
|
69
|
+
@debug = $VERBOSE == true # ruby -W3
|
70
|
+
|
71
|
+
raise ArgumentError, "unknown option #{opts.inspect}" unless opts.empty?
|
72
|
+
|
73
|
+
super(string)
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse
|
77
|
+
res = object
|
78
|
+
|
79
|
+
skip_space
|
80
|
+
error "junk after plist" unless eos?
|
81
|
+
|
82
|
+
res
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def object
|
88
|
+
skip_space
|
89
|
+
|
90
|
+
if scan(/\{/) then dictionary
|
91
|
+
elsif scan(/\(/) then array
|
92
|
+
elsif scan(/</) then data
|
93
|
+
elsif scan(/"/) then quoted_string
|
94
|
+
else unquoted_string
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def quoted_string
|
99
|
+
puts "creating quoted string #{inspect}" if @debug
|
100
|
+
|
101
|
+
result = ''
|
102
|
+
|
103
|
+
loop do
|
104
|
+
if scan(/\\/)
|
105
|
+
result << escaped
|
106
|
+
elsif scan(/"/)
|
107
|
+
break
|
108
|
+
elsif eos?
|
109
|
+
error("unterminated quoted string")
|
110
|
+
else scan(/./)
|
111
|
+
error("unterminated quoted string") unless matched
|
112
|
+
result << matched
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
def escaped
|
120
|
+
if scan(/"|\\|\\\//)
|
121
|
+
matched
|
122
|
+
elsif scan(/a|b|f|n|v|r|t/)
|
123
|
+
CONTROL_CHAR[matched]
|
124
|
+
elsif scan(/u[0-9a-f]{4}/i)
|
125
|
+
[ matched[1..-1].to_i(16) ].pack("U")
|
126
|
+
elsif scan(/\d{1,3}/)
|
127
|
+
[ matched.to_i(8) ].pack("C")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def unquoted_string
|
132
|
+
puts "creating unquoted string #{inspect}" if @debug
|
133
|
+
|
134
|
+
if scan(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ((\+|-)\d{4})?/)
|
135
|
+
puts "returning time" if @debug
|
136
|
+
require 'date'
|
137
|
+
DateTime.parse(matched)
|
138
|
+
elsif scan(/-?\d+?\.\d+\b/)
|
139
|
+
puts "returning float" if @debug
|
140
|
+
@parse_numbers ? matched.to_f : matched
|
141
|
+
elsif scan(/-?\d+\b/)
|
142
|
+
puts "returning int" if @debug
|
143
|
+
@parse_numbers ? matched.to_i : matched
|
144
|
+
elsif scan(/\b(true|false)\b/)
|
145
|
+
val = matched == 'true'
|
146
|
+
if @parse_bools
|
147
|
+
val
|
148
|
+
else
|
149
|
+
@parse_numbers ? BOOLS[val].to_i : BOOLS[val]
|
150
|
+
end
|
151
|
+
elsif eos?
|
152
|
+
error("unexpected end-of-string")
|
153
|
+
else
|
154
|
+
puts "returning string" if @debug
|
155
|
+
scan(/(\w|\.|\/)+/)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def data
|
160
|
+
puts "creating data #{inspect}" if @debug
|
161
|
+
|
162
|
+
scan(/(.+?)>/)
|
163
|
+
|
164
|
+
hex = self[1].delete(" ")
|
165
|
+
[hex].pack("H*")
|
166
|
+
end
|
167
|
+
|
168
|
+
def array
|
169
|
+
puts "creating array #{inspect}" if @debug
|
170
|
+
|
171
|
+
skip_space
|
172
|
+
|
173
|
+
arr = []
|
174
|
+
until scan(/\)/)
|
175
|
+
val = object()
|
176
|
+
|
177
|
+
return nil unless val
|
178
|
+
skip_space
|
179
|
+
|
180
|
+
unless skip(/,\s*/)
|
181
|
+
skip_space
|
182
|
+
if scan(/\)/)
|
183
|
+
return arr << val
|
184
|
+
else
|
185
|
+
error "missing ',' or ')' for array"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
arr << val
|
190
|
+
error "unexpected end-of-string when parsing array" if eos?
|
191
|
+
end
|
192
|
+
|
193
|
+
arr
|
194
|
+
end
|
195
|
+
|
196
|
+
def dictionary
|
197
|
+
puts "creating dict #{inspect}" if @debug
|
198
|
+
|
199
|
+
skip_space
|
200
|
+
|
201
|
+
dict = {}
|
202
|
+
until scan(/\}/)
|
203
|
+
key = object()
|
204
|
+
p :key => key if @debug
|
205
|
+
|
206
|
+
error "expected terminating '}' for dictionary" unless key
|
207
|
+
|
208
|
+
# dictionary keys must be strings, even if represented as 12345 in the plist
|
209
|
+
if key.is_a?(Integer) && key >= 0
|
210
|
+
key = key.to_s
|
211
|
+
end
|
212
|
+
error "dictionary key must be string (\"quoted\" or alphanumeric)" unless key.is_a? String
|
213
|
+
|
214
|
+
skip_space
|
215
|
+
error "missing '=' in dictionary" unless scan(/=/)
|
216
|
+
skip_space
|
217
|
+
|
218
|
+
val = object()
|
219
|
+
p :val => val if @debug
|
220
|
+
|
221
|
+
skip_space
|
222
|
+
error "missing ';' in dictionary" unless skip(/;/)
|
223
|
+
skip_space
|
224
|
+
|
225
|
+
dict[key] = val
|
226
|
+
error "unexpected end-of-string when parsing dictionary" if eos?
|
227
|
+
end
|
228
|
+
|
229
|
+
dict
|
230
|
+
end
|
231
|
+
|
232
|
+
def error(msg)
|
233
|
+
line = 1
|
234
|
+
string.split(//).each_with_index do |e, i|
|
235
|
+
line += 1 if e == "\n"
|
236
|
+
break if i == pos
|
237
|
+
end
|
238
|
+
|
239
|
+
context = (pos - 10) < 0 ? 0 : pos - 10
|
240
|
+
err = "#{msg} at line #{line}\n"
|
241
|
+
err << "#{string[context..pos+10]}".inspect << "\n"
|
242
|
+
|
243
|
+
err << "\n#{inspect}" if @debug
|
244
|
+
raise ParseError, err
|
245
|
+
end
|
246
|
+
|
247
|
+
def skip_space
|
248
|
+
puts "skipping whitespace" if @debug
|
249
|
+
skip SPACE_REGEXP
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Vendor
|
2
|
+
module VendorSpec
|
3
|
+
|
4
|
+
class Builder
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require 'tmpdir'
|
8
|
+
require 'find'
|
9
|
+
require 'zip/zipfilesystem'
|
10
|
+
require 'colorize'
|
11
|
+
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :version
|
14
|
+
attr_reader :filename
|
15
|
+
attr_reader :vendor_spec
|
16
|
+
|
17
|
+
def initialize(vendor_spec)
|
18
|
+
loader = Vendor::VendorSpec::Loader.new
|
19
|
+
loader.load vendor_spec
|
20
|
+
|
21
|
+
@folder = File.expand_path(File.join(vendor_spec, '..'))
|
22
|
+
@vendor_spec = loader.dsl.vendor_spec
|
23
|
+
|
24
|
+
@name = safe_filename(@vendor_spec[:name])
|
25
|
+
@version = safe_filename(@vendor_spec[:version])
|
26
|
+
@filename = "#{@name}-#{@version}.vendor"
|
27
|
+
end
|
28
|
+
|
29
|
+
def build
|
30
|
+
tmpdir = Dir.mktmpdir(@filename)
|
31
|
+
|
32
|
+
vendor_file = File.join(tmpdir, "vendor.json")
|
33
|
+
data_dir = File.join(tmpdir, "data")
|
34
|
+
data_files = copy_files(data_dir)
|
35
|
+
|
36
|
+
open(vendor_file, 'w+') { |f| f << @vendor_spec.to_json }
|
37
|
+
|
38
|
+
FileUtils.rm(@filename) if File.exist?(@filename)
|
39
|
+
|
40
|
+
zip_file @filename, [ vendor_file, *data_files ], tmpdir
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def copy_files(data_dir)
|
48
|
+
data_files = []
|
49
|
+
|
50
|
+
@vendor_spec[:files].each do |file|
|
51
|
+
dir = File.dirname(file)
|
52
|
+
path = File.join(@folder, file)
|
53
|
+
copy_to_dir = File.expand_path(File.join(data_dir, dir))
|
54
|
+
copy_to_file = File.join(copy_to_dir, File.basename(file))
|
55
|
+
|
56
|
+
FileUtils.mkdir_p copy_to_dir
|
57
|
+
FileUtils.cp path, copy_to_file
|
58
|
+
|
59
|
+
data_files << copy_to_file
|
60
|
+
end
|
61
|
+
|
62
|
+
data_files
|
63
|
+
end
|
64
|
+
|
65
|
+
def zip_file(filename, files, base_dir)
|
66
|
+
Zip::ZipFile.open(filename, Zip::ZipFile::CREATE)do |zipfile|
|
67
|
+
|
68
|
+
files.each do |file|
|
69
|
+
path = file.gsub(base_dir, '').gsub(/^\//, '')
|
70
|
+
zipfile.add path, file
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def safe_filename(filename)
|
77
|
+
filename.gsub(/[^a-zA-Z0-9\-\_\.]/, '')
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Vendor
|
2
|
+
module VendorSpec
|
3
|
+
|
4
|
+
class DSL
|
5
|
+
|
6
|
+
attr_reader :vendor_spec
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@vendor_spec = Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
if name.to_s.match(/\=$/) || args.length > 0
|
14
|
+
without_equals = name.to_s.gsub(/=$/, '')
|
15
|
+
|
16
|
+
@vendor_spec[without_equals.to_sym] = args.length == 1 ? args.first : args
|
17
|
+
else
|
18
|
+
@vendor_spec[name.to_sym]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate!
|
23
|
+
[ :name, :version, :email, :files ].each do |key|
|
24
|
+
value = @vendor_spec[key]
|
25
|
+
|
26
|
+
if value.respond_to?(:empty?) ? value.empty? : !value
|
27
|
+
raise StandardError.new("Specification is missing the `#{key}` option")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json
|
33
|
+
vendor_spec.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Vendor
|
2
|
+
module VendorSpec
|
3
|
+
|
4
|
+
class Loader
|
5
|
+
|
6
|
+
attr_reader :dsl
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@dsl = Vendor::VendorSpec::DSL.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def vendor(&block)
|
13
|
+
@dsl.instance_eval &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(filename)
|
17
|
+
@dsl.instance_eval(File.read(filename), filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Vendor::XCode
|
2
|
+
|
3
|
+
class Object
|
4
|
+
|
5
|
+
attr_accessor :id
|
6
|
+
attr_reader :attributes
|
7
|
+
|
8
|
+
# I don't know what the ID's are made up of in XCode,
|
9
|
+
# so lets just generate a 24 character string.
|
10
|
+
def self.generate_id
|
11
|
+
(0...24).map { '%02X' % rand(256) }.join
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.object_references
|
15
|
+
@references || []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reference(name)
|
19
|
+
attribute = attribute_name(name)
|
20
|
+
|
21
|
+
define_method name do
|
22
|
+
value = @attributes[attribute]
|
23
|
+
if value.kind_of?(Array)
|
24
|
+
value.map { |id| @project.find_object(id) }
|
25
|
+
else
|
26
|
+
@project.find_object(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@references ||= []
|
31
|
+
@references << name
|
32
|
+
end
|
33
|
+
|
34
|
+
# References are stored in camel case in the project file
|
35
|
+
def self.attribute_name(name)
|
36
|
+
camelized = name.to_s.gsub(/[^\w]/, '').gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
37
|
+
camelized[0].chr.downcase + camelized[1..-1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(options)
|
41
|
+
@id = options[:id]
|
42
|
+
@project = options[:project]
|
43
|
+
@attributes = options[:attributes]
|
44
|
+
|
45
|
+
@attributes['isa'] = self.class.name.split('::').last unless @attributes['isa']
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
properties = { "id" => @id }.merge(@attributes)
|
50
|
+
|
51
|
+
# The class name contains the ISA (the type of the object)
|
52
|
+
properties.delete("isa")
|
53
|
+
|
54
|
+
# Always show the ID first
|
55
|
+
keys = properties.keys.map(&:to_s)
|
56
|
+
keys.delete("id")
|
57
|
+
keys.unshift("id")
|
58
|
+
|
59
|
+
"#<#{self.class.name} #{keys.map{ |key| "#{underscore(key)}: #{properties[key].inspect}" }.join(', ')}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(v, *args)
|
63
|
+
setting = v.to_s.match(/\=$/)
|
64
|
+
name = self.class.attribute_name(v)
|
65
|
+
|
66
|
+
if @attributes.has_key?(name)
|
67
|
+
if setting
|
68
|
+
write_attribute(name, args.first)
|
69
|
+
else
|
70
|
+
read_attribute(name)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_attribute(name, value)
|
78
|
+
@attributes[name.to_s] = value
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_attribute(name)
|
82
|
+
@attributes[name.to_s]
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_ascii_plist
|
86
|
+
@attributes.to_ascii_plist
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# I miss active support...
|
92
|
+
def underscore(string)
|
93
|
+
string.gsub(/::/, '/').
|
94
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
95
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
96
|
+
tr("-", "_").
|
97
|
+
downcase
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Vendor::XCode::Objects
|
2
|
+
|
3
|
+
class PBXFileReference < Vendor::XCode::Object
|
4
|
+
|
5
|
+
reference :file_ref
|
6
|
+
|
7
|
+
def name
|
8
|
+
read_attribute :path
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.file_type_from_extension(extension)
|
12
|
+
case extension
|
13
|
+
when ".h" then "sourcecode.c.h"
|
14
|
+
when ".m" then "sourcecode.c.objc"
|
15
|
+
else "unknown"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|