Struggling to understand the meta/template things

24 views
Skip to first unread message

gary.r...@gmail.com

unread,
Jan 17, 2013, 9:01:26 AM1/17/13
to babush...@googlegroups.com
Hi,

I started playing with Babushka a few weeks ago, I wrote a few deps, everything seemed fine.

I looked through some of Ben's babushka-deps and noticed in some places templates are used (usually within a section that starts meta) to create deps. That seemed quite cool, but I couldn't find any obvious documentation for it so I thought i'd simply have a go at it and base what I wanted to do on the code I could already see.

So I started out with something that seemed quite simple (I actually wrote a load of code around this, but have simplified for this example) templating creation of a directory.

So I wrote a dep to do it:

dep 'directory exists', :dirname do
  met? {
    dirname.p.directory?
  }
  meet {
    shell("mkdir #{dirname}")
  }
end

I then tried to wrap that up with the templating stuff:
meta :dir do
  accepts_value_for :dirname

  template {
    requires 'directory exists'.with(:dirname => dirname)
  }
end

So that I could then do something like:
dep 'dot ssh.dir', :user do
  dirname "~#{user}/.ssh"
end

Not the greatest example I know, but as I say, there's actually a whole bunch of other code too.

Anyhow, when I try to run this dep I get this:
# babushka 'dot ssh.dir'
dot ssh.dir {
  user? test
  directory exists {
    dirname?

And i'm failing to understand why dirname doesn't appear to be set.

Am I doing something obviously wrong or totally misunderstanding how this should work? I tried to look at the code, but I have no clue what's going on. I'm not very experienced with writing DSLs in ruby and I failed miserably to work out how this stuff relates to the ruby code.

Any pointers greatly appreciated... or some documentation that i'm totally failing at finding would be even better!

Ben Hoskings

unread,
Jan 26, 2013, 12:25:19 AM1/26/13
to babush...@googlegroups.com
(Sorry about the delay; I've been on holiday and moving house.)

Good find :) This is due to how templating works.

To demonstrate, I ran your deps with some #tapp (tap & print; essentially tap { puts inspect }) in the right places:

    meta :dir do
      accepts_value_for :dirname

      template {
        requires 'directory exists'.with(:dirname => dirname.tapp)
      }
    end

    dep 'dot ssh.dir', :user do
      dirname "~#{user}/.ssh".tapp
    end

This shows that the requirement happens before dirname is set:

    > babushka 'dot ssh.dir' user=ben
    dot ssh.dir {
    test.rb:14:in `block (2 levels) in <top (required)>': nil
    test.rb:19:in `block in <top (required)>': "~ben/.ssh"
      directory exists {
        dirname? 

When a dep is "defined", what's happening is that its outer block is run against the dep's context (which is an instance of DepContext specific to that dep. It's the scope in which things like 'met?', 'meet', 'requires', etc are defined.

When a templated dep is defined, there's an extra step. First, the 'template' block is run against the dep context, to build the shared pieces of that dep. Then the dep's outer block itself is run against the same context, naturally overriding the template. The upshot of this is that the logic within the template can't see things defined within the dep itself, because at the time the template is run the dep hasn't been defined yet.

In this case, the template running first means that the 'directory exists' requirement happens before the 'dirname' setting in the dep. 

I'd structure it like this instead:

    dep 'dir', :dirname do
      met? {
        dirname.p.directory?
      }
      meet {
        shell("mkdir #{dirname}")
      }
    end

    dep 'dot ssh.dir', :user do
      requires 'dir'.with("~#{user}/.ssh")
    end

Of course, that just avoids using a template, and there are cases where you need one (e.g. when you need a custom block). In that case, I'd use a 'setup' block, which means the 'requires' call happens after define time:

    meta :dir do
      accepts_value_for :dirname

      template {
        setup {
          requires 'directory exists'.with(:dirname => dirname.tapp)
        }
      }
    end

Deps define themselves lazily, which in practise means each dep defines itself as it's run; run some deps with '--debug' to see this happen. Using the 'setup' block just means that the 'requires' call happens during runtime itself. Here are docs on the runtime order: https://github.com/benhoskings/babushka/blob/master/lib/babushka/dep.rb#L165-L178

Cheers
Ben




Reply all
Reply to author
Forward
0 new messages