This is a command line tool for generating configuration files from a custom JSON schema. Loosely based on ConfigGenerator, but significantly more extensible.
Why not just use ConfigGenerator?
Because each configuration requires a completely separate input file, it doesn’t allow for sharing values across configurations, and it also makes it too easy to forget to add values across every configuration.
How to use
Simply pass a folder and a scheme name to the command:
generateconfig will find all files with a .config file extension, search for a suitable template, and output a .swift file for each file.
Schemas
Default
A sample of the schema is:
{
"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'"
}
}
}
The “key” will be used as a static property name in a class so should have a format that is acceptable to the swift compiler. Most likely lowerCamelCase.
type can have the following values:
String: A swift string value
URL: A url. Will be converted to URL(string: "the value")!
Int: An integer value.
Double: A double value
Bool: A boolean value
Colour: A colour in hex format, will be output as a UIColor.
DynamicColour: A pair of colours, in hex format, that will be output as an iOS 13-compatible dynamic colour.
DynamicColourReference: A pair of colours, as properties on the current configuration or somewhere else, that will be output as an iOS 13-compatible dynamic colour.
Image: The name of an image. Will be converted to UIImage(named: "the value")!.
Regex: A regular expression pattern. Will be converted to try! NSRegularExpression(patthen: "the value", options: [])
EncryptionKey: A key to use to encrypt sensitive info.
Encrypted: A value that should be encrypted using the provided key
Dictionary: A dictionary. Keys should be strings, values in the dictionary should be either string, numeric, or a new dictionary.
Enum types. Set the type to the name of the enum, set the value to be the case, preceded by a ., so .thing. If you need enums from a custom module, add a string array of imports to the template section.
overrides contains values that are different to the provided defaultValue. The keys in this dictionary should be a regex pattern to match the scheme passed in. The values should be the same type as the defaultValue as specified by type. If two overridden values could match, the first suitable value found is used. overrides is optional, if not provided, all schemes will use the defaultValue.
Note properties can also be grouped together as per the second example. Any number of properties can be added to a named group, which will create a nested class within the parent config class with the properties attached.
Associated Properties
Sometimes you may want to map a property to the output of another property, rather than a passed in scheme. Take the example below:
The logoName property has an associatedProperty, which ties it’s overrides to the value of host instead of the passed in scheme. This allows for more concise override lists, as in the example above both the “test” and “stage” scheme will produce a “logo-test.png” logoName.
Note that there are a couple of caveats when using associatedProperty:
The keys in overrides do not use regular expression pattern matching, and instead require an exact string match.
The associatedPropertymust have a String type.
Reference Properties
Sometimes you may want to make a property return the output of another property, depending on the passed in scheme. For example:
The textColour property will be return red for all schemes bar the greenScheme where it will be return green.
Enum
This schema should be used for creating enums.
A sample of the schema is:
{
"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 defines which template code config should use to parse this file. template.rawType specifies the raw enum type to use. Currently only “String” is supported. The properties follow the same rules as the default, however type is not required. If no value is provided for defaultValue and no overrides are present, the enum key will also be the raw value.
Extensions
This schema should be used for creating extensions on existing classes.
A Sample of the schema is:
This will output an extension on UIColor in a file called UIColor+Palette.swift.
Custom types
It is possible to use your own custom types with config. Add a customTypes array to your template section and you can then add your values, either as a string, for single values, or as a keyed dictionary. For example:
Placeholders in the initialiser template should be written as {key} or {key:TypeHint} where the type hint is one of the basic primitive types, Bool, String, URL, Int, Double. If no type hint is supplied then the value is treated as an expression.
Common patterns
If you find yourself repeating override patterns, for example (PROD|STAGING) you can list those in the patterns section of the template for reuse in your configuration. For example:
Just add a new class or struct to the project and implement Template. Add your new parser to the templates array in main.swift. Your template should inspect a template dictionary in any config and decide whether it can parse it. Either using a name item, or through other means. Ensure ConfigurationFile is the last item in that array. As the default schema parser it claims to be able to parse all files.
As new templates can be written from scratch, there is no pre-defined schema that your json file should adhere to, but for the sake of readability for other contributors, it would probably be sensible if it resembled the default schema as closely as possible.
Running as a build phase
If running as a build phase within Xcode, it’s recommended to use xcode file lists, both for the source input and the generated files. The input file list will ensure Xcode expires its cache of files and you build with the latest version. The output file list will ensure Xcode waits for your files to be generated before proceeding.
Config
This is a command line tool for generating configuration files from a custom JSON schema. Loosely based on ConfigGenerator, but significantly more extensible.
Why not just use ConfigGenerator?
Because each configuration requires a completely separate input file, it doesn’t allow for sharing values across configurations, and it also makes it too easy to forget to add values across every configuration.
How to use
Simply pass a folder and a scheme name to the command:
generateconfig
will find all files with a.config
file extension, search for a suitable template, and output a .swift file for each file.Schemas
Default
A sample of the schema is:
The “key” will be used as a static property name in a
class
so should have a format that is acceptable to the swift compiler. Most likelylowerCamelCase
.type
can have the following values:String
: A swift string valueURL
: A url. Will be converted toURL(string: "the value")!
Int
: An integer value.Double
: A double valueBool
: A boolean valueColour
: A colour in hex format, will be output as aUIColor
.DynamicColour
: A pair of colours, in hex format, that will be output as an iOS 13-compatible dynamic colour.DynamicColourReference
: A pair of colours, as properties on the current configuration or somewhere else, that will be output as an iOS 13-compatible dynamic colour.Image
: The name of an image. Will be converted toUIImage(named: "the value")!
.Regex
: A regular expression pattern. Will be converted totry! NSRegularExpression(patthen: "the value", options: [])
EncryptionKey
: A key to use to encrypt sensitive info.Encrypted
: A value that should be encrypted using the provided keyDictionary
: A dictionary. Keys should be strings, values in the dictionary should be either string, numeric, or a new dictionary.Reference
: See Reference Properties below.type
to the name of the enum, set the value to be the case, preceded by a.
, so.thing
. If you need enums from a custom module, add a string array of imports to the template section.overrides
contains values that are different to the provideddefaultValue
. The keys in this dictionary should be a regex pattern to match the scheme passed in. The values should be the same type as thedefaultValue
as specified bytype
. If two overridden values could match, the first suitable value found is used.overrides
is optional, if not provided, all schemes will use thedefaultValue
.Note properties can also be grouped together as per the second example. Any number of properties can be added to a named group, which will create a nested class within the parent config class with the properties attached.
Associated Properties
Sometimes you may want to map a property to the output of another property, rather than a passed in scheme. Take the example below:
The
logoName
property has anassociatedProperty
, which ties it’soverrides
to the value ofhost
instead of the passed in scheme. This allows for more concise override lists, as in the example above both the “test” and “stage” scheme will produce a “logo-test.png” logoName.Note that there are a couple of caveats when using
associatedProperty
:overrides
do not use regular expression pattern matching, and instead require an exact string match.associatedProperty
must have a String type.Reference Properties
Sometimes you may want to make a property return the output of another property, depending on the passed in scheme. For example:
The
textColour
property will bereturn red
for all schemes bar thegreenScheme
where it will bereturn green
.Enum
This schema should be used for creating enums. A sample of the schema is:
template.name
defines which template codeconfig
should use to parse this file.template.rawType
specifies the raw enum type to use. Currently only “String” is supported. The properties follow the same rules as the default, howevertype
is not required. If no value is provided fordefaultValue
and nooverrides
are present, the enum key will also be the raw value.Extensions
This schema should be used for creating extensions on existing classes. A Sample of the schema is:
This will output an extension on
UIColor
in a file calledUIColor+Palette.swift
.Custom types
It is possible to use your own custom types with config. Add a
customTypes
array to yourtemplate
section and you can then add your values, either as a string, for single values, or as a keyed dictionary. For example:Placeholders in the initialiser template should be written as
{key}
or{key:TypeHint}
where the type hint is one of the basic primitive types,Bool
,String
,URL
,Int
,Double
. If no type hint is supplied then the value is treated as an expression.Common patterns
If you find yourself repeating override patterns, for example
(PROD|STAGING)
you can list those in thepatterns
section of the template for reuse in your configuration. For example:DynamicColour and DynamicColourReference
To support iOS 13’s dark mode, it is possible to output colours as dynamic. For example:
will output:
Similarly, it is possible to use references to another colour, so:
will output:
Writing your own schemas
Just add a new class or struct to the project and implement
Template
. Add your new parser to thetemplates
array in main.swift. Your template should inspect atemplate
dictionary in any config and decide whether it can parse it. Either using aname
item, or through other means. EnsureConfigurationFile
is the last item in that array. As the default schema parser it claims to be able to parse all files.As new templates can be written from scratch, there is no pre-defined schema that your json file should adhere to, but for the sake of readability for other contributors, it would probably be sensible if it resembled the default schema as closely as possible.
Running as a build phase
If running as a build phase within Xcode, it’s recommended to use xcode file lists, both for the source input and the generated files. The input file list will ensure Xcode expires its cache of files and you build with the latest version. The output file list will ensure Xcode waits for your files to be generated before proceeding.