Murray 是一个命令行工具,用于将模板文件集成到项目中。
它可以帮助开发者基于他们自己的模板、文件夹结构和命名约定,快速地为任何类型的项目搭建新功能的骨架。
它使用 Swift 编写,但与任何基于文本的项目兼容。
模板语言是 Stencil,并带有 StencilSwiftKit 的扩展。
假设您需要为您的软件创建一个功能 - 让我们称之为 Feature
。我们可以将其想象为应用程序的一个屏幕,并且您以后需要创建具有相同结构但基本名称不同的其他屏幕(Feature
可能会变为 Product
或 User
或任何其他名称)。
还假设您遵循 MVC(模型-视图-控制器)模式并使用 Swift 作为编程语言。
几乎可以肯定,您最终会创建 3 个不同的文件
Feature.swift
文件FeatureController.swift
文件FeatureView.swift
文件。如果您还遵循 TDD(或只是对您的软件进行单元测试),您可能还需要一个 FeatureTests.swift
文件。
最重要的是,您可能还需要在现有代码的特定部分通过编写 FeatureController()
来实例化至少一个控制器。
Murray 背后的理念是在终端中运行类似以下的命令
murray run screen Feature
找到您在正确位置(例如:Scenes/Feature
文件夹)需要的所有文件,这些文件已经预先填充了一些样板代码(例如:模型文件的 struct Feature: Codable {}
),并且所有链接都已就绪,以便您可以快速开始开发您的... 功能。
添加
spm:synesthesia-it/murray = "latest"
到您的 .mise-toml
示例
[tools]
tuist = "latest"
swiftlint = 'latest'
swiftformat = 'latest'
sourcery = 'latest'
xcodes = 'latest'
xcbeautify = 'latest'
"spm:synesthesia-it/murray" = "latest"
并运行 mise install
mint install synesthesia-it/Murray
curl -fsSL https://raw.githubusercontent.com/synesthesia-it/Murray/master/install.sh | sh
如果您想尝试 Murray 并直接从代码编译它,可以使用 make
。这对于向项目贡献代码尤其有用。
确保您已在您的机器上安装了 Homebrew。
make build
将构建 Murray 的发布版本,并将可执行文件复制到 /usr/local/bin
文件夹中
make setup
将正确设置环境并生成 XCode 项目
make lint
将通过遵循 Swiftlint 标准来确保代码编写正确
所有概念都在下面详细解释。 要快速了解 Murray,看看它是否符合您的需求,这里有一个基本工作设置的快速入门(示例适用于 Swift 项目)
murray scaffold murrayfile
- 这将创建所需的 murrayfile(空)。murray scaffold package Project
- 这将创建一个名为 Project
的骨架包,并将其链接到您的 Murrayfile。murray scaffold bone Project Model Model.swift.stencil
- 这将在 Murray/Project/Model
中创建一个骨架,其中包含一个空的模板文件(我们假设它用于“模型”,但它可以是任何东西,并且您可以拥有多个)。Murray/Project/Model/Model.yml
:您会看到模板文件的 to
参数为空,因为它将取决于您的项目结构。假设您将所有内容都放在“Sources”文件夹中,请将 to
值指向 "Sources/Models/{{name|firstUppercase}}.swift"
(记住周围的引号 - YAML 有时很棘手:))Murray/Project/Model/Model.swift.stencil
中编辑您的模板。 由于它是一个模型,可能 struct {{name|firstUppercase}}: Codable {}
是您正在寻找的,但它可以比这更有用和复杂(记住,这只是一个快速入门!)。Product
模型! 在您项目的根文件夹中运行 murray run MyTest product
- 您将在 Sources/Models/Product.swift
中找到您的新模型。 是不是很酷?Swift(iOS/macOS/tvOS/watchOS)项目还需要将文件添加到 xcodeproj 才能使用。 有关 Xcode 插件的信息,请参阅插件部分。
Murray 基于 Skeleton(骨架)的概念 - 对于给定预定义架构的任何类型项目的起点(例如:具有 Bootstrap 的 HTML5 项目、具有 MVVM/MVC/TCA/任何东西的 iOS 项目、React Native 起始项目模板),它将通过小部分的样板代码来扩展,这些代码需要在特定的子文件夹中以一致的方式创建 - 我们称它们为 Bones(骨骼)。 我们的想法是将预编译的文件准备到位,并根据您的需要进行编辑。
Murray 不会在创建文件后解释您的文件内容,它是完全与语言无关的。
您将一个或多个骨骼组合成一个 Procedure(过程),这是一个带有名称的命令,用于将骨骼“安装”到您的项目中。 您向该过程提供一个 Context(上下文),其中包含一组变量,这些变量将用于将您的模板文件转换为最终文件。
模板是一个文本文件,其中包含最终代码结构和将被解析的变量的混合。 模板是围绕一种模板语言构建的,而 Murray 使用 Stencil
一个基本的例子(对于 SwiftUI 项目)可以是
import SwiftUI
struct {{name|firstUppercase}}View: View {
@ObservedObject var viewModel: {{name|firstUppercase}}ViewModel
var body: some View {
Text("{{name}} is cool!")
}
}
当提供一个包含名为 name
的变量的上下文时,包含在 {{}}
符号中的所有占位符都将被该变量名称替换。 如果 name
值为 murray
,则结果将是
import SwiftUI
struct MurrayView: View {
@ObservedObject var viewModel: MurrayViewModel
var body: some View {
Text("murray is cool!")
}
}
Stencil 提供了“基本”的模板体验; 为了增强功能并创建更复杂的模板,Murray 还实现了 StencilSwiftKit 扩展。 查看两个文档以了解模板用法。
上下文是一个键值对映射(一个字典),其中包含将在模板中替换的变量。
它可以通过命令行提供(本地上下文 - 它会根据您的需要在每次调用时更改),并在主 Murrayfile
中提供一些全局默认值。
上下文还添加了一些动态值,如当前时间/日期/年份、当前的 git 作者、文件路径等,因此每个创建的模板都可以呈现如下内容
// {{_filename._to}} - {{_author}} © {{_year}}
呈现为
// Murray.swift - Stefano Mondino - © 2023
一个包是一组过程和骨骼,可以很容易地在不同的团队和/或人员之间重新分发。 最常见的用例场景涉及包含当前骨架的所有过程的单个包,但在某些情况下,包可以混合和/或扩展。
包包含在一个文件夹中,因此很容易移动(zip 或 git 子文件夹)
Murray 支持 YAML 或 JSON 作为配置文件。
主要的入口点是项目根目录中的 Murrayfile,其中包含环境哈希映射和包含的包列表(作为相对于项目根目录的路径)。
每个包配置文件都包含支持的 过程 列表,每个过程都链接到一个或多个 骨骼 列表
每个骨骼都有一个配置文件,其中列出了运行包含当前骨骼的过程时将创建的新文件,以及一些其他选项。 有关更多详细信息,请参见下文
每个命令都有一个 --help
选项,解释了所选命令的正确用法。
要获取有关正在发生的事情的解释性日志,请将 --verbose
与任何命令一起使用。
murray list
返回所有可用过程的完整列表,方法是连接 Murrayfile 中包含的包。
带有两个包的示例
foo@bar:~$ murray list
Package.procedureA
Package.procedureB
Package.procedureC
AnotherPackage.procedureA
您可以在以下
run
执行中使用过程的名称; 如果两个不同的包具有相同的名称,请将包名称添加到过程名称中
murray run <procedureName> <name> <parameters>
运行选定的过程。 如果上下文需要多于 name
参数,请使用 parameter:value
语法添加键值对参数列表
示例(假设 procedureA
需要上下文中的 name
和 module
参数)
foo@bar:~$ murray run procedureA test module:MyModule
选项
--verbose
:启用完整日志记录--preview
:跳过任何文件创建并提供将由当前执行创建/修改的文件列表murray clone <name> <git_path> <subfolder> <parameters>
克隆一个包含 Skeletonfile 的远程骨架,以便从中创建一个名为 <name>
的新项目。
如果 Skeletonfile 包含在远程子文件夹中,您可以使用 <subfolder>
参数指定它。
对于 git_path
,您可以使用 🌱 Mint 使用的相同语法来指定 git 分支或标签。 例如 github.com/synesthesia-it/murray@wip/3.0
表示 wip/3.0
分支。
如果您需要从本地文件夹加载骨架,可以使用本地路径而不是 git url,只要您提供 --copyFromLocalFolder
参数。
murray scaffold <type>
为 Murray 组件创建基本配置结构。
每个创建新配置文件的命令都接受一个可选的 --format
参数,接受 yml
或 json
作为内容数据结构。 Murray 默认格式是 YAML。
foo@bar:~$ murray scaffold skeleton --format yml
在当前文件夹中创建一个新的 Skeletonfile。
foo@bar:~$ murray scaffold murrayfile --format yml
在当前文件夹中创建一个空的 Murrayfile。
foo@bar:~$ murray scaffold Package <packageName> --format yml --folder ".Murray"
在指定文件夹中创建一个名为 packageName
的新包。 如果未指定,则文件夹是一个名为 Murray
的新目录(相对于 Murrayfile)。
foo@bar:~$ murray scaffold bone <packageName> <name> <files> --format yml --skipProcedure
在名为 packageName
的包中创建一个名为 name
的新 Bone(骨骼)。 您可以提供一个文件名列表(用空格分隔),这些文件将在骨骼的文件夹中创建为空文件。
骨骼配置文件的文件格式将与包含它的包相同。
示例(假设 MyPackage
是包名称)
foo@bar:~$ murray scaffold bone MyPackage CustomBone FileA.swift.stencil FileB.swift.stencil`
将在 MyPackage 中创建一个新的骨骼,包含配置文件和两个空的模板文件。
--skipProcedure
参数跳过在包配置中创建包含新项目的新过程。 默认为 false
(这意味着 - 如果设置为 true
- 您需要通过编辑配置文件手动将新过程添加到您的包)。
murray scaffold procedure <packageName> <name> <boneNames>
将一个新过程追加到名为 packageName
的包中,该过程包含名为 boneNames
的骨骼(用空格分隔)。 该过程的名称将被称为 name
。
示例(假设 MyPackage
是包名称,并且它已经包含名为 CustomBone
和 AnotherBone
的骨骼)
murray scaffold procedure MyPackage MyProcedure CustomBone AnotherBone
将创建一个名为 MyProcedure
的过程,其中包含 CustomBone
和 AnotherBone
。 它们将按此确切顺序执行。
然后,您可以调用 murray run MyProcedure Test
以在您的项目中使用它。
开发人员可能需要在 murray run
执行中的某些关键时刻之前或之后执行一些任务。 这些时刻通常是
之前 和 之后 称为 phases (阶段),每个插件可能会或可能不支持这两种阶段。
MurrayKit 公开了 Plugin
的上下文,以确保允许每个 (Swift) 开发人员轻松地将自定义行为集成到执行中。 每个 Plugin
可能有自己的参数,这些参数将在正确的时间使用。
要为项目/过程/全局执行声明插件,您需要添加一个名为 plugins
的字典节点,其中包含您需要使用的插件的名称及其自身的参数。(请参阅下面的示例)
目前,每个插件都必须与 Murray 源代码一起编译。 动态插件正在开发中。
Murray 附带 2 个插件:Xcode
和 Shell
xcode 插件允许用户自动将新文件添加到 Xcode 项目中的特定目标。
它仅支持 after (之后) 阶段(在创建特定文件之前将其添加到 Xcode 项目是没有意义的)。
接受的参数是
targets
:要将当前文件添加到的目标的数组。projectPath
:(可选)包含 xcode 项目的路径(相对于 Murrayfile 文件夹)。 如果未指定,它将使用在 Murrayfile 文件夹中找到的第一个 xcodeproj
。 这对于具有多个 xcodeproj 的模块化项目很有用。有关演示,请参见 Examples 文件夹。
shell 插件在当前项目执行之前或之后执行任何 shell 命令。 它可以在任何级别使用(替换,文件,过程或全局)。 接受的参数是
before
:在当前元素执行之前运行的命令数组after
:在当前元素执行之后运行的命令数组一个常见的用例是在每次执行后运行 linter(例如:swiftlint --fix
或 swiftformat
),以确保由 murray 执行创建或编辑的文件仍然按照您的逻辑格式化良好
使用 XcodeGen 或 Tuist 的项目可以运行 xcodegen
或 tuist generate
来重新创建 xcode 项目。 对于已经支持项目生成的项目来说,这通常比 Xcode 插件更好。
Murrayfile 中的示例
...
plugins:
shell:
after:
- "swiftformat"
- "swiftlint --fix"
- "tuist generate"
Murrayfile 包含当前项目和环境支持的包的列表。 它还支持在任何过程之前或之后执行的插件。
该环境包含一个基本上下文,该上下文在执行期间在每个 Stencil 模板中都可用(无论是在代码文件还是配置文件中)。
您还可以递归地引用其他环境变量。
避免在环境中创建“变量循环”。
Parameters (参数)
packages
:指向包配置文件的路径列表(相对于 Murrayfile)plugins
:插件数据environment
:包含所有执行可用的上下文的字典。mainPlaceholder
:(可选)每个模板中使用的主要占位符的名称。 默认为 name
。Example (YAML) (示例 (YAML))
packages:
- "Murray/MyPackage/MyPackage.yml
- "Murray/AnotherPackage/AnotherPackage.yml
plugins:
shell:
after:
- "make"
environment:
company: Synesthesia
paths:
sources: "Sources"
module: "{{paths.sources}}/{{module}}"
tests: "Tests"
包含包及其过程的定义。
Parameters (参数)
name
:包的名称description
:对包的可读描述,简要说明其功能。procedures
:过程数组,每个过程包含name
:过程的名称,用于 murray run
命令description
:(可选)对过程的可读描述,简要说明其功能。 它将显示在 murray list
中。plugins
:插件数据items
:指向骨骼配置文件的路径,相对于当前 Package 配置文件。 您可以根据需要组合任意多个(它们将按照提供的顺序执行)。Example (YAML) (示例 (YAML))
name: MyPackage
description: Some meaningful description
procedures:
- name: ProcedureA
description: I'm sure this will be meaningful to you
items:
- BoneA/bone.yml
- name: ProcedureB
description: I'm sure this will be meaningful to you
items:
- BoneB/bone.yml
- name: ProcedureC
description: I'm sure this will be meaningful to you
items:
- BoneA/bone.yml
- BoneB/bone.yml
包含单个 Bone(骨骼)的定义。 一个骨骼是新文件和现有文件中替换的组合。 虽然可能没有意义,但骨骼可以完全为空(或仅包含插件数据)。
Parameters (参数)
name
:骨骼的名称。 它用于搭建新过程。description
:对骨骼的可读描述,简要说明其功能。parameters
:当前骨骼接受的参数列表。 每一个都由以下定义name
:参数的名称。description
:关于此参数应该做什么的简短说明。 可选。values
:当前参数的允许值列表。 可选,如果未提供,将接受任何参数。isRequired
:一个布尔值,它要求在包含此骨骼的每个执行中都存在此参数,否则在缺少时会引发错误plugins
:插件数据paths
:将由此骨骼创建的每个新文件的路径定义数组。 每一个都由以下定义from
:相对于当前骨骼配置文件的文件或文件夹的路径。 当指向文件夹时,它将根据提供的上下文解析所有包含的文件。to
:模板(或文件夹)将被解析到的目标路径(相对于 Murrayfile)。 它支持上下文解析(意味着它可能包含 stencil 模板)。plugins
:此路径文件/文件夹的插件数据replacements
:将按顺序执行的替换数组。 每一个都由以下定义destination
:包含将进行替换的文件的目标文件路径(相对于 Murrayfile)placeholder
:将在 destination
中搜索并由 text
或 source
内容替换的文本行。 该占位符始终在替换后添加回去。 提示:您可以使用文件中的注释作为占位符并查找它们。text
:(可选)将放置在 destination
中 placeholder
之前的少量文本。 对于大量的替换文本,请使用 source
source
:(可选)指向包含可解析文本的文件(相对于骨骼)的路径,该文本将放置在 destination
文件中 placeholder
之前。 对于小的替换,请改用 text
属性。 请注意,至少需要 text
和 source
中的一个。 当同时指定两者时,始终使用 source
。Example (YAML) (示例 (YAML))
name: sceneViewModel
parameters: []
paths:
- from: ViewModel.swift.stencil
to: "{{paths.scenes}}/{{name|firstUppercase}}/{{name|firstUppercase}}ViewModel.swift"
plugins:
xcode:
targets: ["{{mainTarget}}"]
- from: ViewModelTests.swift.stencil
to: "{{paths.tests}}/{{name|firstUppercase}}/{{name|firstUppercase}}ViewModel.swift"
plugins:
xcode:
targets: ["{{mainTestTarget}}"]
description: A scene view model with tests
replacements:
- destination: "MurrayDemo/MainTabViewModel.swift"
placeholder: "// murray: viewModel"
text: "let {{name|firstLowercase}}ViewModel = {{name|firstUppercase}}ViewModel()\n"
Skeletonfile 包含将 Skeleton(骨架)启动为新项目的所有信息。
通常,与骨骼相反,骨架是一个完全可以正常工作的项目,需要事先开发; 因此,它不太可能包含模板和骨骼(但它可以包含预配置的包和一个 Murrayfile,当然)。
Skeletonfile 的目的是列出本地脚本和(如果需要)不与项目一起编译的模板文件夹列表,以便可以根据提供的上下文解析它们,然后将其删除。
运行
murray clone
后,Skeletonfile 本身会从新项目中删除。 通常没有必要让它出现在您的项目中。
Skeletonfile 参数是
paths
:文件夹和/或文件的路径数组,其语法与骨骼中使用的语法相同。 每一个都由以下定义from
:相对于 Skeletonfile 的文件或文件夹的路径。 当指向文件夹时,它将根据提供的上下文解析所有包含的文件。to
:相对于 Skeletonfile 的目标路径,该文件夹/文件将被解析到该路径中。 它支持上下文解析(意味着它可能包含 stencil 模板)。scripts
:将在克隆和路径执行后执行的 bash 脚本数组initializeGit
:在执行后是否自动初始化一个空的 git(使用 git init
)。 默认为 false
。目前不支持 Skeletonfile 的插件,这意味着 iOS 和其他 Apple Swift 项目的 xcodeproj 文件需要在创建后手动重命名。 请改用 Xcodegen 或 Tuist,并让 skeletonfile 使用脚本更改您的 project.yml / Project.swift 文件。
上下文是运行时值(CLI 中的参数)、环境变量(Murrayfile 中“硬编码”的值)和动态值(取决于当前时间或当前机器的标准值)的混合。
Murrayfile 环境变量字典中的值可以通过点表示法(例如:some.nested.key
)在每个模板中使用,并且可以针对当前上下文进行解析,递归地将它们与动态值、环境变量或运行时值混合。
这是一个混合示例,它创建一个 {{fileHeader}}
变量,该变量应打印类似以下的内容:
// Filename.swift
// Stefano Mondino - Synesthesia ©2023
当为名为 Filename.swift
的文件创建骨架时
environment:
company: Synesthesia
gitName: "{{_author}}"
currentYear: "{{_year}}"
fileHeader: |
// {{_filename._to}}
// {{gitName}} - {{company}} ©{{currentYear}}
这些是在上下文中可用的值,并且随着时间和/或机器的生成而动态变化。当不可用时,它们返回一个空字符串。
_year
:当前年份_date
:dd/MM/yyyy
格式的日期_dateTime
:dd/MM/yyyy HH:mm:ss
格式的日期和时间_timestamp
:自 1970 年 1 月 1 日以来的秒数时间戳_author
:git 配置中设置的文件作者姓名。如果 git 未正确配置,则返回空字符串。_filename
:当前创建的文件名。使用 _from
表示源模板,使用 _to
表示目标文件名(例如:_filename._to
)_path
:当前创建的文件的完整路径。使用 _from
表示源模板,使用 _to
表示目标路径(例如:_path_._to
)Murray 很大程度上受到了 Ruby on Rails 和其他“基于 Web”的软件中可用的 scaffold 功能的启发。早在 2018 年我们开始开发 Murray 时,似乎没有任何类似的工具可用于移动开发。
Sourcery 是一种类似的软件,它基于配置文件和 Stencil 模板,但仅适用于 Swift 项目,因为它解释 Swift 代码并自动生成基于它们的文件(例如协议的自动实现)。生成的文件不应该被更改,因为每次执行 Sourcery 都会重新生成它们,丢弃用户的每次更改。
SwiftGen(在某种程度上)与 Sourcery 相关,但适用于 Swift 资源(assets、translations 等)。 同样,它是为 Swift 项目量身定制的,不具备跨平台专用功能。
Genesis 看起来非常相似,但似乎不支持在其他文件中进行替换(仅支持新模板)
Tuist 具有 scaffolding 功能,但需要使用 Tuist 本身配置项目,并且同样仅适用于 Swift 项目。它也不支持在文件中进行替换。 但是,我们建议尽可能使用 Tuist,并彻底忘记 xcodeproj 文件。
MurrayKit 是一个 Package,可以在任何 Swift 项目中使用,以使用 Murray 功能。
文档目前正在开发中
Murray 由 Synesthesia 团队开发。 欢迎通过打开 Github issues 和/或 PR 来做出贡献。