vendor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +12 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +68 -0
  5. data/LICENCE +19 -0
  6. data/Rakefile +11 -0
  7. data/Readme.markdown +109 -0
  8. data/bin/vendor +62 -0
  9. data/lib/vendor/api.rb +49 -0
  10. data/lib/vendor/cli/auth.rb +38 -0
  11. data/lib/vendor/cli.rb +23 -0
  12. data/lib/vendor/config.rb +51 -0
  13. data/lib/vendor/extensions/array.rb +7 -0
  14. data/lib/vendor/extensions/fixnum.rb +7 -0
  15. data/lib/vendor/extensions/hash.rb +10 -0
  16. data/lib/vendor/extensions/string.rb +11 -0
  17. data/lib/vendor/plist.rb +255 -0
  18. data/lib/vendor/vendor_spec/builder.rb +83 -0
  19. data/lib/vendor/vendor_spec/dsl.rb +39 -0
  20. data/lib/vendor/vendor_spec/loader.rb +23 -0
  21. data/lib/vendor/version.rb +5 -0
  22. data/lib/vendor/xcode/object.rb +102 -0
  23. data/lib/vendor/xcode/objects/pbx_build_file.rb +9 -0
  24. data/lib/vendor/xcode/objects/pbx_container_item_proxy.rb +8 -0
  25. data/lib/vendor/xcode/objects/pbx_file_reference.rb +21 -0
  26. data/lib/vendor/xcode/objects/pbx_frameworks_build_phase.rb +9 -0
  27. data/lib/vendor/xcode/objects/pbx_group.rb +13 -0
  28. data/lib/vendor/xcode/objects/pbx_native_target.rb +11 -0
  29. data/lib/vendor/xcode/objects/pbx_project.rb +12 -0
  30. data/lib/vendor/xcode/objects/pbx_resources_build_phase.rb +7 -0
  31. data/lib/vendor/xcode/objects/pbx_shell_script_build_phase.rb +7 -0
  32. data/lib/vendor/xcode/objects/pbx_sources_build_phase.rb +9 -0
  33. data/lib/vendor/xcode/objects/pbx_target_dependency.rb +7 -0
  34. data/lib/vendor/xcode/objects/pbx_variant_group.rb +7 -0
  35. data/lib/vendor/xcode/objects/xc_build_configuration.rb +7 -0
  36. data/lib/vendor/xcode/objects/xc_configuration_list.rb +9 -0
  37. data/lib/vendor/xcode/project.rb +130 -0
  38. data/lib/vendor.rb +33 -0
  39. data/spec/spec_helper.rb +17 -0
  40. data/spec/support/api_stubs.rb +12 -0
  41. data/spec/support/resources/files/SecondViewController.h +13 -0
  42. data/spec/support/resources/files/SecondViewController.m +43 -0
  43. data/spec/support/resources/projects/ProjectWithSpecs/ProjectWithSpecs.xcodeproj/project.pbxproj +320 -0
  44. data/spec/support/resources/projects/TabBarWithUnitTests/TabBarWithUnitTests.xcodeproj/project.pbxproj +473 -0
  45. data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.h +18 -0
  46. data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.m +73 -0
  47. data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.vendorspec +11 -0
  48. data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.h +18 -0
  49. data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.m +73 -0
  50. data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.vendorspec +11 -0
  51. data/spec/support/temp_project.rb +21 -0
  52. data/spec/vendor/api_spec.rb +25 -0
  53. data/spec/vendor/cli/auth_spec.rb +54 -0
  54. data/spec/vendor/config_spec.rb +62 -0
  55. data/spec/vendor/vendor_spec/builder_spec.rb +86 -0
  56. data/spec/vendor/vendor_spec/dsl_spec.rb +67 -0
  57. data/spec/vendor/vendor_spec/loader_spec.rb +41 -0
  58. data/spec/vendor/xcode/object_spec.rb +76 -0
  59. data/spec/vendor/xcode/objects/pbx_project_spec.rb +26 -0
  60. data/spec/vendor/xcode/project_spec.rb +211 -0
  61. data/vendor.gemspec +33 -0
  62. metadata +234 -0
@@ -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,5 @@
1
+ module Vendor
2
+
3
+ VERSION = "0.0.1"
4
+
5
+ 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,9 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXBuildFile < Vendor::XCode::Object
4
+
5
+ reference :file_ref
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXContainerItemProxy < Vendor::XCode::Object
4
+
5
+
6
+ end
7
+
8
+ 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
@@ -0,0 +1,9 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXFrameworksBuildPhase < Vendor::XCode::Object
4
+
5
+ reference :files
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,13 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXGroup < Vendor::XCode::Object
4
+
5
+ reference :children
6
+
7
+ def name
8
+ @attributes['name'] || @attributes['path']
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXNativeTarget < Vendor::XCode::Object
4
+
5
+ reference :build_phases
6
+ reference :product_reference
7
+ reference :build_configuration_list
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,12 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXProject < Vendor::XCode::Object
4
+
5
+ reference :build_configuration_list
6
+ reference :product_ref_group
7
+ reference :main_group
8
+ reference :targets
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,7 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXResourcesBuildPhase < Vendor::XCode::Object
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXShellScriptBuildPhase < Vendor::XCode::Object
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXSourcesBuildPhase < Vendor::XCode::Object
4
+
5
+ reference :files
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,7 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXTargetDependency < Vendor::XCode::Object
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class PBXVariantGroup < Vendor::XCode::Object
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class XCBuildConfiguration < Vendor::XCode::Object
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ module Vendor::XCode::Objects
2
+
3
+ class XCConfigurationList < Vendor::XCode::Object
4
+
5
+ reference :build_configurations
6
+
7
+ end
8
+
9
+ end