rubrowser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ Bundler.require(:default)
4
+
5
+ require 'server'
6
+ Rubrowser::Server.start
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Rubrowser
2
+ VERSION = '0.1.0'.freeze
3
+ end
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();});