A tool to render yaml into InnoSetup iss files.
I wanted to have a way to define my InnoSetup scripts in a more structured, reusable way, and I like yaml. I also happen to dislike a lot the InnoSetup scripting language, so I wanted to avoid it as much as possible. This tool is the result of that.
Well, it's a fun project to work on, and I think it can be useful for some people. I also wanted to learn how to write a parser without using either Jinja or a super complex architecture, keeping it simple and easy to understand was in itself a challenge. Also, I wanted to achieve some things currently not possible with InnoSetup, like:
The tool reads a yaml file and renders it into an InnoSetup iss file, which can then be compiled with the InnoSetup compiler.
{}
syntax.yamelinno.py [options] <input_yml_file>
Options:
-o, --output <output_file> Output file. If not specified, the output will be printed to stdout.
-s, --schema <schema_file> Schema file. If not specified, the schema will be read from "base-schema.yml", which will be searched in any of the available schemas directories.
-v, --version Display the version of the tool.
-h, --help Display this help message.
# input.yml
setup:
appId: '{2b6f0cc904d137be2e1730235f5664094b831186}' #UUID:
appName: "MyApp" #Name
appVersion: "1.0" #Version
files:
- source: "src/main.js"
destDir: '{app}'
flags:
- ignoreversion
# schema.yml
setup:
renderedName: Setup
children: keys
required: true
keys:
appId:
renderedName: AppId
required: true
appName:
renderedName: AppName
required: true
appVersion:
renderedName: AppVersion
required: true
defaultDirName:
renderedName: DefaultDirName
required: false
defaultDirPath:
renderedName: DefaultDirPath
required: false
files:
renderedName: Files
children: entries
# Entry structure
entry:
source:
renderedName: Source
required: true
destDir:
renderedName: DestDir
required: true
flags:
renderedName: Flags
required: false
With those files in place, you can run the tool like this:
yamelinno input.yml -o output.iss
and get:
[Setup]
AppId='{{2b6f0cc904d137be2e1730235f5664094b831186}'
AppName=MyApp
AppVersion=1.0
[Files]
Source: "src/main.js"; DestDir: "{app}"; Flags: ignoreversion
The tool reads the input file and searches recursively for the referenced templates. It then merges them into a single yaml file, validates it against the schema and, if the file is valid, proceeds to render the iss file. The schema is used to validate the input file and to provide hints to the user. More on that later.
Templates are just pre-filled yaml files, which can be included in the main yaml file (or in other templates). They are resolved recursively, so a template can include other templates, which can include other templates, and so on. Here is an example:
# template.yml
# A template to include a LICENSE file in the installation directory.
files:
- source: "LICENSE"
destDir: '{app}'
flags:
- ignoreversion
# input.yml
setup:
appId: '{2b6f0cc904d137be2e1730235f5664094b831186}' #UUID:
appName: "MyApp"
appVersion: "1.0"
templates:
- "template.yml"
[Setup]
AppId='{{2b6f0cc904d137be2e1730235f5664094b831186}'
AppName=MyApp
AppVersion=1.0
[Files]
Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion
The are two possible ways to include a template in the main yaml file:
templates
. This is what is shown in the example above.Here is an example of the extended syntax:
# template.yml
# A template to include a file in the installation directory.
files:
- source: '!sourceFile' # Single quotes are used to escape special characters.
destDir: '{app}'
flags:
- ignoreversion
The !sourceFile
key will be replaced by the value of the sourceFile
key in the input file, as shown below.
templates:
- path: "template.yml" # Path to the template file.
inputs:
# Values to be used in the template. To use special characters, such as `:`, or `#`,
# you can surround the value with single quotes. Using '!' is unsupported yet.
sourceFile: 'C:\LICENSE'
# If overwrite is true, the values in the template will overwrite the values in the
# previous templates. If false, the values in the template will be merged with the
# values in the previous templates, trying to preserve as much information as possible.
# That is, dictionaries will be merged (only conflicting keys will be overwritten), and
# lists will be appended.
overwrite: false
As a result, you will get:
[Files]
Source: "C:\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
Some more things to add:
overwrite
key is optional, and defaults to false
.inputs
key is optional, and defaults to an empty list.path
key is required if you are using the extended syntax.# template.yml
files:
- source: '!sourceFile'
destDir: '{app}\!source'
flags:
- ignoreversion
You would expect to use it as follows:
templates:
- path: "template.yml"
inputs:
source: 'myDir'
sourceFile: 'C:\LICENSE'
But the result will be:
[Files]
Source: "myDirFile"; DestDir: "{app}\myDir"; Flags: ignoreversion
This is because the sourceFile
value is fully contained in the source
value, and in this case, the replacement is done in the order the values are resolved. This is a known issue and will be addressed in future versions. For now, you can avoid this by using different names for the input values.
The path to the template file is resolved in the following way:
templates
key. This allows you to include templates that are in the same directory as the main yaml file, without having to specify the full path for any of them. You can also store all your templates in a single directory and reference them by filename only.The schema is a yaml file that defines the structure of the input yaml file. It is used to validate the input file and to provide hints to the user. The schema is also a yaml file, so you can modify it to add missing keys or entries without having to modify the tool. The attributes and structure of the schema are pretty much self-explanatory, but here is a brief explanation of the keys:
renderedName
: The name of the key in the rendered iss file.children
: The type of the key. It can be keys
, entries
or raw
. keys
are used to define a dictionary, while entries
are used to define a list of dictionaries (which follow the same schema that is defined in the entry
key). raw
is just a raw string that is rendered as is, useful for directives that don't follow the key-value structure (such as [Code]
).required
: Whether the key is required or not. If a key is required and not present in the input file, the tool will raise an error. This can be added to sections, keys, and entries.keys
: A dictionary that defines the structure of the keys in the section. This is used to validate the input file and to provide hints to the user.entry
: A dictionary that defines the structure of the entries in the section. This is used to validate the input file and to provide hints to the user.type
: The type of the key. It can be str
, int
, float
, bool
, list
, dict
, among other variations. This is used to validate the input file. It's optional, so you can omit it if you don't want to validate the type of the key.Here is an example of a schema file:
setup:
renderedName: Setup
children: keys
required: true
keys:
appId:
renderedName: AppId
required: true
type: str
appName:
renderedName: AppName
required: true
appVersion:
renderedName: AppVersion
required: true
defaultDirName:
renderedName: DefaultDirName
required: false
defaultDirPath:
renderedName: DefaultDirPath
required: false
A base schema is provided with the tool, so you can use it as a starting point to define your own schema. The base schema is pretty much the same as the schema above, but with more keys and entries defined. You can find it in the schemas/base-schema.yml
file. If you want to use a different schema, you can specify it with the -s
or --schema
option. If you don't specify a schema, the tool will try to find the base schema in any of the available schemas directories.
NOTE: Although the provided schema does follow pretty much the same naming conventions as the InnoSetup directives, it doesn't have to be that way. You can define the keys and entries in the schema in any way you see fit, as long as you follow the structure defined above. As long as you keep the renderedName
a valid InnoSetup directive, the tool will render it correctly.
Just like the templates, the schema is resolved in the following way:
You can use this tool in many ways, but here are some examples:
You can run it directly as a python script. Just clone the repository, install the dependencies, and run the script with the desired options. For example:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python yamelinno.py input.yml -o output.iss
You can also compile the script into an executable using pyinstaller
. Just install pyinstaller
and run the following command:
pyinstaller --onefile yamelinno.py
This will create an executable file in the dist
directory. You can then run it as any other executable. This should work on Linux and Windows, but YMMV.
You can use the provided container image to run the tool. Just pull the image and run it with the desired options. For example:
podman run --rm -v $(pwd):/app ghcr.io/nhermosilla14/yamelinno:latest input.yml -o output.iss
You can build the container image yourself. Just clone the repository and build using the provided Containerfile. For example:
# Using podman is recommended
podman build -t yamelinno -f Containerfile .
# Or using docker
docker build -t yamelinno -f Containerfile .
This will build the container image with the name yamelinno
. You can then run it as shown above.
This project follows some coding guidelines to keep the codebase clean and easy to understand. Here are some of them:
If you are willing to follow these guidelines, you are welcome to contribute to this project in any way you see fit. PRs, comments, bug reports and ideas are all welcome!
This tool would not exist if there was no InnoSetup, and that's all thanks to the awesome people at https://jrsoftware.org/ (particularly Jordan Russell). Thanks for making it available for free, and for allowing the development of derivative works. You can check it out at the link above, it's a great piece of software.
This tool is free software, and may be redistributed under the terms specified in the GNU General Public License version 3.0 (or later). See the LICENSE file for details.