appmap 0.18.1
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 +7 -0
- data/.dockerignore +5 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +100 -0
- data/Dockerfile.appmap +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +117 -0
- data/appmap.gemspec +41 -0
- data/appmap.yml +8 -0
- data/examples/install.rb +76 -0
- data/examples/mock_webapp/Gemfile +1 -0
- data/examples/mock_webapp/appmap.yml +2 -0
- data/examples/mock_webapp/exe/mock_webapp_request +12 -0
- data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
- data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
- data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
- data/exe/_appmap-record-self +49 -0
- data/exe/appmap +168 -0
- data/lib/appmap/algorithm/prune_class_map.rb +65 -0
- data/lib/appmap/command/inspect.rb +11 -0
- data/lib/appmap/command/record.rb +91 -0
- data/lib/appmap/command/upload.rb +103 -0
- data/lib/appmap/config/directory.rb +65 -0
- data/lib/appmap/config/file.rb +13 -0
- data/lib/appmap/config/named_function.rb +21 -0
- data/lib/appmap/config/package_dir.rb +52 -0
- data/lib/appmap/config/path.rb +25 -0
- data/lib/appmap/config.rb +65 -0
- data/lib/appmap/feature.rb +262 -0
- data/lib/appmap/inspect/inspector.rb +99 -0
- data/lib/appmap/inspect/parse_node.rb +170 -0
- data/lib/appmap/inspect/parser.rb +15 -0
- data/lib/appmap/inspect.rb +91 -0
- data/lib/appmap/middleware/remote_recording.rb +122 -0
- data/lib/appmap/parser.rb +60 -0
- data/lib/appmap/rails/action_handler.rb +77 -0
- data/lib/appmap/rails/sql_handler.rb +148 -0
- data/lib/appmap/railtie.rb +32 -0
- data/lib/appmap/rspec/parse_node.rb +41 -0
- data/lib/appmap/rspec/parser.rb +15 -0
- data/lib/appmap/rspec.rb +288 -0
- data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +65 -0
- data/lib/appmap/trace/tracer.rb +347 -0
- data/lib/appmap/version.rb +5 -0
- data/lib/appmap.rb +26 -0
- data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
- data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
- data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
- data/lore/pages/layout.pug +66 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
- data/lore/public/stylesheets/style.css +8 -0
- data/package-lock.json +1066 -0
- data/package.json +24 -0
- data/spec/abstract_controller4_base_spec.rb +58 -0
- data/spec/abstract_controller_base_spec.rb +59 -0
- data/spec/fixtures/rack_users_app/.dockerignore +2 -0
- data/spec/fixtures/rack_users_app/.gitignore +2 -0
- data/spec/fixtures/rack_users_app/Dockerfile +32 -0
- data/spec/fixtures/rack_users_app/Gemfile +10 -0
- data/spec/fixtures/rack_users_app/appmap.yml +3 -0
- data/spec/fixtures/rack_users_app/config.ru +2 -0
- data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
- data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
- data/spec/fixtures/rails4_users_app/.gitignore +13 -0
- data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
- data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
- data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails4_users_app/Gemfile +77 -0
- data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
- data/spec/fixtures/rails4_users_app/Rakefile +6 -0
- data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
- data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
- data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
- data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails4_users_app/bin/rails +9 -0
- data/spec/fixtures/rails4_users_app/bin/setup +29 -0
- data/spec/fixtures/rails4_users_app/bin/spring +17 -0
- data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
- data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/database.yml +17 -0
- data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
- data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
- data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
- data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
- data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
- data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
- data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
- data/spec/fixtures/rails4_users_app/config.ru +4 -0
- data/spec/fixtures/rails4_users_app/create_app +23 -0
- data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
- data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
- data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
- data/spec/fixtures/rails4_users_app/docker-compose.yml +24 -0
- data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
- data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails4_users_app/log/.keep +0 -0
- data/spec/fixtures/rails4_users_app/public/404.html +67 -0
- data/spec/fixtures/rails4_users_app/public/422.html +67 -0
- data/spec/fixtures/rails4_users_app/public/500.html +66 -0
- data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
- data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
- data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
- data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
- data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
- data/spec/fixtures/rails_users_app/.dockerignore +1 -0
- data/spec/fixtures/rails_users_app/.gitignore +39 -0
- data/spec/fixtures/rails_users_app/.rspec +1 -0
- data/spec/fixtures/rails_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails_users_app/Dockerfile +29 -0
- data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails_users_app/Gemfile +51 -0
- data/spec/fixtures/rails_users_app/Rakefile +6 -0
- data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
- data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
- data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
- data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails_users_app/bin/_appmap-record-self +29 -0
- data/spec/fixtures/rails_users_app/bin/appmap +29 -0
- data/spec/fixtures/rails_users_app/bin/byebug +29 -0
- data/spec/fixtures/rails_users_app/bin/gli +29 -0
- data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
- data/spec/fixtures/rails_users_app/bin/rackup +29 -0
- data/spec/fixtures/rails_users_app/bin/rails +4 -0
- data/spec/fixtures/rails_users_app/bin/rake +29 -0
- data/spec/fixtures/rails_users_app/bin/rspec +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
- data/spec/fixtures/rails_users_app/bin/sequel +29 -0
- data/spec/fixtures/rails_users_app/bin/setup +25 -0
- data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
- data/spec/fixtures/rails_users_app/bin/thor +29 -0
- data/spec/fixtures/rails_users_app/bin/update +25 -0
- data/spec/fixtures/rails_users_app/config/application.rb +51 -0
- data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
- data/spec/fixtures/rails_users_app/config/database.yml +17 -0
- data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
- data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
- data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
- data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
- data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
- data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
- data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
- data/spec/fixtures/rails_users_app/config.ru +5 -0
- data/spec/fixtures/rails_users_app/create_app +11 -0
- data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
- data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
- data/spec/fixtures/rails_users_app/docker-compose.yml +24 -0
- data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails_users_app/log/.keep +0 -0
- data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
- data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
- data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
- data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
- data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
- data/spec/rack_handler_webrick_spec.rb +59 -0
- data/spec/rails_spec_helper.rb +34 -0
- data/spec/railtie_spec.rb +35 -0
- data/spec/record_sql_rails4_pg_spec.rb +76 -0
- data/spec/record_sql_rails_pg_spec.rb +68 -0
- data/spec/rspec_feature_metadata_spec.rb +30 -0
- data/spec/spec_helper.rb +6 -0
- data/test/cli_test.rb +81 -0
- data/test/config_test.rb +149 -0
- data/test/explict_inspect_test.rb +29 -0
- data/test/fixtures/active_record_like/active_record/aggregations.rb +4 -0
- data/test/fixtures/active_record_like/active_record/association.rb +4 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +6 -0
- data/test/fixtures/active_record_like/active_record/caps/caps.rb +4 -0
- data/test/fixtures/active_record_like/active_record.rb +2 -0
- data/test/fixtures/cli_record_test/appmap.yml +2 -0
- data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +7 -0
- data/test/fixtures/ignore_non_ruby_file/class.rb +3 -0
- data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +1 -0
- data/test/fixtures/includes_excludes/lib/a/a_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/a_2.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +8 -0
- data/test/fixtures/includes_excludes/lib/b/b_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/root_1.rb +4 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a.rb +2 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b.rb +2 -0
- data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +6 -0
- data/test/fixtures/parse_file/defs_static_function.rb +96 -0
- data/test/fixtures/parse_file/function_within_class.rb +36 -0
- data/test/fixtures/parse_file/include_public_methods.rb +127 -0
- data/test/fixtures/parse_file/instance_function.rb +17 -0
- data/test/fixtures/parse_file/modules.rb +71 -0
- data/test/fixtures/parse_file/sclass_static_function.rb +88 -0
- data/test/fixtures/parse_file/toplevel_class.rb +13 -0
- data/test/fixtures/parse_file/toplevel_function.rb +14 -0
- data/test/fixtures/rspec_recorder/Gemfile +5 -0
- data/test/fixtures/rspec_recorder/appmap.yml +3 -0
- data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
- data/test/fixtures/rspec_recorder/spec/hello_spec.rb +9 -0
- data/test/fixtures/trace_test/trace_program_1.rb +44 -0
- data/test/implicit_inspect_test.rb +33 -0
- data/test/include_exclude_test.rb +48 -0
- data/test/prerecorded_trace_test.rb +76 -0
- data/test/rspec_test.rb +22 -0
- data/test/test_helper.rb +46 -0
- data/test/trace_test.rb +92 -0
- metadata +501 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib')
|
|
6
|
+
|
|
7
|
+
require 'appmap'
|
|
8
|
+
require 'appmap/feature'
|
|
9
|
+
require 'shellwords'
|
|
10
|
+
|
|
11
|
+
def usage
|
|
12
|
+
warn 'Usage: trace-self <trace-file>'
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
trace_file = ARGV.shift || usage
|
|
17
|
+
usage unless ARGV.empty?
|
|
18
|
+
|
|
19
|
+
replay_events = File.read(trace_file)
|
|
20
|
+
.split("\n")
|
|
21
|
+
.map(&:strip)
|
|
22
|
+
.reject(&:empty?)
|
|
23
|
+
.map(&JSON.method(:parse))
|
|
24
|
+
.map { |te| te['event'] = te['event'].intern; te }
|
|
25
|
+
.map { |te| OpenStruct.new(te) }
|
|
26
|
+
|
|
27
|
+
require 'appmap/trace/tracer'
|
|
28
|
+
|
|
29
|
+
def method_call_from_event(evt)
|
|
30
|
+
AppMap::Trace::MethodCall.new(evt.id, evt.event.intern, evt.defined_class, evt.method_id, evt.path, evt.lineno, evt.static, evt.thread_id, evt.variables)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# _parent_id and _elapsed are ignored since they are already specified in the
|
|
34
|
+
# data being replayed.
|
|
35
|
+
def method_return_from_event(evt, _parent_id, _elapsed)
|
|
36
|
+
AppMap::Trace::MethodReturn.new(evt.id, evt.event.intern, evt.defined_class, evt.method_id, evt.path, evt.lineno, evt.static, evt.thread_id, evt.variables).tap do |mr|
|
|
37
|
+
mr.parent_id = evt.parent_id
|
|
38
|
+
mr.elapsed = evt.elapsed
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
tracer = AppMap::Trace.tracer
|
|
43
|
+
handler = AppMap::Trace::TracePointHandler.new(tracer)
|
|
44
|
+
handler.call_constructor = method(:method_call_from_event)
|
|
45
|
+
handler.return_constructor = method(:method_return_from_event)
|
|
46
|
+
|
|
47
|
+
replay_events.each do |evt|
|
|
48
|
+
handler.handle evt
|
|
49
|
+
end
|
data/exe/appmap
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'gli'
|
|
5
|
+
|
|
6
|
+
require 'appmap'
|
|
7
|
+
require 'appmap/version'
|
|
8
|
+
|
|
9
|
+
# AppMap CLI.
|
|
10
|
+
module AppMap
|
|
11
|
+
class App
|
|
12
|
+
extend GLI::App
|
|
13
|
+
|
|
14
|
+
program_desc 'AppMap client'
|
|
15
|
+
|
|
16
|
+
version AppMap::VERSION
|
|
17
|
+
|
|
18
|
+
subcommand_option_handling :normal
|
|
19
|
+
arguments :strict
|
|
20
|
+
preserve_argv true
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def default_appmap_file
|
|
26
|
+
ENV['APPMAP_FILE'] || 'appmap.json'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def output_file_flag(c, default_value: nil)
|
|
30
|
+
c.desc 'Name of the output file'
|
|
31
|
+
c.long_desc <<~DESC
|
|
32
|
+
Use a single dash '-' for stdout.
|
|
33
|
+
DESC
|
|
34
|
+
c.default_value default_value if default_value
|
|
35
|
+
c.arg_name 'filename'
|
|
36
|
+
c.flag %i[o output]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc 'AppMap configuration file name'
|
|
41
|
+
default_value ENV['APPMAP_CONFIG'] || 'appmap.yml'
|
|
42
|
+
arg_name 'filename'
|
|
43
|
+
flag %i[c config]
|
|
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
|
+
features = AppMap::Command::Inspect.new(@config).perform
|
|
52
|
+
@output_file.write JSON.pretty_generate(features)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
desc 'Record the execution of a program and generate an AppMap.'
|
|
57
|
+
arg_name 'program'
|
|
58
|
+
command :record do |c|
|
|
59
|
+
output_file_flag(c, default_value: default_appmap_file)
|
|
60
|
+
|
|
61
|
+
c.action do |_, _, args|
|
|
62
|
+
# My subcommand name
|
|
63
|
+
ARGV.shift
|
|
64
|
+
|
|
65
|
+
# Consume the :output option, if provided
|
|
66
|
+
if %w[-o --output].find { |arg_name| ARGV[0] == arg_name.to_s }
|
|
67
|
+
ARGV.shift
|
|
68
|
+
ARGV.shift
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Name of the program to execute. GLI will ensure that it's present.
|
|
72
|
+
program = args.shift or help_now!("'program' argument is required")
|
|
73
|
+
|
|
74
|
+
# Also pop the program name from ARGV, because the command will use raw ARGV
|
|
75
|
+
# to load the extra arguments into this Ruby process.
|
|
76
|
+
ARGV.shift
|
|
77
|
+
|
|
78
|
+
require 'appmap/command/record'
|
|
79
|
+
AppMap::Command::Record.new(@config, program).perform do |features, events|
|
|
80
|
+
@output_file.write JSON.generate(classMap: features,
|
|
81
|
+
metadata: AppMap::Command::Record.detect_metadata,
|
|
82
|
+
events: events)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc 'Upload a scenario file to AppLand.'
|
|
88
|
+
arg_name 'filename'
|
|
89
|
+
command :upload do |c|
|
|
90
|
+
output_file_flag(c, default_value: '-')
|
|
91
|
+
|
|
92
|
+
c.desc 'Whether to open the new scenario in the browser'
|
|
93
|
+
c.default_value true
|
|
94
|
+
c.switch [:open]
|
|
95
|
+
|
|
96
|
+
c.desc 'AppLand website URL'
|
|
97
|
+
c.default_value ENV['APPLAND_URL'] || 'https://appland-staging.herokuapp.com'
|
|
98
|
+
c.flag :url
|
|
99
|
+
|
|
100
|
+
# TODO: This will be replaced with proper login
|
|
101
|
+
c.desc 'User id to own the scenario'
|
|
102
|
+
c.default_value(ENV['APPLAND_USER'])
|
|
103
|
+
c.flag :user
|
|
104
|
+
|
|
105
|
+
c.desc 'Organization id to own the scenario'
|
|
106
|
+
c.default_value(ENV['APPLAND_ORG'])
|
|
107
|
+
c.flag :org
|
|
108
|
+
|
|
109
|
+
c.action do |_, options, args|
|
|
110
|
+
require 'appmap/command/upload'
|
|
111
|
+
|
|
112
|
+
filenames = args
|
|
113
|
+
help_now!("'filename' argument is required") if filenames.empty?
|
|
114
|
+
|
|
115
|
+
url = options[:url]
|
|
116
|
+
user = options[:user]
|
|
117
|
+
org = options[:org]
|
|
118
|
+
|
|
119
|
+
batch_id = nil
|
|
120
|
+
uuids = filenames.map do |filename|
|
|
121
|
+
appmap = JSON.parse(File.read(filename))
|
|
122
|
+
|
|
123
|
+
warn "Uploading #{filename.inspect}"
|
|
124
|
+
upload = AppMap::Command::Upload.new(@config, appmap, url, user, org)
|
|
125
|
+
upload.batch_id = batch_id if batch_id
|
|
126
|
+
upload.perform.tap do |response|
|
|
127
|
+
batch_id ||= response.batch_id
|
|
128
|
+
@output_file.puts "Scenario Id: #{response.scenario_uuid}"
|
|
129
|
+
end.scenario_uuid
|
|
130
|
+
end
|
|
131
|
+
@output_file.puts "Batch Id: #{batch_id}"
|
|
132
|
+
|
|
133
|
+
if options[:open] && STDIN.tty?
|
|
134
|
+
if uuids.length == 1
|
|
135
|
+
system "open #{url}/scenarios/#{uuids.first}"
|
|
136
|
+
else
|
|
137
|
+
system "open #{url}/scenario_batches/#{batch_id}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
pre do |global, _, options, _|
|
|
144
|
+
@config = interpret_config_option(global[:config])
|
|
145
|
+
@output_file = interpret_output_file_option(options[:output])
|
|
146
|
+
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class << self
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
def interpret_config_option(fname)
|
|
154
|
+
require 'appmap/config'
|
|
155
|
+
AppMap::Config.load_from_file fname
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def interpret_output_file_option(file_name)
|
|
159
|
+
Hash.new { |_, fname| -> { File.new(fname, 'w') } }.tap do |open_output_file|
|
|
160
|
+
open_output_file[nil] = -> { nil }
|
|
161
|
+
open_output_file['-'] = -> { $stdout }
|
|
162
|
+
end[file_name].call
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
exit AppMap::App.run(ARGV)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module AppMap
|
|
2
|
+
module Algorithm
|
|
3
|
+
# Prune a class map so that only functions, classes and packages which are referenced
|
|
4
|
+
# by some event are retained.
|
|
5
|
+
class PruneClassMap
|
|
6
|
+
attr_reader :class_map
|
|
7
|
+
# Set this attribute to a function which will log algorithm events.
|
|
8
|
+
attr_writer :logger
|
|
9
|
+
attr_accessor :events
|
|
10
|
+
|
|
11
|
+
# Construct the algorithm, with a class map that will be pruned in place.
|
|
12
|
+
def initialize(class_map)
|
|
13
|
+
@class_map = class_map
|
|
14
|
+
@logger = ->(msg) {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def prune
|
|
18
|
+
# This proc counts the number of objects in the class map whose type is 'k'
|
|
19
|
+
count = proc do |k, e|
|
|
20
|
+
n = 0
|
|
21
|
+
n += 1 if e['type'] == k
|
|
22
|
+
n += (e['children'] || []).map { |child| count.call(k, child) }.reduce(0, :+)
|
|
23
|
+
n
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@logger.call "Full classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
|
|
27
|
+
|
|
28
|
+
# Prune all the classes which fail a test.
|
|
29
|
+
reject = proc do |list, test|
|
|
30
|
+
list.tap do |_|
|
|
31
|
+
list.each do |item|
|
|
32
|
+
children = item['children']
|
|
33
|
+
next unless children
|
|
34
|
+
|
|
35
|
+
reject.call(children, test)
|
|
36
|
+
end
|
|
37
|
+
list.reject!(&test)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if events
|
|
42
|
+
locations = \
|
|
43
|
+
Set.new(events.select { |e| e['event'] == 'call' }
|
|
44
|
+
.map { |e| [ e['path'], e['lineno'] ].join(':') })
|
|
45
|
+
|
|
46
|
+
# Prune all functions which aren't called
|
|
47
|
+
reject.call class_map,
|
|
48
|
+
->(e) { e['type'] == 'function' && !locations.member?(e['location']) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Prune all empty classes
|
|
52
|
+
reject.call class_map,
|
|
53
|
+
->(e) { e['type'] == 'class' && (e['children'] || []).empty? }
|
|
54
|
+
|
|
55
|
+
# Prune all empty packages
|
|
56
|
+
reject.call class_map,
|
|
57
|
+
->(e) { e['type'] == 'package' && (e['children'] || []).empty? }
|
|
58
|
+
|
|
59
|
+
@logger.call "Pruned classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
|
|
60
|
+
|
|
61
|
+
class_map
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module AppMap
|
|
2
|
+
module Command
|
|
3
|
+
RecordStruct = Struct.new(:config, :program)
|
|
4
|
+
|
|
5
|
+
class Record < RecordStruct
|
|
6
|
+
class << self
|
|
7
|
+
# Builds a Hash of metadata which can be detected by inspecting the system.
|
|
8
|
+
def detect_metadata
|
|
9
|
+
{
|
|
10
|
+
language: {
|
|
11
|
+
name: 'ruby',
|
|
12
|
+
engine: RUBY_ENGINE,
|
|
13
|
+
version: RUBY_VERSION
|
|
14
|
+
}
|
|
15
|
+
}.tap do |m|
|
|
16
|
+
if defined?(::Rails)
|
|
17
|
+
m[:frameworks] ||= []
|
|
18
|
+
m[:frameworks] << {
|
|
19
|
+
name: 'rails',
|
|
20
|
+
version: ::Rails.version
|
|
21
|
+
}
|
|
22
|
+
m[:layout] = 'rails'
|
|
23
|
+
end
|
|
24
|
+
m[:git] = git_metadata if git_available
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def git_available
|
|
31
|
+
@git_available = system('git status 2>&1 > /dev/null') if @git_available.nil?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def git_metadata
|
|
35
|
+
git_repo = `git config --get remote.origin.url`.strip
|
|
36
|
+
git_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
37
|
+
git_sha = `git rev-parse HEAD`.strip
|
|
38
|
+
git_status = `git status -s`.split("\n").map(&:strip)
|
|
39
|
+
git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
|
|
40
|
+
git_last_annotated_tag = nil if git_last_annotated_tag.blank?
|
|
41
|
+
git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
|
|
42
|
+
git_last_tag = nil if git_last_tag.blank?
|
|
43
|
+
git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
|
|
44
|
+
git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
repository: git_repo,
|
|
48
|
+
branch: git_branch,
|
|
49
|
+
commit: git_sha,
|
|
50
|
+
status: git_status,
|
|
51
|
+
git_last_annotated_tag: git_last_annotated_tag,
|
|
52
|
+
git_last_tag: git_last_tag,
|
|
53
|
+
git_commits_since_last_annotated_tag: git_commits_since_last_annotated_tag,
|
|
54
|
+
git_commits_since_last_tag: git_commits_since_last_tag
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def perform(&block)
|
|
60
|
+
features = AppMap.inspect(config)
|
|
61
|
+
functions = features.map(&:collect_functions).flatten
|
|
62
|
+
|
|
63
|
+
require 'appmap/trace/tracer'
|
|
64
|
+
|
|
65
|
+
tracer = AppMap::Trace.tracers.trace(functions)
|
|
66
|
+
|
|
67
|
+
events = []
|
|
68
|
+
quit = false
|
|
69
|
+
event_thread = Thread.new do
|
|
70
|
+
while tracer.event? || !quit
|
|
71
|
+
event = tracer.next_event
|
|
72
|
+
if event
|
|
73
|
+
events << event.to_h
|
|
74
|
+
else
|
|
75
|
+
sleep 0.0001
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
event_thread.abort_on_exception = true
|
|
80
|
+
|
|
81
|
+
at_exit do
|
|
82
|
+
quit = true
|
|
83
|
+
event_thread.join
|
|
84
|
+
yield features, events
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
load program if program
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'faraday'
|
|
3
|
+
|
|
4
|
+
module AppMap
|
|
5
|
+
module Command
|
|
6
|
+
UploadResponse = Struct.new(:batch_id, :scenario_uuid)
|
|
7
|
+
|
|
8
|
+
UploadStruct = Struct.new(:config, :data, :url, :user, :org)
|
|
9
|
+
class Upload < UploadStruct
|
|
10
|
+
MAX_DEPTH = 12
|
|
11
|
+
|
|
12
|
+
attr_accessor :batch_id
|
|
13
|
+
|
|
14
|
+
def initialize(config, data, url, user, org)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
# TODO: Make this an option
|
|
18
|
+
@max_depth = MAX_DEPTH
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def perform
|
|
22
|
+
appmap = data.clone
|
|
23
|
+
|
|
24
|
+
# If it's a list, upload it as a classMap
|
|
25
|
+
if data.is_a?(Hash)
|
|
26
|
+
events = data.delete('events') || []
|
|
27
|
+
class_map = data.delete('classMap') || []
|
|
28
|
+
|
|
29
|
+
pruned_events = []
|
|
30
|
+
stack = []
|
|
31
|
+
events.each do |evt|
|
|
32
|
+
if evt['event'] == 'call'
|
|
33
|
+
stack << evt
|
|
34
|
+
stack_depth = stack.length
|
|
35
|
+
else
|
|
36
|
+
stack_depth = stack.length
|
|
37
|
+
stack.pop
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
prune = stack_depth > @max_depth
|
|
41
|
+
|
|
42
|
+
pruned_events << evt unless prune
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
warn "Pruned events to #{pruned_events.length}" if events.length > pruned_events.length
|
|
46
|
+
|
|
47
|
+
events = pruned_events
|
|
48
|
+
|
|
49
|
+
class_map = prune(class_map, events: events)
|
|
50
|
+
appmap[:classMap] = class_map
|
|
51
|
+
appmap[:events] = events
|
|
52
|
+
else
|
|
53
|
+
class_map = prune(data)
|
|
54
|
+
appmap = { "classMap": class_map, "events": [] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
upload_file = { user: user, org: org, data: appmap }.compact
|
|
58
|
+
|
|
59
|
+
conn = Faraday.new(url: url)
|
|
60
|
+
response = conn.post do |req|
|
|
61
|
+
req.url '/api/scenarios'
|
|
62
|
+
req.headers['Content-Type'] = 'application/json'
|
|
63
|
+
req.headers[AppMap::BATCH_HEADER_NAME] = @batch_id if @batch_id
|
|
64
|
+
req.body = JSON.generate(upload_file)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
unless response.body.blank?
|
|
68
|
+
message = begin
|
|
69
|
+
JSON.parse(response.body)
|
|
70
|
+
rescue JSON::ParserError => e
|
|
71
|
+
warn "Response is not valid JSON (#{e.message})"
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless response.success?
|
|
77
|
+
error = [ 'Upload failed' ]
|
|
78
|
+
error << ": #{message}" if message
|
|
79
|
+
raise error.join
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
batch_id = @batch_id || response.headers[AppMap::BATCH_HEADER_NAME]
|
|
83
|
+
|
|
84
|
+
uuid = message['uuid']
|
|
85
|
+
UploadResponse.new(batch_id, uuid)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
protected
|
|
89
|
+
|
|
90
|
+
def debug?
|
|
91
|
+
ENV['DEBUG'] == 'true' || ENV['GLI_DEBUG'] == 'true'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def prune(class_map, events: nil)
|
|
95
|
+
require 'appmap/algorithm/prune_class_map'
|
|
96
|
+
Algorithm::PruneClassMap.new(class_map).tap do |alg|
|
|
97
|
+
alg.events = events if events
|
|
98
|
+
alg.logger = ->(msg) { warn msg } if debug?
|
|
99
|
+
end.prune
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module AppMap
|
|
2
|
+
module Config
|
|
3
|
+
# A normal directory is scanned for AppMap features without interpreting the
|
|
4
|
+
# directory as a 'package'.
|
|
5
|
+
#
|
|
6
|
+
# @appmap
|
|
7
|
+
class Directory < Path
|
|
8
|
+
# @appmap
|
|
9
|
+
def initialize(path)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @appmap
|
|
14
|
+
def children
|
|
15
|
+
child_files.sort + child_directories.sort
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def ruby_file?(path)
|
|
21
|
+
::File.file?(path) && (path =~ /\.rb$/ || ruby_shebang?(path))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ruby_shebang?(path)
|
|
25
|
+
lines = begin
|
|
26
|
+
::File.read(path).split("\n")
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
if e.message.index 'invalid byte sequence'
|
|
29
|
+
warn "Unable to load file #{path.inspect} : #{e.message}"
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
lines[0] && lines[0].index('#!/usr/bin/env ruby') == 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def child_files
|
|
38
|
+
expand_path = ->(fname) { ::File.join(path, fname) }
|
|
39
|
+
Dir.new(path).entries.select do |fname|
|
|
40
|
+
::File.file?(expand_path.call(fname)) &&
|
|
41
|
+
!::File.symlink?(expand_path.call(fname)) &&
|
|
42
|
+
ruby_file?(expand_path.call(fname))
|
|
43
|
+
end.select do |fname|
|
|
44
|
+
!exclude?(::File.join(path, fname))
|
|
45
|
+
end.map do |fname|
|
|
46
|
+
File.new(expand_path.call(fname)).tap do |f|
|
|
47
|
+
f.mode = mode
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def child_directories
|
|
53
|
+
File.new(path).entries.select do |fname|
|
|
54
|
+
!%w[. ..].include?(fname) && !::File.directory?(fname)
|
|
55
|
+
end.select do |dir|
|
|
56
|
+
!exclude?(::File.join(path, dir))
|
|
57
|
+
end.map do |dir|
|
|
58
|
+
PackageDir.new(dir, [module_name, dir].join('/')).tap do |m|
|
|
59
|
+
m.mode = mode
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module AppMap
|
|
2
|
+
module Config
|
|
3
|
+
NamedFunctionStruct = Struct.new(:id, :gem_name, :file_path, :class_names, :method_name, :static)
|
|
4
|
+
|
|
5
|
+
# Identifies a specific function within a Gem to be instrumented.
|
|
6
|
+
#
|
|
7
|
+
# * `id` A unique identifier for the named function. This is used to associate custom logic with the
|
|
8
|
+
# named function when the trace events are being handled.
|
|
9
|
+
# * `gem_name` Name of the Gem.
|
|
10
|
+
# * `file_path` Name of the file within the Gem in which the function is located.
|
|
11
|
+
# * `class_names` Array of the module/class name scope which contains the function. For example,
|
|
12
|
+
# `%w[Rack Handler WEBrick]`.
|
|
13
|
+
# * `method_name` Name of the method within the class name scope.
|
|
14
|
+
# * `static` Whether it's a static or instance method.
|
|
15
|
+
class NamedFunction < NamedFunctionStruct
|
|
16
|
+
def children
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module AppMap
|
|
4
|
+
module Config
|
|
5
|
+
# Scan a directory for AppMap features, treating it as a package and its
|
|
6
|
+
# sub-folders as sub-packages.
|
|
7
|
+
#
|
|
8
|
+
# @appmap
|
|
9
|
+
class PackageDir < Directory
|
|
10
|
+
attr_accessor :package_name, :base_path, :exclude
|
|
11
|
+
|
|
12
|
+
# @appmap
|
|
13
|
+
def initialize(path, package_name = Pathname.new(path || '').basename.to_s)
|
|
14
|
+
super(path)
|
|
15
|
+
|
|
16
|
+
@package_name = package_name
|
|
17
|
+
@base_path = path
|
|
18
|
+
@exclude = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sub_package_dir(dir)
|
|
22
|
+
PackageDir.new(::File.join(path, dir), dir).tap do |m|
|
|
23
|
+
m.base_path = base_path
|
|
24
|
+
m.exclude = exclude
|
|
25
|
+
m.mode = mode
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def exclude?(path)
|
|
30
|
+
relative_path = path.gsub("#{base_path}/", '')
|
|
31
|
+
exclude.member?(relative_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @appmap
|
|
35
|
+
def children
|
|
36
|
+
child_files.sort + child_packages.sort
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
protected
|
|
40
|
+
|
|
41
|
+
def child_packages
|
|
42
|
+
::Dir.new(path).entries.select do |fname|
|
|
43
|
+
!%w[. ..].include?(fname) && ::File.directory?(::File.join(path, fname))
|
|
44
|
+
end.select do |dir|
|
|
45
|
+
!exclude?(::File.join(path, dir))
|
|
46
|
+
end.map do |dir|
|
|
47
|
+
sub_package_dir(dir)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module AppMap
|
|
2
|
+
module Config
|
|
3
|
+
PathStruct = Struct.new(:path)
|
|
4
|
+
|
|
5
|
+
# Path is an abstract configuration of a file, directory, or package.
|
|
6
|
+
class Path < PathStruct
|
|
7
|
+
attr_accessor :mode
|
|
8
|
+
|
|
9
|
+
def initialize(path)
|
|
10
|
+
super(path)
|
|
11
|
+
|
|
12
|
+
@mode = :implicit
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def <=>(other)
|
|
16
|
+
path <=> other.path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Automatically determined configurations of child file/package paths.
|
|
20
|
+
def children
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|