Class: Fastlane::SwiftFunction

Inherits:
Object
  • Object
show all
Defined in:
fastlane/lib/fastlane/swift_fastlane_function.rb

Direct Known Subclasses

ToolSwiftFunction

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(action_name: nil, action_description: nil, action_details: nil, keys: nil, key_descriptions: nil, key_default_values: nil, key_optionality_values: nil, key_type_overrides: nil, key_is_strings: nil, return_type: nil, return_value: nil, sample_return_value: nil) ⇒ SwiftFunction

Returns a new instance of SwiftFunction.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 18

def initialize(action_name: nil, action_description: nil, action_details: nil, keys: nil, key_descriptions: nil, key_default_values: nil, key_optionality_values: nil, key_type_overrides: nil, key_is_strings: nil, return_type: nil, return_value: nil, sample_return_value: nil)
  @function_name = action_name
  @function_description = action_description
  @function_details = action_details
  @param_names = keys
  @param_descriptions = key_descriptions
  @param_default_values = key_default_values
  @param_optionality_values = key_optionality_values
  @param_is_strings = key_is_strings
  @return_type = return_type
  @return_value = non_empty(string: return_value)
  @sample_return_value = non_empty(string: sample_return_value)
  @param_type_overrides = key_type_overrides

  # rubocop:disable Layout/LineLength
  # class instance?
  @reserved_words = %w[associativity break case catch class continue convenience default deinit didSet do else enum extension fallthrough false final for func guard if in infix init inout internal lazy let mutating nil operator override precedence private public repeat required return self static struct subscript super switch throws true try var weak where while willSet].to_set
  # rubocop:enable Layout/LineLength
end

Instance Attribute Details

#default_values_to_ignoreObject

Returns the value of attribute default_values_to_ignore.



16
17
18
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 16

def default_values_to_ignore
  @default_values_to_ignore
end

#function_descriptionObject

Returns the value of attribute function_description.



4
5
6
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 4

def function_description
  @function_description
end

#function_detailsObject

Returns the value of attribute function_details.



5
6
7
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 5

def function_details
  @function_details
end

#function_nameObject

Returns the value of attribute function_name.



3
4
5
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 3

def function_name
  @function_name
end

#param_default_valuesObject

Returns the value of attribute param_default_values.



11
12
13
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 11

def param_default_values
  @param_default_values
end

#param_descriptionsObject

Returns the value of attribute param_descriptions.



10
11
12
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 10

def param_descriptions
  @param_descriptions
end

#param_is_stringsObject

Returns the value of attribute param_is_strings.



14
15
16
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 14

def param_is_strings
  @param_is_strings
end

#param_namesObject

Returns the value of attribute param_names.



9
10
11
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 9

def param_names
  @param_names
end

#param_optionality_valuesObject

Returns the value of attribute param_optionality_values.



12
13
14
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 12

def param_optionality_values
  @param_optionality_values
end

#param_type_overridesObject

Returns the value of attribute param_type_overrides.



13
14
15
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 13

def param_type_overrides
  @param_type_overrides
end

#reserved_wordsObject

Returns the value of attribute reserved_words.



15
16
17
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 15

def reserved_words
  @reserved_words
end

#return_typeObject

Returns the value of attribute return_type.



6
7
8
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 6

def return_type
  @return_type
end

#return_valueObject

Returns the value of attribute return_value.



7
8
9
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 7

def return_value
  @return_value
end

#sample_return_valueObject

Returns the value of attribute sample_return_value.



8
9
10
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 8

def sample_return_value
  @sample_return_value
end

Instance Method Details

#build_argument_listObject



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 278

def build_argument_list
  unless @param_names
    return "[]" # return empty list for argument
  end

  argument_object_strings = @param_names.zip(param_type_overrides, param_default_values, param_optionality_values, param_is_strings).map do |name, type_override, default_value, is_optional, is_string|
    type = get_type(param: name, default_value: default_value, optional: is_optional, param_type_override: type_override, is_string: is_string)
    sanitized_name = camel_case_lower(string: name)
    sanitized_name = sanitize_reserved_word(word: sanitized_name)
    type_string = type_override == :string_callback ? ".stringClosure" : "nil"

    if !(type_override == :string_callback || !(is_optional && default_value.nil? && !type.start_with?('Any')))
      { name: "#{sanitized_name.gsub('`', '')}Arg", arg: "let #{sanitized_name.gsub('`', '')}Arg = #{sanitized_name}.asRubyArgument(name: \"#{name}\", type: #{type_string})" }
    else
      { name: "#{sanitized_name.gsub('`', '')}Arg", arg: "let #{sanitized_name.gsub('`', '')}Arg = RubyCommand.Argument(name: \"#{name}\", value: #{sanitized_name}, type: #{type_string})" }
    end
  end
  return argument_object_strings
end

#camel_case_lower(string: nil) ⇒ Object



85
86
87
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 85

def camel_case_lower(string: nil)
  string.split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
end

#determine_type_from_override(type_override: nil, default_type: nil) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 89

def determine_type_from_override(type_override: nil, default_type: nil)
  if type_override == Array
    return "[String]"
  elsif type_override == Hash
    return "[String : Any]"
  elsif type_override == Integer
    return "Int"
  elsif type_override == Boolean
    return "Bool"
  elsif type_override == Float
    return "Float"
  elsif type_override == String
    return "String"
  elsif type_override == :string_callback
    # David Hart:
    # It doesn't make sense to add escaping annotations to optional closures because they aren't function types:
    # they are basically an enum (Optional) containing a function, the same way you would store a closure in any type:
    # it's implicitly escaping because it's owned by another type.
    return "((String) -> Void)?"
  else
    return default_type
  end
end

#fix_documentation_indentation(string: nil) ⇒ Object



273
274
275
276
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 273

def fix_documentation_indentation(string: nil)
  indent = ' '
  string.gsub("\n", "\n#{indent}")
end

#get_type(param: nil, default_value: nil, optional: nil, param_type_override: nil, is_string: true) ⇒ Object



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
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 120

def get_type(param: nil, default_value: nil, optional: nil, param_type_override: nil, is_string: true)
  require 'bigdecimal'
  unless param_type_override.nil?
    type = determine_type_from_override(type_override: param_type_override)
  end

  # defaulting type to Any if is_string is false so users are allowed to input all allowed types
  type ||= is_string ? "String" : "Any"

  optional_specifier = ""
  # if we are optional and don't have a default value, we'll need to use ?
  optional_specifier = "?" if (optional && default_value.nil?) && type != "((String) -> Void)?"

  # If we have a default value of true or false, we can infer it is a Bool
  if default_value.class == FalseClass
    type = "Bool"
  elsif default_value.class == TrueClass
    type = "Bool"
  elsif default_value.kind_of?(Array)
    type = "[String]"
  elsif default_value.kind_of?(Hash)
    type = "[String : Any]"
  # Although we can have a default value of Integer type, if param_type_override overridden that value, respect it.
  elsif default_value.kind_of?(Integer)
    if type == "Double" || type == "Float"
      begin
        # If we're not able to instantiate
        _ = BigDecimal(default_value)
      rescue
        # We set it as a Int
        type = "Int"
      end
    else
      type = "Int"
    end
  end
  return "#{type}#{optional_specifier}"
end

#implementationObject



322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 322

def implementation
  args = build_argument_list
  implm = "#{args.group_by { |h| h[:arg] }.keys.join("\n")}\n"
  if args.empty?
    implm += "let args: [RubyCommand.Argument] = []\n"
  else
    implm += "let args = [#{args.group_by { |h| h[:name] }.keys.join(",\n")}]\n"
    implm += ".filter { $0?.value != nil }\n"
    implm += ".compactMap { $0 }\n"
  end
  implm += "let command = RubyCommand(commandID: \"\", methodName: \"#{@function_name}\", className: nil, args: args)\n"

  return implm + "  #{return_statement}"
end

#non_empty(string: nil) ⇒ Object



45
46
47
48
49
50
51
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 45

def non_empty(string: nil)
  if string.nil? || string.to_s.empty?
    return nil
  else
    return string
  end
end

#override_default_value_if_not_correct_type(param_name: nil, param_type: nil, default_value: nil) ⇒ Object



113
114
115
116
117
118
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 113

def override_default_value_if_not_correct_type(param_name: nil, param_type: nil, default_value: nil)
  return "[]" if param_type == "[String]" && default_value == ""
  return "nil" if param_type == "((String) -> Void)?"

  return default_value
end

#parametersObject

rubocop:disable Metrics/PerceivedComplexity



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
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 160

def parameters
  unless @param_names
    return ""
  end

  param_names_and_types = @param_names.zip(param_default_values, param_optionality_values, param_type_overrides, param_is_strings).map do |param, default_value, optional, param_type_override, is_string|
    type = get_type(param: param, default_value: default_value, optional: optional, param_type_override: param_type_override, is_string: is_string)

    unless default_value.nil?
      if type == "[String : Any]"
        # we can't handle default values for Hashes, yet
        # see method swift_default_implementations for similar behavior
        default_value = "[:]"
      elsif type != "Bool" && type != "[String]" && type != "Int" && type != "@escaping ((String) -> Void)" && type != "Float" && type != "Double"
        default_value = "\"#{default_value}\""
      elsif type == "Float" || type == "Double"
        require 'bigdecimal'
        default_value = BigDecimal(default_value).to_s
      end
    end

    # if we don't have a default value, but the param is optional, set a default value in Swift to be nil
    if optional && default_value.nil?
      default_value = "nil"
    end

    # sometimes we get to the point where we have a default value but its type is wrong
    # so we need to correct that because [String] = "" is not valid swift
    default_value = override_default_value_if_not_correct_type(param_type: type, param_name: param, default_value: default_value)

    param = camel_case_lower(string: param)
    param = sanitize_reserved_word(word: param)

    if default_value.nil?
      "#{param}: #{type}"
    else
      if type == "((String) -> Void)?"
        "#{param}: #{type} = nil"
      elsif optional && type.end_with?('?') && !type.start_with?('Any')
        "#{param}: OptionalConfigValue<#{type}> = .fastlaneDefault(#{default_value})"
      else
        "#{param}: #{type} = #{default_value}"
      end
    end
  end

  return param_names_and_types
end

#return_declarationObject



53
54
55
56
57
58
59
60
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 53

def return_declaration
  expected_type = swift_type_for_return_type
  unless expected_type.to_s.length > 0
    return ""
  end

  return " -> #{expected_type}"
end

#return_statementObject



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 298

def return_statement
  returned_object = "runner.executeCommand(command)"
  case @return_type
  when :array_of_strings
    returned_object = "parseArray(fromString: #{returned_object})"
  when :hash_of_strings
    returned_object = "parseDictionary(fromString: #{returned_object})"
  when :hash
    returned_object = "parseDictionary(fromString: #{returned_object})"
  when :bool
    returned_object = "parseBool(fromString: #{returned_object})"
  when :int
    returned_object = "parseInt(fromString: #{returned_object})"
  end

  expected_type = swift_type_for_return_type

  return_string = "_ = "
  if expected_type.length > 0
    return_string = "return "
  end
  return "#{return_string}#{returned_object}"
end

#sanitize_reserved_word(word: nil) ⇒ Object



38
39
40
41
42
43
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 38

def sanitize_reserved_word(word: nil)
  unless @reserved_words.include?(word)
    return word
  end
  return "`#{word}`"
end

#swift_codeObject

rubocop:enable Metrics/PerceivedComplexity



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 210

def swift_code
  function_name = camel_case_lower(string: self.function_name)
  function_return_declaration = self.return_declaration
  discardable_result = function_return_declaration.length > 0 ? "@discardableResult " : ''

  # Calculate the necessary indent to line up parameter names on new lines
  # with the first parameter after the opening paren following the function name.
  # i.e.: @discardableResult func someFunctionName(firstParameter: T
  #                                                secondParameter: T)
  # This just creates a string with as many spaces are necessary given whether or not
  # the function has a 'discardableResult' annotation, the 'func' keyword, function name
  # and the opening paren.
  function_keyword_definition = 'public func '
  open_paren = '('
  closed_paren = ')'
  indent = ' ' * (discardable_result.length + function_name.length + function_keyword_definition.length + open_paren.length)
  params = self.parameters.join(",\n#{indent}")

  return "#{swift_documentation}#{discardable_result}#{function_keyword_definition}#{function_name}#{open_paren}#{params}#{closed_paren}#{function_return_declaration} {\n#{self.implementation}\n}\n"
end

#swift_documentationObject



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 231

def swift_documentation
  has_parameters = @param_names && @param_names.length > 0
  unless @function_description || @function_details || has_parameters
    return ''
  end

  description = " #{fix_documentation_indentation(string: @function_description)}" if @function_description
  details = " #{fix_documentation_indentation(string: @function_details)}" if @function_details
  separator = ''
  documentation_elements = [description, swift_parameter_documentation, swift_return_value_documentation, details].compact
  # Adds newlines between each documentation element.
  documentation = documentation_elements.flat_map { |element| [element, separator] }.tap(&:pop).join("\n")

  return "/**\n#{documentation.gsub('/*', '/\\*')}\n*/\n"
end

#swift_parameter_documentationObject



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 247

def swift_parameter_documentation
  unless @param_names && @param_names.length > 0
    return nil
  end

  names_and_descriptions = @param_names.zip(@param_descriptions)

  if @param_names.length == 1
    detail_strings = names_and_descriptions.map { |name, description| " - parameter #{camel_case_lower(string: name)}: #{description}" }
    return detail_strings.first
  else
    detail_strings = names_and_descriptions.map { |name, description| "   - #{camel_case_lower(string: name)}: #{description}" }
    return " - parameters:\n#{detail_strings.join("\n")}"
  end
end

#swift_return_value_documentationObject



263
264
265
266
267
268
269
270
271
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 263

def swift_return_value_documentation
  unless @return_value
    return nil
  end

  sample = ". Example: #{@sample_return_value}" if @sample_return_value

  return " - returns: #{return_value}#{sample}"
end

#swift_type_for_return_typeObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'fastlane/lib/fastlane/swift_fastlane_function.rb', line 62

def swift_type_for_return_type
  unless @return_type
    return ""
  end

  case @return_type
  when :string
    return "String"
  when :array_of_strings
    return "[String]"
  when :hash_of_strings
    return "[String : String]"
  when :hash
    return "[String : Any]"
  when :bool
    return "Bool"
  when :int
    return "Int"
  else
    return ""
  end
end