butler 1.8.2 → 1.8.3

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 (68) hide show
  1. data/Rakefile +1 -1
  2. data/bin/botcontrol +1 -1
  3. data/data/butler/dialogs/create_config.rb +2 -2
  4. data/data/butler/dialogs/quickcreate.rb +6 -4
  5. data/data/butler/dialogs/uninstall.rb +4 -3
  6. data/data/butler/plugins/core/access.rb +10 -10
  7. data/data/butler/plugins/dev/bleakhouse.rb +19 -8
  8. data/data/butler/plugins/operator/deop.rb +10 -1
  9. data/data/butler/plugins/operator/devoice.rb +9 -0
  10. data/data/butler/plugins/operator/limit.rb +12 -0
  11. data/data/butler/plugins/operator/op.rb +9 -0
  12. data/data/butler/plugins/operator/voice.rb +10 -0
  13. data/data/butler/plugins/util/calculator.rb +11 -0
  14. data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +68 -0
  15. data/data/butler/services/org.rubyforge.butler/log/1/service.rb +198 -0
  16. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/acknowledge.yaml +8 -0
  17. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/gratitude.yaml +3 -0
  18. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/hello.yaml +6 -0
  19. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance.yaml +7 -0
  20. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance_about.yaml +3 -0
  21. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/insult.yaml +3 -0
  22. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/rejection.yaml +12 -0
  23. data/data/butler/services/org.rubyforge.butler/strings/1/service.rb +50 -0
  24. data/lib/access.rb +6 -3
  25. data/lib/access/privilege.rb +9 -78
  26. data/lib/access/privilegelist.rb +75 -0
  27. data/lib/access/role.rb +14 -94
  28. data/lib/access/role/base.rb +40 -0
  29. data/lib/access/rolelist.rb +99 -0
  30. data/lib/access/savable.rb +6 -3
  31. data/lib/access/user.rb +21 -19
  32. data/lib/access/version.rb +17 -0
  33. data/lib/access/yamlbase.rb +64 -48
  34. data/lib/butler.rb +1 -0
  35. data/lib/butler/bot.rb +8 -2
  36. data/lib/butler/control.rb +3 -1
  37. data/lib/butler/initialvalues.rb +1 -1
  38. data/lib/butler/irc/client.rb +6 -0
  39. data/lib/butler/irc/message.rb +14 -9
  40. data/lib/butler/irc/parser.rb +8 -5
  41. data/lib/butler/irc/parser/generic.rb +33 -1
  42. data/lib/butler/irc/parser/rfc2812.rb +5 -2
  43. data/lib/butler/plugin.rb +22 -2
  44. data/lib/butler/plugins.rb +2 -7
  45. data/lib/butler/service.rb +73 -0
  46. data/lib/butler/services.rb +65 -0
  47. data/lib/butler/version.rb +1 -1
  48. data/lib/ruby/array/random.rb +17 -0
  49. data/lib/ruby/string/camelcase.rb +14 -0
  50. data/test/test_access.rb +164 -59
  51. data/test/test_access/privilege/banners.statistics.yaml +3 -0
  52. data/test/test_access/privilege/banners.yaml +3 -0
  53. data/test/test_access/privilege/news.create.yaml +3 -0
  54. data/test/test_access/privilege/news.delete.yaml +3 -0
  55. data/test/test_access/privilege/news.edit.yaml +3 -0
  56. data/test/test_access/privilege/news.read.yaml +3 -0
  57. data/test/test_access/privilege/news.yaml +3 -0
  58. data/test/test_access/privilege/paid_content.yaml +3 -0
  59. data/test/test_access/privilege/statistics.ftp.yaml +3 -0
  60. data/test/test_access/privilege/statistics.web.yaml +3 -0
  61. data/test/test_access/privilege/statistics.yaml +3 -0
  62. data/test/test_access/role/chiefeditor.yaml +7 -0
  63. data/test/test_access/role/editor.yaml +9 -0
  64. data/test/test_access/user/test.yaml +12 -0
  65. metadata +51 -5
  66. data/data/butler/plugins/core/user.rb +0 -166
  67. data/data/butler/plugins/dev/onhandlers.rb +0 -93
  68. data/data/butler/plugins/service/log.rb +0 -183
@@ -0,0 +1,40 @@
1
+ class Access
2
+ class Role
3
+
4
+ # The module to extend the Database manager
5
+ module Base
6
+
7
+ # Create a new Role
8
+ # role is a role-oid, should be \w+
9
+ # description is a piece of text describing the role
10
+ # privileges are the privileges the role provides
11
+ def create(role, description=nil, privilege_oids=[], role_oids=[])
12
+ raise "Role #{role} already exists" if exists?(role)
13
+ role = Role.new(role, description)
14
+ role.access = access
15
+ role.base = self
16
+ add(role)
17
+ role_oids.each { |role_oid|
18
+ role.roles.add_oid(role_oid)
19
+ }
20
+ privilege_oids.each { |priv|
21
+ role.privileges.add_oid(priv)
22
+ }
23
+ role
24
+ end
25
+
26
+ # Restore an Access::Privilege from it's storable data
27
+ def load(*args) # :nodoc:
28
+ return nil unless data = super
29
+ roles = access.role
30
+ data[:roles] = data[:roles].map { |role| roles[role] }
31
+ array = data.values_at(:oid, :description)
32
+ array << data
33
+ role = Role.new(*array)
34
+ role.access = access
35
+ role.base = self
36
+ role
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,99 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'access'
10
+
11
+
12
+
13
+ class Access
14
+ # A list of roles
15
+ class RoleList
16
+ include Enumerable
17
+
18
+ # owner must be capable of #save and #
19
+ # roles is the list of Role instances (array)
20
+ def initialize(owner, roles=nil)
21
+ @owner = owner
22
+ @roles = {}
23
+ roles.each { |role| @roles[role] = @owner.access.role[role] } if roles
24
+ end
25
+
26
+ # Tests if any of the roles in self allows a privilege under
27
+ # given conditions (may be nil to indicate no condition)
28
+ def allow?(privilege, condition=nil)
29
+ @roles.any? { |oid, role| role.allows?(privilege, condition) }
30
+ end
31
+
32
+ # add a role (Role instance)
33
+ def add(role)
34
+ @roles[role.oid] = role
35
+ @owner.save
36
+ end
37
+
38
+ def add_oid(role_oid)
39
+ role = @owner.access.role[role_oid]
40
+ raise "Role #{role_oid} does not exist" unless role
41
+ add(role)
42
+ end
43
+
44
+ # all roles
45
+ def list
46
+ @roles.dup
47
+ end
48
+
49
+ # Iterate over the roles (oid & role)
50
+ # :yield: role_oid, role
51
+ def each(&block)
52
+ @roles.each(&block)
53
+ end
54
+
55
+ # Iterate over the role oids
56
+ def each_oid(&block)
57
+ @roles.each_key(&block)
58
+ end
59
+
60
+ # Iterate over the roles
61
+ def each_role(&block)
62
+ @roles.each_value(&block)
63
+ end
64
+
65
+ # :nodoc:
66
+ # prepare for storage
67
+ def storable
68
+ @roles.map { |oid, role| oid }
69
+ end
70
+
71
+ # :nodoc:
72
+ def access
73
+ @owner.access
74
+ end
75
+
76
+ # delete a role (Role instance)
77
+ def remove(role)
78
+ remove_oid(role.oid)
79
+ end
80
+
81
+ # delete a role by its oid
82
+ def remove_oid(role_oid)
83
+ @roles.delete(role_oid)
84
+ @owner.save
85
+ end
86
+
87
+ def save
88
+ @owner.save
89
+ end
90
+
91
+ def inspect # :nodoc:
92
+ "#<%s:0x%08x roles=%s>" % [
93
+ self.class,
94
+ object_id << 1,
95
+ @roles.map { |oid, role| oid }.join(', '),
96
+ ]
97
+ end
98
+ end
99
+ end
@@ -7,7 +7,10 @@
7
7
 
8
8
 
9
9
  class Access
10
+
10
11
  # Mixin for classes representing records
12
+ # Requires a method base (Savable creates an accessor to @base) and
13
+ # an #oid method.
11
14
  module Savable
12
15
 
13
16
  # The Access instance that manages this records base
@@ -15,15 +18,15 @@ class Access
15
18
 
16
19
  # The "Base" instance that manages this record
17
20
  attr_accessor :base
18
-
21
+
19
22
  # save changes to this record
20
23
  def save
21
- base.save(id())
24
+ base.save(oid())
22
25
  end
23
26
 
24
27
  # delete this record from the database
25
28
  def delete
26
- base.delete(id())
29
+ base.delete(oid())
27
30
  end
28
31
  end
29
32
  end
data/lib/access/user.rb CHANGED
@@ -14,24 +14,24 @@ require 'access/admin'
14
14
 
15
15
  class Access
16
16
  # Access::User
17
- # Use nil-id if you don't want the object to be stored.
17
+ # Use nil-oid if you don't want the object to be stored.
18
18
  class User
19
19
  include Savable
20
20
 
21
21
  module Base
22
22
  # Create a new - inactive(!) - user
23
23
  def create(user_id, credentials, meta=nil, admin=false, opt={})
24
- raise "User-id #{user_id} already exists" if exists?(user_id)
24
+ raise "User-oid #{user_id} already exists" if exists?(user_id)
25
25
  credentials = credentials ? access.hash_credentials(credentials, user_id) : "*"
26
26
  user = User.new(
27
+ access,
28
+ self,
27
29
  user_id,
28
30
  credentials,
29
31
  meta,
30
32
  admin,
31
- {:active => !!opt.delete(:active)}.merge(opt)
33
+ { :active => !!opt.delete(:active) }.merge(opt)
32
34
  )
33
- user.access = access
34
- user.base = self
35
35
  add(user)
36
36
  user
37
37
  end
@@ -39,9 +39,9 @@ class Access
39
39
  # Restore an Access::User from it's storable data
40
40
  def load(*args) # :nodoc:
41
41
  return nil unless data = super
42
- array = data.values_at(:id, :credentials, :meta, :admin)
42
+ array = data.values_at(:oid, :credentials, :meta, :admin)
43
43
  array << data
44
- user = User.new(*array)
44
+ user = User.new(access, self, *array)
45
45
  user.access = access
46
46
  user.base = self
47
47
  user
@@ -49,7 +49,7 @@ class Access
49
49
  end
50
50
 
51
51
  # The record-id
52
- attr_reader :id
52
+ attr_reader :oid
53
53
 
54
54
  # The credentials of the user (password)
55
55
  attr_reader :credentials
@@ -67,7 +67,7 @@ class Access
67
67
  # Simplified to hashes and scalar values.
68
68
  def storable
69
69
  {
70
- :id => @id,
70
+ :oid => @oid,
71
71
  :credentials => @credentials,
72
72
  :meta => @meta,
73
73
  :admin => @admin,
@@ -78,18 +78,20 @@ class Access
78
78
  end
79
79
 
80
80
  # access: the access-instance the user is tied to (necessary for storing)
81
- # id: a string/integer, identifying the user
81
+ # oid: a string/integer, identifying the user
82
82
  # credentials: (if not subclassed) a string authenticating the user, will be hashed before storing.
83
83
  # meta: meta data about the user
84
84
  # admin: admin's have all privileges granted
85
- def initialize(id, credentials, meta=nil, admin=false, other={})
86
- @id = id
85
+ def initialize(access, base, oid, credentials, meta=nil, admin=false, other={})
86
+ @access = access
87
+ @base = base
88
+ @oid = oid
87
89
  @credentials = credentials
88
90
  @admin = admin
89
91
  @active = other.has_key?(:active) ? other[:active] : false
90
92
  @meta = meta
91
- @roles = Roles.new(self, other[:role])
92
- @privileges = Privileges.new(self, other[:privileges])
93
+ @roles = RoleList.new(self, other[:roles])
94
+ @privileges = PrivilegeList.new(self, other[:privileges])
93
95
  @logged = false
94
96
 
95
97
  extend(Admin) if @admin
@@ -100,7 +102,7 @@ class Access
100
102
  # accepted as correct) or a plaintext password, the method uses
101
103
  # Access#hash_credentials to hash the passed in value.
102
104
  def credentials=(value)
103
- @credentials = value ? access.hash_credentials(value, @id) : "*"
105
+ @credentials = value ? access.hash_credentials(value, @oid) : "*"
104
106
  save
105
107
  end
106
108
 
@@ -173,22 +175,22 @@ class Access
173
175
 
174
176
  # :nodoc:
175
177
  def eql?(other)
176
- self.class == other.class && @id.eql?(other.id)
178
+ self.class == other.class && @oid.eql?(other.oid)
177
179
  end
178
180
  alias == eql? # in ruby1.8 that's unecessary as == uses eql? per default
179
181
 
180
182
  # :nodoc:
181
183
  def hash
182
- @id.hash
184
+ @oid.hash
183
185
  end
184
186
 
185
187
  # :nodoc:
186
188
  def inspect
187
- "#<%s:0x%08x base: %s id: %s credentials: %s %s%s%s>" % [
189
+ "#<%s:0x%08x base: %s oid: %s credentials: %s %s%s%s>" % [
188
190
  self.class,
189
191
  object_id << 1,
190
192
  "#{@base.class}(#{(class <<@base; self; end).ancestors.first})",
191
- @id.inspect,
193
+ @oid.inspect,
192
194
  @credentials,
193
195
  @active ? 'active' : 'inactive',
194
196
  admin? ? ' admin' : '',
@@ -0,0 +1,17 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Access
10
+ module VERSION # :nodoc:
11
+ MAJOR = 1
12
+ MINOR = 0
13
+ TINY = 0
14
+ STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
15
+ def self.to_s; STRING; end
16
+ end
17
+ end
@@ -7,16 +7,18 @@
7
7
 
8
8
 
9
9
  require 'access'
10
+ require 'thread'
10
11
  require 'yaml'
11
12
 
12
13
 
13
14
 
14
15
  class Access
15
16
 
16
- # An Access compatible storage backend
17
+ # An Access compatible storage backend, based on YAML
17
18
  class YAMLBase
18
19
  include Enumerable
19
20
 
21
+ # The access instance this base is tied to
20
22
  attr_accessor :access
21
23
 
22
24
  # extender: a module that will extend this instance and provide additional
@@ -24,105 +26,119 @@ class Access
24
26
  # framework: the Access instance this data-storage is tied to
25
27
  # path: the path to the data
26
28
  def initialize(extender, path=nil)
27
- @path = path
28
- @extender = extender
29
+ extend(extender) if extender
30
+ @path = path || default_path # default_path comes from the extender
29
31
  @data = {}
30
- extend(extender)
32
+ @lock = Mutex.new
31
33
 
32
- @path ||= default_path
33
34
  raise "Path must be a directory (#@path)" unless File.directory?(@path)
34
35
  end
35
36
 
36
- # The full path and filename to the data object belonging to id
37
- def filename(id)
38
- "#{@path}/#{escape(id)}.yaml"
37
+ # The full path and filename to the data object belonging to oid
38
+ def filename(oid)
39
+ "#{@path}/#{escape(oid)}.yaml"
39
40
  end
40
41
 
41
42
  # Escapes path names
42
- def escape(id)
43
- id.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
43
+ def escape(oid)
44
+ oid.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
44
45
  end
45
46
 
46
- # Extracts the record-id from a path
47
- def path2id(path)
47
+ # Extracts the record-oid from a path
48
+ def path2oid(path)
48
49
  path[(@path.length+1)..-6].gsub(".", "/").gsub(/%([\dA-Fa-f]{2})/) { $1.to_i(16).chr }
49
50
  end
50
51
 
51
52
  # Load an entry, will not write to cache, returns nil if entry doesn't exist
52
- def load(id)
53
- file = filename(id)
54
- File.exist?(file) ? YAML.load_file(file) : nil
53
+ def load(oid)
54
+ file = filename(oid)
55
+ YAML.load_file(file)
56
+ rescue Errno::ENOENT
57
+ nil
55
58
  end
56
59
 
57
60
  # Loads all entries into cache
58
61
  def cache_all
59
62
  slice = (@path.length+1)..-6
60
- Dir.glob("#{@path}/*.yaml") { |path| # /**
61
- id = path[slice]
62
- @data[id] ||= load(id)
63
+ @lock.synchronize {
64
+ Dir.glob("#{@path}/*.yaml") { |path| # /**
65
+ oid = path[slice]
66
+ @data[oid] ||= load(oid)
67
+ }
63
68
  }
64
69
  end
65
70
 
66
71
 
67
- # Retrieve the object with id, if force_load cache will be ignored.
68
- def [](id, force_load=false)
69
- if force_load then
70
- @data[id] = load(id)
71
- else
72
- @data[id] ||= load(id)
73
- end
72
+ # Retrieve the object with oid, if force_load cache will be ignored.
73
+ def [](oid, force_load=false)
74
+ @lock.synchronize {
75
+ if force_load then
76
+ @data[oid] = load(oid)
77
+ else
78
+ @data[oid] ||= load(oid)
79
+ end
80
+ }
74
81
  end
75
82
 
76
83
  # Add a record to the storage
77
84
  def <<(record)
78
- @data[record.id] = record
79
- save(record.id)
85
+ @lock.synchronize {
86
+ @data[record.oid] = record
87
+ save(record.oid)
88
+ }
80
89
  end
81
90
  alias add <<
82
91
 
83
92
  # Delete a record from the storage
84
- def delete(id)
85
- @data.delete(id)
86
- File.delete(filename(id))
93
+ def delete_oid(oid)
94
+ @lock.synchronize {
95
+ @data.delete(oid)
96
+ File.delete(filename(oid))
97
+ }
87
98
  end
88
99
 
89
- # All record id's
90
- def keys
91
- Dir.glob("#{@path}/*.yaml").map { |path|
92
- path2id(path)
93
- }
100
+ # Delete a record from the storage
101
+ def delete(record)
102
+ delete_oid(record.oid)
94
103
  end
95
-
96
- # Iterate over records, yielding id and object
104
+
105
+ # Iterate over records, yielding oid and object
97
106
  def each
98
107
  slice = (@path.length+1)..-6
99
108
  Dir.glob("#{@path}/*.yaml") { |path|
100
- id = path[slice]
101
- yield(id, @data[id] || load(id))
109
+ oid = path[slice]
110
+ yield(oid, @data[oid] || load(oid))
102
111
  }
103
112
  end
104
113
 
105
- # Iterate over records, yielding id
114
+ # Iterate over records, yielding oid
106
115
  def each_key
107
116
  Dir.glob("#{@path}/*.yaml") { |path|
108
- yield(path2id(path))
117
+ yield(path2oid(path))
118
+ }
119
+ end
120
+
121
+ # All record oid's
122
+ def keys
123
+ Dir.glob("#{@path}/*.yaml").map { |path|
124
+ path2oid(path)
109
125
  }
110
126
  end
111
127
 
112
128
  # Check existency of an object.
113
- def exists?(id)
114
- @data.has_key?(id) || File.exists?(filename(id))
129
+ def exists?(oid)
130
+ @data.has_key?(oid) || File.exists?(filename(oid))
115
131
  end
116
132
  alias exist? exists?
117
133
 
118
134
  # Synchronize data-cache with filesystem.
119
- def save(id=nil)
120
- if @data.has_key?(id) then
121
- File.open(filename(id), 'w') { |fh| fh.write(@data[id].storable.to_yaml) }
122
- elsif id.nil? then
135
+ def save(oid=nil)
136
+ if @data.has_key?(oid) then
137
+ File.open(filename(oid), 'w') { |fh| fh.write(@data[oid].storable.to_yaml) }
138
+ elsif oid.nil? then
123
139
  @data.each_key { |key| save(key) }
124
140
  else
125
- raise "Could not save '#{id}' since it's not in @data"
141
+ raise "Could not save '#{oid}' since it's not in @data"
126
142
  end
127
143
  end
128
144
  end