Murray

Swift

Murray 是一个命令行工具,用于将模板文件集成到项目中。

它可以帮助开发者基于他们自己的模板、文件夹结构和命名约定,快速地为任何类型的项目搭建新功能的骨架。

它使用 Swift 编写,但与任何基于文本的项目兼容。

模板语言是 Stencil,并带有 StencilSwiftKit 的扩展。

一个真实的例子

假设您需要为您的软件创建一个功能 - 让我们称之为 Feature。我们可以将其想象为应用程序的一个屏幕,并且您以后需要创建具有相同结构但基本名称不同的其他屏幕(Feature 可能会变为 ProductUser 或任何其他名称)。

还假设您遵循 MVC(模型-视图-控制器)模式并使用 Swift 作为编程语言。

几乎可以肯定,您最终会创建 3 个不同的文件

如果您还遵循 TDD(或只是对您的软件进行单元测试),您可能还需要一个 FeatureTests.swift 文件。

最重要的是,您可能还需要在现有代码的特定部分通过编写 FeatureController() 来实例化至少一个控制器。

Murray 背后的理念是在终端中运行类似以下的命令

murray run screen Feature 

找到您在正确位置(例如:Scenes/Feature 文件夹)需要的所有文件,这些文件已经预先填充了一些样板代码(例如:模型文件的 struct Feature: Codable {}),并且所有链接都已就绪,以便您可以快速开始开发您的... 功能。

安装 (macOS)

使用 Mise

添加

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

mint install synesthesia-it/Murray

从源代码编译(来自 main 分支的最新版本)

curl -fsSL https://raw.githubusercontent.com/synesthesia-it/Murray/master/install.sh | sh

Make

如果您想尝试 Murray 并直接从代码编译它,可以使用 make。这对于向项目贡献代码尤其有用。

确保您已在您的机器上安装了 Homebrew。

make build 将构建 Murray 的发布版本,并将可执行文件复制到 /usr/local/bin 文件夹中

make setup 将正确设置环境并生成 XCode 项目

make lint 将通过遵循 Swiftlint 标准来确保代码编写正确

快速入门

所有概念都在下面详细解释。 要快速了解 Murray,看看它是否符合您的需求,这里有一个基本工作设置的快速入门(示例适用于 Swift 项目)

  1. 在您项目的根文件夹中,运行 murray scaffold murrayfile - 这将创建所需的 murrayfile(空)。
  2. 在同一文件夹中,运行 murray scaffold package Project - 这将创建一个名为 Project 的骨架包,并将其链接到您的 Murrayfile。
  3. 运行 murray scaffold bone Project Model Model.swift.stencil - 这将在 Murray/Project/Model 中创建一个骨架,其中包含一个空的模板文件(我们假设它用于“模型”,但它可以是任何东西,并且您可以拥有多个)。
  4. 编辑 Murray/Project/Model/Model.yml:您会看到模板文件的 to 参数为空,因为它将取决于您的项目结构。假设您将所有内容都放在“Sources”文件夹中,请将 to 值指向 "Sources/Models/{{name|firstUppercase}}.swift" (记住周围的引号 - YAML 有时很棘手:))
  5. Murray/Project/Model/Model.swift.stencil 中编辑您的模板。 由于它是一个模型,可能 struct {{name|firstUppercase}}: Codable {} 是您正在寻找的,但它可以比这更有用和复杂(记住,这只是一个快速入门!)。
  6. 让我们在您的项目中创建一个 Product 模型! 在您项目的根文件夹中运行 murray run MyTest product - 您将在 Sources/Models/Product.swift 中找到您的新模型。 是不是很酷?
  7. 将更多的骨架搭建到您的项目中,将它们混合在一起,并为您的架构创建完美的骨架系统 - 您的生产力将得到提高,并且您的项目将更加清晰!

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,其中包含环境哈希映射和包含的包列表(作为相对于项目根目录的路径)。

每个包配置文件都包含支持的 过程 列表,每个过程都链接到一个或多个 骨骼 列表

每个骨骼都有一个配置文件,其中列出了运行包含当前骨骼的过程时将创建的新文件,以及一些其他选项。 有关更多详细信息,请参见下文

CLI 命令

每个命令都有一个 --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 需要上下文中的 namemodule 参数)

foo@bar:~$ murray run procedureA test module:MyModule

选项

克隆

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 参数,接受 ymljson 作为内容数据结构。 Murray 默认格式是 YAML。

骨架

foo@bar:~$ murray scaffold skeleton --format yml

在当前文件夹中创建一个新的 Skeletonfile。

Murrayfile

foo@bar:~$ murray scaffold murrayfile --format yml

在当前文件夹中创建一个空的 Murrayfile。

foo@bar:~$ murray scaffold Package <packageName> --format yml --folder ".Murray"

在指定文件夹中创建一个名为 packageName 的新包。 如果未指定,则文件夹是一个名为 Murray 的新目录(相对于 Murrayfile)。

Bone (骨骼)

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 - 您需要通过编辑配置文件手动将新过程添加到您的包)。

Procedure (过程)

murray scaffold procedure <packageName> <name> <boneNames>

将一个新过程追加到名为 packageName 的包中,该过程包含名为 boneNames 的骨骼(用空格分隔)。 该过程的名称将被称为 name

示例(假设 MyPackage 是包名称,并且它已经包含名为 CustomBoneAnotherBone 的骨骼)

murray scaffold procedure MyPackage MyProcedure CustomBone AnotherBone

将创建一个名为 MyProcedure 的过程,其中包含 CustomBoneAnotherBone。 它们将按此确切顺序执行。

然后,您可以调用 murray run MyProcedure Test 以在您的项目中使用它。

Plugins (插件)

开发人员可能需要在 murray run 执行中的某些关键时刻之前或之后执行一些任务。 这些时刻通常是

之前之后 称为 phases (阶段),每个插件可能会或可能不支持这两种阶段。

MurrayKit 公开了 Plugin 的上下文,以确保允许每个 (Swift) 开发人员轻松地将自定义行为集成到执行中。 每个 Plugin 可能有自己的参数,这些参数将在正确的时间使用。

要为项目/过程/全局执行声明插件,您需要添加一个名为 plugins 的字典节点,其中包含您需要使用的插件的名称及其自身的参数。(请参阅下面的示例)

目前,每个插件都必须与 Murray 源代码一起编译。 动态插件正在开发中。

Murray 附带 2 个插件:XcodeShell

Xcode Plugin (Xcode 插件)

xcode 插件允许用户自动将新文件添加到 Xcode 项目中的特定目标。

它仅支持 after (之后) 阶段(在创建特定文件之前将其添加到 Xcode 项目是没有意义的)。

接受的参数是

有关演示,请参见 Examples 文件夹。

Shell Plugin (Shell 插件)

shell 插件在当前项目执行之前或之后执行任何 shell 命令。 它可以在任何级别使用(替换,文件,过程或全局)。 接受的参数是

一个常见的用例是在每次执行后运行 linter(例如:swiftlint --fixswiftformat),以确保由 murray 执行创建或编辑的文件仍然按照您的逻辑格式化良好

使用 XcodeGen 或 Tuist 的项目可以运行 xcodegentuist generate 来重新创建 xcode 项目。 对于已经支持项目生成的项目来说,这通常比 Xcode 插件更好。

Murrayfile 中的示例

...
plugins:
  shell:
    after: 
    - "swiftformat"
    - "swiftlint --fix"
    - "tuist generate"

配置文件

Murrayfile

Murrayfile 包含当前项目和环境支持的包的列表。 它还支持在任何过程之前或之后执行的插件。

该环境包含一个基本上下文,该上下文在执行期间在每个 Stencil 模板中都可用(无论是在代码文件还是配置文件中)。

您还可以递归地引用其他环境变量。

避免在环境中创建“变量循环”。

Parameters (参数)

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"

PackageFile (包文件)

包含包及其过程的定义。

Parameters (参数)

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

BoneFile (骨骼文件)

包含单个 Bone(骨骼)的定义。 一个骨骼是新文件和现有文件中替换的组合。 虽然可能没有意义,但骨骼可以完全为空(或仅包含插件数据)。

Parameters (参数)

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 (骨架文件)

Skeletonfile 包含将 Skeleton(骨架)启动为新项目的所有信息。

通常,与骨骼相反,骨架是一个完全可以正常工作的项目,需要事先开发; 因此,它不太可能包含模板和骨骼(但它可以包含预配置的包和一个 Murrayfile,当然)。

Skeletonfile 的目的是列出本地脚本和(如果需要)不与项目一起编译的模板文件夹列表,以便可以根据提供的上下文解析它们,然后将其删除。

运行 murray clone 后,Skeletonfile 本身会从新项目中删除。 通常没有必要让它出现在您的项目中。

Skeletonfile 参数是

目前不支持 Skeletonfile 的插件,这意味着 iOS 和其他 Apple Swift 项目的 xcodeproj 文件需要在创建后手动重命名。 请改用 Xcodegen 或 Tuist,并让 skeletonfile 使用脚本更改您的 project.yml / Project.swift 文件。

Context values (上下文值)

上下文是运行时值(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}}

动态值

这些是在上下文中可用的值,并且随着时间和/或机器的生成而动态变化。当不可用时,它们返回一个空字符串。

替代方案和类似软件

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

MurrayKit 是一个 Package,可以在任何 Swift 项目中使用,以使用 Murray 功能。

文档目前正在开发中

鸣谢与贡献

Murray 由 Synesthesia 团队开发。 欢迎通过打开 Github issues 和/或 PR 来做出贡献。