deps_grapher 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|