autumn 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +11 -0
- data/CHANGELOG +567 -0
- data/MANIFEST +110 -0
- data/README +1114 -0
- data/README.textile +1153 -0
- data/Rakefile +75 -0
- data/autumn.gemspec +44 -0
- data/bin/autumn +11 -0
- data/lib/autumn.rb +8 -0
- data/lib/autumn/authentication.rb +238 -0
- data/lib/autumn/channel_leaf.rb +107 -0
- data/lib/autumn/coder.rb +166 -0
- data/lib/autumn/console_boot.rb +10 -0
- data/lib/autumn/ctcp.rb +250 -0
- data/lib/autumn/daemon.rb +207 -0
- data/lib/autumn/datamapper_hacks.rb +290 -0
- data/lib/autumn/foliater.rb +231 -0
- data/lib/autumn/formatting.rb +236 -0
- data/lib/autumn/generator.rb +231 -0
- data/lib/autumn/genesis.rb +190 -0
- data/lib/autumn/inheritable_attributes.rb +162 -0
- data/lib/autumn/leaf.rb +738 -0
- data/lib/autumn/log_facade.rb +49 -0
- data/lib/autumn/misc.rb +87 -0
- data/lib/autumn/resources/daemons/Anothernet.yml +3 -0
- data/lib/autumn/resources/daemons/AustHex.yml +29 -0
- data/lib/autumn/resources/daemons/Bahamut.yml +67 -0
- data/lib/autumn/resources/daemons/Dancer.yml +3 -0
- data/lib/autumn/resources/daemons/GameSurge.yml +3 -0
- data/lib/autumn/resources/daemons/IRCnet.yml +3 -0
- data/lib/autumn/resources/daemons/Ithildin.yml +7 -0
- data/lib/autumn/resources/daemons/KineIRCd.yml +56 -0
- data/lib/autumn/resources/daemons/PTlink.yml +6 -0
- data/lib/autumn/resources/daemons/QuakeNet.yml +20 -0
- data/lib/autumn/resources/daemons/RFC1459.yml +158 -0
- data/lib/autumn/resources/daemons/RFC2811.yml +16 -0
- data/lib/autumn/resources/daemons/RFC2812.yml +36 -0
- data/lib/autumn/resources/daemons/RatBox.yml +25 -0
- data/lib/autumn/resources/daemons/Ultimate.yml +24 -0
- data/lib/autumn/resources/daemons/Undernet.yml +6 -0
- data/lib/autumn/resources/daemons/Unreal.yml +110 -0
- data/lib/autumn/resources/daemons/_Other.yml +7 -0
- data/lib/autumn/resources/daemons/aircd.yml +33 -0
- data/lib/autumn/resources/daemons/bdq-ircd.yml +3 -0
- data/lib/autumn/resources/daemons/hybrid.yml +38 -0
- data/lib/autumn/resources/daemons/ircu.yml +67 -0
- data/lib/autumn/resources/daemons/tr-ircd.yml +8 -0
- data/lib/autumn/script.rb +74 -0
- data/lib/autumn/speciator.rb +165 -0
- data/lib/autumn/stem.rb +919 -0
- data/lib/autumn/stem_facade.rb +176 -0
- data/lib/autumn/tool/bin.rb +301 -0
- data/lib/autumn/tool/create.rb +48 -0
- data/lib/autumn/tool/project_creator.rb +110 -0
- data/lib/autumn/version.rb +3 -0
- data/lib/skel/Rakefile +163 -0
- data/lib/skel/config/global.yml +2 -0
- data/lib/skel/config/seasons/testing/database.yml +4 -0
- data/lib/skel/config/seasons/testing/leaves.yml +9 -0
- data/lib/skel/config/seasons/testing/season.yml +2 -0
- data/lib/skel/config/seasons/testing/stems.yml +10 -0
- data/lib/skel/leaves/administrator/README +20 -0
- data/lib/skel/leaves/administrator/controller.rb +67 -0
- data/lib/skel/leaves/administrator/views/autumn.txt.erb +1 -0
- data/lib/skel/leaves/administrator/views/reload.txt.erb +11 -0
- data/lib/skel/leaves/insulter/README +17 -0
- data/lib/skel/leaves/insulter/controller.rb +65 -0
- data/lib/skel/leaves/insulter/views/about.txt.erb +1 -0
- data/lib/skel/leaves/insulter/views/help.txt.erb +1 -0
- data/lib/skel/leaves/insulter/views/insult.txt.erb +1 -0
- data/lib/skel/leaves/scorekeeper/README +34 -0
- data/lib/skel/leaves/scorekeeper/config.yml +2 -0
- data/lib/skel/leaves/scorekeeper/controller.rb +104 -0
- data/lib/skel/leaves/scorekeeper/helpers/general.rb +64 -0
- data/lib/skel/leaves/scorekeeper/models/channel.rb +12 -0
- data/lib/skel/leaves/scorekeeper/models/person.rb +14 -0
- data/lib/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
- data/lib/skel/leaves/scorekeeper/models/score.rb +14 -0
- data/lib/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
- data/lib/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
- data/lib/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
- data/lib/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
- data/lib/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
- data/lib/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
- data/lib/skel/log/README +1 -0
- data/lib/skel/script/console +28 -0
- data/lib/skel/script/destroy +48 -0
- data/lib/skel/script/generate +48 -0
- data/lib/skel/shared/README +1 -0
- data/lib/skel/tmp/README +1 -0
- data/spec/authentication_spec.rb +328 -0
- data/spec/channel_leaf_spec.rb +142 -0
- data/spec/coder_spec.rb +146 -0
- data/spec/ctcp_spec.rb +222 -0
- data/spec/daemon_spec.rb +202 -0
- data/spec/datamapper_hacks_spec.rb +164 -0
- data/tasks/authors.rake +30 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/copyright.rake +21 -0
- data/tasks/doc.rake +7 -0
- data/tasks/gem.rake +23 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/install_dependencies.rake +6 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +23 -0
- data/tasks/release.rake +52 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/setup.rake +24 -0
- data/tasks/spec.rake +7 -0
- data/tasks/yard.rake +4 -0
- metadata +188 -0
data/MANIFEST
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
AUTHORS
|
2
|
+
CHANGELOG
|
3
|
+
MANIFEST
|
4
|
+
README
|
5
|
+
README.textile
|
6
|
+
Rakefile
|
7
|
+
autumn.gemspec
|
8
|
+
bin/autumn
|
9
|
+
lib/autumn.rb
|
10
|
+
lib/autumn/authentication.rb
|
11
|
+
lib/autumn/channel_leaf.rb
|
12
|
+
lib/autumn/coder.rb
|
13
|
+
lib/autumn/console_boot.rb
|
14
|
+
lib/autumn/ctcp.rb
|
15
|
+
lib/autumn/daemon.rb
|
16
|
+
lib/autumn/datamapper_hacks.rb
|
17
|
+
lib/autumn/foliater.rb
|
18
|
+
lib/autumn/formatting.rb
|
19
|
+
lib/autumn/generator.rb
|
20
|
+
lib/autumn/genesis.rb
|
21
|
+
lib/autumn/inheritable_attributes.rb
|
22
|
+
lib/autumn/leaf.rb
|
23
|
+
lib/autumn/log_facade.rb
|
24
|
+
lib/autumn/misc.rb
|
25
|
+
lib/autumn/resources/daemons/Anothernet.yml
|
26
|
+
lib/autumn/resources/daemons/AustHex.yml
|
27
|
+
lib/autumn/resources/daemons/Bahamut.yml
|
28
|
+
lib/autumn/resources/daemons/Dancer.yml
|
29
|
+
lib/autumn/resources/daemons/GameSurge.yml
|
30
|
+
lib/autumn/resources/daemons/IRCnet.yml
|
31
|
+
lib/autumn/resources/daemons/Ithildin.yml
|
32
|
+
lib/autumn/resources/daemons/KineIRCd.yml
|
33
|
+
lib/autumn/resources/daemons/PTlink.yml
|
34
|
+
lib/autumn/resources/daemons/QuakeNet.yml
|
35
|
+
lib/autumn/resources/daemons/RFC1459.yml
|
36
|
+
lib/autumn/resources/daemons/RFC2811.yml
|
37
|
+
lib/autumn/resources/daemons/RFC2812.yml
|
38
|
+
lib/autumn/resources/daemons/RatBox.yml
|
39
|
+
lib/autumn/resources/daemons/Ultimate.yml
|
40
|
+
lib/autumn/resources/daemons/Undernet.yml
|
41
|
+
lib/autumn/resources/daemons/Unreal.yml
|
42
|
+
lib/autumn/resources/daemons/_Other.yml
|
43
|
+
lib/autumn/resources/daemons/aircd.yml
|
44
|
+
lib/autumn/resources/daemons/bdq-ircd.yml
|
45
|
+
lib/autumn/resources/daemons/hybrid.yml
|
46
|
+
lib/autumn/resources/daemons/ircu.yml
|
47
|
+
lib/autumn/resources/daemons/tr-ircd.yml
|
48
|
+
lib/autumn/script.rb
|
49
|
+
lib/autumn/speciator.rb
|
50
|
+
lib/autumn/stem.rb
|
51
|
+
lib/autumn/stem_facade.rb
|
52
|
+
lib/autumn/tool/bin.rb
|
53
|
+
lib/autumn/tool/create.rb
|
54
|
+
lib/autumn/tool/project_creator.rb
|
55
|
+
lib/autumn/version.rb
|
56
|
+
lib/skel/Rakefile
|
57
|
+
lib/skel/config/global.yml
|
58
|
+
lib/skel/config/seasons/testing/database.yml
|
59
|
+
lib/skel/config/seasons/testing/leaves.yml
|
60
|
+
lib/skel/config/seasons/testing/season.yml
|
61
|
+
lib/skel/config/seasons/testing/stems.yml
|
62
|
+
lib/skel/leaves/administrator/README
|
63
|
+
lib/skel/leaves/administrator/controller.rb
|
64
|
+
lib/skel/leaves/administrator/views/autumn.txt.erb
|
65
|
+
lib/skel/leaves/administrator/views/reload.txt.erb
|
66
|
+
lib/skel/leaves/insulter/README
|
67
|
+
lib/skel/leaves/insulter/controller.rb
|
68
|
+
lib/skel/leaves/insulter/views/about.txt.erb
|
69
|
+
lib/skel/leaves/insulter/views/help.txt.erb
|
70
|
+
lib/skel/leaves/insulter/views/insult.txt.erb
|
71
|
+
lib/skel/leaves/scorekeeper/README
|
72
|
+
lib/skel/leaves/scorekeeper/config.yml
|
73
|
+
lib/skel/leaves/scorekeeper/controller.rb
|
74
|
+
lib/skel/leaves/scorekeeper/helpers/general.rb
|
75
|
+
lib/skel/leaves/scorekeeper/models/channel.rb
|
76
|
+
lib/skel/leaves/scorekeeper/models/person.rb
|
77
|
+
lib/skel/leaves/scorekeeper/models/pseudonym.rb
|
78
|
+
lib/skel/leaves/scorekeeper/models/score.rb
|
79
|
+
lib/skel/leaves/scorekeeper/tasks/stats.rake
|
80
|
+
lib/skel/leaves/scorekeeper/views/about.txt.erb
|
81
|
+
lib/skel/leaves/scorekeeper/views/change.txt.erb
|
82
|
+
lib/skel/leaves/scorekeeper/views/history.txt.erb
|
83
|
+
lib/skel/leaves/scorekeeper/views/points.txt.erb
|
84
|
+
lib/skel/leaves/scorekeeper/views/usage.txt.erb
|
85
|
+
lib/skel/log/README
|
86
|
+
lib/skel/script/console
|
87
|
+
lib/skel/script/destroy
|
88
|
+
lib/skel/script/generate
|
89
|
+
lib/skel/shared/README
|
90
|
+
lib/skel/tmp/README
|
91
|
+
spec/authentication_spec.rb
|
92
|
+
spec/channel_leaf_spec.rb
|
93
|
+
spec/coder_spec.rb
|
94
|
+
spec/ctcp_spec.rb
|
95
|
+
spec/daemon_spec.rb
|
96
|
+
spec/datamapper_hacks_spec.rb
|
97
|
+
tasks/authors.rake
|
98
|
+
tasks/changelog.rake
|
99
|
+
tasks/copyright.rake
|
100
|
+
tasks/doc.rake
|
101
|
+
tasks/gem.rake
|
102
|
+
tasks/gem_installer.rake
|
103
|
+
tasks/install_dependencies.rake
|
104
|
+
tasks/manifest.rake
|
105
|
+
tasks/rcov.rake
|
106
|
+
tasks/release.rake
|
107
|
+
tasks/reversion.rake
|
108
|
+
tasks/setup.rake
|
109
|
+
tasks/spec.rake
|
110
|
+
tasks/yard.rake
|
data/README
ADDED
@@ -0,0 +1,1114 @@
|
|
1
|
+
= Autumn: A Ruby IRC Bot Framework
|
2
|
+
|
3
|
+
<b>Version 3.1.5 (May 3, 2009)</b>
|
4
|
+
|
5
|
+
Author:: Tim Morgan (mailto:riscfuture@gmail.com)
|
6
|
+
Copyright:: Copyright (c)2007-2009 Tim Morgan
|
7
|
+
License:: Distributed under the same terms as Ruby. Portions of this code are
|
8
|
+
copyright (c)2004 David Heinemeier Hansson; please see
|
9
|
+
libs/inheritable_attributes.rb for more information.
|
10
|
+
|
11
|
+
Autumn is a full-featured framework on top of which IRC bots (called "leaves")
|
12
|
+
can be quickly and easily built. It features a very Ruby-like approach to
|
13
|
+
bot-writing, a complete framework for loading and daemonizing your bots,
|
14
|
+
multiple environment contexts, a database-backed model, and painless logging
|
15
|
+
support.
|
16
|
+
|
17
|
+
== Requirements
|
18
|
+
|
19
|
+
Autumn requires RubyGems (http://www.rubygems.org) and the Daemons and Facets*
|
20
|
+
gems, as well as some of the gems spun off from Facets. Install RubyGems then
|
21
|
+
run <tt>sudo gem install daemons facets anise english</tt> in a command line in
|
22
|
+
order to run Autumn.
|
23
|
+
|
24
|
+
If you wish to use a database backend for your bot, you will need the DataMapper
|
25
|
+
gem. To install, see the DataMapper website (http://www.datamapper.org).
|
26
|
+
|
27
|
+
The included example bot Scorekeeper requires the DataMapper gem. It can
|
28
|
+
optionally use the Chronic gem to enhance its textual date parsing. The other
|
29
|
+
example bot, Insulter, is much simpler and can run under any Autumn
|
30
|
+
configuration.
|
31
|
+
|
32
|
+
== Directory Structure
|
33
|
+
|
34
|
+
An Autumn installation is like a Ruby on Rails installation: There is a
|
35
|
+
certain directory structure where your files go. A lot of files and folders will
|
36
|
+
seem confusing to people who have never used Autumn before, but bear with me. In
|
37
|
+
a bit I will explain in detail what all of this stuff is. For now, here is an
|
38
|
+
overview you can consult for future reference:
|
39
|
+
|
40
|
+
* <b>config/</b> - Configuration files and season definitions
|
41
|
+
* global.yml - Universal settings that apply to every season
|
42
|
+
* <b>seasons/</b> - Contains directories for each season (see "Seasons")
|
43
|
+
* <b>testing/</b> - Example season
|
44
|
+
* database.yml - Example database configuration file
|
45
|
+
* leaves.yml - Example bot configuration file
|
46
|
+
* season.yml - Season configuration
|
47
|
+
* stems.yml - Example IRC configuration file
|
48
|
+
* <b>doc/</b> - HTML documentation generated by RDoc
|
49
|
+
* <b>leaves/</b> - Autumn leaves documentation
|
50
|
+
* <b>leaves/</b> - Autumn leaves. Each subdirectory contains all the code and
|
51
|
+
data for a leaf.
|
52
|
+
* <b>insulter/</b> - Very simple example leaf
|
53
|
+
* <i>See the *scorekeeper* directory</i>
|
54
|
+
* <b>scorekeeper/</b> - Database-backed, full-featured example leaf
|
55
|
+
* config.yml - Optional leaf-global configuration options
|
56
|
+
* controller.rb - The leaf's controller object
|
57
|
+
* <b>data/</b> - Optional directory for data storage (not used by Autumn)
|
58
|
+
* <b>helpers/</b> - Modules that extend the controller and views
|
59
|
+
* <b>models/</b> - Active record-type database objects
|
60
|
+
* <b>tasks</b> - Additional rake tasks for this leaf
|
61
|
+
* <b>views/</b> - ERb views for each of the leaf's commands
|
62
|
+
* <b>log/</b> - Directory where (most) Autumn logs are written (see the "Logs"
|
63
|
+
section)
|
64
|
+
* Rakefile - Contains the rake tasks used to control Autumn (see the "Tasks"
|
65
|
+
section)
|
66
|
+
* README - This file
|
67
|
+
* README.textile - Textile-formatted readme
|
68
|
+
* <b>script/</b> - Helper scripts for controlling Autumn
|
69
|
+
* destroy - Destroys Autumn objects
|
70
|
+
* generate - Creates Autumn objects
|
71
|
+
* <b>shared/</b> - Shared code libraries available to all leaves
|
72
|
+
* <b>tmp/</b> - Temporary files, such as PID files
|
73
|
+
|
74
|
+
== Configuring Autumn for Your First Launch
|
75
|
+
|
76
|
+
Before you can run Autumn and try out the example leaves, you'll need to set up
|
77
|
+
a few things. Here are the steps:
|
78
|
+
|
79
|
+
=== Creating your bot's tree
|
80
|
+
|
81
|
+
To create a new tree, change to a directory where you want your bot to run from,
|
82
|
+
then use <tt>autumn create PROJECT</tt> to create the directory (PROJECT should
|
83
|
+
be replaced with your bot's name) with the structure outlined above.
|
84
|
+
|
85
|
+
=== Configure Your Testing Season
|
86
|
+
|
87
|
+
In Autumn, your leaves run in an environment, called a "season." Each season has
|
88
|
+
different leaves and different settings for those leaves. By default, Autumn
|
89
|
+
comes with a season called "testing" already set up for you. You can edit that
|
90
|
+
season or create a new one with <tt>script/generate season [season name]</tt>.
|
91
|
+
The files for your season are stored in the config/seasons directory.
|
92
|
+
|
93
|
+
First, edit the stems.yml file. This file stores information about your IRC
|
94
|
+
connection. Edit it to connect to an IRC server of your choosing. For more
|
95
|
+
information, see "Stems" below.
|
96
|
+
|
97
|
+
Next, edit the database.yml file. As mentioned previously, Scorekeeper requires
|
98
|
+
the DataMapper gem because it uses a persistent store. By default it's set up to
|
99
|
+
use a SQLite 3 database, but you can use PostgreSQL or MySQL if you'd like. If
|
100
|
+
you'd prefer not to install any of these database solutions, delete the
|
101
|
+
database.yml file and remove the Scorekeeper leaf from the leaves.yml and
|
102
|
+
stems.yml files.
|
103
|
+
|
104
|
+
If you do choose to set up a database, you will have to run <tt>rake
|
105
|
+
db:migrate</tt> after your database.yml file is configured and your database is
|
106
|
+
created.
|
107
|
+
|
108
|
+
Lastly, view the leaves.yml file. You shouldn't have to make any changes to this
|
109
|
+
file, but it's a good idea to look at it to see how leaves are configured. You
|
110
|
+
can do the same with the season.yml file. See "Seasons" and "Leaves" below for
|
111
|
+
more.
|
112
|
+
|
113
|
+
=== Starting the Server
|
114
|
+
|
115
|
+
Run the shell command <tt>autumn start</tt> to start the server. After a short
|
116
|
+
while, your leaf should appear in the channel you specified. You can type
|
117
|
+
"!points Coolguy +5" and then "!points" to get started using Scorekeeper, or
|
118
|
+
"!insult" to play with Insulter. Have some fun, and when you're satisfied, stop
|
119
|
+
the server by typing "!quit".
|
120
|
+
|
121
|
+
If you'd like to daemonize your server, you can use the shell commands <tt>autumn
|
122
|
+
start -D</tt> and <tt>autumn stop</tt>. For more information, see "Tasks"
|
123
|
+
below (app: namespace).
|
124
|
+
|
125
|
+
== Making Your Own Leaf
|
126
|
+
|
127
|
+
Making your own leaf using Autumn is easy. In this tutorial, I'll show you how
|
128
|
+
to make a simple Fortune bot that responds to a few basic commands.
|
129
|
+
|
130
|
+
=== Step 1: Subclass Leaf
|
131
|
+
|
132
|
+
Create a new leaf by typing <tt>script/generate leaf fortune</tt>. This will
|
133
|
+
create a fortune directory in the leaves directory, along with the bare bones of
|
134
|
+
files needed within that directory. Edit the controller.rb file. First we'll
|
135
|
+
create an array to hold our fortunes:
|
136
|
+
|
137
|
+
FORTUNES = [
|
138
|
+
"You will make someone happy today.",
|
139
|
+
"Someone you don't expect will be important to you today.",
|
140
|
+
"Today will bring unexpected hardships."
|
141
|
+
]
|
142
|
+
|
143
|
+
As you can see, our 3 meager fortunes are stored in the +FORTUNES+ class
|
144
|
+
constant. Now, we'll want it to respond to the "!fortune" command, and all you
|
145
|
+
have to do is create a method called +fortune_command+ to make it work:
|
146
|
+
|
147
|
+
def fortune_command(stem, sender, reply_to, msg)
|
148
|
+
FORTUNES.pick
|
149
|
+
end
|
150
|
+
|
151
|
+
The +pick+ method is provided by Facets, so you may need to add a <tt>require
|
152
|
+
'facets/random'</tt> line at the top of your file. Our method returns a fortune
|
153
|
+
at random, which is automatically transmitted to the channel or nick where the
|
154
|
+
command was received.
|
155
|
+
|
156
|
+
Of course, any self-respecting fortune bot announces its presence when it starts
|
157
|
+
up, so, in your +Controller+ class, override the Autumn::Leaf#did_start_up
|
158
|
+
method to display a cheerful greeting:
|
159
|
+
|
160
|
+
def did_start_up
|
161
|
+
stems.message 'FortuneBot at your service! Type "!fortune" to get your fortune!'
|
162
|
+
end
|
163
|
+
|
164
|
+
...and that's it! You now have a fully functional fortune bot featuring -- not
|
165
|
+
two -- but <i>three</i> unique and exciting fortunes!
|
166
|
+
|
167
|
+
(For more on that <tt>stems.message</tt> bit, see "Stems.")
|
168
|
+
|
169
|
+
=== Step 2: Add the Leaf to Your Season
|
170
|
+
|
171
|
+
If you want, you can add the fortune bot to your leaves.yml and stems.yml files
|
172
|
+
to try it out. Adding a leaf is easy; simply duplicate the structure used for
|
173
|
+
another leaf's entry and change the values as appropriate. A typical two-leaf
|
174
|
+
configuration will look like:
|
175
|
+
|
176
|
+
Scorekeeper:
|
177
|
+
class: Scorekeeper
|
178
|
+
respond_to_private_messages: false
|
179
|
+
Fortune:
|
180
|
+
class: Fortune
|
181
|
+
respond_to_private_messages: true
|
182
|
+
|
183
|
+
As you notice, each leaf instance is given a name. In this example the name
|
184
|
+
happens to be the same as the leaf's _type_ name, but you could run two copies
|
185
|
+
of a leaf like so:
|
186
|
+
|
187
|
+
Fortune1:
|
188
|
+
class: Fortune
|
189
|
+
Fortune2:
|
190
|
+
class: Fortune
|
191
|
+
|
192
|
+
This doesn't make a whole lot of sense for our fortune bot, but for more
|
193
|
+
complicated bots it can be useful.
|
194
|
+
|
195
|
+
We've created the leaf, but we have to add it to the stem for it to work.
|
196
|
+
(Remember, a stem is an IRC connection and a leaf is a bot.) So, in your
|
197
|
+
stems.yml file, add an entry for this leaf. Your new config will appear
|
198
|
+
something like:
|
199
|
+
|
200
|
+
Example:
|
201
|
+
nick: Scorekeeper
|
202
|
+
leaves:
|
203
|
+
- Scorekeeper
|
204
|
+
- Fortune
|
205
|
+
rejoin: true
|
206
|
+
channel: somechannel
|
207
|
+
server: irc.someserver.com
|
208
|
+
|
209
|
+
When you restart the server, the bot will come back online and will now also
|
210
|
+
respond to the "!fortune" command. This is a helpful tutorial on how stems and
|
211
|
+
leaves are separate. One leaf can have many stems, and one stem can have many
|
212
|
+
leaves. You can combine these two entities however you need.
|
213
|
+
|
214
|
+
=== Step 3: Upgrade to ERb Views
|
215
|
+
|
216
|
+
You've already learned that for your <tt>[word]_command</tt>-type methods, the
|
217
|
+
bot responds with whatever string your method returns. For more complicated
|
218
|
+
commands, however, you may want to upgrade to full view abstraction, a la Ruby
|
219
|
+
on Rails. This is what the views directory is for.
|
220
|
+
|
221
|
+
If you place a .txt.erb file in the views directory named after your command, it
|
222
|
+
will be parsed by ERb and rendered as the result. You can pass variables to the
|
223
|
+
ERb parser by using the Autumn::Leaf#var method. Let's upgrade our
|
224
|
+
+fortune_command+ method for that:
|
225
|
+
|
226
|
+
def fortune_command(stem, sender, reply_to, msg)
|
227
|
+
var :fortune => FORTUNES.pick
|
228
|
+
end
|
229
|
+
|
230
|
+
We can then write a view, fortune.txt.erb, which will render the fortune:
|
231
|
+
|
232
|
+
<%= var :fortune %>
|
233
|
+
|
234
|
+
OK, so admittedly, this doesn't really get us anywhere, but for more complicated
|
235
|
+
bots, this well help separate view and controller concerns.
|
236
|
+
|
237
|
+
For more information on view rendering, see the Autumn::Leaf#render method.
|
238
|
+
|
239
|
+
== Seasons
|
240
|
+
|
241
|
+
Each time you start Autumn, the process launches in a certain season (a.k.a.
|
242
|
+
environment context). This season is defined in the config/global.yml file. You
|
243
|
+
can temporarily override it by setting the +SEASON+ environment variable (e.g.,
|
244
|
+
<tt>SEASON=production autumn start</tt>).
|
245
|
+
|
246
|
+
It's important to realize that an season is just a name, nothing more. You can
|
247
|
+
have as many seasons as you like, and name them anything that you like. Autumn
|
248
|
+
will load the config files for the season you've indicated as active. Autumn
|
249
|
+
doesn't really care if it's named "production" or "live" or
|
250
|
+
"testing-on-jeffs-machine"; it's all the same to Autumn.
|
251
|
+
|
252
|
+
Your season's configuration is stored in the season.yml file within your season
|
253
|
+
directory. Currently it supports one directive, +logging+. This sets the minimum
|
254
|
+
log level (such as +debug+ or +warn+). If the log level is set to +debug+, it
|
255
|
+
also enables console output parroting. (See the "Logging" section.)
|
256
|
+
|
257
|
+
The power of seasons comes in custom configuration options. For instance,
|
258
|
+
consider that you have a testing and production season. In your testing season,
|
259
|
+
your season.yml file contains:
|
260
|
+
|
261
|
+
dont_http: true
|
262
|
+
|
263
|
+
and in production, it contains:
|
264
|
+
|
265
|
+
dont_http: false
|
266
|
+
|
267
|
+
Now, in your code, you might have a method like:
|
268
|
+
|
269
|
+
def scores_command(stem, sender, reply_to, msg)
|
270
|
+
if options[:dont_http] then
|
271
|
+
return "Some fake sports scores."
|
272
|
+
else
|
273
|
+
# go on the web and find real sports scores
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
=== Standard Configuration Options
|
278
|
+
|
279
|
+
==== Global
|
280
|
+
|
281
|
+
System-wide configuration is done in the config/global.yml file. It supports by
|
282
|
+
default the following directives:
|
283
|
+
|
284
|
+
+season+:: The season to launch in.
|
285
|
+
+log_history+:: The number of historical logfiles to keep (default 10).
|
286
|
+
|
287
|
+
In addition, the following options are available (but cannot be set in the yml
|
288
|
+
file):
|
289
|
+
|
290
|
+
+root+:: The root directory of the Autumn installation.
|
291
|
+
+system_logger+:: The Autumn::LogFacade instance that records system messages.
|
292
|
+
|
293
|
+
==== Season
|
294
|
+
|
295
|
+
Season-specific configuration is done in the config/seasons/[season]/season.yml
|
296
|
+
file. Currently it only supports one directive, +logging+, which takes log
|
297
|
+
levels such as +debug+ or +warn+.
|
298
|
+
|
299
|
+
==== Stem
|
300
|
+
|
301
|
+
Stem-specific configuration is done in the config/seasons/[season]/stems.yml
|
302
|
+
file. It's important to note that stem and leaf configurations are completely
|
303
|
+
independent of each other. (In other words, stem options do not override leaf
|
304
|
+
options, nor vice versa.) Therefore, you generally won't add custom directives
|
305
|
+
to the stems.yml file, because you generally won't be working with stems
|
306
|
+
directly. The standard options are:
|
307
|
+
|
308
|
+
+server+:: The address of the IRC server.
|
309
|
+
+port+:: The IRC server port (default 6667).
|
310
|
+
+local_ip+:: The IP address to connect on (for virtual hosting).
|
311
|
+
+nick+:: The nick to request.
|
312
|
+
+password+:: The nick's password, if it is registered.
|
313
|
+
+channel+:: A channel to join.
|
314
|
+
+channels+:: A list of channels to join.
|
315
|
+
+leaf+:: The name of a leaf to run.
|
316
|
+
+leaves+:: A list of leaves to run. (These are the names of leaf configurations
|
317
|
+
in leaves.yml, not leaf subclasses.)
|
318
|
+
+rejoin+:: If true, the stem will rejoin any channels it is kicked from.
|
319
|
+
+server_password+:: The password for the IRC server, if necessary.
|
320
|
+
+ssl+:: If true, the connection to the IRC server will be made over SSL.
|
321
|
+
+server_type+:: The IRC server type. See resources/daemons for a list of valid
|
322
|
+
server types. If you do not manually set this value, it will be
|
323
|
+
guessed automatically.
|
324
|
+
+case_sensitive_channel_names+:: If true, channel names will be compared with
|
325
|
+
case sensitivity.
|
326
|
+
+dont_ghost+:: If true, the stem will not try to GHOST a registered nick if it's
|
327
|
+
taken.
|
328
|
+
+ghost_without_password+:: If true, the stem will use the GHOST command without
|
329
|
+
a password. Set this for servers that use some other
|
330
|
+
form of nick authentication, such as hostname-based.
|
331
|
+
+user+:: The username to send (optional).
|
332
|
+
+name+:: The user's real name (optional).
|
333
|
+
+throttle+:: If enabled, the stem will throttle large amounts of simultaneous
|
334
|
+
messages.
|
335
|
+
+throttle_rate+:: Sets the number of seconds that pass between consecutive
|
336
|
+
PRIVMSG's when the leaf's output is throttled.
|
337
|
+
+throttle_threshold+:: Sets the number of simultaneous messages that must
|
338
|
+
be queued before the leaf begins throttling output.
|
339
|
+
+nick_regex+:: The regular expression used to match nicknames in server
|
340
|
+
messages. By default, it conforms to the RFC-1459 definition.
|
341
|
+
|
342
|
+
The +channel+ and +channels+ directives can also be used to specify a password
|
343
|
+
for a password protected channel, like so:
|
344
|
+
|
345
|
+
channel:
|
346
|
+
channelname: channelpassword
|
347
|
+
|
348
|
+
or
|
349
|
+
|
350
|
+
channels:
|
351
|
+
- channel1: password1
|
352
|
+
- channel2: password2
|
353
|
+
|
354
|
+
The +port+, +server_type+, and <tt>channel</tt>/<tt>channels</tt> options are
|
355
|
+
set in the config file but not available in the +options+ hash. They are
|
356
|
+
accessed directly from attributes in the Stem instance, such as the +channels+
|
357
|
+
attribute.
|
358
|
+
|
359
|
+
==== Leaf
|
360
|
+
|
361
|
+
Leaf-specific configuration is done in the config/seasons/[season]/leaves.yml
|
362
|
+
file and the leaves/[leaf]/config.yml file, with the former taking precedence
|
363
|
+
over the latter. As mentioned above, leaf and stem configurations are completely
|
364
|
+
separate, so one does not override the other. The standard options are:
|
365
|
+
|
366
|
+
+class+:: The type of the leaf. It must be a subdirectory in the leaves
|
367
|
+
directory.
|
368
|
+
+command_prefix+:: The text that must precede each command. Defaults to "!".
|
369
|
+
+respond_to_private_messages+:: If true, the leaf will parse commands in
|
370
|
+
whispers, and respond over whispers to those
|
371
|
+
commands.
|
372
|
+
+database+:: A database connection to use (as defined in database.yml). By
|
373
|
+
default Autumn will choose a connection named after your leaf.
|
374
|
+
+formatter+:: The name of a module in Autumn::Formatting that will handle output
|
375
|
+
formatting and colorization. This defaults to mIRC-style
|
376
|
+
formatting.
|
377
|
+
|
378
|
+
In addition, the following options are available (but cannot be set in the yml
|
379
|
+
file):
|
380
|
+
|
381
|
+
+root+:: The root directory of the leaf installation.
|
382
|
+
|
383
|
+
The leaves.yml file is optional. When not included, each leaf in the leaves
|
384
|
+
directory will be automatically instantiated once.
|
385
|
+
|
386
|
+
=== Custom Configuration Options
|
387
|
+
|
388
|
+
All configuration files support user-generated directives. You can set options
|
389
|
+
at any level. Options at a more narrow level override those at a broader level.
|
390
|
+
|
391
|
+
Options are maintained and cataloged by the Autumn::Speciator singleton. You
|
392
|
+
could access the singleton directly, but most objects have an +options+
|
393
|
+
attribute providing simpler access to the Speciator.
|
394
|
+
|
395
|
+
For example, to access options in a leaf, all you do is call, for example,
|
396
|
+
<tt>options[:my_custom_option]</tt>. +my_custom_option+ can be set at the
|
397
|
+
global, season, or leaf level.
|
398
|
+
|
399
|
+
== Leaves
|
400
|
+
|
401
|
+
The Autumn::Leaf class has many tools to help you write your leaves. These
|
402
|
+
include things like filters, helpers, loggers, and an easy to use IRC library.
|
403
|
+
The Autumn::Leaf and Autumn::Stem class docs are the most thorough way of
|
404
|
+
learning about each of these features, but I'll walk you through the basics
|
405
|
+
here.
|
406
|
+
|
407
|
+
=== The Many Methods of Leaf
|
408
|
+
|
409
|
+
By subclassing Autumn::Leaf, you gain access to a number of neat utilities.
|
410
|
+
These generally come in three classes: IRC commands that have already been
|
411
|
+
written for you, utility methods you can call, and invoked methods you can
|
412
|
+
override. Utility methods do things like add filters. Invoked methods are called
|
413
|
+
when certain events happen, like when your leaf starts up or when a private
|
414
|
+
message is received. You override them in your leaf to customize how it responds
|
415
|
+
to these events.
|
416
|
+
|
417
|
+
<b>Invoked methods</b>:: +will_start_up+, +did_start_up+,
|
418
|
+
+did_receive_channel_message+, etc.
|
419
|
+
<b>Utility methods</b>:: +before_filter+, +database+, etc.
|
420
|
+
<b>IRC commands</b>:: +quit_command+, +reload_command+, +autumn_command+, etc.
|
421
|
+
|
422
|
+
See the class docs for more information on these methods.
|
423
|
+
|
424
|
+
In addition, your leaf is designated as a listener for its Autumn::Stem
|
425
|
+
instances. In short, this means if you want even finer control over the IRC
|
426
|
+
connection, you can implement listener methods. See the
|
427
|
+
Autumn::Stem#add_listener method for examples of such methods.
|
428
|
+
|
429
|
+
Finally, your leaf can implement methods that are broadcast by listener plugins.
|
430
|
+
An example of such a plugin is the Autumn::CTCP class, which is included in all
|
431
|
+
stems by default. Visit its class docs to learn more about how to send and
|
432
|
+
receive CTCP requests.
|
433
|
+
|
434
|
+
=== Filters
|
435
|
+
|
436
|
+
Filters are methods that are run either before or after a command is executed.
|
437
|
+
In the former case, they can also prevent the command from being run. This is
|
438
|
+
useful for authentication, for instance: A filter could determine if someone is
|
439
|
+
authorized to run a command, and prevent the command from being run if not.
|
440
|
+
|
441
|
+
Use filters to save yourself the effort of rewriting code that will run before
|
442
|
+
or after a command is executed. Filter methods are named <tt>[word]_filter</tt>
|
443
|
+
and they are added to the filter chain using the +before_filter+ and
|
444
|
+
+after_filter+ methods (like in Ruby on Rails). As an example, imagine you
|
445
|
+
wanted your bot to say something after each command:
|
446
|
+
|
447
|
+
class Controller > Autumn::Leaf
|
448
|
+
after_filter :outro
|
449
|
+
|
450
|
+
private
|
451
|
+
|
452
|
+
def outro_filter(stem, channel, sender, command, msg, opts)
|
453
|
+
stem.message "This has been a production of OutroBot!", channel
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
The result of this is that after each command, the leaf will make a dramatic
|
458
|
+
exit. (Why did I use +after_filter+ and not +before_filter+? Because as I said
|
459
|
+
earlier, a +before_filter+ can stop the command from being executed; the only
|
460
|
+
way we know for sure that the command was executed -- and therefore should be
|
461
|
+
outroed -- is to use an +after_filter+.)
|
462
|
+
|
463
|
+
I made the +outro_filter+ method private because I felt it shouldn't be exposed
|
464
|
+
to other classes; this is not a requirement of the filter framework, though.
|
465
|
+
|
466
|
+
Now let's say you wanted to prevent the command from being run in some cases.
|
467
|
+
The most obvious application of this feature is authentication. Autumn already
|
468
|
+
includes a robust authentication module, but for the sake of example, let's
|
469
|
+
pretend you wanted to do your own authentication in your leaf. So, you write a
|
470
|
+
+before_filter+ to determine if the user is authenticated.
|
471
|
+
<tt>before_filter</tt>s have return values; if they return false, the filter
|
472
|
+
chain is halted and the command is suppressed. If you want to have your leaf
|
473
|
+
display some sort of message (like "Nice try!"), you need to include that in
|
474
|
+
your filter.
|
475
|
+
|
476
|
+
As an example, here's a simple form of authentication that just checks a
|
477
|
+
person's nick:
|
478
|
+
|
479
|
+
class Controller < Autumn::Leaf
|
480
|
+
before_filter :authenticate, :only => :quit, :admin => 'Yournick'
|
481
|
+
|
482
|
+
def authenticate_filter(stem, channel, sender, command, msg, opts)
|
483
|
+
sender == opts[:admin]
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
I'm introducing you to three new features with this sample:
|
488
|
+
|
489
|
+
* You can use the <tt>:only</tt> option to limit your filter to certain
|
490
|
+
commands. Note that you specify the _command_ name as a symbol, _not_ the
|
491
|
+
method name (which would be +quit_command+ in this case).
|
492
|
+
* You can pass your own options to +before_filter+ and +after_filter+; they are
|
493
|
+
passed through to your method via the last parameter, +opts+.
|
494
|
+
* The return value of a +before_filter+ is used to determine if the command
|
495
|
+
should be run. So be careful that your method does not return nil or false
|
496
|
+
unless you really mean for the command to be suppressed.
|
497
|
+
|
498
|
+
Both of these examples use the parameters sent to your filter method. They are,
|
499
|
+
in order:
|
500
|
+
|
501
|
+
1. the Autumn::Stem instance that received the command,
|
502
|
+
2. the name of the channel to which the command was sent (or nil if it was a
|
503
|
+
private message),
|
504
|
+
3. the sender hash,
|
505
|
+
4. the name of the command that was typed, as a symbol,
|
506
|
+
5. any additional parameters after the command (same as the +msg+ parameter in
|
507
|
+
the <tt>[word]_command</tt> methods),
|
508
|
+
6. the custom options that were given to +before_filter+ or +after_filter+.
|
509
|
+
|
510
|
+
There are two built-in options that you can specify for +before_filter+ and
|
511
|
+
+after_filter+, and those are +only+ and +except+. They work just like in Rails:
|
512
|
+
The +only+ option limits the filter to running only on the given command or list
|
513
|
+
of commands, and the +except+ option prevents the filter from being run on the
|
514
|
+
given command or list. All other options are passed to the filter for you to
|
515
|
+
use.
|
516
|
+
|
517
|
+
Filters are run in the order they are added to the filter chain. Therefore, a
|
518
|
+
superclass's filters will run before a subclass's filters, and filters added
|
519
|
+
later in a class definition will be run after those added earlier.
|
520
|
+
|
521
|
+
If you subclass one of your leaves, it inherits your superclass's filters. The
|
522
|
+
Autumn::Leaf superclass does not have any filters by default, though by default
|
523
|
+
new leaves come with a simple authentication filter that checks the user's
|
524
|
+
privilege level.
|
525
|
+
|
526
|
+
=== Authentication
|
527
|
+
|
528
|
+
You don't need to write a +before_filter+ as shown above, because Autumn already
|
529
|
+
includes a robust authentication module. The Autumn::Authentication module
|
530
|
+
includes the +Base+ class and four different subclasses of it. Each of these
|
531
|
+
subclasses handles a different type of authentication. You can choose the
|
532
|
+
authentication strategy you want on a leaf-by-leaf basis or for a whole season.
|
533
|
+
|
534
|
+
To specify the kind of authentication you want, you must add an +authentication+
|
535
|
+
directive to your config. If you want to set it for an individual leaf, add it
|
536
|
+
to the leaves.yml file. If you want all leaves to have the same authentication
|
537
|
+
strategy, add it to the season.yml or global.yml file.
|
538
|
+
|
539
|
+
The +authentication+ directive should be a hash that, at a minimum, includes a
|
540
|
+
key called +type+. This is the snake_cased name of subclass in
|
541
|
+
Autumn::Authentication that you wish to use. As an example, here is an entry for
|
542
|
+
an Administrator bot in a leaves.yml file, with ops-based authentication.
|
543
|
+
|
544
|
+
Administrator:
|
545
|
+
class: Administrator
|
546
|
+
authentication:
|
547
|
+
type: op
|
548
|
+
|
549
|
+
This will instantiate the Autumn::Authentication::Op class for use with the
|
550
|
+
Administrator bot.
|
551
|
+
|
552
|
+
Other authentication strategies may require additional information. For
|
553
|
+
instance, if you want to used nick-based authentication, your leaves.yml file
|
554
|
+
might look like:
|
555
|
+
|
556
|
+
Administrator:
|
557
|
+
class: Administrator
|
558
|
+
authentication:
|
559
|
+
type: nick
|
560
|
+
nick: MyNick
|
561
|
+
|
562
|
+
See the class docs for each subclass in Autumn::Authentication for more info on
|
563
|
+
how you should set up your configs.
|
564
|
+
|
565
|
+
=== Persistent Stores
|
566
|
+
|
567
|
+
If you would like to use a persistent store for your leaf, you should install
|
568
|
+
the DataMapper gem and a DataObjects gem for your database of choice (MySQL,
|
569
|
+
PostgreSQL, or SQLite). DataMapper works almost identically to ActiveRecord, so
|
570
|
+
if you have any Rails programming experience, you should be able to dive right
|
571
|
+
in.
|
572
|
+
|
573
|
+
Once you've got DataMapper installed, you should create one or more database
|
574
|
+
connections in your config/seasons/[season]/database.yml file. A sample database
|
575
|
+
connection looks like:
|
576
|
+
|
577
|
+
connection_name:
|
578
|
+
adapter: mysql
|
579
|
+
host: localhost
|
580
|
+
username: root
|
581
|
+
password: pass
|
582
|
+
database: database_name
|
583
|
+
|
584
|
+
or, in a smaller syntax:
|
585
|
+
|
586
|
+
connection_name: mysql://root@pass:localhost/database_name
|
587
|
+
|
588
|
+
If you are using the "sqlite3" adapter, the +database+ option is the path to the
|
589
|
+
file where the data should be written (example:
|
590
|
+
leaves/fortune/data/my_database.db). You can name your connection however you
|
591
|
+
want, but you _should_ name it after either your leaf or your leaf subclass.
|
592
|
+
(More on this below.)
|
593
|
+
|
594
|
+
You should also create DataMapper model classes for each of your model objects.
|
595
|
+
You can place them within your leaf's models directory. This works almost
|
596
|
+
exactly the same as the app/models directory in Rails.
|
597
|
+
|
598
|
+
Once your database, data models, and leaves have been configured, you can use
|
599
|
+
the <tt>rake db:migrate</tt> task to automatically populate your database.
|
600
|
+
|
601
|
+
Now, unlike Rails, Autumn supports multiple database connections. Two leaves can
|
602
|
+
use two different database connections, or share the same database connection.
|
603
|
+
Because of this, it's important to understand how to manage your connections.
|
604
|
+
Autumn tries to do this for you by guessing which connection belongs to which
|
605
|
+
leaf, based on their names.
|
606
|
+
|
607
|
+
For example, imagine you have a leaf named "Fortune" and an instance of that
|
608
|
+
leaf in leaves.yml named "MyFortune". If you name your database connection
|
609
|
+
either "Fortune" or "MyFortune" (or "fortune" or "my_fortune"), it will
|
610
|
+
automatically be associated with that leaf. What this means is that for the
|
611
|
+
leaf's command methods (such as +about_command+) and invoked methods (such as
|
612
|
+
+did_receive_private_message+), the database connection will already be set for
|
613
|
+
you, and you can start using your DataMapper objects just like ActiveRecord
|
614
|
+
objects.
|
615
|
+
|
616
|
+
If, on the other hand, you either <b>named your database connection differently
|
617
|
+
from your leaf or subclass name</b> or you <b>are writing a method outside of
|
618
|
+
the normal flow of leaf methods</b> (for instance, one that is directly called
|
619
|
+
by a Stem, or a different listener), you will need to call the +database+ method
|
620
|
+
and pass it a block containing your code.
|
621
|
+
|
622
|
+
This is terribly confusing, so let me give you an example. Let's assume you've
|
623
|
+
got a fortune bot running a leaf named "FortuneLeaf", so your leaves.yml
|
624
|
+
configuration is:
|
625
|
+
|
626
|
+
FortuneBot:
|
627
|
+
class: FortuneLeaf
|
628
|
+
|
629
|
+
And you have a database connection for that leaf, named after the leaf's class:
|
630
|
+
|
631
|
+
fortune_leaf:
|
632
|
+
adapter: sqlite3
|
633
|
+
database: leaves/fortune_leaf/data/development.db
|
634
|
+
|
635
|
+
Let's further assume you have a simple DataMapper object:
|
636
|
+
|
637
|
+
class Fortune
|
638
|
+
include DataMapper::Resource
|
639
|
+
property :id, Integer, :serial => true
|
640
|
+
property :text, String
|
641
|
+
end
|
642
|
+
|
643
|
+
Now, if we wanted to write a "!fortune" command, it would appear something like
|
644
|
+
this:
|
645
|
+
|
646
|
+
def fortune_command(stem, sender, reply_to, msg)
|
647
|
+
fortunes = Fortune.all
|
648
|
+
fortunes[rand(fortunes.size)].text
|
649
|
+
end
|
650
|
+
|
651
|
+
Autumn automatically knows to execute this DataMapper code in the correct
|
652
|
+
database context. It knows this because your leaf's name is +FortuneLeaf+, and
|
653
|
+
your database context is named the same.
|
654
|
+
|
655
|
+
But what if you wanted to use that connection for other leaves too, so you named
|
656
|
+
it something like "local_database"? Now, Autumn won't be able to guess that you
|
657
|
+
want to use that DB context, so you have to specify it manually:
|
658
|
+
|
659
|
+
def fortune_command(stem, sender, reply_to, msg)
|
660
|
+
database(:local_database) do
|
661
|
+
fortunes = Fortune.all
|
662
|
+
return fortunes[rand(fortunes.size)].text
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
If that is too tedious, you can specify the database connection manually in the
|
667
|
+
leaves.yml file:
|
668
|
+
|
669
|
+
FortuneBot:
|
670
|
+
class: FortuneLeaf
|
671
|
+
database: local_database
|
672
|
+
|
673
|
+
OK, now onto the second special case. Imagine you want your fortune bot to also
|
674
|
+
send a fortune in response to a CTCP VERSION request. So, you'd implement a
|
675
|
+
method like so:
|
676
|
+
|
677
|
+
def ctcp_version_request(handler, stem, sender, arguments)
|
678
|
+
fortune = random_fortune # Loads a random fortune
|
679
|
+
send_ctcp_reply stem, sender[:nick], 'VERSION', fortune.text
|
680
|
+
end
|
681
|
+
|
682
|
+
This will break -- why? Because the +ctcp_version_request+ method is in the
|
683
|
+
realm of the Autumn::CTCP class, _not_ the Autumn::Leaf class. (You can see this
|
684
|
+
by investigating the CTCP class docs; it shows you what methods you can
|
685
|
+
implement for CTCP support.) Basically, the +CTCP+ class calls your method
|
686
|
+
directly, giving the Autumn::Leaf class no chance to set up the database first.
|
687
|
+
So to fix it, make a call to +database+ first:
|
688
|
+
|
689
|
+
def ctcp_version_request(handler, stem, sender, arguments)
|
690
|
+
fortune = database { random_fortune }
|
691
|
+
send_ctcp_reply stem, sender[:nick], 'VERSION', fortune.text
|
692
|
+
end
|
693
|
+
|
694
|
+
This will execute those methods in the scope of the database connection guessed
|
695
|
+
by Autumn::Leaf. Of course, you can manually pass in a connection name if
|
696
|
+
necessary.
|
697
|
+
|
698
|
+
<b>Another important note:</b> You will need to make a call to @database@ in any
|
699
|
+
child threads your leaf creates. The database context is not automatically
|
700
|
+
carried over to such threads.
|
701
|
+
|
702
|
+
=== Your Leaf's Module; or, "What Do I Do About Namespace Conflicts?"
|
703
|
+
|
704
|
+
So, if you have two database-backed leaves, it's entirely likely that both of
|
705
|
+
them will use some sort of DataMapper resource named +Channel+, or something
|
706
|
+
similar. You can't define the class +Channel+ twice in two different ways, so
|
707
|
+
how do you deal with this?
|
708
|
+
|
709
|
+
The answer is: It's already dealt with for you. Go ahead and define the class
|
710
|
+
twice. Or three times.
|
711
|
+
|
712
|
+
The longer explanation is: Secretly, behind the scenes, <b>all your leaf code is
|
713
|
+
being cleverly loaded into a module named after your leaf</b>. So, when, in your
|
714
|
+
controller.rb code, it says <tt>class Controller < Autumn::Leaf</tt>, you should
|
715
|
+
read it as <tt>class MyLeafName::Controller < Autumn::Leaf</tt>. When you define
|
716
|
+
your model with <tt>class Channel</tt>, it's really read as <tt>class
|
717
|
+
MyLeafName::Channel</tt>.
|
718
|
+
|
719
|
+
Don't worry about table names or associations or anything, either. Just go ahead
|
720
|
+
and use it as if it weren't in a module. The libs/datamapper_hacks.rb file has
|
721
|
+
all the necessary code changes to make this bit of trickery work.
|
722
|
+
|
723
|
+
=== Using Support Modules
|
724
|
+
|
725
|
+
Helper modules placed in your leaf's helpers directory will automatically be
|
726
|
+
loaded and included in your leaf controller and views. To create a helper
|
727
|
+
module, place Ruby files to be loaded into the helpers directory. Make sure your
|
728
|
+
helper modules' names end with the word "Helper".
|
729
|
+
|
730
|
+
For instance, if your leaf's name is "Fortune", and you needed two helpers, a
|
731
|
+
database helper and a network helper, you could create two modules named
|
732
|
+
+DatabaseHelper+ and +NetworkHelper+. Any modules named in this fashion and
|
733
|
+
placed in the helpers subdirectory will be loaded and appended to the
|
734
|
+
controller and its views automatically.
|
735
|
+
|
736
|
+
=== Debugging Your Leaf
|
737
|
+
|
738
|
+
If you make a simple code change to your leaf, you can reload it without having
|
739
|
+
to restart the whole process. See the Autumn::Leaf#reload_command documentation
|
740
|
+
for more information on when and how you can reload your leaf's code.
|
741
|
+
|
742
|
+
If an error occurs on a live production instance, it will be logged to the log
|
743
|
+
file for your season. You can inspect the log file to determine what went wrong.
|
744
|
+
|
745
|
+
If the error happens before the logger is available, oftentimes it will appear
|
746
|
+
in the autumn.output or autumn.log files. These files are generated by the
|
747
|
+
daemon library and note any uncaught exceptions or standard outs. They are in
|
748
|
+
the tmp directory.
|
749
|
+
|
750
|
+
The most tricky of errors can happen before the process is daemonized. If your
|
751
|
+
process is quitting prematurely, and you don't see anything in either log file,
|
752
|
+
consider running <tt>autumn start</tt>, allowing you to see any exceptions for
|
753
|
+
yourself.
|
754
|
+
|
755
|
+
Unfortunately, it's still possible that the bug might not appear when you do
|
756
|
+
this, but only appear when the process is daemonized. In this situation, I'd
|
757
|
+
recommend installing rdebug (<tt>sudo gem install rdebug</tt>) and stepping
|
758
|
+
through the code to figure out what's going wrong. In particular, make sure you
|
759
|
+
step into the +Foliater+'s +start_stems+ method, when it creates the new
|
760
|
+
threads. It's possible your exception will rear its head once you step into that
|
761
|
+
line of code.
|
762
|
+
|
763
|
+
== Stems
|
764
|
+
|
765
|
+
Autumn::Stem is a full-featured IRC client library, written from the ground up
|
766
|
+
for Autumn. It makes extensive use of implicit protocols, meaning that most
|
767
|
+
features are accessed by implementing the methods you feel are necessary.
|
768
|
+
|
769
|
+
Most of the time, you will only work with stems indirectly via leaves. For
|
770
|
+
instance, if you want an "!opped" command that returns true if the sender is an
|
771
|
+
operator, it would look like this:
|
772
|
+
|
773
|
+
def opped_command(stem, sender, reply_to, msg)
|
774
|
+
stem.channel_members[reply_to][sender[:nick]] == :operator ? "You are opped." : "You are not opped."
|
775
|
+
end
|
776
|
+
|
777
|
+
Let's break this down. In order to figure out if someone is opped or not, we
|
778
|
+
need three pieces of information: their nick, the channel they are in, and the
|
779
|
+
IRC server they are connected to.
|
780
|
+
|
781
|
+
The +stem+ parameter contains the Autumn::Stem instance that received this
|
782
|
+
message. It is our link to that server. Through it we can perform IRC actions
|
783
|
+
and make requests.
|
784
|
+
|
785
|
+
Autumn::Stem includes an attribute +channel_members+, a hash of channels mapped
|
786
|
+
to their members. The channel that received the message is passed via the
|
787
|
+
+reply_to+ parameter. So we call <tt>channel_members[reply_to]</tt> and we
|
788
|
+
receive a hash of member names to their privilege levels. The +sender+ parameter
|
789
|
+
contains information about the person who sent the command, including their
|
790
|
+
nick. So we use their nick to resolve their privilege level.
|
791
|
+
|
792
|
+
Complicated? Sure it is. That's the price we pay for separating stems from
|
793
|
+
leaves. But what if you, like probably 90% of the people out there who use
|
794
|
+
Autumn, only have one stem? Why should you have to call the same damn stem each
|
795
|
+
and every time?
|
796
|
+
|
797
|
+
Fortunately, your pleas are not in vain. For leaves that run off only one stem,
|
798
|
+
the stem's methods are rolled right into the leaf. So, that "!opped" command
|
799
|
+
method becomes:
|
800
|
+
|
801
|
+
def opped_command(stem, sender, reply_to, msg)
|
802
|
+
channel_members[reply_to][sender[:nick]] == :operator ? "You are opped." : "You are not opped."
|
803
|
+
end
|
804
|
+
|
805
|
+
OK, so it's not like a world-class improvement, but it helps.
|
806
|
+
|
807
|
+
The primary thing your leaf will probably do with a Stem instance is use it to
|
808
|
+
send messages, like so:
|
809
|
+
|
810
|
+
def about_command(stem, sender, reply_to, msg)
|
811
|
+
stem.message "I am a pretty awesome bot!", reply_to
|
812
|
+
end
|
813
|
+
|
814
|
+
Fortunately, if you just return a string, Autumn::Leaf will automatically send
|
815
|
+
it for you, simplifying our method:
|
816
|
+
|
817
|
+
def about_command(stem, sender, reply_to, msg)
|
818
|
+
"I am a pretty awesome bot!"
|
819
|
+
end
|
820
|
+
|
821
|
+
You would still interact with the stem directly if you wanted to do something
|
822
|
+
like announce your leaf's presence to everyone. To do this, you'd have to send
|
823
|
+
a message to every channel of every stem the leaf is a listener for:
|
824
|
+
|
825
|
+
stems.each { |stem| stem.channels.each { |channel| stem.message "Hello!", channel } }
|
826
|
+
|
827
|
+
But! Autumn::Stem#message will automatically send a message to every channel if
|
828
|
+
you don't specify any channels, simplifying our code to:
|
829
|
+
|
830
|
+
stems.each { |stem| stem.message "Hello!" }
|
831
|
+
|
832
|
+
It gets even better. <b>You can call methods on the +stems+ array as if it were
|
833
|
+
a stem itself!</b> This simplifies the line significantly:
|
834
|
+
|
835
|
+
stems.message "Hello!"
|
836
|
+
|
837
|
+
Pretty nifty, huh? This also works for functions as well as methods; for
|
838
|
+
instance, the Autumn::Stem#ready? function, which returns true if a stem is
|
839
|
+
ready:
|
840
|
+
|
841
|
+
stems.ready? #=> [ true, true, false, true ] (for example)
|
842
|
+
|
843
|
+
=== The nitty-gritty of stems
|
844
|
+
|
845
|
+
The section above dealt with stems as they relate to leaves. But when would you
|
846
|
+
need to deal with a stem directly? Generally, never. However, if you find
|
847
|
+
that Autumn::Leaf doesn't have what you need, you may have to turn to
|
848
|
+
Autumn::Stem to get the functionality you are looking for. So let's take a look
|
849
|
+
at how Stem works.
|
850
|
+
|
851
|
+
A stem interacts with interested parties via the listener protocol. Your leaf
|
852
|
+
signals its interest to a stem by calling Autumn::Stem#add_listener. When a leaf
|
853
|
+
or any other object becomes a stem's listener, that stem then invokes methods on
|
854
|
+
the listener whenever an IRC event occurs.
|
855
|
+
|
856
|
+
Let's take a simple example. Assume you wanted to build a basic textual IRC
|
857
|
+
client using Stem. You'd first want to indicate that your client is a listener:
|
858
|
+
|
859
|
+
class MyClient
|
860
|
+
def initialize(stem)
|
861
|
+
@stem = stem
|
862
|
+
@stem.add_listener self
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
Now the stem will send method calls to your +MyClient+ instance every time an
|
867
|
+
IRC event occurs. None of these methods are required -- you can implement as few
|
868
|
+
or as many as you want. The different methods that Stem will send are documented
|
869
|
+
in the Autumn::Stem#add_listener method docs. One very important method is the
|
870
|
+
+irc_privmsg_event+ method. Let's implement it:
|
871
|
+
|
872
|
+
def irc_privmsg_event(stem, sender, arguments)
|
873
|
+
puts "#{arguments[:channel]} <#{sender[:nick]}> #{arguments[:message]}"
|
874
|
+
end
|
875
|
+
|
876
|
+
Now we've got the most important part of our IRC client done -- receiving
|
877
|
+
messages.
|
878
|
+
|
879
|
+
You can also send IRC events using stem. It's simple: Every IRC command (such as
|
880
|
+
JOIN and PRIVMSG and MODE) has a corresponding method in Stem (such as +join+
|
881
|
+
and +privmsg+ and +mode+). These methods aren't in the API docs because they're
|
882
|
+
implemented using +method_missing+. Their arguments are exactly the same as the
|
883
|
+
arguments the IRC command expects, and in the same order.
|
884
|
+
|
885
|
+
So how do we send a message? Well according to RFC-1459, the basic IRC spec, the
|
886
|
+
PRIVMSG command takes two arguments: a list of receivers, and the text to be
|
887
|
+
sent. So, we know our method call should look something like this:
|
888
|
+
|
889
|
+
@stem.privmsg recipient, message
|
890
|
+
|
891
|
+
Astute readers will note that the spec shows a _list_ of recipients, and indeed,
|
892
|
+
you can call the method like so:
|
893
|
+
|
894
|
+
@stem.privmsg [ recipient1, recipient2 ], message
|
895
|
+
|
896
|
+
That's the basics of how Autumn::Stem works, but there's one other thing worth
|
897
|
+
mentioning, and that's listener plugins. The details are in the
|
898
|
+
Autumn::Stem#add_listener method docs, but the short of it is that these are
|
899
|
+
special listeners that bestow their powers onto other listeners.
|
900
|
+
|
901
|
+
The best example of this is the Autumn::CTCP class. This class is indeed a Stem
|
902
|
+
listener: It listens to PRIVMSG events from the stem, and checks them to see if
|
903
|
+
they are CTCP requests. However, it _also_ gives you, the author of another
|
904
|
+
listener (such as your leaf) the ability to implement methods according to _its_
|
905
|
+
protocol.
|
906
|
+
|
907
|
+
For example, say you wanted to respond to CTCP VERSION requests with your own
|
908
|
+
version information. You do it like so:
|
909
|
+
|
910
|
+
def ctcp_version_request(handler, stem, sender, arguments)
|
911
|
+
send_ctcp_reply stem, sender[:nick], 'VERSION', "AwesomeBot 2.0 by Sancho Sample"
|
912
|
+
end
|
913
|
+
|
914
|
+
What's going on here? Because the Autumn::CTCP class is a listener plugin, it is
|
915
|
+
sending its own method calls as well as implementing Stem's method calls. One
|
916
|
+
such call is the +ctcp_version_request+ method, which you can see in the CTCP
|
917
|
+
class docs. Somewhere deep in the annals of Autumn::Foliater, there is some code
|
918
|
+
similar to the following:
|
919
|
+
|
920
|
+
ctcp = Autumn::CTCP.new
|
921
|
+
stem.add_listener ctcp
|
922
|
+
|
923
|
+
Thus, every stem comes pre-fab with a CTCP listener plugin. That plugin is
|
924
|
+
intercepting PRIVMSG events and checking if they're CTCP requests. If they are,
|
925
|
+
it is invoking methods, such as +ctcp_version_request+, in all of the stem's
|
926
|
+
other listeners, among which is your leaf. Hopefully you understand how this all
|
927
|
+
fits together.
|
928
|
+
|
929
|
+
The lesson to take home here is two-fold: Firstly, if you'd like CTCP support in
|
930
|
+
your leaf, know that it's the Autumn::CTCP class that is providing the method
|
931
|
+
calls to your leaf, not the Autumn::Stem class. Secondly, this should hopefully
|
932
|
+
give you some ideas should you want to write your own listener plugin to enhance
|
933
|
+
Stem's functionality.
|
934
|
+
|
935
|
+
== Autumn's Logging
|
936
|
+
|
937
|
+
Autumn uses Ruby's Logger class to log; however, it uses Autumn::LogFacade to
|
938
|
+
prepend additional information to each log entry. The LogFacade class has the
|
939
|
+
exact same external API as Logger, so you can use it like a typical Ruby or
|
940
|
+
Ruby on Rails logger. Many objects (such as Leaf and Stem) include a +logger+
|
941
|
+
attribute:
|
942
|
+
|
943
|
+
logger.debug "Debug statement"
|
944
|
+
logger.fatal $!
|
945
|
+
|
946
|
+
See the LogFacade class docs for details.
|
947
|
+
|
948
|
+
== Tasks
|
949
|
+
|
950
|
+
The included Rakefile contains a number of useful tasks to help you develop and
|
951
|
+
deploy your leaves. You can always get a list of tasks by typing
|
952
|
+
<tt>rake --tasks</tt>. The various commands you can run are:
|
953
|
+
|
954
|
+
Application tasks:
|
955
|
+
|
956
|
+
* <b>rake app:start</b> - Starts the Autumn daemon in the background.
|
957
|
+
* <b>rake app:stop</b> - Stops the Autumn daemon.
|
958
|
+
* <b>rake app:restart</b> - Reloads the Autumn daemons.
|
959
|
+
* <b>rake app:run</b> - Starts the Autumn daemon in the foreground.
|
960
|
+
* <b>rake app:zap</b> - Forces the daemon to a stopped state. Use this command
|
961
|
+
if your daemon is not running but autumn start -D thinks it still is.
|
962
|
+
|
963
|
+
Database tasks:
|
964
|
+
|
965
|
+
* <b>LEAF=[leaf name] rake db:migrate</b> - Creates all the tables for a leaf,
|
966
|
+
as specified by the leaf's model objects.
|
967
|
+
|
968
|
+
Documentation tasks:
|
969
|
+
|
970
|
+
* <b>rake doc:api</b> - Generates HTML documentation for Autumn, found in the
|
971
|
+
doc/api directory.
|
972
|
+
* <b>rake doc:leaves</b> - Generates HTML documentation for your leaves, found
|
973
|
+
in the doc/leaves directory.
|
974
|
+
* <b>rake doc:clear</b> - Removes all HTML documentation.
|
975
|
+
|
976
|
+
Logging tasks:
|
977
|
+
|
978
|
+
* <b>rake log:clear</b> - Clears the log files for all seasons.
|
979
|
+
* <b>rake log:errors</b> - Prints a list of error-level log messages for the
|
980
|
+
current season, and uncaught exceptions in all seasons.
|
981
|
+
|
982
|
+
=== Custom leaf tasks
|
983
|
+
|
984
|
+
You can define your own leaf-specific tasks in the tasks subdirectory within
|
985
|
+
your leaf's directory. Any .rake files there will be loaded by rake. The tasks
|
986
|
+
will be added within a task-group named after your leaf. Use Scorekeeper as an
|
987
|
+
example: If you type <tt>rake --tasks</tt>, you'll see one other task,
|
988
|
+
<tt>rake scorekeeper:scores</tt>. The "scores" task is defined in the
|
989
|
+
leaves/scorekeeper/tasks/stats.rake file, and placed in the "scorekeeper" task
|
990
|
+
group by Autumn.
|
991
|
+
|
992
|
+
Also, if you open that file up, you'll notice that you have to refer to your
|
993
|
+
leaf's classes by their _full_ names, including the leaf module. (See "Your
|
994
|
+
Leaf's Module" if you're confused.)
|
995
|
+
|
996
|
+
== Scripts
|
997
|
+
|
998
|
+
Autumn includes some scripts to help you control it.
|
999
|
+
|
1000
|
+
=== script/console
|
1001
|
+
|
1002
|
+
Bootstraps an IRb console with the Autumn environment configured. Stems and
|
1003
|
+
leaves are accessile from the Foliater instance. DataMapper models can be used.
|
1004
|
+
Does not start any stems (in other words, no actual server login occurs).
|
1005
|
+
|
1006
|
+
Usage: script/console <options>
|
1007
|
+
|
1008
|
+
where <options> may contain:
|
1009
|
+
|
1010
|
+
<tt>--irb</tt>:: Invoke a different Ruby terminal.
|
1011
|
+
|
1012
|
+
You can alter the season by setting the +SEASON+ environment variable.
|
1013
|
+
|
1014
|
+
=== autumn start|stop|status|restart PROJECT [options]
|
1015
|
+
|
1016
|
+
The autumn binary can be used to control an Autumn daemon.
|
1017
|
+
Starts, stops, shows status of and manages the daemon.
|
1018
|
+
PROJECT is the path to the base of your autumn bot
|
1019
|
+
|
1020
|
+
+start+:: start an instance of the application
|
1021
|
+
+stop+:: stop all instances of the application
|
1022
|
+
+restart+:: stop all instances and restart them afterwards
|
1023
|
+
+run+:: start the application and stay on top
|
1024
|
+
|
1025
|
+
where [options] are
|
1026
|
+
<tt>-D, --daemonize</tt>:: Daemonize the bot
|
1027
|
+
<tt>-m, --monitor</tt>:: Try to restart from crashes in daemon mode
|
1028
|
+
|
1029
|
+
Common options:
|
1030
|
+
<tt>-h, --help</tt>:: Show help
|
1031
|
+
|
1032
|
+
=== script/destroy
|
1033
|
+
|
1034
|
+
Destroys the files for leaves, seasons, and other objects of the Autumn
|
1035
|
+
framework.
|
1036
|
+
|
1037
|
+
Usage: script/destroy <options> <object> <name>
|
1038
|
+
|
1039
|
+
<object>:: The object type to destroy. Valid types are "leaf" and "season".
|
1040
|
+
<name>:: The name of the object to destroy. For example, you can call
|
1041
|
+
"script/destroy leaf Scorekeeper" to remove a leaf named Scorekeeper.
|
1042
|
+
|
1043
|
+
<tt>--help, -h</tt>:: Displays this usage information.
|
1044
|
+
<tt>--vcs, -c</tt>:: Remove any created files or directories from the project's
|
1045
|
+
version control system. (Autodetects CVS, Git, and
|
1046
|
+
Subversion.)
|
1047
|
+
|
1048
|
+
=== script/generate
|
1049
|
+
|
1050
|
+
Generates template files for leaves, seasons, and other Autumn objects.
|
1051
|
+
|
1052
|
+
Usage: script/generate <options> <template> <name>
|
1053
|
+
|
1054
|
+
<template>:: The template to create. Valid templates are "leaf" and "season".
|
1055
|
+
<name>:: The name to give the created template. For example, you can call
|
1056
|
+
"script/generate leaf Scorekeeper" to create a leaf named Scorekeeper.
|
1057
|
+
|
1058
|
+
<tt>--help, -h</tt>:: Displays this usage information.
|
1059
|
+
<tt>--vcs, -c</tt>:: Add any created files or directories to the project's
|
1060
|
+
version control system. (Autodetects CVS, Git, and
|
1061
|
+
Subversion.)
|
1062
|
+
|
1063
|
+
=== autumn start
|
1064
|
+
|
1065
|
+
Runs Autumn from the command line. This script will not exit until all leaves
|
1066
|
+
have exited. You can set the SEASON environment variable to override the season.
|
1067
|
+
|
1068
|
+
== Thread Safety
|
1069
|
+
|
1070
|
+
Autumn is a multi-threaded IRC client. When a message is received, a new thread
|
1071
|
+
is spawned to process the message. In this thread, the message will be parsed,
|
1072
|
+
and all listener hooks will be invoked, including your leaf's methods. The
|
1073
|
+
thread will terminate once the message has been fully processed and all methods
|
1074
|
+
invoked.
|
1075
|
+
|
1076
|
+
I have made every effort to ensure that Autumn::Stem and Autumn::Leaf are
|
1077
|
+
thread-safe, as well as other relevant support classes such as Autumn::CTCP. It
|
1078
|
+
is now in your hands to ensure your leaves are thread-safe! This basically means
|
1079
|
+
recognizing that, while your leaf is churning away at whatever command it
|
1080
|
+
received, things can and will change in the background. If your command requires
|
1081
|
+
your leaf to have operator privileges, write your code under the assumption that
|
1082
|
+
operator could be taken from your leaf in the middle of executing the command.
|
1083
|
+
Write data in critical blocks, use transactions in your database calls ... you
|
1084
|
+
know the deal. Don't assume things will be the same between one line of code and
|
1085
|
+
the next.
|
1086
|
+
|
1087
|
+
If you require thread synchronization at the expense of performance, you can use
|
1088
|
+
the <tt>:stem_sync</tt> annotation. See the Autumn::Stem class docs under
|
1089
|
+
"Synchronous Methods" for more information.
|
1090
|
+
|
1091
|
+
== Getting Ready for Deployment
|
1092
|
+
|
1093
|
+
There's only a few things you need to do once your leaf is ready to greet
|
1094
|
+
the Real World:
|
1095
|
+
|
1096
|
+
1. Create a new production season. Configure your stems, leaves, and database
|
1097
|
+
as necessary for your production environment.
|
1098
|
+
2. In config/global.yml, set the season to your production season.
|
1099
|
+
3. If desired, user autumn start -m to set the <tt>:monitor</tt> option to true. This
|
1100
|
+
will spawn a monitor process that will relaunch Autumn if it crashes.
|
1101
|
+
|
1102
|
+
== Other Information
|
1103
|
+
|
1104
|
+
Please see http://github.com/RISCfuture/autumn/wikis/known-bugs for a list of
|
1105
|
+
known bugs, and http://github.com/RISCfuture/autumn/wikis/version-history for
|
1106
|
+
complete version history.
|
1107
|
+
|
1108
|
+
*<i>Why do you require Facets?</i>, I hear you ask. Facets doesn't add any super
|
1109
|
+
awesome new features to Ruby like Daemons or DataMapper does. It does, however,
|
1110
|
+
improve code reuse, and I'm a big fan of that. Why should a million different
|
1111
|
+
Ruby projects all write the same <tt>Symbol#to_proc</tt> method or the same
|
1112
|
+
<tt>Hash#symbolize_keys</tt> method? I use Facets because that job has already
|
1113
|
+
been done, and staying DRY means staying DRY _between_ codebases, not just
|
1114
|
+
within them.
|