ircbot 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README +43 -21
  2. data/Rakefile +9 -6
  3. data/bin/ircbot +3 -27
  4. data/lib/ircbot.rb +12 -15
  5. data/lib/ircbot/client.rb +32 -0
  6. data/lib/ircbot/client/commands.rb +34 -0
  7. data/lib/ircbot/client/config.rb +55 -0
  8. data/lib/ircbot/client/config/channels.rb +40 -0
  9. data/lib/ircbot/client/config/plugins.rb +16 -0
  10. data/lib/ircbot/client/encoding.rb +17 -0
  11. data/lib/ircbot/client/plugins.rb +64 -0
  12. data/lib/ircbot/core_ext/delegation.rb +135 -0
  13. data/lib/ircbot/core_ext/extending.rb +80 -0
  14. data/lib/ircbot/core_ext/message.rb +18 -0
  15. data/lib/ircbot/framework.rb +22 -40
  16. data/lib/ircbot/plugin.rb +63 -0
  17. data/lib/ircbot/plugins.rb +141 -0
  18. data/lib/ircbot/version.rb +4 -0
  19. data/plugins/echo.rb +14 -0
  20. data/plugins/irc.rb +19 -0
  21. data/plugins/plugins.rb +59 -0
  22. data/spec/config_spec.rb +83 -0
  23. data/spec/fixtures/sama-zu.yml +8 -0
  24. data/spec/framework_spec.rb +18 -0
  25. data/spec/its_helper.rb +15 -0
  26. data/spec/plugin_spec.rb +70 -0
  27. data/spec/plugins_spec.rb +30 -0
  28. data/spec/provide_helper.rb +36 -0
  29. data/spec/spec_helper.rb +16 -0
  30. metadata +40 -19
  31. data/lib/irc/agent.rb +0 -177
  32. data/lib/irc/client.rb +0 -476
  33. data/lib/irc/const.rb +0 -242
  34. data/lib/irc/irc.rb +0 -324
  35. data/lib/irc/localize.rb +0 -260
  36. data/lib/ircbot/agent_manager.rb +0 -89
  37. data/lib/ircbot/config_client.rb +0 -369
  38. data/lib/ircbot/core_ext/digest.rb +0 -14
  39. data/lib/ircbot/core_ext/irc.rb +0 -61
  40. data/lib/ircbot/core_ext/rand-polimorphism.rb +0 -23
  41. data/lib/ircbot/core_ext/writefile.rb +0 -45
  42. data/lib/ircbot/ordered_hash.rb +0 -91
  43. data/lib/ircbot/reply_client.rb +0 -328
@@ -0,0 +1,135 @@
1
+ class Module
2
+ # Provides a delegate class method to easily expose contained objects' methods
3
+ # as your own. Pass one or more methods (specified as symbols or strings)
4
+ # and the name of the target object as the final <tt>:to</tt> option (also a symbol
5
+ # or string). At least one method and the <tt>:to</tt> option are required.
6
+ #
7
+ # Delegation is particularly useful with Active Record associations:
8
+ #
9
+ # class Greeter < ActiveRecord::Base
10
+ # def hello() "hello" end
11
+ # def goodbye() "goodbye" end
12
+ # end
13
+ #
14
+ # class Foo < ActiveRecord::Base
15
+ # belongs_to :greeter
16
+ # delegate :hello, :to => :greeter
17
+ # end
18
+ #
19
+ # Foo.new.hello # => "hello"
20
+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
21
+ #
22
+ # Multiple delegates to the same target are allowed:
23
+ #
24
+ # class Foo < ActiveRecord::Base
25
+ # belongs_to :greeter
26
+ # delegate :hello, :goodbye, :to => :greeter
27
+ # end
28
+ #
29
+ # Foo.new.goodbye # => "goodbye"
30
+ #
31
+ # Methods can be delegated to instance variables, class variables, or constants
32
+ # by providing them as a symbols:
33
+ #
34
+ # class Foo
35
+ # CONSTANT_ARRAY = [0,1,2,3]
36
+ # @@class_array = [4,5,6,7]
37
+ #
38
+ # def initialize
39
+ # @instance_array = [8,9,10,11]
40
+ # end
41
+ # delegate :sum, :to => :CONSTANT_ARRAY
42
+ # delegate :min, :to => :@@class_array
43
+ # delegate :max, :to => :@instance_array
44
+ # end
45
+ #
46
+ # Foo.new.sum # => 6
47
+ # Foo.new.min # => 4
48
+ # Foo.new.max # => 11
49
+ #
50
+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
51
+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
52
+ # delegated to.
53
+ #
54
+ # Person = Struct.new(:name, :address)
55
+ #
56
+ # class Invoice < Struct.new(:client)
57
+ # delegate :name, :address, :to => :client, :prefix => true
58
+ # end
59
+ #
60
+ # john_doe = Person.new("John Doe", "Vimmersvej 13")
61
+ # invoice = Invoice.new(john_doe)
62
+ # invoice.client_name # => "John Doe"
63
+ # invoice.client_address # => "Vimmersvej 13"
64
+ #
65
+ # It is also possible to supply a custom prefix.
66
+ #
67
+ # class Invoice < Struct.new(:client)
68
+ # delegate :name, :address, :to => :client, :prefix => :customer
69
+ # end
70
+ #
71
+ # invoice = Invoice.new(john_doe)
72
+ # invoice.customer_name # => "John Doe"
73
+ # invoice.customer_address # => "Vimmersvej 13"
74
+ #
75
+ # If the object to which you delegate can be nil, you may want to use the
76
+ # :allow_nil option. In that case, it returns nil instead of raising a
77
+ # NoMethodError exception:
78
+ #
79
+ # class Foo
80
+ # attr_accessor :bar
81
+ # def initialize(bar = nil)
82
+ # @bar = bar
83
+ # end
84
+ # delegate :zoo, :to => :bar
85
+ # end
86
+ #
87
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
88
+ #
89
+ # class Foo
90
+ # attr_accessor :bar
91
+ # def initialize(bar = nil)
92
+ # @bar = bar
93
+ # end
94
+ # delegate :zoo, :to => :bar, :allow_nil => true
95
+ # end
96
+ #
97
+ # Foo.new.zoo # returns nil
98
+ #
99
+ def delegate(*methods)
100
+ options = methods.pop
101
+ unless options.is_a?(Hash) && to = options[:to]
102
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
103
+ end
104
+
105
+ if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
106
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
107
+ end
108
+
109
+ prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
110
+
111
+ file, line = caller.first.split(':', 2)
112
+ line = line.to_i
113
+
114
+ methods.each do |method|
115
+ on_nil =
116
+ if options[:allow_nil]
117
+ 'return'
118
+ else
119
+ %(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
120
+ end
121
+
122
+ module_eval(<<-EOS, file, line)
123
+ def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
124
+ #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
125
+ rescue NoMethodError # rescue NoMethodError
126
+ if #{to}.nil? # if client.nil?
127
+ #{on_nil}
128
+ else # else
129
+ raise # raise
130
+ end # end
131
+ end # end
132
+ EOS
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,80 @@
1
+ class Object
2
+ def remove_subclasses_of(*superclasses) #:nodoc:
3
+ Class.remove_class(*subclasses_of(*superclasses))
4
+ end
5
+
6
+ begin
7
+ ObjectSpace.each_object(Class.new) {}
8
+
9
+ # Exclude this class unless it's a subclass of our supers and is defined.
10
+ # We check defined? in case we find a removed class that has yet to be
11
+ # garbage collected. This also fails for anonymous classes -- please
12
+ # submit a patch if you have a workaround.
13
+ def subclasses_of(*superclasses) #:nodoc:
14
+ subclasses = []
15
+
16
+ superclasses.each do |sup|
17
+ ObjectSpace.each_object(class << sup; self; end) do |k|
18
+ if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
19
+ subclasses << k
20
+ end
21
+ end
22
+ end
23
+
24
+ subclasses
25
+ end
26
+ rescue RuntimeError
27
+ # JRuby and any implementations which cannot handle the objectspace traversal
28
+ # above fall back to this implementation
29
+ def subclasses_of(*superclasses) #:nodoc:
30
+ subclasses = []
31
+
32
+ superclasses.each do |sup|
33
+ ObjectSpace.each_object(Class) do |k|
34
+ if superclasses.any? { |superclass| k < superclass } &&
35
+ (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
36
+ subclasses << k
37
+ end
38
+ end
39
+ subclasses.uniq!
40
+ end
41
+ subclasses
42
+ end
43
+ end
44
+
45
+ def extended_by #:nodoc:
46
+ ancestors = class << self; ancestors end
47
+ ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
48
+ end
49
+
50
+ def extend_with_included_modules_from(object) #:nodoc:
51
+ object.extended_by.each { |mod| extend mod }
52
+ end
53
+
54
+ unless defined? instance_exec # 1.9
55
+ module InstanceExecMethods #:nodoc:
56
+ end
57
+ include InstanceExecMethods
58
+
59
+ # Evaluate the block with the given arguments within the context of
60
+ # this object, so self is set to the method receiver.
61
+ #
62
+ # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
63
+ def instance_exec(*args, &block)
64
+ begin
65
+ old_critical, Thread.critical = Thread.critical, true
66
+ n = 0
67
+ n += 1 while respond_to?(method_name = "__instance_exec#{n}")
68
+ InstanceExecMethods.module_eval { define_method(method_name, &block) }
69
+ ensure
70
+ Thread.critical = old_critical
71
+ end
72
+
73
+ begin
74
+ send(method_name, *args)
75
+ ensure
76
+ InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,18 @@
1
+ ######################################################################
2
+ ### core_ext
3
+
4
+ class Net::IRC::Message
5
+ def channel
6
+ params[0]
7
+ end
8
+
9
+ def message
10
+ params[1]
11
+ end
12
+
13
+ def reply(client, text)
14
+ to = (client.config.nick == params[0]) ? prefix.nick : params[0]
15
+ client.privmsg to, text.to_s unless text.to_s.empty?
16
+ end
17
+ end
18
+
@@ -1,50 +1,32 @@
1
1
  module Ircbot
2
- mattr_accessor :load_paths
3
- self.load_paths = {}
4
-
5
- class InvalidFilename < SecurityError; end
6
- class FileNotFound < SecurityError; end
7
-
8
- class Recover < RuntimeError
9
- attr_reader :wait
10
-
11
- def initialize(wait = 300)
12
- @wait = wait
13
- end
2
+ def paths
3
+ @paths ||= Mash.new
14
4
  end
15
5
 
16
- class << self
17
- def root
18
- @root || File.expand_path(Dir.pwd)
19
- end
20
-
21
- def root=(value)
22
- @root = value
23
- end
6
+ def root
7
+ @root || Pathname(Dir.pwd).expand_path
8
+ end
24
9
 
25
- def push_path(type, path, file_glob = "**/*.rb")
26
- load_paths[type] = [Pathname(path), file_glob]
27
- end
10
+ def system_root
11
+ (Pathname(File.dirname(__FILE__)) + ".." + "..").expand_path
12
+ end
28
13
 
29
- def dir_for(type)
30
- load_paths[type][0] or
31
- raise RuntimeError, "directory not found: #{type}"
32
- end
14
+ def root=(value)
15
+ @root = Pathname(value)
16
+ end
33
17
 
34
- def glob_for(type)
35
- load_paths[type][1]
36
- end
18
+ def push_path(type, path)
19
+ paths[type] ||= []
20
+ paths[type] << Pathname(path)
21
+ end
37
22
 
38
- def path_for(type, name)
39
- name = name.to_s
40
- raise InvalidFilename, name if name =~ %r{\.\.|~|/}
23
+ def glob_for(type, name)
24
+ Array(paths[type]).reverse.select{|p| p.directory?}.map{|d|
25
+ Dir.glob(d + "**/#{name}.*")
26
+ }.flatten.compact.map{|i| Pathname(i)}
27
+ end
41
28
 
42
- path = dir_for(type) + name
43
- path.readable_real? or
44
- raise FileNotFound, name
29
+ attr_accessor :toplevel_binding
45
30
 
46
- return path
47
- end
48
- end
31
+ extend self
49
32
  end
50
-
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Ircbot
4
+ class Plugin
5
+ class Null
6
+ def method_missing(*)
7
+ self
8
+ end
9
+ end
10
+
11
+ class InitialMessage < Net::IRC::Message
12
+ def initialize(nick = nil)
13
+ super nick, "PRIVMSG", ["#channel", "(initialize)"]
14
+ end
15
+ end
16
+
17
+ attr_accessor :message
18
+ attr_accessor :plugins
19
+ attr_accessor :running
20
+ attr_accessor :plugin_name
21
+
22
+ def initialize(plugins = nil)
23
+ @plugins = plugins || Plugins.new
24
+ @message = InitialMessage.new(self.class.name)
25
+ @running = false
26
+ end
27
+
28
+ ######################################################################
29
+ ### Accessors
30
+
31
+ delegate :plugin!, :client, :bot, :config, :to=>"@plugins"
32
+
33
+ def plugin_name
34
+ @plugin_name ||= Extlib::Inflection.foreign_key(self.class.name).sub(/(_plugin)?_id$/,'')
35
+ end
36
+
37
+ def inspect
38
+ "<%sPlugin: %s>" % [running ? '*' : '', plugin_name]
39
+ end
40
+
41
+ def to_s
42
+ "%s%s" % [running ? '*' : '', plugin_name]
43
+ end
44
+
45
+ ######################################################################
46
+ ### Operations
47
+
48
+ def help
49
+ raise "no helps for #{plugin_name}"
50
+ end
51
+
52
+ private
53
+ def plugin(name)
54
+ plugin!(name)
55
+ rescue
56
+ Null.new
57
+ end
58
+
59
+ def direct?
60
+ message.channel == config.nick
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,141 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Ircbot
4
+ class PluginNotFound < RuntimeError; end
5
+
6
+ class Plugins
7
+ attr_reader :plugins
8
+ attr_reader :client
9
+ delegate :config, :to=>"client"
10
+
11
+ def initialize(client = nil, plugins = nil)
12
+ @client = client || Client::Standalone.new
13
+ @plugins = Dictionary.new
14
+
15
+ load_plugins Array(plugins)
16
+ end
17
+
18
+ ######################################################################
19
+ ### Enumerable
20
+
21
+ include Enumerable
22
+
23
+ def each(&block)
24
+ plugins.values.__send__(:each, &block)
25
+ end
26
+
27
+ ######################################################################
28
+ ### Accessors
29
+
30
+ def plugin!(name)
31
+ find_plugin!(name)
32
+ end
33
+
34
+ def plugin(name)
35
+ plugin!(name) rescue nil
36
+ end
37
+ alias :[] :plugin!
38
+
39
+ def bot
40
+ client
41
+ end
42
+
43
+ ######################################################################
44
+ ### Operations
45
+
46
+ def start(plugin)
47
+ find_plugin(plugin).running = true
48
+ end
49
+
50
+ def stop(plugin)
51
+ find_plugin(plugin).running = false
52
+ end
53
+
54
+ def load(plugin)
55
+ load_plugins(plugin)
56
+ end
57
+
58
+ def delete(plugin)
59
+ name = plugin.is_a?(Plugin) ? plugin.name : plugin.to_s
60
+ plugin!(name)
61
+ @plugins.delete(name)
62
+ end
63
+
64
+ ######################################################################
65
+ ### IO
66
+
67
+ def load_plugins(plugin)
68
+ case plugin
69
+ when Array
70
+ plugin.each do |name|
71
+ self << name
72
+ end
73
+ when Plugin
74
+ plugin.plugins = self
75
+ plugins[plugin.plugin_name] = plugin
76
+ plugin.running = true
77
+ when Class
78
+ if plugin.ancestors.include?(Ircbot::Plugin)
79
+ self << plugin.new(self)
80
+ else
81
+ raise ArgumentError, "#{plugin} is not Ircbot::Plugin"
82
+ end
83
+ when String, Symbol
84
+ begin
85
+ name = plugin.to_s
86
+ unless @plugins[name]
87
+ self << load_plugin(name)
88
+ end
89
+ rescue Exception => e
90
+ broadcast "Plugin error(#{name}): #{e}[#{e.class}]"
91
+ end
92
+ else
93
+ raise NotImplementedError, "#<< for #{plugin.class}"
94
+ end
95
+ return self
96
+ end
97
+ alias :<< :load_plugins
98
+
99
+ def load_plugin(name)
100
+ path = Ircbot.glob_for(:plugin, name).first or
101
+ raise PluginNotFound, name.to_s
102
+
103
+ script = path.read{}
104
+ eval(script, Ircbot.toplevel_binding)
105
+
106
+ class_name = Extlib::Inflection.camelize(name) + "Plugin"
107
+ return Object.const_get(class_name).new
108
+
109
+ # Object.subclasses_of(Plugin).each do |k|
110
+ # return k.new if Extlib::Inflection.demodulize(k.name) =~ /^#{class_name}(Plugin)?$/
111
+ # end
112
+
113
+ rescue NameError => e
114
+ raise LoadError, "Expected #{path} to define #{class_name} (#{e})"
115
+ end
116
+
117
+ def active
118
+ select(&:running)
119
+ end
120
+
121
+ def inspect
122
+ plugins.values.inspect
123
+ end
124
+
125
+ private
126
+ def find_plugin!(plugin)
127
+ case plugin
128
+ when String, Symbol
129
+ @plugins[plugin.to_s] or raise PluginNotFound, plugin.to_s
130
+ when Plugin
131
+ plugin
132
+ else
133
+ raise PluginNotFound, "#{plugin.class}(#{plugin})"
134
+ end
135
+ end
136
+
137
+ def broadcast(text)
138
+ p text
139
+ end
140
+ end
141
+ end