DOCS: Add an intro tutorial with json output.

This commit is contained in:
Jeremy Wall 2019-03-29 12:43:15 -04:00
parent 2e38a3486e
commit d3f7e49790
4 changed files with 181 additions and 2 deletions

View File

@ -9,5 +9,6 @@ A collection of useful Tutorials
Some example use cases for UCG.
* <a href="a-json-config">A gentle introduction to ucg while compiling to a json file.</a>
* <a href="script">Creating a launch script for a docker container</a>
* <a href="recursive-modules">Recursive Modules</a>

View File

@ -0,0 +1,178 @@
+++
title = "A simple JSON configuration"
slug = "a-json-config"
weight = 1
in_search_index = true
+++
UCG is a pure functional language, with immutable values, and forward type
inference. It can be used to translate it's various datatypes into a number of
common configuration file formats as well as create some environment variables
and command line flags.
One of it's intended use cases is to create json configuration files safely.
Lets imagine a webservice that uses json as it's configuration format. It needs
to know a few things to operate properly.
* The port it should listen on
* The ip address it should listen on
* The database connection information
* The host to check for it's dynamic configuration service (i.e. Consul or Etcd)
The service expects the config file to look something like this
```json
{
"port": 8888,
"addr": "0.0.0.0",
"db": {
"host": "mydbhost",
"port": 3306,
"database": "myservicedb",
},
"config_svc": "https://consul.internal:8080"
}
```
To get started we need to create file config.ucg that to hold our services
configuration.
```sh
touch config.ucg
```
Then we create our configuration starting with a UCG tuple.
```
let conf = {
port = 8888,
addr = "0.0.0.0",
};
```
This is our first UCG statement. Statements in UCG start with an optional
keyword, in this case `let` and are terminated by a semicolon (`;`).
`let` introduces a named binding, in this case, a tuple. Inside the tuple we
have two keys: `port` and `addr`. Quoting a key is optional in UCG if the key
is a single word with all word characters or `_` and no spaces. If you need
dashes or spaces you can quote the key. The first key `port` is a number,
specifically an integer. the second `addr` is a string.
Now we need to add our database and dynamic configuration service values to our
tuple.
```
let db_confs = import "db/mysql/hosts.ucg";
let consul_hosts = import "services/consul/hosts.ucg".host_pool;
let conf = {
port = 8888,
addr = "0.0.0.0",
db = {
host = db_confs.host_pool.addr,
port = db_confs.host_pool.port,
database = "myservicedb",
},
config_svc = consul_hosts.url,
};
```
There are a couple new items here. The first is the UCG import expression.
Import expressions start with the keyword `import` and are followed by a string
specifying the relative or absolute path to a UCG file. Relative import paths
are relative to the file they appear in or if not there then relative the path
specified in the `UCG_IMPORT_PATH` variable. When you import a file all of it's
named bindings are exposed as a UCG tuple. We select specific values from the
`services/consul/hosts.ucg` file as part of the import statement using dot
selectors.
This file assumes that the `db/mysql/hosts.ucg` import contains a named binding
called `host_pool` that is a tuple with an `addr` field and a `port` field. It
also assumes the `services/consul/hosts.ucg` has a host_pool named binding with
a url field.
Imports allow us to share our configuration values for shared services among
all of the configurations for their consumers regardless of the format they
expect.
Let's create those two files now.
```sh
mkdir -p db/mysql/
mkdir -p services/consul
touch db/mysql/hosts.ucg
touch db/services/consul/hosts.ucg
```
In the `db/mysql/hosts.ucg` file add the following.
```
let host_pool = {
addr = "mysql-pool.internal,
port = 3306,
};
```
In the `services/consul/hosts.ucg` file add the following.
```
let make_pool = func (addr, port) => {
addr = addr,
port = port,
url = "https://@:@" % (addr, port),
};
let host_pool = make_pool("consul.internal", 8080);
```
Our `services/consul/hosts.ucg` file is a little more interesting that our
previous files. We introduce UCG functions as well as format strings. A UCG
function starts with the `func` keyword a set of argments the fatcomma (`=>`)
and a valid UCG expression. Functions can close over their environment and they
can reference the variables defined in the argument in the expression. This one
constructs a tuple for us.
The function also uses a format string to construct our URL for the consul
service. Format strings start with a template string followed by the `%`
character. They are then followed by a parenthesized list with the items to
substitute in the string.
Format strings also have an alternate form that you can read about in the
reference.
We have now constructed our config format but we don't have a json file yet.
For that we need an `out` statement. Back in our `config.ucg` file add the
following statement at the bottom: `out json conf;`.
```
let db_confs = import "db/mysql/hosts.ucg";
let consul_hosts = import "services/consul/hosts.ucg".host_pool;
let conf = {
port = 8888,
addr = "0.0.0.0",
db = {
host = db_confs.host_pool.addr,
port = db_confs.host_pool.port,
database = "myservicedb",
},
config_svc = consul_hosts.url,
};
out json conf;
```
The out statement converts the UCG intermediate representation into the desired
output format. Each output format has a defined translation and in some cases
expected rules that the value should satisfy. Any errors in the translation
will result in a compile error and no output will be generated. In this case we
specify the `json` compile target and the `conf` value we generated.
Running the following will generate a `config.json` file that looks like our
example above.
```sh
ucg build config.ucg
```

View File

@ -1,7 +1,7 @@
+++
title = "Recursive Modules and their Uses"
slug = "recursive-modules"
weight = 2
weight = 3
in_search_index = true
+++

View File

@ -1,6 +1,6 @@
+++
title = "Creating a launch script for a docker container"
weight = 1
weight = 2
sort_by = "weight"
in_search_index = true
+++