// RubyCommand.swift // Copyright (c) 2023 FastlaneTools // // ** NOTE ** // This file is provided by fastlane and WILL be overwritten in future updates // If you want to add extra functionality to this project, create a new file in a // new group so that it won't be marked for upgrade // import Foundation struct RubyCommand: RubyCommandable { var type: CommandType { return .action } struct Argument { enum ArgType { case stringClosure var typeString: String { switch self { case .stringClosure: return "string_closure" // this should match when is in ruby's SocketServerActionCommandExecutor } } } let name: String let value: Any? let type: ArgType? init(name: String, value: Any?, type: ArgType? = nil) { self.name = name self.value = value self.type = type } var hasValue: Bool { return value != nil } var json: String { if let someValue = value { let typeJson: String if let type = type { typeJson = ", \"value_type\" : \"\(type.typeString)\"" } else { typeJson = "" } if type == .stringClosure { return "{\"name\" : \"\(name)\", \"value\" : \"ignored_for_closure\"\(typeJson)}" } else if let array = someValue as? [String] { return "{\"name\" : \"\(name)\", \"value\" : \(array)\(typeJson)}" } else if let hash = someValue as? [String: Any] { let jsonData = try! JSONSerialization.data(withJSONObject: hash, options: []) let jsonString = String(data: jsonData, encoding: .utf8)! return "{\"name\" : \"\(name)\", \"value\" : \(jsonString)\(typeJson)}" } else { let dictionary = [ "name": name, "value": someValue, ] let jsonData = try! JSONSerialization.data(withJSONObject: dictionary, options: []) let jsonString = String(data: jsonData, encoding: .utf8)! return jsonString } } else { // Just exclude this arg if it doesn't have a value return "" } } } let commandID: String let methodName: String let className: String? let args: [Argument] let id: String = UUID().uuidString var closure: ((String) -> Void)? { let callbacks = args.filter { ($0.type != nil) && $0.type == .stringClosure } guard let callback = callbacks.first else { return nil } guard let callbackArgValue = callback.value else { return nil } guard let callbackClosure = callbackArgValue as? ((String) -> Void) else { return nil } return callbackClosure } func callbackClosure(_ callbackArg: String) -> ((String) -> Void)? { // WARNING: This will perform the first callback it receives let callbacks = args.filter { ($0.type != nil) && $0.type == .stringClosure } guard let callback = callbacks.first else { verbose(message: "received call to performCallback with \(callbackArg), but no callback available to perform") return nil } guard let callbackArgValue = callback.value else { verbose(message: "received call to performCallback with \(callbackArg), but callback is nil") return nil } guard let callbackClosure = callbackArgValue as? ((String) -> Void) else { verbose(message: "received call to performCallback with \(callbackArg), but callback type is unknown \(callbackArgValue.self)") return nil } return callbackClosure } func performCallback(callbackArg: String, socket: SocketClient, completion: @escaping () -> Void) { verbose(message: "Performing callback with: \(callbackArg)") socket.leave() callbackClosure(callbackArg)?(callbackArg) completion() } var commandJson: String { let argsArrayJson = args .map { $0.json } .filter { $0 != "" } let argsJson: String? if !argsArrayJson.isEmpty { argsJson = "\"args\" : [\(argsArrayJson.joined(separator: ","))]" } else { argsJson = nil } let commandIDJson = "\"commandID\" : \"\(commandID)\"" let methodNameJson = "\"methodName\" : \"\(methodName)\"" var jsonParts = [commandIDJson, methodNameJson] if let argsJson = argsJson { jsonParts.append(argsJson) } if let className = className { let classNameJson = "\"className\" : \"\(className)\"" jsonParts.append(classNameJson) } let commandJsonString = "{\(jsonParts.joined(separator: ","))}" return commandJsonString } } // Please don't remove the lines below // They are used to detect outdated files // FastlaneRunnerAPIVersion [0.9.2]