Class: Autumn::Leaf
- Inherits:
-
Object
- Object
- Autumn::Leaf
- Includes:
- Anise::Annotation
- Defined in:
- lib/autumn/leaf.rb
Overview
This is the superclass that all Autumn leaves use. To write a leaf, sublcass this class and implement methods for each of your leaf’s commands. Your leaf’s repertoire of commands is derived from the names of the methods you write. For instance, to have your leaf respond to a “!hello” command in IRC, write a method like so:
def hello_command(stem, sender, reply_to, msg)
stem. "Why hello there!", reply_to
end
You can also implement this method as:
def hello_command(stem, sender, reply_to, msg)
return "Why hello there!"
end
Methods of the form [word]_command
tell the leaf to respond to commands in IRC of the form “![word]”. They should accept four parameters:
-
the Stem that received the message,
-
the sender hash for the person who sent the message (see below),
-
the “reply-to” string (either the name of the channel that the command was typed on, or the nick of the person that whispered the message), and
-
any text following the command. For instance, if the person typed “!eat A tasty slice of pizza”, the last parameter would be “A tasty slice of pizza”. This is nil if no text was supplied with the command.
Sender hashes: A “sender hash” is a hash with the following keys: nick
(the user’s nickname), user
(the user’s username), and host
(the user’s hostname). Any of these fields except nick
could be nil. Sender hashes are used throughout the Stem and Leaf classes, as well as other classes; they always have the same keys.
If your *_command
method returns a string, it will be sent as an IRC message to “reply-to” parameter.If your leaf needs to respond to more complicated commands, you will have to override the did_receive_channel_message method (see below). If you like, you can remove the quit_command method in your subclass, for instance, to prevent the leaf from responding to !quit. You can also protect that method using filters (see “Filters”).
If you want to separate view logic from the controller, you can use ERb to template your views. See the render method for more information.
Hook Methods
Aside from adding your own *_command
-type methods, you should investigate overriding the “hook” methods, such as will_start_up, did_start_up, did_receive_private_message, did_receive_channel_message, etc. There’s a laundry list of so-named methods you can override. Their default implementations do nothing, so there’s no need to call super
.
Stem Convenience Methods
Most of the IRC actions (such as joining and leaving a channel, setting a topic, etc.) are part of a Stem object. If your leaf is only running off of one stem, you can call these stem methods directly, as if they were methods in the Leaf class. Otherwise, you will need to specify which stem to perform these IRC actions on. Usually, the stem is given to you, as a parameter for your *_command
method, for instance.
For the sake of convenience, you can make Stem method calls on the stems
attribute; these calls will be forwarded to every stem in the stems
attribute. For instance, to broadcast a message to all servers and all channels:
stems. "Ready for orders!"
Filters
Like Ruby on Rails, you can add filters to each of your commands to be executed before or after the command is run. You can do this using the before_filter and after_filter methods, just like in Rails. Filters are run in the order they are added to the chain. Thus, if you wanted to run your preload filter before you ran your cache filter, you’d write the calls in this order:
class MyLeaf < Leaf
before_filter :my_preload
before_filter :my_cache
end
See the documentation for the before_filter and after_filter methods and the README file for more information on filters.
Authentication
If a leaf is initialized with a hash for the authentication
option, the values of that hash are used to choose an authenticator that will be run before each command. This authenticator will determine whether or not the user can run that command. The options that can be specified in this hash are:
type
-
The name of a class in the Autumn::Authentication module, in snake_case. Thus, if you wanted to use the Autumn::Authentication::Password class, which does password-based authentication, you’d set this value to
password
. only
-
A list of protected commands for which authentication is required; all other commands are unprotected.
except
-
A list of unprotected commands; all other commands require authentication.
silent
-
Normally, when someone fails to authenticate himself before running a protected command, the leaf responds with an error message (e.g., “You have to authenticate with a password first”). Set this to true to suppress this behaivor.
In addition, you can also specify any custom options for your authenticator. These options are passed to the authenticator’s initialize method. See the classes in the Autumn::Authentication module for such options.
If you annotate a command method as protected, the authenticator will be run unconditionally, regardless of the only
or except
options:
class Controller < Autumn::Leaf
def destructive_command(stem, sender, reply_to, msg)
# ...
end
ann :destructive_command, :protected => true
end
Logging
Autumn comes with a framework for logging as well. It’s very similar to the Ruby on Rails logging framework. To log an error message:
logger.error "Quiz data is missing!"
By default the logger will only log info
events and above in production seasons, and will log all messages for debug seasons. (See the README for more on seasons.) To customize the logger, and for more information on logging, see the LogFacade class documentation.
Colorizing and Formatting Text
The Autumn::Formatting module contains sub-modules which handle formatting for different clients (such as mIRC-style formatting, the most common). The specific formatting module that’s included depends on the leaf’s initialization options; see initialize.
Direct Known Subclasses
Constant Summary collapse
- DEFAULT_COMMAND_PREFIX =
Default for the
command_prefix
init option. '!'
- @@view_alias =
Hash.new { |h,k| k }
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
The LogFacade instance for this leaf.
-
#options ⇒ Object
readonly
The configuration for this leaf.
-
#stems ⇒ Object
readonly
The Stem instances running this leaf.
Instance Method Summary collapse
-
#database(dbname = nil, &block) ⇒ Object
Performs the block in the context of a database, referenced by symbol.
-
#database_name ⇒ Object
Trues to guess the name of the database connection this leaf is using.
-
#initialize(opts = {}) ⇒ Leaf
constructor
Instantiates a leaf.
-
#inspect ⇒ Object
:nodoc:.
-
#irc_invite_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_join_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_kick_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_mode_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_nick_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_notice_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_part_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_privmsg_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_quit_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#irc_topic_event(stem, sender, arguments) ⇒ Object
:nodoc:.
-
#method_missing(meth, *args) ⇒ Object
Simplifies method calls for one-stem leaves.
-
#preconfigure ⇒ Object
:nodoc:.
-
#stem_ready(stem) ⇒ Object
METHODS INVOKED BY STEM #########################.
-
#will_start_up ⇒ Object
Invoked just before the leaf starts up.
Constructor Details
#initialize(opts = {}) ⇒ Leaf
Instantiates a leaf. This is generally handled by the Foliater class. Valid options are:
command_prefix
-
The string that must precede all command names (default “!”)
responds_to_private_messages
-
If true, the bot responds to known commands sent in private messages.
logger
-
The LogFacade instance for this leaf.
database
-
The name of a custom database connection to use.
formatter
-
The name of an Autumn::Formatting class to use as the formatter (chooses Autumn::Formatting::DEFAULT by default).
As well as any user-defined options you want.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/autumn/leaf.rb', line 178 def initialize(opts={}) @port = opts[:port] @options = opts @options[:command_prefix] ||= DEFAULT_COMMAND_PREFIX @break_flag = false @logger = [:logger] @stems = Set.new # Let the stems array respond to methods as if it were a single stem class << @stems def method_missing(meth, *args) if all? { |stem| stem.respond_to? meth } then collect { |stem| stem.send(meth, *args) } else super end end end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args) ⇒ Object
Simplifies method calls for one-stem leaves.
207 208 209 210 211 212 213 |
# File 'lib/autumn/leaf.rb', line 207 def method_missing(meth, *args) # :nodoc: if stems.size == 1 and stems.only.respond_to? meth then stems.only.send meth, *args else super end end |
Instance Attribute Details
#logger ⇒ Object (readonly)
The LogFacade instance for this leaf.
158 159 160 |
# File 'lib/autumn/leaf.rb', line 158 def logger @logger end |
#options ⇒ Object (readonly)
The configuration for this leaf.
162 163 164 |
# File 'lib/autumn/leaf.rb', line 162 def @options end |
#stems ⇒ Object (readonly)
The Stem instances running this leaf.
160 161 162 |
# File 'lib/autumn/leaf.rb', line 160 def stems @stems end |
Instance Method Details
#database(dbname = nil, &block) ⇒ Object
Performs the block in the context of a database, referenced by symbol. For instance, if you had defined in database.yml a connection named “scorekeeper”, you could access that connection like so:
database(:scorekeeper) do
[...]
end
If your database is named after your leaf (as in the example above for a leaf named “Scorekeeper”), it will automatically be set as the database context for the scope of all hook, filter and command methods. However, if your database connection is named differently, or if you are working in a method not invoked by the Leaf class, you will need to set the connection using this method.
If you omit the dbname
parameter, it will try to guess the name of your database connection using the leaf’s name and the leaf’s class name.
If the database connection cannot be found, the block is executed with no database scope.
317 318 319 320 321 322 323 324 |
# File 'lib/autumn/leaf.rb', line 317 def database(dbname=nil, &block) dbname ||= database_name if dbname then repository dbname, &block else yield end end |
#database_name ⇒ Object
Trues to guess the name of the database connection this leaf is using. Looks for database connections named after either this leaf’s identifier or this leaf’s class name. Returns nil if no suitable connection is found.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/autumn/leaf.rb', line 330 def database_name # :nodoc: return nil unless Module.constants.include? 'DataMapper' or Module.constants.include? :DataMapper raise "No such database connection #{[:database]}" if [:database] and DataMapper::Repository.adapters[[:database]].nil? # Custom database connection specified return [:database].to_sym if [:database] # Leaf config name return leaf_name.to_sym if DataMapper::Repository.adapters[leaf_name.to_sym] # Leaf config name, underscored return leaf_name.methodize.to_sym if DataMapper::Repository.adapters[leaf_name.methodize.to_sym] # Leaf class name return self.class.to_s.to_sym if DataMapper::Repository.adapters[self.class.to_s.to_sym] # Leaf class name, underscored return self.class.to_s.methodize.to_sym if DataMapper::Repository.adapters[self.class.to_s.methodize.to_sym] # I give up return nil end |
#inspect ⇒ Object
:nodoc:
347 348 349 |
# File 'lib/autumn/leaf.rb', line 347 def inspect # :nodoc: "#<#{self.class.to_s} #{leaf_name}>" end |
#irc_invite_event(stem, sender, arguments) ⇒ Object
:nodoc:
261 262 263 |
# File 'lib/autumn/leaf.rb', line 261 def irc_invite_event(stem, sender, arguments) # :nodoc: database { someone_did_invite stem, sender, arguments[:recipient], arguments[:channel] } end |
#irc_join_event(stem, sender, arguments) ⇒ Object
:nodoc:
234 235 236 |
# File 'lib/autumn/leaf.rb', line 234 def irc_join_event(stem, sender, arguments) # :nodoc: database { someone_did_join_channel stem, sender, arguments[:channel] } end |
#irc_kick_event(stem, sender, arguments) ⇒ Object
:nodoc:
265 266 267 |
# File 'lib/autumn/leaf.rb', line 265 def irc_kick_event(stem, sender, arguments) # :nodoc: database { someone_did_kick stem, sender, arguments[:channel], arguments[:recipient], arguments[:message] } end |
#irc_mode_event(stem, sender, arguments) ⇒ Object
:nodoc:
242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/autumn/leaf.rb', line 242 def irc_mode_event(stem, sender, arguments) # :nodoc: database do if arguments[:recipient] then gained_usermodes(stem, arguments[:mode]) { |prop| someone_did_gain_usermode stem, arguments[:recipient], prop, arguments[:parameter], sender } lost_usermodes(stem, arguments[:mode]) { |prop| someone_did_lose_usermode stem, arguments[:recipient], prop, arguments[:parameter], sender } elsif arguments[:parameter] and stem.server_type.privilege_mode?(arguments[:mode]) then gained_privileges(stem, arguments[:mode]) { |prop| someone_did_gain_privilege stem, arguments[:channel], arguments[:parameter], prop, sender } lost_privileges(stem, arguments[:mode]) { |prop| someone_did_lose_privilege stem, arguments[:channel], arguments[:parameter], prop, sender } else gained_properties(stem, arguments[:mode]) { |prop| channel_did_gain_property stem, arguments[:channel], prop, arguments[:parameter], sender } lost_properties(stem, arguments[:mode]) { |prop| channel_did_lose_property stem, arguments[:channel], prop, arguments[:parameter], sender } end end end |
#irc_nick_event(stem, sender, arguments) ⇒ Object
:nodoc:
279 280 281 |
# File 'lib/autumn/leaf.rb', line 279 def irc_nick_event(stem, sender, arguments) # :nodoc: database { nick_did_change stem, sender, arguments[:nick] } end |
#irc_notice_event(stem, sender, arguments) ⇒ Object
:nodoc:
269 270 271 272 273 274 275 276 277 |
# File 'lib/autumn/leaf.rb', line 269 def irc_notice_event(stem, sender, arguments) # :nodoc: database do if arguments[:recipient] then did_receive_notice stem, sender, arguments[:recipient], arguments[:message] else did_receive_notice stem, sender, arguments[:channel], arguments[:message] end end end |
#irc_part_event(stem, sender, arguments) ⇒ Object
:nodoc:
238 239 240 |
# File 'lib/autumn/leaf.rb', line 238 def irc_part_event(stem, sender, arguments) # :nodoc: database { someone_did_leave_channel stem, sender, arguments[:channel] } end |
#irc_privmsg_event(stem, sender, arguments) ⇒ Object
:nodoc:
222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/autumn/leaf.rb', line 222 def irc_privmsg_event(stem, sender, arguments) # :nodoc: database do if arguments[:channel] then command_parse stem, sender, arguments stem, sender, arguments[:channel], arguments[:message] else command_parse stem, sender, arguments if [:respond_to_private_messages] stem, sender, arguments[:message] end end end |
#irc_quit_event(stem, sender, arguments) ⇒ Object
:nodoc:
283 284 285 |
# File 'lib/autumn/leaf.rb', line 283 def irc_quit_event(stem, sender, arguments) # :nodoc: database { someone_did_quit stem, sender, arguments[:message] } end |
#irc_topic_event(stem, sender, arguments) ⇒ Object
:nodoc:
257 258 259 |
# File 'lib/autumn/leaf.rb', line 257 def irc_topic_event(stem, sender, arguments) # :nodoc: database { someone_did_change_topic stem, sender, arguments[:channel], arguments[:topic] } end |
#preconfigure ⇒ Object
:nodoc:
198 199 200 201 202 203 |
# File 'lib/autumn/leaf.rb', line 198 def preconfigure # :nodoc: if [:authentication] then @authenticator = Autumn::Authentication.const_get([:authentication]['type'].camelcase).new([:authentication].rekey(&:to_sym)) stems.add_listener @authenticator end end |
#stem_ready(stem) ⇒ Object
METHODS INVOKED BY STEM #########################
217 218 219 220 |
# File 'lib/autumn/leaf.rb', line 217 def stem_ready(stem) # :nodoc: return unless Thread.exclusive { stems.ready?.all? } database { startup_check } end |
#will_start_up ⇒ Object
Invoked just before the leaf starts up. Override this method to do any pre-startup tasks you need. The leaf is fully initialized and all methods and helper objects are available.
293 294 |
# File 'lib/autumn/leaf.rb', line 293 def will_start_up end |