Grain

Swift 中的数据序列化模板语言

使用 DSL 在 .swift 文件中用 Swift 描述数据,然后命令行应用程序将其渲染成任何格式,例如 JSON。

动机

在编写 JSON 或其他类似内容时,我们可能会因重复描述而感到疲惫。
我们经常认为可以像编程语言一样有效地完成这项工作。
jsonnet 是解决该问题的一种预处理器。
Grain 旨在在 Swift 语言中实现这样的功能。

创建一个 swift 文件,然后编写数据,并使用 Grain 工具渲染它。

使用 Swift 的优势在于

使用 Grain 的灵感

命名

序列化 -> cereal -> grain

安装

mint 🌱

$ mint install muukii/Grain

从 make

$ make install

未来将添加其他安装方式

概述

$ grain <template file>
$ grain <File 1> <File 2> ...
$ grain <File 1> <File 2> ... --output /path/to/output/

它使用相同的名称将结果写入给定的路径。
Schema.swift -> Schema.json

说明

一个基本案例

创建描述数据的 Data.swift

import GrainDescriptor

serialize {
  
  GrainObject {
    GrainMember("value") {
      1
    }
    
    for i in 0..<10 {
      GrainMember("key_\(i)") {
        i
      }
    }
  }
  
}

在终端中将 Data.swift 渲染为 JSON

$ grain Data.swift
{
  "key_0" : 0,
  "key_1" : 1,
  "key_2" : 2,
  "key_3" : 3,
  "key_4" : 4,
  "key_5" : 5,
  "key_6" : 6,
  "key_7" : 7,
  "key_8" : 8,
  "key_9" : 9,
  "value" : 1
}

serialize 函数是创建输出的方式

serialize 函数声明了什么渲染成数据。
它还具有隐式参数,用于自定义如何编码以及如何保存为文件。

serialize {
  // describe what you serialize
}

output 参数接受结果应该如何输出。

serialize(output: .stdout) { ... }

打印到 stdout

serialize(output: .file) { ... }

写入源文件所在目录或指定的输出目录(使用 --output 选项)中的文件

serialize(output: .file(.named("<file_name>")) { ... }

写入文件与上面类似,但使用给定名称的不同名称。

它允许我们多次定义。
这将使用给定名称创建文件。

serialize(output: .file(.named("pattern-1"))) {
  
}

serialize(output: .file(.named("pattern-2"))) {
  
}

创建组件并组合它们以有效地描述数据

在 Component.swift 中

import GrainDescriptor

serialize {
  
  GrainObject {
    GrainMember("data") {
      Results(records: [
        .init(name: "A", age: 1),
        .init(name: "B", age: 2),
      ])
    }
  }
  
}

// MARK: - Components

struct Record: GrainView {
  
  let name: String
  let age: Int
  
  var body: some GrainView {
    GrainObject {
      GrainMember("name") {
        name
      }
      GrainMember("age") {
        age
      }
    }
  }
  
}

struct Results: GrainView {
  
  let records: [Record]
  
  var body: some GrainView {
    GrainObject {
      GrainMember("results") {
        records
      }
    }
  }
  
}
$ grain Component.swift
{
  "data" : {
    "results" : [
      {
        "age" : 1,
        "name" : "A"
      },
      {
        "age" : 2,
        "name" : "B"
      }
    ]
  }
}

使用 async/await

模板文件允许编写 async/await 操作。
GrainDescriptor 内置了 Alamofire
例如,我们可以创建使用网络(如获取数据)的序列化数据。

import GrainDescriptor

let response = try await AF.request("https://httpbin.org/get").serializingString().value

serialize {
  
  GrainObject {  
    GrainMember("result") {
      response
    }
  }
  
}

示例

OpenAPI 规范
import GrainDescriptor

serialize {
  Endpoint(methods: [
    .init(
      method: .get,
      summary: "Hello",
      description: "Hello Get Method",
      operationID: "id",
      tags: ["Awesome API"]
    )
  ])
}

// MARK: - Components

public struct Endpoint: GrainView {
  
  public var methods: [Method]
  
  public var body: some GrainView {
    GrainObject {
      for method in methods {
        GrainMember(method.method.rawValue) {
          method
        }
      }
    }
  }
}

public struct Method: GrainView {
  
  public enum HTTPMethod: String {
    case get
    case post
    case put
    case delete
  }
  
  public var method: HTTPMethod
  public var summary: String
  public var description: String
  public var operationID: String
  public var tags: [String]
  
  public var body: some GrainView {
    GrainObject {
      GrainMember("operationId") { operationID }
      GrainMember("description") { description }
      GrainMember("summary") { summary }
      GrainMember("tags") { tags }
    }
  }
}
$ grain endpoints.swift
{
  "get" : {
    "description" : "Hello Get Method",
    "operationId" : "id",
    "summary" : "Hello",
    "tags" : [
      "Awesome API"
    ]
  }
}