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,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "layer/registry"
|
4
|
+
require_relative "visualizer/color"
|
5
|
+
|
6
|
+
module DepsGrapher
|
7
|
+
class Layer
|
8
|
+
class << self
|
9
|
+
def fetch(file_path)
|
10
|
+
Registry.fetch file_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def names
|
14
|
+
Registry.all.to_set(&:name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def visible_names
|
18
|
+
Registry.all.select(&:visible).to_set(&:name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def exist?(file_path)
|
22
|
+
Registry.exist? file_path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :name, :visible
|
27
|
+
|
28
|
+
def initialize(&block)
|
29
|
+
@visible = true
|
30
|
+
DSL.new(self).instance_eval(&block)
|
31
|
+
|
32
|
+
assert!
|
33
|
+
|
34
|
+
return if default?
|
35
|
+
|
36
|
+
source.files.each do |file|
|
37
|
+
Registry.register file, self
|
38
|
+
end
|
39
|
+
|
40
|
+
SourceCache.register! name, source
|
41
|
+
end
|
42
|
+
|
43
|
+
def source(&block)
|
44
|
+
@source_defined = true if block
|
45
|
+
@source ||= Source.new(name, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def color(&block)
|
49
|
+
@color_defined = true if block
|
50
|
+
Visualizer::Color.new(name, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def assert!
|
56
|
+
raise ArgumentError, "layer: no `name` given" unless name
|
57
|
+
raise ArgumentError, "layer `#{name}` has no `source` block" unless default? || @source_defined
|
58
|
+
raise ArgumentError, "layer `#{name}` has no `color` block" unless @color_defined
|
59
|
+
end
|
60
|
+
|
61
|
+
def default?
|
62
|
+
name == :__default
|
63
|
+
end
|
64
|
+
|
65
|
+
Default = (new do
|
66
|
+
name :__default
|
67
|
+
visible true
|
68
|
+
color do
|
69
|
+
background "#BDBDBD"
|
70
|
+
border "#9E9E9E"
|
71
|
+
font "#BDBDBD"
|
72
|
+
end
|
73
|
+
end).freeze
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
module Logging
|
5
|
+
def info(&block)
|
6
|
+
DepsGrapher.logger.info(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def warn(&block)
|
10
|
+
DepsGrapher.logger.warn(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def error(&block)
|
14
|
+
DepsGrapher.logger.error(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def verbose(&block)
|
18
|
+
DepsGrapher.logger.info(&block) if DepsGrapher.config.verbose
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class Matcher
|
5
|
+
def initialize(pattern)
|
6
|
+
@regexp = convert_to_regexp pattern
|
7
|
+
end
|
8
|
+
|
9
|
+
def match?(value)
|
10
|
+
@regexp.match? value
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@regexp.inspect
|
15
|
+
end
|
16
|
+
alias inspect to_s
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def convert_to_regexp(pattern)
|
21
|
+
if pattern.is_a?(Regexp)
|
22
|
+
pattern
|
23
|
+
else
|
24
|
+
Regexp.new("\\A#{pattern.gsub(/\.?\*/, ".*")}\\z")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class Node
|
5
|
+
class << self
|
6
|
+
def guid
|
7
|
+
@guid ||= 0
|
8
|
+
@guid += 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch(class_name)
|
12
|
+
registry[class_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(class_name, location)
|
16
|
+
return nil if class_name.nil? || location.nil?
|
17
|
+
|
18
|
+
registry[class_name] ||= new class_name, location
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
registry.values
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def registry
|
28
|
+
@registry ||= {}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private_class_method :new
|
33
|
+
|
34
|
+
attr_accessor :id, :class_name, :location, :parent, :deps_count
|
35
|
+
|
36
|
+
def initialize(class_name, location)
|
37
|
+
@id = "n#{self.class.guid}"
|
38
|
+
@class_name = class_name
|
39
|
+
@location = location
|
40
|
+
@parent = nil
|
41
|
+
@deps_count = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def label
|
45
|
+
deps_count.positive? ? "#{class_name} (#{deps_count})" : class_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def layer
|
49
|
+
@layer ||= Layer.fetch(location).name
|
50
|
+
end
|
51
|
+
|
52
|
+
def increment_deps_count!
|
53
|
+
@deps_count += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(other)
|
57
|
+
other.is_a?(self.class) && id == other.id
|
58
|
+
end
|
59
|
+
alias == eql?
|
60
|
+
|
61
|
+
def hash
|
62
|
+
id.hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class PluginLoader
|
5
|
+
class << self
|
6
|
+
def load!(plugin_dir)
|
7
|
+
new(plugin_dir).load!
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private_class_method :new
|
12
|
+
|
13
|
+
def initialize(plugin_dir)
|
14
|
+
@plugin_dir = plugin_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def load!
|
18
|
+
return if plugin_dir.blank? || !Dir.exist?(plugin_dir)
|
19
|
+
|
20
|
+
Dir.glob(File.join(plugin_dir, "**", "*.rb")).sort.each do |file|
|
21
|
+
require file
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :plugin_dir
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class Source
|
5
|
+
attr_accessor :root, :glob_pattern
|
6
|
+
|
7
|
+
def initialize(name, &block)
|
8
|
+
@name = name
|
9
|
+
@include_pattern = nil
|
10
|
+
@exclude_pattern = nil
|
11
|
+
|
12
|
+
DSL.new(self).instance_eval(&block)
|
13
|
+
|
14
|
+
assert!
|
15
|
+
|
16
|
+
@glob_pattern = Array(glob_pattern.presence || File.join("**", "*.rb")).each_with_object([]) do |pattern, array|
|
17
|
+
array << File.join(root, pattern)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def files
|
22
|
+
Dir.glob(glob_pattern).sort.uniq.each_with_object([]) do |file, files|
|
23
|
+
next if include_pattern && !include_pattern.match?(file)
|
24
|
+
next if exclude_pattern&.match?(file)
|
25
|
+
|
26
|
+
files << file
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def include_pattern=(pattern)
|
31
|
+
@include_pattern = Matcher.new pattern
|
32
|
+
end
|
33
|
+
|
34
|
+
def exclude_pattern=(pattern)
|
35
|
+
@exclude_pattern = Matcher.new pattern
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"glob_pattern: #{glob_pattern.inspect}, include_pattern: #{include_pattern.inspect}, exclude_pattern: #{exclude_pattern.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :include_pattern, :exclude_pattern
|
45
|
+
|
46
|
+
def assert!
|
47
|
+
raise ArgumentError, "source: no `root` given" if root.blank?
|
48
|
+
raise ArgumentError, "source: directory not found `#{root}`" unless Dir.exist?(root)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class SourceCache
|
5
|
+
class ClassNameExtractor < Parser::AST::Processor
|
6
|
+
class << self
|
7
|
+
def cache
|
8
|
+
@cache ||= Set.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(&block)
|
13
|
+
super()
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def extract!(file_path)
|
18
|
+
source_buffer = Parser::Source::Buffer.new(file_path)
|
19
|
+
parser = Parser::CurrentRuby.new
|
20
|
+
process parser.parse(source_buffer.read)
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_module(node)
|
24
|
+
const_node = node.children[0]
|
25
|
+
name = extract_const_name const_node
|
26
|
+
|
27
|
+
@namespace_stack ||= []
|
28
|
+
@namespace_stack.push name
|
29
|
+
|
30
|
+
super
|
31
|
+
|
32
|
+
const_name = @namespace_stack.join("::")
|
33
|
+
|
34
|
+
unless self.class.cache.include? const_name
|
35
|
+
self.class.cache << const_name
|
36
|
+
@block.call const_name, node.location.name.source_buffer.name
|
37
|
+
end
|
38
|
+
|
39
|
+
@namespace_stack.pop
|
40
|
+
end
|
41
|
+
alias on_class on_module
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def extract_const_name(node)
|
46
|
+
case node.type
|
47
|
+
when :const
|
48
|
+
base, name = *node
|
49
|
+
if base.nil?
|
50
|
+
name.to_s
|
51
|
+
else
|
52
|
+
[extract_const_name(base), name].compact.join "::"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise "unexpected node type: #{node.type}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class SourceCache
|
5
|
+
module Registry
|
6
|
+
class << self
|
7
|
+
def fetch(name)
|
8
|
+
registry.fetch name
|
9
|
+
rescue KeyError
|
10
|
+
raise SourceCacheNotFound, "source cache not found: #{name}"
|
11
|
+
end
|
12
|
+
alias [] fetch
|
13
|
+
|
14
|
+
def key?(name)
|
15
|
+
registry.key? name
|
16
|
+
end
|
17
|
+
|
18
|
+
def register(by_const_name, by_location)
|
19
|
+
by_const_name.each do |const_name, location|
|
20
|
+
registry[const_name] ||= location
|
21
|
+
end
|
22
|
+
|
23
|
+
by_location.each do |location, const_name|
|
24
|
+
registry[location] ||= const_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_cache(key)
|
29
|
+
restore_cache! key
|
30
|
+
yield
|
31
|
+
persist_cache! key
|
32
|
+
end
|
33
|
+
|
34
|
+
def persist_cache!(key)
|
35
|
+
cache_file = DepsGrapher.cache_file key
|
36
|
+
cache_file.write @registry
|
37
|
+
end
|
38
|
+
|
39
|
+
def restore_cache!(key)
|
40
|
+
cache_file = DepsGrapher.cache_file key
|
41
|
+
loaded = cache_file.read
|
42
|
+
|
43
|
+
unless loaded
|
44
|
+
@restored_cache = false
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
@registry = loaded
|
49
|
+
@restored_cache = true
|
50
|
+
end
|
51
|
+
|
52
|
+
def restored_cache?
|
53
|
+
@restored_cache
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def registry
|
59
|
+
@registry ||= {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DepsGrapher
|
4
|
+
class SourceCache
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def register!(name, source)
|
9
|
+
new(name, source).register!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :root
|
14
|
+
|
15
|
+
def initialize(name, source)
|
16
|
+
@name = name
|
17
|
+
@source = source
|
18
|
+
@root = @source.root
|
19
|
+
@cache_by_const_name = {}
|
20
|
+
@cache_by_location = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def register!
|
24
|
+
return if Registry.restored_cache?
|
25
|
+
|
26
|
+
class_name_extractor = ClassNameExtractor.new do |class_name, location|
|
27
|
+
@cache_by_const_name[class_name] = location
|
28
|
+
@cache_by_location[location] ||= class_name
|
29
|
+
end
|
30
|
+
|
31
|
+
verbose { "Collecting `#{@name}` layer by #{@source}" }
|
32
|
+
|
33
|
+
start = Time.now
|
34
|
+
|
35
|
+
@source.files.each do |file|
|
36
|
+
class_name_extractor.extract! file
|
37
|
+
end
|
38
|
+
|
39
|
+
info do
|
40
|
+
"Found #{@cache_by_const_name.size} modules/classes, #{@cache_by_location.size} locations in `#{@name}` layer (#{Time.now - start} sec)"
|
41
|
+
end
|
42
|
+
verbose { "" }
|
43
|
+
|
44
|
+
Registry.register @cache_by_const_name, @cache_by_location
|
45
|
+
|
46
|
+
ClassNameExtractor.cache.clear
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../vis"
|
4
|
+
|
5
|
+
module DepsGrapher
|
6
|
+
class Vis
|
7
|
+
class Box < self
|
8
|
+
command_option "vis:box"
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def font_color(_)
|
13
|
+
"#fff"
|
14
|
+
end
|
15
|
+
|
16
|
+
def shape
|
17
|
+
:box
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Network vis <%= shape %></title>
|
5
|
+
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no″>
|
6
|
+
<meta charset="utf-8">
|
7
|
+
<style>
|
8
|
+
#network {
|
9
|
+
width: 100vw;
|
10
|
+
height: 100vh;
|
11
|
+
}
|
12
|
+
</style>
|
13
|
+
<% required_js.each do |js| -%>
|
14
|
+
<script src="<%= File.basename js %>"></script>
|
15
|
+
<% end -%>
|
16
|
+
</head>
|
17
|
+
<body>
|
18
|
+
<div id="network"></div>
|
19
|
+
<script>
|
20
|
+
const json = <%= data.to_json %>;
|
21
|
+
const nodes = new vis.DataSet(json['nodes']);
|
22
|
+
const edges = new vis.DataSet(json['edges']);
|
23
|
+
const container = document.getElementById('network');
|
24
|
+
const data = {
|
25
|
+
nodes: nodes,
|
26
|
+
edges: edges
|
27
|
+
};
|
28
|
+
const options = {
|
29
|
+
layout: {
|
30
|
+
improvedLayout: false
|
31
|
+
},
|
32
|
+
};
|
33
|
+
const network = new vis.Network(container, data, options);
|
34
|
+
</script>
|
35
|
+
</body>
|
36
|
+
</html>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "visualizer/base"
|
4
|
+
|
5
|
+
module DepsGrapher
|
6
|
+
class Vis < Visualizer::Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def required_js
|
10
|
+
["https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def template_path
|
14
|
+
File.expand_path File.join("vis", "template.erb"), __dir__
|
15
|
+
end
|
16
|
+
|
17
|
+
def shape
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
nodes = @nodes.each_with_object([]) do |node, array|
|
23
|
+
next if skip_node?(node)
|
24
|
+
|
25
|
+
array << convert_node(node)
|
26
|
+
end
|
27
|
+
|
28
|
+
edges = @edges.each_with_object([]) do |edge, array|
|
29
|
+
next if skip_edge?(edge)
|
30
|
+
|
31
|
+
array << convert_edge(edge)
|
32
|
+
end
|
33
|
+
|
34
|
+
{ nodes: nodes, edges: edges }
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_node(node)
|
38
|
+
root_node = node.parent.nil?
|
39
|
+
|
40
|
+
{
|
41
|
+
id: node.id,
|
42
|
+
label: node.label,
|
43
|
+
shape: shape,
|
44
|
+
size: (root_node ? 10 : 5) + [1.5 * node.deps_count, 20].min,
|
45
|
+
font: {
|
46
|
+
size: (root_node ? 8 : 5) + [1.2 * node.deps_count, 5].min,
|
47
|
+
color: font_color(node.layer)
|
48
|
+
},
|
49
|
+
color: color_settings(node.layer).except(:font)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def convert_edge(edge)
|
54
|
+
{
|
55
|
+
from: edge.from.id,
|
56
|
+
to: edge.to.id,
|
57
|
+
arrows: :to
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def skip_node?(node)
|
62
|
+
options[:layers].present? && !options[:layers].include?(node.layer)
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip_edge?(edge)
|
66
|
+
skip_node?(edge.to)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "registry"
|
4
|
+
require_relative "command_option"
|
5
|
+
require_relative "color"
|
6
|
+
|
7
|
+
module DepsGrapher
|
8
|
+
module Visualizer
|
9
|
+
class Base
|
10
|
+
class << self
|
11
|
+
def command_option(name, default: false)
|
12
|
+
Registry.register self, CommandOption.new(name, default)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
def initialize(downloader, options)
|
19
|
+
@downloader = downloader
|
20
|
+
@options = options
|
21
|
+
@nodes = []
|
22
|
+
@edges = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def accept!(nodes, edges)
|
26
|
+
@nodes = nodes
|
27
|
+
@edges = edges
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def render
|
32
|
+
required_js.each do |url|
|
33
|
+
@downloader.download url
|
34
|
+
end
|
35
|
+
|
36
|
+
ERB.new(File.read(template_path), trim_mode: "-").result(binding)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def required_js
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
def template_path
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def color(layer_name)
|
50
|
+
Color[layer_name]
|
51
|
+
end
|
52
|
+
|
53
|
+
def color_map(type)
|
54
|
+
Color.generate_map(type)
|
55
|
+
end
|
56
|
+
|
57
|
+
def arrow_color(layer_name)
|
58
|
+
color(layer_name).arrow || background_color(layer_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def background_color(layer_name)
|
62
|
+
color(layer_name).background
|
63
|
+
end
|
64
|
+
|
65
|
+
def font_color(layer_name)
|
66
|
+
color(layer_name).font
|
67
|
+
end
|
68
|
+
|
69
|
+
def color_settings(layer_name)
|
70
|
+
color(layer_name).settings
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|