gem_bench 2.0.2 → 2.0.4
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +47 -23
- data/CODE_OF_CONDUCT.md +74 -25
- data/CONTRIBUTING.md +1 -3
- data/README.md +49 -17
- data/lib/gem_bench/gemfile_line_tokenizer.rb +132 -40
- data/lib/gem_bench/jersey.rb +100 -66
- data/lib/gem_bench/player.rb +2 -0
- data/lib/gem_bench/scout.rb +6 -4
- data/lib/gem_bench/strict_version_gem.rb +3 -1
- data/lib/gem_bench/strict_version_requirement.rb +2 -1
- data/lib/gem_bench/team.rb +16 -6
- data/lib/gem_bench/version.rb +2 -1
- data/lib/gem_bench.rb +34 -21
- data.tar.gz.sig +0 -0
- metadata +52 -60
- metadata.gz.sig +0 -0
@@ -1,14 +1,51 @@
|
|
1
1
|
module GemBench
|
2
|
+
# Line by line parser of a Gemfile
|
3
|
+
# Uses regular expressions, which, I know, GROSS, but also meh
|
4
|
+
# You aren't using this as a runtime gem in your app are you?
|
5
|
+
# Let me know how you use it!
|
2
6
|
class GemfileLineTokenizer
|
3
7
|
GEM_REGEX = /\A\s*gem\s+([^#]*).*\Z/.freeze # run against gem lines like: "gem 'aftership', # Ruby SDK of AfterShip API."
|
4
|
-
|
5
|
-
|
8
|
+
# HEREDOC support? (?<op_heredoc><<[~-]?[A-Z0-9_]+\.?[a-z0-9_]+)
|
9
|
+
OP_QUO_REG_PROC = lambda { |idx = nil| /((?<op_quo#{idx}>['"]{1})|(?<op_pct_q#{idx}>%[Qq]?[\(\[\{]{1})|(?<op_heredoc><<[~-]?[A-Z0-9_]+\.?[a-z0-9_]+))/x }
|
10
|
+
# No close for the heredoc, as it will be on a different line...
|
11
|
+
CL_QUO_REG_PROC = lambda { |idx = nil| /((?<cl_quo#{idx}>['"])|(?<cl_pct_q#{idx}>[\)\]\}]{1}))/x }
|
12
|
+
GEM_NAME_REGEX = /\A\s*gem\s+#{OP_QUO_REG_PROC.call.source}(?<name>[^'")]*)#{CL_QUO_REG_PROC.call.source}?.*\Z/.freeze # run against gem lines like: "gem 'aftership', # Ruby SDK of AfterShip API."
|
13
|
+
VERSION_CONSTRAINT = /#{OP_QUO_REG_PROC.call.source}(?<version>[^'")]*)#{CL_QUO_REG_PROC.call.source}/.freeze
|
6
14
|
GEMFILE_HASH_CONFIG_KEY_REGEX_PROC = lambda { |key|
|
7
|
-
|
15
|
+
/
|
16
|
+
\A\s*[^#]*
|
17
|
+
(
|
18
|
+
# when key is "branch" will find: `branch: "main"`
|
19
|
+
(?<key1>#{key}:\s*)
|
20
|
+
#{OP_QUO_REG_PROC.call("k1").source}(?<value1>[^'")]*)?#{CL_QUO_REG_PROC.call("k1").source}
|
21
|
+
)
|
22
|
+
|
|
23
|
+
(
|
24
|
+
# when key is "branch" will find: `"branch" => "main"`
|
25
|
+
(?<key2>#{OP_QUO_REG_PROC.call("k2a").source}#{key}#{CL_QUO_REG_PROC.call("k2a").source}\s*=>\s*)
|
26
|
+
#{OP_QUO_REG_PROC.call("k2b").source}(?<value2>[^'")]*)?#{CL_QUO_REG_PROC.call("k2b").source}
|
27
|
+
)
|
28
|
+
|
|
29
|
+
(
|
30
|
+
# when key is "branch" will find: `:branch => "main"`
|
31
|
+
(?<key3>:#{key}\s*=>\s*)
|
32
|
+
#{OP_QUO_REG_PROC.call("k3").source}(?<value3>[^'")]*)?#{CL_QUO_REG_PROC.call("k3").source}
|
33
|
+
)
|
34
|
+
|
|
35
|
+
(
|
36
|
+
# when key is "branch" will find: `"branch": "main"`
|
37
|
+
(?<key4>#{OP_QUO_REG_PROC.call("k4a").source}#{key}#{CL_QUO_REG_PROC.call("k4a").source}:\s*)
|
38
|
+
#{OP_QUO_REG_PROC.call("k4b").source}(?<value4>[^'")]*)?#{CL_QUO_REG_PROC.call("k4b").source}
|
39
|
+
)
|
40
|
+
/x
|
8
41
|
}
|
9
42
|
VERSION_PATH = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("path").freeze
|
10
43
|
VERSION_GIT = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("git").freeze
|
11
44
|
VERSION_GITHUB = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("github").freeze
|
45
|
+
VERSION_GITLAB = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("gitlab").freeze
|
46
|
+
VERSION_BITBUCKET = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("bitbucket").freeze
|
47
|
+
VERSION_CODEBERG = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("codeberg").freeze
|
48
|
+
VERSION_SRCHUT = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("srchut").freeze
|
12
49
|
VERSION_GIT_REF = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("ref").freeze
|
13
50
|
VERSION_GIT_TAG = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("tag").freeze
|
14
51
|
VERSION_GIT_BRANCH = GEMFILE_HASH_CONFIG_KEY_REGEX_PROC.call("branch").freeze
|
@@ -17,13 +54,19 @@ module GemBench
|
|
17
54
|
git_ref
|
18
55
|
git_tag
|
19
56
|
]
|
20
|
-
|
57
|
+
STR_SYNTAX_TYPES = {
|
58
|
+
quoted: true,
|
59
|
+
pct_q: true,
|
60
|
+
# We could try to support HEREDOC via parsing the lines following in all_lines, but... ugh.
|
61
|
+
heredoc: false,
|
62
|
+
unknown: false,
|
63
|
+
}.freeze
|
21
64
|
attr_reader :line
|
22
65
|
attr_reader :relevant_lines, :is_gem, :all_lines, :index, :tokens, :version_type, :name, :parse_success, :valid
|
23
66
|
# version will be a string if it is a normal constraint like '~> 1.2.3'
|
24
67
|
# version will be a hash if it is an alternative constraint like:
|
25
68
|
# git: "blah/blah", ref: "shasha"
|
26
|
-
attr_reader :version
|
69
|
+
attr_reader :version, :str_syntax_type
|
27
70
|
|
28
71
|
def initialize(all_lines, line, index)
|
29
72
|
@line = line.strip
|
@@ -31,9 +74,9 @@ module GemBench
|
|
31
74
|
if is_gem
|
32
75
|
@all_lines = all_lines
|
33
76
|
@index = index
|
34
|
-
@tokens = self.line.split(",")
|
77
|
+
@tokens = self.line.split(",").map(&:strip)
|
35
78
|
determine_name
|
36
|
-
if name
|
79
|
+
if name && STR_SYNTAX_TYPES[str_syntax_type]
|
37
80
|
determine_relevant_lines
|
38
81
|
determine_version
|
39
82
|
@parse_success = true
|
@@ -49,35 +92,61 @@ module GemBench
|
|
49
92
|
private
|
50
93
|
|
51
94
|
# not a gem line. noop.
|
95
|
+
#
|
96
|
+
# @return void
|
52
97
|
def noop
|
53
98
|
@parse_success = false
|
54
99
|
@valid = false
|
100
|
+
|
101
|
+
nil
|
55
102
|
end
|
56
103
|
|
104
|
+
# @return void
|
57
105
|
def determine_name
|
58
106
|
# uses @tokens[0] because the gem name must be before the first comma
|
59
107
|
match_data = @tokens[0].match(GEM_NAME_REGEX)
|
108
|
+
@str_syntax_type =
|
109
|
+
if match_data[:op_quo] && match_data[:cl_quo]
|
110
|
+
:quoted
|
111
|
+
elsif match_data[:op_pct_q] && match_data[:cl_pct_q]
|
112
|
+
:pct_q
|
113
|
+
# Not handling heredoc, aside from not exploding, as it isn't a reasonable use case.
|
114
|
+
# elsif match_data[:op_heredoc]
|
115
|
+
# :heredoc
|
116
|
+
else
|
117
|
+
:unknown
|
118
|
+
end
|
60
119
|
@name = match_data[:name]
|
120
|
+
|
121
|
+
nil
|
61
122
|
end
|
62
123
|
|
124
|
+
# @return void
|
63
125
|
def determine_relevant_lines
|
64
126
|
@relevant_lines = [line, *following_non_gem_lines].compact
|
127
|
+
|
128
|
+
nil
|
65
129
|
end
|
66
130
|
|
131
|
+
# @return void
|
67
132
|
def determine_version
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
)
|
133
|
+
@version = {}
|
134
|
+
return if version_path
|
135
|
+
|
136
|
+
(
|
137
|
+
(version_git || version_provider) && (
|
138
|
+
check_for_version_of_type_git_tag ||
|
139
|
+
check_for_version_of_type_git_branch
|
140
|
+
)
|
141
|
+
) ||
|
76
142
|
# Needs to be the last check because it can only check for a quoted string,
|
77
143
|
# and quoted strings are part of the other types, so they have to be checked first with higher specificity
|
78
144
|
check_for_version_of_type_constraint
|
145
|
+
|
146
|
+
nil
|
79
147
|
end
|
80
148
|
|
149
|
+
# @return [true, false]
|
81
150
|
def check_for_version_of_type_constraint
|
82
151
|
# index 1 of the comma-split tokens will usually be the version constraint, if there is one
|
83
152
|
possible_constraint = @tokens[1]
|
@@ -85,7 +154,7 @@ module GemBench
|
|
85
154
|
|
86
155
|
match_data = possible_constraint.strip.match(VERSION_CONSTRAINT)
|
87
156
|
# the version constraint is in a regex capture group
|
88
|
-
if match_data && (@version = match_data[
|
157
|
+
if match_data && (@version = match_data[:version].strip)
|
89
158
|
@version_type = :constraint
|
90
159
|
true
|
91
160
|
else
|
@@ -93,8 +162,8 @@ module GemBench
|
|
93
162
|
end
|
94
163
|
end
|
95
164
|
|
165
|
+
# @return [true, false]
|
96
166
|
def version_path
|
97
|
-
@version = {}
|
98
167
|
line = relevant_lines.detect { |next_line| next_line.match(VERSION_PATH) }
|
99
168
|
return false unless line
|
100
169
|
|
@@ -105,8 +174,8 @@ module GemBench
|
|
105
174
|
)
|
106
175
|
end
|
107
176
|
|
177
|
+
# @return [true, false]
|
108
178
|
def version_git
|
109
|
-
@version = {}
|
110
179
|
line = relevant_lines.detect { |next_line| next_line.match(VERSION_GIT) }
|
111
180
|
return false unless line
|
112
181
|
|
@@ -117,21 +186,40 @@ module GemBench
|
|
117
186
|
)
|
118
187
|
end
|
119
188
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
189
|
+
# @return [true, false]
|
190
|
+
def version_provider
|
191
|
+
matcher = nil
|
192
|
+
line = relevant_lines.detect do |next_line|
|
193
|
+
matcher =
|
194
|
+
case next_line
|
195
|
+
when VERSION_GITHUB
|
196
|
+
VERSION_GITHUB
|
197
|
+
when VERSION_GITLAB
|
198
|
+
VERSION_GITLAB
|
199
|
+
when VERSION_BITBUCKET
|
200
|
+
VERSION_BITBUCKET
|
201
|
+
when VERSION_CODEBERG
|
202
|
+
VERSION_CODEBERG
|
203
|
+
when VERSION_SRCHUT
|
204
|
+
VERSION_SRCHUT
|
205
|
+
end
|
206
|
+
end
|
207
|
+
return false unless line && matcher
|
124
208
|
|
125
209
|
enhance_version(
|
126
|
-
line.match(
|
210
|
+
line.match(matcher),
|
127
211
|
:github,
|
128
212
|
:github,
|
129
213
|
)
|
130
214
|
end
|
131
215
|
|
132
|
-
|
216
|
+
# @return [true, false]
|
217
|
+
def check_for_version_of_type_git_branch
|
218
|
+
return false unless _check_for_version_of_type_git_branch
|
219
|
+
|
133
220
|
line = relevant_lines.detect { |next_line| next_line.match(VERSION_GIT_REF) }
|
134
|
-
|
221
|
+
# At this point we at least have a branch, though perhaps not a ref.
|
222
|
+
return true unless line
|
135
223
|
|
136
224
|
enhance_version(
|
137
225
|
line.match(VERSION_GIT_REF),
|
@@ -140,6 +228,7 @@ module GemBench
|
|
140
228
|
)
|
141
229
|
end
|
142
230
|
|
231
|
+
# @return [true, false]
|
143
232
|
def check_for_version_of_type_git_tag
|
144
233
|
line = relevant_lines.detect { |next_line| next_line.match(VERSION_GIT_TAG) }
|
145
234
|
return false unless line
|
@@ -151,7 +240,8 @@ module GemBench
|
|
151
240
|
)
|
152
241
|
end
|
153
242
|
|
154
|
-
|
243
|
+
# @return [true, false]
|
244
|
+
def _check_for_version_of_type_git_branch
|
155
245
|
line = relevant_lines.detect { |next_line| next_line.match(VERSION_GIT_BRANCH) }
|
156
246
|
return false unless line
|
157
247
|
|
@@ -162,7 +252,7 @@ module GemBench
|
|
162
252
|
)
|
163
253
|
end
|
164
254
|
|
165
|
-
#
|
255
|
+
# @return [Array[String]] - each line following the current line, which is not a gem line
|
166
256
|
def following_non_gem_lines
|
167
257
|
all_lines[(index + 1)..-1]
|
168
258
|
.reject { |x| x.strip.empty? || x.match(GemBench::TRASH_REGEX) }
|
@@ -174,23 +264,25 @@ module GemBench
|
|
174
264
|
end
|
175
265
|
end
|
176
266
|
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
break mem if mem.keys.length >= 2
|
183
|
-
end
|
267
|
+
# @return [String] the name of the named capture which has a value (one of: value1, value2, value3, etc.)
|
268
|
+
def determine_named_capture(match_data)
|
269
|
+
match_data.names
|
270
|
+
.select { |name| name.start_with?("value") }
|
271
|
+
.detect { |capture| !match_data[capture]&.empty? }
|
184
272
|
end
|
185
273
|
|
274
|
+
# @return [true]
|
186
275
|
def enhance_version(match_data, version_key, type)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
276
|
+
named_capture = determine_named_capture(match_data)
|
277
|
+
value = match_data[named_capture]
|
278
|
+
if value
|
279
|
+
@version[version_key] = value
|
280
|
+
@version_type = type
|
281
|
+
else
|
282
|
+
@version[version_key] = ""
|
283
|
+
@version_type = :invalid
|
284
|
+
end
|
191
285
|
|
192
|
-
@version.merge!({version_key => normalized_capture["value"]})
|
193
|
-
@version_type = type
|
194
286
|
true
|
195
287
|
end
|
196
288
|
end
|
data/lib/gem_bench/jersey.rb
CHANGED
@@ -1,29 +1,32 @@
|
|
1
1
|
# Std Libs Dependencies
|
2
2
|
require "tmpdir"
|
3
3
|
|
4
|
-
# Re-write a gem to a temp directory, re-namespace the primary namespace of that gem module, and load it.
|
5
|
-
# If the original gem defines multiple top-level namespaces, they can all be renamed by providing more key value pairs.
|
6
|
-
# If the original gem monkey patches other libraries, that behavior can't be isolated, so YMMV.
|
7
|
-
#
|
8
|
-
# NOTE: Non-top-level namespaces do not need to be renamed, as they are isolated within their parent namespace.
|
9
|
-
#
|
10
|
-
# Usage
|
11
|
-
#
|
12
|
-
# jersey = GemBench::Jersey.new(
|
13
|
-
# gem_name: "alt_memery"
|
14
|
-
# trades: {
|
15
|
-
# "Memery" => "AltMemery"
|
16
|
-
# },
|
17
|
-
# metadata: {
|
18
|
-
# something: "a value here",
|
19
|
-
# something_else: :obviously,
|
20
|
-
# },
|
21
|
-
# )
|
22
|
-
# jersey.doff_and_don
|
23
|
-
# # The re-namespaced constant is now available!
|
24
|
-
# AltMemery # => AltMemery
|
25
|
-
#
|
26
4
|
module GemBench
|
5
|
+
# Re-write a gem to a temp directory, re-namespace the primary namespace of that gem module, and load it.
|
6
|
+
# If the original gem defines multiple top-level namespaces, they can all be renamed by providing more key value pairs.
|
7
|
+
# If the original gem monkey patches other libraries, that behavior can't be isolated, so YMMV.
|
8
|
+
#
|
9
|
+
# NOTE: Non-top-level namespaces do not need to be renamed, as they are isolated within their parent namespace.
|
10
|
+
#
|
11
|
+
# Usage
|
12
|
+
#
|
13
|
+
# jersey = GemBench::Jersey.new(
|
14
|
+
# gem_name: "alt_memery"
|
15
|
+
# trades: {
|
16
|
+
# "Memery" => "AltMemery"
|
17
|
+
# },
|
18
|
+
# metadata: {
|
19
|
+
# something: "a value here",
|
20
|
+
# something_else: :obviously,
|
21
|
+
# },
|
22
|
+
# )
|
23
|
+
# jersey.doff_and_don
|
24
|
+
# # The re-namespaced constant is now available!
|
25
|
+
# AltMemery # => AltMemery
|
26
|
+
#
|
27
|
+
# Benchmarking Example
|
28
|
+
#
|
29
|
+
# See: https://github.com/panorama-ed/memo_wise/blob/main/benchmarks/benchmarks.rb
|
27
30
|
class Jersey
|
28
31
|
attr_reader :gem_name
|
29
32
|
attr_reader :gem_path
|
@@ -41,110 +44,141 @@ module GemBench
|
|
41
44
|
@verbose = verbose
|
42
45
|
end
|
43
46
|
|
47
|
+
# return [true, false] proxy for whether the copied, re-namespaced gem has been successfully loaded
|
44
48
|
def required?
|
45
49
|
gem_path && trades.values.all? { |new_namespace| Object.const_defined?(new_namespace) }
|
46
50
|
end
|
47
51
|
|
48
|
-
# Generates
|
52
|
+
# Generates a temp directory, and creates a copy of a gem within it.
|
53
|
+
# Re-namespaces the copy according to the `trades` configuration.
|
54
|
+
# Then requires each file of the "copied gem", resulting
|
49
55
|
# in a loaded gem that will not have namespace
|
50
|
-
# collisions when alongside the original-namespaced gem.
|
56
|
+
# collisions when loaded alongside the original-namespaced gem.
|
57
|
+
# Note that "copied gem" in the previous sentence is ambiguous without the supporting context.
|
58
|
+
# The "copied gem" can mean either the original, or the "copy", which is why this gem refers to
|
59
|
+
# a "doffed gem" (the original) and a "donned gem" (the copy).
|
51
60
|
# If a block is provided the contents of each file will be yielded to the block,
|
52
|
-
# after all namespace substitutions are complete, but before the contents
|
61
|
+
# after all namespace substitutions from `trades` are complete, but before the contents
|
53
62
|
# are written to the re-namespaced gem. The return value of the block will be
|
54
63
|
# written to the file in this scenario.
|
55
64
|
#
|
56
65
|
# @return void
|
57
66
|
def doff_and_don(&block)
|
58
|
-
return puts "
|
67
|
+
return puts "[#{gem_name}] Skipped (not loaded on #{RUBY_VERSION})" unless gem_path
|
59
68
|
|
60
|
-
puts "Doffing #{gem_path}" if verbose
|
61
|
-
Dir.mktmpdir do |
|
69
|
+
puts "[#{gem_name}] Doffing #{gem_path}" if verbose
|
70
|
+
Dir.mktmpdir do |tmp_dir|
|
62
71
|
files = []
|
63
72
|
Dir[File.join(gem_path, "lib", "**", "*.rb")].map do |file|
|
64
73
|
if verbose
|
65
|
-
puts
|
66
|
-
puts
|
67
|
-
puts "--------------------------------"
|
74
|
+
puts "[#{gem_name}] --------------------------------"
|
75
|
+
puts "[#{gem_name}] Doffing file #{file}"
|
76
|
+
puts "[#{gem_name}] --------------------------------"
|
68
77
|
end
|
78
|
+
basename = File.basename(file)
|
69
79
|
dirname = File.dirname(file)
|
70
|
-
puts "dirname: #{dirname}" if verbose
|
80
|
+
puts "[#{gem_name}][#{basename}] dirname: #{dirname}" if verbose
|
71
81
|
is_at_gem_root = dirname[(-4)..(-1)] == "/lib"
|
72
|
-
puts "is_at_gem_root: #{is_at_gem_root}" if verbose
|
82
|
+
puts "[#{gem_name}][#{basename}] is_at_gem_root: #{is_at_gem_root}" if verbose
|
73
83
|
lib_split = dirname.split("/lib/")[-1]
|
74
|
-
puts "lib_split: #{lib_split}" if verbose
|
84
|
+
puts "[#{gem_name}][#{basename}] lib_split: #{lib_split}" if verbose
|
75
85
|
# lib_split could be like:
|
76
86
|
# - "ruby/gems/3.2.0/gems/method_source-1.1.0/lib"
|
77
87
|
# - "method_source"
|
78
88
|
# Se we check to make sure it is actually a subdir of the gem's lib directory
|
79
89
|
full_path = File.join(gem_path, "lib", lib_split)
|
80
90
|
relative_path = !is_at_gem_root && Dir.exist?(full_path) && lib_split
|
81
|
-
puts "relative_path: #{relative_path}" if verbose
|
82
|
-
filename = File.basename(file)[0..-4]
|
83
|
-
puts "filename: #{filename}" if verbose
|
91
|
+
puts "[#{gem_name}][#{basename}] relative_path: #{relative_path}" if verbose
|
84
92
|
|
85
93
|
if relative_path
|
86
|
-
dir_path = File.join(
|
94
|
+
dir_path = File.join(tmp_dir, relative_path)
|
87
95
|
Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
|
88
|
-
puts "
|
89
|
-
files << create_tempfile_copy(file,
|
96
|
+
puts "[#{gem_name}][#{basename}] copying file to #{dir_path}" if verbose
|
97
|
+
files << create_tempfile_copy(file, dir_path, basename, :dd1, &block)
|
90
98
|
else
|
91
|
-
puts "directory not relative (#{
|
92
|
-
files << create_tempfile_copy(file,
|
99
|
+
puts "[#{gem_name}][#{basename}] directory not relative (#{tmp_dir})" if verbose
|
100
|
+
files << create_tempfile_copy(file, tmp_dir, basename, :dd2, &block)
|
93
101
|
end
|
94
102
|
end
|
95
|
-
load_gem_copy(files)
|
103
|
+
load_gem_copy(tmp_dir, files)
|
96
104
|
end
|
97
105
|
|
98
106
|
nil
|
99
107
|
end
|
100
108
|
|
101
|
-
|
109
|
+
# @return [String] Namespace of the doffed (original) gem
|
110
|
+
def doffed_primary_namespace
|
111
|
+
trades.keys.first
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [String] Namespace of the donned gem
|
115
|
+
def donned_primary_namespace
|
102
116
|
trades.values.first
|
103
117
|
end
|
104
118
|
|
105
119
|
# Will raise NameError if called before #doff_and_don
|
120
|
+
# @return [Class, nil]
|
106
121
|
def as_klass
|
107
|
-
Object.const_get(
|
122
|
+
Object.const_get(donned_primary_namespace) if gem_path
|
108
123
|
end
|
109
124
|
|
110
125
|
private
|
111
126
|
|
112
|
-
|
127
|
+
# @param tmp_dir [String] absolute file path of the tmp directory
|
128
|
+
# @param files [Array[String]] absolute file path of each file in the donned gem
|
129
|
+
# @return void
|
130
|
+
def load_gem_copy(tmp_dir, files)
|
131
|
+
if verbose
|
132
|
+
puts "[#{gem_name}] Doffed gem located at #{gem_path}"
|
133
|
+
puts "[#{gem_name}] Donned gem located at #{tmp_dir}"
|
134
|
+
puts "[#{gem_name}] Primary namespace updated: #{doffed_primary_namespace} => #{donned_primary_namespace}"
|
135
|
+
puts "[#{gem_name}] Donned files:\n\t#{files.join("\n\t")}"
|
136
|
+
end
|
113
137
|
files.each do |filepath|
|
114
|
-
#
|
138
|
+
# But files required here may not load their own internal files properly if they are still using `require`.
|
139
|
+
# Since Ruby 2.2, best practice for ruby libraries is to use require_relative for internal files,
|
140
|
+
# and require for external files and dependencies.
|
141
|
+
# Ref: https://github.com/panorama-ed/memo_wise/issues/349
|
142
|
+
# We *can* use `require` *here*, because filepath here is an absolute paths
|
115
143
|
require filepath
|
116
|
-
# rescue LoadError => e
|
117
|
-
# puts file.to_s
|
118
|
-
# puts tempfile.path
|
119
|
-
# puts e.class
|
120
|
-
# puts e.message
|
121
|
-
# end
|
122
144
|
end
|
145
|
+
|
146
|
+
nil
|
123
147
|
end
|
124
148
|
|
149
|
+
# @param orig_file_path [String] absolute file path of the original file
|
150
|
+
# @param tmp_dir [String] absolute file path of the tmp directory
|
151
|
+
# @param basename [String] the basename of the file being copied
|
152
|
+
# @param debug_info [Symbol] for debugging purposes
|
125
153
|
# @return [String] the file path of the new copy of the original file
|
126
|
-
def create_tempfile_copy(
|
127
|
-
|
128
|
-
|
129
|
-
new_jersey(
|
154
|
+
def create_tempfile_copy(orig_file_path, tmp_dir, basename, debug_info, &block)
|
155
|
+
File.open(File.join(tmp_dir, basename), "w") do |donned_file|
|
156
|
+
# Value of block is returned from File.open, and thus from this method
|
157
|
+
new_jersey(orig_file_path, donned_file, basename, debug_info, &block)
|
130
158
|
end
|
131
159
|
end
|
132
160
|
|
133
|
-
#
|
134
|
-
|
135
|
-
|
161
|
+
# New Jersey is not Ohio. Writes donned files to disk.
|
162
|
+
#
|
163
|
+
# @param doffed_file_path [String] absolute file path of the original file
|
164
|
+
# @param donned_file [File] the file which needs to be written to disk
|
165
|
+
# @param basename [String] the basename of the file for verbose logging
|
166
|
+
# @param debug_info [Symbol] for debugging purposes
|
167
|
+
# @return [String] the file path of the donned file
|
168
|
+
def new_jersey(doffed_file_path, donned_file, basename, debug_info = nil)
|
169
|
+
nj = File.read(doffed_file_path)
|
136
170
|
trades.each do |old_namespace, new_namespace|
|
137
171
|
nj.gsub!(old_namespace, new_namespace)
|
138
172
|
end
|
139
173
|
if verbose
|
140
|
-
puts "new_jersey
|
141
|
-
puts "new_jersey
|
142
|
-
puts "new_jersey
|
174
|
+
puts "[#{gem_name}][#{basename}] new_jersey doffed_file_path: #{doffed_file_path}"
|
175
|
+
puts "[#{gem_name}][#{basename}] new_jersey donned_file path: #{donned_file.path}"
|
176
|
+
puts "[#{gem_name}][#{basename}] new_jersey debug_info: #{debug_info}"
|
143
177
|
end
|
144
178
|
nj = yield nj if block_given?
|
145
|
-
|
146
|
-
|
147
|
-
|
179
|
+
donned_file.write(nj)
|
180
|
+
donned_file.close
|
181
|
+
donned_file.path
|
148
182
|
end
|
149
183
|
end
|
150
184
|
end
|
data/lib/gem_bench/player.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module GemBench
|
2
|
+
# Each gem either needs to be required at boot time or not.
|
3
|
+
# Player helps determine which gems can use `require: false` in the Gemfile to cut down load times.
|
2
4
|
class Player
|
3
5
|
# MAJOR.MINOR split on point length == 2
|
4
6
|
# MAJOR.MINOR.PATCH split on point length == 3
|
data/lib/gem_bench/scout.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# Scout's job is to figure out where gems are hiding
|
2
2
|
#
|
3
3
|
module GemBench
|
4
|
+
# Looks through loaded gems' (RubyGems & Bundler) source code searching for stuff
|
4
5
|
class Scout
|
5
6
|
attr_reader :gem_paths, :gemfile_path, :gemfile_lines, :gemfile_trash, :loaded_gems
|
6
7
|
|
7
|
-
def initialize(check_gemfile: nil)
|
8
|
+
def initialize(check_gemfile: nil, **options)
|
8
9
|
@check_gemfile = check_gemfile.nil? ? true : check_gemfile
|
9
|
-
@gemfile_path = "#{Dir.pwd}/Gemfile"
|
10
|
+
@gemfile_path = options.fetch(:gemfile_path, "#{Dir.pwd}/Gemfile")
|
10
11
|
gem_lookup_paths_from_bundler
|
11
12
|
gem_lines_from_gemfile
|
12
13
|
# Gem.loaded_specs are the gems that have been loaded / required.
|
@@ -27,10 +28,10 @@ module GemBench
|
|
27
28
|
.map { |x| x.to_s }
|
28
29
|
.reject { |p| p.empty? }
|
29
30
|
.map { |x| "#{x}/gems" }
|
30
|
-
@gem_paths <<
|
31
|
+
@gem_paths << Bundler.install_path.to_s # Pathname => String
|
31
32
|
@gem_paths << "#{Bundler.bundle_path}/gems"
|
32
33
|
@gem_paths.uniq!
|
33
|
-
rescue Bundler::GemfileNotFound =>
|
34
|
+
rescue Bundler::GemfileNotFound => _e
|
34
35
|
# Don't fail here, but also don't check the Gemfile.
|
35
36
|
@check_gemfile = false
|
36
37
|
ensure
|
@@ -42,6 +43,7 @@ module GemBench
|
|
42
43
|
file = File.open(gemfile_path)
|
43
44
|
# Get all lines as an array
|
44
45
|
all_lines = file.readlines
|
46
|
+
file.close
|
45
47
|
# Remove all the commented || blank lines
|
46
48
|
@gemfile_trash, @gemfile_lines = all_lines.partition { |x| x =~ GemBench::TRASH_REGEX }
|
47
49
|
@gemfile_trash.reject! { |x| x == "\n" } # remove blank lines
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module GemBench
|
2
|
+
# Helps determine if a gem dependency is "valid" according to the strict rules of:
|
3
|
+
# - every gem must have a version requirement of some sort
|
2
4
|
class StrictVersionGem
|
3
5
|
attr_reader :name, :version, :version_type, :valid, :relevant_lines, :index, :tokenized_line
|
4
6
|
|
5
7
|
class << self
|
6
8
|
def from_line(all_lines, line, index, opts = {})
|
7
9
|
tokenized_line = GemfileLineTokenizer.new(all_lines, line, index)
|
8
|
-
return unless tokenized_line.
|
10
|
+
return unless tokenized_line.parse_success
|
9
11
|
|
10
12
|
new(
|
11
13
|
tokenized_line.name,
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module GemBench
|
2
|
+
# Helps enforce a version requirement for every dependency in a Gemfile
|
2
3
|
class StrictVersionRequirement
|
3
4
|
attr_reader :gemfile_path, :gems, :starters, :benchers, :verbose
|
4
5
|
|
5
6
|
def initialize(options = {})
|
6
|
-
@gemfile_path = "#{Dir.pwd}/Gemfile"
|
7
|
+
@gemfile_path = options.fetch(:gemfile_path, "#{Dir.pwd}/Gemfile")
|
7
8
|
file = File.open(gemfile_path)
|
8
9
|
# Get all lines as an array
|
9
10
|
all_lines = file.readlines
|
data/lib/gem_bench/team.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require "forwardable"
|
2
2
|
|
3
3
|
module GemBench
|
4
|
+
# It doesn't make sense to use Team unless the Gemfile you want to evaluate is currently loaded.
|
5
|
+
# For example:
|
6
|
+
# - if you are in a rails console, and want to evaluate the Gemfile of the Rails app, that's great!
|
7
|
+
# - if you are in a context with no Gemfile loaded, or a different Gemfile loaded than the one you want to evaluate,
|
8
|
+
# this class may not give sensible results. This is because it checks loaded gems via RubyGems and Bundler.
|
4
9
|
class Team
|
5
10
|
EXCLUDE = %w[
|
6
11
|
bundler
|
@@ -19,6 +24,8 @@ module GemBench
|
|
19
24
|
cancan
|
20
25
|
friendly_id
|
21
26
|
faker
|
27
|
+
capistrano3-puma
|
28
|
+
wkhtmltopdf-binary
|
22
29
|
]
|
23
30
|
# A comment preceding the require: false anywhere on the line should not be considered an active require: false
|
24
31
|
extend Forwardable
|
@@ -34,13 +41,16 @@ module GemBench
|
|
34
41
|
:current_gemfile_suggestions,
|
35
42
|
:bad_ideas
|
36
43
|
|
37
|
-
def initialize(options
|
44
|
+
def initialize(**options)
|
38
45
|
@look_for_regex = options[:look_for_regex]
|
39
46
|
# find: Find gems containing specific strings in code
|
40
47
|
# bench: Find gems that can probably be benched (require: false) in the Gemfile
|
41
48
|
@check_type = @look_for_regex ? :find : :bench
|
42
49
|
@benching = @check_type == :bench
|
43
|
-
@scout = GemBench::Scout.new(
|
50
|
+
@scout = GemBench::Scout.new(
|
51
|
+
check_gemfile: options.fetch(:check_gemfile, benching?),
|
52
|
+
gemfile_path: options.fetch(:gemfile_path, "#{Dir.pwd}/Gemfile"),
|
53
|
+
)
|
44
54
|
@exclude_file_pattern_regex_proc = options[:exclude_file_pattern_regex_proc].respond_to?(:call) ? options[:exclude_file_pattern_regex_proc] : GemBench::EXCLUDE_FILE_PATTERN_REGEX_PROC
|
45
55
|
# Among the loaded gems there may be some that did not need to be.
|
46
56
|
@excluded, @all = @scout.loaded_gems.partition { |x| EXCLUDE.include?(x[0]) }
|
@@ -192,13 +202,13 @@ module GemBench
|
|
192
202
|
end
|
193
203
|
|
194
204
|
def check(player)
|
195
|
-
gem_paths.
|
205
|
+
gem_paths.detect do |path|
|
196
206
|
glob_path = "#{path}/#{player.file_path_glob}"
|
197
|
-
file_paths = Dir.glob(
|
207
|
+
file_paths = Dir.glob(glob_path)
|
198
208
|
puts "[GemBench] checking #{player} at #{glob_path} (#{file_paths.length} files)" if extra_verbose?
|
199
|
-
file_paths.
|
209
|
+
file_paths.detect do |file_path|
|
200
210
|
player.set_starter(file_path, line_match: look_for_regex)
|
201
|
-
|
211
|
+
player.starter?
|
202
212
|
end
|
203
213
|
end
|
204
214
|
end
|