aquarium 0.4.0 → 0.4.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 (45) hide show
  1. data/CHANGES +26 -5
  2. data/README +8 -8
  3. data/RELEASE-PLAN +20 -2
  4. data/TODO.rb +26 -0
  5. data/UPGRADE +5 -5
  6. data/examples/aspect_design_example.rb +1 -1
  7. data/examples/aspect_design_example_spec.rb +1 -1
  8. data/examples/design_by_contract_example.rb +4 -9
  9. data/examples/design_by_contract_example_spec.rb +7 -9
  10. data/examples/exception_wrapping_example.rb +48 -0
  11. data/examples/exception_wrapping_example_spec.rb +49 -0
  12. data/examples/reusable_aspect_hack_example.rb +56 -0
  13. data/examples/reusable_aspect_hack_example_spec.rb +80 -0
  14. data/lib/aquarium.rb +1 -0
  15. data/lib/aquarium/aspects.rb +1 -1
  16. data/lib/aquarium/aspects/advice.rb +16 -13
  17. data/lib/aquarium/aspects/aspect.rb +81 -56
  18. data/lib/aquarium/aspects/join_point.rb +4 -4
  19. data/lib/aquarium/aspects/pointcut.rb +49 -73
  20. data/lib/aquarium/dsl.rb +2 -0
  21. data/lib/aquarium/dsl/aspect_dsl.rb +77 -0
  22. data/lib/aquarium/{aspects/dsl → dsl}/object_dsl.rb +2 -2
  23. data/lib/aquarium/extras/design_by_contract.rb +1 -1
  24. data/lib/aquarium/finders.rb +1 -1
  25. data/lib/aquarium/finders/method_finder.rb +26 -26
  26. data/lib/aquarium/finders/type_finder.rb +45 -39
  27. data/lib/aquarium/utils/array_utils.rb +6 -5
  28. data/lib/aquarium/utils/default_logger.rb +2 -1
  29. data/lib/aquarium/utils/options_utils.rb +178 -67
  30. data/lib/aquarium/utils/set_utils.rb +8 -3
  31. data/lib/aquarium/version.rb +1 -1
  32. data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -14
  33. data/spec/aquarium/aspects/aspect_spec.rb +91 -7
  34. data/spec/aquarium/aspects/pointcut_spec.rb +61 -0
  35. data/spec/aquarium/{aspects/dsl → dsl}/aspect_dsl_spec.rb +76 -32
  36. data/spec/aquarium/finders/method_finder_spec.rb +80 -80
  37. data/spec/aquarium/finders/type_finder_spec.rb +57 -52
  38. data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +12 -12
  39. data/spec/aquarium/spec_example_types.rb +4 -3
  40. data/spec/aquarium/utils/array_utils_spec.rb +9 -7
  41. data/spec/aquarium/utils/options_utils_spec.rb +106 -5
  42. data/spec/aquarium/utils/set_utils_spec.rb +14 -0
  43. metadata +12 -7
  44. data/lib/aquarium/aspects/dsl.rb +0 -2
  45. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +0 -64
@@ -0,0 +1,2 @@
1
+ # NEVER require 'aquarium/dsl/object_dsl' here!
2
+ require 'aquarium/dsl/aspect_dsl'
@@ -0,0 +1,77 @@
1
+ require 'aquarium/aspects/aspect'
2
+ require 'aquarium/utils/type_utils'
3
+
4
+ # Convenience methods added to the current type to provide a low-level AOP DSL.
5
+ # If you don't want these methods added to a type, then only require aspect.rb
6
+ # and create instances of Aspect.
7
+
8
+ module Aquarium
9
+ module DSLMethods
10
+
11
+ def advise *options, &block
12
+ o = append_implicit_self options
13
+ Aquarium::Aspects::Aspect.new *o, &block
14
+ end
15
+
16
+ %w[before after after_returning after_raising around].each do |advice_kind|
17
+ module_eval(<<-ADVICE_METHODS, __FILE__, __LINE__)
18
+ def #{advice_kind} *options, &block
19
+ advise :#{advice_kind}, *options, &block
20
+ end
21
+ ADVICE_METHODS
22
+ end
23
+
24
+ %w[after after_returning after_raising].each do |after_kind|
25
+ module_eval(<<-AFTER, __FILE__, __LINE__)
26
+ def before_and_#{after_kind} *options, &block
27
+ advise :before, :#{after_kind}, *options, &block
28
+ end
29
+ AFTER
30
+ end
31
+
32
+ alias :after_returning_from :after_returning
33
+ alias :after_raising_within :after_raising
34
+ alias :after_raising_within_or_returning_from :after
35
+
36
+ alias :before_and_after_returning_from :before_and_after_returning
37
+ alias :before_and_after_raising_within :before_and_after_raising
38
+ alias :before_and_after_raising_within_or_returning_from :before_and_after
39
+
40
+ def pointcut *options, &block
41
+ o = append_implicit_self options
42
+ Aquarium::Aspects::Pointcut.new *o, &block
43
+ end
44
+
45
+ private
46
+ def append_implicit_self options
47
+ opts = options.dup
48
+ if (!opts.empty?) && opts.last.kind_of?(Hash)
49
+ opts.last[:default_objects] = self
50
+ else
51
+ opts << {:default_objects => self}
52
+ end
53
+ opts
54
+ end
55
+ end
56
+
57
+ module DSL
58
+ include Aquarium::DSLMethods
59
+ # Add the methods as class, not instance, methods.
60
+ def self.append_features clazz
61
+ super(class << clazz; self; end)
62
+ end
63
+ end
64
+
65
+ # Backwards compatibility with old name.
66
+ module Aspects
67
+ module DSL
68
+ module AspectDSL
69
+ include Aquarium::DSLMethods
70
+ # Add the methods as class, not instance, methods.
71
+ def self.append_features clazz
72
+ super(class << clazz; self; end)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,8 +1,8 @@
1
- require 'aquarium/aspects/dsl/aspect_dsl'
1
+ require 'aquarium/dsl/aspect_dsl'
2
2
 
3
3
  # Add aspect convenience methods to Object. Only require this
4
4
  # file if you really want these methods on all objects in your runtime!
5
5
  class Object
6
- include Aquarium::Aspects::DSL::AspectDSL
6
+ include Aquarium::DSL
7
7
  end
8
8
 
@@ -1,7 +1,7 @@
1
1
  # A simple Design by Contract module. Adds advice to test that the contract, which is specified with
2
2
  # a block passes. Note that it doesn't attempt to handle the correct behavior under contract
3
3
  # inheritance.
4
- # Warning: This module automatically includes Aquarium::Aspects::DSL::AspectDSL into the class with
4
+ # Warning: This module automatically includes Aquarium::DSL into the class with
5
5
  # the contract and it adds the :precondition, :postcondition, and the :invariant methods to Object!
6
6
  require 'aquarium'
7
7
 
@@ -1,3 +1,3 @@
1
1
  require 'aquarium/finders/finder_result'
2
- require 'aquarium/finders/method_finder'
3
2
  require 'aquarium/finders/type_finder'
3
+ require 'aquarium/finders/method_finder'
@@ -1,6 +1,7 @@
1
1
  require 'set'
2
2
  require File.dirname(__FILE__) + '/../utils/array_utils'
3
3
  require File.dirname(__FILE__) + '/../utils/invalid_options'
4
+ require File.dirname(__FILE__) + '/../utils/set_utils'
4
5
  require File.dirname(__FILE__) + '/../utils/type_utils'
5
6
  require File.dirname(__FILE__) + '/../utils/options_utils'
6
7
  require File.dirname(__FILE__) + '/finder_result'
@@ -17,7 +18,7 @@ module Aquarium
17
18
  # Method names, not method objects, are always returned, because we can only get
18
19
  # method objects for instance methods if we have an instance!
19
20
  #
20
- # finder_result = MethodFinder.new.find :types => ... {, :methods => ..., :options => [...]}
21
+ # finder_result = MethodFinder.new.find :types => ... {, :methods => ..., :method_options => [...]}
21
22
  # where
22
23
  # "{}" indicate optional arguments
23
24
  #
@@ -68,10 +69,10 @@ module Aquarium
68
69
  # One or more method names and regular expressions to exclude from the match.
69
70
  # Specify one or an array of values.
70
71
  #
71
- # <tt>:options => method_options</tt>::
72
- # <tt>:method_options => method_options</tt>::
73
- # <tt>:method_option => method_options</tt>::
74
- # <tt>:restricting_methods_to => method_options</tt>::
72
+ # <tt>:method_options => options</tt>::
73
+ # <tt>:method_option => options</tt>::
74
+ # <tt>:options => options</tt>::
75
+ # <tt>:restricting_methods_to => options</tt>::
75
76
  # By default, searches for public instance methods. Specify one or more
76
77
  # of the following options for alternatives. You can combine any of the
77
78
  # <tt>:public</tt>, <tt>:protected</tt>, and <tt>:private</tt>, as well as
@@ -90,7 +91,9 @@ module Aquarium
90
91
  # +Derived+ class that is defined in the +Base+ class, you won't find it!
91
92
  #
92
93
  def find options = {}
93
- init_specification options, CANONICAL_OPTIONS
94
+ init_specification options, CANONICAL_OPTIONS do
95
+ finish_specification_initialization
96
+ end
94
97
  return Aquarium::Finders::FinderResult.new if nothing_to_find?
95
98
  types_and_objects = input_types + input_objects
96
99
  method_names_or_regexps = input_methods
@@ -108,28 +111,25 @@ module Aquarium
108
111
 
109
112
  NIL_OBJECT = MethodFinder.new unless const_defined?(:NIL_OBJECT)
110
113
 
111
- # TODO move Pointcut's options to here.
112
- CANONICAL_OPTIONS = {
113
- "types" => %w[type for_type for_types on_type on_types in_type in_types within_type within_types],
114
+ # TODO remove (or deprecate) the "options" option!
115
+ METHOD_FINDER_CANONICAL_OPTIONS = {
114
116
  "objects" => %w[object for_object for_objects on_object on_objects in_object in_objects within_object within_objects],
115
117
  "methods" => %w[method within_method within_methods calling invoking invocations_of calls_to sending_message_to sending_messages_to],
116
- "options" => %w[method_options method_option restricting_methods_to]
118
+ "method_options" => %w[options method_option restricting_methods_to]
117
119
  }
118
120
 
119
- %w[types objects methods].each do |key|
120
- CANONICAL_OPTIONS["exclude_#{key}"] = CANONICAL_OPTIONS[key].map {|x| "exclude_#{x}"}
121
+ %w[objects methods].each do |key|
122
+ METHOD_FINDER_CANONICAL_OPTIONS["exclude_#{key}"] = METHOD_FINDER_CANONICAL_OPTIONS[key].map {|x| "exclude_#{x}"}
121
123
  end
122
- CANONICAL_OPTIONS["methods"].dup.each do |synonym|
123
- CANONICAL_OPTIONS["methods"] << "#{synonym}_methods_matching"
124
+ METHOD_FINDER_CANONICAL_OPTIONS["methods"].dup.each do |synonym|
125
+ if synonym =~ /methods?$/
126
+ METHOD_FINDER_CANONICAL_OPTIONS["methods"] << "#{synonym}_matching"
127
+ else
128
+ METHOD_FINDER_CANONICAL_OPTIONS["methods"] << "#{synonym}_methods_matching"
129
+ end
124
130
  end
125
131
 
126
- ALL_ALLOWED_OPTIONS = CANONICAL_OPTIONS.keys.inject([]) {|ary,i| ary << i << CANONICAL_OPTIONS[i]}.flatten
127
-
128
- ALL_ALLOWED_OPTION_SYMBOLS = ALL_ALLOWED_OPTIONS.map {|o| o.intern}
129
-
130
- def all_allowed_option_symbols
131
- ALL_ALLOWED_OPTION_SYMBOLS
132
- end
132
+ CANONICAL_OPTIONS = METHOD_FINDER_CANONICAL_OPTIONS.merge(Aquarium::Finders::TypeFinder::TYPE_FINDER_CANONICAL_OPTIONS)
133
133
 
134
134
  RECOGNIZED_METHOD_OPTIONS = {
135
135
  "all" => %w[all_methods],
@@ -200,8 +200,8 @@ module Aquarium
200
200
  Aquarium::Finders::FinderResult.new types_and_objects_to_matched_methods.merge(:not_matched => types_and_objects_not_matched)
201
201
  end
202
202
 
203
- def init_type_specific_specification original_options, options_hash
204
- @specification[:options] = MethodFinder.init_method_options(@specification[:options]) if @specification[:options]
203
+ def finish_specification_initialization
204
+ @specification[:method_options] = MethodFinder.init_method_options(@specification[:method_options]) if @specification[:method_options]
205
205
  extra_validation
206
206
  end
207
207
 
@@ -231,7 +231,7 @@ module Aquarium
231
231
  end
232
232
 
233
233
  def exclude_ancestor_methods?
234
- @specification[:options].include?(:exclude_ancestor_methods)
234
+ @specification[:method_options].include?(:exclude_ancestor_methods)
235
235
  end
236
236
 
237
237
  private
@@ -273,7 +273,7 @@ module Aquarium
273
273
  is_type = Aquarium::Utils::TypeUtils.is_type?(type_or_object)
274
274
  scope_prefixes = []
275
275
  class_instance_prefixes = []
276
- @specification[:options].each do |opt, value|
276
+ @specification[:method_options].each do |opt, value|
277
277
  opt_string = opt.to_s
278
278
  case opt_string
279
279
  when "public", "private", "protected"
@@ -314,7 +314,7 @@ module Aquarium
314
314
  end
315
315
 
316
316
  def extra_validation
317
- method_options = @specification[:options]
317
+ method_options = @specification[:method_options]
318
318
  return if method_options.nil?
319
319
  if method_options.include?(:singleton) &&
320
320
  (method_options.include?(:class) || method_options.include?(:public) ||
@@ -13,8 +13,31 @@ module Aquarium
13
13
  class TypeFinder
14
14
  include Aquarium::Utils::ArrayUtils
15
15
  include Aquarium::Utils::TypeUtils
16
+ include Aquarium::Utils::OptionsUtils
16
17
 
17
- TYPES_SYNONYMS = %w[name names type types]
18
+ def self.add_ancestors_and_descendents_option_variants_for option, options_hash
19
+ all_variants = options_hash[option].dup
20
+ options_hash["#{option}_and_descendents"] = all_variants.map {|x| "#{x}_and_descendents"}
21
+ options_hash["#{option}_and_ancestors"] = all_variants.map {|x| "#{x}_and_ancestors"}
22
+ end
23
+
24
+ TYPE_FINDER_CANONICAL_OPTIONS = {
25
+ "types" => %w[type class classes module modules name names],
26
+ }
27
+ # Add the ancestors and descendents first, then add all the preposition and exclude variants, so the latter
28
+ # are added to the former...
29
+ TYPE_FINDER_CANONICAL_OPTIONS.keys.dup.each do |type_option|
30
+ add_ancestors_and_descendents_option_variants_for type_option, TYPE_FINDER_CANONICAL_OPTIONS
31
+ end
32
+ TYPE_FINDER_CANONICAL_OPTIONS.keys.dup.each do |type_option|
33
+ add_prepositional_option_variants_for type_option, TYPE_FINDER_CANONICAL_OPTIONS
34
+ add_exclude_options_for type_option, TYPE_FINDER_CANONICAL_OPTIONS
35
+ end
36
+
37
+ CANONICAL_OPTIONS = TYPE_FINDER_CANONICAL_OPTIONS.dup
38
+
39
+ canonical_options_given_methods CANONICAL_OPTIONS
40
+ canonical_option_accessor CANONICAL_OPTIONS
18
41
 
19
42
  # Usage:
20
43
  # finder_result = TypeFinder.new.find [options => [...] ]
@@ -90,54 +113,37 @@ module Aquarium
90
113
  # Note: a common idiom in aspects is to include descendents of a type, but not the type
91
114
  # itself. You can do as in the following example:
92
115
  # <tt>... :type_and_descendents => "Foo", :exclude_type => "Foo"
93
- # TODO: Use the new OptionsUtils.
116
+ #
94
117
  def find options = {}
95
- result = Aquarium::Finders::FinderResult.new
96
- excluded = Aquarium::Finders::FinderResult.new
97
- unknown_options = []
98
- input_type_nil = false
99
- noop = false
100
- options.each do |option, value|
101
- unless TypeFinder.is_recognized_option option
102
- unknown_options << option
103
- next
104
- end
105
- if value.nil?
106
- input_type_nil = true
107
- next
108
- end
109
- noop = value if option == :noop
110
- next if noop
111
- if option.to_s =~ /^exclude_/
112
- excluded << find_matching(value, option)
113
- else
114
- result << find_matching(value, option)
115
- end
116
- end
117
- handle_errors unknown_options, input_type_nil
118
- result - excluded
118
+ init_specification options, CANONICAL_OPTIONS
119
+ result = do_find_types
120
+ unset_specification
121
+ result
119
122
  end
120
123
 
121
124
 
122
125
  protected
123
126
 
124
- def handle_errors unknown_options, input_type_nil
125
- message = ""
126
- message += "Unknown options: #{unknown_options.inspect}. " unless unknown_options.empty?
127
- message += "Input type specification can't be nil! " if input_type_nil
128
- raise Aquarium::Utils::InvalidOptions.new(message) unless message.empty?
127
+ # Hack. Since the finder could be reused, unset the specification created by #find.
128
+ def unset_specification
129
+ @specification = {}
129
130
  end
130
-
131
- def self.is_recognized_option option_or_symbol
132
- TYPES_SYNONYMS.each do |t|
133
- ['', "exclude_"].each do |excl|
134
- return true if ["#{excl}#{t}", "#{excl}#{t}_and_descendents", "#{excl}#{t}_and_ancestors"].include?(option_or_symbol.to_s)
131
+
132
+ def do_find_types
133
+ result = Aquarium::Finders::FinderResult.new
134
+ excluded = Aquarium::Finders::FinderResult.new
135
+ return result if noop
136
+ @specification.each do |option, types|
137
+ next unless TYPE_FINDER_CANONICAL_OPTIONS.keys.include?(option.to_s)
138
+ next if types.nil? or types.empty?
139
+ target_result = option.to_s =~ /^exclude_/ ? excluded : result
140
+ types.each do |value|
141
+ target_result << find_matching(value, option)
135
142
  end
136
143
  end
137
- return true if option_or_symbol.to_s.eql?("noop")
138
- false
144
+ result - excluded
139
145
  end
140
-
146
+
141
147
  def find_matching regexpes_or_names, option
142
148
  result = Aquarium::Finders::FinderResult.new
143
149
  expressions = make_array regexpes_or_names
@@ -7,22 +7,23 @@ module Aquarium
7
7
 
8
8
  # Return an array containing the input item or list of items. If the input
9
9
  # is an array, it is returned. In all cases, the constructed array is a
10
- # flattened version of the input and any nil elements are removed by #strip_nils.
10
+ # flattened version of the input and any nil elements are removed by #strip_array_nils.
11
11
  # Note that this behavior effectively converts +nil+ to +[]+.
12
12
  def make_array *value_or_enum
13
13
  ArrayUtils.make_array value_or_enum
14
14
  end
15
15
 
16
16
  def self.make_array *value_or_enum
17
- strip_nils do_make_array(value_or_enum)
17
+ strip_array_nils do_make_array(value_or_enum)
18
18
  end
19
19
 
20
20
  # Return a copy of the input array with all nils removed.
21
- def strip_nils array
22
- ArrayUtils.strip_nils array
21
+ def strip_array_nils array
22
+ ArrayUtils.strip_array_nils array
23
23
  end
24
24
 
25
- def self.strip_nils array
25
+ # Return a copy of the input array with all nils removed.
26
+ def self.strip_array_nils array
26
27
  array.to_a.compact
27
28
  end
28
29
 
@@ -6,8 +6,9 @@ module Aquarium
6
6
  # Individual objects may chose to create their own loggers.
7
7
  module DefaultLogger
8
8
 
9
+ DEFAULT_SEVERITY_LEVEL = Logger::Severity::WARN
9
10
  @@default_logger = Logger.new STDERR
10
- @@default_logger.level = Logger::Severity::WARN
11
+ @@default_logger.level = DEFAULT_SEVERITY_LEVEL
11
12
 
12
13
  def self.logger
13
14
  @@default_logger
@@ -4,44 +4,88 @@ require 'aquarium/utils/default_logger'
4
4
  module Aquarium
5
5
  module Utils
6
6
 
7
- # Support parsing and processing of key-value pairs of options.
8
- # Types including this module must define the following methods (see Pointcut for an example):
9
- # <tt>all_allowed_option_symbols</tt>
10
- # Return an array of all allowed options as symbols.
11
- # <tt>init_type_specific_specification(original_options, options_hash)</tt>
12
- # Called to perform any final options handling unique for the type (optional).
13
- # In addition, including types should have their <tt>initialize</tt> methods calls this module's
14
- # <tt>init_specification</tt> to do the options processing.
7
+ # Support parsing and processing of key-value pairs of options, where the values are always converted
8
+ # to sets.
9
+ # Types including this module should have their <tt>initialize</tt> methods call this module's
10
+ # <tt>init_specification</tt>
11
+ # to do the options processing. See its documentation for more details.
12
+ #
13
+ # Several class methods are included in including types for defining convenience instance methods.
14
+ # for options +:foo+ and +:bar+, calling:
15
+ # <tt>canonical_options_given_methods :foo, :bar</tt>
16
+ # will define several methods for each option specified, e.g.,:
17
+ # <tt>foo_given? # => returns true if a value was specified for the :foo option</tt>
18
+ # <tt>foo_given # => returns the value of @specification[:foo]</tt>
19
+ # <tt>bar_given? # etc.
20
+ # <tt>bar_given
21
+ # If you would like corresponding reader and writer methods, pass a list of the keys for which you want these
22
+ # methods defined to
23
+ # <tt>canonical_option_reader :foo, :bar # analogous to attr_reader
24
+ # <tt>canonical_option_writer :foo, :bar # analogous to attr_writer
25
+ # <tt>canonical_option_accessor :foo, :bar # analogous to attr_accessor
26
+ # For all of these methods, you can also pass CANONICAL_OPTIONS (discussed below) to define methods
27
+ # for all of the "canonical" options. _E.g.,_
28
+ # <tt>canonical_option_accessor CANONICAL_OPTIONS
29
+ #
30
+ # These methods are not defined by default to prevent accidentally overriding other methods that you might
31
+ # have defined with the same names. Also, note that the writer methods will convert the inputs to sets,
32
+ # following the conventions for the options and the readers will return the sets. If you want different handling,
33
+ # you'll have to provide custom implementations. Note that special-case accessor methods are already defined
34
+ # for the :noop and :logger options (discussed below) where the writers expect single values, not sets, and the
35
+ # readers return the single values.
36
+ # Finally, these +canonical_option_*+ methods should only be called with the *keys* for the +CANONICAL_OPTIONS+.
37
+ # The keys are considered the "canonical options", while the values for the keys are synonyms that can be used instead.
15
38
  #
16
39
  # This module also defines several universal options that will be available to all types that include this module:
17
- # <tt>:logger => options_hash[:logger] || default system-wide Logger</tt>
18
- # A standard library Logger used for any messages. A default system-wide logger is used otherwise.
40
+ # <tt>:logger</tt>
41
+ # A Ruby standard library Logger used for any messages. A default system-wide logger is used otherwise.
19
42
  # The corresponding <tt>logger</tt> and <tt>logger=</tt> accessors are defined.
20
43
  #
21
- # <tt>:logger_stream => options_hash[:logger_stream]</tt>
44
+ # <tt>:logger_stream</tt>
22
45
  # An an alternative to defining the logger, you can define just the output stream where log output will be written.
23
46
  # If this option is specified, a new logger will be created for the instance with this output stream.
24
- # There is no corresponding accessors; use the corresponding methods on the <tt>logger</tt> object instead.
47
+ # There are no corresponding accessors; use the appropriate methods on the <tt>logger</tt> object instead.
25
48
  #
26
- # <tt>:severity => options_hash[:severity]</tt>
27
- # The logging severity level, one of the Logger::Severity values or the corresponding integer value.
49
+ # <tt>:severity</tt>
50
+ # The logging severity level, one of the Logger::Severity values or a corresponding integer value.
28
51
  # If this option is specified, a new logger will be created for the instance with this output stream.
29
- # There is no corresponding accessors; use the corresponding methods on the <tt>logger</tt> object instead.
52
+ # There are no corresponding accessors; use the corresponding methods on the <tt>logger</tt> object instead.
30
53
  #
31
54
  # <tt>:noop => options_hash[:noop] || false</tt>
32
55
  # If true, don't do "anything", the interpretation of which will vary with the type receiving the option.
33
- # For example, a type might go through some initialization, such as parsng its argument list, but
34
- # do nothing after that. Primarily useful for debugging.
56
+ # For example, a type might go through some initialization, such as parsng its options, but
57
+ # do nothing after that. Primarily useful for debugging and testing.
35
58
  # The value can be accessed through the <tt>noop</tt> and <tt>noop=</tt> accessors.
59
+ #
36
60
  module OptionsUtils
61
+ include SetUtils
62
+ include ArrayUtils
37
63
 
38
64
  def self.universal_options
39
65
  [:logger_stream, :logger, :severity, :noop]
40
66
  end
41
67
 
42
- def init_specification options, canonical_options, &optional_block
68
+ def self.universal_prepositions
69
+ [:for, :on, :in, :within]
70
+ end
71
+
72
+ attr_reader :specification
73
+
74
+ # Class #initialize methods call this method to process the input options.
75
+ # Pass an optional block to the method that takes no parameters if you want
76
+ # to do additional processing of the options before init_specification validates
77
+ # the options. The block will have access to the @specification hash built up by
78
+ # init_specification and to a new attribute @original_options, which will be a
79
+ # copy of the original options passed to init_specification (it will be either a
80
+ # hash or an array).
81
+ # Finally, if the block returns a value or an array of values, they will be
82
+ # treated as keys to ignore in the options when they are validated. This is a
83
+ # way of dynamically treating an option as valid that can't be known in advance.
84
+ # (See Aspect and Pointcut for examples of this feature in use.)
85
+ def init_specification options, canonical_options, additional_allowed_options = []
43
86
  @canonical_options = canonical_options
44
- @original_options = options.dup unless options.nil?
87
+ @additional_allowed_options = additional_allowed_options.map{|x| x.respond_to?(:intern) ? x.intern : x}
88
+ @original_options = options.nil? ? {} : options.dup
45
89
  @specification = {}
46
90
  options ||= {}
47
91
  options_hash = hashify options
@@ -51,39 +95,21 @@ module Aquarium
51
95
  ary << options_hash[o.intern] if options_hash[o.intern]
52
96
  ary
53
97
  end
54
- @specification[key.intern] = Set.new(make_array(all_related_options))
98
+ @specification[key.intern] = Set.new(all_related_options.flatten)
55
99
  end
56
-
57
- universal_options = {
58
- :logger_stream => options_hash[:logger_stream],
59
- :severity => options_hash[:severity],
60
- :noop => options_hash[:noop] || false
61
- }
62
-
63
- set_logger_if_logger_or_stream_specified universal_options, options_hash
64
- set_logger_severity_if_specified universal_options, options_hash
65
- set_logger_if_not_specified universal_options, options_hash
66
100
 
67
101
  OptionsUtils::universal_options.each do |uopt|
68
- @specification[uopt] = Set.new([universal_options[uopt]]) unless universal_options[uopt].nil?
102
+ @specification[uopt] = Set.new(make_array(options_hash[uopt])) unless options_hash[uopt].nil?
69
103
  end
70
- init_type_specific_specification @original_options, options_hash, &optional_block
71
- validate_options options_hash
72
- end
73
-
74
- [:logger, :noop].each do |name|
75
- module_eval(<<-EOF, __FILE__, __LINE__)
76
- def #{name}
77
- @specification[:#{name}].to_a.first
78
- end
79
- def #{name}= value
80
- @specification[:#{name}] = Set.new([value])
81
- end
82
- EOF
83
- end
84
-
85
- # Override for type-specific initialization
86
- def init_type_specific_specification original_options, options_hash, &optional_block
104
+ @specification[:noop] ||= Set.new([false])
105
+ set_logger_if_stream_specified
106
+ set_logger_severity_if_specified
107
+ set_default_logger_if_not_specified
108
+
109
+ ignorables = yield if block_given?
110
+ ignorables = [] if ignorables.nil?
111
+ ignorables = [ignorables] unless ignorables.kind_of? Array
112
+ validate_options(options_hash.reject {|k,v| ignorables.include?(k)})
87
113
  end
88
114
 
89
115
  def hashify options
@@ -104,33 +130,118 @@ module Aquarium
104
130
  raise Aquarium::Utils::InvalidOptions.new("Unknown options specified: #{unknowns.inspect}") if unknowns.size > 0
105
131
  end
106
132
 
107
- protected
108
-
109
- def set_logger_if_logger_or_stream_specified universal_options, options_hash
110
- if not options_hash[:logger].nil?
111
- universal_options[:logger] = options_hash[:logger]
112
- elsif not options_hash[:logger_stream].nil?
113
- universal_options[:logger] = Logger.new options_hash[:logger_stream]
114
- end
133
+ [:logger, :noop].each do |name|
134
+ module_eval(<<-EOF, __FILE__, __LINE__)
135
+ def #{name}
136
+ @specification[:#{name}].kind_of?(Set) ? @specification[:#{name}].to_a.first : @specification[:#{name}]
137
+ end
138
+ def #{name}= value
139
+ @specification[:#{name}] = make_set(make_array(value))
140
+ end
141
+ EOF
115
142
  end
116
143
 
117
- def set_logger_severity_if_specified universal_options, options_hash
118
- unless options_hash[:severity].nil?
119
- unless universal_options[:logger].nil?
120
- universal_options[:logger].level = options_hash[:severity]
121
- else
122
- universal_options[:logger] = Logger.new STDERR
123
- universal_options[:logger].level = options_hash[:severity]
144
+ module ClassMethods
145
+ def canonical_option_reader *canonical_option_key_list
146
+ return if canonical_option_key_list.nil? or canonical_option_key_list.empty?
147
+ keys = determine_options_for_accessors canonical_option_key_list
148
+ keys.each do |name|
149
+ define_method(name) do
150
+ @specification[name]
151
+ end
152
+ end
153
+ end
154
+ def canonical_option_writer *canonical_option_key_list
155
+ return if canonical_option_key_list.nil? or canonical_option_key_list.empty?
156
+ keys = determine_options_for_accessors canonical_option_key_list
157
+ keys.each do |name|
158
+ define_method("#{name}=") do |value|
159
+ @specification[name] = make_set(make_array(value))
160
+ end
161
+ end
162
+ end
163
+ def canonical_option_accessor *canonical_option_key_list
164
+ canonical_option_reader *canonical_option_key_list
165
+ canonical_option_writer *canonical_option_key_list
166
+ end
167
+
168
+ def canonical_options_given_methods canonical_options
169
+ keys = canonical_options.respond_to?(:keys) ? canonical_options.keys : canonical_options
170
+ (keys + OptionsUtils::universal_options).each do |name|
171
+ module_eval(<<-EOF, __FILE__, __LINE__)
172
+ def #{name}_given
173
+ @specification[:#{name}]
174
+ end
175
+
176
+ def #{name}_given?
177
+ not (#{name}_given.nil? or #{name}_given.empty?)
178
+ end
179
+ EOF
180
+ end
181
+ end
182
+
183
+ # Service method that adds a new canonical option and corresponding array with
184
+ # "exclude_" prepended to all values. The new options are added to the input hash.
185
+ def add_exclude_options_for option, options_hash
186
+ all_variants = options_hash[option].dup
187
+ options_hash["exclude_#{option}"] = all_variants.map {|x| "exclude_#{x}"}
188
+ end
189
+
190
+ # Service method that adds a new canonical option and corresponding array with
191
+ # "preposition" prefixes, e.g., "on_", "for_", etc. prepended to all values.
192
+ # The new options are added to the input hash.
193
+ def add_prepositional_option_variants_for option, options_hash
194
+ all_variants = options_hash[option].dup + [option]
195
+ OptionsUtils.universal_prepositions.each do |prefix|
196
+ all_variants.each do |variant|
197
+ options_hash[option] << "#{prefix}_#{variant}"
198
+ end
124
199
  end
125
200
  end
201
+
202
+ protected
203
+ def determine_options_for_accessors canonical_option_key_list
204
+ keys = canonical_option_key_list
205
+ if canonical_option_key_list.kind_of?(Array) and canonical_option_key_list.size == 1
206
+ keys = canonical_option_key_list[0]
207
+ end
208
+ if keys.respond_to? :keys
209
+ keys = keys.keys
210
+ end
211
+ keys
212
+ end
213
+ end
214
+
215
+ def self.append_features clazz
216
+ super
217
+ ClassMethods.send :append_features, (class << clazz; self; end)
218
+ end
219
+
220
+ protected
221
+
222
+ def all_allowed_option_symbols
223
+ @canonical_options.to_a.flatten.map {|o| o.intern} + @additional_allowed_options
224
+ end
225
+
226
+ # While it's tempting to use the #logger_stream_given?, etc. methods, they will only exist if the
227
+ # including class called canonical_options_given_methods!
228
+ def set_logger_if_stream_specified
229
+ return if @specification[:logger_stream].nil? or @specification[:logger_stream].empty?
230
+ self.logger = Logger.new @specification[:logger_stream].to_a.first
231
+ self.logger.level = DefaultLogger::DEFAULT_SEVERITY_LEVEL
126
232
  end
127
233
 
128
- def set_logger_if_not_specified universal_options, options_hash
129
- if universal_options[:logger].nil?
130
- universal_options[:logger] = DefaultLogger.logger
131
- end
234
+ def set_logger_severity_if_specified
235
+ return if @specification[:severity].nil? or @specification[:severity].empty?
236
+ if self.logger.nil?
237
+ self.logger = Logger.new STDERR
238
+ end
239
+ self.logger.level = @specification[:severity].to_a.first
132
240
  end
133
241
 
242
+ def set_default_logger_if_not_specified
243
+ self.logger ||= DefaultLogger.logger
244
+ end
134
245
  end
135
246
  end
136
247
  end