deprecatable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +9 -0
- data/.gemtest +1 -0
- data/HISTORY.rdoc +4 -0
- data/Manifest.txt +26 -0
- data/README.rdoc +175 -0
- data/Rakefile +33 -0
- data/examples/alert_frequency.rb +101 -0
- data/examples/at_exit.rb +68 -0
- data/examples/caller_context_padding.rb +97 -0
- data/lib/deprecatable.rb +106 -0
- data/lib/deprecatable/alerter.rb +162 -0
- data/lib/deprecatable/call_site.rb +91 -0
- data/lib/deprecatable/call_site_context.rb +112 -0
- data/lib/deprecatable/deprecated_method.rb +210 -0
- data/lib/deprecatable/options.rb +138 -0
- data/lib/deprecatable/registry.rb +55 -0
- data/lib/deprecatable/util.rb +26 -0
- data/test/helpers.rb +18 -0
- data/test/test_deprecatable.rb +149 -0
- data/test/test_deprecatable_alerter.rb +41 -0
- data/test/test_deprecatable_call_site.rb +28 -0
- data/test/test_deprecatable_call_site_context.rb +57 -0
- data/test/test_deprecatable_deprecated_method.rb +61 -0
- data/test/test_deprecatable_options.rb +83 -0
- data/test/test_deprecatable_registry.rb +32 -0
- data/test/test_deprecatable_util.rb +13 -0
- metadata +146 -0
@@ -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
|
data/test/helpers.rb
ADDED
@@ -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
|