deps_grapher 1.0.0
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/CHANGELOG.md +3 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +327 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/deps_grapher +96 -0
- data/bin/setup +8 -0
- data/deps_grapher.gemspec +34 -0
- data/lefthook.yml +14 -0
- data/lib/deps_grapher/ast_processor.rb +211 -0
- data/lib/deps_grapher/ast_processor_policy.rb +45 -0
- data/lib/deps_grapher/cache_file.rb +48 -0
- data/lib/deps_grapher/cli.rb +29 -0
- data/lib/deps_grapher/command/analyzer.rb +89 -0
- data/lib/deps_grapher/command/init.rb +49 -0
- data/lib/deps_grapher/configuration.rb +99 -0
- data/lib/deps_grapher/context.rb +48 -0
- data/lib/deps_grapher/cytoscape/cose.rb +36 -0
- data/lib/deps_grapher/cytoscape/fcose.rb +36 -0
- data/lib/deps_grapher/cytoscape/klay.rb +27 -0
- data/lib/deps_grapher/cytoscape/template.erb +61 -0
- data/lib/deps_grapher/cytoscape.rb +88 -0
- data/lib/deps_grapher/dsl.rb +50 -0
- data/lib/deps_grapher/edge.rb +58 -0
- data/lib/deps_grapher/errors.rb +8 -0
- data/lib/deps_grapher/event.rb +63 -0
- data/lib/deps_grapher/graph.rb +71 -0
- data/lib/deps_grapher/graphile/generator.rb +42 -0
- data/lib/deps_grapher/graphile/graphile.erb +119 -0
- data/lib/deps_grapher/graphile/graphile.temp.erb +23 -0
- data/lib/deps_grapher/html_writer.rb +16 -0
- data/lib/deps_grapher/input.rb +40 -0
- data/lib/deps_grapher/layer/registry.rb +32 -0
- data/lib/deps_grapher/layer.rb +75 -0
- data/lib/deps_grapher/logging.rb +21 -0
- data/lib/deps_grapher/matcher.rb +28 -0
- data/lib/deps_grapher/node.rb +65 -0
- data/lib/deps_grapher/plugin_dsl.rb +12 -0
- data/lib/deps_grapher/plugin_loader.rb +29 -0
- data/lib/deps_grapher/source.rb +51 -0
- data/lib/deps_grapher/source_cache/class_name_extractor.rb +60 -0
- data/lib/deps_grapher/source_cache/registry.rb +64 -0
- data/lib/deps_grapher/source_cache.rb +49 -0
- data/lib/deps_grapher/version.rb +5 -0
- data/lib/deps_grapher/vis/box.rb +21 -0
- data/lib/deps_grapher/vis/dot.rb +17 -0
- data/lib/deps_grapher/vis/template.erb +36 -0
- data/lib/deps_grapher/vis.rb +69 -0
- data/lib/deps_grapher/visualizer/base.rb +74 -0
- data/lib/deps_grapher/visualizer/color/registry.rb +30 -0
- data/lib/deps_grapher/visualizer/color.rb +62 -0
- data/lib/deps_grapher/visualizer/command_option.rb +22 -0
- data/lib/deps_grapher/visualizer/downloader.rb +42 -0
- data/lib/deps_grapher/visualizer/js_option.rb +39 -0
- data/lib/deps_grapher/visualizer/registry.rb +37 -0
- data/lib/deps_grapher/visualizer.rb +11 -0
- data/lib/deps_grapher.rb +42 -0
- metadata +135 -0
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "parser/current"
|
4
|
+
require_relative "source_cache"
|
5
|
+
require_relative "node"
|
6
|
+
require_relative "edge"
|
7
|
+
|
8
|
+
module DepsGrapher
|
9
|
+
class AstProcessor < Parser::AST::Processor
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def processed
|
14
|
+
@processed ||= Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def event_processed
|
18
|
+
@event_processed ||= Set.new
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_writer :depth
|
22
|
+
|
23
|
+
def depth
|
24
|
+
@depth ||= 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(file_path, graph, event_processors, advanced_const_resolver, ignore_errors)
|
29
|
+
super()
|
30
|
+
@file_path = file_path
|
31
|
+
@target = File.basename(file_path, ".*").camelize
|
32
|
+
@graph = graph
|
33
|
+
@event_processors = event_processors
|
34
|
+
@advanced_const_resolver = advanced_const_resolver
|
35
|
+
@ignore_errors = ignore_errors
|
36
|
+
end
|
37
|
+
|
38
|
+
def call
|
39
|
+
return if self.class.processed.include?(@file_path)
|
40
|
+
|
41
|
+
log do
|
42
|
+
source_buffer = Parser::Source::Buffer.new(@file_path)
|
43
|
+
parser = Parser::CurrentRuby.new
|
44
|
+
process parser.parse(source_buffer.read)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_module(ast_node)
|
49
|
+
const_node = ast_node.children[0]
|
50
|
+
name = extract_const_name const_node
|
51
|
+
|
52
|
+
@namespace_stack ||= []
|
53
|
+
@namespace_stack.push name
|
54
|
+
|
55
|
+
if name == @target
|
56
|
+
fully_qualified_class_name = @namespace_stack.join "::"
|
57
|
+
@current_node = Node.add fully_qualified_class_name, @file_path
|
58
|
+
self.class.processed << @file_path
|
59
|
+
end
|
60
|
+
|
61
|
+
super
|
62
|
+
|
63
|
+
@namespace_stack.pop
|
64
|
+
end
|
65
|
+
alias on_class on_module
|
66
|
+
|
67
|
+
def on_const(ast_node)
|
68
|
+
const_name = extract_const_name ast_node
|
69
|
+
|
70
|
+
process_recursively! const_name, ast_node do
|
71
|
+
Event.add name: :const_found, const_name: _1, location: _2
|
72
|
+
end
|
73
|
+
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_send(ast_node)
|
78
|
+
if @current_node
|
79
|
+
receiver_node, method_name, = *ast_node
|
80
|
+
|
81
|
+
const_name = call_advanced_const_resolver ast_node
|
82
|
+
const_name ||= extract_const_name receiver_node
|
83
|
+
|
84
|
+
process_recursively! const_name, receiver_node do
|
85
|
+
Event.add name: :method_found, const_name: _1, location: _2, method: method_name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def call_event_processor(event)
|
95
|
+
return if event.blank? || @event_processors.blank?
|
96
|
+
return if self.class.event_processed.include?(event.key)
|
97
|
+
|
98
|
+
self.class.event_processed << event.key
|
99
|
+
|
100
|
+
@event_processors.each do |matcher, (prop, event_processor)|
|
101
|
+
if matcher.is_a?(Regexp)
|
102
|
+
next unless matcher.match? event.send(prop)
|
103
|
+
else
|
104
|
+
next unless matcher == event.send(prop)
|
105
|
+
end
|
106
|
+
|
107
|
+
event_processor.call event
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def call_advanced_const_resolver(ast_node)
|
112
|
+
return nil unless @advanced_const_resolver.respond_to?(:call)
|
113
|
+
|
114
|
+
@advanced_const_resolver.call ast_node
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_recursively!(const_name, ast_node)
|
118
|
+
return unless @current_node
|
119
|
+
|
120
|
+
begin
|
121
|
+
const_name, location = if ast_node
|
122
|
+
guess_source_location(const_name, ast_node)
|
123
|
+
else
|
124
|
+
find_source_location!(const_name)
|
125
|
+
end
|
126
|
+
rescue SourceLocationNotFound => e
|
127
|
+
raise e unless @ignore_errors
|
128
|
+
end
|
129
|
+
|
130
|
+
return unless const_name && location
|
131
|
+
|
132
|
+
event = yield const_name, location
|
133
|
+
call_event_processor event
|
134
|
+
|
135
|
+
return unless event&.processing?
|
136
|
+
|
137
|
+
node = Node.add const_name, location
|
138
|
+
edge = Edge.add @current_node, node
|
139
|
+
|
140
|
+
return unless edge
|
141
|
+
|
142
|
+
@graph.add_edge edge
|
143
|
+
|
144
|
+
self.class.new(
|
145
|
+
node.location,
|
146
|
+
@graph,
|
147
|
+
@event_processors,
|
148
|
+
@advanced_const_resolver,
|
149
|
+
@ignore_errors
|
150
|
+
).call
|
151
|
+
end
|
152
|
+
|
153
|
+
def extract_const_name(ast_node)
|
154
|
+
return nil if ast_node.nil?
|
155
|
+
|
156
|
+
return nil unless ast_node.type == :const
|
157
|
+
|
158
|
+
base, name = *ast_node
|
159
|
+
if base.nil?
|
160
|
+
name.to_s
|
161
|
+
else
|
162
|
+
[extract_const_name(base), name].compact.join "::"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def guess_namespace(file_path)
|
167
|
+
const_name = SourceCache::Registry.fetch(file_path)
|
168
|
+
namespace = const_name.split("::")
|
169
|
+
parts = namespace.last
|
170
|
+
while parts.present?
|
171
|
+
return if yield(namespace)
|
172
|
+
|
173
|
+
parts = namespace.pop
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def guess_source_location(const_name, ast_node)
|
178
|
+
return if const_name.blank?
|
179
|
+
|
180
|
+
find_source_location! const_name
|
181
|
+
rescue SourceLocationNotFound
|
182
|
+
guess_namespace(ast_node.location.name.source_buffer.name) do |ns|
|
183
|
+
return find_source_location! [*ns, const_name].compact.join("::")
|
184
|
+
rescue SourceLocationNotFound
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def find_source_location!(const_name)
|
190
|
+
return if const_name.blank?
|
191
|
+
|
192
|
+
location = SourceCache::Registry.fetch const_name
|
193
|
+
[const_name, location]
|
194
|
+
rescue SourceCacheNotFound
|
195
|
+
raise SourceLocationNotFound, "source location not found for #{const_name}"
|
196
|
+
end
|
197
|
+
|
198
|
+
def log
|
199
|
+
verbose do
|
200
|
+
indent = "*" * self.class.depth
|
201
|
+
indent << " " if indent.present?
|
202
|
+
"Analyzing #{indent}#{@file_path}"
|
203
|
+
end
|
204
|
+
|
205
|
+
self.class.depth += 1
|
206
|
+
yield
|
207
|
+
ensure
|
208
|
+
self.class.depth -= 1
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class AstProcessorPolicy
|
5
|
+
def initialize(context, &block)
|
6
|
+
@context = context
|
7
|
+
DSL.new(self).instance_eval(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def const(matcher, &block)
|
11
|
+
event_processor matcher, :const_name, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
def include_const(matcher)
|
15
|
+
const matcher, &:processing!
|
16
|
+
end
|
17
|
+
|
18
|
+
def exclude_const(matcher)
|
19
|
+
const matcher, &:skip_processing!
|
20
|
+
end
|
21
|
+
|
22
|
+
def location(matcher, &block)
|
23
|
+
event_processor matcher, :location, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
def include_location(matcher)
|
27
|
+
location matcher, &:processing!
|
28
|
+
end
|
29
|
+
|
30
|
+
def exclude_location(matcher)
|
31
|
+
location matcher, &:skip_processing!
|
32
|
+
end
|
33
|
+
|
34
|
+
def event_processor(matcher, prop, &block)
|
35
|
+
@context.event_processors[matcher] = [prop, block]
|
36
|
+
end
|
37
|
+
|
38
|
+
def advanced_const_resolver(callable = nil, &block)
|
39
|
+
raise ArgumentError, "You must provide a block or callable" unless block || callable
|
40
|
+
raise ArgumentError, "The provided object must respond to #call" if callable && !callable.respond_to?(:call)
|
41
|
+
|
42
|
+
@context.advanced_const_resolver = block || callable
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class CacheFile
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(file:, ttl:)
|
8
|
+
@file = file
|
9
|
+
@ttl = ttl
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(target)
|
13
|
+
return if File.exist?(@file)
|
14
|
+
|
15
|
+
FileUtils.mkdir_p File.dirname(@file)
|
16
|
+
|
17
|
+
info { "Writing cache to #{@file}" }
|
18
|
+
|
19
|
+
File.open(@file, "w") do |f|
|
20
|
+
Marshal.dump target, f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def read
|
25
|
+
return nil unless File.exist?(@file)
|
26
|
+
|
27
|
+
info { "Reading cache from #{@file} (#{File.size(@file)} bytes)" }
|
28
|
+
|
29
|
+
File.open(@file) do |f|
|
30
|
+
return Marshal.load f
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def stale?
|
35
|
+
return false unless File.exist?(@file)
|
36
|
+
|
37
|
+
File.mtime(@file).to_i < (Time.now.to_i - @ttl)
|
38
|
+
end
|
39
|
+
|
40
|
+
def clean!(force: false)
|
41
|
+
return unless File.exist?(@file)
|
42
|
+
return unless force || stale?
|
43
|
+
|
44
|
+
FileUtils.rm_f @file
|
45
|
+
info { "Removed stale cache file: #{@file}" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class Cli
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
STATUS_SUCCESS = 0
|
8
|
+
STATUS_FAILURE = 1
|
9
|
+
private_constant :STATUS_SUCCESS, :STATUS_FAILURE
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def run!(command)
|
13
|
+
new(command).run!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(command)
|
18
|
+
@command = command
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!
|
22
|
+
@command.run!
|
23
|
+
STATUS_SUCCESS
|
24
|
+
rescue StandardError => e
|
25
|
+
error { e.backtrace.unshift(e.message).join("\n") }
|
26
|
+
STATUS_FAILURE
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
module Command
|
5
|
+
class Analyzer
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
def initialize(file_paths, context)
|
9
|
+
@file_paths = file_paths
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
if file_paths.empty?
|
15
|
+
warn { "No files to analyze" }
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
clean_dir!
|
20
|
+
analyze!
|
21
|
+
visualize!
|
22
|
+
rescue TargetNodeNotFound => e
|
23
|
+
info { e.message }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :file_paths, :context
|
29
|
+
|
30
|
+
def clean_dir!
|
31
|
+
context.clean_dir!
|
32
|
+
end
|
33
|
+
|
34
|
+
def analyze!
|
35
|
+
file_paths.each do |file_path|
|
36
|
+
file_path = File.expand_path file_path
|
37
|
+
|
38
|
+
unless File.exist?(file_path)
|
39
|
+
warn { "Skipping #{file_path}" }
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
ast_processor = AstProcessor.new(
|
44
|
+
file_path,
|
45
|
+
graph,
|
46
|
+
context.event_processors,
|
47
|
+
context.advanced_const_resolver,
|
48
|
+
context.ignore_errors
|
49
|
+
)
|
50
|
+
|
51
|
+
ast_processor.call
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def visualize!
|
56
|
+
writer = context.create_writer
|
57
|
+
visualizer = context.create_visualizer
|
58
|
+
visualizer.accept!(*extract_graph_elements)
|
59
|
+
bytesize = writer.write visualizer.render
|
60
|
+
|
61
|
+
info { "Writing to #{writer.path} (#{bytesize} bytes)" }
|
62
|
+
info { "Run `open #{writer.path}` to view the graph" }
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_graph_elements
|
66
|
+
if context.target_path
|
67
|
+
source_path_matcher = nil
|
68
|
+
source_path_matcher = Matcher.new(context.source_path) if context.source_path.present?
|
69
|
+
|
70
|
+
target_path_matcher = Matcher.new(context.target_path)
|
71
|
+
|
72
|
+
message = []
|
73
|
+
message << "Searching paths"
|
74
|
+
message << "from `#{source_path_matcher}`" if source_path_matcher
|
75
|
+
message << "to `#{target_path_matcher}`"
|
76
|
+
|
77
|
+
info { message.join(" ") }
|
78
|
+
graph.find_path source_path_matcher, target_path_matcher
|
79
|
+
else
|
80
|
+
[Node.all, Edge.all]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def graph
|
85
|
+
@graph ||= context.create_graph
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "readline"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module DepsGrapher
|
7
|
+
module Command
|
8
|
+
class Init
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
def initialize(context)
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def run!
|
16
|
+
dest = File.expand_path("graphile.rb")
|
17
|
+
|
18
|
+
return if File.exist?(dest) && !ask_yes_no("Overwrite `#{dest}`?")
|
19
|
+
|
20
|
+
context.generate_graphile dest
|
21
|
+
|
22
|
+
info { "\n`#{dest}` was created." }
|
23
|
+
info { "Please edit the configuration file." }
|
24
|
+
info { "Run `bundle exec deps_grapher -c #{File.basename(dest)}`." }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :context
|
30
|
+
|
31
|
+
def ask_yes_no(question)
|
32
|
+
stty_save = `stty -g`.chomp
|
33
|
+
trap("INT") do
|
34
|
+
system "stty", stty_save
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
while (input = Readline.readline("#{question} (y/n): ", true))
|
39
|
+
case input
|
40
|
+
when "y"
|
41
|
+
return true
|
42
|
+
when "n"
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class Configuration
|
5
|
+
include PluginDSL
|
6
|
+
|
7
|
+
class_attribute :path
|
8
|
+
class_attribute :root_dir, default: File.expand_path(File.join("..", ".."), __dir__)
|
9
|
+
class_attribute :visualizer
|
10
|
+
class_attribute :visualizer_options, default: { layers: [] }
|
11
|
+
class_attribute :source_path # source class name on graph
|
12
|
+
class_attribute :target_path # target class name on graph
|
13
|
+
class_attribute :clean, default: false
|
14
|
+
class_attribute :logger
|
15
|
+
class_attribute :cache_dir, default: File.expand_path(File.join("..", "..", "tmp", "deps_grapher", "cache"), __dir__)
|
16
|
+
class_attribute :cache_ttl, default: 60 * 5 # 5 minutes
|
17
|
+
class_attribute :output_dir, default: File.expand_path(File.join("..", "..", "tmp", "deps_grapher", "graph"), __dir__)
|
18
|
+
class_attribute :ignore_errors, default: false
|
19
|
+
class_attribute :verbose, default: false
|
20
|
+
class_attribute :dump, default: false
|
21
|
+
|
22
|
+
attr_accessor :layers
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
self.logger = Logger.new($stderr).tap do
|
26
|
+
_1.formatter = ->(_, _, _, msg) { "#{msg}\n" }
|
27
|
+
end
|
28
|
+
|
29
|
+
self.visualizer = Visualizer::Registry.default_visualizer
|
30
|
+
|
31
|
+
@layers = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def available_visualizers
|
35
|
+
Visualizer::Registry.available_visualizers
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_plugin!
|
39
|
+
PluginLoader.load! plugin_dir
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge!(options)
|
43
|
+
options.each do |key, value|
|
44
|
+
send "#{key}=", value if respond_to?("#{key}=") && !value.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def input
|
49
|
+
@input ||= Input.new(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def layer(&block)
|
53
|
+
Layer.new(&block).tap do |layer|
|
54
|
+
@layers[layer.name] = layer
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ast_processor_policy(&block)
|
59
|
+
AstProcessorPolicy.new context, &block
|
60
|
+
end
|
61
|
+
|
62
|
+
def plugin_dir(dir = nil)
|
63
|
+
return @plugin_dir unless dir
|
64
|
+
|
65
|
+
@plugin_dir = dir
|
66
|
+
end
|
67
|
+
|
68
|
+
def load!(file)
|
69
|
+
return unless file
|
70
|
+
|
71
|
+
file = File.expand_path file
|
72
|
+
raise ArgumentError, "no such file: #{file}" unless File.exist?(file)
|
73
|
+
|
74
|
+
self.path = file
|
75
|
+
|
76
|
+
content = File.read file
|
77
|
+
|
78
|
+
cache_key = Digest::MD5.hexdigest(content)
|
79
|
+
|
80
|
+
SourceCache::Registry.with_cache cache_key do
|
81
|
+
DSL.new(self).instance_eval content
|
82
|
+
end
|
83
|
+
|
84
|
+
return unless dump
|
85
|
+
|
86
|
+
at_exit do
|
87
|
+
warn ""
|
88
|
+
warn "=============================="
|
89
|
+
warn " Configuration"
|
90
|
+
warn "=============================="
|
91
|
+
puts content
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def context
|
96
|
+
@context ||= Context.new self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module DepsGrapher
|
6
|
+
class Context
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@config,
|
10
|
+
:ignore_errors,
|
11
|
+
:source_path,
|
12
|
+
:target_path
|
13
|
+
|
14
|
+
attr_accessor :event_processors, :advanced_const_resolver
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@config = config
|
18
|
+
@event_processors = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def clean_dir!
|
22
|
+
return unless @config.clean
|
23
|
+
|
24
|
+
FileUtils.rm_rf @config.output_dir
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_graphile(dest)
|
28
|
+
Graphile::Generator.new(@config).call dest
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_temp_graphile
|
32
|
+
generate_graphile nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_writer
|
36
|
+
HtmlWriter.new @config.output_dir
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_visualizer
|
40
|
+
downloader = Visualizer::Downloader.new @config.output_dir
|
41
|
+
Visualizer.fetch(@config.visualizer).new downloader, @config.visualizer_options
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_graph
|
45
|
+
target_path ? Graph.new : NullGraph.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../cytoscape"
|
4
|
+
|
5
|
+
module DepsGrapher
|
6
|
+
class Cytoscape
|
7
|
+
class Cose < self
|
8
|
+
command_option "cy:cose"
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def required_js
|
13
|
+
super.tap do |js|
|
14
|
+
js << "https://unpkg.com/cose-base/cose-base.js"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def layout_options
|
19
|
+
option = Visualizer::JsOption.new(
|
20
|
+
name: :cose,
|
21
|
+
directed: true,
|
22
|
+
padding: 10,
|
23
|
+
nodeOverlap: 20,
|
24
|
+
refresh: 20,
|
25
|
+
numIter: 1000,
|
26
|
+
initialTemp: 200,
|
27
|
+
coolingFactor: 0.95,
|
28
|
+
minTemp: 1.0
|
29
|
+
)
|
30
|
+
option.add_function(name: :nodeRepulsion, args: :edge, body: "return 100000")
|
31
|
+
option.add_function(name: :idealEdgeLength, args: :edge, body: "return 100")
|
32
|
+
option.add_function(name: :edgeElasticity, args: :edge, body: "return 32")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "cose"
|
4
|
+
|
5
|
+
module DepsGrapher
|
6
|
+
class Cytoscape
|
7
|
+
class Fcose < Cose
|
8
|
+
command_option "cy:fcose"
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def required_js
|
13
|
+
super.tap do |js|
|
14
|
+
js << "https://unpkg.com/cytoscape-fcose/cytoscape-fcose.js"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def layout_options
|
19
|
+
option = Visualizer::JsOption.new(
|
20
|
+
name: :fcose,
|
21
|
+
directed: true,
|
22
|
+
padding: 10,
|
23
|
+
nodeOverlap: 20,
|
24
|
+
refresh: 20,
|
25
|
+
numIter: 1000,
|
26
|
+
initialTemp: 200,
|
27
|
+
coolingFactor: 0.95,
|
28
|
+
minTemp: 1.0
|
29
|
+
)
|
30
|
+
option.add_function(name: :nodeRepulsion, args: :edge, body: "return 100000")
|
31
|
+
option.add_function(name: :idealEdgeLength, args: :edge, body: "return 100")
|
32
|
+
option.add_function(name: :edgeElasticity, args: :edge, body: "return 32")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|