Skip to content

Commit b6720a9

Browse files
committed
Add PathPrefixNormalizer
1 parent 55c7bbe commit b6720a9

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

lib/deprecation_toolkit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module DeprecationToolkit
88
autoload :DeprecationSubscriber, "deprecation_toolkit/deprecation_subscriber"
99
autoload :Configuration, "deprecation_toolkit/configuration"
1010
autoload :Collector, "deprecation_toolkit/collector"
11+
autoload :PathPrefixNormalizer, "deprecation_toolkit/path_prefix_normalizer"
1112
autoload :ReadWriteHelper, "deprecation_toolkit/read_write_helper"
1213
autoload :TestTriggerer, "deprecation_toolkit/test_triggerer"
1314

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require "pathname"
4+
5+
module DeprecationToolkit
6+
class PathPrefixNormalizer
7+
attr_reader :path_prefixes, :replacement
8+
9+
def initialize(*path_prefixes, replacement: "")
10+
@path_prefixes = path_prefixes.compact.map do |path_prefix|
11+
raise ArgumentError, "path prefixes must be absolute: #{path_prefix}" unless Pathname.new(path_prefix).absolute?
12+
13+
ending_in_separator(path_prefix)
14+
end.sort_by { |path| -path.length }
15+
@replacement = replacement.empty? ? replacement : ending_in_separator(replacement)
16+
end
17+
18+
def call(message)
19+
message.gsub(pattern, replacement)
20+
end
21+
22+
def to_s
23+
"s#{pattern}#{replacement}"
24+
end
25+
26+
def pattern
27+
# Naively anchor to the start of a path.
28+
# The last character of each segment of a path is likely to match /\w/.
29+
# Therefore, if the preceeding character does not match /w/, we're probably not in in the middle of a path.
30+
# e.g. In a containerized environment, we may be given `/app` as a path prefix (perhaps from Rails.root).
31+
# Given the following path: `/app/components/foo/app/models/bar.rb`,
32+
# we should replace the prefix, producing: `components/foo/app/models/bar.rb`,
33+
# without corrupting other occurences: `components/foomodels/bar.rb`
34+
@pattern ||= /(?<!\w)#{Regexp.union(path_prefixes)}/
35+
end
36+
37+
def ending_in_separator(path)
38+
File.join(path, "")
39+
end
40+
end
41+
end
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
module DeprecationToolkit
6+
class PathPrefixNormalizerTest < ActiveSupport::TestCase
7+
test "can strip a single path prefix" do
8+
prefix = File.join("", "a")
9+
normalizer = PathPrefixNormalizer.new(prefix)
10+
11+
full_path = File.join("", "a", "b", "c")
12+
normalized_path = File.join("b", "c")
13+
14+
assert_equal normalized_path, normalizer.call(full_path)
15+
end
16+
17+
test "can strip multiple path prefixes" do
18+
prefixes = [File.join("", "a"), File.join("", "d")]
19+
normalizer = PathPrefixNormalizer.new(*prefixes)
20+
21+
full_path = File.join("", "a", "b", "c")
22+
normalized_path = File.join("b", "c")
23+
24+
assert_equal normalized_path, normalizer.call(full_path)
25+
26+
full_path = File.join("", "d", "e", "f")
27+
normalized_path = File.join("e", "f")
28+
29+
assert_equal normalized_path, normalizer.call(full_path)
30+
end
31+
32+
test "multiple path prefixes are replaced individually, not in succession" do
33+
prefixes = [File.join("", "a"), File.join("", "d")]
34+
normalizer = PathPrefixNormalizer.new(*prefixes)
35+
36+
full_path = File.join("", "a", "b", "c", "d", "e", "f")
37+
normalized_path = File.join("b", "c", "d", "e", "f")
38+
39+
assert_equal normalized_path, normalizer.call(full_path)
40+
end
41+
42+
test "replacement is customizable" do
43+
prefix = File.join("", "a")
44+
normalizer = PathPrefixNormalizer.new(prefix, replacement: File.join("eh", ""))
45+
46+
full_path = File.join("", "a", "b", "c")
47+
normalized_path = File.join("eh", "b", "c")
48+
49+
assert_equal normalized_path, normalizer.call(full_path)
50+
end
51+
52+
test "path separator is appended to replacement if not present" do
53+
prefix = File.join("", "a")
54+
normalizer = PathPrefixNormalizer.new(prefix, replacement: "eh")
55+
56+
full_path = File.join("", "a", "b", "c")
57+
normalized_path = File.join("eh", "b", "c")
58+
59+
assert_equal normalized_path, normalizer.call(full_path)
60+
end
61+
62+
test "path separator is not appended to empty replacement" do
63+
prefix = File.join("", "a")
64+
normalizer = PathPrefixNormalizer.new(prefix, replacement: "")
65+
66+
full_path = File.join("", "a", "b", "c")
67+
normalized_path = File.join("b", "c")
68+
69+
assert_equal normalized_path, normalizer.call(full_path)
70+
end
71+
72+
test "given prefix is extended to include trailing separator if not present" do
73+
prefix = File.join("", "a")
74+
normalizer = PathPrefixNormalizer.new(prefix)
75+
76+
full_path = File.join("", "a", "b", "c")
77+
normalized_path = File.join("b", "c")
78+
79+
assert_equal normalized_path, normalizer.call(full_path)
80+
end
81+
82+
test "if given prefix already includes trailing separator, it is not duplicated" do
83+
prefix = File.join("", "a", "")
84+
normalizer = PathPrefixNormalizer.new(prefix)
85+
86+
full_path = File.join("", "a", "b", "c")
87+
normalized_path = File.join("b", "c")
88+
89+
assert_equal normalized_path, normalizer.call(full_path)
90+
end
91+
92+
test "only path prefixes are replaced, not infixes or suffixes" do
93+
prefixes = [File.join("", "a"), File.join("", "b"), File.join("", "c")]
94+
normalizer = PathPrefixNormalizer.new(*prefixes)
95+
96+
full_path = File.join("", "a", "b", "c", "c", "b", "a")
97+
normalized_path = File.join("b", "c", "c", "b", "a")
98+
99+
assert_equal normalized_path, normalizer.call(full_path)
100+
end
101+
102+
test "paths are normalized even if in the middle of the given string" do
103+
prefix = File.join("", "a")
104+
normalizer = PathPrefixNormalizer.new(prefix)
105+
106+
original_message = "The code in #{File.join("", "a", "b", "c")} on line 1 is deprecated!"
107+
normalized_message = "The code in #{File.join("b", "c")} on line 1 is deprecated!"
108+
109+
assert_equal normalized_message, normalizer.call(original_message)
110+
end
111+
112+
test "relative paths are not accepted" do
113+
prefix = File.join("a", "", "b")
114+
115+
error = assert_raises(ArgumentError) do
116+
PathPrefixNormalizer.new(prefix)
117+
end
118+
119+
assert_equal "path prefixes must be absolute: #{prefix}", error.message
120+
end
121+
122+
test "multiple paths in given string are normalized" do
123+
prefix = File.join("", "a")
124+
normalizer = PathPrefixNormalizer.new(prefix)
125+
126+
original_message = "There are deprecations in #{File.join("", "a", "b")} and #{File.join("", "a", "c")}"
127+
normalized_message = "There are deprecations in b and c"
128+
129+
assert_equal normalized_message, normalizer.call(original_message)
130+
end
131+
132+
test "matches are as long as possible" do
133+
prefixes = [File.join("", "a"), File.join("", "a", "b")]
134+
normalizer = PathPrefixNormalizer.new(*prefixes)
135+
136+
full_path = File.join("", "a", "b", "c")
137+
normalized_path = "c"
138+
139+
assert_equal normalized_path, normalizer.call(full_path)
140+
end
141+
142+
test "nil prefixes are ignored" do
143+
prefixes = [File.join("", "a"), nil]
144+
normalizer = PathPrefixNormalizer.new(*prefixes)
145+
146+
full_path = File.join("", "a", "b")
147+
normalized_path = "b"
148+
149+
assert_equal normalized_path, normalizer.call(full_path)
150+
end
151+
end
152+
end

0 commit comments

Comments
 (0)