-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
config_item.rb
359 lines (298 loc) 路 14.7 KB
/
config_item.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
84
85
86
87
88
89
90
91
92
93
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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
257
258
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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
require_relative '../ui/ui'
require_relative '../ui/errors/fastlane_error'
require_relative '../helper'
require_relative '../module'
require 'json'
module FastlaneCore
class ConfigItem
# [Symbol] the key which is used as command parameters or key in the fastlane tools
attr_accessor :key
# [String] the name of the environment variable, which is only used if no other values were found
attr_accessor :env_name
# [Array] the names of the environment variables, which is only used if no other values were found
attr_accessor :env_names
# [String] A description shown to the user
attr_accessor :description
# [String] A string of length 1 which is used for the command parameters (e.g. -f)
attr_accessor :short_option
# the value which is used if there was no given values and no environment values
attr_accessor :default_value
# [Boolean] Set if the default value is generated dynamically
attr_accessor :default_value_dynamic
# the value which is used during Swift code generation
# if the default_value reads from ENV or a file, or from local credentials, we need
# to provide a different default or it might be included in our autogenerated Swift
# as a built-in default for the fastlane gem. This is because when we generate the
# Swift API at deployment time, it fetches the default_value from the config_items
attr_accessor :code_gen_default_value
# An optional block which is called when a new value is set.
# Check value is valid. This could be type checks or if a folder/file exists
# You have to raise a specific exception if something goes wrong. Use `user_error!` for the message: UI.user_error!("your message")
attr_accessor :verify_block
# [Boolean] is false by default. If set to true, also string values will not be asked to the user
attr_accessor :optional
# [Boolean] is false by default. If set to true, type of the parameter will not be validated.
attr_accessor :skip_type_validation
# [Array] array of conflicting option keys(@param key). This allows to resolve conflicts intelligently
attr_accessor :conflicting_options
# An optional block which is called when options conflict happens
attr_accessor :conflict_block
# [String] Set if the option is deprecated. A deprecated option should be optional and is made optional if the parameter isn't set, and fails otherwise
attr_accessor :deprecated
# [Boolean] Set if the variable is sensitive, such as a password or API token, to prevent echoing when prompted for the parameter
# If a default value exists, it won't be used during code generation as default values can read from environment variables.
attr_accessor :sensitive
# [Boolean] Set if the default value should never be used during code generation for Swift
# We generate the Swift API at deployment time, and if there is a value that should never be
# included in the Fastlane.swift or other autogenerated classes, we need to strip it out.
# This includes things like API keys that could be read from ENV[]
attr_accessor :code_gen_sensitive
# [Boolean] Set if the variable is to be converted to a shell-escaped String when provided as a Hash or Array
# Allows items expected to be strings used in shell arguments to be alternatively provided as a Hash or Array for better readability and auto-escaped for us.
attr_accessor :allow_shell_conversion
# [Boolean] Set if the variable can be used from shell
attr_accessor :display_in_shell
# Creates a new option
# @param key (Symbol) the key which is used as command parameters or key in the fastlane tools
# @param env_name (String) the name of the environment variable, which is only used if no other values were found
# @param env_names (Array) the names of the environment variables, which is only used if no other values were found
# @param description (String) A description shown to the user
# @param short_option (String) A string of length 1 which is used for the command parameters (e.g. -f)
# @param default_value the value which is used if there was no given values and no environment values
# @param default_value_dynamic (Boolean) Set if the default value is generated dynamically
# @param verify_block an optional block which is called when a new value is set.
# Check value is valid. This could be type checks or if a folder/file exists
# You have to raise a specific exception if something goes wrong. Append .red after the string
# @param is_string *DEPRECATED: Use `type` instead* (Boolean) is that parameter a string? Defaults to true. If it's true, the type string will be verified.
# @param type (Class) the data type of this config item. Takes precedence over `is_string`. Use `:shell_string` to allow types `String`, `Hash` and `Array` that will be converted to shell-escaped strings
# @param skip_type_validation (Boolean) is false by default. If set to true, type of the parameter will not be validated.
# @param optional (Boolean) is false by default. If set to true, also string values will not be asked to the user
# @param conflicting_options ([]) array of conflicting option keys(@param key). This allows to resolve conflicts intelligently
# @param conflict_block an optional block which is called when options conflict happens
# @param deprecated (Boolean|String) Set if the option is deprecated. A deprecated option should be optional and is made optional if the parameter isn't set, and fails otherwise
# @param sensitive (Boolean) Set if the variable is sensitive, such as a password or API token, to prevent echoing when prompted for the parameter
# @param display_in_shell (Boolean) Set if the variable can be used from shell
# rubocop:disable Metrics/ParameterLists
# rubocop:disable Metrics/PerceivedComplexity
def initialize(key: nil,
env_name: nil,
env_names: nil,
description: nil,
short_option: nil,
default_value: nil,
default_value_dynamic: false,
verify_block: nil,
is_string: true,
type: nil,
skip_type_validation: false,
optional: nil,
conflicting_options: nil,
conflict_block: nil,
deprecated: nil,
sensitive: nil,
code_gen_sensitive: false,
code_gen_default_value: nil,
display_in_shell: true)
UI.user_error!("key must be a symbol") unless key.kind_of?(Symbol)
UI.user_error!("env_name must be a String") unless (env_name || '').kind_of?(String)
UI.user_error!("env_names must be an Array") unless (env_names || []).kind_of?(Array)
(env_names || []).each do |name|
UI.user_error!("env_names must only contain String") unless (name || '').kind_of?(String)
end
if short_option
UI.user_error!("short_option for key :#{key} must of type String") unless short_option.kind_of?(String)
UI.user_error!("short_option for key :#{key} must be a string of length 1") unless short_option.delete('-').length == 1
end
if description
UI.user_error!("Do not let descriptions end with a '.', since it's used for user inputs as well for key :#{key}") if description[-1] == '.'
end
if conflicting_options
conflicting_options.each do |conflicting_option_key|
UI.user_error!("Conflicting option key must be a symbol") unless conflicting_option_key.kind_of?(Symbol)
end
end
if deprecated
# deprecated options are automatically optional
optional = true if optional.nil?
UI.crash!("Deprecated option must be optional") unless optional
# deprecated options are marked deprecated in their description
description = deprecated_description(description, deprecated)
end
optional = false if optional.nil?
sensitive = false if sensitive.nil?
@key = key
@env_name = env_name
@env_names = [env_name].compact + (env_names || [])
@description = description
@short_option = short_option
@default_value = default_value
@default_value_dynamic = default_value_dynamic
@verify_block = verify_block
@is_string = is_string
@data_type = type
@data_type = String if type == :shell_string
@optional = optional
@conflicting_options = conflicting_options
@conflict_block = conflict_block
@deprecated = deprecated
@sensitive = sensitive
@code_gen_sensitive = code_gen_sensitive || sensitive
@allow_shell_conversion = (type == :shell_string)
@display_in_shell = display_in_shell
@skip_type_validation = skip_type_validation # sometimes we allow multiple types which causes type validation failures, e.g.: export_options in gym
@code_gen_default_value = code_gen_default_value
update_code_gen_default_value_if_able!
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/ParameterLists
# if code_gen_default_value is nil, use the default value if it isn't a `code_gen_sensitive` value
def update_code_gen_default_value_if_able!
# we don't support default values for procs
if @data_type == :string_callback
@code_gen_default_value = nil
return
end
if @code_gen_default_value.nil?
unless @code_gen_sensitive
@code_gen_default_value = @default_value
end
end
end
def verify!(value)
valid?(value)
end
def ensure_generic_type_passes_validation(value)
if @skip_type_validation
return
end
if data_type != :string_callback && data_type && !value.kind_of?(data_type)
UI.user_error!("'#{self.key}' value must be a #{data_type}! Found #{value.class} instead.")
end
end
def ensure_boolean_type_passes_validation(value)
if @skip_type_validation
return
end
# We need to explicitly test against Fastlane::Boolean, TrueClass/FalseClass
if value.class != FalseClass && value.class != TrueClass
UI.user_error!("'#{self.key}' value must be either `true` or `false`! Found #{value.class} instead.")
end
end
def ensure_array_type_passes_validation(value)
if @skip_type_validation
return
end
# Arrays can be an either be an array or string that gets split by comma in auto_convert_type
if !value.kind_of?(Array) && !value.kind_of?(String)
UI.user_error!("'#{self.key}' value must be either `true` or `false`! Found #{value.class} instead.")
end
end
# Make sure, the value is valid (based on the verify block)
# Raises an exception if the value is invalid
def valid?(value)
# we also allow nil values, which do not have to be verified.
return true if value.nil?
# Verify that value is the type that we're expecting, if we are expecting a type
if data_type == Fastlane::Boolean
ensure_boolean_type_passes_validation(value)
elsif data_type == Array
ensure_array_type_passes_validation(value)
else
ensure_generic_type_passes_validation(value)
end
if @verify_block
begin
@verify_block.call(value)
rescue => ex
UI.error("Error setting value '#{value}' for option '#{@key}'")
raise Interface::FastlaneError.new, ex.to_s
end
end
true
end
def fetch_env_value
env_names.each do |name|
next if ENV[name].nil?
# verify! before using (see https://github.com/fastlane/fastlane/issues/14449)
return ENV[name].dup if verify!(auto_convert_value(ENV[name]))
end
return nil
end
# rubocop:disable Metrics/PerceivedComplexity
# Returns an updated value type (if necessary)
def auto_convert_value(value)
return nil if value.nil?
if data_type == Array
return value.split(',') if value.kind_of?(String)
elsif data_type == Integer
return value.to_i if value.to_i.to_s == value.to_s
elsif data_type == Float
return value.to_f if value.to_f.to_s == value.to_s
elsif data_type == Symbol
return value.to_sym if value.to_sym.to_s == value.to_s
elsif allow_shell_conversion
return value.shelljoin if value.kind_of?(Array)
return value.map { |k, v| "#{k.to_s.shellescape}=#{v.shellescape}" }.join(' ') if value.kind_of?(Hash)
elsif data_type == Hash && value.kind_of?(String)
begin
parsed = JSON.parse(value)
return parsed if parsed.kind_of?(Hash)
rescue JSON::ParserError
end
elsif data_type != String
# Special treatment if the user specified true, false, on, off or YES, NO
# There is no boolean type, so we just do it here
if %w(yes YES true TRUE on ON).include?(value)
return true
elsif %w(no NO false FALSE off OFF).include?(value)
return false
end
end
# rubocop:enable Metrics/PerceivedComplexity
return value # fallback to not doing anything
end
# Determines the defined data type of this ConfigItem
def data_type
if @data_type
@data_type
else
(@is_string ? String : nil)
end
end
# Replaces the attr_accessor, but maintains the same interface
def string?
data_type == String
end
# it's preferred to use self.string? In most cases, except in commander_generator.rb, cause... reasons
def is_string
return @is_string
end
def to_s
[@key, @description].join(": ")
end
def deprecated_description(initial_description, deprecated)
has_description = !initial_description.to_s.empty?
description = "**DEPRECATED!**"
if deprecated.kind_of?(String)
description << " #{deprecated}"
description << " -" if has_description
end
description << " #{initial_description}" if has_description
description
end
def doc_default_value
return "[*](#parameters-legend-dynamic)" if self.default_value_dynamic
return "" if self.default_value.nil?
return "`''`" if self.default_value.instance_of?(String) && self.default_value.empty?
return "`:#{self.default_value}`" if self.default_value.instance_of?(Symbol)
"`#{self.default_value}`"
end
def help_default_value
return "#{self.default_value} *".strip if self.default_value_dynamic
return "" if self.default_value.nil?
return "''" if self.default_value.instance_of?(String) && self.default_value.empty?
return ":#{self.default_value}" if self.default_value.instance_of?(Symbol)
self.default_value
end
end
end