comboy-autumn 3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/README.textile +1192 -0
  2. data/autumn.gemspec +25 -0
  3. data/bin/autumn +27 -0
  4. data/lib/autumn.rb +2 -0
  5. data/lib/autumn/authentication.rb +290 -0
  6. data/lib/autumn/channel_leaf.rb +107 -0
  7. data/lib/autumn/coder.rb +166 -0
  8. data/lib/autumn/console_boot.rb +9 -0
  9. data/lib/autumn/ctcp.rb +250 -0
  10. data/lib/autumn/daemon.rb +207 -0
  11. data/lib/autumn/datamapper_hacks.rb +290 -0
  12. data/lib/autumn/foliater.rb +231 -0
  13. data/lib/autumn/formatting.rb +236 -0
  14. data/lib/autumn/generator.rb +231 -0
  15. data/lib/autumn/genesis.rb +191 -0
  16. data/lib/autumn/inheritable_attributes.rb +162 -0
  17. data/lib/autumn/leaf.rb +738 -0
  18. data/lib/autumn/log_facade.rb +49 -0
  19. data/lib/autumn/misc.rb +87 -0
  20. data/lib/autumn/script.rb +74 -0
  21. data/lib/autumn/speciator.rb +165 -0
  22. data/lib/autumn/stem.rb +919 -0
  23. data/lib/autumn/stem_facade.rb +176 -0
  24. data/resources/daemons/Anothernet.yml +3 -0
  25. data/resources/daemons/AustHex.yml +29 -0
  26. data/resources/daemons/Bahamut.yml +67 -0
  27. data/resources/daemons/Dancer.yml +3 -0
  28. data/resources/daemons/GameSurge.yml +3 -0
  29. data/resources/daemons/IRCnet.yml +3 -0
  30. data/resources/daemons/Ithildin.yml +7 -0
  31. data/resources/daemons/KineIRCd.yml +56 -0
  32. data/resources/daemons/PTlink.yml +6 -0
  33. data/resources/daemons/QuakeNet.yml +20 -0
  34. data/resources/daemons/RFC1459.yml +158 -0
  35. data/resources/daemons/RFC2811.yml +16 -0
  36. data/resources/daemons/RFC2812.yml +36 -0
  37. data/resources/daemons/RatBox.yml +25 -0
  38. data/resources/daemons/Ultimate.yml +24 -0
  39. data/resources/daemons/Undernet.yml +6 -0
  40. data/resources/daemons/Unreal.yml +110 -0
  41. data/resources/daemons/_Other.yml +7 -0
  42. data/resources/daemons/aircd.yml +33 -0
  43. data/resources/daemons/bdq-ircd.yml +3 -0
  44. data/resources/daemons/hybrid.yml +38 -0
  45. data/resources/daemons/ircu.yml +67 -0
  46. data/resources/daemons/tr-ircd.yml +8 -0
  47. data/skel/Rakefile +135 -0
  48. data/skel/config/global.yml +2 -0
  49. data/skel/config/seasons/testing/database.yml +7 -0
  50. data/skel/config/seasons/testing/leaves.yml +7 -0
  51. data/skel/config/seasons/testing/season.yml +2 -0
  52. data/skel/config/seasons/testing/stems.yml +9 -0
  53. data/skel/leaves/administrator/README +20 -0
  54. data/skel/leaves/administrator/controller.rb +67 -0
  55. data/skel/leaves/administrator/views/autumn.txt.erb +1 -0
  56. data/skel/leaves/administrator/views/reload.txt.erb +11 -0
  57. data/skel/leaves/insulter/README +17 -0
  58. data/skel/leaves/insulter/controller.rb +65 -0
  59. data/skel/leaves/insulter/views/about.txt.erb +1 -0
  60. data/skel/leaves/insulter/views/help.txt.erb +1 -0
  61. data/skel/leaves/insulter/views/insult.txt.erb +1 -0
  62. data/skel/leaves/scorekeeper/README +34 -0
  63. data/skel/leaves/scorekeeper/config.yml +2 -0
  64. data/skel/leaves/scorekeeper/controller.rb +104 -0
  65. data/skel/leaves/scorekeeper/helpers/general.rb +64 -0
  66. data/skel/leaves/scorekeeper/models/channel.rb +12 -0
  67. data/skel/leaves/scorekeeper/models/person.rb +14 -0
  68. data/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
  69. data/skel/leaves/scorekeeper/models/score.rb +14 -0
  70. data/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
  71. data/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
  72. data/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
  73. data/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
  74. data/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
  75. data/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
  76. data/skel/script/console +34 -0
  77. data/skel/script/daemon +29 -0
  78. data/skel/script/destroy +48 -0
  79. data/skel/script/generate +48 -0
  80. data/skel/script/server +15 -0
  81. metadata +170 -0
@@ -0,0 +1,166 @@
1
+ # Defines the Autumn::Coder class and its subclasses, which generate template
2
+ # code for the script/generate utility.
3
+
4
+ module Autumn
5
+
6
+ # Helper class that generates shell Ruby code. This class knows how to
7
+ # generate Ruby code for template classes and methods.
8
+
9
+ class Coder # :nodoc:
10
+ # The generated code string.
11
+ attr :output
12
+
13
+ # Creates a new instance with an indent level of 0 and an empty output
14
+ # string.
15
+
16
+ def initialize
17
+ @indent = 0
18
+ @output = String.new
19
+ end
20
+
21
+ # Creates a new class empty class. This method yields another Generator,
22
+ # which you can populate with the contents of the class, if you wish.
23
+ # Example:
24
+ #
25
+ # gen.klass("Foo") { |foo| foo.method("bar") }
26
+ #
27
+ # produces:
28
+ #
29
+ # class Foo
30
+ # def bar
31
+ # end
32
+ # end
33
+
34
+ def klass(name, superclass=nil)
35
+ if superclass then
36
+ self << "class #{name} < #{superclass}"
37
+ else
38
+ self << "class #{name}"
39
+ end
40
+
41
+ if block_given? then
42
+ generator = self.class.new
43
+ yield generator
44
+ indent!
45
+ self << generator.output
46
+ unindent!
47
+ end
48
+
49
+ self << "end"
50
+
51
+ return self
52
+ end
53
+
54
+ # Creates a new empty method. Any additional parameters are considered to be
55
+ # the generated method's parameters. They can be symbols/strings (taken to
56
+ # be the parameter's name), or hashes associating the parameter's name to
57
+ # its default value.
58
+ #
59
+ # This method yields another Generator, which you can populate with the
60
+ # contents of the method, if you wish. Example:
61
+ #
62
+ # gen.method("test", :required, { :optional => 'default' })
63
+ #
64
+ # produces:
65
+ #
66
+ # def test(required, optional="default")
67
+ # end
68
+
69
+ def method(name, *params)
70
+ if params.empty? then
71
+ self << "def #{name}"
72
+ else
73
+ self << "def #{name}(#{parameterize params})"
74
+ end
75
+
76
+ if block_given? then
77
+ generator = self.class.new
78
+ yield generator
79
+ indent!
80
+ self << generator.output
81
+ unindent!
82
+ end
83
+
84
+ self << "end"
85
+
86
+ return self
87
+ end
88
+
89
+ # Increases the indent level for all future lines of code appended to this
90
+ # Generator.
91
+
92
+ def indent!
93
+ @indent = @indent + 1
94
+ end
95
+
96
+ # Decreases the indent level for all future lines of code appended to this
97
+ # Generator.
98
+
99
+ def unindent!
100
+ @indent = @indent - 1 unless @indent == 0
101
+ end
102
+
103
+ # Adds a line of code to this Generator, sequentially.
104
+
105
+ def <<(str)
106
+ str.split(/\n/).each do |line|
107
+ @output << "#{tab}#{line}\n"
108
+ end
109
+ end
110
+
111
+ def doc=(str)
112
+ doc_lines = str.line_wrap(80 - tab.size - 2).split("\n")
113
+ doc_lines.map! { |str| "#{tab}# #{str}\n" }
114
+ @output = doc_lines.join + "\n" + @output
115
+ end
116
+
117
+ # Appends a blank line to the output.
118
+
119
+ def newline!
120
+ @output << "\n"
121
+ end
122
+
123
+ private
124
+
125
+ def parameterize(params)
126
+ param_strs = Array.new
127
+ params.each do |param|
128
+ if param.kind_of? Hash and param.size == 1 then
129
+ name = param.keys.only
130
+ default = param.values.only
131
+ raise ArgumentError, "Invalid parameter #{name.inspect}" unless name.respond_to? :to_s and not name.to_s.empty?
132
+ param_strs << "#{name.to_s}=#{default.inspect}"
133
+ elsif param.respond_to? :to_s and not param.to_s.empty? then
134
+ param_strs << param.to_s
135
+ else
136
+ raise ArgumentError, "Invalid parameter #{param.inspect}"
137
+ end
138
+ end
139
+ return param_strs.join(', ')
140
+ end
141
+
142
+ def tab
143
+ ' ' * @indent
144
+ end
145
+ end
146
+
147
+ # Generates Autumn-specific code templates like leaves and filters.
148
+
149
+ class TemplateCoder < Coder # :nodoc:
150
+
151
+ # Generates an Leaf subclass with the given name.
152
+
153
+ def leaf(name)
154
+ controller = klass('Controller', 'Autumn::Leaf') do |leaf|
155
+ leaf.newline!
156
+ leaf << '# Typing "!about" displays some basic information about this leaf.'
157
+ leaf.newline!
158
+ about_command = leaf.method('about_command', 'stem', 'sender', 'reply_to', 'msg') do |about|
159
+ about << '# This method renders the file "about.txt.erb"'
160
+ end
161
+ end
162
+ controller.doc = "Controller for the #{name.camelcase} leaf."
163
+ return controller
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,9 @@
1
+ # Used by the script/console script to load the Autumn environment when IRb
2
+ # is executed.
3
+
4
+ require 'autumn/genesis'
5
+
6
+ # We set APP_ROOT in the script/console file to set it for the scope of that
7
+ # file. We have to set it again in here for the scope of the IRb session.
8
+ @genesis = Autumn::Genesis.new
9
+ @genesis.boot! false
@@ -0,0 +1,250 @@
1
+ # Defines the Autum::CTCP class, which implements CTCP support.
2
+
3
+ require 'time'
4
+
5
+ module Autumn
6
+
7
+ # A listener for a Stem that listens for and handles CTCP requests. You can
8
+ # add CTCP support for your IRC client by instantiating this object and
9
+ # passing it to the Stem#add_listener method.
10
+ #
11
+ # CTCP stands for Client-to-Client Protocol and is a way that IRC clients and
12
+ # servers can request and transmit more information about each other. Modern
13
+ # IRC clients all have CTCP support, and many servers expect or assume that
14
+ # their clients support CTCP. CTCP is also used as a basis for further
15
+ # extensions to IRC, such as DCC and XDCC.
16
+ #
17
+ # This class implements the spec defined at http://www.invlogic.com/irc/ctcp.html
18
+ #
19
+ # Because some IRC servers will disconnect clients that send a large number of
20
+ # messages in a short period of time, this listener will only send one CTCP
21
+ # reply per second, with up to a maximum of 10 replies waiting in the queue
22
+ # (after which new requests are ignored). These values can be adjusted in the
23
+ # initialization options.
24
+ #
25
+ # This class acts as a listener plugin: Any of the methods specified below can
26
+ # be implemented by any other listener, and will be invoked by this listener
27
+ # when appropriate.
28
+ #
29
+ # To respond to incoming CTCP requests, you should implement methods of the
30
+ # form <tt>ctcp_*_request</tt>, where "*" is replaced with the lowercase name
31
+ # of the CTCP command. (For example, to handle VERSION requests, implement
32
+ # +ctcp_version_request+). This method will be invoked whenever a request is
33
+ # received by the IRC client. It will be given the following parameters:
34
+ #
35
+ # 1. the CTCP instance that parsed the request,
36
+ # 2. the Stem instance that received the request,
37
+ # 3. the person who sent the request (a hash in the form of that used by Stem;
38
+ # see Stem#add_listener for more information), and
39
+ # 4. an array of arguments passed along with the request.
40
+ #
41
+ # In addition, you can implement +ctcp_request_received+, which will then be
42
+ # invoked for any and all incoming CTCP requests. It is passed the following
43
+ # arguments:
44
+ #
45
+ # 1. the name of the request, as a lowercase symbol,
46
+ # 2. the CTCP instance that parsed the request,
47
+ # 3. the Stem instance that received the request,
48
+ # 4. the person who sent the request (a sender hash -- see the Leaf docs), and
49
+ # 5. an array of arguments passed along with the request.
50
+ #
51
+ # This class will by default respond to some incoming CTCP requests and
52
+ # generate appropriate replies; however, it does not implement any specific
53
+ # behavior for parsing incoming replies. If you wish to parse replies, you
54
+ # should implement methods in your listener of the form
55
+ # <tt>ctcp_*_response</tt>, with the "*" character replaced as above. This
56
+ # method will be invoked whenever a reply is received by this listener. You
57
+ # can also implement <tt>ctcp_response_received</tt> just as above. The
58
+ # parameters for these methods are the same as those listed above.
59
+ #
60
+ # Responses are assumed to be any CTCP messages that are sent as a NOTICE (as
61
+ # opposed to a PRIVMSG). Because they are NOTICEs, your program should not
62
+ # send a message in response.
63
+ #
64
+ # In addition to responding to incoming CTCP requests and replies, your
65
+ # listener can use its stem to send CTCP requests and replies. See the added
66
+ # method for more detail.
67
+
68
+ class CTCP
69
+ # Format of an embedded CTCP request.
70
+ CTCP_REQUEST = /\x01(.+?)\x01/
71
+ # CTCP commands whose arguments are encoded according to the CTCP spec (as
72
+ # opposed to other commands, whose arguments are plaintext).
73
+ ENCODED_COMMANDS = [ 'VERSION', 'PING' ]
74
+
75
+ # Creates a new CTCP parser. Options are:
76
+ #
77
+ # +reply_queue_size+:: The maximum number of pending replies to store in the
78
+ # queue, after which new CTCP requests are ignored.
79
+ # +reply_rate+:: The minimum time, in seconds, between consecutive CTCP
80
+ # replies.
81
+
82
+ def initialize(options={})
83
+ @options = options
84
+ @options[:reply_queue_size] ||= 10
85
+ @options[:reply_rate] ||= 0.25
86
+ @reply_thread = Hash.new
87
+ @reply_queue = Hash.new do |hsh, key|
88
+ hsh[key] = ForgetfulQueue.new(@options[:reply_queue_size])
89
+ @reply_thread[key] = Thread.new(key) do |stem|
90
+ loop do #TODO wake thread when stem is quitting so this thread can terminate?
91
+ reply = @reply_queue[stem].pop
92
+ stem.notice reply[:recipient], reply[:message]
93
+ sleep @options[:reply_rate]
94
+ end
95
+ end
96
+ hsh[key]
97
+ end
98
+ end
99
+
100
+ # Parses CTCP requests in a PRIVMSG event.
101
+
102
+ def irc_privmsg_event(stem, sender, arguments) # :nodoc:
103
+ arguments[:message].scan(CTCP_REQUEST).flatten.each do |ctcp|
104
+ ctcp_args = ctcp.split(' ')
105
+ request = ctcp_args.shift
106
+ ctcp_args = ctcp_args.map { |arg| unquote arg } if ENCODED_COMMANDS.include? request
107
+ meth = "ctcp_#{request.downcase}_request".to_sym
108
+ stem.broadcast meth, self, stem, sender, ctcp_args
109
+ stem.broadcast :ctcp_request_received, request.downcase.to_sym, self, stem, sender, ctcp_args
110
+ end
111
+ end
112
+
113
+ # Parses CTCP responses in a NOTICE event.
114
+
115
+ def irc_notice_event(stem, sender, arguments) # :nodoc:
116
+ arguments[:message].scan(CTCP_REQUEST).flatten.each do |ctcp|
117
+ ctcp_args = ctcp.split(' ')
118
+ request = ctcp_args.shift
119
+ ctcp_args = ctcp_args.map { |arg| unquote arg } if ENCODED_COMMANDS.include? request
120
+ meth = "ctcp_#{request.downcase}_response".to_sym
121
+ stem.broadcast meth, self, stem, sender, ctcp_args
122
+ stem.broadcast :ctcp_response_received, request.downcase.to_sym, self, stem, sender, ctcp_args
123
+ end
124
+ end
125
+
126
+ # Replies to a CTCP VERSION request by sending:
127
+ #
128
+ # * the name of the IRC client ("Autumn, a Ruby IRC framework"),
129
+ # * the operating system name and version, and
130
+ # * the home page URL for Autumn.
131
+ #
132
+ # To determine the OS name and version, this method runs the <tt>uname
133
+ # -sr</tt> command; if your operating system does not support this command,
134
+ # you should override this method.
135
+ #
136
+ # Although the CTCP spec states that the VERSION response should be three
137
+ # encoded strings (as shown above), many modern clients expect one plaintext
138
+ # string. If you'd prefer compatibility with those clients, you should
139
+ # override this method to return a single plaintext string and remove the
140
+ # VERSION command from +ENCODED_COMMANDS+.
141
+
142
+ def ctcp_version_request(handler, stem, sender, arguments)
143
+ return unless handler == self
144
+ send_ctcp_reply stem, sender[:nick], 'VERSION', "Autumn #{AUTUMN_VERSION}, a Ruby IRC framework", `uname -sr`, "http://github.com/RISCfuture/autumn"
145
+ end
146
+
147
+ # Replies to a CTCP PING request by sending back the same arguments as a
148
+ # PONG reply.
149
+
150
+ def ctcp_ping_request(handler, stem, sender, arguments)
151
+ return unless handler == self
152
+ send_ctcp_reply stem, sender[:nick], 'PING', *arguments
153
+ end
154
+
155
+ # Replies to a CTCP TIME request by sending back the local time in RFC-822
156
+ # format.
157
+
158
+ def ctcp_time_request(handler, stem, sender, arguments)
159
+ return unless handler == self
160
+ send_ctcp_reply stem, sender[:nick], 'TIME', Time.now.rfc822
161
+ end
162
+
163
+ # Adds a CTCP reply to the queue. You must pass the Stem instance that will
164
+ # be sending this reply, the recipient (channel or nick), and the name of
165
+ # the CTCP command (as an uppercase string). Any additional arguments are
166
+ # taken to be arguments of the CTCP reply, and are thus encoded and joined
167
+ # by space characters, as specified in the CTCP white paper. The arguments
168
+ # should all be strings.
169
+ #
170
+ # +recipient+ can be a nick, a channel name, or a sender hash, as necessary.
171
+ # Encoding of arguments is only done for commands in +ENCODED_COMMANDS+.
172
+
173
+ def send_ctcp_reply(stem, recipient, command, *arguments)
174
+ recipient = recipient[:nick] if recipient.kind_of? Hash
175
+ @reply_queue[stem] << { :recipient => recipient, :message => make_ctcp_message(command, *arguments) }
176
+ end
177
+
178
+ # When this listener is added to a stem, the stem gains the ability to send
179
+ # CTCP messages directly. Methods of the form <tt>ctcp_*</tt>, where "*" is
180
+ # the lowercase name of a CTCP action, will be forwarded to this listener,
181
+ # which will send the CTCP message. The first parameter of the method is the
182
+ # nick of one or more recipients; all other parameters are parameters for
183
+ # the CTCP command. See the CTCP spec for more information on the different
184
+ # commands and parameters available.
185
+ #
186
+ # For example, to send an action (such as "/me is cold") to a channel:
187
+ #
188
+ # stem.ctcp_action "#channel", "is cold"
189
+ #
190
+ # In addition, the stem gains the ability to send CTCP replies. Replies are
191
+ # messages that are added to this class's reply queue, adding flood abuse
192
+ # prevention. To send a reply, call a Stem method of the form
193
+ # <tt>ctcp_reply_*</tt>, where "*" is the command name you are replying to,
194
+ # in lowercase. Pass first the nick or sender hash of the recipient, then
195
+ # any parameters as specified by the CTCP spec. For example, to respond to a
196
+ # CTCP VERSION request:
197
+ #
198
+ # stem.ctcp_reply_version sender, 'Bot Name', 'Computer Name', 'Other Info'
199
+ #
200
+ # (Note that responding to VERSION requests is already handled by this class
201
+ # so you'll need to either override or delete the ctcp_version_request
202
+ # method to do this.)
203
+
204
+ def added(stem)
205
+ stem.instance_variable_set :@ctcp, self
206
+ class << stem
207
+ def method_missing(meth, *args)
208
+ if meth.to_s =~ /^ctcp_reply_([a-z]+)$/ then
209
+ @ctcp.send_ctcp_reply self, args.shift, $1.to_s.upcase, *args
210
+ elsif meth.to_s =~ /^ctcp_([a-z]+)$/ then
211
+ privmsg args.shift, @ctcp.make_ctcp_message($1.to_s.upcase, *args)
212
+ else
213
+ super
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ # Creates a CTCP-formatted message with the given command (uppercase string)
220
+ # and arguments (strings). The string returned is suitable for transmission
221
+ # over IRC using the PRIVMSG command.
222
+
223
+ def make_ctcp_message(command, *arguments)
224
+ arguments = arguments.map { |arg| quote arg } if ENCODED_COMMANDS.include? command
225
+ "\01#{arguments.unshift(command).join(' ')}\01"
226
+ end
227
+
228
+ private
229
+
230
+ def quote(str)
231
+ chars = str.split('')
232
+ chars.map! do |char|
233
+ case char
234
+ when "\0" then '\0'
235
+ when "\1" then '\1'
236
+ when "\n" then '\n'
237
+ when "\r" then '\r'
238
+ when " " then '\@'
239
+ when "\\" then '\\\\'
240
+ else char
241
+ end
242
+ end
243
+ return chars.join
244
+ end
245
+
246
+ def unquote(str)
247
+ str.gsub('\\\\', '\\').gsub('\@', " ").gsub('\r', "\r").gsub('\n', "\n").gsub('\1', "\1").gsub('\0', "\0")
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,207 @@
1
+ # Defines the Autumn::Daemon class, which stores information on the different
2
+ # implementations of IRC by different server daemons.
3
+
4
+ module Autumn
5
+
6
+ # Describes an IRC server daemon program. Different IRC servers run off of
7
+ # different IRC daemons, each of which has a slightly different implementation
8
+ # of the IRC protocol. To encapsulate this, the Daemon class stores the names
9
+ # of some of the more common IRC server types, as well as the unique
10
+ # information about those daemons, such as supported usermodes, response
11
+ # codes, and supported channel types.
12
+ #
13
+ # An instance of Daemon is an IRC server type. The Daemon class keeps a
14
+ # catalog of all instances, assigning each a descriptive name (for example,
15
+ # "Unreal" for the UnrealIRCd program, a popular IRC server daemon).
16
+ #
17
+ # A Daemon instance can be queried for information about the IRC server type,
18
+ # as necessary to parse messages from that IRC server.
19
+ #
20
+ # A "default" daemon will also be created representing a common denominator of
21
+ # IRC features, which is used in situations where a server's exact type is
22
+ # unknown. This daemon will consist of all non-conflicting entries among the
23
+ # defined daemons.
24
+ #
25
+ # In addition to the methods and attributes below, you can also call predicate
26
+ # methods such as <tt>usermode?</tt> and <tt>channel_prefix?</tt> to test if a
27
+ # character is in a set of known modes/privileges/prefixes, or if a number is
28
+ # in the set of known events.
29
+
30
+ class Daemon
31
+
32
+ # Creates a new Daemon instance associated with a given name. You must also
33
+ # pass in the hashes for it to store.
34
+
35
+ def initialize(name, info)
36
+ if name.nil? and info.nil? then # it's the default hash
37
+ raise "Already created a default Daemon" if self.class.class_variable_defined? :@@default
38
+ @usermode = Hash.parroting
39
+ @privilege = Hash.parroting
40
+ @user_prefix = Hash.parroting
41
+ @channel_prefix = Hash.parroting
42
+ @channel_mode = Hash.parroting
43
+ @server_mode = Hash.parroting
44
+ @event = Hash.parroting
45
+ @default = true
46
+ else
47
+ @usermode = Hash.parroting(info['usermode'])
48
+ @privilege = Hash.parroting(info['privilege'])
49
+ @user_prefix = Hash.parroting(info['user_prefix'])
50
+ @channel_prefix = Hash.parroting(info['channel_prefix'])
51
+ @channel_mode = Hash.parroting(info['channel_mode'])
52
+ @server_mode = Hash.parroting(info['server_mode'])
53
+ @event = Hash.parroting(info['event'])
54
+ @@instances[name] = self
55
+
56
+ # Build up our default so it contains all keys with no conflicting
57
+ # values across different server specs. Delete keys from the default
58
+ # hash for which we found duplicates.
59
+ info.each do |hname, hsh|
60
+ next unless @@default.respond_to? hname.to_sym
61
+ default_hash = @@default.send(hname.to_sym)
62
+
63
+ uniques = hsh.reject { |k, v| default_hash.include? k }
64
+ default_hash.update uniques
65
+ default_hash.reject! { |k, v| hsh.include?(k) and hsh[k] != v }
66
+ end
67
+ end
68
+ end
69
+
70
+ # Returns a Daemon instance by associated name.
71
+
72
+ def self.[](name)
73
+ @@instances[name]
74
+ end
75
+
76
+ # Returns the fallback server type.
77
+
78
+ def self.default
79
+ @@default
80
+ end
81
+
82
+ # Yields the name of each Daemon registered with the class.
83
+
84
+ def self.each_name
85
+ @@instances.keys.sort.each { |name| yield name }
86
+ end
87
+
88
+ # Hash of usermode characters (e.g., <tt>i</tt>) to their symbol
89
+ # representations (e.g., <tt>:invisible</tt>).
90
+
91
+ def usermode
92
+ @default ? @usermode : @@default.usermode.merge(@usermode)
93
+ end
94
+
95
+ # Hash of user privilege characters (e.g., <tt>v</tt>) to their symbol
96
+ # representations (e.g., <tt>:voiced</tt>).
97
+
98
+ def privilege
99
+ @default ? @privilege : @@default.privilege.merge(@privilege)
100
+ end
101
+
102
+ # Hash of user privilege prefixes (e.g., <tt>@</tt>) to their symbol
103
+ # representations (e.g., <tt>:operator</tt>).
104
+
105
+ def user_prefix
106
+ @default ? @user_prefix : @@default.user_prefix.merge(@user_prefix)
107
+ end
108
+
109
+ # Hash of channel prefixes (e.g., <tt>&</tt>) to their symbol
110
+ # representations (e.g., <tt>:local</tt>).
111
+
112
+ def channel_prefix
113
+ @default ? @channel_prefix : @@default.channel_prefix.merge(@channel_prefix)
114
+ end
115
+
116
+ # Hash of channel mode characters (e.g., <tt>m</tt>) to their symbol
117
+ # representations (e.g., <tt>:moderated</tt>).
118
+
119
+ def channel_mode
120
+ @default ? @channel_mode : @@default.channel_mode.merge(@channel_mode)
121
+ end
122
+
123
+ # Hash of server mode characters (e.g., <tt>H</tt>) to their symbol
124
+ # representations (e.g., <tt>:hidden</tt>).
125
+
126
+ def server_mode
127
+ @default ? @server_mode : @@default.server_mode.merge(@server_mode)
128
+ end
129
+
130
+ # Hash of numerical event codes (e.g., 372) to their symbol representations
131
+ # (e.g., <tt>:motd</tt>).
132
+
133
+ def event
134
+ @default ? @event : @@default.event.merge(@event)
135
+ end
136
+
137
+ # Returns true if the mode string (e.g., "+v") appears to be changing a user
138
+ # privilege as opposed to a channel mode.
139
+
140
+ def privilege_mode?(mode)
141
+ raise ArgumentError, "Invalid mode string '#{mode}'" unless mode =~ /^[\+\-]\S+$/
142
+ mode.except_first.chars.all? { |c| privilege? c }
143
+ end
144
+
145
+ # Returns true if the first character(s) of a nick are valid privilege
146
+ # prefixes.
147
+
148
+ def nick_prefixed?(nick)
149
+ user_prefix? nick[0,1]
150
+ end
151
+
152
+ # Given a nick, returns that nick stripped of any privilege characters on
153
+ # the left side.
154
+
155
+ def just_nick(name)
156
+ nick = name.dup
157
+ while nick_prefixed?(nick)
158
+ nick.slice! 0, 1
159
+ end
160
+ return nick
161
+ end
162
+
163
+ # Given a nick, returns the privileges granted to this nick, as indicated by
164
+ # the prefix characters. Returns :unvoiced if no prefix characters are
165
+ # present. Returns an array of privileges if multiple prefix characters are
166
+ # present.
167
+
168
+ def nick_privilege(name)
169
+ privs = Set.new
170
+ nick = name.dup
171
+ while user_prefix? nick[0,1]
172
+ privs << user_prefix[nick[0,1]]
173
+ nick.slice! 0, 1
174
+ end
175
+ case privs.size
176
+ when 0 then :unvoiced
177
+ when 1 then privs.only
178
+ else privs
179
+ end
180
+ end
181
+
182
+ def method_missing(meth, *args) # :nodoc:
183
+ if meth.to_s =~ /^([a-z_]+)\?$/ then
184
+ base = $1
185
+ if (instance_variables.include?("@#{base}") or instance_variables.include?("@#{base}".to_sym)) and args.size == 1 then
186
+ if base.end_with?('_prefix') and args.only.kind_of?(Numeric) then
187
+ arg = args.only.chr
188
+ else
189
+ arg = args.only
190
+ end
191
+ eval "#{base}.include? #{arg.inspect}"
192
+ end
193
+ else
194
+ super
195
+ end
196
+ end
197
+
198
+ def inspect # :nodoc:
199
+ "#<#{self.class.to_s} #{@@instances.index self}>"
200
+ end
201
+
202
+ private
203
+
204
+ @@instances = Hash.new
205
+ @@default = self.new(nil, nil)
206
+ end
207
+ end