ircbot 0.0.2 → 0.1.0

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 (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