rubrowser 0.1.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/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +35 -0
- data/bin/rubrowser +6 -0
- data/lib/d3.rb +33 -0
- data/lib/data.rb +51 -0
- data/lib/parser/directory.rb +44 -0
- data/lib/parser/factory.rb +16 -0
- data/lib/parser/file.rb +101 -0
- data/lib/rubrowser.rb +3 -0
- data/lib/server.rb +25 -0
- data/lib/tree.rb +75 -0
- data/public/css/application.css +36 -0
- data/public/javascript/application.js +121 -0
- data/public/javascript/d3.js +16231 -0
- data/public/javascript/jquery.js +10074 -0
- data/readme.md +29 -0
- data/views/index.haml +12 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c00a8f96b4234e3126fb912dc5e16b91867db034
|
4
|
+
data.tar.gz: fe569abb2aca1ef762f8dae59d02d0517ae0d268
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57fbbf57070c4d09d6b24ed74d749f065c9bb3e6aaa713793e8f3566dae3d1187d9fec80ed5e6ddb46ae96800747febc9cb62a9168f7a2ad443f03583edd4893
|
7
|
+
data.tar.gz: efdde91ea7fbeeb5d8de14c3f7ec45d36d3bbc41b90e76c039120aa4f57700bc82e7d4e2881134065cb8753bf2dfd39cf752bfe528fe9524fe71ae5717a8962c
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
tmp/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rubrowser (0.1.0)
|
5
|
+
haml (~> 4.0, >= 4.0.0)
|
6
|
+
parallel (~> 1.9, >= 1.9.0)
|
7
|
+
parser (~> 2.3, >= 2.3.0)
|
8
|
+
sinatra (~> 1.4, >= 1.4.0)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
ast (2.3.0)
|
14
|
+
haml (4.0.7)
|
15
|
+
tilt
|
16
|
+
parallel (1.9.0)
|
17
|
+
parser (2.3.1.2)
|
18
|
+
ast (~> 2.2)
|
19
|
+
rack (1.6.4)
|
20
|
+
rack-protection (1.5.3)
|
21
|
+
rack
|
22
|
+
sinatra (1.4.7)
|
23
|
+
rack (~> 1.5)
|
24
|
+
rack-protection (~> 1.4)
|
25
|
+
tilt (>= 1.3, < 3)
|
26
|
+
tilt (2.0.5)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
rubrowser!
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
1.12.5
|
data/bin/rubrowser
ADDED
data/lib/d3.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rubrowser
|
2
|
+
class D3
|
3
|
+
def initialize(node)
|
4
|
+
@node = node
|
5
|
+
end
|
6
|
+
|
7
|
+
def constants
|
8
|
+
node_id(node)
|
9
|
+
end
|
10
|
+
|
11
|
+
def occurences
|
12
|
+
node_occurences(node)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :node
|
18
|
+
|
19
|
+
def node_id(node)
|
20
|
+
node.children.map do |n|
|
21
|
+
node_id(n)
|
22
|
+
end.reduce(:+).to_a.push({ name: node.name, id: node.id })
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_occurences(node)
|
26
|
+
occurences = []
|
27
|
+
occurences += node.children.map { |n| node_occurences(n) }.reduce(:+).to_a
|
28
|
+
|
29
|
+
occurences += node.occurences.map { |n| {source: node.id, target: n.id } }.to_a
|
30
|
+
occurences
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/data.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'parser/factory'
|
2
|
+
require 'tree'
|
3
|
+
require 'd3'
|
4
|
+
|
5
|
+
module Rubrowser
|
6
|
+
class Data
|
7
|
+
def self.instance
|
8
|
+
@_instance ||= new
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@files = ARGV
|
13
|
+
@parsed = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def constants
|
17
|
+
@_constants ||= d3.constants.to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
def occurences
|
21
|
+
@_occurences ||= d3.occurences.to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse
|
25
|
+
return if parsed?
|
26
|
+
parsers.each(&:parse)
|
27
|
+
@parsed = true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :files, :parsed
|
33
|
+
alias parsed? parsed
|
34
|
+
|
35
|
+
def parsers
|
36
|
+
@_parsers ||= files.map do |file|
|
37
|
+
Rubrowser::Parser::Factory.build(file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def d3
|
42
|
+
@_d3 ||= Rubrowser::D3.new(tree)
|
43
|
+
end
|
44
|
+
|
45
|
+
def tree
|
46
|
+
parse
|
47
|
+
@_tree ||= Rubrowser::Tree.from_parsers(parsers)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'parser/factory'
|
2
|
+
require 'parallel'
|
3
|
+
|
4
|
+
module Rubrowser
|
5
|
+
module Parser
|
6
|
+
class Directory
|
7
|
+
attr_reader :directory
|
8
|
+
|
9
|
+
def initialize(directory)
|
10
|
+
@directory = directory
|
11
|
+
@parsers = []
|
12
|
+
read
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
self.parsers = Parallel.map(parsers){ |parser| parser.parse }
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def definitions
|
21
|
+
parsers.map(&:definitions).map(&:to_a).reduce(:+) || []
|
22
|
+
end
|
23
|
+
|
24
|
+
def occurences
|
25
|
+
parsers.map(&:occurences).map(&:to_a).reduce(:+) || []
|
26
|
+
end
|
27
|
+
|
28
|
+
def count
|
29
|
+
parsers.map(&:count).reduce(:+)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_accessor :parsers
|
35
|
+
|
36
|
+
def read
|
37
|
+
files = Dir.glob(::File.join(directory, '**', '*.rb'))
|
38
|
+
self.parsers = Parallel.map(files) do |file|
|
39
|
+
Factory.build(file)
|
40
|
+
end.compact
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'parser/file'
|
2
|
+
require 'parser/directory'
|
3
|
+
|
4
|
+
module Rubrowser
|
5
|
+
module Parser
|
6
|
+
class Factory
|
7
|
+
def self.build(file)
|
8
|
+
if ::File.file?(file)
|
9
|
+
File.new(file)
|
10
|
+
elsif ::File.directory?(file)
|
11
|
+
Directory.new(file)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/parser/file.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
module Rubrowser
|
4
|
+
module Parser
|
5
|
+
class File
|
6
|
+
FILE_SIZE_LIMIT = 2 * 1024 * 1024
|
7
|
+
|
8
|
+
attr_reader :file, :definitions, :occurences
|
9
|
+
|
10
|
+
def initialize(file)
|
11
|
+
@file = file
|
12
|
+
@definitions = Set.new
|
13
|
+
@occurences = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
if file_valid?(file)
|
18
|
+
::File.open(file) do |f|
|
19
|
+
code = f.read
|
20
|
+
ast = ::Parser::CurrentRuby.parse(code)
|
21
|
+
constants = parse_block(ast)
|
22
|
+
self.definitions = constants[:definitions].uniq
|
23
|
+
self.occurences = constants[:occurences].uniq
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def file_valid?(file)
|
30
|
+
!::File.symlink?(file) && ::File.file?(file) && ::File.size(file) <= FILE_SIZE_LIMIT
|
31
|
+
end
|
32
|
+
|
33
|
+
def count
|
34
|
+
1
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_writer :definitions, :occurences
|
40
|
+
|
41
|
+
def parse_block(node, parents = [])
|
42
|
+
return { definitions: [], occurences: [] } unless node.is_a?(::Parser::AST::Node)
|
43
|
+
|
44
|
+
case node.type
|
45
|
+
when :module
|
46
|
+
parse_module(node, parents)
|
47
|
+
when :class
|
48
|
+
parse_class(node, parents)
|
49
|
+
when :const
|
50
|
+
parse_const(node, parents)
|
51
|
+
else
|
52
|
+
node
|
53
|
+
.children
|
54
|
+
.map { |n| parse_block(n, parents) }
|
55
|
+
.reduce { |a, e| merge_constants(a, e) } || { definitions: [], occurences: [] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_module(node, parents = [])
|
60
|
+
name = resolve_const_path(node.children.first, parents)
|
61
|
+
node
|
62
|
+
.children[1..-1]
|
63
|
+
.map { |n| parse_block(n, name) }
|
64
|
+
.reduce { |a, e| merge_constants(a, e) }
|
65
|
+
.tap { |constants| constants[:definitions].unshift(name) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_class(node, parents = [])
|
69
|
+
name = resolve_const_path(node.children.first, parents)
|
70
|
+
node
|
71
|
+
.children[1..-1]
|
72
|
+
.map { |n| parse_block(n, name) }
|
73
|
+
.reduce { |a, e| merge_constants(a, e) }
|
74
|
+
.tap { |constants| constants[:definitions].unshift(name) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_const(node, parents = [])
|
78
|
+
constant = resolve_const_path(node)
|
79
|
+
namespace = parents[0...-1]
|
80
|
+
constants = if namespace.empty? || constant.first.nil?
|
81
|
+
[{ parents => [constant.compact] }]
|
82
|
+
else
|
83
|
+
[{ parents => (namespace.size-1).downto(0).map { |i| namespace[0..i] + constant }.push(constant) }]
|
84
|
+
end
|
85
|
+
{ definitions: [], occurences: constants }
|
86
|
+
end
|
87
|
+
|
88
|
+
def merge_constants(constants1, constants2)
|
89
|
+
{
|
90
|
+
definitions: constants1[:definitions] + constants2[:definitions],
|
91
|
+
occurences: constants1[:occurences] + constants2[:occurences]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def resolve_const_path(node, parents = [])
|
96
|
+
return parents unless node.is_a?(::Parser::AST::Node) && [:const, :cbase].include?(node.type)
|
97
|
+
resolve_const_path(node.children.first, parents) + [node.children.last]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/rubrowser.rb
ADDED
data/lib/server.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'data'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rubrowser
|
6
|
+
class Server < Sinatra::Base
|
7
|
+
get '/' do
|
8
|
+
data = Rubrowser::Data.instance
|
9
|
+
haml :index,
|
10
|
+
locals: {
|
11
|
+
constants: data.constants,
|
12
|
+
occurences: data.occurences
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.start
|
17
|
+
Rubrowser::Data.instance.parse
|
18
|
+
Thread.new do
|
19
|
+
run! host: 'localhost',
|
20
|
+
port: 9000,
|
21
|
+
root: File.expand_path('../../', __FILE__)
|
22
|
+
end.join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/tree.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Rubrowser
|
2
|
+
class Tree
|
3
|
+
attr_reader :name, :children, :occurences, :parent
|
4
|
+
|
5
|
+
def self.from_parsers(parsers)
|
6
|
+
return Tree.new if parsers.empty?
|
7
|
+
|
8
|
+
definitions = parsers.map(&:definitions).reduce(:+).uniq
|
9
|
+
occurences = parsers.map(&:occurences).reduce(:+).uniq
|
10
|
+
Tree.new.tap do |tree|
|
11
|
+
definitions.each { |definition| tree.add_child(definition) }
|
12
|
+
occurences.each { |occurence| tree.add_occurence(*occurence.first) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(name = 'Kernel', parent = nil)
|
17
|
+
@name = name
|
18
|
+
@parent = parent
|
19
|
+
@children = Set.new
|
20
|
+
@occurences = Set.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def root?
|
24
|
+
@parent.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
return name if root? || parent.root?
|
29
|
+
"#{parent.id}::#{name}".to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_child(child_name_path = [])
|
33
|
+
return if child_name_path.empty?
|
34
|
+
child = get_or_create_child(child_name_path[0])
|
35
|
+
children.add(child)
|
36
|
+
child.add_child(child_name_path[1..-1])
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_occurence(user, constants)
|
40
|
+
user_node = find_node(user)
|
41
|
+
occured_node = constants.map { |constant| find_node(constant) }.compact.first
|
42
|
+
return unless user_node && occured_node
|
43
|
+
user_node.occurences << occured_node
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_node(path)
|
47
|
+
return self if path.empty?
|
48
|
+
child = children.find { |c| c.name == path.first }
|
49
|
+
return unless child
|
50
|
+
child.find_node(path[1..-1])
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_or_create_child(child_name)
|
54
|
+
children.find { |child| child_name == child.name } || Tree.new(child_name, self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def eq?(other)
|
58
|
+
other.name == name
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_h
|
62
|
+
{
|
63
|
+
id: id,
|
64
|
+
name: name
|
65
|
+
}.tap do |hash|
|
66
|
+
hash[:children] = children.map(&:to_h) unless children.empty?
|
67
|
+
hash[:occurences] = occurences.map(&:id) unless occurences.empty?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_writer :occurences
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
html, body{
|
2
|
+
width: 100%;
|
3
|
+
height: 100%;
|
4
|
+
padding: 0px;
|
5
|
+
margin: 0px;
|
6
|
+
}
|
7
|
+
.dependency_graph, .dependency_graph svg{
|
8
|
+
width: 100%;
|
9
|
+
height: 100%;
|
10
|
+
}
|
11
|
+
|
12
|
+
line{
|
13
|
+
stroke: #888;
|
14
|
+
}
|
15
|
+
|
16
|
+
.link {
|
17
|
+
fill: none;
|
18
|
+
stroke: #666;
|
19
|
+
stroke-width: 1.5px;
|
20
|
+
}
|
21
|
+
|
22
|
+
circle {
|
23
|
+
fill: #ccc;
|
24
|
+
stroke: #333;
|
25
|
+
stroke-width: 1.5px;
|
26
|
+
}
|
27
|
+
|
28
|
+
.fixed {
|
29
|
+
fill: #f00;
|
30
|
+
}
|
31
|
+
|
32
|
+
text {
|
33
|
+
font: 10px sans-serif;
|
34
|
+
pointer-events: none;
|
35
|
+
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
|
36
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
var ParseGraph = function(){
|
2
|
+
var svg = d3.select(".dependency_graph svg");
|
3
|
+
var $svg = $('.dependency_graph svg');
|
4
|
+
|
5
|
+
if(!svg.size()){
|
6
|
+
return false;
|
7
|
+
}
|
8
|
+
|
9
|
+
|
10
|
+
var width = $svg.width(),
|
11
|
+
height = $svg.height(),
|
12
|
+
constants = JSON.parse(svg.attr('data-constants')),
|
13
|
+
occurences = JSON.parse(svg.attr('data-occurences'));
|
14
|
+
var color = d3.scaleOrdinal(d3.schemeCategory20);
|
15
|
+
|
16
|
+
var drag = d3.drag()
|
17
|
+
.on("start", dragstarted)
|
18
|
+
.on("drag", dragged)
|
19
|
+
.on("end", dragended);
|
20
|
+
|
21
|
+
|
22
|
+
svg.call(d3.zoom().on("zoom", function () {
|
23
|
+
container.attr("transform", d3.event.transform);
|
24
|
+
}));
|
25
|
+
|
26
|
+
container = svg.append('g');
|
27
|
+
|
28
|
+
|
29
|
+
var simulation = d3.forceSimulation()
|
30
|
+
.force("link", d3.forceLink().id(function(d) { return d.id; }))
|
31
|
+
.force("charge", d3.forceManyBody())
|
32
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
33
|
+
.force("forceCollide", d3.forceCollide(function(){ return 80; }));
|
34
|
+
|
35
|
+
var link = container.append("g")
|
36
|
+
.attr("class", "links")
|
37
|
+
.selectAll("path")
|
38
|
+
.data(occurences)
|
39
|
+
.enter().append("path")
|
40
|
+
.attr("class", 'link')
|
41
|
+
.attr("marker-end", "url(#occurence)");
|
42
|
+
|
43
|
+
var circle = container.append("g").selectAll("circle")
|
44
|
+
.data(constants)
|
45
|
+
.enter().append("circle")
|
46
|
+
.attr("r", 6)
|
47
|
+
.on("dblclick", dblclick)
|
48
|
+
.call(drag);
|
49
|
+
|
50
|
+
var text = container.append("g").selectAll("text")
|
51
|
+
.data(constants)
|
52
|
+
.enter().append("text")
|
53
|
+
.attr("x", 8)
|
54
|
+
.attr("y", ".31em")
|
55
|
+
.text(function(d) { return d.id; });
|
56
|
+
|
57
|
+
container.append("defs").selectAll("marker")
|
58
|
+
.data(['occurence'])
|
59
|
+
.enter().append("marker")
|
60
|
+
.attr("id", function(d) { return d; })
|
61
|
+
.attr("viewBox", "0 -5 10 10")
|
62
|
+
.attr("refX", 15)
|
63
|
+
.attr("refY", -1.5)
|
64
|
+
.attr("markerWidth", 6)
|
65
|
+
.attr("markerHeight", 6)
|
66
|
+
.attr("orient", "auto")
|
67
|
+
.append("path")
|
68
|
+
.attr("d", "M0,-5L10,0L0,5");
|
69
|
+
|
70
|
+
simulation
|
71
|
+
.nodes(constants)
|
72
|
+
.on("tick", ticked);
|
73
|
+
|
74
|
+
simulation.force("link")
|
75
|
+
.links(occurences);
|
76
|
+
|
77
|
+
function ticked() {
|
78
|
+
link.attr("d", linkArc);
|
79
|
+
circle.attr("transform", transform);
|
80
|
+
text.attr("transform", transform);
|
81
|
+
}
|
82
|
+
|
83
|
+
function linkArc(d) {
|
84
|
+
var dx = d.target.x - d.source.x,
|
85
|
+
dy = d.target.y - d.source.y,
|
86
|
+
dr = 0; // Math.sqrt(dx * dx + dy * dy);
|
87
|
+
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
|
88
|
+
}
|
89
|
+
|
90
|
+
function dragstarted(d) {
|
91
|
+
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
|
92
|
+
d3.select(this).classed("fixed", true );
|
93
|
+
d.fx = d.x;
|
94
|
+
d.fy = d.y;
|
95
|
+
}
|
96
|
+
|
97
|
+
function dragged(d) {
|
98
|
+
d.fx = d3.event.x;
|
99
|
+
d.fy = d3.event.y;
|
100
|
+
}
|
101
|
+
|
102
|
+
function dragended(d) {
|
103
|
+
if (!d3.event.active) simulation.alphaTarget(0);
|
104
|
+
}
|
105
|
+
|
106
|
+
function dblclick(d) {
|
107
|
+
var node = d3.select(this);
|
108
|
+
node.classed("fixed", false);
|
109
|
+
d.fx = null;
|
110
|
+
d.fy = null;
|
111
|
+
}
|
112
|
+
|
113
|
+
function transform(d) {
|
114
|
+
return "translate(" + d.x + "," + d.y + ")";
|
115
|
+
}
|
116
|
+
|
117
|
+
return true;
|
118
|
+
|
119
|
+
};
|
120
|
+
|
121
|
+
$(function(){ParseGraph();});
|