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