1
1
# frozen_string_literal: true
2
2
3
+ require 'pathname'
3
4
require 'set'
4
5
require 'yaml'
5
6
require 'appmap/util'
@@ -24,7 +25,7 @@ class Config
24
25
# * +labels+ is used to apply labels to matching code. This is really only useful when the package will be applied to
25
26
# specific functions, via TargetMethods.
26
27
# * +shallow+ indicates shallow mapping, in which only the entrypoint to a gem is recorded.
27
- Package = Struct . new ( :name , :path , :gem , :require_name , :exclude , :labels , :shallow ) do
28
+ Package = Struct . new ( :name , :path , :gem , :require_name , :exclude , :labels , :shallow , :builtin ) do
28
29
# This is for internal use only.
29
30
private_methods :gem
30
31
@@ -50,6 +51,10 @@ def build_from_path(path, shallow: false, require_name: nil, exclude: [], labels
50
51
Package . new ( path , path , nil , require_name , exclude , labels , shallow )
51
52
end
52
53
54
+ def build_from_builtin ( path , shallow : false , require_name : nil , exclude : [ ] , labels : [ ] )
55
+ Package . new ( path , path , nil , require_name , exclude , labels , shallow , true )
56
+ end
57
+
53
58
# Builds a package for gem. Generally corresponds to a `gem:` entry in appmap.yml. Also used when mapping
54
59
# a builtin.
55
60
def build_from_gem ( gem , shallow : true , require_name : nil , exclude : [ ] , labels : [ ] , optional : false , force : false )
@@ -110,6 +115,8 @@ def to_h
110
115
method_names : method_names
111
116
}
112
117
end
118
+
119
+ alias as_json to_h
113
120
end
114
121
private_constant :TargetMethods
115
122
@@ -138,9 +145,11 @@ def to_h
138
145
private_constant :MethodHook
139
146
140
147
class << self
141
- def package_hooks ( methods , path : nil , gem : nil , force : false , handler_class : nil , require_name : nil )
148
+ def package_hooks ( methods , path : nil , gem : nil , force : false , builtin : false , handler_class : nil , require_name : nil )
142
149
Array ( methods ) . map do |method |
143
- package = if gem
150
+ package = if builtin
151
+ Package . build_from_builtin ( path , require_name : require_name , labels : method . labels , shallow : false )
152
+ elsif gem
144
153
Package . build_from_gem ( gem , require_name : require_name , labels : method . labels , shallow : false , force : force , optional : true )
145
154
elsif path
146
155
Package . build_from_path ( path , require_name : require_name , labels : method . labels , shallow : false )
@@ -171,8 +180,10 @@ def declare_hook(hook_decl)
171
180
require_name = hook_decl [ 'require_name' ]
172
181
gem_name = hook_decl [ 'gem' ]
173
182
path = hook_decl [ 'path' ]
183
+ builtin = hook_decl [ 'builtin' ]
174
184
175
185
options = {
186
+ builtin : builtin ,
176
187
gem : gem_name ,
177
188
path : path ,
178
189
require_name : require_name || gem_name || path ,
@@ -185,35 +196,75 @@ def declare_hook(hook_decl)
185
196
package_hooks ( methods , **options )
186
197
end
187
198
188
- def load_builtin_hooks
189
- load_hooks ( 'builtin_hooks' ) do |path , config |
190
- config [ 'path' ] = path
199
+ def declare_hook_deprecated ( hook_decl )
200
+ function_name = hook_decl [ 'name' ]
201
+ package , cls , functions = [ ]
202
+ if function_name
203
+ package , cls , _ , function = Util . parse_function_name ( function_name )
204
+ functions = Array ( function )
205
+ else
206
+ package = hook_decl [ 'package' ]
207
+ cls = hook_decl [ 'class' ]
208
+ functions = hook_decl [ 'function' ] || hook_decl [ 'functions' ]
209
+ raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
191
210
end
211
+
212
+ functions = Array ( functions ) . map ( &:to_sym )
213
+ labels = hook_decl [ 'label' ] || hook_decl [ 'labels' ]
214
+ req = hook_decl [ 'require' ]
215
+ builtin = hook_decl [ 'builtin' ]
216
+
217
+ package_options = { }
218
+ package_options [ :labels ] = Array ( labels ) . map ( &:to_s ) if labels if labels
219
+ package_options [ :require_name ] = req
220
+ package_options [ :require_name ] ||= package if builtin
221
+ tm = TargetMethods . new ( functions , Package . build_from_path ( package , **package_options ) )
222
+ ClassTargetMethods . new ( cls , tm )
192
223
end
193
224
194
- def load_gem_hooks
195
- load_hooks ( 'gem_hooks' ) do |path , config |
196
- config [ 'gem' ] = path
197
- end
225
+ def builtin_hooks_path
226
+ [ [ __dir__ , 'builtin_hooks' ] . join ( '/' ) ] + ( ENV [ 'APPMAP_BUILTIN_HOOKS_PATH' ] || '' ) . split ( /[;:]/ )
227
+ end
228
+
229
+ def gem_hooks_path
230
+ [ [ __dir__ , 'gem_hooks' ] . join ( '/' ) ] + ( ENV [ 'APPMAP_GEM_HOOKS_PATH' ] || '' ) . split ( /[;:]/ )
198
231
end
199
232
200
- def load_hooks ( dir , &block )
201
- basedir = [ __dir__ , dir ] . join ( '/' )
202
- [ ] . tap do |hooks |
203
- Dir . glob ( "#{ basedir } /**/*.yml" ) . each do |yaml_file |
204
- path = yaml_file [ basedir . length + 1 ...-4 ]
205
- YAML . load ( File . read ( yaml_file ) ) . map do |config |
206
- yield path , config
207
- config
208
- end . each do |config |
209
- hooks << declare_hook ( config )
233
+ def load_hooks
234
+ loader = lambda do |dir , &block |
235
+ basename = dir . split ( '/' ) . compact . join ( '/' )
236
+ [ ] . tap do |hooks |
237
+ Dir . glob ( Pathname . new ( dir ) . join ( '**' ) . join ( '*.yml' ) . to_s ) . each do |yaml_file |
238
+ path = yaml_file [ basename . length + 1 ...-4 ]
239
+ YAML . load ( File . read ( yaml_file ) ) . map do |config |
240
+ block . call path , config
241
+ config
242
+ end . each do |config |
243
+ hooks << declare_hook ( config )
244
+ end
210
245
end
246
+ end . compact
247
+ end
248
+
249
+ builtin_hooks = builtin_hooks_path . map do |path |
250
+ loader . ( path ) do |path , config |
251
+ config [ 'path' ] = path
252
+ config [ 'builtin' ] = true
253
+ end
254
+ end
255
+
256
+ gem_hooks = gem_hooks_path . map do |path |
257
+ loader . ( path ) do |path , config |
258
+ config [ 'gem' ] = path
259
+ config [ 'builtin' ] = false
211
260
end
212
- end . compact . flatten
261
+ end
262
+
263
+ ( builtin_hooks + gem_hooks ) . flatten
213
264
end
214
265
end
215
266
216
- attr_reader :name , :appmap_dir , :packages , :exclude , :swagger_config , :depends_config , :hooked_methods , :builtin_hooks
267
+ attr_reader :name , :appmap_dir , :packages , :exclude , :swagger_config , :depends_config , :gem_hooks , :builtin_hooks
217
268
218
269
def initialize ( name ,
219
270
packages : [ ] ,
@@ -230,29 +281,19 @@ def initialize(name,
230
281
@exclude = exclude
231
282
@functions = functions
232
283
233
- @builtin_hooks = self . class . load_builtin_hooks . each_with_object ( Hash . new { |h , k | h [ k ] = [ ] } ) do |cls_target_methods , hooked_methods |
234
- hooked_methods [ cls_target_methods . cls ] << cls_target_methods . target_methods
235
- end
236
-
237
- @hooked_methods = self . class . load_gem_hooks . each_with_object ( Hash . new { |h , k | h [ k ] = [ ] } ) do |cls_target_methods , hooked_methods |
238
- hooked_methods [ cls_target_methods . cls ] << cls_target_methods . target_methods
239
- end
240
-
241
- functions . each do |func |
242
- package_options = { }
243
- package_options [ :labels ] = func . labels if func . labels
244
- package_options [ :require_name ] = func . require_name
245
- package_options [ :require_name ] ||= func . package if func . builtin
246
- hook = TargetMethods . new ( func . function_names , Package . build_from_path ( func . package , **package_options ) )
247
- if func . builtin
248
- @builtin_hooks [ func . cls ] ||= [ ]
249
- @builtin_hooks [ func . cls ] << hook
284
+ @builtin_hooks = Hash . new { |h , k | h [ k ] = [ ] }
285
+ @gem_hooks = Hash . new { |h , k | h [ k ] = [ ] }
286
+
287
+ ( functions + self . class . load_hooks ) . each_with_object ( Hash . new { |h , k | h [ k ] = [ ] } ) do |cls_target_methods , gem_hooks |
288
+ hooks = if cls_target_methods . target_methods . package . builtin
289
+ @builtin_hooks
250
290
else
251
- @hooked_methods [ func . cls ] << hook
291
+ @gem_hooks
252
292
end
293
+ hooks [ cls_target_methods . cls ] << cls_target_methods . target_methods
253
294
end
254
295
255
- @hooked_methods . each_value do |hooks |
296
+ @gem_hooks . each_value do |hooks |
256
297
@hook_paths += Array ( hooks ) . map { |hook | hook . package . path } . compact
257
298
end
258
299
end
@@ -317,23 +358,14 @@ def load(config_data)
317
358
} . compact
318
359
319
360
if config_data [ 'functions' ]
320
- config_params [ :functions ] = config_data [ 'functions' ] . map do |function_data |
321
- function_name = function_data [ 'name' ]
322
- package , cls , functions = [ ]
323
- if function_name
324
- package , cls , _ , function = Util . parse_function_name ( function_name )
325
- functions = Array ( function )
361
+ config_params [ :functions ] = config_data [ 'functions' ] . map do |hook_decl |
362
+ if hook_decl [ 'name' ] || hook_decl [ 'package' ]
363
+ declare_hook_deprecated ( hook_decl )
326
364
else
327
- package = function_data [ 'package' ]
328
- cls = function_data [ 'class' ]
329
- functions = function_data [ 'function' ] || function_data [ 'functions' ]
330
- raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
365
+ # Support the same syntax within the 'functions' that's used for externalized
366
+ # hook config.
367
+ declare_hook ( hook_decl )
331
368
end
332
-
333
- functions = Array ( functions ) . map ( &:to_sym )
334
- labels = function_data [ 'label' ] || function_data [ 'labels' ]
335
- labels = Array ( labels ) . map ( &:to_s ) if labels
336
- Function . new ( package , cls , labels , functions , function_data [ 'builtin' ] , function_data [ 'require' ] )
337
369
end
338
370
end
339
371
@@ -405,7 +437,7 @@ def package
405
437
# Hook a method which is specified by class and method name.
406
438
def package_for_code_object
407
439
class_name = cls . to_s . index ( '#<Class:' ) == 0 ? cls . to_s [ '#<Class:' . length ...-1 ] : cls . name
408
- Array ( config . hooked_methods [ class_name ] )
440
+ Array ( config . gem_hooks [ class_name ] )
409
441
. find { |hook | hook . include_method? ( method . name ) }
410
442
&.package
411
443
end
0 commit comments