Lunfardo
Lunfardo is a framework to easily define simple (and not quite so simple) DSLs. For example it can be used to define an object configuration DSL.
Status
Installation
Add this line to your application's Gemfile:
gem 'lunfardo', '~> 0.1'
And then execute:
$ bundle
Or install it yourself as:
$ gem install lunfardo
Usage
Suppose you have the class
class SomeClass
def self.filename()
@filename
end
def self.set_filename(filename)
@filename = filename
end
end
and you would like to make it configurable:
SomeClass.configure do
filename 'some_file.txt'
end
To do that, define the DSL:
include 'lunfardo'
class SomeClassConfigurationDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filename do |name|
perform do |name|
context.set_filename(name)
end
end
end
and then add the configure
method to SomeClass
:
class SomeClass
def self.filename()
@filename
end
def self.set_filename(filename)
@filename = filename
end
def self.configure(&block)
SomeClassConfigurationDSL.evaluate_on(self, &block)
end
end
and that's it. This is the simpliest use, but it can be used for more complex DSLs too.
API
Accessing the context from the DSL
You can access the context from the DSL sending context
:
include 'lunfardo'
class SomeClassConfigurationDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filename do |name|
perform do |name|
context.files << name
end
end
end
# to be used like
SomeClass.configure do
filename 'file_1.txt'
filename 'file_2.txt'
end
Evaluating code at the begining of a configuration scope
You can evaluate code before evaluating the rest of the configuration:
include 'lunfardo'
class SomeClassConfigurationDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filenames do
before do
context.files = []
end
on :filename do |name|
perform do |name|
context.files << name
end
end
end
end
# to be used like
SomeClass.configure do
filenames do
filename 'file_1.txt'
filename 'file_2.txt'
end
end
Evaluating code at the end of a configuration scope
You can evaluate code after evaluating the rest of the configuration:
include 'lunfardo'
class SomeClassConfigurationDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filenames do
attr_reader :files
before do
@files = []
end
on :filename do |name|
perform do |name|
outer.files << name
end
end
after do
context.files = @files.join(', ')
end
end
end
# to be used like
SomeClass.configure do
filenames do
filename 'file_1.txt'
filename 'file_2.txt'
end
end
The #after
scope will also return its evaluation:
include 'lunfardo'
class BloatedJoinDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filenames do
attr_reader :files
before do
@files = []
end
on :filename do |name|
perform do |name|
outer.files << name
end
end
after do
@files.join(', ')
end
end
end
# to be used like
files = BloatedJoinDSL.evaluate do
filenames do
filename 'file_1.txt'
filename 'file_2.txt'
end
end
files == ['file_1.txt', 'file_2.txt']
Defining instance variables and methods in a configuration scope
In the previous example you can see that you can use instance variables and methods inside a configuration scope.
You can also define any method using define
:
include 'lunfardo'
class BloatedJoinDSL
include CabezaDeTermo::Lunfardo::Behaviour
on :filenames do
attr_reader :files
before do
@files = []
end
on :filename do |name|
define :extension do
'.txt'.freeze
end
perform do |name|
outer.files << name + extension
end
end
after do
@files.join(', ')
end
end
end
# to be used like
files = BloatedJoinDSL.evaluate do
filenames do
filename 'file_1'
filename 'file_2'
end
end
files == ['file_1.txt', 'file_2.txt']
Accessing the outer scope of a configuration scope
If you need to access the outer scope of your current scope, send outer
on :filename do |name|
perform do |name|
outer.files << name
end
end
or if you need to reach an outer scope several layers above
on :filename do |name|
perform do |name|
outer(3).files << name
end
end
Handling a block instead of evaluating it
Sometimes you won't want to evaluate a block but to handle it in a custom way.
In that case, you must declare in you scope definition (at the #on
message) that you expect the &block
parameter:
class ValidationsMessageLibraryDSL
include CabezaDeTermo::Lunfardo::Behaviour
dsl do
attr_reader :messages
before do
@messages = Hash[]
end
on :message_for do |validation_name, &|
perform do |validation_name, &|
outer.[validation_name] =
end
end
after do
@messages
end
end
end
# to be used like
= ValidationsMessageLibraryDSL.evaluate do
:presence do |validation|
"can not be left blank"
end
:format do |validation|
"doesn't match expected format: #{validation.expected_value}"
end
end
Defining recursive configurations
You can define a configuration using recursion like in the example:
class ParamsDSL
include CabezaDeTermo::Lunfardo::Behaviour
dsl do
on :params do
attr_reader :params
before do
@params = Hash[]
end
on :param do
attr_reader :name
attr_reader :params
before do |name, value = nil|
@name = name
@params = outer.params
end
perform do |name, value = nil|
@params[name] = value.nil? ? Hash[] : value
end
on :param do
attr_reader :name
attr_reader :params
before do |name, value = nil|
@name = name
@params = outer.params[outer.name]
end
perform do |name, value = nil|
@params[name] = value.nil? ? Hash[] : value
end
recursive :param, scope: self
end
end
after do
@params
end
end
end
end
# to be used like
params = ParamsDSL.evaluate do
params do
param :client do
param :name, 'Juan Salvo'
param :address do
param :city, 'Buenos Aires'
end
end
end
end
params == {:client => {:name=>"Juan Salvo ", :address=>{:city=>"Buenos Aires"}}}
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/cabeza-de-termo/lunfardo.
License
The gem is available as open source under the terms of the MIT License.