testflight 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = Introduction
2
+
3
+ If you are an iOS developer, like me, you probably have dealt with the tedious
4
+ tasks of manually building, packaging, tagging, commiting and deploying your
5
+ iOS applications to testflightapp.com. Your probably wondered if there is an easier way to do so.
6
+
7
+ Well, you are absolutely right! This gem is going to make your life much easier. Here is what it does:
8
+
9
+ 1. Reads your XCode project defintion to get your app version and build number
10
+ 2. Commits all your changes to the git repository
11
+ 3. Builds your project (or workspace)
12
+ 4. Packages your application into an IPA file
13
+ 5. Uploads the IPA file to testflightapp.com
14
+ 6. Tags your project with the version and build
15
+ 7. Increments your app build number and commits it to the repo
16
+
17
+ All that with a simple command:
18
+
19
+ $ takeoff
20
+
21
+
22
+ = Configuration
23
+
24
+ All you have to do is add .tesflight file to your project folder with the following content:
25
+
26
+ build:
27
+ developer_name: # This must be your company name or your name as it appears in your certificate
28
+ increment_bundle: true
29
+ commit_changes: true
30
+ testflight:
31
+ api_token: # Get it from https://testflightapp.com/account/#api
32
+ team_token: # Get it from https://testflightapp.com/dashboard/team/edit/
33
+ distribution_lists: ["Internal", "Everyone"] # Configure your distribution lists on https://testflightapp.com
34
+
35
+
36
+ Then go to your project folder and simple type:
37
+
38
+ $ takeoff
39
+
40
+
41
+ Have a safe flight!
42
+
43
+
44
+ = Contributions
45
+
46
+ Clone the repository, make any changes you like and send me a pull request.
47
+
48
+
data/bin/takeoff CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'fileutils'
4
- require 'yaml'
5
3
  require 'pp'
6
- require 'plist'
4
+
5
+ require File.expand_path(File.join(File.dirname(__FILE__), "../lib/testflight/config.rb"))
6
+ require File.expand_path(File.join(File.dirname(__FILE__), "../lib/testflight/prompter.rb"))
7
7
 
8
8
  XCODE_BUILDER = "/usr/bin/xcodebuild"
9
9
  XCODE_PACKAGER = "/usr/bin/xcrun"
@@ -17,260 +17,90 @@ TESTFLIGHT_ENDPOINT = "http://testflightapp.com/api/builds.json"
17
17
  # package files
18
18
  # deploy to testflight app
19
19
 
20
-
21
- def project_dir
22
- Dir.pwd
23
- end
24
-
25
- def project_files
26
- @project_files ||= Dir.entries(project_dir)
27
- end
28
-
29
- def file_name_by_ext(ext)
30
- project_files.select{|file| file.match(/#{ext}$/)}.first
31
- end
32
-
33
-
34
- def workspace_name
35
- @workspace_name ||= file_name_by_ext('xcworkspace')
36
- end
37
-
38
- def project_name
39
- @project_name ||= file_name_by_ext('xcodeproj')
40
- end
41
-
42
- def type
43
- return "unknown" if workspace_name.nil? and project_name.nil?
44
- @type ||= workspace_name ? 'workspace' : 'project'
45
- end
46
-
47
- def application_name
48
- @application_name ||= begin
49
- name = workspace_name || project_name
50
- name = name.split(".")[0..-2].join(".") if name
51
- name
52
- end
53
- end
54
-
55
- def default_config
56
- {
57
- "build" => {
58
- "developer_name" => "As it appears in your Apple certificate",
59
- "increment_bundle" => true,
60
- "commit_changes" => true
61
- },
62
- "testflight" => {
63
- "api_token" => "Get it from https://testflightapp.com/account/#api",
64
- "team_token" => "Get it from https://testflightapp.com/dashboard/team/edit/",
65
- "distribution_lists" => [""]
66
- }
67
- }
68
- end
69
-
70
- def valid_config?
71
- return false unless config
72
- return false if config.empty?
73
- return false if config["build"].nil?
74
- return false if config["build"]["developer_name"].nil?
75
- return false if config["testflight"].nil?
76
- return false if config["testflight"]["api_token"].nil?
77
- return false if config["testflight"]["team_token"].nil?
78
- return false if config["testflight"]["distribution_lists"].nil?
79
- true
80
- end
81
-
82
- def config_file
83
- '.testflight'
84
- end
85
-
86
- def config
87
- @config ||= begin
88
- if File.exist?(config_file)
89
- YAML::load(File.open(config_file))
90
- else
91
- File.open(config_file, "w") do |f|
92
- f.write(default_config.to_yaml)
93
- end
94
- YAML::load(File.open(config_file))
95
- end
96
- end
97
- end
98
-
99
- def build_dir
100
- "#{project_dir}/build"
101
- end
102
-
103
- def provisioning_dir
104
- "#{project_dir}/Provisioning"
105
- end
106
-
107
- def distributions_dir
108
- "#{project_dir}/Distributions"
109
- end
20
+ ####################################################################################
21
+ ## Building Project
22
+ ####################################################################################
110
23
 
111
24
  def build_workspace
112
- cmd = "#{XCODE_BUILDER} -workspace '#{workspace_name}' "
113
- cmd << "-scheme '#{application_name}' "
25
+ cmd = "#{XCODE_BUILDER} -workspace '#{Testflight::Config.workspace_name}' "
26
+ cmd << "-scheme '#{Testflight::Config.application_name}' "
114
27
  cmd << "-sdk 'iphoneos6.0' "
115
28
  cmd << "-configuration 'AdHoc' "
116
29
  cmd << "-arch 'armv6 armv7' "
117
- cmd << "CONFIGURATION_BUILD_DIR='#{build_dir}' "
30
+ cmd << "CONFIGURATION_BUILD_DIR='#{Testflight::Config.build_dir}' "
118
31
  pp "Building Workspace..."
119
32
  pp cmd
120
33
  unless system(cmd)
121
- pp "Failed to package the workspace"
34
+ pp "Failed to build workspace"
122
35
  exit 1
123
36
  end
124
37
  end
125
38
 
126
39
  def build_project
127
- cmd = "#{XCODE_BUILDER} -target '#{application_name}' "
40
+ cmd = "#{XCODE_BUILDER} -target '#{Testflight::Config.application_name}' "
128
41
  cmd << "-sdk 'iphoneos6.0' "
129
42
  cmd << "-configuration 'AdHoc' "
43
+
130
44
  pp "Building Project..."
131
45
  pp cmd
132
46
  unless system(cmd)
133
- pp "Failed to build the project"
47
+ pp "Failed to build project"
134
48
  exit 1
135
49
  end
136
50
  end
137
51
 
138
- def ad_hoc_provisioning_name
139
- @ad_hoc_provisioning_name ||= begin
140
- files = Dir.entries(provisioning_dir)
141
- files.select{|file| file.match(/mobileprovision$/)}.first
142
- end
143
- end
144
-
145
- def distribution_file
146
- "#{distributions_dir}/#{application_name}.ipa"
147
- end
52
+ ####################################################################################
53
+ ## Packaging Project
54
+ ####################################################################################
148
55
 
149
56
  def package_workspace
150
57
  cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
151
- cmd << "-v '#{build_dir}/#{application_name}.app' "
152
- cmd << "-o '#{distribution_file}' "
153
- cmd << "--sign '#{config["build"]["developer_name"]}' "
154
- cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
58
+ cmd << "-v '#{Testflight::Config.build_dir}/#{Testflight::Config.application_name}.app' "
59
+ cmd << "-o '#{Testflight::Config.distribution_file}' "
60
+ cmd << "--sign '#{Testflight::Config.developer_name}' "
61
+ cmd << "--embed '#{Testflight::Config.provisioning_dir}/#{Testflight::Config.ad_hoc_provisioning_name}'"
155
62
 
156
63
  pp "Packaging Workspace..."
157
64
  pp cmd
158
65
  unless system(cmd)
159
- pp "Failed to package the workspace"
66
+ pp "Failed to package workspace"
160
67
  exit 1
161
68
  end
162
69
  end
163
70
 
164
71
  def package_project
165
72
  cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
166
- cmd << "-v '#{build_dir}/AdHoc-iphoneos/#{application_name}.app' "
167
- cmd << "-o '#{distribution_file}' "
168
- cmd << "--sign '#{config["build"]["developer_name"]}' "
169
- cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
73
+ cmd << "-v '#{Testflight::Config.build_dir}/AdHoc-iphoneos/#{Testflight::Config.application_name}.app' "
74
+ cmd << "-o '#{Testflight::Config.distribution_file}' "
75
+ cmd << "--sign '#{Testflight::Config.developer_name}' "
76
+ cmd << "--embed '#{Testflight::Config.provisioning_dir}/#{Testflight::Config.ad_hoc_provisioning_name}'"
170
77
 
171
78
  pp "Packaging Project..."
172
79
  pp cmd
173
80
  unless system(cmd)
174
- pp "Failed to package the project"
81
+ pp "Failed to package project"
175
82
  exit 1
176
83
  end
177
84
  end
178
85
 
179
- def setup
180
- unless application_name
181
- pp "This folder does not contain an xCode project or a workspace."
182
- exit 1
183
- end
184
-
185
- unless valid_config?
186
- pp "Ensure that you have provided all of the information in the #{config_file} config file"
187
- exit 1
188
- end
189
-
190
- unless project_files.include?("Distributions")
191
- FileUtils.mkdir("Distributions")
192
- @project_files = nil
193
- end
194
-
195
- unless project_files.include?("Provisioning")
196
- FileUtils.mkdir("Provisioning")
197
- @project_files = nil
198
- end
199
-
200
- unless ad_hoc_provisioning_name
201
- pp "Please copy your Ad Hoc Provisioning Profile into the provisioning folder."
202
- exit 1
203
- end
204
- end
205
-
206
- def read_options(question, opts=["Yes", "No"], vals=["y", "yes", "n", "no"], joiner = "/")
207
- prompt = "(#{opts.join(joiner)})? "
208
- pp question
209
-
210
- $stdout.print(prompt)
211
-
212
- $stdin.each_line do |line|
213
- value = line.strip.downcase
214
- return value if vals.include?(value)
215
- $stdout.print(prompt)
216
- end
217
- end
218
-
219
- def read_info(question, prompt="> ", allow_blank=false)
220
- pp(question) if question
221
-
222
- $stdout.print(prompt)
223
-
224
- $stdin.each_line do |line|
225
- value = line.strip.downcase
226
- return value if allow_blank
227
- return value unless value.empty?
228
- $stdout.print(prompt)
229
- end
230
- end
231
-
232
- def read_distribution_lists
233
- lists = []
234
- vals = read_info(nil, prompt="? ")
235
- vals.split(",").each do |index|
236
- index = index.to_i - 1
237
- return nil if index<0 or index>=config["testflight"]["distribution_lists"].size
238
- lists << config["testflight"]["distribution_lists"][index]
239
- end
240
- lists
241
- end
242
-
243
- def select_distribution_lists
244
- return [] unless config["testflight"]["distribution_lists"]
245
-
246
- pp "Which distribution lists would you like to send this build to? (Select one or more lists, separated with comma.)"
247
- config["testflight"]["distribution_lists"].each_with_index do |list, index|
248
- pp " #{index+1}) #{list}"
249
- end
250
-
251
- lists = read_distribution_lists
252
- while lists.nil?
253
- pp "Invalid selection, please try again."
254
- lists = read_distribution_lists
255
- end
256
-
257
- lists
258
- end
86
+ ####################################################################################
87
+ ## Uploading Project
88
+ ####################################################################################
259
89
 
260
90
  def upload_to_testflightapp
261
91
  pp "Uploading to TestFlightApp..."
262
92
 
263
- notes = read_info("What has changed in this build?")
93
+ notes = Testflight::Prompter.read_info("What has changed in this build?")
264
94
 
265
- lists = select_distribution_lists
95
+ lists = Testflight::Prompter.select_set("Which distribution lists would you like to send this build to? Select one or more lists, separated with comma.", Testflight::Config.distribution_lists)
266
96
 
267
- notify = read_options("Would you like to notify your team members by email about this build?")
97
+ notify = Testflight::Prompter.read_options("Would you like to notify your team members by email about this build?")
268
98
  ["y", "yes"].include?(notify) ? notify = "true" : notify = "false"
269
99
 
270
100
  cmd = "curl #{TESTFLIGHT_ENDPOINT} "
271
- cmd << "-F file=@#{distribution_file} "
272
- cmd << "-F api_token=#{config["testflight"]["api_token"]} "
273
- cmd << "-F team_token=#{config["testflight"]["team_token"]} "
101
+ cmd << "-F file=@#{Testflight::Config.distribution_file} "
102
+ cmd << "-F api_token=#{Testflight::Config.api_token} "
103
+ cmd << "-F team_token=#{Testflight::Config.team_token} "
274
104
  cmd << "-F notify=#{notify} "
275
105
  cmd << "-F distribution_lists=#{lists.join(",")} "
276
106
  cmd << "-F notes='#{notes}'"
@@ -286,35 +116,13 @@ end
286
116
  ## Project Versioning
287
117
  ####################################################################################
288
118
 
289
- def project_info_path
290
- files = Dir["**/#{application_name}-Info.plist"]
291
- if files.empty?
292
- pp "Cannot locate #{application_name}-Info.plist file. Please make sure such file exists in your project."
293
- exit 1
294
- end
295
- files.first
296
- end
297
-
298
- def project_info
299
- @project_info ||= Plist::parse_xml(project_info_path)
300
- end
301
-
302
- def project_version
303
- "#{project_info["CFBundleShortVersionString"]} (#{project_info["CFBundleVersion"]})"
304
- end
305
-
306
- def project_version_short
307
- "#{project_info["CFBundleShortVersionString"]}.#{project_info["CFBundleVersion"]}"
308
- end
309
-
310
119
  def increment_bundle_version
311
- project_info["CFBundleVersion"] = (project_info["CFBundleVersion"].to_i + 1).to_s
312
- pp "Incrementing bundle version to #{project_info["CFBundleVersion"]}..."
120
+ Testflight::Config.project_info["CFBundleVersion"] = (Testflight::Config.build_number.to_i + 1).to_s
121
+ pp "Incrementing bundle version to #{Testflight::Config.build_number}..."
313
122
 
314
- File.open(project_info_path, "w") do |f|
315
- f.write(project_info.to_plist)
123
+ File.open(Testflight::Config.project_info_path, "w") do |f|
124
+ f.write(Testflight::Config.project_info.to_plist)
316
125
  end
317
- @project_info = nil
318
126
  end
319
127
 
320
128
  ####################################################################################
@@ -333,7 +141,7 @@ def commit_changes(msg)
333
141
  system("git commit -m '#{msg}'")
334
142
  system("git push")
335
143
 
336
- system("git tag -a #{project_version_short} -m 'Release #{project_version}'")
144
+ system("git tag -a #{Testflight::Config.project_version_short} -m 'Release #{Testflight::Config.project_version}'")
337
145
  end
338
146
 
339
147
  ####################################################################################
@@ -341,13 +149,13 @@ end
341
149
  ####################################################################################
342
150
 
343
151
  def deploy
344
- setup
152
+ Testflight::Config.setup
345
153
 
346
- if config["build"]["commit_changes"]
347
- commit_changes("Preparing build #{project_version}")
154
+ if Testflight::Config.commit_changes?
155
+ commit_changes("Preparing build #{Testflight::Config.project_version}")
348
156
  end
349
-
350
- if workspace_name
157
+
158
+ if Testflight::Config.workspace?
351
159
  build_workspace
352
160
  package_workspace
353
161
  else
@@ -359,10 +167,10 @@ def deploy
359
167
 
360
168
  pp ""
361
169
 
362
- if config["build"]["increment_bundle"]
170
+ if Testflight::Config.increment_bundle?
363
171
  increment_bundle_version
364
- if config["build"]["commit_changes"]
365
- commit_changes("Incrementing build number to #{project_version}")
172
+ if Testflight::Config.commit_changes?
173
+ commit_changes("Incrementing build number to #{Testflight::Config.project_version}")
366
174
  end
367
175
  end
368
176
 
@@ -0,0 +1,221 @@
1
+ #--
2
+ # Copyright (c) 2012 Michael Berkovich
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'fileutils'
25
+ require 'yaml'
26
+ require 'pp'
27
+ require 'plist'
28
+
29
+ module Testflight
30
+ class Config
31
+
32
+ def self.path
33
+ '.testflight'
34
+ end
35
+
36
+ def self.defaults
37
+ {
38
+ "build" => {
39
+ "developer_name" => "As it appears in your Apple certificate",
40
+ "increment_bundle" => true,
41
+ "commit_changes" => true
42
+ },
43
+ "testflight" => {
44
+ "api_token" => "Get it from https://testflightapp.com/account/#api",
45
+ "team_token" => "Get it from https://testflightapp.com/dashboard/team/edit/",
46
+ "distribution_lists" => [""]
47
+ }
48
+ }
49
+ end
50
+
51
+ def self.config
52
+ @config ||= begin
53
+ if File.exist?(path)
54
+ YAML::load(File.open(path))
55
+ else
56
+ File.open(path, "w") do |f|
57
+ f.write(defaults.to_yaml)
58
+ end
59
+ raise "Please update #{path} with all necessary properties."
60
+ end
61
+ end
62
+ end
63
+
64
+ def self.commit_changes?
65
+ config["build"]["commit_changes"]
66
+ end
67
+
68
+ def self.increment_bundle?
69
+ config["build"]["increment_bundle"]
70
+ end
71
+
72
+ def self.developer_name
73
+ config["build"]["developer_name"]
74
+ end
75
+
76
+ def self.distribution_lists
77
+ config["testflight"]["distribution_lists"]
78
+ end
79
+
80
+ def self.api_token
81
+ config["testflight"]["api_token"]
82
+ end
83
+
84
+ def self.team_token
85
+ config["testflight"]["team_token"]
86
+ end
87
+
88
+ def self.valid?
89
+ return false unless config
90
+ return false if config.empty?
91
+ return false if config["build"].nil?
92
+ return false if config["build"]["developer_name"].nil?
93
+ return false if config["testflight"].nil?
94
+ return false if config["testflight"]["api_token"].nil?
95
+ return false if config["testflight"]["team_token"].nil?
96
+ return false if config["testflight"]["distribution_lists"].nil?
97
+ true
98
+ end
99
+
100
+ def self.project_dir
101
+ Dir.pwd
102
+ end
103
+
104
+ def self.project_files
105
+ @project_files ||= Dir.entries(project_dir)
106
+ end
107
+
108
+ def self.file_name_by_ext(ext)
109
+ project_files.select{|file| file.match(/#{ext}$/)}.first
110
+ end
111
+
112
+ def self.workspace_name
113
+ @workspace_name ||= file_name_by_ext('xcworkspace')
114
+ end
115
+
116
+ def self.project_name
117
+ @project_name ||= file_name_by_ext('xcodeproj')
118
+ end
119
+
120
+ def self.type
121
+ return "unknown" if workspace_name.nil? and project_name.nil?
122
+ @type ||= workspace_name ? 'workspace' : 'project'
123
+ end
124
+
125
+ def self.workspace?
126
+ type == "workspace"
127
+ end
128
+
129
+ def self.project?
130
+ type == "project"
131
+ end
132
+
133
+ def self.application_name
134
+ @application_name ||= begin
135
+ name = workspace_name || project_name
136
+ name = name.split(".")[0..-2].join(".") if name
137
+ name
138
+ end
139
+ end
140
+
141
+ def self.build_dir
142
+ "#{project_dir}/build"
143
+ end
144
+
145
+ def self.provisioning_dir
146
+ "#{project_dir}/Provisioning"
147
+ end
148
+
149
+ def self.distributions_dir
150
+ "#{project_dir}/Distributions"
151
+ end
152
+
153
+ def self.ad_hoc_provisioning_name
154
+ @ad_hoc_provisioning_name ||= begin
155
+ files = Dir.entries(provisioning_dir)
156
+ files.select{|file| file.match(/mobileprovision$/)}.first
157
+ end
158
+ end
159
+
160
+ def self.distribution_file
161
+ "#{distributions_dir}/#{application_name}.ipa"
162
+ end
163
+
164
+ def self.setup
165
+ unless application_name
166
+ pp "This folder does not contain an xCode project or a workspace."
167
+ exit 1
168
+ end
169
+
170
+ unless valid?
171
+ pp "Ensure that you have provided all of the information in the #{config_file} config file"
172
+ exit 1
173
+ end
174
+
175
+ unless project_files.include?("Distributions")
176
+ FileUtils.mkdir("Distributions")
177
+ @project_files = nil
178
+ end
179
+
180
+ unless project_files.include?("Provisioning")
181
+ FileUtils.mkdir("Provisioning")
182
+ @project_files = nil
183
+ end
184
+
185
+ unless ad_hoc_provisioning_name
186
+ pp "Please copy your Ad Hoc Provisioning Profile into the provisioning folder."
187
+ exit 1
188
+ end
189
+ end
190
+
191
+ def self.project_info_path
192
+ files = Dir["**/#{application_name}-Info.plist"]
193
+ if files.empty?
194
+ pp "Cannot locate #{application_name}-Info.plist file. Please make sure such file exists in your project."
195
+ exit 1
196
+ end
197
+ files.first
198
+ end
199
+
200
+ def self.project_info
201
+ @project_info ||= Plist::parse_xml(project_info_path)
202
+ end
203
+
204
+ def self.version
205
+ project_info["CFBundleShortVersionString"]
206
+ end
207
+
208
+ def self.build_number
209
+ project_info["CFBundleVersion"]
210
+ end
211
+
212
+ def self.project_version
213
+ "#{version} (#{build_number})"
214
+ end
215
+
216
+ def self.project_version_short
217
+ "#{version}.#{build_number}"
218
+ end
219
+
220
+ end
221
+ end
@@ -0,0 +1,81 @@
1
+ #--
2
+ # Copyright (c) 2012 Michael Berkovich
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Testflight
25
+ class Prompter
26
+
27
+ def self.read_options(question=nil, opts=["Yes", "No"], vals=["y", "yes", "n", "no"], joiner = "/")
28
+ prompt = "(#{opts.join(joiner)})? "
29
+ pp question if question
30
+
31
+ $stdout.print(prompt)
32
+
33
+ $stdin.each_line do |line|
34
+ value = line.strip.downcase
35
+ return value if vals.include?(value)
36
+ $stdout.print(prompt)
37
+ end
38
+ end
39
+
40
+ def self.read_info(question=nil, prompt="> ", allow_blank=false)
41
+ pp question if question
42
+
43
+ $stdout.print(prompt)
44
+
45
+ $stdin.each_line do |line|
46
+ value = line.strip.downcase
47
+ return value if allow_blank
48
+ return value unless value.empty?
49
+ $stdout.print(prompt)
50
+ end
51
+ end
52
+
53
+ def self.read_set_values(opts)
54
+ lists = []
55
+ vals = read_info(nil, prompt="? ")
56
+ vals.split(",").each do |index|
57
+ index = index.to_i - 1
58
+ return nil if index<0 or index>=opts.size
59
+ lists << opts[index]
60
+ end
61
+ lists
62
+ end
63
+
64
+ def self.select_set(question, opts = [])
65
+ pp question
66
+
67
+ opts.each_with_index do |opt, index|
68
+ pp " #{index+1}) #{opt}"
69
+ end
70
+
71
+ vals = read_set_values(opts)
72
+ while vals.nil?
73
+ pp "Invalid selection, please try again."
74
+ vals = read_set_values(opts)
75
+ end
76
+
77
+ vals
78
+ end
79
+
80
+ end
81
+ end
@@ -22,7 +22,7 @@
22
22
  #++
23
23
 
24
24
  module Testflight
25
- VERSION = "0.1.1"
25
+ VERSION = "0.1.2"
26
26
  end
27
27
 
28
28
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testflight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -37,15 +37,18 @@ extensions: []
37
37
  extra_rdoc_files: []
38
38
  files:
39
39
  - bin/takeoff
40
+ - lib/testflight/config.rb
41
+ - lib/testflight/prompter.rb
40
42
  - lib/testflight/version.rb
41
43
  - LICENSE
42
- - README.md
44
+ - README.rdoc
43
45
  homepage: https://github.com/berk/testflight
44
46
  licenses: []
45
47
  post_install_message:
46
48
  rdoc_options: []
47
49
  require_paths:
48
- - lib
50
+ - - lib
51
+ - lib/testflight
49
52
  required_ruby_version: !ruby/object:Gem::Requirement
50
53
  none: false
51
54
  requirements:
data/README.md DELETED
File without changes