butler 1.8.0 → 1.8.1

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 (57) hide show
  1. data/CHANGELOG +40 -0
  2. data/README +9 -9
  3. data/Rakefile +15 -71
  4. data/bin/botcontrol +151 -146
  5. data/data/butler/dialogs/botcontrol.rb +8 -3
  6. data/data/butler/dialogs/create.rb +18 -18
  7. data/data/butler/dialogs/create_config.rb +8 -0
  8. data/data/butler/dialogs/en/create_config.yaml +2 -0
  9. data/data/butler/dialogs/en/list.yaml +2 -1
  10. data/data/butler/dialogs/info.rb +2 -2
  11. data/data/butler/dialogs/list.rb +13 -8
  12. data/data/butler/plugins/games/countdown.rb +41 -0
  13. data/data/butler/plugins/games/roll.rb +59 -0
  14. data/lib/access.rb +6 -107
  15. data/lib/access/admin.rb +3 -0
  16. data/lib/access/role.rb +37 -2
  17. data/lib/access/savable.rb +5 -0
  18. data/lib/access/user.rb +21 -2
  19. data/lib/access/yamlbase.rb +4 -0
  20. data/lib/butler.rb +4 -4
  21. data/lib/butler/bot.rb +13 -13
  22. data/lib/butler/irc/client.rb +10 -2
  23. data/lib/butler/irc/parser.rb +7 -2
  24. data/lib/butler/irc/parser/commands.rb +24 -7
  25. data/lib/butler/irc/parser/generic.rb +27 -315
  26. data/lib/butler/irc/parser/rfc2812.rb +328 -0
  27. data/lib/butler/irc/socket.rb +1 -1
  28. data/lib/butler/irc/user.rb +13 -0
  29. data/lib/butler/plugin.rb +1 -1
  30. data/lib/butler/plugin/configproxy.rb +4 -4
  31. data/lib/butler/plugins.rb +1 -1
  32. data/lib/butler/version.rb +1 -1
  33. data/lib/configuration.rb +22 -71
  34. data/lib/dialogline.rb +12 -0
  35. data/lib/event.rb +5 -2
  36. data/lib/installer.rb +52 -24
  37. data/lib/iterator.rb +17 -7
  38. data/lib/log.rb +32 -5
  39. data/lib/log/comfort.rb +33 -16
  40. data/lib/log/entry.rb +25 -5
  41. data/lib/log/fakeio.rb +1 -0
  42. data/lib/log/splitter.rb +6 -2
  43. data/lib/ostructfixed.rb +5 -0
  44. data/lib/ruby/exception/detailed.rb +3 -3
  45. data/lib/scheduler.rb +19 -4
  46. data/lib/scriptfile.rb +9 -2
  47. data/lib/string.rb +176 -0
  48. data/lib/string/ascii.rb +31 -0
  49. data/lib/string/mbencoded.rb +79 -0
  50. data/lib/string/sbencoded.rb +77 -0
  51. data/lib/string/utf8.rb +157 -0
  52. data/lib/templater.rb +68 -10
  53. data/lib/w3validator.rb +86 -0
  54. data/test/irc/serverlistings/test_rpl_hiddenhost.txt +60 -0
  55. data/test/test_access.rb +101 -0
  56. data/test/test_configuration.rb +63 -0
  57. metadata +19 -2
@@ -0,0 +1,77 @@
1
+ require 'string'
2
+
3
+ # SingleByte encoded Strings, more speed than MBEncoded for operations like
4
+ # length, index, [] and []= (predefined length of characters)
5
+ class String::SBEncoded < String
6
+ def self.new(string, encoding, collation=nil)
7
+ raise "No encoding given" unless Encodings[encoding]
8
+ obj = allocate
9
+ obj.send(:initialize, string, encoding, collation)
10
+ obj
11
+ end
12
+
13
+ attr_reader :encoding
14
+
15
+ attr_reader :collation
16
+
17
+ def initialize(string, encoding, collation=nil)
18
+ super(string)
19
+ @encoding = encoding.freeze
20
+ @collation = collation.freeze
21
+ end
22
+
23
+ def <=>(other)
24
+ unless other.collation == @collation
25
+ raise ArgumentError, "Collations don't match: #{@collation} <=> #{other.collation}"
26
+ end
27
+ unless other.encoding == @encoding
28
+ raise ArgumentError.new("Encodings don't match: #{@encoding} <=> #{other.encoding}")
29
+ end
30
+ self.utf8 <=> other.utf8
31
+ end
32
+
33
+ # all methods delegated to utf8-ified strings (and if necessary it's reconversion)
34
+ def upcase; utf8.upcase.to_s(@encoding, @collation); end
35
+ def downcase; utf8.downcase.to_s(@encoding, @collation); end
36
+ def capitalize; utf8.capitalize.to_s(@encoding, @collation); end
37
+
38
+ def [](*args)
39
+ String::SBEncoded.new(super, @encoding, @collation)
40
+ end
41
+ alias slice []
42
+
43
+ def chop
44
+ String::SBEncoded.new(super, @encoding, @collation)
45
+ end
46
+ def strip
47
+ String::SBEncoded.new(super, @encoding, @collation)
48
+ end
49
+ def lstrip
50
+ String::SBEncoded.new(super, @encoding, @collation)
51
+ end
52
+ def rstrip
53
+ String::SBEncoded.new(super, @encoding, @collation)
54
+ end
55
+
56
+
57
+ # ! variants, using self.replace
58
+ def upcase!; replace(upcase); end
59
+ def downcase!; replace(downcase); end
60
+ def capitalize!; replace(capitalize); end
61
+
62
+ def dup
63
+ String::SBEncoded.new(self, @encoding, @collation)
64
+ end
65
+
66
+ def utf8(collation=nil)
67
+ String::UTF8.new(
68
+ Iconv.iconv(String::UTF8::UTF8, @encoding, self).first,
69
+ String::UTF8::UTF8,
70
+ collation || @collation
71
+ )
72
+ end
73
+
74
+ def inspect
75
+ "#{encoding}(#{collation||'none'}):#{super}"
76
+ end
77
+ end
@@ -0,0 +1,157 @@
1
+ require 'string'
2
+
3
+ class String::UTF8 < String
4
+ unless method_defined?(:byte_insert)
5
+ UTF8 = 'utf-8'
6
+ alias byte_insert insert
7
+ alias original_succ! succ!
8
+ alias original_succ succ
9
+ private :original_succ!
10
+ private :original_succ
11
+ end
12
+
13
+ def self.new(string, encoding, collation=nil)
14
+ raise "Encoding must be 'utf-8' but is '#{encoding}'" unless encoding == UTF8
15
+ obj = allocate
16
+ obj.send(:initialize, string, collation)
17
+ obj
18
+ end
19
+
20
+ attr_reader :collation
21
+
22
+ def initialize(string, collation=nil)
23
+ super(Unicode::normalize_KC(string))
24
+ @collation = collation
25
+ end
26
+
27
+ def encoding
28
+ String::UTF8::UTF8
29
+ end
30
+
31
+ # See String#[].
32
+ # May return an integer > 255 when used like "\342\210\205"[0] # => 8709
33
+ def [](arg1, arg2=nil) #:nodoc:
34
+ if arg2 then
35
+ unpack("U*").slice(arg1, arg2).pack("U*").utf8
36
+ elsif Range === arg1 then
37
+ unpack("U*").slice(arg1).pack("U*").utf8
38
+ else
39
+ unpack("U*").slice(*args)
40
+ end
41
+ end
42
+ alias slice []
43
+
44
+ def []=(*args)
45
+ value = args.pop
46
+ codepoints = unpack("U*")
47
+ codepoints[*args] = value.utf8.unpack("U*")
48
+ replace(codepoints.pack("U*"))
49
+ self
50
+ end
51
+
52
+ def ==(other)
53
+ super(other.utf8)
54
+ end
55
+
56
+ def <=>(other)
57
+ raise "Can't compare strings with different collation" unless @collation == other.collation
58
+ Unicode.strcmp(self, other)
59
+ end
60
+
61
+ def length; @length||=unpack("U*").size; end
62
+ def upcase; Unicode::upcase(self).utf8; end
63
+ def downcase; Unicode::downcase(self).utf8; end
64
+ def capitalize; Unicode::capitalize(self).utf8; end
65
+ def swapcase
66
+ up = Unicode::upcase(self)
67
+ down = Unicode::downcase(self)
68
+ unpack("U*").zip(up.unpack("U*"), down.unpack("U*")).map { |n,u,d|
69
+ n == u ? d : u
70
+ }.pack("U*")
71
+ end
72
+ def reverse; unpack("U*").reverse.pack("U*").utf8; end
73
+ def strip; gsub(UNICODE_L_PAT, '').gsub(UNICODE_L_PAT, '').utf8; end
74
+ def lstrip; gsub(UNICODE_L_PAT, '').utf8; end
75
+ def rstrip; gsub(UNICODE_T_PAT, '').utf8; end
76
+
77
+ def index(item, offset=0)
78
+ case item
79
+ when Regexp
80
+ mb = unpack("U*")[offset..-1].pack("U*")
81
+ bi = mb.byte_index(item)
82
+ bi && mb.byte_slice(0,bi).unpack("U*").size+offset
83
+ when Integer
84
+ # sucks, but Array#index does not accept an offset-arg
85
+ unpack("U*")[offset..-1].index(item)+offset
86
+ else
87
+ raise "Must be of same encoding" if String === item and encoding != item.encoding
88
+ if offset.zero? then
89
+ bi = byte_index(item)
90
+ bi && byte_slice(0,bi).unpack("U*").size
91
+ else
92
+ index(Regexp.new(Regexp.escape(item)), offset)
93
+ end
94
+ end
95
+ end
96
+
97
+ def rindex(item, offset=-1)
98
+ case item
99
+ when Integer
100
+ unpack("U*")[0..offset].rindex(item)
101
+ else
102
+ raise "Must be of same encoding" if String === item and encoding != item.encoding
103
+ bi = byte_rindex(item, offset)
104
+ bi && byte_slice(0,bi).unpack("U*").size
105
+ end
106
+ end
107
+
108
+ # Inserts the string at codepoint offset specified in offset.
109
+ def insert(offset, fragment) #:nodoc:
110
+ replace(unpack("U*").insert(offset, fragment.unpack("U*")).flatten.pack("U*"))
111
+ end
112
+
113
+ def chop!
114
+ gsub!(/(?:.|\r?\n)\z/u, '')
115
+ end
116
+
117
+ def chop
118
+ gsub(/(?:.|\r?\n)\z/u, '')
119
+ end
120
+
121
+ def each_char(&block)
122
+ scan(/./um, &block)
123
+ self
124
+ end
125
+
126
+ def utf8(collation=nil)
127
+ String::UTF8.new(self, String::UTF8::UTF8, collation)
128
+ end
129
+
130
+ def dup
131
+ String::UTF8.new(self, String::UTF8::UTF8, @collation)
132
+ end
133
+
134
+ # Decomposes the string and returns the decomposed string
135
+ def decompose #:nodoc:
136
+ Unicode::decompose(self)
137
+ end
138
+
139
+ # Normalizes the string to form C and returns the result
140
+ def normalize_C #:nodoc:
141
+ Unicode::normalize_C(self)
142
+ end
143
+
144
+ # Normalizes the string to form D and returns the result
145
+ def normalize_D #:nodoc:
146
+ Unicode::normalize_D(self)
147
+ end
148
+
149
+ # Normalizes the string to form KC and returns the result
150
+ def normalize_KC #:nodoc:
151
+ Unicode::normalize_KC(self)
152
+ end
153
+
154
+ def inspect
155
+ "#{encoding}(#{collation||'none'}):#{super}"
156
+ end
157
+ end
@@ -11,22 +11,66 @@ require 'ostruct'
11
11
 
12
12
 
13
13
 
14
+ # = Indexing
15
+ # Author: Stefan Rusterholz
16
+ # Contact: apeiros@gmx.net>
17
+ # Version: 0.0.1
18
+ # Date: 2007-10-12
19
+ #
20
+ # = About
21
+ # A helper class for ERB, allows constructs like the one in the Synopsis to
22
+ # enable simple use of variables/methods in templates.
23
+ #
24
+ # = Synopsis
25
+ # tmpl = Templater.new("Hello <%= name %>!")
26
+ # tmpl.result(self, :name => 'world') # => 'Hello World!'
27
+ #
14
28
  class Templater
29
+ module VERSION
30
+ STRING = "0.0.2"
31
+ end
32
+
33
+ # A proc for &on_error in Templater::Variables.new or Templater#result.
34
+ # Raises the error further on.
15
35
  Raiser = proc { |e|
16
36
  raise
17
37
  }
38
+
39
+ # A proc for &on_error in Templater::Variables.new or Templater#result.
40
+ # Inserts <<error_class: error_message>> in the place where the error
41
+ # occurred.
18
42
  Teller = proc { |e|
19
43
  "<<#{e.class}: #{e}>>"
20
44
  }
21
45
 
46
+ # = Indexing
47
+ # Author: Stefan Rusterholz
48
+ # Contact: apeiros@gmx.net>
49
+ # Version: 0.0.1
50
+ # Date: 2007-10-12
51
+ #
52
+ # = About
53
+ # Used by Templater to resolve variables.
54
+ #
55
+ # = Synopsis
56
+ # tmpl = Variables.new(delegator, :variable => "content") { |exception|
57
+ # do_something_with_exception
58
+ # }
59
+ #
22
60
  class Variables < OpenStruct
61
+
62
+ # === Arguments
63
+ # * delegator: All method calls and undefined variables are delegated to this object as method call.
64
+ # * variables: A hash with variables in it, keys must be Symbols.
65
+ # * &on_error: The block is yielded in case of an exception with the exception as argument
66
+ #
23
67
  def initialize(delegator=nil, variables={}, &on_error)
24
68
  super(variables)
25
69
  @delegator = delegator
26
70
  @on_error = on_error || RaiseExceptions
27
71
  end
28
72
 
29
- def method_missing(m, *a, &b)
73
+ def method_missing(m, *a, &b) # :nodoc:
30
74
  if @table.key?(m) || m.to_s =~ /=$/ then
31
75
  super
32
76
  elsif @delegator && @delegator.respond_to?(m) then
@@ -38,7 +82,7 @@ class Templater
38
82
  @on_error.call(e)
39
83
  end
40
84
 
41
- def inspect
85
+ def inspect # :nodoc:
42
86
  @table.inspect
43
87
  end
44
88
  end
@@ -49,24 +93,38 @@ class Templater
49
93
  :trim_mode => '%<>',
50
94
  :eoutvar => '_erbout'
51
95
  }
96
+
52
97
  # binding method
53
98
  Binder = Object.instance_method(:binding)
54
99
 
100
+ # The template string
55
101
  attr_reader :string
102
+
103
+ # Like Templater.new, but instead of a template string, the path to the file
104
+ # containing the template. Sets :filename.
105
+ def self.file(path, opt=nil)
106
+ new(File.read, (opt || {}).merge(:filename => path))
107
+ end
108
+
109
+ # ==== Arguments
110
+ # * string: The template string, it becomes frozen
111
+ # * opt: Option hash, keys:
112
+ # * :filename: The filename used for the evaluation (useful for error messages)
113
+ # * :safe_level: see ERB.new
114
+ # * :trim_mode: see ERB.new
115
+ # * :eoutvar: see ERB.new
56
116
  def initialize(string, opt={})
57
117
  opt, string = string, nil if string.kind_of?(Hash)
58
118
  opt = Opt.merge(opt)
59
- file = nil
60
- if !string || File.exist?(string) then
61
- string ||= opt[:filename]
62
- file = string
63
- string = File.read(string)
64
- end
65
- @string = string.dup.freeze
119
+ file = opt.delete(:filename)
120
+ @string = string.freeze
66
121
  @erb = ERB.new(@string, *opt.values_at(:safe_level, :trim_mode, :eoutvar))
67
122
  @erb.filename = file if file
68
123
  end
69
124
 
125
+ # See Templater::Variables.new
126
+ # Returns the evaluated template. Default &on_error is the Templater::Raiser
127
+ # proc.
70
128
  def result(delegator=nil, variables={}, &on_error)
71
129
  variables ||= {}
72
130
  on_error ||= Raiser
@@ -75,7 +133,7 @@ class Templater
75
133
  rescue NameError => e
76
134
  raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
77
135
  end
78
-
136
+
79
137
  def inspect # :nodoc:
80
138
  "#<%s:0x%x string=%s>" % [
81
139
  self.class,
@@ -0,0 +1,86 @@
1
+ require 'mechanize'
2
+
3
+ class W3Validator
4
+ attr_reader :glob
5
+ attr_reader :files
6
+
7
+ def initialize(glob, cache=nil)
8
+ @glob = glob.freeze
9
+ @files = glob ? Dir.glob(glob).freeze : [].freeze
10
+ @agent = WWW::Mechanize.new
11
+ # bug in current implementation? unpredictably raises EOF exceptions without this
12
+ @agent.keep_alive = false
13
+ @cache = cache ? Marshal.load(File.read(cache)) : nil
14
+ @cache_file = cache
15
+ require 'zlib' if @cache
16
+ end
17
+
18
+ def cache
19
+ data = {}
20
+ @files.each { |file| data[file] = Zlib.crc32(File.read(file)) }
21
+ File.open(cache, "w") { |fh| fh.write(Marshal.dump(data)) }
22
+ @cache = data
23
+ end
24
+
25
+ def cache_file(file)
26
+ require 'zlib'
27
+ data = Marshal.load(File.read(cache))
28
+ data[file] = Zlib.crc32(File.read(file))
29
+ File.open(cache, "w") { |fh| fh.write(Marshal.dump(data)) }
30
+ data
31
+ end
32
+
33
+ def validate_changed(cachefile, delay=2)
34
+ require 'zlib'
35
+ crcs = File.exist?(cachefile) ? Marshal.load(File.read(cachefile)) {}
36
+ warn "No cachefile." unless File.exist?(cachefile)
37
+ orig_files = @files.dup
38
+ @files = @files.reject { |file| Zlib.crc32(File.read(file)) == crcs[file] }
39
+ puts "Validating files:", *@files
40
+ result = validate(delay, &block)
41
+ @files = orig_files.freeze
42
+ result
43
+ end
44
+
45
+ # returns whether all are valid, yields file, valid if a block was given.
46
+ def validate(delay=2)
47
+ @files.map { |file|
48
+ valid = valid?(file)
49
+ yield(file, valid) if block_given?
50
+ sleep(delay)
51
+ valid
52
+ }.all?
53
+ end
54
+
55
+ def rehash
56
+ @files = @glob ? Dir.glob(@glob).freeze : [].freeze
57
+ end
58
+
59
+ def glob=(glob)
60
+ @glob = glob
61
+ rehash
62
+ end
63
+
64
+ def valid?(file, tries=3, delay=3)
65
+ rescued = 0
66
+ form = @agent.get('http://validator.w3.org/').forms[1]
67
+ form.file_uploads.first.file_name = file
68
+ result = form.submit
69
+ cache_file(file) if @cache_file
70
+ case (result/"title").first.inner_text[/\[([^\/]+)\]/, 1]
71
+ when "Valid": true
72
+ when "Invalid": false
73
+ else raise "Title #{(result/"title").first.inner_text.gsub(/\s+/m, ' ')} not recognized"
74
+ end
75
+ rescue Exception => e
76
+ warn e
77
+ tries -= 1
78
+ raise "could not validate" if tries.zero?
79
+ sleep(delay)
80
+ retry
81
+ end
82
+
83
+ def inspect
84
+ "#<%s:0x%08x glob=%s>" % [self.class, object_id<<1, @glob.inspect]
85
+ end
86
+ end