butler 1.8.3 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/CHANGELOG.txt +293 -37
  2. data/README.txt +10 -0
  3. data/Rakefile +24 -13
  4. data/bin/botcontrol +6 -5
  5. data/data/butler/dialogs/create.rb +21 -6
  6. data/data/butler/dialogs/create_config.rb +5 -2
  7. data/data/butler/dialogs/en/create.yaml +6 -3
  8. data/data/butler/dialogs/en/create_config.yaml +1 -0
  9. data/data/butler/dialogs/en/quickcreate.yaml +1 -1
  10. data/data/butler/dialogs/en/sync.yaml +7 -0
  11. data/data/butler/dialogs/en/username.yaml +2 -0
  12. data/data/butler/dialogs/quickcreate.rb +6 -2
  13. data/data/butler/dialogs/sync.rb +83 -0
  14. data/data/butler/dialogs/username.rb +1 -0
  15. data/data/butler/plugins/core/ping.rb +22 -0
  16. data/data/butler/plugins/core/remote.rb +38 -0
  17. data/data/butler/plugins/dev/eval.rb +6 -4
  18. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  19. data/data/butler/plugins/dev/rawlog.rb +109 -45
  20. data/data/butler/plugins/games/countdown.rb +23 -14
  21. data/data/butler/plugins/games/eightball.rb +21 -13
  22. data/data/butler/plugins/games/roll.rb +12 -12
  23. data/data/butler/plugins/irc/join.rb +2 -2
  24. data/data/butler/plugins/irc/notice.rb +10 -10
  25. data/data/butler/plugins/irc/part.rb +12 -12
  26. data/data/butler/plugins/irc/privmsg.rb +10 -10
  27. data/data/butler/plugins/irc/quit.rb +12 -12
  28. data/data/butler/plugins/operator/devoice.rb +1 -1
  29. data/data/butler/plugins/public/help.rb +10 -4
  30. data/data/butler/plugins/{util → service}/calculator.rb +0 -0
  31. data/data/butler/plugins/service/define.rb +16 -13
  32. data/data/butler/plugins/service/log.rb +85 -0
  33. data/data/butler/plugins/service/seen.rb +64 -0
  34. data/data/butler/plugins/service/svn.rb +6 -5
  35. data/data/butler/plugins/util/load.rb +3 -1
  36. data/data/butler/services/org.rubyforge.butler/calculator/1/service.rb +96 -0
  37. data/data/butler/services/org.rubyforge.butler/log/1/service.rb +148 -68
  38. data/lib/access/admin.rb +5 -0
  39. data/lib/blank.rb +32 -0
  40. data/lib/butler.rb +4 -4
  41. data/lib/butler/bot.rb +118 -33
  42. data/lib/butler/control.rb +5 -4
  43. data/lib/butler/debuglog.rb +12 -4
  44. data/lib/butler/dialog.rb +1 -1
  45. data/lib/butler/initialvalues.rb +1 -1
  46. data/lib/butler/irc/client.rb +31 -12
  47. data/lib/butler/irc/message.rb +32 -13
  48. data/lib/butler/irc/parser.rb +67 -30
  49. data/lib/butler/irc/parser/{commands.rb → command.rb} +0 -38
  50. data/lib/butler/irc/parser/generic.rb +9 -12
  51. data/lib/butler/irc/parser/rfc2812.rb +40 -2
  52. data/lib/butler/irc/socket.rb +66 -41
  53. data/lib/butler/irc/string.rb +1 -5
  54. data/lib/butler/plugin.rb +56 -23
  55. data/lib/butler/plugin/configproxy.rb +1 -0
  56. data/lib/butler/plugin/more.rb +2 -2
  57. data/lib/butler/plugins.rb +7 -1
  58. data/lib/butler/remote/connection.rb +113 -0
  59. data/lib/butler/remote/message.rb +157 -0
  60. data/lib/butler/remote/server.rb +85 -0
  61. data/lib/butler/remote/user.rb +46 -0
  62. data/lib/butler/service.rb +2 -1
  63. data/lib/butler/services.rb +2 -2
  64. data/lib/butler/version.rb +2 -2
  65. data/lib/configuration.rb +13 -16
  66. data/lib/ostructfixed.rb +0 -6
  67. data/lib/ruby/array/random.rb +2 -1
  68. data/lib/scriptfile.rb +63 -14
  69. data/lib/timingoutresource.rb +54 -0
  70. data/test/test_scriptfile.rb +51 -0
  71. metadata +63 -61
  72. data/data/butler/dialogs/en/sync_plugins.yaml +0 -3
  73. data/data/butler/dialogs/sync_plugins.rb +0 -30
  74. data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +0 -68
  75. data/lib/log/splitter.rb +0 -30
  76. data/test/test_access/privilege/banners.statistics.yaml +0 -3
  77. data/test/test_access/privilege/banners.yaml +0 -3
  78. data/test/test_access/privilege/news.create.yaml +0 -3
  79. data/test/test_access/privilege/news.delete.yaml +0 -3
  80. data/test/test_access/privilege/news.edit.yaml +0 -3
  81. data/test/test_access/privilege/news.read.yaml +0 -3
  82. data/test/test_access/privilege/news.yaml +0 -3
  83. data/test/test_access/privilege/paid_content.yaml +0 -3
  84. data/test/test_access/privilege/statistics.ftp.yaml +0 -3
  85. data/test/test_access/privilege/statistics.web.yaml +0 -3
  86. data/test/test_access/privilege/statistics.yaml +0 -3
  87. data/test/test_access/role/chiefeditor.yaml +0 -7
  88. data/test/test_access/role/editor.yaml +0 -9
  89. data/test/test_access/user/test.yaml +0 -12
@@ -0,0 +1,85 @@
1
+ class Butler
2
+ module Remote
3
+ class Server
4
+ include Log::Comfort
5
+
6
+ attr_reader :host
7
+ attr_reader :port
8
+
9
+ def initialize(bot)
10
+ @bot = bot
11
+ @host = nil
12
+ @port = nil
13
+ @clients = []
14
+ @server = nil
15
+ @logger = nil
16
+ @accepter = nil
17
+ @lock = Mutex.new
18
+ end
19
+
20
+ def host=(value)
21
+ stop
22
+ @host = value.freeze
23
+ @server = TCPServer.new(@host, @port)
24
+ start
25
+ end
26
+
27
+ def port=(value)
28
+ stop
29
+ @port = value.freeze
30
+ start
31
+ end
32
+
33
+ def start(host=@host, port=@port)
34
+ if @host != host || @port != port then
35
+ @host = host.freeze
36
+ @port = port
37
+ @server = TCPServer.new(@host, @port)
38
+ stop
39
+ end
40
+ @accepter ||= Thread.new(&method(:accepter))
41
+ end
42
+
43
+ def stop
44
+ @lock.synchronize {
45
+ @accepter.kill if @accepter
46
+ @accepter = nil
47
+ @clients.each { |client| client.quit("Server terminated") }
48
+ @clients = []
49
+ }
50
+ end
51
+
52
+ def login(*args)
53
+ @bot.access.login(*args)
54
+ end
55
+
56
+ def dispatch(*args)
57
+ @bot.dispatch(*args)
58
+ end
59
+
60
+ def myself
61
+ @bot.myself
62
+ end
63
+
64
+ def disconnected(connection)
65
+ @lock.synchronize {
66
+ @clients.delete(connection)
67
+ }
68
+ end
69
+
70
+ def running?
71
+ @accepter ? true : false
72
+ end
73
+
74
+ def accepter
75
+ while socket = @server.accept
76
+ @lock.synchronize {
77
+ @clients << Connection.open_thread(self, socket)
78
+ }
79
+ end
80
+ rescue => e
81
+ exception(e)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,46 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ module Remote
11
+ class User
12
+ attr_reader :access
13
+ attr_reader :session
14
+
15
+ def authorized?(*args)
16
+ @access.authorized?(*args)
17
+ end
18
+
19
+ def initialize(name, access)
20
+ @access = access
21
+ @session = Session.new
22
+ @name = name.dup.freeze
23
+ end
24
+
25
+ def nick
26
+ @name
27
+ end
28
+
29
+ def status
30
+ :online
31
+ end
32
+
33
+ def channels
34
+ []
35
+ end
36
+
37
+ def history
38
+ []
39
+ end
40
+
41
+ def method_missing(*args, &block)
42
+ return nil
43
+ end
44
+ end
45
+ end
46
+ end
@@ -16,7 +16,7 @@ class Butler
16
16
  include Log::Comfort
17
17
 
18
18
  attr_reader :butler
19
- attr_reader :message
19
+ attr_reader :path
20
20
  attr_reader :name
21
21
  attr_reader :rdn
22
22
  attr_reader :version
@@ -34,6 +34,7 @@ class Butler
34
34
  @version = version
35
35
  @listener = []
36
36
  load(@path.fixtures) if File.exist?(@path.fixtures)
37
+ info("Loaded service #{@rdn} '#{@name}':#{@version} (#{self})")
37
38
  end
38
39
 
39
40
  def register(object)
@@ -39,7 +39,7 @@ class Butler
39
39
 
40
40
  begin
41
41
  constant = "%s_%08X" % [name.camelcase, rand(0xffffffff)]
42
- end while Butler::Plugins.const_defined?(constant)
42
+ end while Butler::Services.const_defined?(constant)
43
43
  service = Butler::Services.const_set(constant, Module.new)
44
44
  service.extend(Butler::Service)
45
45
  service.logger = @butler.logger
@@ -62,4 +62,4 @@ class Butler
62
62
  @services[name]
63
63
  end
64
64
  end
65
- end
65
+ end
@@ -9,8 +9,8 @@
9
9
  class Butler #:nodoc:
10
10
  module VERSION #:nodoc:
11
11
  MAJOR = 1
12
- MINOR = 8
13
- TINY = 3
12
+ MINOR = 9
13
+ TINY = 0
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY].join('.')
16
16
  end
@@ -11,12 +11,6 @@ require 'yaml'
11
11
 
12
12
 
13
13
 
14
- class String
15
- def config_key # FIXME %2e instead of . is just ugly...
16
- gsub(/\//, '%2f')
17
- end
18
- end
19
-
20
14
  # == Indexing
21
15
  # Author: Stefan Rusterholz
22
16
  # Contact: apeiros@gmx.net>
@@ -47,6 +41,17 @@ class Configuration
47
41
  end
48
42
 
49
43
  ConfFile = Struct.new(:data, :path, :mtime)
44
+
45
+ class <<self
46
+ def escape(name)
47
+ # encode control characters, potentially dangerous characters and leading /'s
48
+ name.gsub(/[\x00-\x1f%\x2f\x7f{}()\[\]~*+-]/) { |m| "%%%02x"%m[0] }.sub(/^\x2e/, '%2e')
49
+ end
50
+
51
+ def unescape(name)
52
+ name.gsub(/%[a-f\d]{2}/) { |m| m[1,2].to_i(16).chr }
53
+ end
54
+ end
50
55
 
51
56
  attr_reader :base
52
57
 
@@ -79,6 +84,7 @@ class Configuration
79
84
  def [](key)
80
85
  file, key = split(key)
81
86
  return nil unless key_exist?(file, key)
87
+ load(file) unless key
82
88
  key ? @files[file][key] : @files[file]
83
89
  end
84
90
 
@@ -129,20 +135,11 @@ class Configuration
129
135
  keys = Array === key ? key.map { |k| String(k) } : key.split(/\//)
130
136
  file = @base.dup
131
137
  raise InvalidKey.new(key) if keys.empty?
132
- file << "/#{encode_filename(keys.shift)}" while (File.directory?(file) && keys.first)
138
+ file << "/#{Configuration.escape(keys.shift)}" while (File.directory?(file) && keys.first)
133
139
  file << ".yaml"
134
140
  return [file, keys.empty? ? nil : keys.join(".")]
135
141
  end
136
142
 
137
- def encode_filename(name)
138
- # FIXME: {}()\[\]~ what about those?
139
- name.gsub(/[\x00-\x1f%\x2f\x7f]/) { |m| "%%%02x"%m[0] }.sub(/^\x2e/, '%2e')
140
- end
141
-
142
- def decode_filename(name)
143
- name.gsub(/%[a-f\d]{2}/) { |m| m[1,2].to_i(16).chr }
144
- end
145
-
146
143
  def load(file, force=false)
147
144
  if force || !@files.has_key?(file) then
148
145
  @files[file] = nil
@@ -11,12 +11,6 @@ require 'ostruct'
11
11
 
12
12
 
13
13
  class OpenStruct
14
- def self.with(&block)
15
- struct = new
16
- struct.instance_eval(&block)
17
- struct
18
- end
19
-
20
14
  # Get the field by its name, faster than #send and with
21
15
  # dynamic field names more convenient.
22
16
  def [](field)
@@ -7,8 +7,9 @@ class Array
7
7
  else
8
8
  raise ArgumentError unless Integer === n and n.between?(0,length)
9
9
  ary = dup
10
+ l = length
10
11
  n.times { |i|
11
- r = rand(n-i)+i
12
+ r = rand(l-i)+i
12
13
  ary[r], ary[i] = ary[i], ary[r]
13
14
  }
14
15
  ary.first(n)
@@ -7,13 +7,12 @@
7
7
 
8
8
 
9
9
  require 'ruby/kernel/non_verbose'
10
+ require 'thread'
10
11
 
11
12
  # use data after __END__ in .rb files like normal files
12
13
  # WARNING:
13
14
  # - if you have an __END__ alone in a multiline comment or string
14
15
  # your .rb file will be damaged.
15
- # - Current implementation *will* append an __END__ to the file, regardeless
16
- #  of what you do (that will change)
17
16
  #
18
17
  # Example:
19
18
  # require 'scriptfile'
@@ -35,6 +34,16 @@ class ScriptFile < File
35
34
  (IO.instance_methods(false)+File.instance_methods(false) - %w[see tell pos truncate]).each { |m| private m }
36
35
 
37
36
  class <<self
37
+ # Append data to the file. Immediatly closes it after writing
38
+ def write(file, text)
39
+ open(file, "a") { |fh| fh.write(text) }
40
+ end
41
+
42
+ # Write data to the file. Immediatly closes it after writing
43
+ def write(file, text)
44
+ open(file, "w") { |fh| fh.write(text) }
45
+ end
46
+
38
47
  # Like File::read, slurp the whole files DATA at once.
39
48
  def read(file)
40
49
  open(file) { |fh| fh.read }
@@ -51,31 +60,71 @@ class ScriptFile < File
51
60
 
52
61
  # Same as File::new
53
62
  def initialize(file, mode="r")
54
- super(file, "r+") # "r#{'+' unless mode=='r'}"
55
- @offset = 0
63
+ super(file, "r#{'+' unless mode=='r'}") # "r#{'+' unless mode=='r'}"
64
+ @offset = 0
65
+ @end_present = false
66
+ @lock = Mutex.new
67
+
68
+ # find the __END__
56
69
  while line = gets
57
70
  @offset += line.size
58
- break if line =~ /^__END__$/
59
- end
60
- unless line then
61
- file_seek(-1, IO::SEEK_CUR)
62
- unless read(1) == "\n" then
63
- puts
64
- @offset += 1
71
+ if line =~ /^__END__$/
72
+ @end_present = true
73
+ break
65
74
  end
66
- print "__END__\n"
67
- @offset += 8
68
75
  end
76
+
69
77
  case mode
70
78
  when /^w/: truncate
71
79
  when /^a/: read
72
80
  end
73
81
  end
82
+
83
+ # append the end-line before writing
84
+ def write_end
85
+ return if @end_present
86
+ @lock.synchronize {
87
+ @end_present = true
88
+ file_seek(-1, IO::SEEK_END)
89
+ unless read(1) == "\n" then
90
+ puts
91
+ @offset += 1
92
+ end
93
+ puts "__END__"
94
+ @offset += 8
95
+ }
96
+ end
97
+ private :write_end
74
98
 
75
99
  public :each, :each_byte, :each_line
76
100
  public :gets, :getc, :read, :readchar, :readline, :readlines
77
- public :puts, :putc, :print, :printf, :write
78
101
  public :ctime, :atime, :mtime
102
+ public :puts, :putc, :print, :printf, :write
103
+
104
+ def puts(*a)
105
+ write_end
106
+ super
107
+ end
108
+
109
+ def putc(*a)
110
+ write_end
111
+ super
112
+ end
113
+
114
+ def print(*a)
115
+ write_end
116
+ super
117
+ end
118
+
119
+ def write(*a)
120
+ write_end
121
+ super
122
+ end
123
+
124
+ def printf(*a)
125
+ write_end
126
+ super
127
+ end
79
128
 
80
129
  alias file_seek seek unless method_defined?(:file_seek)
81
130
  private :file_seek
@@ -0,0 +1,54 @@
1
+ require 'blank'
2
+
3
+ # in case timeout was not required
4
+ module Timeout; class Error < Interrupt; end; end
5
+
6
+ class TimingOutResource < Blank
7
+ @get = Object.instance_method(:instance_variable_get)
8
+ @set = Object.instance_method(:instance_variable_set)
9
+
10
+ # Timeout a resource immediatly
11
+ def self.timeout(resource)
12
+ @set.bind(resource).call(:@due, Time.now-1)
13
+ @get.bind(resource).call(:@sleeper).wakeup
14
+ end
15
+
16
+ # Postpone the timeout of a resource
17
+ def self.postpone(resource, by=nil)
18
+ by ||= @get.bind(resource).call(:@timeout)
19
+ @set.bind(resource).call(:@due, Time.now+by)
20
+ end
21
+
22
+ # Create a proxy object to a resource with a timeout so that
23
+ # the object can be freed/garbage collected after a specific
24
+ # time. After the timeout, all calls will raise a Timeout::Error
25
+ # (or the error you specified). Any method call before the timeout
26
+ # will reset the timer.
27
+ def initialize(resource, timeout, error=nil, &on_timeout)
28
+ @resource = resource
29
+ @timeout = timeout
30
+ @on_timeout = on_timeout
31
+ @due = Time.now+timeout
32
+ @error = error
33
+
34
+ @sleeper = Thread.new {
35
+ until (now=Time.now) >= @due
36
+ sleep(@due-now)
37
+ end
38
+ @on_timeout.call(@resource)
39
+ @resource = nil # enable garbage collection
40
+ @on_timeout = nil # enable garbage collection
41
+ }
42
+ end
43
+
44
+ def method_missing(*args, &block)
45
+ if @resource then
46
+ @due = Time.now+@timeout
47
+ @resource.__send__(*args, &block)
48
+ elsif @error then
49
+ Kernel.raise(@error)
50
+ else
51
+ Kernel.raise(Timeout::Error, "Resource no longer available")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ require 'fileutils'
2
+ require 'ruby/file/write'
3
+ require 'scriptfile'
4
+ require 'test/unit'
5
+
6
+ class TestScriptFile < Test::Unit::TestCase
7
+ Base = File.expand_path(File.dirname(__FILE__)+'/test_scriptfile')
8
+ NoData = <<EOT
9
+ class ExampleScript
10
+ def which_has
11
+ end
12
+
13
+ def no_end
14
+ end
15
+ end
16
+ EOT
17
+ NoDataNoNL = <<EOT.chomp
18
+ class ExampleScript
19
+ def which_has
20
+ end
21
+
22
+ def no_end
23
+ end
24
+ end
25
+ EOT
26
+ WithData = <<EOT
27
+ class ExampleScript
28
+ def which_has
29
+ end
30
+
31
+ def no_end
32
+ end
33
+ end
34
+ __END__
35
+ Here we have some data
36
+ EOT
37
+
38
+ def setup
39
+ FileUtils.mkdir(Base)
40
+ File.write(Base+'/no_end.rb', NoData)
41
+ File.write(Base+'/no_end_no_nl.rb', NoDataNoNL)
42
+ File.write(Base+'/withdata.rb', WithData)
43
+ end
44
+
45
+ def teardown
46
+ FileUtils.rm_r(Base)
47
+ end
48
+
49
+ def test_all
50
+ end
51
+ end