comboy-autumn 3.1

Sign up to get free protection for your applications and to get access to all the features.
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