3 # * when Users are deleted, we have to delete them from the appropriate
\r
5 # * do we want to handle a Channel list for each User telling which
\r
6 # Channels is the User on (of those the client is on too)?
\r
12 # This module defines the fundamental building blocks for IRC
\r
14 # Author:: Giuseppe Bilotta (giuseppe.bilotta@gmail.com)
\r
15 # Copyright:: Copyright (c) 2006 Giuseppe Bilotta
\r
19 # We start by extending the String class
\r
20 # with some IRC-specific methods
\r
24 # This method returns a string which is the downcased version of the
\r
25 # receiver, according to IRC rules: due to the Scandinavian origin of IRC,
\r
26 # the characters <tt>{}|^</tt> are considered the uppercase equivalent of
\r
29 # Since IRC is mostly case-insensitive (the Windows way: case is preserved,
\r
30 # but it's actually ignored to check equality), this method is rather
\r
31 # important when checking if two strings refer to the same entity
\r
34 # Modern server allow different casemaps, too, in which some or all
\r
35 # of the extra characters are not converted
\r
37 def irc_downcase(casemap='rfc1459')
\r
40 self.tr("\x41-\x5e", "\x61-\x7e")
\r
41 when 'strict-rfc1459'
\r
42 self.tr("\x41-\x5d", "\x61-\x7d")
\r
44 self.tr("\x41-\x5a", "\x61-\x7a")
\r
46 raise TypeError, "Unknown casemap #{casemap}"
\r
50 # This is the same as the above, except that the string is altered in place
\r
52 # See also the discussion about irc_downcase
\r
54 def irc_downcase!(casemap='rfc1459')
\r
57 self.tr!("\x41-\x5e", "\x61-\x7e")
\r
58 when 'strict-rfc1459'
\r
59 self.tr!("\x41-\x5d", "\x61-\x7d")
\r
61 self.tr!("\x41-\x5a", "\x61-\x7a")
\r
63 raise TypeError, "Unknown casemap #{casemap}"
\r
67 # Upcasing functions are provided too
\r
69 # See also the discussion about irc_downcase
\r
71 def irc_upcase(casemap='rfc1459')
\r
74 self.tr("\x61-\x7e", "\x41-\x5e")
\r
75 when 'strict-rfc1459'
\r
76 self.tr("\x61-\x7d", "\x41-\x5d")
\r
78 self.tr("\x61-\x7a", "\x41-\x5a")
\r
80 raise TypeError, "Unknown casemap #{casemap}"
\r
86 # See also the discussion about irc_downcase
\r
88 def irc_upcase!(casemap='rfc1459')
\r
91 self.tr!("\x61-\x7e", "\x41-\x5e")
\r
92 when 'strict-rfc1459'
\r
93 self.tr!("\x61-\x7d", "\x41-\x5d")
\r
95 self.tr!("\x61-\x7a", "\x41-\x5a")
\r
97 raise TypeError, "Unknown casemap #{casemap}"
\r
101 # This method checks if the receiver contains IRC glob characters
\r
103 # IRC has a very primitive concept of globs: a <tt>*</tt> stands for "any
\r
104 # number of arbitrary characters", a <tt>?</tt> stands for "one and exactly
\r
105 # one arbitrary character". These characters can be escaped by prefixing them
\r
106 # with a slash (<tt>\\</tt>).
\r
108 # A known limitation of this glob syntax is that there is no way to escape
\r
109 # the escape character itself, so it's not possible to build a glob pattern
\r
110 # where the escape character precedes a glob.
\r
113 self =~ /^[*?]|[^\\][*?]/
\r
116 # This method is used to convert the receiver into a Regular Expression
\r
117 # that matches according to the IRC glob syntax
\r
120 regmask = Regexp.escape(self)
\r
121 regmask.gsub!(/(\\\\)?\\[*?]/) { |m|
\r
130 raise "Unexpected match #{m} when converting #{self}"
\r
133 Regexp.new(regmask)
\r
138 # ArrayOf is a subclass of Array whose elements are supposed to be all
\r
139 # of the same class. This is not intended to be used directly, but rather
\r
140 # to be subclassed as needed (see for example Irc::UserList and Irc::NetmaskList)
\r
142 # Presently, only very few selected methods from Array are overloaded to check
\r
143 # if the new elements are the correct class. An orthodox? method is provided
\r
144 # to check the entire ArrayOf against the appropriate class.
\r
146 class ArrayOf < Array
\r
148 attr_reader :element_class
\r
150 # Create a new ArrayOf whose elements are supposed to be all of type _kl_,
\r
151 # optionally filling it with the elements from the Array argument.
\r
153 def initialize(kl, ar=[])
\r
154 raise TypeError, "#{kl.inspect} must be a class name" unless kl.class <= Class
\r
156 @element_class = kl
\r
161 raise TypeError, "#{self.class} can only be initialized from an Array"
\r
165 # Private method to check the validity of the elements passed to it
\r
166 # and optionally raise an error
\r
168 # TODO should it accept nils as valid?
\r
170 def internal_will_accept?(raising, *els)
\r
172 unless el.class <= @element_class
\r
173 raise TypeError if raising
\r
179 private :internal_will_accept?
\r
181 # This method checks if the passed arguments are acceptable for our ArrayOf
\r
183 def will_accept?(*els)
\r
184 internal_will_accept?(false, *els)
\r
187 # This method checks that all elements are of the appropriate class
\r
190 will_accept?(*self)
\r
193 # This method is similar to the above, except that it raises an exception
\r
194 # if the receiver is not valid
\r
196 raise TypeError unless valid?
\r
199 # Overloaded from Array#<<, checks for appropriate class of argument
\r
202 super(el) if internal_will_accept?(true, el)
\r
205 # Overloaded from Array#unshift, checks for appropriate class of argument(s)
\r
209 super(el) if internal_will_accept?(true, *els)
\r
213 # Overloaded from Array#+, checks for appropriate class of argument elements
\r
216 super(ar) if internal_will_accept?(true, *ar)
\r
220 # The Irc module is used to keep all IRC-related classes
\r
221 # in the same namespace
\r
226 # A Netmask identifies each user by collecting its nick, username and
\r
227 # hostname in the form <tt>nick!user@host</tt>
\r
229 # Netmasks can also contain glob patterns in any of their components; in this
\r
230 # form they are used to refer to more than a user or to a user appearing
\r
235 # * <tt>*!*@*</tt> refers to everybody
\r
236 # * <tt>*!someuser@somehost</tt> refers to user +someuser+ on host +somehost+
\r
237 # regardless of the nick used.
\r
240 attr_reader :nick, :user, :host
\r
241 attr_reader :casemap
\r
244 # Netmask.new(netmask) => new_netmask
\r
245 # Netmask.new(hash={}, casemap=nil) => new_netmask
\r
246 # Netmask.new("nick!user@host", casemap=nil) => new_netmask
\r
248 # Create a new Netmask in any of these forms
\r
249 # 1. from another Netmask (does a .dup)
\r
250 # 2. from a Hash with any of the keys <tt>:nick</tt>, <tt>:user</tt> and
\r
252 # 3. from a String in the form <tt>nick!user@host</tt>
\r
254 # In all but the first forms a casemap may be speficied, the default
\r
257 # The nick is downcased following IRC rules and according to the given casemap.
\r
259 # FIXME check if user and host need to be downcased too.
\r
261 # Empty +nick+, +user+ or +host+ are converted to the generic glob pattern
\r
263 def initialize(str={}, casemap=nil)
\r
266 raise ArgumentError, "Can't set casemap when initializing from other Netmask" if casemap
\r
267 @casemap = str.casemap.dup
\r
268 @nick = str.nick.dup
\r
269 @user = str.user.dup
\r
270 @host = str.host.dup
\r
272 @casemap = casemap || str[:casemap] || 'rfc1459'
\r
273 @nick = str[:nick].to_s.irc_downcase(@casemap)
\r
274 @user = str[:user].to_s
\r
275 @host = str[:host].to_s
\r
277 if str.match(/(\S+)(?:!(\S+)@(?:(\S+))?)?/)
\r
278 @casemap = casemap || 'rfc1459'
\r
279 @nick = $1.irc_downcase(@casemap)
\r
283 raise ArgumentError, "#{str} is not a valid netmask"
\r
286 raise ArgumentError, "#{str} is not a valid netmask"
\r
289 @nick = "*" if @nick.to_s.empty?
\r
290 @user = "*" if @user.to_s.empty?
\r
291 @host = "*" if @host.to_s.empty?
\r
294 # This method changes the nick of the Netmask, downcasing the argument
\r
295 # following IRC rules and defaulting to the generic glob pattern if
\r
296 # the result is the null string.
\r
299 @nick = newnick.to_s.irc_downcase(@casemap)
\r
300 @nick = "*" if @nick.empty?
\r
303 # This method changes the user of the Netmask, defaulting to the generic
\r
304 # glob pattern if the result is the null string.
\r
307 @user = newuser.to_s
\r
308 @user = "*" if @user.empty?
\r
311 # This method changes the hostname of the Netmask, defaulting to the generic
\r
312 # glob pattern if the result is the null string.
\r
315 @host = newhost.to_s
\r
316 @host = "*" if @host.empty?
\r
319 # This method checks if a Netmask is definite or not, by seeing if
\r
320 # any of its components are defined by globs
\r
323 return @nick.has_irc_glob? || @user.has_irc_glob? || @host.has_irc_glob?
\r
326 # A Netmask is easily converted to a String for the usual representation
\r
329 return "#{nick}@#{user}!#{host}"
\r
332 # This method is used to match the current Netmask against another one
\r
334 # The method returns true if each component of the receiver matches the
\r
335 # corresponding component of the argument. By _matching_ here we mean that
\r
336 # any netmask described by the receiver is also described by the argument.
\r
338 # In this sense, matching is rather simple to define in the case when the
\r
339 # receiver has no globs: it is just necessary to check if the argument
\r
340 # describes the receiver, which can be done by matching it against the
\r
341 # argument converted into an IRC Regexp (see String#to_irc_regexp).
\r
343 # The situation is also easy when the receiver has globs and the argument
\r
344 # doesn't, since in this case the result is false.
\r
346 # The more complex case in which both the receiver and the argument have
\r
347 # globs is not handled yet.
\r
351 raise TypeError, "#{arg} and #{self} have different casemaps" if @casemap != cmp.casemap
\r
352 raise TypeError, "#{arg} is not a valid Netmask" unless cmp.class <= Netmask
\r
353 [:nick, :user, :host].each { |component|
\r
354 us = self.send(:component)
\r
355 them = cmp.send(:component)
\r
356 raise NotImplementedError if us.has_irc_glob? && them.has_irc_glob?
\r
357 return false if us.has_irc_glob? && !them.has_irc_glob?
\r
358 return false unless us =~ them.to_irc_regexp
\r
363 # Case equality. Checks if arg matches self
\r
366 Netmask(arg).matches?(self)
\r
371 # A NetmaskList is an ArrayOf <code>Netmask</code>s
\r
373 class NetmaskList < ArrayOf
\r
375 # Create a new NetmaskList, optionally filling it with the elements from
\r
376 # the Array argument fed to it.
\r
377 def initialize(ar=[])
\r
383 # An IRC User is identified by his/her Netmask (which must not have
\r
384 # globs). In fact, User is just a subclass of Netmask. However,
\r
385 # a User will not allow one's host or user data to be changed: only the
\r
386 # nick can be dynamic
\r
389 # * see if it's worth to add the other USER data
\r
390 # * see if it's worth to add AWAY status
\r
391 # * see if it's worth to add NICKSERV status
\r
393 class User < Netmask
\r
394 private :host=, :user=
\r
396 # Create a new IRC User from a given Netmask (or anything that can be converted
\r
397 # into a Netmask) provided that the given Netmask does not have globs.
\r
399 def initialize(str, casemap=nil)
\r
401 raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if has_irc_glob?
\r
406 # A UserList is an ArrayOf <code>User</code>s
\r
408 class UserList < ArrayOf
\r
410 # Create a new UserList, optionally filling it with the elements from
\r
411 # the Array argument fed to it.
\r
412 def initialize(ar=[])
\r
418 # An IRC Channel is identified by its name, and it has a set of properties:
\r
424 attr_reader :name, :type, :casemap
\r
426 # Create a new method. Auxiliary function for the following
\r
427 # auxiliary functions ...
\r
429 def create_method(name, &block)
\r
430 self.class.send(:define_method, name, &block)
\r
432 private :create_method
\r
434 # Create a new channel boolean flag
\r
436 def new_bool_flag(sym, acc=nil, default=false)
\r
437 @flags[sym.to_sym] = default
\r
438 racc = (acc||sym).to_s << "?"
\r
439 wacc = (acc||sym).to_s << "="
\r
440 create_method(racc.to_sym) { @flags[sym.to_sym] }
\r
441 create_method(wacc.to_sym) { |val|
\r
442 @flags[sym.to_sym] = val
\r
446 # Create a new channel flag with data
\r
448 def new_data_flag(sym, acc=nil, default=false)
\r
449 @flags[sym.to_sym] = default
\r
450 racc = (acc||sym).to_s
\r
451 wacc = (acc||sym).to_s << "="
\r
452 create_method(racc.to_sym) { @flags[sym.to_sym] }
\r
453 create_method(wacc.to_sym) { |val|
\r
454 @flags[sym.to_sym] = val
\r
458 # Create a new variable with accessors
\r
460 def new_variable(name, default=nil)
\r
461 v = "@#{name}".to_sym
\r
462 instance_variable_set(v, default)
\r
463 create_method(name.to_sym) { instance_variable_get(v) }
\r
464 create_method("#{name}=".to_sym) { |val|
\r
465 instance_variable_set(v, val)
\r
469 # Create a new UserList
\r
471 def new_userlist(name, default=UserList.new)
\r
472 new_variable(name, default)
\r
475 # Create a new NetmaskList
\r
477 def new_netmasklist(name, default=NetmaskList.new)
\r
478 new_variable(name, default)
\r
481 # Creates a new channel with the given name, optionally setting the topic
\r
482 # and an initial users list.
\r
484 # No additional info is created here, because the channel flags and userlists
\r
485 # allowed depend on the server.
\r
487 # FIXME doesn't check if users have the same casemap as the channel yet
\r
489 def initialize(name, topic="", users=[], casemap=nil)
\r
490 @casemap = casemap || 'rfc1459'
\r
492 raise ArgumentError, "Channel name cannot be empty" if name.to_s.empty?
\r
493 raise ArgumentError, "Unknown channel prefix #{name[0].chr}" if name !~ /^[&#+!]/
\r
494 raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/
\r
496 @name = name.irc_downcase(@casemap)
\r
498 new_variable(:topic, topic)
\r
500 new_userlist(:users)
\r
505 @users = UserList.new(users)
\r
507 raise ArgumentError, "Invalid user list #{users.inspect}"
\r
510 # new_variable(:creator)
\r
513 # new_userlist(:super_ops)
\r
514 # new_userlist(:ops)
\r
515 # new_userlist(:half_ops)
\r
516 # new_userlist(:voices)
\r
518 # # Ban and invite lists
\r
519 # new_netmasklist(:banlist)
\r
520 # new_netmasklist(:exceptlist)
\r
521 # new_netmasklist(:invitelist)
\r
525 # new_bool_flag(:a, :anonymous)
\r
526 # new_bool_flag(:i, :invite_only)
\r
527 # new_bool_flag(:m, :moderated)
\r
528 # new_bool_flag(:n, :no_externals)
\r
529 # new_bool_flag(:q, :quiet)
\r
530 # new_bool_flag(:p, :private)
\r
531 # new_bool_flag(:s, :secret)
\r
532 # new_bool_flag(:r, :will_reop)
\r
533 # new_bool_flag(:t, :free_topic)
\r
535 # new_data_flag(:k, :key)
\r
536 # new_data_flag(:l, :limit)
\r
539 # A channel is local to a server if it has the '&' prefix
\r
545 # A channel is modeless if it has the '+' prefix
\r
551 # A channel is safe if it has the '!' prefix
\r
557 # A channel is safe if it has the '#' prefix
\r
565 # A ChannelList is an ArrayOf <code>Channel</code>s
\r
567 class ChannelList < ArrayOf
\r
569 # Create a new ChannelList, optionally filling it with the elements from
\r
570 # the Array argument fed to it.
\r
571 def initialize(ar=[])
\r
577 # An IRC Server represents the Server the client is connected to.
\r
581 attr_reader :hostname, :version, :usermodes, :chanmodes
\r
582 attr_reader :supports, :capab
\r
584 attr_reader :channels, :users
\r
586 # Create a new Server, with all instance variables reset
\r
587 # to nil (for scalar variables), the channel and user lists
\r
588 # are empty, and @supports is initialized to the default values
\r
589 # for all known supported features.
\r
592 @hostname = @version = @usermodes = @chanmodes = nil
\r
594 :casemapping => 'rfc1459',
\r
597 :addr_list => nil, # Type A
\r
598 :has_param => nil, # Type B
\r
599 :set_param => nil, # Type C
\r
600 :no_params => nil # Type D
\r
602 :channellen => 200,
\r
603 :chantypes => "#&",
\r
613 :modes => 'ov'.scan(/./),
\r
614 :prefixes => '@+'.scan(/./)
\r
624 @channels = ChannelList.new
\r
625 @channel_names = Array.new
\r
627 @users = UserList.new
\r
628 @user_nicks = Array.new
\r
631 # This method is used to parse a 004 RPL_MY_INFO line
\r
633 def parse_my_info(line)
\r
634 ar = line.split(' ')
\r
641 def noval_warn(key, val, &block)
\r
643 yield if block_given?
\r
645 warn "No #{key.to_s.upcase} value"
\r
649 def val_warn(key, val, &block)
\r
650 if val == true or val == false or val.nil?
\r
651 yield if block_given?
\r
653 warn "No #{key.to_s.upcase} value must be specified, got #{val}"
\r
656 private :noval_warn, :val_warn
\r
658 # This method is used to parse a 005 RPL_ISUPPORT line
\r
660 # See the RPL_ISUPPORT draft[http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt]
\r
662 # TODO this is just an initial draft that does nothing special.
\r
663 # We want to properly parse most of the supported capabilities
\r
666 def parse_isupport(line)
\r
667 ar = line.split(' ')
\r
670 prekey, val = en.split('=', 2)
\r
671 if prekey =~ /^-(.*)/
\r
672 key = $1.downcase.to_sym
\r
675 key = prekey.downcase.to_sym
\r
678 when :casemapping, :network
\r
679 noval_warn(key, val) {
\r
680 @supports[key] = val
\r
682 when :chanlimit, :idchan, :maxlist, :targmax
\r
683 noval_warn(key, val) {
\r
684 groups = val.split(',')
\r
686 k, v = g.split(':')
\r
687 @supports[key][k] = v.to_i
\r
691 noval_warn(key, val) {
\r
692 reparse += "CHANLIMIT=(chantypes):#{val} "
\r
695 noval_warn(key, val) {
\r
696 @supports[key]['PRIVMSG'] = val.to_i
\r
697 @supports[key]['NOTICE'] = val.to_i
\r
700 noval_warn(key, val) {
\r
701 groups = val.split(',')
\r
702 @supports[key][:addr_list] = groups[0].scan(/./)
\r
703 @supports[key][:has_param] = groups[1].scan(/./)
\r
704 @supports[key][:set_param] = groups[2].scan(/./)
\r
705 @supports[key][:no_params] = groups[3].scan(/./)
\r
707 when :channellen, :kicklen, :modes, :topiclen
\r
709 @supports[key] = val.to_i
\r
711 @supports[key] = nil
\r
714 @supports[key] = val # can also be nil
\r
717 @supports[key] = val
\r
720 @supports[key] = val
\r
722 noval_warn(key, val) {
\r
723 @supports[key] = val.to_i
\r
727 val.scan(/\((.*)\)(.*)/) { |m, p|
\r
728 @supports[key][:modes] = m.scan(/./)
\r
729 @supports[key][:prefixes] = p.scan(/./)
\r
732 @supports[key][:modes] = nil
\r
733 @supports[key][:prefixes] = nil
\r
736 val_warn(key, val) {
\r
737 @supports[key] = val.nil? ? true : val
\r
740 noval_warn(key, val) {
\r
741 @supports[key] = val.scan(/./)
\r
744 noval_warn(key, val) {
\r
745 @supports[key] = val.split(',')
\r
748 @supports[key] = val.nil? ? true : val
\r
751 reparse.gsub!("(chantypes)",@supports[:chantypes])
\r
752 parse_isupport(reparse) unless reparse.empty?
\r
755 # Returns the casemap of the server.
\r
758 @supports[:casemapping] || 'rfc1459'
\r
761 # Checks if the receiver already has a channel with the given _name_
\r
763 def has_channel?(name)
\r
764 @channel_names.index(name)
\r
766 alias :has_chan? :has_channel?
\r
768 # Returns the channel with name _name_, if available
\r
770 def get_channel(name)
\r
771 idx = @channel_names.index(name)
\r
772 @channels[idx] if idx
\r
774 alias :get_chan :get_channel
\r
776 # Create a new Channel object and add it to the list of
\r
777 # <code>Channel</code>s on the receiver, unless the channel
\r
778 # was present already. In this case, the default action is
\r
779 # to raise an exception, unless _fails_ is set to false
\r
781 # The Channel is automatically created with the appropriate casemap
\r
783 def new_channel(name, topic="", users=[], fails=true)
\r
784 if !has_chan?(name)
\r
786 prefix = name[0].chr
\r
788 # Give a warning if the new Channel goes over some server limits.
\r
790 # FIXME might need to raise an exception
\r
792 warn "#{self} doesn't support channel prefix #{prefix}" unless @supports[:chantypes].includes?(prefix)
\r
793 warn "#{self} doesn't support channel names this long (#{name.length} > #{@support[:channellen]}" unless name.length <= @supports[:channellen]
\r
795 # Next, we check if we hit the limit for channels of type +prefix+
\r
796 # if the server supports +chanlimit+
\r
798 @supports[:chanlimit].keys.each { |k|
\r
799 next unless k.includes?(prefix)
\r
801 @channel_names.each { |n|
\r
802 count += 1 if k.includes?(n[0].chr)
\r
804 raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimits][k]
\r
807 # So far, everything is fine. Now create the actual Channel
\r
809 chan = Channel.new(name, topic, users, self.casemap)
\r
811 # We wade through +prefix+ and +chanmodes+ to create appropriate
\r
812 # lists and flags for this channel
\r
814 @supports[:prefix][:modes].each { |mode|
\r
815 chan.new_userlist(mode)
\r
816 } if @supports[:prefix][:modes]
\r
818 @supports[:chanmodes].each { |k, val|
\r
823 chan.new_netmasklist(mode)
\r
825 when :has_param, :set_param
\r
827 chan.new_data_flag(mode)
\r
831 chan.new_bool_flag(mode)
\r
837 # * appropriate @flags
\r
838 # * a UserList for each @supports[:prefix]
\r
839 # * a NetmaskList for each @supports[:chanmodes] of type A
\r
841 @channels << newchan
\r
842 @channel_names << name
\r
846 raise "Channel #{name} already exists on server #{self}" if fails
\r
847 return get_channel(name)
\r
850 # Remove Channel _name_ from the list of <code>Channel</code>s
\r
852 def delete_channel(name)
\r
853 idx = has_channel?(name)
\r
854 raise "Tried to remove unmanaged channel #{name}" unless idx
\r
855 @channel_names.delete_at(idx)
\r
856 @channels.delete_at(idx)
\r
859 # Checks if the receiver already has a user with the given _nick_
\r
861 def has_user?(nick)
\r
862 @user_nicks.index(nick)
\r
865 # Returns the user with nick _nick_, if available
\r
868 idx = @user_nicks.index(name)
\r
872 # Create a new User object and add it to the list of
\r
873 # <code>User</code>s on the receiver, unless the User
\r
874 # was present already. In this case, the default action is
\r
875 # to raise an exception, unless _fails_ is set to false
\r
877 # The User is automatically created with the appropriate casemap
\r
879 def new_user(str, fails=true)
\r
880 tmp = User.new(str, self.casemap)
\r
881 if !has_user?(tmp.nick)
\r
882 warn "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@support[:nicklen]}" unless tmp.nick.length <= @supports[:nicklen]
\r
884 @user_nicks << tmp.nick
\r
887 old = get_user(tmp.nick)
\r
888 raise "User #{tmp.nick} has inconsistent Netmasks! #{self} knows #{old} but access was tried with #{tmp}" if old != tmp
\r
889 raise "User #{tmp} already exists on server #{self}" if fails
\r
890 return get_user(tmp)
\r
893 # Returns the User with the given Netmask on the server,
\r
894 # creating it if necessary. This is a short form for
\r
895 # new_user(_str_, +false+)
\r
898 new_user(str, false)
\r
901 # Remove User _someuser_ from the list of <code>User</code>s.
\r
902 # _someuser_ must be specified with the full Netmask.
\r
904 def delete_user(someuser)
\r
905 idx = has_user?(user.nick)
\r
906 raise "Tried to remove unmanaged user #{user}" unless idx
\r
907 have = self.user(user)
\r
908 raise "User #{someuser.nick} has inconsistent Netmasks! #{self} knows #{have} but access was tried with #{someuser}" if have != someuser
\r
909 @user_nicks.delete_at(idx)
\r
910 @users.delete_at(idx)
\r
913 # Create a new Netmask object with the appropriate casemap
\r
915 def new_netmask(str)
\r
916 if str.class <= Netmask
\r
917 raise "Wrong casemap for Netmask #{str.inspect}" if str.casemap != self.casemap
\r
920 Netmask.new(str, self.casemap)
\r
923 # Finds all <code>User</code>s on server whose Netmask matches _mask_
\r
925 def find_users(mask)
\r
926 nm = new_netmask(mask)
\r
927 @users.inject(UserList.new) {
\r
929 list << user if user.matches?(nm)
\r
942 # puts " -- irc_regexp tests"
\r
943 # ["*", "a?b", "a*b", "a\\*b", "a\\?b", "a?\\*b", "*a*\\**b?"].each { |s|
\r
946 # puts s.to_irc_regexp.inspect
\r
947 # puts "aUb".match(s.to_irc_regexp)[0] if "aUb" =~ s.to_irc_regexp
\r
950 # puts " -- Netmasks"
\r
952 # masks << Netmask.new("start")
\r
953 # masks << masks[0].dup
\r
954 # masks << Netmask.new(masks[0])
\r
955 # puts masks.join("\n")
\r
957 # puts " -- Changing 1"
\r
958 # masks[1].nick = "me"
\r
959 # puts masks.join("\n")
\r
961 # puts " -- Changing 2"
\r
962 # masks[2].nick = "you"
\r
963 # puts masks.join("\n")
\r
965 # puts " -- Channel example"
\r
966 # ch = Channel.new("#prova")
\r
968 # puts " -- Methods"
\r
969 # puts ch.methods.sort.join("\n")
\r
970 # puts " -- Instance variables"
\r
971 # puts ch.instance_variables.join("\n")
\r