aquarium 0.4.0 → 0.4.1

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