New post about YAML
This commit is contained in:
parent
5d59956cf2
commit
8166afd667
504
pages/2018-08-27-YAML-For-Nonprogrammers.md
Normal file
504
pages/2018-08-27-YAML-For-Nonprogrammers.md
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
layout: post
|
||||||
|
title: YAML for Non-programmers
|
||||||
|
subtitle: and programmers
|
||||||
|
tags: [homeassistant]
|
||||||
|
|
||||||
|
It's been a while...
|
||||||
|
|
||||||
|
### Introduction
|
||||||
|
I've been fiddling with home automation for a few years, but only recently
|
||||||
|
found my way to [http://www.home-assistant.io](homeassistant) - a project with
|
||||||
|
great developers and a great community. I've been hanging about the official
|
||||||
|
[Discord Chat Server](https://discord.gg/c5DvZ4e), and try to give something
|
||||||
|
back by helping people when I can.
|
||||||
|
|
||||||
|
One thing I noticed is that people often struggle with is the fact that the
|
||||||
|
configuration is made through YAML. A strange choice for the kind of
|
||||||
|
quasi-programming you may want to do when automating your home appliances.
|
||||||
|
|
||||||
|
In this post I have tried to describe how YAML works, and how I think about the
|
||||||
|
way it represents basic data structures. I hope it can be useful to someone...
|
||||||
|
|
||||||
|
### Dictionaries and lists
|
||||||
|
|
||||||
|
To understand YAML, you need to understand what it's describing. There are two
|
||||||
|
main concepts: *Dictionaries* and *Lists*.
|
||||||
|
|
||||||
|
|
||||||
|
**Lists** are quite simple. It's just a list - an ordered collection of things.
|
||||||
|
There are two things you need to remember though. Let me show them to you in a
|
||||||
|
list :)
|
||||||
|
|
||||||
|
- Lists are ordered. If you make a list with a dog, a cat and an elephant, the
|
||||||
|
dog will be first, the elephant will be last and the cat will be in between.
|
||||||
|
- Lists can be lists of anything; strings, integers, booleans, or even
|
||||||
|
dictionaries or lists.
|
||||||
|
|
||||||
|
A list of lists might sound weird, but just think about the lists you have in
|
||||||
|
your home.
|
||||||
|
|
||||||
|
- A shopping list
|
||||||
|
- A todo list
|
||||||
|
- A list of things to pack for the vacation next week
|
||||||
|
- The list of passwords on a post-it under your keyboard
|
||||||
|
|
||||||
|
**Dictionaries** are not much more complicated. You've probably seen it in some
|
||||||
|
way or another, but perhaps with a different name. In different programming
|
||||||
|
languages they can be called *dictionaries*, *hashes*, *maps*, *hash tables*,
|
||||||
|
*tables*, *collections* or even just *objects*. The technical name is
|
||||||
|
*Associative Array*.
|
||||||
|
|
||||||
|
Regardless of name, the concept is simple. A dictionary is a collection of
|
||||||
|
key-value pairs. That is, a Name for something, and that Something. The name -
|
||||||
|
the *key* must be unique. The same key can not be used twice in the same
|
||||||
|
dictionary. The Something - the *value* can be anything at all, just like the
|
||||||
|
items in a list.
|
||||||
|
|
||||||
|
Let's look at some sample dictionaries:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Monday: Sausage and beans
|
||||||
|
Tuesday: Fish
|
||||||
|
Wednesday: French onion soup
|
||||||
|
Thursday: Pea soup and pancakes
|
||||||
|
Friday: Pizza
|
||||||
|
```
|
||||||
|
|
||||||
|
Each day is labeled by a key, and has a value - what are you going to eat that day.
|
||||||
|
Note that while you could add another `Wednesday` to the end of the list, it
|
||||||
|
wouldn't really make sense. Thus keys have to be unique. The values doesn't
|
||||||
|
however. It would make perfect sense to eat pizza again on Saturday.
|
||||||
|
|
||||||
|
Since keys are unique, their order is not important:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Wednesday: French onion soup
|
||||||
|
Friday: Pizza
|
||||||
|
Monday: Sausage and beans
|
||||||
|
Tuesday: Fish
|
||||||
|
Thursday: Pea soup and pancakes
|
||||||
|
```
|
||||||
|
|
||||||
|
This dictionary contains exactly the same data as the one above. A clear
|
||||||
|
difference from a list, where the order itself is a part of the data.
|
||||||
|
|
||||||
|
Another dictionary:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
That's a dictionary. Looks kind of like a database of a sort, doesn't it?
|
||||||
|
Like the address book in your email program? Ah! But don't get fooled. The
|
||||||
|
address book is a **list**, not a **dictionary**. However - the *items* in the
|
||||||
|
list are *dictionaries*.
|
||||||
|
|
||||||
|
|
||||||
|
Let's add on to that dictionary:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
Hobbies: singing, woodworking, home automation
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we added an entry to the dictionary where the value is a list. I have three
|
||||||
|
hobbies. This illustrates that the value of a dictionary key-value pair can be
|
||||||
|
anything. Even a dictionary:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
Hobbies: singing, woodworking, home automation
|
||||||
|
Phones: Home: +46 (0)XX XXXXXX, Work: +40 (0)XX XXXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
And remember that lists can contain dictionaries too...
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
Hobbies: singing, woodworking, home automation
|
||||||
|
Phones: Home: +46 (0)XX XXXXXX, Work: +40 (0)XX XXXXXX
|
||||||
|
Children: Name: N, Age: 3 ; Name: H, Age: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
But that this point things are getting advanced. It's hard to keep track of
|
||||||
|
what is a dictionary and what is a list and what contains what...
|
||||||
|
|
||||||
|
If only there was a language to describe these concepts... a sort of Markup
|
||||||
|
Language, if you will... but who needs Yet Another one of those?
|
||||||
|
|
||||||
|
Let me tell you about
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
|
||||||
|
Javascript Structured Object Notation - [JSON](https://www.json.org/). You
|
||||||
|
thought I was going to say YAML, didn't you? We'll get there too.
|
||||||
|
|
||||||
|
JSON is a simple way of writing down the concepts described above which can be
|
||||||
|
easily understood both by humans and by computers.
|
||||||
|
|
||||||
|
Basically, there are *objects* and *arrays*, but let's call them
|
||||||
|
*dictionaries* and *lists* instead.
|
||||||
|
|
||||||
|
*lists* are surrounded by square brackets and contain items separated by commas.
|
||||||
|
The items can be strings, numbers, dictionaries, lists or any of the magic
|
||||||
|
values `true`, `false`, or `null`.
|
||||||
|
|
||||||
|
*Dictionaries* are surrounded by curly braces and contain key-value pairs
|
||||||
|
separated by commas. Each key-value pair has the key, a colon and the value.
|
||||||
|
Keys must be strings, but values can be anything that can be in a list.
|
||||||
|
|
||||||
|
Let's look at our dictionary from above in JSON format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
Name: "Thomas Lovén",
|
||||||
|
Email: "thomasloven@example.com",
|
||||||
|
Hobbies: [
|
||||||
|
"singing",
|
||||||
|
"woodworking",
|
||||||
|
"home automation"
|
||||||
|
]
|
||||||
|
Phones: {
|
||||||
|
Home: "+46 (0)XX XXXXXX",
|
||||||
|
Work: "+46 (0)XX XXXXXX"
|
||||||
|
}
|
||||||
|
Children: [
|
||||||
|
{ Name: "N", Age: 3},
|
||||||
|
{ Name: "H", Age: 1}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I added some line breaks and indentations to make it more pretty, but this is
|
||||||
|
much easier to read. Even the last key-value pair about my children.
|
||||||
|
Two things to note
|
||||||
|
|
||||||
|
- You don't need quotes around the keys, but you do around values that are
|
||||||
|
strings. If you want whitespace in a key (which is entirely OK) it must be
|
||||||
|
quoted, though.
|
||||||
|
- The indentations and newlines I added, and in fact any whitespace not in
|
||||||
|
quotes, is ignored.
|
||||||
|
|
||||||
|
OK. Now you understand one markup language. Let's learn something different.
|
||||||
|
|
||||||
|
### YAML
|
||||||
|
|
||||||
|
[YAML](http://yaml.org/) Ain't a Markup Language - but it's pretty darn close,
|
||||||
|
to be honest.
|
||||||
|
|
||||||
|
While probably not historically accurate, YAML can be seen as an evolution of
|
||||||
|
JSON. In fact, any valid JSON is also valid YAML. That might be important to
|
||||||
|
remember. There are some notable differences, though.
|
||||||
|
|
||||||
|
First of all, YAML does away with the braces. Instead items in lists are
|
||||||
|
separated by newlines where each item starts with a dash:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- Item 1
|
||||||
|
- Item 2
|
||||||
|
- Item 3 is a long one that stretches over multiple lines.
|
||||||
|
The new item won't start until we get to a line that starts
|
||||||
|
with a dash, like the one below this one.
|
||||||
|
- Item 4
|
||||||
|
-
|
||||||
|
- Item 5a
|
||||||
|
- Item 5b
|
||||||
|
- Item 5c
|
||||||
|
- Item 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Some things to note:
|
||||||
|
|
||||||
|
- There are no quotes. In YAML quotes are pretty much optional.This can be a
|
||||||
|
blessing and a curse. For example `"true"` is a string, but `true` is a
|
||||||
|
boolean value.
|
||||||
|
- Indentation is important. Item 3 in the list stretches over multiple lines.
|
||||||
|
Each line after the first one is indented (with an equal number of spaces,
|
||||||
|
*NOT* tabs). The same is true for Item 5, which is a list. Each item in the
|
||||||
|
sub-list is indented with an equal number of spaced.
|
||||||
|
- The items of the list are not of the same type. Most are strings, but item 5
|
||||||
|
is a list.
|
||||||
|
|
||||||
|
Dictionaries are also separated on lines with the key, a colon and the value:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
Hobbies:
|
||||||
|
- singing
|
||||||
|
- woodworking
|
||||||
|
- home automation
|
||||||
|
Phones:
|
||||||
|
Home: +46 (0)XX XXXXXX
|
||||||
|
Work: +46 (0)XX XXXXXX
|
||||||
|
Children:
|
||||||
|
-
|
||||||
|
Name: N
|
||||||
|
Age: 3
|
||||||
|
- Name: H
|
||||||
|
Age: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Things to note:
|
||||||
|
|
||||||
|
- The value corresponding to the key `Hobbies` is a list. Like above, each line
|
||||||
|
of the value is indented by an equal number of spaces.
|
||||||
|
- The value corresponding to the key `Phones` is a dictionary. The same
|
||||||
|
indentation rules apply.
|
||||||
|
- The value corresponding to the key "Children" is a list where each item is a
|
||||||
|
dictionary. So each line in each dictionary is indented twice.
|
||||||
|
- The second entry in the list of children uses a contracted form, where the
|
||||||
|
first key-value pair of the dictionary is put on the same line that signifies
|
||||||
|
the list item. More on this later.
|
||||||
|
|
||||||
|
And that's all the basics of data representation using YAML.
|
||||||
|
|
||||||
|
### On indentation
|
||||||
|
|
||||||
|
As I've been trying to help people with their configurations on the
|
||||||
|
homeassistant Discord server, I have found one problem which occurs more than
|
||||||
|
any other. Indentation errors.
|
||||||
|
|
||||||
|
*Indentation is important*
|
||||||
|
|
||||||
|
It *must* be correct, or the YAML won't be accepted by the parser, or it will
|
||||||
|
describe something entirely different from what you intended.
|
||||||
|
|
||||||
|
The only advice I can give is to think carefully about the structure of the
|
||||||
|
data you are trying to represent. What is your object? Is it a dictionary or a
|
||||||
|
list? Where is it contained? Is it freestanding? Is it the value of a
|
||||||
|
dictionary key-value pair? Is it an item in a list? What is it's parent? What
|
||||||
|
are it's children? What are it's siblings?
|
||||||
|
|
||||||
|
It it's a complex structure, it might help to make a drawing on actual paper.
|
||||||
|
|
||||||
|
In the YAML dictionary sample above, I used a contracted for in my list of
|
||||||
|
dictionaries. This is common practice, but may be a bit confusing at first
|
||||||
|
since it makes the indentation unclear.
|
||||||
|
|
||||||
|
If might be easier to understand the structure of the document if you use the
|
||||||
|
expanded form:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Children:
|
||||||
|
-
|
||||||
|
Name: N
|
||||||
|
Age: 3
|
||||||
|
-
|
||||||
|
Name: H
|
||||||
|
Age: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced topics
|
||||||
|
|
||||||
|
#### Comments
|
||||||
|
Adding comments to your code makes it easier to understand. Both to other
|
||||||
|
people, and - more importantly - to you when you return to it in six months
|
||||||
|
because something stopped working.
|
||||||
|
|
||||||
|
In YAML, comments begin with a number sign `#`, last until the end of the line
|
||||||
|
and are ignored by the parser.
|
||||||
|
```yaml
|
||||||
|
# A dictionary about me
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com # This isn't really my email
|
||||||
|
Hobbies:
|
||||||
|
# Just some of the ways I like to waste time
|
||||||
|
- singing # choir, mostly
|
||||||
|
- woodworking
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Spaces and colons
|
||||||
|
|
||||||
|
As mentioned, YAML doesn't require quotes around strings, but they are allowed.
|
||||||
|
Quotes can be useful to tweak the parsing. Imagine for example the following list:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- Halflife 1
|
||||||
|
- Halflife 2
|
||||||
|
- Halflife 2: Episode Two
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a list of strings, right? Wrong. The third entry is a dictionary with
|
||||||
|
the key "Halflife 2" and the value "Episode Two" (keys can contain spaces, by the way).
|
||||||
|
|
||||||
|
To fix this, you can use quotes:
|
||||||
|
```yaml
|
||||||
|
- Halflife 1
|
||||||
|
- Halflife 2
|
||||||
|
- "Halflife 2: Episode Two"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using JSON
|
||||||
|
|
||||||
|
There's a reason I went through JSON to explain YAML. As I said, all JSON is
|
||||||
|
also valid YAML. This allows for compact notation:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Name: Thomas Lovén
|
||||||
|
Email: thomasloven@example.com
|
||||||
|
Hobbies: [singing, woodworking, home automation]
|
||||||
|
Phones:
|
||||||
|
Home: +46 (0)XX XXXXXX
|
||||||
|
Work: +46 (0)XX XXXXXX
|
||||||
|
Children:
|
||||||
|
- {Name: N, Age: 3}
|
||||||
|
- {Name: H, Age: 1}
|
||||||
|
```
|
||||||
|
|
||||||
|
I mention this because you just might run into it sometime. I like to use it in
|
||||||
|
my configurations to bring down the line count, but it's easy to go overboard
|
||||||
|
and make the data hard to read instead. In the end it's a matter of taste.
|
||||||
|
|
||||||
|
Note that there are still no quotes. That's OK as long as you don't want
|
||||||
|
commas, } or ] in the value.
|
||||||
|
|
||||||
|
#### Merging
|
||||||
|
|
||||||
|
Dictionaries can be merged using the key: `<<`. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
a key: a value
|
||||||
|
b key: b value
|
||||||
|
<<: {d key: d value, e key: e value}
|
||||||
|
```
|
||||||
|
|
||||||
|
will be parsed as
|
||||||
|
```yaml
|
||||||
|
a key: a value
|
||||||
|
b key: b value
|
||||||
|
c key: c value
|
||||||
|
d key: d value
|
||||||
|
```
|
||||||
|
|
||||||
|
and so will
|
||||||
|
```yaml
|
||||||
|
a key: a value
|
||||||
|
b key: b value
|
||||||
|
<<:
|
||||||
|
c key: c value
|
||||||
|
d key: d value
|
||||||
|
```
|
||||||
|
|
||||||
|
In short, the `<<` key takes a dictionary as its value, and merges it into the
|
||||||
|
parent dictionary.
|
||||||
|
|
||||||
|
|
||||||
|
#### Node anchors
|
||||||
|
|
||||||
|
Merging is very convenient when used in combination with node anchors.
|
||||||
|
|
||||||
|
Node anchors are a way of saving a dictionary, and reusing it later
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
my_dict: &my_dict
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case `&my_dict` is NOT the value corresponding to the key `my_dict`,
|
||||||
|
but a node anchor - as signified by the ampersand `&`.
|
||||||
|
|
||||||
|
The anchor saves the value for later reuse and can be recalled any number of
|
||||||
|
times using an asterisk `*`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
a dictionary: &saved
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
|
||||||
|
another dictionary: *saved
|
||||||
|
|
||||||
|
a list:
|
||||||
|
- *saved
|
||||||
|
- *saved
|
||||||
|
```
|
||||||
|
|
||||||
|
This will be parsed as:
|
||||||
|
```yaml
|
||||||
|
a dictionary:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
|
||||||
|
another dictionary:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
|
||||||
|
a list:
|
||||||
|
-
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
-
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
You can also merge an anchor if you want to add more entries to the dict:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
base: &base
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
|
||||||
|
extended version:
|
||||||
|
<<: *base
|
||||||
|
c: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, understanding how this will parse shouldn't be a problem to you.
|
||||||
|
|
||||||
|
Now, for my final trick:
|
||||||
|
#### Merging while defining.
|
||||||
|
|
||||||
|
The problem with the above examples is that you need to put the definition
|
||||||
|
somewhere. The YAML snippets above will have the dictionary keys `a
|
||||||
|
dictionary`and `base` defined and set no matter what. Sometimes that's
|
||||||
|
impractical, which is why you often see the following in homeassistant
|
||||||
|
packages:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
homeassistant:
|
||||||
|
customize:
|
||||||
|
package.node_anchors:
|
||||||
|
common: &common
|
||||||
|
key1: val1
|
||||||
|
key2: val2
|
||||||
|
|
||||||
|
sensor.my_sensor:
|
||||||
|
<<: *common
|
||||||
|
icon: mdi:temp
|
||||||
|
sensor.another_sensor:
|
||||||
|
<<: *common
|
||||||
|
icon: mdi:home
|
||||||
|
```
|
||||||
|
|
||||||
|
The `package.node_anchors` key in the `customize` dictionary contains a
|
||||||
|
dictionary of stuff that is simply ignored. Anything you put there will have no
|
||||||
|
effect on the package, so it's a great place to define anchors.
|
||||||
|
|
||||||
|
Another possibility is to put the definition in the first place it is used, and
|
||||||
|
merge it immediately:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
homeassistant:
|
||||||
|
customize:
|
||||||
|
sensor.my_sensor:
|
||||||
|
<<: &common {key1: val1, key2: val2}
|
||||||
|
icon: mdi:temp
|
||||||
|
sensor.another_sensor:
|
||||||
|
<<: *common
|
||||||
|
icon: mdi:temp
|
||||||
|
```
|
||||||
|
|
||||||
|
Not all YAML parsers allow this, but it seems to work with homeassistant.
|
Loading…
x
Reference in New Issue
Block a user