cantango-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +54 -0
  4. data/Gemfile.lock +231 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.mdown +90 -0
  7. data/Rakefile +48 -0
  8. data/VERSION +1 -0
  9. data/cantango-core.gemspec +177 -0
  10. data/lib/cantango/ability.rb +5 -0
  11. data/lib/cantango/ability/base.rb +61 -0
  12. data/lib/cantango/ability/cache.rb +7 -0
  13. data/lib/cantango/ability/cache/key.rb +38 -0
  14. data/lib/cantango/ability/cached.rb +28 -0
  15. data/lib/cantango/ability/executor.rb +7 -0
  16. data/lib/cantango/ability/executor/base.rb +53 -0
  17. data/lib/cantango/ability/executor/cache_mode.rb +33 -0
  18. data/lib/cantango/ability/executor/modes.rb +52 -0
  19. data/lib/cantango/ability/executor/no_cache_mode.rb +17 -0
  20. data/lib/cantango/ability/helper.rb +11 -0
  21. data/lib/cantango/ability/helper/account.rb +13 -0
  22. data/lib/cantango/ability/helper/engine.rb +32 -0
  23. data/lib/cantango/ability/helper/role.rb +21 -0
  24. data/lib/cantango/ability/helper/role_group.rb +21 -0
  25. data/lib/cantango/ability/helper/user.rb +18 -0
  26. data/lib/cantango/cancan/rule.rb +6 -0
  27. data/lib/cantango/core.rb +84 -0
  28. data/lib/cantango/engine.rb +39 -0
  29. data/lib/cantango/filter.rb +5 -0
  30. data/lib/cantango/filter/base.rb +31 -0
  31. data/lib/cantango/helpers.rb +5 -0
  32. data/lib/cantango/helpers/debug.rb +10 -0
  33. data/lib/cantango/loader.rb +5 -0
  34. data/lib/cantango/loader/yaml.rb +33 -0
  35. data/lib/cantango/macros.rb +11 -0
  36. data/lib/cantango/macros/account.rb +14 -0
  37. data/lib/cantango/macros/user.rb +16 -0
  38. data/lib/cantango/model.rb +5 -0
  39. data/lib/cantango/model/guest.rb +25 -0
  40. data/lib/cantango/rails.rb +7 -0
  41. data/lib/cantango/rails/engine.rb +63 -0
  42. data/lib/cantango/rails/helpers/base_helper.rb +29 -0
  43. data/lib/cantango/rails/helpers/controller_helper.rb +17 -0
  44. data/lib/cantango/rails/helpers/rest_helper.rb +76 -0
  45. data/lib/cantango/rails/helpers/view_helper.rb +17 -0
  46. data/lib/cantango/rails/railtie.rb +7 -0
  47. data/lib/cantango/rspec.rb +1 -0
  48. data/lib/cantango/rspec/config.rb +3 -0
  49. data/lib/cantango/rspec/matchers.rb +1 -0
  50. data/lib/cantango/rspec/matchers/be_allowed_to.rb +28 -0
  51. data/lib/cantango/rules.rb +8 -0
  52. data/lib/cantango/rules/adaptor.rb +37 -0
  53. data/lib/cantango/rules/adaptor/active_record.rb +10 -0
  54. data/lib/cantango/rules/adaptor/data_mapper.rb +11 -0
  55. data/lib/cantango/rules/adaptor/generic.rb +16 -0
  56. data/lib/cantango/rules/adaptor/mongo.rb +19 -0
  57. data/lib/cantango/rules/adaptor/mongo_mapper.rb +10 -0
  58. data/lib/cantango/rules/adaptor/mongoid.rb +9 -0
  59. data/lib/cantango/rules/adaptor/relational.rb +13 -0
  60. data/lib/cantango/rules/dsl.rb +24 -0
  61. data/lib/cantango/rules/relation.rb +67 -0
  62. data/lib/cantango/rules/rule_class.rb +11 -0
  63. data/lib/cantango/rules/scope.rb +24 -0
  64. data/lib/cantango/scope.rb +5 -0
  65. data/lib/cantango/scope/ability.rb +20 -0
  66. data/lib/generators/cantango/install/install_generator.rb +37 -0
  67. data/lib/generators/cantango/install/templates/cantango.rb +4 -0
  68. data/lib/generators/cantango/install/templates/categories.yml +0 -0
  69. data/lib/generators/cantango/install/templates/permissions.yml +6 -0
  70. data/spec/cantango/ability/base_spec.rb +73 -0
  71. data/spec/cantango/ability/cached_spec.rb +0 -0
  72. data/spec/cantango/ability/executor/base2.rb +75 -0
  73. data/spec/cantango/ability/executor/base_spec.rb +67 -0
  74. data/spec/cantango/ability/executor/cache_mode_spec.rb +77 -0
  75. data/spec/cantango/ability/executor/modes_spec.rb +68 -0
  76. data/spec/cantango/ability/executor/no_cache_mode_spec.rb +0 -0
  77. data/spec/cantango/cancan/rule_spec.rb +0 -0
  78. data/spec/cantango/core_spec.rb +9 -0
  79. data/spec/cantango/engine_spec.rb +0 -0
  80. data/spec/cantango/filter/base_spec.rb +0 -0
  81. data/spec/cantango/helpers/debug_spec.rb +0 -0
  82. data/spec/cantango/loader/yaml_spec.rb +0 -0
  83. data/spec/cantango/macros/account_spec.rb +0 -0
  84. data/spec/cantango/macros/user_spec.rb +0 -0
  85. data/spec/cantango/rspec/be_allowed_to_spec.rb +0 -0
  86. data/spec/cantango/rules/adaptor/active_record_spec.rb +0 -0
  87. data/spec/cantango/rules/adaptor/data_mapper_spec.rb +0 -0
  88. data/spec/cantango/rules/adaptor/mongo_mapper_spec.rb +0 -0
  89. data/spec/cantango/rules/adaptor/mongoid_spec.rb +0 -0
  90. data/spec/cantango/rules/adaptor_spec.rb +0 -0
  91. data/spec/cantango/rules/dsl_spec.rb +0 -0
  92. data/spec/cantango/rules/relation_spec.rb +0 -0
  93. data/spec/cantango/rules/rule_class_spec.rb +0 -0
  94. data/spec/cantango/rules/scope_spec.rb +0 -0
  95. data/spec/cantango/rules_spec.rb +55 -0
  96. data/spec/cantango/scope/ability_spec.rb +0 -0
  97. data/spec/cantango_spec.rb +0 -0
  98. data/spec/generators/cantango/install_generator_spec.rb +42 -0
  99. data/spec/spec_helper.rb +9 -0
  100. metadata +310 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,177 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "cantango-core"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kristian Mandrup", "Stanislaw Pankevich"]
12
+ s.date = "2011-11-25"
13
+ s.description = "Define your permission rules as role- or role group specific permits.\nIntegrates well with multiple Devise user acounts.\nIncludes rules caching.\nStore permissions in yaml file or key-value store"
14
+ s.email = "kmandrup@gmail.com, s.pankevich@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.mdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.mdown",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "cantango-core.gemspec",
29
+ "lib/cantango/ability.rb",
30
+ "lib/cantango/ability/base.rb",
31
+ "lib/cantango/ability/cache.rb",
32
+ "lib/cantango/ability/cache/key.rb",
33
+ "lib/cantango/ability/cached.rb",
34
+ "lib/cantango/ability/executor.rb",
35
+ "lib/cantango/ability/executor/base.rb",
36
+ "lib/cantango/ability/executor/cache_mode.rb",
37
+ "lib/cantango/ability/executor/modes.rb",
38
+ "lib/cantango/ability/executor/no_cache_mode.rb",
39
+ "lib/cantango/ability/helper.rb",
40
+ "lib/cantango/ability/helper/account.rb",
41
+ "lib/cantango/ability/helper/engine.rb",
42
+ "lib/cantango/ability/helper/role.rb",
43
+ "lib/cantango/ability/helper/role_group.rb",
44
+ "lib/cantango/ability/helper/user.rb",
45
+ "lib/cantango/cancan/rule.rb",
46
+ "lib/cantango/core.rb",
47
+ "lib/cantango/engine.rb",
48
+ "lib/cantango/filter.rb",
49
+ "lib/cantango/filter/base.rb",
50
+ "lib/cantango/helpers.rb",
51
+ "lib/cantango/helpers/debug.rb",
52
+ "lib/cantango/loader.rb",
53
+ "lib/cantango/loader/yaml.rb",
54
+ "lib/cantango/macros.rb",
55
+ "lib/cantango/macros/account.rb",
56
+ "lib/cantango/macros/user.rb",
57
+ "lib/cantango/model.rb",
58
+ "lib/cantango/model/guest.rb",
59
+ "lib/cantango/rails.rb",
60
+ "lib/cantango/rails/engine.rb",
61
+ "lib/cantango/rails/helpers/base_helper.rb",
62
+ "lib/cantango/rails/helpers/controller_helper.rb",
63
+ "lib/cantango/rails/helpers/rest_helper.rb",
64
+ "lib/cantango/rails/helpers/view_helper.rb",
65
+ "lib/cantango/rails/railtie.rb",
66
+ "lib/cantango/rspec.rb",
67
+ "lib/cantango/rspec/config.rb",
68
+ "lib/cantango/rspec/matchers.rb",
69
+ "lib/cantango/rspec/matchers/be_allowed_to.rb",
70
+ "lib/cantango/rules.rb",
71
+ "lib/cantango/rules/adaptor.rb",
72
+ "lib/cantango/rules/adaptor/active_record.rb",
73
+ "lib/cantango/rules/adaptor/data_mapper.rb",
74
+ "lib/cantango/rules/adaptor/generic.rb",
75
+ "lib/cantango/rules/adaptor/mongo.rb",
76
+ "lib/cantango/rules/adaptor/mongo_mapper.rb",
77
+ "lib/cantango/rules/adaptor/mongoid.rb",
78
+ "lib/cantango/rules/adaptor/relational.rb",
79
+ "lib/cantango/rules/dsl.rb",
80
+ "lib/cantango/rules/relation.rb",
81
+ "lib/cantango/rules/rule_class.rb",
82
+ "lib/cantango/rules/scope.rb",
83
+ "lib/cantango/scope.rb",
84
+ "lib/cantango/scope/ability.rb",
85
+ "lib/generators/cantango/install/install_generator.rb",
86
+ "lib/generators/cantango/install/templates/cantango.rb",
87
+ "lib/generators/cantango/install/templates/categories.yml",
88
+ "lib/generators/cantango/install/templates/permissions.yml",
89
+ "spec/cantango/ability/base_spec.rb",
90
+ "spec/cantango/ability/cached_spec.rb",
91
+ "spec/cantango/ability/executor/base2.rb",
92
+ "spec/cantango/ability/executor/base_spec.rb",
93
+ "spec/cantango/ability/executor/cache_mode_spec.rb",
94
+ "spec/cantango/ability/executor/modes_spec.rb",
95
+ "spec/cantango/ability/executor/no_cache_mode_spec.rb",
96
+ "spec/cantango/cancan/rule_spec.rb",
97
+ "spec/cantango/core_spec.rb",
98
+ "spec/cantango/engine_spec.rb",
99
+ "spec/cantango/filter/base_spec.rb",
100
+ "spec/cantango/helpers/debug_spec.rb",
101
+ "spec/cantango/loader/yaml_spec.rb",
102
+ "spec/cantango/macros/account_spec.rb",
103
+ "spec/cantango/macros/user_spec.rb",
104
+ "spec/cantango/rspec/be_allowed_to_spec.rb",
105
+ "spec/cantango/rules/adaptor/active_record_spec.rb",
106
+ "spec/cantango/rules/adaptor/data_mapper_spec.rb",
107
+ "spec/cantango/rules/adaptor/mongo_mapper_spec.rb",
108
+ "spec/cantango/rules/adaptor/mongoid_spec.rb",
109
+ "spec/cantango/rules/adaptor_spec.rb",
110
+ "spec/cantango/rules/dsl_spec.rb",
111
+ "spec/cantango/rules/relation_spec.rb",
112
+ "spec/cantango/rules/rule_class_spec.rb",
113
+ "spec/cantango/rules/scope_spec.rb",
114
+ "spec/cantango/rules_spec.rb",
115
+ "spec/cantango/scope/ability_spec.rb",
116
+ "spec/cantango_spec.rb",
117
+ "spec/generators/cantango/install_generator_spec.rb",
118
+ "spec/spec_helper.rb"
119
+ ]
120
+ s.homepage = "http://github.com/kristianmandrup/cantango"
121
+ s.licenses = ["MIT"]
122
+ s.require_paths = ["lib"]
123
+ s.rubygems_version = "1.8.10"
124
+ s.summary = "CanCan extension with role oriented permission management and more"
125
+
126
+ if s.respond_to? :specification_version then
127
+ s.specification_version = 3
128
+
129
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
130
+ s.add_runtime_dependency(%q<rails>, [">= 3.0.1"])
131
+ s.add_runtime_dependency(%q<cancan>, [">= 1.4"])
132
+ s.add_runtime_dependency(%q<sugar-high>, [">= 0.6.0"])
133
+ s.add_runtime_dependency(%q<sweetloader>, ["~> 0.1.0"])
134
+ s.add_runtime_dependency(%q<hashie>, [">= 0"])
135
+ s.add_development_dependency(%q<rspec-rails>, [">= 2.6.1"])
136
+ s.add_development_dependency(%q<forgery>, [">= 0.3"])
137
+ s.add_development_dependency(%q<factory_girl>, [">= 0"])
138
+ s.add_development_dependency(%q<sqlite3>, [">= 0"])
139
+ s.add_development_dependency(%q<rspec>, [">= 2.6.0"])
140
+ s.add_development_dependency(%q<jeweler>, [">= 1.6.4"])
141
+ s.add_development_dependency(%q<bundler>, [">= 1.1.rc"])
142
+ s.add_development_dependency(%q<rdoc>, [">= 0"])
143
+ s.add_development_dependency(%q<rake>, [">= 0.9.2.2"])
144
+ else
145
+ s.add_dependency(%q<rails>, [">= 3.0.1"])
146
+ s.add_dependency(%q<cancan>, [">= 1.4"])
147
+ s.add_dependency(%q<sugar-high>, [">= 0.6.0"])
148
+ s.add_dependency(%q<sweetloader>, ["~> 0.1.0"])
149
+ s.add_dependency(%q<hashie>, [">= 0"])
150
+ s.add_dependency(%q<rspec-rails>, [">= 2.6.1"])
151
+ s.add_dependency(%q<forgery>, [">= 0.3"])
152
+ s.add_dependency(%q<factory_girl>, [">= 0"])
153
+ s.add_dependency(%q<sqlite3>, [">= 0"])
154
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
155
+ s.add_dependency(%q<jeweler>, [">= 1.6.4"])
156
+ s.add_dependency(%q<bundler>, [">= 1.1.rc"])
157
+ s.add_dependency(%q<rdoc>, [">= 0"])
158
+ s.add_dependency(%q<rake>, [">= 0.9.2.2"])
159
+ end
160
+ else
161
+ s.add_dependency(%q<rails>, [">= 3.0.1"])
162
+ s.add_dependency(%q<cancan>, [">= 1.4"])
163
+ s.add_dependency(%q<sugar-high>, [">= 0.6.0"])
164
+ s.add_dependency(%q<sweetloader>, ["~> 0.1.0"])
165
+ s.add_dependency(%q<hashie>, [">= 0"])
166
+ s.add_dependency(%q<rspec-rails>, [">= 2.6.1"])
167
+ s.add_dependency(%q<forgery>, [">= 0.3"])
168
+ s.add_dependency(%q<factory_girl>, [">= 0"])
169
+ s.add_dependency(%q<sqlite3>, [">= 0"])
170
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
171
+ s.add_dependency(%q<jeweler>, [">= 1.6.4"])
172
+ s.add_dependency(%q<bundler>, [">= 1.1.rc"])
173
+ s.add_dependency(%q<rdoc>, [">= 0"])
174
+ s.add_dependency(%q<rake>, [">= 0.9.2.2"])
175
+ end
176
+ end
177
+
@@ -0,0 +1,5 @@
1
+ module CanTango
2
+ module Ability
3
+ autoload_modules :Base, :Cache, :Cached, :Executor, :Helper
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ module CanTango
2
+ module Ability
3
+ class Base
4
+ include CanCan::Ability
5
+
6
+ attr_reader :options, :candidate
7
+
8
+ # Equivalent to a CanCan Ability#initialize call
9
+ # which executes all the permission logic
10
+ def initialize candidate, options = {}
11
+ raise "Candidate must be something!" if !candidate
12
+ @candidate, @options = candidate, options
13
+
14
+ clear_rules!
15
+ permit_rules
16
+
17
+ execute_engines! if engines_on?
18
+ end
19
+
20
+ def cached?
21
+ false
22
+ end
23
+
24
+ def rules
25
+ @rules
26
+ end
27
+
28
+ # developer can add rules here to be included in all subclasses!
29
+ def permit_rules
30
+ end
31
+
32
+ def clear_rules!
33
+ @rules ||= default_rules
34
+ end
35
+
36
+ def session
37
+ @session = options[:session] || {} # seperate session cache for each type of user?
38
+ end
39
+
40
+ def subject
41
+ return candidate.active_user if masquerade_user?
42
+ return candidate.active_account if masquerade_account?
43
+ candidate
44
+ end
45
+
46
+ def config
47
+ CanTango.config
48
+ end
49
+
50
+ Helper.modules.each do |name|
51
+ include "CanTango::Ability::Helper::#{name.to_s.camelize}".constantize
52
+ end
53
+
54
+ protected
55
+
56
+ def default_rules
57
+ []
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ module CanTango
2
+ module Ability
3
+ module Cache
4
+ autoload_modules :Key
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module CanTango
2
+ module Ability
3
+ module Cache
4
+ class Key
5
+ attr_reader :user, :subject
6
+
7
+ def initialize user, subject = nil
8
+ @user = user
9
+ @subject = subject || user
10
+ end
11
+
12
+ def self.create_for ability
13
+ self.new ability.user, ability.subject
14
+ end
15
+
16
+ def value
17
+ raise "No key could be generated for User:#{user.inspect} and Subject:#{subject} - key field: #{user_key_field}" if !user_key
18
+ @value ||= user_key.hash
19
+ end
20
+
21
+ def to_s
22
+ "key hash: #{value}"
23
+ end
24
+
25
+ protected
26
+
27
+ def user_key
28
+ # raise "#{user.class} must have a method ##{user_key_field}. You can configure this with CanTango.config#user.unique_key_field" if !user.respond_to?(user_key_field)
29
+ user.send(user_key_field) if user.respond_to? user_key_field
30
+ end
31
+
32
+ def user_key_field
33
+ CanTango.config.user.unique_key_field || :email
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ module CanTango
2
+ module Ability
3
+ class Cached < Base
4
+ # Equivalent to a CanCan Ability#initialize call
5
+ # which executes all the permission logic
6
+ def initialize candidate, options = {}
7
+ raise "Candidate must be something!" if !candidate
8
+ @candidate, @options = candidate, options
9
+
10
+ # return if cached_rules?
11
+
12
+ clear_rules!
13
+ permit_rules
14
+
15
+ execute_engines! if engines_on?
16
+
17
+ # cache_rules!
18
+ end
19
+
20
+ def cached?
21
+ true
22
+ end
23
+
24
+ def permit_rules
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module CanTango
2
+ module Ability
3
+ module Executor
4
+ autoload_modules :Base, :CacheMode, :NoCacheMode, :Modes
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,53 @@
1
+ module CanTango
2
+ module Ability
3
+ module Executor
4
+ class Base
5
+ include CanTango::Helpers::Debug
6
+
7
+ delegate :session, :user, :subject, :candidate, :cached?, :to => :ability
8
+
9
+ def execute!
10
+ return if !valid?
11
+ start_execute
12
+ return if cached? # rule set already loaded
13
+
14
+ clear_rules!
15
+ permit_rules
16
+
17
+ end_execute
18
+ rules # return rule set
19
+ end
20
+
21
+ def rules
22
+ @rules ||= []
23
+ end
24
+
25
+ def clear_rules!
26
+ @rules ||= []
27
+ end
28
+
29
+ def permit_rules
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def cached?
34
+ false
35
+ end
36
+
37
+ protected
38
+
39
+ def start_execute
40
+ debug "Executing base Ability"
41
+ end
42
+
43
+ def end_execute
44
+ debug "DONE"
45
+ end
46
+
47
+ def key_method_names
48
+ []
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module CanTango
2
+ class Ability
3
+ module Executor
4
+ class CachedMode < Base
5
+ include CanTango::Ability::CacheHelpers
6
+
7
+ # FIX! no reason for double cache check!
8
+ def cached?
9
+ @rules = cached_rules if cached_rules? && caching_on?
10
+ end
11
+
12
+ def cache
13
+ @cache ||= cache_class.new self, :cache_key => cache_key, :key_method_names => key_method_names
14
+ end
15
+
16
+ protected
17
+
18
+ def cache_class
19
+ CanTango::Ability::Cache
20
+ end
21
+
22
+ def start_execute
23
+ debug "Executing cached ability"
24
+ end
25
+
26
+ def end_execute
27
+ cache_rules! if caching_on?
28
+ debug "DONE"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ module CanTango
2
+ module Ability
3
+ module Executor
4
+ class Modes < Base
5
+ def initialize candidate, options = {}
6
+ raise "Candidate must be something!" if !candidate
7
+ @candidate, @options = [candidate, options]
8
+ calculate_modal_rules.normalize!
9
+ end
10
+
11
+ def rules
12
+ @rules ||= []
13
+ end
14
+
15
+ def modal_rules mode
16
+ mode?(mode) ? get_ability(mode).rules : []
17
+ end
18
+
19
+ protected
20
+
21
+ def calculate_modal_rules
22
+ @rules = modes.inject([]) do |result, mode|
23
+ result = result + modal_rules(mode)
24
+ result
25
+ end
26
+ self
27
+ end
28
+
29
+ def normalize!
30
+ rules.flatten!
31
+ rules.compact!
32
+ end
33
+
34
+ def get_ability mode
35
+ ability_class_for(mode).new candidate, options
36
+ end
37
+
38
+ def ability_class_for mode
39
+ mode == :cache ? CanTango::Ability::Cached : CanTango::Ability::NonCached
40
+ end
41
+
42
+ def mode? mode
43
+ modes.include?(mode)
44
+ end
45
+
46
+ def modes
47
+ CanTango.config.ability.modes
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end