Skip to content

Commit

Permalink
Merge pull request #20 from alpaca-tc/refactor-options
Browse files Browse the repository at this point in the history
Refactor options
  • Loading branch information
alpaca-tc authored Apr 24, 2024
2 parents 0c3ed94 + 988051c commit 57ef7ff
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 43 deletions.
72 changes: 54 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# DiverDown

`divertdown` is a tool that dynamically analyzes application dependencies and creates a dependency map.
This tool was created to analyze Ruby applications for use in large-scale refactoring such as moduler monolith.
`DiverDown` is a tool designed to dynamically analyze application dependencies and generate a comprehensive dependency map. It is particularly useful for analyzing Ruby applications, aiding significantly in large-scale refactoring projects or transitions towards a modular monolith architecture.

The results of the analysis can be viewed the dependency map graph, and and since the file-by-file association can be categorized into specific groups, you can deepen your architectural consideration.
## Features

## Installation
- **Dependency Mapping**: Analyze and generate an application dependencies.
- **Module Categorization**: Organizes file-by-file associations into specific groups, facilitating deeper modular monolith architectural analysis and understanding.

## Getting Started

Add this line to your application's Gemfile:

Expand All @@ -27,7 +29,7 @@ Or install it yourself as:

### `DiverDown::Trace`

Analyzes the processing of ruby code and outputs the analysis results as `DiverDown::Definition`.
The `DiverDown::Trace` module analyzes the execution of Ruby code and outputs the results as `DiverDown::Definition` objects.

```ruby
tracer = DiverDown::Trace::Tracer.new
Expand Down Expand Up @@ -57,8 +59,49 @@ tracer.trace(title: 'title', definition_group: 'group name') do
end
```

The analysis results should be output to a specific directory.
Files saved in `.json` or `.yaml` can be read by `DiverDown::Web`.
**Options**

When analyzing user applications, it is recommended to specify the option.

|name|type|description|example|default|
| --- | --- | --- | --- | --- |
| `module_set` | Hash{ <br> modules: Array<Module, String> \| Set<Module, String> \| nil,<br> paths: Array\<String> \| Set\<String> \| nil <br>}<br>\| DiverDown::Trace::ModuleSet | Specify the class/module to be included in the analysis results.<br><br>If you know the module name:<br>`{ modules: [ModA, ModB] }`<br><br>If you know module path:<br>`{ paths: ['/path/to/rails/app/models/mod_a.rb'] }` | `{ paths: Dir["app/**/*.rb"] }` | `nil`. All class/modul are target. |
| `caller_paths` | `Array<String> \| nil` | Specifies a list of allowed paths as caller paths. By specifying the user application path in this list of paths and excluding paths under the gem, the caller path is identified back to the user application path. | `Dir["app/**/*.rb"]` | `nil`. All paths are target. |
| `filter_method_id_path` | `#call \| nil` | lambda to convert the caller path. | `->(path) { path.remove(Rails.root) }` | `nil`. No conversion. |

**Example**

```ruby
# Your rails application paths
application_paths = [
*Dir['app/**/*.rb'],
*Dir['lib/**/*.rb'],
].map { File.expand_path(_1) }

ignored_application_paths = [
'app/models/application_record.rb',
].map { File.expand_path(_1) }

module_set = DiverDown::Trace::ModuleSet.new(modules: modules - ignored_modules)

filter_method_id_path = ->(path) { path.remove("#{Rails.root}/") }

tracer = DiverDown::Trace::Tracer.new(
caller_paths: application_paths,
module_set: {
paths: (application_paths - ignored_application_paths)
},
filter_method_id_path:
)

definition = tracer.trace do
# do something
end
```

#### Output Results

The analysis results are intended to be saved to a specific directory in either `.json` or `.yaml` format. These files are compatible with `DiverDown::Web`, which can read and display the results.

```ruby
dir = 'tmp/diver_down'
Expand All @@ -71,19 +114,14 @@ File.write(File.join(dir, "#{definition.title}.json"), definition.to_h.to_json)
File.write(File.join(dir, "#{definition.title}.yaml"), definition.to_h.to_yaml)
```

**Options**

TODO

### `DiverDown::Web`

View the analysis results in a browser.

This gem is designed to consider large application with a modular monolithic architecture.
Each file in the analysis can be specified to belong to a module you specify on the browser.
This gem is specifically designed to analyze large applications with a modular monolithic architecture. It allows users to categorize each analyzed file into specified modules directly through the web interface.

- `--definition-dir` specifies the directory where the analysis results are stored.
- `--module-store-path` will store the results specifying which module each file belongs to. If not specified, the specified results are stored in tempfile.
- `--definition-dir` Specifies the directory where the analysis results are stored.
- `--module-store-path` Designates a path to save the results that include details on which module each file belongs to. If this option is not specified, the results will be temporarily stored in a default temporary file.

```sh
bundle exec diver_down_web --definition-dir tmp/diver_down --module-store-path tmp/module_store.yml
Expand All @@ -102,7 +140,7 @@ open http://localhost:8080
- Ruby: `bundle exec rspec`, `bundle exec rubocop`
- TypeScript: `pnpm run test`, `pnpm run lint`

### Development DiverDown::Web
### Development `DiverDown::Web`

If you want to develop `DiverDown::Web` locally, set up a server for development.

Expand All @@ -113,8 +151,6 @@ $ pnpm run dev
# Start server for backend
$ bundle install
# DIVER_DOWN_DIR specifies the directory where the analysis results are stored.
# DIVER_DOWN_MODULE_STORE specifies a yaml file that defines which module the file belongs to, but this file is newly created, so it works even if the file does not exist.
$ DIVER_DOWN_DIR=/path/to/definitions_dir DIVER_DOWN_MODULE_STORE=/path/to/module_store.yml bundle exec puma
```

Expand Down
4 changes: 2 additions & 2 deletions diver_down.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
spec.authors = ['alpaca-tc']
spec.email = ['[email protected]']

spec.summary = 'Tool to dynamically analyze applications and create dependency maps'
spec.description = ''
spec.summary = 'dynamically analyze application dependencies and generate a comprehensive dependency map'
spec.description = 'DiverDown is a tool designed to dynamically analyze application dependencies and generate a comprehensive dependency map. It is particularly useful for analyzing Ruby applications, aiding significantly in large-scale refactoring projects or transitions towards a modular monolith architecture.'
spec.homepage = 'https://github.com/alpaca-tc/diver_down'
spec.license = 'MIT'
spec.required_ruby_version = '>= 3.2.0'
Expand Down
12 changes: 6 additions & 6 deletions lib/diver_down/trace/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class Session
# @param [DiverDown::Trace::IgnoredMethodIds, nil] ignored_method_ids
# @param [Set<String>, nil] List of paths to finish traversing when searching for a caller. If nil, all paths are finished.
# @param [#call, nil] filter_method_id_path
def initialize(module_set: DiverDown::Trace::ModuleSet.new, ignored_method_ids: nil, target_file_set: nil, filter_method_id_path: nil, definition: DiverDown::Definition.new)
def initialize(module_set: DiverDown::Trace::ModuleSet.new, ignored_method_ids: nil, caller_paths: nil, filter_method_id_path: nil, definition: DiverDown::Definition.new)
@module_set = module_set
@ignored_method_ids = ignored_method_ids
@target_file_set = target_file_set
@caller_paths = caller_paths
@filter_method_id_path = filter_method_id_path
@definition = definition
@trace_point = build_trace_point
Expand Down Expand Up @@ -99,7 +99,7 @@ def build_trace_point

caller_location = find_neast_caller_location(maximum_back_stack_size)

# `caller_location` is nil if it is filtered by target_files
# `caller_location` is nil if it is filtered by caller_paths
if caller_location
pushed = true
source = @definition.find_or_build_source(source_name)
Expand Down Expand Up @@ -130,12 +130,12 @@ def find_neast_caller_location(stack_size)
finished_frame_size = excluded_frame_size + stack_size + 1
frame_pos = 1

# If @target_file_set is nil, return the caller location.
return caller_locations(excluded_frame_size + 1, excluded_frame_size + 1)[0] if @target_file_set.nil?
# If @caller_paths is nil, return the caller location.
return caller_locations(excluded_frame_size + 1, excluded_frame_size + 1)[0] if @caller_paths.nil?

Thread.each_caller_location do
break if finished_frame_size < frame_pos
return _1 if @target_file_set.include?(_1.path)
return _1 if @caller_paths.include?(_1.path)

frame_pos += 1
end
Expand Down
12 changes: 6 additions & 6 deletions lib/diver_down/trace/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ class << self
end

# @param module_set [DiverDown::Trace::ModuleSet, Array<Module, String>]
# @param target_files [Array<String>, nil] if nil, trace all files
# @param caller_paths [Array<String>, nil] if nil, trace all files
# @param ignored_method_ids [Array<String>]
# @param filter_method_id_path [#call, nil] filter method_id.path
# @param module_set [DiverDown::Trace::ModuleSet, nil] for optimization
def initialize(module_set: {}, target_files: nil, ignored_method_ids: nil, filter_method_id_path: nil)
if target_files && !target_files.all? { Pathname.new(_1).absolute? }
raise ArgumentError, "target_files must be absolute path(#{target_files})"
def initialize(module_set: {}, caller_paths: nil, ignored_method_ids: nil, filter_method_id_path: nil)
if caller_paths && !caller_paths.all? { Pathname.new(_1).absolute? }
raise ArgumentError, "caller_paths must be absolute path(#{caller_paths})"
end

@module_set = if module_set.is_a?(DiverDown::Trace::ModuleSet)
Expand All @@ -46,7 +46,7 @@ def initialize(module_set: {}, target_files: nil, ignored_method_ids: nil, filte
DiverDown::Trace::IgnoredMethodIds.new(ignored_method_ids)
end

@target_file_set = target_files&.to_set
@caller_paths = caller_paths&.to_set
@filter_method_id_path = filter_method_id_path
end

Expand Down Expand Up @@ -77,7 +77,7 @@ def new_session(title: SecureRandom.uuid, definition_group: nil)
DiverDown::Trace::Session.new(
module_set: @module_set,
ignored_method_ids: @ignored_method_ids,
target_file_set: @target_file_set,
caller_paths: @caller_paths,
filter_method_id_path: @filter_method_id_path,
definition: DiverDown::Definition.new(
title:,
Expand Down
22 changes: 11 additions & 11 deletions spec/diver_down/trace/tracer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ def fill_default(hash)
end

describe '#initialize' do
describe 'with relative path target_files' do
describe 'with relative path caller_paths' do
it 'raises ArgumentError' do
expect {
described_class.new(
target_files: ['relative/path']
caller_paths: ['relative/path']
)
}.to raise_error(ArgumentError, /target_files must be absolute path/)
}.to raise_error(ArgumentError, /caller_paths must be absolute path/)
end
end

Expand Down Expand Up @@ -104,15 +104,15 @@ def self.call
describe 'when tracing script' do
# @param path [String]
# @return [DiverDown::Definition]
def trace_fixture(path, module_set: {}, target_files: nil, ignored_method_ids: [], filter_method_id_path: nil, definition_group: nil)
def trace_fixture(path, module_set: {}, caller_paths: nil, ignored_method_ids: [], filter_method_id_path: nil, definition_group: nil)
# NOTE: Script need to define .run method
script = fixture_path(path)
load script, AntipollutionModule
antipollution_environment = AntipollutionKlass.allocate

tracer = described_class.new(
module_set:,
target_files:,
caller_paths:,
ignored_method_ids:,
filter_method_id_path:
)
Expand Down Expand Up @@ -242,7 +242,7 @@ def trace_fixture(path, module_set: {}, target_files: nil, ignored_method_ids: [
))
end

it 'traces tracer_module.rb with target_files' do
it 'traces tracer_module.rb with caller_paths' do
definition = trace_fixture(
'tracer_module.rb',
module_set: {
Expand All @@ -252,7 +252,7 @@ def trace_fixture(path, module_set: {}, target_files: nil, ignored_method_ids: [
'AntipollutionModule::C',
],
},
target_files: []
caller_paths: []
)

expect(definition.to_h).to match(fill_default(
Expand Down Expand Up @@ -542,7 +542,7 @@ def trace_fixture(path, module_set: {}, target_files: nil, ignored_method_ids: [
'::C',
],
},
target_files: [
caller_paths: [
fixture_path('tracer_separated_file.rb'),
]
)
Expand Down Expand Up @@ -588,7 +588,7 @@ def trace_fixture(path, module_set: {}, target_files: nil, ignored_method_ids: [
'AntipollutionModule::D',
],
},
target_files: [
caller_paths: [
fixture_path('tracer_ignored_call_stack.rb'),
]
)
Expand Down Expand Up @@ -691,7 +691,7 @@ def self.call
module_set: {
modules: [A, B, C],
},
target_files: [File.join(dir, 'a.rb'), File.join(dir, 'c.rb')]
caller_paths: [File.join(dir, 'a.rb'), File.join(dir, 'c.rb')]
)

definition = A.call do
Expand Down Expand Up @@ -739,7 +739,7 @@ def self.call
module_set: {
modules: [A, B],
},
target_files: [File.join(dir, 'b.rb')]
caller_paths: [File.join(dir, 'b.rb')]
)

definition = tracer.trace(title: 'title') do
Expand Down

0 comments on commit 57ef7ff

Please sign in to comment.