appmap 0.23.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +17 -8
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +19 -0
  6. data/README.md +29 -12
  7. data/Rakefile +3 -3
  8. data/appmap.gemspec +3 -1
  9. data/exe/appmap +6 -18
  10. data/lib/appmap.rb +47 -6
  11. data/lib/appmap/algorithm/prune_class_map.rb +2 -0
  12. data/lib/appmap/algorithm/stats.rb +4 -2
  13. data/lib/appmap/class_map.rb +143 -0
  14. data/lib/appmap/command/record.rb +8 -6
  15. data/lib/appmap/command/stats.rb +2 -0
  16. data/lib/appmap/command/upload.rb +4 -2
  17. data/lib/appmap/event.rb +168 -0
  18. data/lib/appmap/hook.rb +151 -0
  19. data/lib/appmap/middleware/remote_recording.rb +14 -20
  20. data/lib/appmap/rails/action_handler.rb +10 -6
  21. data/lib/appmap/rails/sql_handler.rb +10 -8
  22. data/lib/appmap/railtie.rb +31 -18
  23. data/lib/appmap/rspec.rb +238 -261
  24. data/lib/appmap/trace.rb +88 -0
  25. data/lib/appmap/version.rb +1 -1
  26. data/package-lock.json +90 -92
  27. data/spec/abstract_controller4_base_spec.rb +1 -1
  28. data/spec/abstract_controller_base_spec.rb +7 -3
  29. data/spec/config_spec.rb +25 -0
  30. data/spec/fixtures/hook/attr_accessor.rb +5 -0
  31. data/spec/fixtures/hook/class_method.rb +17 -0
  32. data/spec/fixtures/hook/constructor.rb +7 -0
  33. data/spec/fixtures/hook/exception_method.rb +11 -0
  34. data/spec/fixtures/hook/instance_method.rb +23 -0
  35. data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +3 -3
  36. data/spec/fixtures/rails4_users_app/config/database.yml +2 -1
  37. data/spec/fixtures/rails4_users_app/docker-compose.yml +2 -0
  38. data/spec/fixtures/rails_users_app/.ruby-version +1 -1
  39. data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +2 -2
  40. data/spec/fixtures/rails_users_app/config/database.yml +2 -1
  41. data/spec/fixtures/rails_users_app/create_app +1 -0
  42. data/spec/fixtures/rails_users_app/docker-compose.yml +4 -0
  43. data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +1 -1
  44. data/spec/hook_spec.rb +357 -0
  45. data/spec/rails_spec_helper.rb +25 -16
  46. data/spec/railtie_spec.rb +1 -1
  47. data/spec/record_sql_rails_pg_spec.rb +1 -2
  48. data/spec/remote_recording_spec.rb +117 -0
  49. data/spec/spec_helper.rb +1 -0
  50. data/test/cli_test.rb +7 -36
  51. data/test/fixtures/cli_record_test/appmap.yml +2 -1
  52. data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +4 -2
  53. data/test/test_helper.rb +0 -42
  54. metadata +46 -62
  55. data/exe/_appmap-record-self +0 -49
  56. data/lib/appmap/command/inspect.rb +0 -14
  57. data/lib/appmap/config.rb +0 -65
  58. data/lib/appmap/config/directory.rb +0 -65
  59. data/lib/appmap/config/file.rb +0 -13
  60. data/lib/appmap/config/named_function.rb +0 -21
  61. data/lib/appmap/config/package_dir.rb +0 -52
  62. data/lib/appmap/config/path.rb +0 -25
  63. data/lib/appmap/feature.rb +0 -262
  64. data/lib/appmap/inspect.rb +0 -91
  65. data/lib/appmap/inspect/inspector.rb +0 -99
  66. data/lib/appmap/inspect/parse_node.rb +0 -170
  67. data/lib/appmap/inspect/parser.rb +0 -15
  68. data/lib/appmap/parser.rb +0 -60
  69. data/lib/appmap/rspec/parse_node.rb +0 -41
  70. data/lib/appmap/rspec/parser.rb +0 -15
  71. data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +0 -65
  72. data/lib/appmap/trace/tracer.rb +0 -356
  73. data/spec/fixtures/rails_users_app/bin/_appmap-record-self +0 -29
  74. data/spec/rack_handler_webrick_spec.rb +0 -59
  75. data/test/config_test.rb +0 -149
  76. data/test/explict_inspect_test.rb +0 -29
  77. data/test/fixtures/active_record_like/active_record.rb +0 -2
  78. data/test/fixtures/active_record_like/active_record/aggregations.rb +0 -4
  79. data/test/fixtures/active_record_like/active_record/association.rb +0 -4
  80. data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +0 -6
  81. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +0 -8
  82. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +0 -8
  83. data/test/fixtures/active_record_like/active_record/caps/caps.rb +0 -4
  84. data/test/fixtures/ignore_non_ruby_file/class.rb +0 -3
  85. data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +0 -1
  86. data/test/fixtures/includes_excludes/lib/a/a_1.rb +0 -6
  87. data/test/fixtures/includes_excludes/lib/a/a_2.rb +0 -6
  88. data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +0 -8
  89. data/test/fixtures/includes_excludes/lib/b/b_1.rb +0 -6
  90. data/test/fixtures/includes_excludes/lib/root_1.rb +0 -4
  91. data/test/fixtures/inspect_multiple_subdirs/module_a.rb +0 -2
  92. data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +0 -5
  93. data/test/fixtures/inspect_multiple_subdirs/module_b.rb +0 -2
  94. data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +0 -5
  95. data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +0 -5
  96. data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +0 -6
  97. data/test/fixtures/parse_file/defs_static_function.rb +0 -96
  98. data/test/fixtures/parse_file/function_within_class.rb +0 -36
  99. data/test/fixtures/parse_file/include_public_methods.rb +0 -127
  100. data/test/fixtures/parse_file/instance_function.rb +0 -17
  101. data/test/fixtures/parse_file/modules.rb +0 -71
  102. data/test/fixtures/parse_file/sclass_static_function.rb +0 -88
  103. data/test/fixtures/parse_file/toplevel_class.rb +0 -13
  104. data/test/fixtures/parse_file/toplevel_function.rb +0 -14
  105. data/test/fixtures/trace_test/trace_program_1.rb +0 -44
  106. data/test/implicit_inspect_test.rb +0 -33
  107. data/test/include_exclude_test.rb +0 -48
  108. data/test/prerecorded_trace_test.rb +0 -76
  109. data/test/trace_test.rb +0 -92
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ba1394503aa1c8f498901ed3c7b903af4bcf5b60b16fe48eee08d9c64f66d67
4
- data.tar.gz: 665c48d0d450b2500277b2bdcb54dc356e0b31d442c1f7d9fcb47d0c5c054de6
3
+ metadata.gz: d42f8555b02b0478b8927c249f6dfa54c9326b7e3122cd5a0b91b5d1fb308fbb
4
+ data.tar.gz: 525faceacd156300098b897f7ef7789d94d42601f9ccec5d3bfad0f41b62ac54
5
5
  SHA512:
6
- metadata.gz: 279932323e5ca1ba95eb42a7bc4410e795fb308b2861c1b3ee2bb29ece31e54664983731648967e82ce4367813eb67557791c21bab16cfc67192d743b34d7568
7
- data.tar.gz: 0141f02617969b168a2dd0c158e8fbfd5b85caa5bc3fa23691c049ae37adefa269d8e76c3c37b4f08f139bc20071b53bf7b31056b2df1c5dfafef44123a7136b
6
+ metadata.gz: 8290dde850c68b07c51baa70c074a283907c77c15093c8d6ed9b151da66f1dbad866f37beef6a12c2897195b8b18d147a839dd049336fd52fa1b48fb5e3d8ab1
7
+ data.tar.gz: '035025557325693fd0a158ce007dab265ec2646a3cf2e729f42348ac0986081f4356a1cec1e66c1bc7d9608025c6889b497e52ec6f510612a3fb5cedd220f22d'
data/.gitignore CHANGED
@@ -13,5 +13,6 @@ vendor
13
13
  node_modules
14
14
  Gemfile.lock
15
15
  appmap.json
16
+ .vscode
16
17
  .byebug_history
17
18
 
data/.rubocop.yml CHANGED
@@ -1,18 +1,27 @@
1
- Style/MultilineBlockChain:
1
+ Layout/SpaceInsideArrayLiteralBrackets:
2
2
  Enabled: false
3
3
 
4
- Style/NumericPredicate:
4
+ # We have squiggly heredocs
5
+ Layout/HeredocIndentation:
5
6
  Enabled: false
6
7
 
7
- Layout/SpaceInsideArrayLiteralBrackets:
8
- Enabled: false
8
+ Layout/LineLength:
9
+ Max: 120
9
10
 
10
- # We have squiggly heredocs
11
- Layout/IndentHeredoc:
11
+ Style/MultilineBlockChain:
12
12
  Enabled: false
13
13
 
14
+ Style/NumericPredicate:
15
+ Enabled: false
16
+
14
17
  Style/AndOr:
15
18
  Enabled: false
16
19
 
17
- Metrics/LineLength:
18
- Max: 120
20
+ Style/HashEachMethods:
21
+ Enabled: true
22
+
23
+ Style/HashTransformKeys:
24
+ Enabled: true
25
+
26
+ Style/HashTransformValues:
27
+ Enabled: true
data/.travis.yml CHANGED
@@ -1,5 +1,11 @@
1
1
  language: ruby
2
2
 
3
+ addons:
4
+ apt:
5
+ packages:
6
+ # https://docs.travis-ci.com/user/docker/#installing-a-newer-docker-version
7
+ - docker-ce
8
+
3
9
  services:
4
10
  - docker
5
11
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # v0.25.0
2
+
3
+ * Reports `exceptions` in [function return attributes](https://github.com/applandinc/appmap#function-return-attributes).
4
+
5
+ # v0.24.1
6
+ * Fixes an issue which prevented a remote recording from returning scenario data successfully.
7
+ * Remote recording routes now return descriptive status codes as intended.
8
+ * Remote recording routes now have the correct `Content-Type` header.
9
+
10
+ # v0.24.0
11
+
12
+ Internals of `appmap-ruby` have been changed to record each method event using `alias_method`,
13
+ rather than `TracePoint`. Performance is much better as a result.
14
+
15
+ **WARNING** Breaking changes
16
+
17
+ * **Rack** apps no longer generate `http_server_request` events.
18
+ * **appmap inspect** has been removed. `appmap-ruby` no longer parses the source tree. Instead, it observes the methods as they are loaded by the VM. So, to get a class map, you have to create a recording. The `RSpec` recorder still prints an inventory to `Inventory.appmap.json` when it exits. The class map in this file contains every class and method which was loaded by any of the tests.
19
+
1
20
  # v0.23.0
2
21
 
3
22
  * **appmap stats** command added.
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  - [About](#about)
2
+ - [Testing](#testing)
2
3
  - [Installation](#installation)
3
4
  - [Configuration](#configuration)
4
5
  - [Running](#running)
5
6
  - [RSpec](#rspec)
6
7
  - [Remote recording](#remote-recording)
8
+ - [Ruby on Rails](#ruby-on-rails)
7
9
  - [Uploading AppMaps](#uploading-appmaps)
8
10
  - [Build status](#build-status)
9
11
 
@@ -11,21 +13,33 @@
11
13
 
12
14
  `appmap-ruby` is a Ruby Gem for recording and uploading
13
15
  [AppMaps](https://github.com/applandinc/appmap) of your code.
14
- AppMap is a data format which records code structure (modules, classes, and methods), code execution events
16
+ "AppMap" is a data format which records code structure (modules, classes, and methods), code execution events
15
17
  (function calls and returns), and code metadata (repo name, repo URL, commit
16
- SHA, etc). It's more granular than a performance profile, but it's less
18
+ SHA, labels, etc). It's more granular than a performance profile, but it's less
17
19
  granular than a full debug trace. It's designed to be optimal for understanding the design intent and behavior of code.
18
20
 
19
21
  There are several ways to record AppMaps of your Ruby program using the `appmap` gem:
20
22
 
21
- * Run your RSpec tests. An AppMap will be generated for each one.
22
- * Run your application server with AppMap remote recording enabled, and use the AppMap
23
+ * Run your RSpec tests with the environment variable `APPMAP=true`. An AppMap will be generated for each spec.
24
+ * Run your application server with AppMap remote recording enabled, and use the AppMap.
23
25
  browser extension to start, stop, and upload recordings.
26
+ * Run the command `appmap record <program>` to record the entire execution of a program.
24
27
 
25
28
  When you record AppMaps on the command line (for example, by running RSpec tests), you use the `appmap upload` command to
26
29
  upload them to the AppLand website. On the AppLand website, you'll be able to
27
30
  visualize the design of your code and share links with collaborators.
28
31
 
32
+ # Testing
33
+ Before running tests, configure `local.appmap` to point to your local `appmap-ruby` directory.
34
+ ```
35
+ $ bundle config local.appmap $(pwd)
36
+ ```
37
+
38
+ Run the tests via `rake`:
39
+ ```
40
+ $ bundle exec rake test
41
+ ```
42
+
29
43
  # Installation
30
44
 
31
45
  Add `gem 'appmap'` to your Gemfile just as you would any other dependency.
@@ -60,8 +74,6 @@ packages:
60
74
 
61
75
  * **name** Provides the project name (required)
62
76
  * **packages** A list of source code directories which should be instrumented.
63
- * **files** A list of individual files which should be instrumented. This is only used for files which are
64
- not part of the `packages` list.
65
77
 
66
78
  **packages**
67
79
 
@@ -69,9 +81,7 @@ Each entry in the `packages` list is a YAML object which has the following keys:
69
81
 
70
82
  * **path** The path to the source code directory. The path may be relative to the current working directory, or it may
71
83
  be an absolute path.
72
- * **name** A name for the code package. By default, the package name will be the name of the directory in which the code
73
- is located. In the example above, "controllers" or "models".
74
- * **excludes** A list of files and directories which will be ignored. By default, all modules, classes and public
84
+ * **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
75
85
  functions are inspected.
76
86
 
77
87
  # Running
@@ -129,6 +139,8 @@ If you include the `feature` and `feature_group` metadata, these attributes will
129
139
  }
130
140
  ```
131
141
 
142
+ If you don't explicitly declare `feature` and `feature_group`, then they will be inferred from the spec name and example descriptions.
143
+
132
144
  ## Remote recording
133
145
 
134
146
  To manually record ad-hoc AppMaps of your Ruby app, use AppMap remote recording.
@@ -159,12 +171,18 @@ $ bundle exec rails server
159
171
 
160
172
  6. Open the AppApp browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
161
173
 
174
+ ## Ruby on Rails
175
+
176
+ If your app uses Ruby on Rails, the AppMap Railtie will be automatically enabled. Set the Rails config flag `app.config.appmap.enabled = true` to record the entire execution of your Rails app.
177
+
178
+ Note that using this method is kind of a blunt instrument. Recording RSpecs and using Remote Recording are usually better options.
179
+
162
180
  # Uploading AppMaps
163
181
 
164
- To upload an AppMap file to AppLand, run the `appmap upload` command. For example:
182
+ To upload an AppMap file to AppLand, run the `appmap upload` command. For example (with binstubs installed):
165
183
 
166
184
  ```sh-session
167
- $ appmap upload tmp/appmap/rspec/Hello_app_says_hello_when_prompted.appmap.json
185
+ $ ./bin/appmap upload tmp/appmap/rspec/*
168
186
  Uploading "tmp/appmap/rspec/Hello_app_says_hello_when_prompted.appmap.json"
169
187
  Scenario Id: 4da4f267-bdea-48e8-bf67-f39463844230
170
188
  Batch Id: a116f1df-ee57-4bde-8eef-851af0f3d7bc
@@ -172,4 +190,3 @@ Batch Id: a116f1df-ee57-4bde-8eef-851af0f3d7bc
172
190
 
173
191
  # Build status
174
192
  [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
175
-
data/Rakefile CHANGED
@@ -54,7 +54,7 @@ namespace :build do
54
54
  end
55
55
  end
56
56
  end
57
-
57
+
58
58
  namespace :fixtures do
59
59
  RUBY_VERSIONS.each do |ruby_version|
60
60
  namespace ruby_version do
@@ -69,7 +69,7 @@ namespace :build do
69
69
  end
70
70
  end
71
71
  end
72
-
72
+
73
73
  desc "Build all fixture images"
74
74
  task all: ["#{ruby_version}:all"]
75
75
  end
@@ -93,7 +93,7 @@ def run_specs(ruby_version, task_args)
93
93
  task.rspec_opts = args.to_a.join(' ')
94
94
  end
95
95
  end
96
-
96
+
97
97
  # Set up the environment, then execute the rspec task we
98
98
  # created above.
99
99
  ClimateControl.modify(RUBY_VERSION: ruby_version) do
data/appmap.gemspec CHANGED
@@ -30,11 +30,13 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'bundler', '~> 1.16'
31
31
  spec.add_development_dependency 'minitest', '~> 5.0'
32
32
  spec.add_development_dependency 'pry-byebug'
33
- spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rake', '>= 12.3.3'
34
34
  spec.add_development_dependency 'rdoc'
35
+ spec.add_development_dependency 'rubocop'
35
36
 
36
37
  # Testing
37
38
  spec.add_development_dependency 'climate_control'
39
+ spec.add_development_dependency 'diffy'
38
40
  spec.add_development_dependency 'launchy'
39
41
  spec.add_development_dependency 'rspec'
40
42
  spec.add_development_dependency 'selenium-webdriver'
data/exe/appmap CHANGED
@@ -42,17 +42,6 @@ module AppMap
42
42
  arg_name 'filename'
43
43
  flag %i[c config]
44
44
 
45
- desc 'Inspect code and generate a classmap file'
46
- command :inspect do |c|
47
- output_file_flag(c, default_value: default_appmap_file)
48
-
49
- c.action do
50
- require 'appmap/command/inspect'
51
- appmap = AppMap::Command::Inspect.new(@config).perform
52
- @output_file.write JSON.pretty_generate(appmap)
53
- end
54
- end
55
-
56
45
  desc 'Record the execution of a program and generate an AppMap.'
57
46
  arg_name 'program'
58
47
  command :record do |c|
@@ -76,10 +65,10 @@ module AppMap
76
65
  ARGV.shift
77
66
 
78
67
  require 'appmap/command/record'
79
- AppMap::Command::Record.new(@config, program).perform do |features, events|
80
- @output_file.write JSON.generate(version: AppMap::APPMAP_FORMAT_VERSION,
81
- classMap: features,
82
- metadata: AppMap::Command::Record.detect_metadata,
68
+ AppMap::Command::Record.new(@config, program).perform do |version, metadata, class_map, events|
69
+ @output_file.write JSON.generate(version: version,
70
+ metadata: metadata,
71
+ classMap: class_map,
83
72
  events: events)
84
73
  end
85
74
  end
@@ -173,7 +162,7 @@ module AppMap
173
162
  appmap = JSON.parse(File.read(filename))
174
163
 
175
164
  warn "Uploading #{filename.inspect}"
176
- upload = AppMap::Command::Upload.new(@config, appmap, url, user, org)
165
+ upload = AppMap::Command::Upload.new(appmap, url, user, org)
177
166
  upload.batch_id = batch_id if batch_id
178
167
  upload.perform.tap do |response|
179
168
  batch_id ||= response.batch_id
@@ -203,8 +192,7 @@ module AppMap
203
192
  protected
204
193
 
205
194
  def interpret_config_option(fname)
206
- require 'appmap/config'
207
- AppMap::Config.load_from_file fname
195
+ AppMap.configure fname
208
196
  end
209
197
 
210
198
  def interpret_output_file_option(file_name)
data/lib/appmap.rb CHANGED
@@ -13,12 +13,53 @@ module AppMap
13
13
  BATCH_HEADER_NAME = 'AppLand-Scenario-Batch'
14
14
 
15
15
  class << self
16
- # Simplified entry point to inspect code for features.
17
- def inspect(config)
18
- require 'appmap/inspect'
19
- features = config.source_locations.map(&AppMap::Inspect.method(:detect_features)).flatten.compact
20
- features = features.map(&:reparent)
21
- features.each(&:prune)
16
+ @config = nil
17
+ @config_file_path = nil
18
+
19
+ # configuration gets the AppMap configuration.
20
+ def configuration
21
+ raise "AppMap is not configured" unless @config
22
+
23
+ @config
24
+ end
25
+
26
+ # configure applies the configuration from a file. This method can only be performed once.
27
+ # Be sure and call it before +hook+ if you want non-default configuration.
28
+ #
29
+ # Default behavior is to configure from "appmap.yml".
30
+ def configure(config_file_path = 'appmap.yml')
31
+ if @config
32
+ return @config if @config_file_path == config_file_path
33
+
34
+ raise "AppMap is already configured from #{@config_file_path}, can't reconfigure from #{config_file_path}"
35
+ end
36
+
37
+ warn "Configuring AppMap from path #{config_file_path}"
38
+ require 'appmap/hook'
39
+ AppMap::Hook::Config.load_from_file(config_file_path).tap do |config|
40
+ @config = config
41
+ @config_file_path = config_file_path
42
+ end
43
+ end
44
+
45
+ # Activate the code hooks which record function calls as trace events.
46
+ # Call this function before the program code is loaded by the Ruby VM, otherwise
47
+ # the load events won't be seen and the hooks won't activate.
48
+ def hook(config = configure)
49
+ require 'appmap/hook'
50
+ AppMap::Hook.hook(config)
51
+ end
52
+
53
+ # Access the AppMap::Tracers, which can be used to start tracing, stop tracing, and record events.
54
+ def tracing
55
+ require 'appmap/trace'
56
+ @tracing ||= Trace::Tracers.new
57
+ end
58
+
59
+ # Build a class map from a config and a list of Ruby methods.
60
+ def class_map(config, methods)
61
+ require 'appmap/class_map'
62
+ AppMap::ClassMap.build_from_methods(config, methods)
22
63
  end
23
64
  end
24
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AppMap
2
4
  module Algorithm
3
5
  # Prune a class map so that only functions, classes and packages which are referenced
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AppMap
2
4
  module Algorithm
3
5
  StatsStruct = Struct.new(:appmap)
@@ -31,7 +33,7 @@ module AppMap
31
33
  comparator = ->(a,b) { b.count <=> a.count }
32
34
  class_frequency.sort!(&comparator)
33
35
  method_frequency.sort!(&comparator)
34
-
36
+
35
37
  self
36
38
  end
37
39
 
@@ -55,7 +57,7 @@ module AppMap
55
57
  end
56
58
  end
57
59
  Frequency = Struct.new(:name, :count)
58
-
60
+
59
61
  def perform(limit: nil)
60
62
  events = appmap['events'] || []
61
63
  frequency_calc = lambda do |key_func|
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext'
4
+
5
+ module AppMap
6
+ class ClassMap
7
+ module HasChildren
8
+ def self.included(base)
9
+ base.module_eval do
10
+ def children
11
+ @children ||= []
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module Types
18
+ class Root
19
+ include HasChildren
20
+ end
21
+
22
+ Package = Struct.new(:name) do
23
+ include HasChildren
24
+
25
+ def type
26
+ 'package'
27
+ end
28
+
29
+ def to_h
30
+ {
31
+ name: name,
32
+ type: type,
33
+ children: children.map(&:to_h)
34
+ }
35
+ end
36
+ end
37
+ Class = Struct.new(:name) do
38
+ include HasChildren
39
+
40
+ def type
41
+ 'class'
42
+ end
43
+
44
+ def to_h
45
+ {
46
+ name: name,
47
+ type: type,
48
+ children: children.map(&:to_h)
49
+ }
50
+ end
51
+ end
52
+ Function = Struct.new(:name) do
53
+ attr_accessor :static, :location
54
+
55
+ def type
56
+ 'function'
57
+ end
58
+
59
+ def to_h
60
+ {
61
+ name: name,
62
+ type: type,
63
+ location: location,
64
+ static: static
65
+ }
66
+ end
67
+ end
68
+ end
69
+
70
+ class << self
71
+ def build_from_methods(config, methods)
72
+ root = Types::Root.new
73
+ methods.each do |method|
74
+ package = package_for_method(config.packages, method)
75
+ add_function root, package.path, method
76
+ end
77
+ root.children.map(&:to_h)
78
+ end
79
+
80
+ protected
81
+
82
+ def package_for_method(packages, method)
83
+ location = method.method.source_location
84
+ location_file, = location
85
+ location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
86
+
87
+ packages.find do |pkg|
88
+ (location_file.index(pkg.path) == 0) &&
89
+ !pkg.exclude.find { |p| location_file.index(p) }
90
+ end or raise "No package found for method #{method}"
91
+ end
92
+
93
+ def add_function(root, package_name, method)
94
+ location = method.method.source_location
95
+ location_file, lineno = location
96
+ location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
97
+
98
+ static = method.method.owner.singleton_class?
99
+
100
+ object_infos = [
101
+ {
102
+ name: package_name,
103
+ type: 'package'
104
+ }
105
+ ]
106
+ object_infos += method.defined_class.split('::').map do |name|
107
+ {
108
+ name: name,
109
+ type: 'class'
110
+ }
111
+ end
112
+ object_infos << {
113
+ name: method.method.name,
114
+ type: 'function',
115
+ location: [ location_file, lineno ].join(':'),
116
+ static: static
117
+ }
118
+ parent = root
119
+ object_infos.each do |info|
120
+ parent = find_or_create parent.children, info do
121
+ Types.const_get(info[:type].classify).new(info[:name].to_s).tap do |type|
122
+ info.keys.tap do |keys|
123
+ keys.delete(:name)
124
+ keys.delete(:type)
125
+ end.each do |key|
126
+ type.send "#{key}=", info[key]
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def find_or_create(list, info)
134
+ obj = list.find { |item| item.type == info[:type] && item.name == info[:name] }
135
+ return obj if obj
136
+
137
+ yield.tap do |new_obj|
138
+ list << new_obj
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end