butler 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
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