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