YS DRY

My friend Vadim Bauer External link filed this GitHub issue External link for the helm-unittest External link project, to DRY (don't repeat yourself) out some of the repetitive test data.

He also pinged me to weigh in, so I decided to do that in this post!

Simplifying repetitive YAML is what YS was made for. Well, one of many things; but let's focus on that today.

Vadim proposed taking existing YAML data files and using YS variables to define repeated data once, then referring to it in the places it is needed. Solid idea.

Let's use a very simple example here for brevity sake. I'll leave it to you to expand the concepts to your soaking wet YAML files.

# example.yaml
people:
- name: Alice
  likes:
  - programming
  - security
  hates:
  - bugs
  - repetition
- name: Bob
  likes:
  - programming
  - security
  hates:
  - bugs
  - repetition

Vadim's approach:

# example1.ys
!ys-0   # Start YAML file in YS code mode

# Define vars:
likes-these =::  # likes-these is a var. :: needed to put value in data mode
- programming
- security
hates-these =::  # 'data mode' means normal YAML semantics (not code)
- bugs
- repetition

=>::
  people:
  - name: Alice
    likes:: likes-these  # Here :: toggles to code mode (a var)
    hates:: hates-these
  - name: Bob
    likes:: likes-these
    hates:: hates-these

I'll run this command to see if it works:

$ diff -u example.yaml <(ys -Y example1.ys) && echo OK
OK

It works!

Let's ignore for a minute the .ys file is bigger and more complicated than the .yaml file.

As we will see there are many ways to do this with YS. One problem with this first approach is that we had to indent all our original YAML under the => :: to get from code mode to data mode. It would be nicer if we didn't need to change the original data much.

Some readers might be thinking, why not use YAML anchors and aliases. Let's try that…

# example2.yaml
vars:
- &likes-these
  - programming
  - security
- &hates-these
  - bugs
  - repetition

people:
- name: Alice
  likes: *likes-these
  hates: *hates-these
- name: Bob
  likes: *likes-these
  hates: *hates-these

That looks nicer. No extra indenting. We added a vars section with anchors on the reusable things.

Does it work?

$ diff -u example.yaml <(ys -Y example2.yaml) && echo OK
--- example.yaml        2026-06-17 06:59:16.545403994 -0400
+++ /dev/fd/63  2026-06-17 07:15:45.677692380 -0400
@@ -1,3 +1,8 @@
+vars:
+- - programming
+  - security
+- - bugs
+  - repetition
 people:
 - name: Alice
   likes:

shell returned 1

Not quite. The constant problem with YAML's anchor alias feature is that there's no good place to define things to be used later without making them also be part of your data.

Hmmm… What if we put the definitions in a separate YAML document? Remember, YAML allows files with multiple documents.

# example3.yaml
vars:
- &likes-these
  - programming
  - security
- &hates-these
  - bugs
  - repetition
---
people:
- name: Alice
  likes: *likes-these
  hates: *hates-these
- name: Bob
  likes: *likes-these
  hates: *hates-these

If we can load the final document then this looks like it should work. YS does load the last document so let's try it out:

$ diff -u example.yaml <(ys -Y example3.yaml) && echo OK
Error: 1 Anchor not found: &likes-these

--- example.yaml        2026-06-17 06:59:16.545403994 -0400
+++ /dev/fd/63  2026-06-17 07:24:55.183089916 -0400
@@ -1,15 +0,0 @@
-people:
-- name: Alice
-  likes:
-  - programming
-  - security
-  hates:
-  - bugs
-  - repetition
-- name: Bob
-  likes:
-  - programming
-  - security
-  hates:
-  - bugs
-  - repetition

shell returned 1

Bummer! It didn't find the anchor.

The reason is that the YAML spec says that aliases can only refer to anchors that are defined before it in the same document.

We were breaking the law.

Can YAMLScript help us out here? I think it can!

# example4.ys
!ys-0:
vars:
- &likes-these
  - programming
  - security
- &hates-these
  - bugs
  - repetition
--- !ys:
people:
- name: Alice
  likes: *likes-these
  hates: *hates-these
- name: Bob
  likes: *likes-these
  hates: *hates-these

We just tagged the first doc with !ys-0:. The extra : means start in data mode. Then we tagged the second doc with !ys:. Also data mode.

$ diff -u example.yaml <(ys -Y example4.ys) && echo OK
OK

Works!

YS knows the value of cross document referencing. It obeys the YAML laws for existing YAML files. But it changes the YAML laws (in small useful ways) when you turn on !ys.

Note: I've been using .ys and .yaml extensions on the example files. This was just for clarity. ys doesn't care about file extensions.

Let's go back to YS variables using multiple docs:

# example5.ys
!ys-0
likes-these =::
- programming
- security
hates-these =::
- bugs
- repetition
--- !ys:
people:
- name: Alice
  likes:: likes-these
  hates:: hates-these
- name: Bob
  likes:: likes-these
  hates:: hates-these

We start the first doc in code mode to define the vars.

$ diff -u example.yaml <(ys -Y example5.ys) && echo OK
OK

Still works and cleaner.

Can we put variables in another file? Certainly!

# vars.ys
!ys-0

likes-these =::
- programming
- security

hates-these =::
- bugs
- repetition
# example6.ys
!ys-0
load: 'vars.ys'
--- !ys:
people:
- name: Alice
  likes:: likes-these
  hates:: hates-these
- name: Bob
  likes:: likes-these
  hates:: hates-these
$ diff -u example.yaml <(ys -Y example6.ys) && echo OK
OK

We can even do this in one document:

# example7.ys
!ys-0:

=>:
  load: 'vars.ys'

people:
- name: Alice
  likes:: likes-these
  hates:: hates-these
- name: Bob
  likes:: likes-these
  hates:: hates-these

YS has a special feature when you start in data mode and the first key is => then its value is code. You can load files, define vars and functions etc.

Speaking of functions, load is a YS standard library function. There are 100s more. Can we use them in this example? Definitely!

# example8.ys
!ys-0:

=>: load('vars.ys')

people:
- name: Alice
  likes:: likes-these:reverse
  hates:: hates-these + 'birds'
- name: Bob
  likes:: sort(likes-these + 'repositories')
  hates:: hates-these.take(1)

Let's see what we get:

$ ys -Y example8.ys
people:
- name: Alice
  likes:
  - security
  - programming
  hates:
  - bugs
  - repetition
  - birds
- name: Bob
  likes:
  - programming
  - repositories
  - security
  hates:
  - bugs

Fun!

I'll stop here for now. There's a lot more I could say wrt to that issue and different way that YS could be wired into helm-unittest.

Vadim… Maybe we could work on a PR together! helm-unittest.

Comments