deprecatable 1.0.0

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.
@@ -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