Summary
Why shouldn't a DSL also have its own DSL?
The domain of DSL's involves the concepts of "evaluating some DSL", and then "producing some objects and/or code from it".
So, I've created a class that you can subclass called "Evaluator". The "Evaluator" class imbues whatever inherits from it with the power to create DSL evaluators!
The DSL evaluator will have a class method called "eval" which evaluates your DSL, then returns a nested object that has all the accessors that you like.
Example DSL Evaluator
Here's an example DSL DSL for the config file.
config_dsl.rb
1 class ConfigEvaluator < DSL::Evaluator 2 3 property :nick, String # 'String' type specification is optional 4 property :realname, String 5 6 property :botpassword, String 7 8 scope :network do 9 inherit :nick, :realname 10 11 # 'initialize' is called when this scope is created: 12 # network "freenode" { ... } 13 # It would be called with "freenode" as an argument. 14 initialize do |network_name| 15 @network_name = network_name.to_sym 16 @channels = [] 17 @servers = [] 18 end 19 20 ## Methods that are available inside the network scope. 21 22 def server(*args) 23 @servers << args 24 end 25 26 def channel(*args) 27 @channels << args 28 end 29 30 def channels(*args) 31 args.each{ |arg| @channels << arg } 32 end 33 34 end 35 36 end
Example config file
~/.arrbot/config
1 nick "misterbot" 2 realname "Mr. Bot" 3 username "mrbot" 4 5 botpassword "sSeEEeCrrEEttSSssS" 6 7 network "freenode" do 8 server "irc.freenode.net" 9 server "irc.freenode.net", 6668 10 server "somedude.freenode.net" 11 12 channel "#hax" 13 channel "#woot" 14 channels "#awesome", "#power" 15 16 nick "mrbot_" 17 18 on :connect do 19 msg "nickserv", "identify SeEeECRreEEtttSSss" 20 end 21 end 22 23 network "efnet" do 24 server "irc.ef.net" 25 server "irc.efnet.org" 26 server "irc.efn.et" 27 28 10.times do |n| 29 channel "#warez#{n}" 30 end 31 32 channel "#oldwarez" 33 end
To evaluate this code in the evaluator, do:
config = ConfigEvaluator.eval_file('~/.arrbot/config')
- or
config = ConfigEvaluator.eval(&block)
The returned object, 'config', will be an object with accessors for all the "properties" that the DSL was designed to capture. All the methods used to generate those properties in the evaluator get removed, so all you're left with is the produced data.
Questions
1) What would be the best way to define methods that will exist on the "generated" objects?
Possibility: a codeblock
Other possible names:
- generate_methods
- attach_methods
2) How would we allow the user to configure "on someting" events? Like, "on connect", or "on ctcp"?
Possibility: Expose the chat API
Then, the block assigned to each handler would be evaluated in the scope of the IRC client module, so that it could send messages or whatever.
3) Since this bot will be really fast and efficient, and have really good logging facilities, people will probably want it to join a lot of channels.
If that's the case, then they'll need to restart the bot every time it becomes part of a new channel, since our config file is static. It would be better if the bot master could tell the bot to join a channel, and the config file could be updated automatically.
Could we make the generated object reversible? eg:
config.write("~/.arrbot/config")
Note: It must retain the order of the commands in the original config file. Perhaps it could intelligently group together things like servers and channels?
