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 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();});