Using with Alki::Loader

Alki::Loader is a library that extends Ruby’s require method. It can be used to associate "builder" objects with files or directories so that the code within them is processed by the builder object when they are loaded. More documentation can be found here.

The DSLs created by Alki::Dsl can be used as Alki::Loader builder objects, allowing DSLs to be used to define classes and modules.

To get started, in your project create a dsls directory at something like lib/my_project/dsls. This will be where we put our DSL source files.

To register it create a lib/alki_loader.rb file:

lib/alki_loader.rb
# Treat all ruby source files in lib/my_project/dsls as DSL definition files
Alki::Loader.register 'my_project/dsls', builder: 'alki/dsls/dsl'

Note: This registers the builder using a string. This is a "load" string and is used frequently in Alki projects. When used, the string will be require-d and then transformed into a constant name (so "alki/dsls/dsl" becomes Alki::Dsls::Dsl) and the resulting class will be used. In addition to less typing, this also allows lazy loading behavior, where the file and class are only loaded if needed.

The DSL class can be passed directly instead of the load string.

Now a DSL definition file can be created in lib/my_project/dsls. Revisiting the previous example, a "strings" dsl file can be created. Because the file has been registered with the 'alki/dsls/dsl' builder, it will be automatically processed as a DSL definition when loaded.

lib/my_project/dsls/strings.rb
Alki do
  init do
    ctx[:strings] = []
  end

  dsl_method :add do |val|
    ctx[:strings] << val
  end

  finish do
    sep = ctx[:separator] || "\n"
    ctx[:result] = ctx[:strings].join(sep)
  end
end

The Alki do …​ end block is part of Alki::Loader and is required. The rest of the DSL is the same as before. When this file is loaded by Ruby, it will create a DSL class called MyProject::Dsls::Strings.

To use we can require the file normally (making sure to add lib to the load path and requiring 'alki/dsl' first).

$ irb -Ilib
> require 'alki/dsl'
> require 'my_project/dsls/strings'
> MyProject::Dsls::Strings.build do
>   add "hello"
>   add "world"
> end
 => "hello\nworld"
>

The second DSL can now be setup the same way. Note that the require_dsl value has been replaced with a load string.

lib/my_project/dsls/transformable_strings.rb
Alki do
  require_dsl 'my_project/dsls/strings'

  init do
    @transform = nil
  end

  dsl_method :transform do |&blk|
    @transform = blk
  end

  finish do
    if @transform
      ctx[:strings].map! &@transform
    end
  end
end

So what if we want to use our DSL with Alki::Loader as well? First, our DSL right now produces a string, but Alki::Loader requires builders to define a constant with the correct name. Alki::Dsl comes with a "class" DSL that makes this easy. First lets create a new DSL that adapts our transformable_strings DSL into a one that defines a module.

lib/my_project/dsls/strings_class.rb
Alki do
  require_dsl 'alki/dsls/class'
  require_dsl 'my_project/dsls/transformable_strings', :after # This makes it's finish hook
                                                              # run before ours

  finish do
    # Helpers provided by alki/dsls/class
    create_as_module # Don't need a class, just a module
    value = ctx[:result]
    add_class_method(:value) { value }
  end
end

Now we can create a new directory, register it with Alki::Loader, and add a file that uses the DSL. Note that we can set separator in the Alki::Loader register call. Any data values set here are passed in as ctx in the DSL.

lib/alki_loader.rb
Alki::Loader.register 'my_project/dsls', builder: 'alki/dsls/dsl'
Alki::Loader.register 'my_project/strings', builder: 'my_project/dsls/strings_class', separator: ', '
lib/my_project/strings/hello_world.rb
Alki do
  transform &:capitalize

  add "hello"
  add "world"
end
$ irb -Ilib
> require 'alki/dsl'
> require 'my_project/strings/hello_world'
> MyProject::Strings::HelloWorld.value
 => "Hello, World"
>

results matching ""

    No results matching ""