Cómo crear la CLI de Swift

¿Por qué CLI

La herramienta de línea de comandos CLI (interfaz de línea de comandos) es una de las herramientas esenciales para los desarrolladores, y escribir herramientas de línea de comandos para manejar algún trabajo también es una habilidad imprescindible para los desarrolladores. Para algunos trabajos repetitivos, el uso de scripts de línea de comandos puede automatizar tareas, lo que mejorará en gran medida la eficiencia de nuestro trabajo. Al mismo tiempo, también se pueden evitar errores causados ​​por factores humanos. Por ejemplo, el procesamiento por lotes de algunos archivos, tablas, etc., el concepto de integración continua en el desarrollo de aplicaciones también se basa en una serie de scripts de línea de comandos automatizados.

Por qué usar Swift

Hay muchos lenguajes de desarrollo de línea de comandos, como los más básicos shell脚本, y los más comunes, como Python, Go, Rubyetc. Los lenguajes más utilizados y familiares en el trabajo de los desarrolladores de iOS son los lenguajes Objective-c y Swift, por lo que esto traerá varios problemas.

  • Los desarrolladores de aplicaciones necesitan un cierto costo de aprendizaje para escribir lenguajes de secuencias de comandos.
  • Los desarrolladores de aplicaciones necesitan cambiar de idioma con frecuencia al escribir guiones.
  • Swift es el idioma principal de Apple, y los scripts relacionados con iOS se han desarrollado utilizando Swift, como algunos 重签scripts 动态库注入, etc. El programa de línea de comandos desarrollado por Swift no solo puede completar varias tareas de procesamiento por lotes como un lenguaje de secuencias de comandos normal, sino que también tiene ventajas naturales para los proyectos de iOS.

Aunque Objective-cel código escrito también se puede clang XXXXXX.m -framework Foundation -o XXXXXcompilar en un archivo ejecutable para su uso, después de todo, la intención original del diseño de Objective-c no incluye programas de línea de comandos, por lo que todavía hay muchos defectos en algunas funciones, como la introducción de parámetros. y tratar. Cuando se lanzó oficialmente Swift, se anunció que Swift puede desarrollar aplicaciones, scripts, servicios en segundo plano, interfaces, etc., y tiene una amplia gama de aplicaciones.

Proceso básico de Swift CLI

1: Creación del proyecto

Use Swift Package Manager (SPM) para crear proyectos. SPM es una herramienta oficial proporcionada por Apple para administrar la distribución del código fuente. Es similar a Cocoapods o Carthage, pero más ligero y compatible de forma nativa con Xcode. No es necesario configurar varios entornos. Puede usar directamente.

$ cd CLIDemo  // 进入到你的文件夹
$ swift package init --type executable
复制代码

Después de ejecutar el comando, se generarán los archivos necesarios

en

  • Package.swift: similar al Podfile en Cocoapods, que describe las dependencias de referencia de algunas bibliotecas y la configuración del proyecto.

  • Carpeta Source/CLIDemo: nuestro directorio de proyectos, y agregaremos el código fuente o los archivos a este directorio más adelante.

  • CLIDemo.swift: el punto de entrada del programa de línea de comandos, el nombre del archivo no se puede cambiar y contiene la función principal.

    @main
    public struct CLIDemo {
        public private(set) var text = "Hello, World!"
    
        public static func main() {
            print(CLIDemo().text)
        }
    }
    复制代码
  • Carpeta de pruebas: proyecto de prueba, similar al proyecto normal de Xcode.

2: Usa Xcode para desarrollar

Después de crear la estructura de archivos del proyecto, todavía faltan XXX.xcodeprojarchivos y no hay forma de abrirlos directamente con Xcode. Use el siguiente comando para crear una entrada de Xcode

$ swift package generate-xcodeproj
复制代码

A continuación, abra el archivo generado CLIDemo.xcodeproj, seleccione el dispositivo en ejecución como Mac y, después de compilar y ejecutar, puede ver la redacción de texto de Hello World en la consola de Xcode.Hasta ahora, se ha creado todo nuestro proyecto de desarrollo de línea de comandos.

De manera similar, además de usar la GUI de Xcode para compilar y ejecutar en ese momento, también puede usar la línea de comando

$ swift run CLIDemo
复制代码

luego obtener la misma salida

3: Paso y procesamiento de parámetros

Método 1: parámetros de análisis de la API del sistema

Lo anterior describe la creación del proyecto y la escritura de la línea de comando, por lo general tendremos parámetros al llamar a la línea de comando

$ command 参数1 参数2 参数3 ....
复制代码

También hay una API para analizar parámetros a nivel de código.

/// Command-line arguments for the current process.
@frozen public enum CommandLine {

    /// Access to the raw argc value from C.
    public static var argc: Int32 { get }

    /// Access to the raw argv value from C. Accessing the argument vector
    /// through this pointer is unsafe.
    public static var unsafeArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> { get }

    public static var arguments: [String]
}
复制代码

Más simple de usar

// 解析外部传进来的参数
let arguments = CommandLine.arguments

// 第一个参数
let firstArg = arguments[1]

// 第二个参数
let secondtArg = arguments[2]
print("My args = \(arguments)  first = \(firstArg)  second = \(secondtArg)");
复制代码

Cabe señalar que el primer elemento de la matriz de parámetros devuelto aquí es la ruta del archivo ejecutable en sí, y luego el primer parámetro ingresado por el usuario comienza desde el segundo elemento, similar a la función en iOS, donde el primer parámetro es objcMsgSenduno mismo Estos parámetros se pueden analizar para diferentes propósitos.

Forma 2: Usar SwiftArgumentParser

在实际使用中,一个完善的命令行参数一定不会这么简单,而且我们在解析参数的时候也不知道使用方传入参数的顺序,一些简单的命令,或者只有一个参数的情况下可以使用CommandLine 的API,更复杂的情况下需要时用SwiftArgumentParser来进行处理。

SwiftArgumentParser是苹果开源的一个用Swift编写的参数解析器,用于解析命令行参数(command-line arguments),具有直观、易用、简洁、安全的特点。虽然是苹果自己开发的,但是毕竟还是外部库需要使用Swift package打包进来,对Package文件进行编写

import PackageDescription
let package = Package(
    name: "CLIDemo",
    dependencies: [
        //引入swift-argument-parser解析器
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "CLIDemo",
            dependencies: [
                //将解析器依赖到target
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]),
        .testTarget(
            name: "CLIDemoTests",
            dependencies: ["CLIDemo"]),
    ]
)
复制代码

同时CLIDemo.swift代码文件也要相应的进行修改

  • 1:引入ArgumentParser
  • 2:将struct改为Class(方便后续的开发),并遵循ParsableCommand协议
  • 3:修改main函数为run:因为遵循协议后,原来的main被ParsableCommand接管入口,内部会调用函数名为run的函数作为入口。
import ArgumentParser
@main
class CLIDemo: ParsableCommand {
    required init() {
    }
    func run() {
        // 解析外部传进来的参数
        let arguments = CommandLine.arguments
        // 第一个参数
        let firstArg = arguments[1]
        // 第二个参数
        let secondtArg = arguments[2]
        print("My args = \(arguments)  first = \(firstArg)  second = \(secondtArg)");
    }
}
复制代码

工程修改完后已经具备了ArgumentParser的开发环境,ArgumentParser的参数分为三类

  • @Argument:无标记位参数,与上面介绍的直接使用CommandLine的API解析方式相似,该类型的参数没有别名标记位,而且必须按照用户传入的顺序做解析。
  • @Option:带有标记位参数,这个类型的参数就是通过别名或者标记为来标识的,也是我们常见的参数用法比如 -n myName或者--name myName。其中-n--name就是该参数的长别名和短别名,同样因为有了别名,所以解析时候不用关系用户输入参数的顺序。
  • @Flag:标记位,是一个bool变量,比如常用 --verbose-h
static var configuration = CommandConfiguration(abstract: "这是一个测试Demo")
@Argument(help: "这是一个Argument 参数")
var argumentArg: String = "Argument"

@Option(name: [.short, .long], help: "这是一个option参数")
var optionArg: String = "option"

@Flag(name: [.short, .long], help: "这是一个Flag参数")
var flagArg: Bool = false
复制代码

关于参数的描述系统提供以下定义,通常使用shortlong

internal enum Representation: Hashable {
  case long // 参数原标记位,就是变量名
  case customLong(_ name: String, withSingleDash: Bool)  // 自定义标记位
  case short // 参数短标记位  为-加上变量名第一个字母
  case customShort(_ char: Character, allowingJoined: Bool) // 自定义短标记位
}
复制代码

ArgumentParser默认集成了-h参数,完成以上参数定义后,通过-h输出我们命令行Demo帮助文档

:::warning

注意:参数命名时候如果使用驼峰结构,最终的参数会被添加-比如上面的我定义的flagArg,最终命令行的Flag参数为--flag-arg。所以这里尽量不用驼峰结构。

:::

4:调试运行

因为我们是在Xcode中编程开发,所以不用每次都跑到命令行中取执行Swift run CLIDemo XXX来编译运行我们的工具,这样不然切来切去影响工作效率,而且没法使用断点调试,正确的方式是像正常的iOS开发一样直接在Xcode中编译运行。而参数传递可以在Xcode上方的Edit Scheme中处理

然后编译运行即可,以上行为等价于在终端中输入swift run CLIDemo arg1 -f -o option

5:编译成可执行文件

我们假设已经完成了命令行程序的编写,最终要达到的目是执行我们的命令行程序然后输出Hello World!,那么首先我们需要把代码编译成可执行文件,通过如下命令

& swift build -c release
复制代码

编译之后我们可以在工程目录下找到我们产物

这样一来我们就可以把该文件进行分发,让其他人或者服务器端使用我们的的命令行工具了,如果有需要可以把该文件放到/usr/local/bin/ 目录下,这样可以在任意路径下使用

Swift CLI实战(iPa下载器)

上面讲了一个Swift CLI 工具从开发到使用的完整流程,但是一个真正的命令行工具一定不仅仅是输出一个Hello World,需要有子命令公共参数二次输入敏感输入终端输出样式, 进度回调等功能。本节内容会通过实现一个ipa下载器,来介绍下Swift CLI的一些进阶用法,这些用法几乎能覆盖之后百分之九十的工作场景。

一个iPa下载器可以从Appstore下载App,同时集成了Appstore相关能力,如登录,搜索,下载等。我们可以将这些能力封装成不同的子命令来进行调用,像如下这样

1:子命令

子命令也是ArgumentParser的能力项之一,可以在这里查看官方文档,具体代码

static var configuration = CommandConfiguration(abstract: "一个iPa下载工具", subcommands: [Search.self, Login.self, Download.self])
复制代码

其中要创建子命令对应的.swift文件。且每个文件中都应像之前的CLIDemo.swift的结构一样,定义自己的类,且遵循ParsableCommand协议,以Search举例,其他同级子命令同理,子命令嵌套子命令,结构类似,以此类推。

class Search: ParsableCommand {
   required init() {
   }
   static var configuration = CommandConfiguration(abstract: "搜索appstore上的App")
   func run() {
   }
}
复制代码

2:公共参数

当有多个子命令的时候我们一定会有一些参数是公用的,比如上面展示的--verbose,如果每个子命令文件都写一遍显然不现实,所以ArgumentParser提供了OptionGroup选项组的能力。

我们可以在一个公共的类或结构体中定义一系列公用参数,然后在需要使用公共参数的子命令文件中定义@OptionGroup如下图。在解析的时候可以用GlobalOptions.verbose来取值

struct GlobalOptions: ParsableArguments {
    @Flag(name: .shortAndLong)
    var verbose: Bool

    @Argument var values: [Int]
}

class Search: ParsableArguments {
    @Option var name: String
    @OptionGroup var globals: GlobalOptions
}
复制代码

3:Appstore登录

首先登录需要输入用户名密码,所以Login文件的参数一定是包含username,password,使用上面提到的方式很容易将这两个参数传入,但是输入密码的时候如果是明文的话就太不安全了,终端输入密码的方式都是隐式输入,同样我们的工具也要具备这个能力,使用getpass函数可以达到隐式输入图的目的,这样打字就不会显示到终端中,也不用为密码单独分配一个参数。

CommonMethod().showCommonMessage(text: "请输入密码:")
  guard let psd = getpass("") else {
 	 CommonMethod().showErrorMessage(text: "需要输入密码")
   Login.exit()
}
复制代码

  • 登录Api "https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate?guid=MAC地址"

4:二次输入

拿到用户名和密码可以进行Appstore API请求进行登录了,但是Appstore 是有二次认证的,所以我们还需要输入一个授权码。此时我们可以通过Appstore服务端返回的信息来提示用户输入授权码,需要授权码的错误信息为MZFinance.BadLogin.Configurator_message,此时我们的进程还未结束,需要用户二次输入,对应的api为readLine

CommonMethod().showWarningMessage(text: "请输入双重认证的Code:")
let authCode = readLine();
self.authCode = authCode ?? ""
复制代码

收到用户的授权码后,携带授权码重新请求Appstore API接口即可,

5:本地持久化

登录成功后会获得DSID Token以及相关Cookie信息,需要把这些信息持久化到本地,避免每次使用该工具都要走登录流程,持久化的方式可以使用数据库、UserDefault、写文件等方式进行,这些对于iOS开发人员来说并不陌生。

6:文件搜索

文件搜索比较简单,我们可以通过APP的名字进行搜索,入参为:

  • appname :APP名称
  • appid:APP在applestore上的ID(非必要)
  • limit:结果条数限制(非必要)
  • Country:APP所在国家(非必要)

整个搜索流程为:

App 搜索的API为https://itunes.apple.com/search

7:文件下载

通过上一步拿到的bundleid调用AppStore的下载接口可以实现ipa包的下载,所以这里的入参为:

  • bundleid:App的bundleid
  • path:下载路径

拼接好请求后很容易就进入下载流程开始下载了。在Swift中可以使用系统原生的NSUrlsession或者使用一些开源三方空类似Alamofire、moya等。

8: estilo de salida de línea de comando

Al realizar tareas de descarga o algunas tareas que requieren mucho tiempo, debemos proporcionar una barra de progreso para brindar a los usuarios ciertas sugerencias. La barra de progreso en la terminal es en realidad un patrón compuesto por varios códigos de caracteres, y se usan diferentes colores para distinguir diferentes estados.

  • descargando

  • Descarga completa

  • Descarga fracasó

El patrón de la barra de progreso se compone de dos partes.

  • Indica el carácter de finalización: █
  • Indica los caracteres restantes: ░

cronograma

De forma predeterminada, se generan 50 ░, y cada vez que se devuelve el progreso de la descarga, reemplazaremos la parte completa con █ según el porcentaje, mostrando así un estilo similar a una barra de progreso que avanza. Si busca el refinamiento, puede ajustar dinámicamente la longitud de la barra de progreso de acuerdo con el ancho de la ventana de la línea de comando, para evitar que la ventana sea demasiado pequeña, lo que hace que la barra de progreso se muestre en líneas dobladas.

Al mismo tiempo, para garantizar que la barra de progreso permanezca en una línea, el cursor debe moverse a la posición de inicio para cada pantalla y luego volver a mostrarse en esta línea Aquí, \r se usa al principio, y se elimina la operación \n al final de la función de impresión. Aquí hay una función para mostrar el progreso.

func showProcess(process:Float, customEnd:String) -> Void {
		//  宽度50
    let barW = 50
    let com = Int(Float(barW)*Float(process))
    let rem = barW - com
		//  自定义结尾文案和颜色
    var endStr = ""
    var color = ""
    if customEnd.count > 0 {
      endStr = customEnd
    }

  	//  下载完成样式
    if com == 50 {
      endStr = "下载完成:【100%】"
      color = "\u{001B}[0;32m"
    }

	  // 进度条
    let bar = String(repeating: "█", count: com) + String(repeating: "░", count: rem)
  
    // 打印进度条
    print("\r\(color)\(bar) \(endStr)", terminator: "")
  
  	// 刷新输出缓冲区
    fflush(stdout)
}
复制代码

color

Agregar el código correspondiente antes del texto puede cambiar el estilo del texto, como

  • El color del texto \u{001b}[?men el que ? ∈ [30, 37].

    黑(black):\u{001b}[30m
    红(red):\u{001b}[31m
    绿(green):\u{001b}[32m
    黄(yellow):\u{001b}[33m
    蓝(blue):\u{001b}[34m
    品红(magenta):\u{001b}[35m
    蓝绿(cyan):\u{001b}[36m
    白(white):\u{001b}[37m
    还原初始(reset) :\u{001b}[0m
    复制代码
  • Color de fondo del texto \u{001b}[?m, donde ? ∈ [40, 47].

    黑(black):\u{001b}[40m
    红(red):\u{001b}[41m
    绿(green):\u{001b}[42m
    黄(yellow):\u{001b}[43m
    蓝(blue):\u{001b}[44m
    品红(magenta):\u{001b}[45m
    蓝绿(cyan):\u{001b}[46m
    白(white):\u{001b}[47m
    复制代码
  • Estilo de fuente

    加粗加亮:\u{001b}[1m
    降低亮度:\u{001b}[2m
    斜体:\u{001b}[3m
    下划线:\u{001b}[4m
    反色:\u{001b}[7m
    复制代码

Los comandos anteriores se pueden usar solos o en combinación, como combinar las siguientes condiciones

  • \u{001b}[1m: audaz y brillante
  • \u{001b}[4m: guión bajo
  • \u{001b}[42m: fondo verde
  • \u{001b}[31m: fuente roja
print("\n\u{001b}[1m\u{001b}[4m\u{001b}[42m\u{001b}[31m 这是一段绿色背景红色字体加粗带有下划线的文字")
复制代码

Resumir

Después de completar las operaciones anteriores, se completa un descargador ipa y el código fuente específico se puede ver aquí . El uso de Swift para escribir CLI puede mejorar en gran medida la eficiencia de desarrollo de los desarrolladores de iOS y reducir el costo de aprender lenguajes de secuencias de comandos. Al mismo tiempo, Apple continuará actualizando e iterando Swift en cualquier momento, y es posible que pueda hacer más cosas con Swift. en el futuro.

Supongo que te gusta

Origin juejin.im/post/7220244993788559397
Recomendado
Clasificación