Class: BranchIOCLI::Helper::ConfigurationHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/branch_io_cli/helper/configuration_helper.rb

Overview

Processes CLI options. Validates options. Prompts for input in a number of cases. rubocop: disable Metrics/ClassLength

Constant Summary collapse

/\.app\.link$|\.test-app\.link$/
SDK_OPTIONS =
{
  "Set this project up to use CocoaPods and add the Branch SDK." => :cocoapods,
  "Set this project up to use Carthage and add the Branch SDK." => :carthage,
  "Add Branch.framework directly to the project's dependencies." => :direct,
  "Skip adding the framework to the project." => :skip
}

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.add_sdkObject (readonly)

Returns the value of attribute add_sdk.



34
35
36
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 34

def add_sdk
  @add_sdk
end

.all_domainsObject (readonly)

Returns the value of attribute all_domains.



26
27
28
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 26

def all_domains
  @all_domains
end

.cartfile_pathObject (readonly)

Returns the value of attribute cartfile_path.



28
29
30
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 28

def cartfile_path
  @cartfile_path
end

.carthage_commandObject (readonly)

Returns the value of attribute carthage_command.



29
30
31
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 29

def carthage_command
  @carthage_command
end

.cleanObject (readonly)

Returns the value of attribute clean.



39
40
41
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 39

def clean
  @clean
end

.commitObject (readonly)

Returns the value of attribute commit.



37
38
39
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 37

def commit
  @commit
end

.configurationObject (readonly)

Returns the value of attribute configuration.



42
43
44
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 42

def configuration
  @configuration
end

.forceObject (readonly)

Returns the value of attribute force.



35
36
37
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 35

def force
  @force
end

.header_onlyObject (readonly)

Returns the value of attribute header_only.



40
41
42
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 40

def header_only
  @header_only
end

.keysObject (readonly)

Returns the value of attribute keys.



25
26
27
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 25

def keys
  @keys
end

.patch_sourceObject (readonly)

Returns the value of attribute patch_source.



36
37
38
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 36

def patch_source
  @patch_source
end

.pod_repo_updateObject (readonly)

Returns the value of attribute pod_repo_update.



32
33
34
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 32

def pod_repo_update
  @pod_repo_update
end

.podfile_pathObject (readonly)

Returns the value of attribute podfile_path.



27
28
29
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 27

def podfile_path
  @podfile_path
end

.report_pathObject (readonly)

Returns the value of attribute report_path.



43
44
45
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 43

def report_path
  @report_path
end

.schemeObject (readonly)

Returns the value of attribute scheme.



41
42
43
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 41

def scheme
  @scheme
end

.sdk_integration_modeObject (readonly)

Returns the value of attribute sdk_integration_mode.



38
39
40
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 38

def sdk_integration_mode
  @sdk_integration_mode
end

.targetObject (readonly)

Returns the value of attribute target.



30
31
32
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 30

def target
  @target
end

.uri_schemeObject (readonly)

Returns the value of attribute uri_scheme.



31
32
33
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 31

def uri_scheme
  @uri_scheme
end

.validateObject (readonly)

Returns the value of attribute validate.



33
34
35
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 33

def validate
  @validate
end

.workspaceObject (readonly)

Returns the value of attribute workspace.



24
25
26
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 24

def workspace
  @workspace
end

.workspace_pathObject (readonly)

Returns the value of attribute workspace_path.



23
24
25
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 23

def workspace_path
  @workspace_path
end

.xcodeprojObject (readonly)

Returns the value of attribute xcodeproj.



22
23
24
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 22

def xcodeproj
  @xcodeproj
end

.xcodeproj_pathObject (readonly)

Returns the value of attribute xcodeproj_path.



21
22
23
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 21

def xcodeproj_path
  @xcodeproj_path
end

Class Method Details

.all_domains_from_domains(domains) ⇒ Object



432
433
434
435
436
437
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 432

def all_domains_from_domains(domains)
  app_link_roots = app_link_roots_from_domains domains
  app_link_subdomains = app_link_subdomains_from_roots app_link_roots
  custom_domains = custom_domains_from_domains domains
  custom_domains + app_link_subdomains
end

.all_schemesObject



356
357
358
359
360
361
362
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 356

def all_schemes
  if @workspace_path
    @workspace.schemes.keys.reject { |scheme| scheme == "Pods" }
  else
    Xcodeproj::Project.schemes @xcodeproj_path
  end
end


392
393
394
395
396
397
398
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 392

def app_link_roots_from_domains(domains)
  return [] if domains.nil?

  domains.select { |d| d =~ APP_LINK_REGEXP }
         .map { |d| d.sub(APP_LINK_REGEXP, '').sub(/-alternate$/, '') }
         .uniq
end


405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 405

def app_link_subdomains(root)
  app_link_subdomain = root
  return [] if app_link_subdomain.nil?

  live_key = @keys[:live]
  test_key = @keys[:test]

  domains = []
  unless live_key.nil?
    domains += [
      "#{app_link_subdomain}.app.link",
      "#{app_link_subdomain}-alternate.app.link"
    ]
  end
  unless test_key.nil?
    domains += [
      "#{app_link_subdomain}.test-app.link",
      "#{app_link_subdomain}-alternate.test-app.link"
    ]
  end
  domains
end


428
429
430
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 428

def app_link_subdomains_from_roots(roots)
  roots.inject([]) { |domains, root| domains + app_link_subdomains(root) }
end

.custom_domains_from_domains(domains) ⇒ Object



400
401
402
403
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 400

def custom_domains_from_domains(domains)
  return [] if domains.nil?
  domains.reject { |d| d =~ APP_LINK_REGEXP }.uniq
end


120
121
122
123
124
125
126
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 120

def print_identification(command)
  say "\n<%= color(\"branch_io \#{command} v. \#{VERSION}\", BOLD) %>\n\n"
end


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 162

def print_report_configuration
  say "<%= color('Configuration:', BOLD) %>\n\n<%= color('Xcode workspace:', BOLD) %> \#{@workspace_path || '(none)'}\n<%= color('Xcode project:', BOLD) %> \#{@xcodeproj_path || '(none)'}\n<%= color('Scheme:', BOLD) %> \#{@scheme || '(none)'}\n<%= color('Target:', BOLD) %> \#{@target || '(none)'}\n<%= color('Configuration:', BOLD) %> \#{@configuration || '(none)'}\n<%= color('Podfile:', BOLD) %> \#{@podfile_path || '(none)'}\n<%= color('Cartfile:', BOLD) %> \#{@cartfile_path || '(none)'}\n<%= color('Clean:', BOLD) %> \#{@clean.inspect}\n<%= color('Report path:', BOLD) %> \#{@report_path}\n"
end


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 128

def print_setup_configuration
  say "<%= color('Configuration:', BOLD) %>\n\n<%= color('Xcode project:', BOLD) %> \#{@xcodeproj_path}\n<%= color('Target:', BOLD) %> \#{@target.name}\n<%= color('Live key:', BOLD) %> \#{@keys[:live] || '(none)'}\n<%= color('Test key:', BOLD) %> \#{@keys[:test] || '(none)'}\n<%= color('Domains:', BOLD) %> \#{@all_domains}\n<%= color('URI scheme:', BOLD) %> \#{@uri_scheme || '(none)'}\n<%= color('Podfile:', BOLD) %> \#{@podfile_path || '(none)'}\n<%= color('Cartfile:', BOLD) %> \#{@cartfile_path || '(none)'}\n<%= color('Carthage command:', BOLD) %> \#{@carthage_command || '(none)'}\n<%= color('Pod repo update:', BOLD) %> \#{@pod_repo_update.inspect}\n<%= color('Validate:', BOLD) %> \#{@validate.inspect}\n<%= color('Force:', BOLD) %> \#{@force.inspect}\n<%= color('Add SDK:', BOLD) %> \#{@add_sdk.inspect}\n<%= color('Patch source:', BOLD) %> \#{@patch_source.inspect}\n<%= color('Commit:', BOLD) %> \#{@commit.inspect}\n<%= color('SDK integration mode:', BOLD) %> \#{@sdk_integration_mode || '(none)'}\n\n"
end


152
153
154
155
156
157
158
159
160
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 152

def print_validation_configuration
  say "<%= color('Configuration:', BOLD) %>\n\n<%= color('Xcode project:', BOLD) %> \#{@xcodeproj_path}\n<%= color('Target:', BOLD) %> \#{@target.name}\n<%= color('Domains:', BOLD) %> \#{@all_domains || '(none)'}\n"
end

.uri_scheme_without_suffix(scheme) ⇒ Object

Removes any trailing :// from the argument and returns a copy



440
441
442
443
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 440

def uri_scheme_without_suffix(scheme)
  return nil if scheme.nil?
  scheme.sub %r{://$}, ""
end

.validate_all_domains(options, required = true) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 195

def validate_all_domains(options, required = true)
  app_link_roots = app_link_roots_from_domains options.domains

  unless options.app_link_subdomain.nil? || app_link_roots.include?(options.app_link_subdomain)
    app_link_roots << options.app_link_subdomain
  end

  # app_link_roots now contains options.app_link_subdomain, if supplied, and the roots of any
  # .app.link or .test-app.link domains provided via options.domains.

  app_link_subdomains = app_link_subdomains_from_roots app_link_roots

  custom_domains = custom_domains_from_domains options.domains

  @all_domains = (app_link_subdomains + custom_domains).uniq

  while required && @all_domains.empty?
    domains = ask "Please enter domains as a comma-separated list: ", ->(str) { str.split "," }

    @all_domains = all_domains_from_domains domains
  end
end

.validate_buildfile_path(buildfile_path, filename) ⇒ Object



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 445

def validate_buildfile_path(buildfile_path, filename)
  # Disable Podfile/Cartfile update if --no-add-sdk is present
  return unless @sdk_integration_mode.nil?

  # Was --podfile/--cartfile used?
  if buildfile_path
    # Yes: Validate. Prompt if not valid.
    loop do
      valid = buildfile_path =~ %r{/?#{filename}$}
      say "#{filename} path must end in /#{filename}." unless valid

      if valid
        valid = File.exist? buildfile_path
        say "#{buildfile_path} not found." unless valid
      end

      if valid
        if filename == "Podfile"
          @podfile_path = buildfile_path
        else
          @cartfile_path = buildfile_path
        end
        return
      end

      buildfile_path = ask "Please enter the path to your #{filename}: "
    end
  end

  # No: Check for Podfile/Cartfile next to workspace or project
  buildfile_path = File.expand_path "../#{filename}", (@workspace_path || @xcodeproj_path)
  return unless File.exist? buildfile_path

  # Exists: Use it (valid if found)
  if filename == "Podfile"
    @podfile_path = buildfile_path
  else
    @cartfile_path = buildfile_path
  end

  @sdk_integration_mode = filename == "Podfile" ? :cocoapods : :carthage
end

.validate_keys_from_setup_options(options) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 178

def validate_keys_from_setup_options(options)
  live_key = options.live_key
  test_key = options.test_key
  @keys = {}
  @keys[:live] = live_key unless live_key.nil?
  @keys[:test] = test_key unless test_key.nil?

  while @keys.empty?
    say "A live key, a test key or both is required."
    live_key = ask "Please enter your live Branch key or use --live_key [enter for none]: "
    test_key = ask "Please enter your test Branch key or use --test_key [enter for none]: "

    @keys[:live] = live_key unless live_key == ""
    @keys[:test] = test_key unless test_key == ""
  end
end

.validate_report_options(options) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 94

def validate_report_options(options)
  print_identification "report"

  @clean = options.clean
  @header_only = options.header_only
  @scheme = options.scheme
  @target = options.target
  @configuration = options.configuration
  @report_path = options.out || "./report.txt"

  validate_xcodeproj_and_workspace options
  validate_scheme options

  # If neither --podfile nor --cartfile is present, arbitrarily look for a Podfile
  # first.

  # If --cartfile is present, don't look for a Podfile. Just validate that
  # Cartfile.
  validate_buildfile_path(options.podfile, "Podfile") if options.cartfile.nil?

  # If --podfile is present or a Podfile was found, don't look for a Cartfile.
  validate_buildfile_path(options.cartfile, "Cartfile") if @sdk_integration_mode.nil?

  print_report_configuration
end

.validate_scheme(options) ⇒ Object

rubocop: enable Metrics/PerceivedComplexity



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 338

def validate_scheme(options)
  schemes = all_schemes
  if options.scheme && schemes.include?(options.scheme)
    @scheme = options.scheme
  elsif schemes.count == 1
    @scheme = schemes.first
  elsif !schemes.empty?
    say "Please specify one of the following for the --scheme argument:"
    schemes.each do |scheme|
      say " #{scheme}"
    end
    exit 1
  else
    say "No scheme defined in project."
    exit(-1)
  end
end

.validate_sdk_addition(options) ⇒ Object



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 488

def validate_sdk_addition(options)
  return if !options.add_sdk || @sdk_integration_mode

  # If no CocoaPods or Carthage, check to see if the framework is linked.
  target = BranchHelper.target_from_project @xcodeproj, options.target
  return if target.frameworks_build_phase.files.map(&:file_ref).map(&:path).any? { |p| p =~ /Branch.framework$/ }

  # --podfile, --cartfile not specified. No Podfile found. No Cartfile found. No Branch.framework in project.
  # Prompt the user:
  selected = choose do |menu|
    menu.header = "No Podfile or Cartfile specified or found. Here are your options"

    SDK_OPTIONS.each_key { |k| menu.choice k }

    menu.prompt = "What would you like to do?"
  end

  @sdk_integration_mode = SDK_OPTIONS[selected]

  case @sdk_integration_mode
  when :cocoapods
    @podfile_path = File.expand_path "../Podfile", @xcodeproj_path
  when :carthage
    @cartfile_path = File.expand_path "../Cartfile", @xcodeproj_path
    @carthage_command = options.carthage_command
  end
end

.validate_setup_options(options) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 45

def validate_setup_options(options)
  print_identification "setup"

  @pod_repo_update = options.pod_repo_update
  @validate = options.validate
  @patch_source = options.patch_source
  @add_sdk = options.add_sdk
  @force = options.force
  @commit = options.commit

  say "--force is ignored when --no-validate is used." if !options.validate && options.force
  if options.cartfile && options.podfile
    say "--cartfile and --podfile are mutually exclusive. Please specify the file to patch."
    exit 1
  end

  validate_xcodeproj_path options
  validate_target options
  validate_keys_from_setup_options options
  validate_all_domains options, !@target.extension_target_type?
  validate_uri_scheme options

  # If neither --podfile nor --cartfile is present, arbitrarily look for a Podfile
  # first.

  # If --cartfile is present, don't look for a Podfile. Just validate that
  # Cartfile.
  validate_buildfile_path options.podfile, "Podfile" if options.cartfile.nil? && options.add_sdk

  # If --podfile is present or a Podfile was found, don't look for a Cartfile.
  validate_buildfile_path options.cartfile, "Cartfile" if @sdk_integration_mode.nil? && options.add_sdk
  @carthage_command = options.carthage_command if @sdk_integration_mode == :carthage

  validate_sdk_addition options

  BranchHelper.verify_git if @commit

  print_setup_configuration
end

.validate_target(options, allow_extensions = true) ⇒ Object



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 364

def validate_target(options, allow_extensions = true)
  non_test_targets = @xcodeproj.targets.reject(&:test_target_type?)
  raise "No non-test target found in project" if non_test_targets.empty?

  valid_targets = non_test_targets.reject { |t| !allow_extensions && t.extension_target_type? }

  begin
    target = BranchHelper.target_from_project @xcodeproj, options.target

    # If a test target was explicitly specified.
    raise "Cannot use test targets" if target.test_target_type?

    # If an extension target was explicitly specified for validation.
    raise "Extension targets not allowed for this command" if !allow_extensions && target.extension_target_type?

    @target = target
  rescue StandardError => e
    say e.message

    choice = choose do |menu|
      valid_targets.each { |t| menu.choice t.name }
      menu.prompt = "Which target do you wish to use? "
    end

    @target = @xcodeproj.targets.find { |t| t.name = choice }
  end
end

.validate_uri_scheme(options) ⇒ Object



218
219
220
221
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 218

def validate_uri_scheme(options)
  # No validation at the moment. Just strips off any trailing ://
  @uri_scheme = uri_scheme_without_suffix options.uri_scheme
end

.validate_validation_options(options) ⇒ Object



85
86
87
88
89
90
91
92
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 85

def validate_validation_options(options)
  print_identification "validate"

  validate_xcodeproj_path options
  validate_target options, false

  print_validation_configuration
end

.validate_xcodeproj_and_workspace(options) ⇒ Object

rubocop: disable Metrics/PerceivedComplexity



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 259

def validate_xcodeproj_and_workspace(options)
  # 1. What was passed in?
  begin
    if options.workspace
      path = options.workspace
      @workspace = Xcodeproj::Workspace.new_from_xcworkspace options.workspace
      @workspace_path = options.workspace
    end
    if options.xcodeproj
      path = options.xcodeproj
      @xcodeproj = Xcodeproj::Project.open options.xcodeproj
      @xcodeproj_path = options.xcodeproj
    else
      # Pass --workspace and --xcodeproj to override this inference.
      if @workspace && @workspace.file_references.count > 0 && @workspace.file_references.first.path =~ /\.xcodeproj$/
        @xcodeproj_path = File.expand_path "../#{@workspace.file_references.first.path}", @workspace_path
        @xcodeproj = Xcodeproj::Project.open @xcodeproj_path
      end
    end
    return if @workspace || @xcodeproj
  rescue StandardError => e
    say e.message
  end

  # Try to find first a workspace, then a project
  all_workspace_paths = Dir[File.expand_path(File.join(".", "**/*.xcworkspace"))]
                        .reject { |w| w =~ %r{/project.xcworkspace$} }
                        .select do |p|
    valid = true
    Pathname.new(p).each_filename do |f|
      valid = false && break if f == "Carthage" || f == "Pods"
    end
    valid
  end

  if all_workspace_paths.count == 1
    path = all_workspace_paths.first
  elsif all_workspace_paths.count == 0
    all_xcodeproj_paths = Dir[File.expand_path(File.join(".", "**/*.xcodeproj"))]
    xcodeproj_paths = all_xcodeproj_paths.select do |p|
      valid = true
      Pathname.new(p).each_filename do |f|
        valid = false && break if f == "Carthage" || f == "Pods"
      end
      valid
    end

    path = xcodeproj_paths.first if xcodeproj_paths.count == 1
  end
  # If more than one workspace. Don't try to find a project. Just prompt.

  loop do
    path = ask "Please enter a path to your Xcode project or workspace: " if path.nil?
    begin
      if path =~ /\.xcworkspace$/
        @workspace = Xcodeproj::Workspace.new_from_xcworkspace path
        @workspace_path = path

        # Pass --workspace and --xcodeproj to override this inference.
        if @workspace.file_references.count > 0 && @workspace.file_references.first.path =~ /\.xcodeproj$/
          @xcodeproj_path = File.expand_path "../#{@workspace.file_references.first.path}", @workspace_path
          @xcodeproj = Xcodeproj::Project.open @xcodeproj_path
        end

        return
      elsif path =~ /\.xcodeproj$/
        @xcodeproj = Xcodeproj::Project.open path
        @xcodeproj_path = path
        return
      else
        say "Path must end with .xcworkspace or .xcodeproj"
      end
    rescue StandardError => e
      say e.message
    end
  end
end

.validate_xcodeproj_path(options) ⇒ Object

  1. Look for options.xcodeproj.

  2. If not specified, look for projects under . (excluding anything in Pods or Carthage folder).

  3. If none or more than one found, prompt the user.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/branch_io_cli/helper/configuration_helper.rb', line 226

def validate_xcodeproj_path(options)
  if options.xcodeproj
    path = options.xcodeproj
  else
    all_xcodeproj_paths = Dir[File.expand_path(File.join(".", "**/*.xcodeproj"))]
    # find an xcodeproj (ignoring the Pods and Carthage folders)
    # TODO: Improve this filter
    xcodeproj_paths = all_xcodeproj_paths.select do |p|
      valid = true
      Pathname.new(p).each_filename do |f|
        valid = false && break if f == "Carthage" || f == "Pods"
      end
      valid
    end

    path = xcodeproj_paths.first if xcodeproj_paths.count == 1
  end

  loop do
    path = ask "Please enter the path to your Xcode project or use --xcodeproj: " if path.nil?
    # TODO: Allow the user to choose if xcodeproj_paths.count > 0
    begin
      @xcodeproj = Xcodeproj::Project.open path
      @xcodeproj_path = path
      return
    rescue StandardError => e
      say e.message
      path = nil
    end
  end
end