Autograph 提供了用于在 Synopsis 框架之上构建源代码生成实用程序(命令行应用程序)的工具。
Package.Dependency.package(
url: "https://github.com/RedMadRobot/autograph",
from: "1.0.0"
)
首先,为了使用 Swift 构建控制台可执行文件,需要有一个执行入口点,即 main.swift
文件。
Autograph 使用了一种通用方法,即在 main.swift
文件执行期间,你的实用程序应用会实例化一个特殊的 «Application» 类对象,并将控制流传递给它
// main.swift sample code
import Foundation
exit(AutographApplication().run())
macOS 控制台实用程序预期在其执行后返回一个 Int32
代码,任何与 0
不同的代码都应被视为错误,因此 AutographApplication
方法 run()
返回 Int32
。该方法大致如下所示
// class AutographApplication { ...
func run() -> Int32 {
do {
try someDangerousOperation()
try someOtherDangerousOperation()
...
} catch let error {
print(error)
return 1
}
return 0
}
考虑到以上所有内容,你的入口点是 AutographApplication
类。
为了创建你自己的实用程序,你需要创建你自己的 main.swift
文件,按照上面的示例,并创建你自己的 AutographApplication
子类。
AutographApplication
提供了几个方便的扩展点,供你完成执行过程。当应用运行时,它会经历七个主要步骤
AutographApplication
控制台应用默认支持三个参数
-help
— 打印帮助;-verbose
— 在执行期间打印附加信息;-project_name [名称]
— 提供项目名称,用于生成的代码中;如果未设置,则使用 “GEN” 作为默认项目名称。所有参数以及当前工作目录都聚合在一个 ExecutionParameters
实例中
class ExecutionParameters {
let projectName: String
let verbose: Bool
let printHelp: Bool
let workingDirectory: String
}
ExecutionParameters
实例就像一个字典,这样你就可以查询它来获取你自己的参数
/*
./MyUtility -verbose -my_argument value
*/
let parameters: ExecutionParameters = getParameters()
let myArgument: String = parameters["-my_argument"] ?? "default_value"
没有值的参数存储在这个字典中,值为空 String
。
当你的应用使用 -help
参数运行时,执行被中断,并且 AutographApplication.printHelp()
方法被调用。
这是你的第一个扩展点。你可以扩展这个方法,以便提供你自己的帮助信息,如下所示
// class App: AutographApplication {
override func printHelp() {
super.printHelp()
print("""
-input
Input folder with model source files.
If not set, current working directory is used as an input folder.
-output
Where to put generated files.
If not set, current working directory is used as an input folder.
""")
}
别忘了在你的帮助信息后留一个空行。
AutographApplication
调用 provideInputFoldersList(fromParameters:)
方法来获取输入文件夹的列表。此方法默认返回一个空列表。
这是你的下一个主要扩展点。在这里,你需要实现一种方法,让你的实用程序应用确定输入文件夹的列表,应用应从中搜索要分析的源代码文件。
你可以像这样重写此方法
// class App: AutographApplication {
override func provideInputFoldersList(
fromParameters parameters: ExecutionParameters
) throws -> [String] {
let input: String = parameters["-input"] ?? ""
return [input]
}
这样,你可以查询 ExecutionParameters
以获取 -input
参数,并提供一个默认的 ""
值,它代表当前工作目录。
AutographApplication
稍后通过与当前工作目录连接,将所有相对路径转换为绝对路径,因此,空字符串 ""
将导致工作目录作为默认输入文件夹。
如果你认为对于执行来说,拥有一个显式的 -input
参数值至关重要,你可以像这样抛出异常
// class App: AutographApplication {
enum ExecutionError: Error, CustomStringConvertible {
case noInputFolder
var description: String {
switch self {
case .noInputFolder: return "!!! PLEASE PROVIDE AN -input FOLDER !!!"
}
}
}
override func provideInputFoldersList(
fromParameters parameters: ExecutionParameters
) throws -> [String] {
guard let input: String = parameters["-input"]
else { throw ExecutionError.noInputFolder }
return [input]
}
当步骤 #3 完成时,AutographApplication
递归扫描输入文件夹及其子文件夹以查找 *.swift
文件。此操作的结果是一个 URL
对象列表,然后在步骤 #5 中传递给 Synopsis 框架,见下文。
对于此过程,你没有太多可以做的,尽管有一个 open
计算属性 AutographApplication.fileFinder
,你可以在其中返回你自己的 FileFinder
子类实例,如果你想,例如,禁止递归文件搜索。
步骤 #5 非常直接,因为它使用在上一步中找到的源代码文件的 URL
实体列表创建一个 Synopsis
实例。
此外,如果你的应用在 -verbose
模式下运行,它还会调用 Synopsis.printToXcode()
。
你不能扩展或重写此步骤。
一个 Synopsis
实例被传递到 AutographApplication.compose(forSynopsis:parameters:)
方法中,你需要在其中生成新的源代码。终于!
此方法返回一个 Implementation
对象列表,每个对象都包含生成的源代码和一个文件路径,源代码需要存储在该文件路径中
struct Implementation {
let filePath: String
let sourceCode: String
}
通常,此组合过程分为几个步骤。
首先,你需要定义一个输出文件夹路径。AutographApplication
不会将此路径转换为绝对路径,因此,你可以使用相对路径,例如 "."
。
其次,你需要从获得的 Synopsis
实体中提取所有必要的信息。
最后,你将生成实际的源代码。
在每个步骤中,如果出现错误,你可以抛出错误。如果你想让你的应用针对某些特定的源代码发出抱怨,请考虑使用 XcodeMessage
错误。
// class App: AutographApplication {
override func compose(
forSynopsis synopsis: Synopsis,
parameters: ExecutionParameters
) throws -> [Implementation] {
// use current directory as a default output folder:
let output: String = parameters["-output"] ?? "."
// make sure everything is annotated properly:
try synopsis.classes.forEach { (classDescription: ClassDescription) in
guard classDescription.annotations.contains(annotationName: "model")
else {
throw XcodeMessage(
declaration: classDescription.declaration,
message: "[MY GENERATOR] THIS CLASS IS NOT A MODEL"
)
}
}
// my composer may also throw:
return try MyComposer().composeSourceCode(outOfModels: synopsis.classes)
}
最后,你的 Implementation
实例正在被写入硬盘驱动器。
如果需要,将创建所有必要的输出文件夹。此外,如果已经存在生成的源代码文件,并且源代码没有更改 — FileWriter
将不会触碰它。
如果你想调整此过程,有一个 open
计算属性 AutographApplication.fileWriter
,你可以在其中返回你自己的 FileWriter
子类实例。
在应用执行上述步骤期间,如果应用在 -verbose
模式下运行,像 FileFinder
或 FileWriter
这样的不同实用程序可能会打印调试消息。这些实用程序使用相同的 Log.v(message:)
类方法,你可以重写该方法以重定向日志消息。
使用 spm_resolve.command
加载所有依赖项,并使用 spm_generate_xcodeproj.command
组装一个 Xcode 项目文件。此外,确保 Xcode 目标是 macOS。