Config

Coverage Status

这是一个命令行工具,用于从自定义 JSON 模式生成配置文件。它大致基于 ConfigGenerator,但具有显著的可扩展性。

为什么不直接使用 ConfigGenerator?

因为每个配置都需要一个完全独立的输入文件,它不允许跨配置共享值,而且很容易忘记在每个配置中添加值。

如何使用

只需将文件夹和方案名称传递给命令

generateconfig --configPath /path/to/my/config --scheme my-scheme-name

generateconfig 将查找所有带有 .config 文件扩展名的文件,搜索合适的模板,并为每个文件输出一个 .swift 文件。

模式

默认

模式示例:

{
  "template": {
    "imports": [ "MyCustomFramework" ]
  },
  "key": {
    "description": "An optional comment to document the property. Will be added as a comment to the generated code",
    "type": "String",
    "defaultValue": "value to be used by all schemes",
    "overrides": {
      "scheme pattern 1": "a different string to be used by schemes matching 'scheme pattern 1'",
      "scheme pattern 2": "a different string to be used by schemes matching 'scheme pattern 2'"   
    }
  },
  "group: {
    "key": {
      "type": "String",
      "defaultValue": "value to be used by all schemes",
      "overrides": {
        "scheme pattern 1": "a different string to be used by schemes matching 'scheme pattern 1'",
        "scheme pattern 2": "a different string to be used by schemes matching 'scheme pattern 2'"   
      }
  }
}

"key" 将用作 class 中的静态属性名称,因此应具有 Swift 编译器可以接受的格式。最常见的是 lowerCamelCase

type 可以具有以下值

overrides 包含与提供的 defaultValue 不同的值。 此字典中的键应该是一个正则表达式模式,用于匹配传入的方案。 这些值应该与 type 指定的 defaultValue 的类型相同。 如果两个被覆盖的值可以匹配,则使用找到的第一个合适的值。 overrides 是可选的,如果未提供,则所有方案都将使用 defaultValue

请注意,属性也可以按照第二个示例进行分组。 可以将任意数量的属性添加到命名组,这将在父配置类中创建一个带有附加属性的嵌套类。

关联属性

有时您可能希望将一个属性映射到另一个属性的输出,而不是传入的方案。 以下面的示例为例:

{
  "host": {
    "type": "String",
    "defaultValue": "example.com",
    "overrides": {
      "test": "test.example.com",
      "stage": "test.example.com",
      "live": "live.example.com"   
    }
  },
  "logoName": {
    "type": "String",
    "defaultValue" "logo.png",
    "associatedProperty": "host",
    "overrides": {
      "test.example.com": "logo-test.png"
    }
  }
}

logoName 属性有一个 associatedProperty,它将其 overrides 绑定到 host 的值,而不是传入的方案。 这样可以实现更简洁的覆盖列表,如上面的示例所示,"test" 和 "stage" 方案都将生成一个 "logo-test.png" logoName。

请注意,使用 associatedProperty 时有一些注意事项

引用属性

有时您可能希望使一个属性返回另一个属性的输出,具体取决于传入的方案。 例如:

{
  "red": {
    "type": "Colour",
    "defaultValue": "#FF0000"
  },
  "green": {
    "type": "Colour",
    "defaultValue": "#00FF00"  
  },
  "textColour": {
    "type": "Reference",
    "defaultValue": "red",
    "overrides": {
      "greenScheme": "green"
    }
  }
}

对于除 greenScheme 之外的所有方案,textColour 属性将为 return red,而对于 greenScheme,它将为 return green

枚举

此模式应使用于创建枚举。 模式示例:

{
  "template": {
    "name": "enum",
    "rawType": "String"
  },
  "key": {
    "defaultValue": "",
    "overrides": {
      "scheme pattern 1": "a dffierent string to be used by schemes matching 'scheme pattern 1'"
    }
  }
}

template.name 定义 config 应该使用哪个模板代码来解析此文件。 template.rawType 指定要使用的原始枚举类型。 目前仅支持 "String"。 这些属性遵循与默认属性相同的规则,但是不需要 type。 如果没有为 defaultValue 提供任何值,并且没有 overrides,则枚举键也将是原始值。

扩展

此模式应用于在现有类上创建扩展。 模式示例:

{
  "template": {
    "extensionOn": "UIColor",
    "extensionName": "Palette",
    "requiresNonObjC": true
  },
  "brand": {
    "type": "Colour",
    "defaultValue": "#FF0000",
    "overrides": {
      "blue": "#0000FF"
    }
  }
}

这将在名为 UIColor+Palette.swift 的文件中输出 UIColor 的扩展。

自定义类型

可以使用自己的自定义类型配置。 将 customTypes 数组添加到 template 部分,然后可以添加值,可以作为字符串(对于单个值),也可以作为键控字典。 例如:

{
  "template": {
    "customTypes": [
      {
        "typeName": "MyCustomType",
        "initialiser": "MyCustomType(thing: {$0})"
      },
      {
        "typeName": "MyMoreComplexCustomType",
        "initialiser": "MyMoreComplexCustomType(thing: {thing}, otherThing: {otherThing:String})"
      }
    ]
  },
  "myThing": {
    "type": "MyCustomType",
    "defaultValue": "Thingy"
  },
  "myOtherThing": {
    "type": "MyMoreComplexCustomType",
    "defaultValue": {
      "thing": "Thingy",
      "otherThing": "A different thingy"
    }
  }
}

初始化器模板中的占位符应写为 {key}{key:TypeHint},其中类型提示是基本原始类型之一,BoolStringURLIntDouble。 如果未提供任何类型提示,则该值将被视为表达式。

常用模式

如果您发现自己重复覆盖模式,例如 (PROD|STAGING),则可以将它们列在模板的 patterns 部分中,以便在配置中重复使用。 例如:

{
  "template": {
    "patterns": [
      {
        "alias": "prodAndStaging",
        "pattern": "(PROD|STAGING)"
      }
    ]
  },
  "myString": {
    "type": "String",
    "defaultValue": "Hello",
    "overrides": {
      "prodAndStaging": "Overridden value"
    }
  }
}

DynamicColour 和 DynamicColourReference

为了支持 iOS 13 的深色模式,可以将颜色输出为动态颜色。 例如:

  "background": {
    "type": "DynamicColour",
    "defaultValue": {
      "light": "#FF",
      "dark": "#00"
    }
  }

将输出

    @nonobjc static var background: UIColor {
        if #available(iOS 13, *) {
            return UIColor(dynamicProvider: {
                if $0.userInterfaceStyle == .dark {
                    return UIColor(white: 0.0 / 255.0, alpha: 1.0)
                } else {
                    return UIColor(white: 255.0 / 255.0, alpha: 1.0)
                }
            })
        } else {
            return UIColor(white: 255.0 / 255.0, alpha: 1.0)
        }
    }

同样,可以使用对另一种颜色的引用,因此

  "background": {
    "type": "DynamicColourReference",
    "defaultValue": {
      "light": "UIColor.white",
      "dark": "UIColor.black"
    }
  }

将输出

    @nonobjc static var background: UIColor {
        if #available(iOS 13, *) {
            return UIColor(dynamicProvider: {
                if $0.userInterfaceStyle == .dark {
                    return UIColor.black
                } else {
                    return UIColor.white
                }
            })
        } else {
            return UIColor.white
        }
    }

编写自己的模式

只需向项目中添加一个新的类或结构,并实现 Template。 将新的解析器添加到 main.swift 中的 templates 数组中。 您的模板应该检查任何配置中的 template 字典,并确定它是否可以解析它。 可以使用 name 项,或通过其他方式。 确保 ConfigurationFile 是该数组中的最后一项。 作为默认的模式解析器,它声称能够解析所有文件。

由于可以从头开始编写新的模板,因此您的 json 文件应该遵循没有预定义的模式,但是为了其他贡献者的可读性,如果它尽可能地类似于默认模式,那可能是明智的。

作为构建阶段运行

如果在 Xcode 中作为构建阶段运行,建议使用 Xcode 文件列表,无论是对于源输入还是生成的文件。 输入文件列表将确保 Xcode 使其文件缓存过期,并且您使用最新版本进行构建。 输出文件列表将确保 Xcode 在继续操作之前等待生成您的文件。