ursula

Zeus is on the loose, an unstoppable frenzy of creative force! Come June I'll have an entire nursery to celebrate me and chastise my absence in July. ytree needed a witchy aunt, ursula. I could produce tarchives by authoring YAML directly or tapping into the python APIs, both covered in the last post. "Authoring" could also mean templating, which I'd rather not do. Or promote! So ursula implements a lua-driven CLI for dynamic generation. Here's the same example from before, a static set of resource definitions.

--- !ytree/tar/dir/v1
name: ./foo

--- !ytree/tar/file/v1
name: ./foo/bar
contents: !ytree/res/file/v1
  path: files/ytree/example.yaml

--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/buffer/v1
  contents: !!binary aGVsbG8sIHdvcmxk

--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/string/v1
  value: hello, world

Here's a lua script that produces the same document stream. The ytree package being required is implemented in python and exposed to the runtime. I wrapped the ytree.tar resource constructors behind the ursula.fs endpoints so that each time one is declared in lua I simply serialize the object.

local ursula = require("ursula")
local ytree = require("ytree")

return function (values)

   ursula.fs.dir {
      name = "./foo",
   }

   ursula.fs.file {
      name = "./foo/bar",
      contents = ytree.res.file "files/ytree/example.yaml"
   }

   ursula.fs.file {
      name = "./hello/world",
      contents = ytree.res.buffer "aGVsbG8sIHdvcmxk"
   }

   ursula.fs.file {
      name = "./hello/world",
      contents = ytree.res.string "hello, world"
   }

end

If you had that in a file named files/ursula/example.lua then you could run it like this. I truncated the output here to remove the default members like uid for brevity.

$ python -m dynastyf.ursula files.ursula.example

--- !ytree/tar/dir/v1
name: ./foo
--- !ytree/tar/file/v1
name: ./foo/bar
contents: !ytree/res/file/v1
  path: files/ytree/example.yaml
--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/buffer/v1
  contents: !!binary |
    aGVsbG8sIHdvcmxk
--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/string/v1
  value: hello, world

At this point you could save the output or pipe it through ytree.tar to produce an actual tarchive, which was true for the static file to begin with, but this stream is produced on the fly.

$ python -m dynastyf.ursula files.ursula.example \
      | python -m ytree.tar -c \
      | tar -tvf -

drwxr-x--- 0/0               0 1969-12-31 19:00 ./foo/
-rw-r----- 0/0             346 1969-12-31 19:00 ./foo/bar
-rw-r----- 0/0              12 1969-12-31 19:00 ./hello/world
-rw-r----- 0/0              12 1969-12-31 19:00 ./hello/world

I don't leverage it in that example but you can see that the function returned by the module takes one argument, values, which ursula constructs by merging dictionaries loaded from yet more YAML documents. Here's an example of a script that looks up a name from that table (and does a little templating, as a treat).

local ursula = require("ursula")
local ytree = require("ytree")

return function (values)

   ursula.fs.file {
      name = "foo",
      contents = ytree.res.string(
         string.format("%s, %s", values.greeting, values.name)
      )
   }

end
# foo.yaml
greeting: hello
name: foo
$ python -m dynastyf.ursula \
         --values files/ursula/foo.yaml \
         files.ursula.hello
--- !ytree/tar/file/v1
name: foo
contents: !ytree/res/string/v1
  value: hello, foo

Values are merged top down (if there are multiple documents in one file) and left to right (if there are multiple files). Here's a second file to finish the example. I only override the name, so the greeting from foo.yaml will simply pass through.

# bar.yaml
name: bar
$ python -m dynastyf.ursula \
         --values files/ursula/foo.yaml \
         --values files/ursula/bar.yaml \
         files.ursula.hello
--- !ytree/tar/file/v1
name: foo
contents: !ytree/res/string/v1
  value: hello, bar