deprecatable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,210 @@
1
+ require 'deprecatable/util'
2
+ require 'deprecatable/call_site'
3
+ module Deprecatable
4
+ # DeprecatedMethod holds all the information about a method that was marked
5
+ # as 'deprecated' through the Deprecatable Module. The Class, method name,
6
+ # and the file and line number of the deprecated method are stored in
7
+ # DeprecatedMethod.
8
+ #
9
+ # It also is the location in which the calls to the deprected method are
10
+ # stored. Each call to the deprecated method ends up with a call to
11
+ # 'log_invocation'. The 'log_invocation' method records the CallSite of
12
+ # where the deprecated method was called, and the number of times that the
13
+ # deprecated method was called from that CallSite.
14
+ #
15
+ # In general, the first time a deprecated method is called from a particular
16
+ # CallSite, the Alerter is invoked to report the invocation. All subsequent
17
+ # calls to the deprecated method do not alert, although the invocation count
18
+ # is increased. This behavior may be altered through the Deprecatable::Options
19
+ # instance at Deprecatable.options.
20
+ #
21
+ class DeprecatedMethod
22
+ include Util
23
+
24
+ # Public: The Ruby class that has the method being deprecated.
25
+ #
26
+ # Returns the Class whos method is being deprecated.
27
+ attr_reader :klass
28
+
29
+ # Public: The method in the klass being deprecated.
30
+ #
31
+ # Returns the Symbol of the method being deprecated.
32
+ attr_reader :method
33
+
34
+ # Public: The filesystem path of the file where the deprecation took place.
35
+ #
36
+ # Returns the String path to the file.
37
+ attr_reader :file
38
+
39
+ # Public: The line number in the file where hte deprecation took place. This
40
+ # is a 1 indexed value.
41
+ #
42
+ # Returns the Integer line number.
43
+ attr_reader :line_number
44
+
45
+ # Public: The additional message to output with the alerts and reports.
46
+ #
47
+ # Returns the String message.
48
+ attr_reader :message
49
+
50
+ # Public: The date on which the deprecate method will be removed.
51
+ #
52
+ # Returns the removal date.
53
+ attr_reader :removal_date
54
+
55
+ # Public: The version of the software which will no longer have the
56
+ # deprecated method.
57
+ #
58
+ # Returns the version number.
59
+ attr_reader :removal_version
60
+
61
+ # The aliased name of the method being deprecated. This is what is called by
62
+ # the wrapper to invoke the original deprecated method.
63
+ #
64
+ # Returns the String method name.
65
+ attr_reader :deprecated_method_name
66
+
67
+ # Create a new DeprecatedMethod.
68
+ #
69
+ # klass - The Class containing the deprecated method.
70
+ # method - The Symbol method name of the deprecated method.
71
+ # file - The String filesystem path where the deprecation took place.
72
+ # line_number - The Integer line in the file where hte deprecation took
73
+ # place.
74
+ # options - The Hash optional parameters (default: {})
75
+ # :message - A String to output along with the rest of
76
+ # the notifcations about the deprecated
77
+ # method.
78
+ # :removal_date - The date on which the deprecated method
79
+ # will be removed.
80
+ # :removal_version - The version on which the deprecated
81
+ # method will be removed.
82
+ def initialize( klass, method, file, line_number, options = {} )
83
+ @klass = klass
84
+ @method = method
85
+ @file = File.expand_path( file )
86
+ @line_number = Float(line_number).to_i
87
+ @deprecated_method_name = "_deprecated_#{method}"
88
+ @invocations = 0
89
+ @call_sites = Hash.new
90
+ @message = options[:message]
91
+ @removal_date = options[:removal_date]
92
+ @removal_version = options[:removal_version]
93
+ @to_s = nil
94
+ insert_shim( self )
95
+ end
96
+
97
+ # Format the DeprecatedMethod as a String.
98
+ #
99
+ # Returns the DeprecatedMethod as a String.
100
+ def to_s
101
+ unless @to_s then
102
+ target = @klass.kind_of?( Class ) ? "#{@klass.name}#" : "#{@klass.name}."
103
+ @to_s = "#{target}#{@method} defined at #{@file}:#{@line_number}"
104
+ end
105
+ return @to_s
106
+ end
107
+
108
+ # Log the invocation of the DeprecatedMethod at the given CallSite. Alert
109
+ # the media.
110
+ #
111
+ # file - The String path to the file in which the DeprecatedMethod
112
+ # was invoked.
113
+ # line_number - The Integer line_number in the file on which the
114
+ # DeprecatedMethod was invoked.
115
+ #
116
+ # Returns nothing.
117
+ def log_invocation( file, line_number )
118
+ call_site = call_site_for( file, line_number )
119
+ call_site.increment_invocation_count
120
+ alert( call_site )
121
+ end
122
+
123
+ # Tell the Deprecatable.alerter to alert if the number of invocations at the
124
+ # CallSite is less than or equal to the alert frequency.
125
+ #
126
+ # call_site - The CallSite instance representing where this DeprecatedMethod
127
+ # was invoked.
128
+ #
129
+ # Returns nothing.
130
+ def alert( call_site )
131
+ if call_site.invocation_count <= ::Deprecatable.options.alert_frequency then
132
+ ::Deprecatable.alerter.alert( self, call_site )
133
+ end
134
+ end
135
+
136
+ # Gets the lines of all the CallSites where this DeprecatedMethod was
137
+ # invoked.
138
+ #
139
+ # Returns an Array of CallSite instances.
140
+ def call_sites
141
+ @call_sites.values
142
+ end
143
+
144
+ # Gets the sum total of all the invocations of this DeprecatedMethod.
145
+ #
146
+ # Returns the Integer count of invocations.
147
+ def invocation_count
148
+ sum = 0
149
+ @call_sites.values.each { |cs| sum += cs.invocation_count }
150
+ return sum
151
+ end
152
+
153
+ # Gets the unique count of CallSites representing the unique number of
154
+ # locations where this DeprecatedMethod was invoked.
155
+ #
156
+ # Returnts the Integer unique count of CallSites.
157
+ def call_site_count
158
+ @call_sites.size
159
+ end
160
+
161
+ ###################################################################
162
+ private
163
+ ###################################################################
164
+
165
+ # Find the CallSite representing the given file and line_number. It creates
166
+ # a new CallSite instance if necessary.
167
+ #
168
+ # file - The String path to the file in which the DeprecatedMethod
169
+ # was invoked.
170
+ # line_number - The Integer line_number in the file on which the
171
+ # DeprecatedMethod was invoked.
172
+ #
173
+ # Returns the CallSite representing the give file and line number.
174
+ def call_site_for( file, line_number )
175
+ cs = @call_sites[CallSite.gen_key( file, line_number)]
176
+ if cs.nil? then
177
+ cs = CallSite.new( file, line_number, ::Deprecatable.options.caller_context_padding )
178
+ @call_sites[cs.key] = cs
179
+ end
180
+ return cs
181
+ end
182
+
183
+ # Create the wrapper method that replaces the deprecated method. This is
184
+ # where the magic happens.
185
+ #
186
+ # This does the following:
187
+ #
188
+ # 1) aliases the deprecated method to a new method name
189
+ # 2) creates a new method with the original name that
190
+ # 1) logs the invocation of the deprecated method
191
+ # 2) calls the original deprecated method.
192
+ #
193
+ # Returns nothing.
194
+ def insert_shim( dm )
195
+ if not klass.method_defined?( dm.deprecated_method_name ) then
196
+
197
+ klass.module_eval do
198
+
199
+ alias_method dm.deprecated_method_name, dm.method
200
+
201
+ define_method( dm.method ) do |*args, &block|
202
+ dm.log_invocation( *Util.location_of_caller )
203
+ send( dm.deprecated_method_name, *args, &block )
204
+ end
205
+
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,138 @@
1
+ module Deprecatable
2
+ # A Container for the options of Deprecatable.
3
+ #
4
+ # The available options are:
5
+ #
6
+ # caller_context_padding - The number of lines before and after the call
7
+ # site of the deprecated method to record
8
+ # alert_frequency - The maximum number of times to alert for a given
9
+ # call to the deprecated method
10
+ # at_exit_report - Whether or not a deprecation report is issued
11
+ # when the program exits
12
+ #
13
+ # These options may also be overridden with environment varaibles
14
+ #
15
+ # DEPRECATABLE_CALLER_CONTEXT_PADDING
16
+ # DEPRECATABLE_ALERT_FREQUENCY
17
+ # DEPRECATABLE_AT_EXIT_REPORT
18
+ #
19
+ class Options
20
+ # Create a new instance of Options. All of the default values for the
21
+ # options are set.
22
+ #
23
+ # caller_context_padding - 2
24
+ # has_at_exit_report - true
25
+ # alert_frequency - 1
26
+ def initialize
27
+ reset
28
+ end
29
+
30
+ # Reset the options to their default values.
31
+ #
32
+ # Returns nothing.
33
+ def reset
34
+ @caller_context_padding = 2
35
+ @has_at_exit_report = true
36
+ @alert_frequency = 1
37
+ end
38
+
39
+ # Public: Set the number of lines of context surrounding the call site of
40
+ # the deprecated method to display in the alerts and reports. (default: 2)
41
+ #
42
+ # count - The number of lines before and after the callsite to report.
43
+ # This must be a positive number.
44
+ #
45
+ # Returns the count.
46
+ def caller_context_padding=( count )
47
+ raise ArgumentError, "caller_content_padding must have a count > 0" unless count > 0
48
+ @caller_context_padding = count
49
+ end
50
+
51
+ # Public: Get the number of lines of context padding.
52
+ #
53
+ # This may be overridden with the environment variable
54
+ # DEPRECATABLE_CALLER_CONTEXT_PADDING.
55
+ #
56
+ # Returns the Integer number of context padding lines.
57
+ def caller_context_padding
58
+ p = ENV['DEPRECATABLE_CALLER_CONTEXT_PADDING']
59
+ if p then
60
+ p = Float(p).to_i
61
+ raise ArgumentError, "DEPRECATABLE_CALLER_CONTEXT_APDDING must have a value > 0, it is currently #{p}" unless p > 0
62
+ return p
63
+ end
64
+ return @caller_context_padding
65
+ end
66
+
67
+ # Public: Set the maximum number of times an alert for a unqiue CallSite
68
+ # of a DeprecatedMethod will be emitted. (default: :once)
69
+ #
70
+ # That is, when a deprecated method is called from a particular CallSite,
71
+ # normally an 'alert' is sent. This setting controls the maximum number of
72
+ # times that the 'alert' for a particular CallSite is emitted.
73
+ #
74
+ # freq - The alert frequency. This may be set to any number, or to one of
75
+ # the special token values:
76
+ #
77
+ # :never - Never send any alerts
78
+ # :once - Send an alert for a given CallSite only once.
79
+ # :always - Send an alert for every invocation of the
80
+ # DeprecatedMethod.
81
+ #
82
+ # Returns the alert_frequency.
83
+ def alert_frequency=( freq )
84
+ @alert_frequency = frequency_of( freq )
85
+ end
86
+
87
+ # Public: Get the current value of the alert_frequency.
88
+ #
89
+ # This may be overridden with the environment variable
90
+ # DEPRECATABLE_ALERT_FREQUENCY.
91
+ #
92
+ # Returns the Integer value representing the alert_frequency.
93
+ def alert_frequency
94
+ p = ENV['DEPRECATABLE_ALERT_FREQUENCY']
95
+ return frequency_of(p) if p
96
+ return @alert_frequency
97
+ end
98
+
99
+ # Public: Set whether or not the final at_exit_report should be emitted
100
+ #
101
+ # bool - true or false, shall the exit report be emitted.
102
+ #
103
+ # Returns the value set.
104
+ attr_writer :has_at_exit_report
105
+
106
+ # Public: Say whether or not the final at exit report shall be emitted.
107
+ #
108
+ # This may be overridden by the environment variable
109
+ # DEPRECATABLE_HAS_AT_EXIT_REPORT. Setting the environment variable to
110
+ # 'true' will override the existing setting.
111
+ #
112
+ # Returns the boolean of whether or not the exti report should be done.
113
+ def has_at_exit_report?
114
+ return true if ENV['DEPRECATABLE_HAS_AT_EXIT_REPORT'] == "true"
115
+ return @has_at_exit_report
116
+ end
117
+
118
+ ##################################################################
119
+ private
120
+ ##################################################################
121
+
122
+ # Convert the given frequency Symbol/String into its Numeric representation.
123
+ #
124
+ # Return the Numeric value of the input frequency.
125
+ def frequency_of( frequency )
126
+ case frequency.to_s
127
+ when 'always'
128
+ (1.0/0.0)
129
+ when 'once'
130
+ 1
131
+ when 'never'
132
+ 0
133
+ else
134
+ Float(frequency).to_i
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,55 @@
1
+ module Deprecatable
2
+ # The Registry is a container of unique DeprecatedMethod instances.
3
+ # Normally there is only one in existence and it is accessed via
4
+ # 'Deprecatable.registry'
5
+ class Registry
6
+ # Initialize the Registry, which amounts to creating a new Hash.
7
+ #
8
+ # Returns nothing.
9
+ def initialize
10
+ @registry = Hash.new
11
+ end
12
+
13
+ # Public: Return the number of instances of DeprecatedMethod there are in
14
+ # the Registry.
15
+ #
16
+ # Returns an Integer of the size of the Regsitry.
17
+ def size()
18
+ @registry.size
19
+ end
20
+
21
+ # Public: Iterate over all items in the Registry
22
+ #
23
+ # Yields each DeprecatedMethod in the Registry
24
+ # Returns nothing.
25
+ def each
26
+ items.each do |i|
27
+ yield i
28
+ end
29
+ end
30
+
31
+ # Public: Remove all items from the Registry.
32
+ #
33
+ # Returns nothing.
34
+ def clear
35
+ @registry.clear
36
+ end
37
+
38
+ # Public: Register a method to be deprecated.
39
+ #
40
+ # method - An instance of DeprecatedMethod
41
+ #
42
+ # Returns the instance that was passed in.
43
+ def register( dm )
44
+ @registry[dm] = true
45
+ return dm
46
+ end
47
+
48
+ # Public: Return all the DeprecatedMethod instances in the registry.
49
+ #
50
+ # Returns an Array of DeprecatedMethod instances.
51
+ def items
52
+ @registry.keys
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ module Deprecatable
2
+ # Common utility functions used by all the Deprecatable modules and classes
3
+ module Util
4
+ # Find the caller of the method that called the method where
5
+ # location_of_call was invoked.
6
+ #
7
+ # Example:
8
+ #
9
+ # def foo
10
+ # bar() # <--- this file and line number is returned
11
+ # end
12
+ #
13
+ # def bar
14
+ # Deprecatable::Util.location_of_caller
15
+ # end
16
+ #
17
+ # Return the [ file, line number] tuple from which bar() was invoked.
18
+ def self.location_of_caller
19
+ call_line = caller[1]
20
+ file, line, _ = call_line.split(':')
21
+ file = File.expand_path( file )
22
+ line = Float(line).to_i
23
+ return file, line
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ gem "minitest" # required for ruby 1.9 and 'rake rcov'
2
+ require 'minitest/autorun'
3
+ require 'deprecatable'
4
+
5
+ ::Deprecatable.options.has_at_exit_report = false
6
+
7
+ module MiniTest
8
+ class Unit
9
+ class TestCase
10
+ def assert_array_equal( expected, actual, msg = nil )
11
+ assert_equal( expected.size, actual.size, msg )
12
+ expected.each_with_index do |i, idx|
13
+ assert_equal( i, actual[idx], msg )
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end