rubrowser 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89bd09805ec1de06cd644cdeaea90d6476b30198
4
- data.tar.gz: aca07615cb33ab42ac46b36e605c5f736df98d56
3
+ metadata.gz: 533883a63e4f0656b260f7495ea2a62acc82ef64
4
+ data.tar.gz: 2bb665c87d91821898dc4e5965289291dc79809b
5
5
  SHA512:
6
- metadata.gz: b4b93d8dd79f946fa1d6f25e582646b5231ae7ba2d0694a8aa01528e1c35a5c5aae7dd686a1b86907717a6ad3589b2e8caf3dcdd35c03994accee42988a2cee4
7
- data.tar.gz: cdb14c5309c34579a627837f57ba0b36198973cc52666f52dba291caa82d73b400cd2a0e8cd76292ee16b13cde701a40948a36bbacdcd028fa96ca8d3ae68504
6
+ metadata.gz: 457739897ae0990378db89a4c8bdf95d6f5e21ff3d2b5bef310d21318e5c33f8216a0df006aa954710a8b0e32fce86995e8d7ac40e6439ff8bbd4d0c4ad38768
7
+ data.tar.gz: 3923cc2724f4f0602ef7a8651ad84852274f4536f53c62ac8e9188d64573178b228cd3a7d31de109343522b9491ee8206976fd72b5d30b79c0565b2409093f2a
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ Style/Documentation:
2
+ Enabled: false
3
+ Style/FrozenStringLiteralComment:
4
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubrowser (0.1.6)
4
+ rubrowser (0.2.0)
5
5
  parser (~> 2.3, >= 2.3.0)
6
6
 
7
7
  GEM
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Emad Elsaid
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/data.rb CHANGED
@@ -1,30 +1,29 @@
1
1
  require 'parser/factory'
2
- require 'tree'
3
- require 'd3'
4
2
 
5
3
  module Rubrowser
6
4
  class Data
7
- def initialize(paths)
8
- @files = paths
5
+ def initialize(files)
6
+ @files = files
9
7
  @parsed = false
8
+ parse
10
9
  end
11
10
 
12
- def constants
13
- @_constants ||= d3.constants.to_a
11
+ def definitions
12
+ @_constants ||= parsers.map(&:definitions).reduce(:+).to_a
14
13
  end
15
14
 
16
- def occurences
17
- @_occurences ||= d3.occurences.to_a
15
+ def relations
16
+ @_relations ||= parsers.map(&:relations).reduce(:+).to_a
18
17
  end
19
18
 
19
+ private
20
+
20
21
  def parse
21
22
  return if parsed?
22
23
  parsers.each(&:parse)
23
24
  @parsed = true
24
25
  end
25
26
 
26
- private
27
-
28
27
  attr_reader :files, :parsed
29
28
  alias parsed? parsed
30
29
 
@@ -33,15 +32,5 @@ module Rubrowser
33
32
  Rubrowser::Parser::Factory.build(file)
34
33
  end
35
34
  end
36
-
37
- def d3
38
- @_d3 ||= Rubrowser::D3.new(tree)
39
- end
40
-
41
- def tree
42
- parse
43
- @_tree ||= Rubrowser::Tree.from_parsers(parsers)
44
- end
45
-
46
35
  end
47
36
  end
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+
3
+ module Rubrowser
4
+ module Formatter
5
+ class JSON
6
+ def initialize(data)
7
+ @data = data
8
+ end
9
+
10
+ def call
11
+ {
12
+ definitions: data.definitions.map { |d| definition_as_json(d) },
13
+ relations: data.relations.map { |r| relation_as_json(r, data.definitions) }
14
+ }.to_json
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :data
20
+
21
+ def definition_as_json(definition)
22
+ {
23
+ type: demoularize(definition.class.name),
24
+ namespace: definition.to_s,
25
+ file: definition.file,
26
+ line: definition.line
27
+ }
28
+ end
29
+
30
+ def relation_as_json(relation, definitions)
31
+ {
32
+ type: demoularize(relation.class.name),
33
+ namespace: relation.namespace.to_s,
34
+ resolved_namespace: relation.resolve(definitions).to_s,
35
+ caller: relation.caller_namespace.to_s,
36
+ file: relation.file,
37
+ line: relation.line
38
+ }
39
+ end
40
+
41
+ def demoularize(class_name)
42
+ class_name.split('::').last || ''
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ module Rubrowser
2
+ module Parser
3
+ module Definition
4
+ class Base
5
+ attr_reader :namespace, :file, :line
6
+
7
+ def initialize(namespace, file: nil, line: nil)
8
+ @namespace = Array(namespace)
9
+ @file = file
10
+ @line = line
11
+ end
12
+
13
+ def name
14
+ namespace.last
15
+ end
16
+
17
+ def parent
18
+ new(namespace[0...-1])
19
+ end
20
+
21
+ def kernel?
22
+ namespace.empty?
23
+ end
24
+
25
+ def ==(other)
26
+ namespace == other.namespace
27
+ end
28
+
29
+ def to_s
30
+ namespace.join('::')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ require 'parser/definition/base'
2
+
3
+ module Rubrowser
4
+ module Parser
5
+ module Definition
6
+ class Class < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'parser/definition/base'
2
+
3
+ module Rubrowser
4
+ module Parser
5
+ module Definition
6
+ class Module < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -17,8 +17,8 @@ module Rubrowser
17
17
  parsers.map(&:definitions).map(&:to_a).reduce([], :+)
18
18
  end
19
19
 
20
- def occurences
21
- parsers.map(&:occurences).map(&:to_a).reduce([], :+)
20
+ def relations
21
+ parsers.map(&:relations).map(&:to_a).reduce([], :+)
22
22
  end
23
23
 
24
24
  private
data/lib/parser/file.rb CHANGED
@@ -1,27 +1,29 @@
1
1
  require 'parser/current'
2
+ require 'parser/definition/class'
3
+ require 'parser/definition/module'
4
+ require 'parser/relation/base'
2
5
 
3
6
  module Rubrowser
4
7
  module Parser
5
8
  class File
6
9
  FILE_SIZE_LIMIT = 2 * 1024 * 1024
7
10
 
8
- attr_reader :file, :definitions, :occurences
11
+ attr_reader :file, :definitions, :relations
9
12
 
10
13
  def initialize(file)
11
- @file = file
14
+ @file = ::File.absolute_path(file)
12
15
  @definitions = []
13
- @occurences = []
16
+ @relations = []
14
17
  end
15
18
 
16
19
  def parse
17
20
  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
- @definitions = constants[:definitions].uniq
23
- @occurences = constants[:occurences].uniq
24
- end
21
+ code = ::File.read(file)
22
+ ast = ::Parser::CurrentRuby.parse(code)
23
+ constants = parse_block(ast)
24
+
25
+ @definitions = constants[:definitions]
26
+ @relations = constants[:relations]
25
27
  end
26
28
  rescue ::Parser::SyntaxError
27
29
  warn "SyntaxError in #{file}"
@@ -36,62 +38,79 @@ module Rubrowser
36
38
  private
37
39
 
38
40
  def parse_block(node, parents = [])
39
- return { definitions: [], occurences: [] } unless node.is_a?(::Parser::AST::Node)
41
+ return empty_result unless valid_node?(node)
40
42
 
41
43
  case node.type
42
- when :module
43
- parse_module(node, parents)
44
- when :class
45
- parse_class(node, parents)
46
- when :const
47
- parse_const(node, parents)
48
- else
49
- node
50
- .children
51
- .map { |n| parse_block(n, parents) }
52
- .reduce { |a, e| merge_constants(a, e) } || { definitions: [], occurences: [] }
44
+ when :module then parse_module(node, parents)
45
+ when :class then parse_class(node, parents)
46
+ when :const then parse_const(node, parents)
47
+ else parse_array(node.children, parents)
53
48
  end
54
49
  end
55
50
 
56
51
  def parse_module(node, parents = [])
57
- name = resolve_const_path(node.children.first, parents)
58
- node
59
- .children[1..-1]
60
- .map { |n| parse_block(n, name) }
61
- .reduce { |a, e| merge_constants(a, e) }
62
- .tap { |constants| constants[:definitions].unshift(name) }
52
+ namespace = ast_consts_to_array(node.children.first, parents)
53
+ definition = Definition::Module.new(
54
+ namespace,
55
+ file: file,
56
+ line: node.loc.line
57
+ )
58
+ constants = { definitions: [definition] }
59
+ children_constants = parse_array(node.children[1..-1], namespace)
60
+
61
+ merge_constants(children_constants, constants)
63
62
  end
64
63
 
65
64
  def parse_class(node, parents = [])
66
- name = resolve_const_path(node.children.first, parents)
67
- node
68
- .children[1..-1]
69
- .map { |n| parse_block(n, name) }
70
- .reduce { |a, e| merge_constants(a, e) }
71
- .tap { |constants| constants[:definitions].unshift(name) }
65
+ namespace = ast_consts_to_array(node.children.first, parents)
66
+ definition = Definition::Class.new(
67
+ namespace,
68
+ file: file,
69
+ line: node.loc.line
70
+ )
71
+ constants = { definitions: [definition] }
72
+ children_constants = parse_array(node.children[1..-1], namespace)
73
+
74
+ merge_constants(children_constants, constants)
72
75
  end
73
76
 
74
77
  def parse_const(node, parents = [])
75
- constant = resolve_const_path(node)
76
- namespace = parents[0...-1]
77
- constants = if namespace.empty? || constant.first.nil?
78
- [{ parents => [constant.compact] }]
79
- else
80
- [{ parents => (namespace.size-1).downto(0).map { |i| namespace[0..i] + constant }.push(constant) }]
81
- end
82
- { definitions: [], occurences: constants }
78
+ constant = ast_consts_to_array(node)
79
+ definition = Relation::Base.new(
80
+ constant,
81
+ parents,
82
+ file: file,
83
+ line: node.loc.line
84
+ )
85
+ { relations: [definition] }
86
+ end
87
+
88
+ def parse_array(arr, parents = [])
89
+ arr.map { |n| parse_block(n, parents) }
90
+ .reduce { |a, e| merge_constants(a, e) }
83
91
  end
84
92
 
85
93
  def merge_constants(c1, c2)
94
+ c1 ||= {}
95
+ c2 ||= {}
86
96
  {
87
- definitions: c1[:definitions] + c2[:definitions],
88
- occurences: c1[:occurences] + c2[:occurences]
97
+ definitions: c1[:definitions].to_a + c2[:definitions].to_a,
98
+ relations: c1[:relations].to_a + c2[:relations].to_a
89
99
  }
90
100
  end
91
101
 
92
- def resolve_const_path(node, parents = [])
93
- return parents unless node.is_a?(::Parser::AST::Node) && [:const, :cbase].include?(node.type)
94
- resolve_const_path(node.children.first, parents) + [node.children.last]
102
+ def ast_consts_to_array(node, parents = [])
103
+ return parents unless valid_node?(node) &&
104
+ [:const, :cbase].include?(node.type)
105
+ ast_consts_to_array(node.children.first, parents) + [node.children.last]
106
+ end
107
+
108
+ def empty_result
109
+ {}
110
+ end
111
+
112
+ def valid_node?(node)
113
+ node.is_a?(::Parser::AST::Node)
95
114
  end
96
115
  end
97
116
  end
@@ -0,0 +1,60 @@
1
+ require 'parser/definition/base'
2
+
3
+ module Rubrowser
4
+ module Parser
5
+ module Relation
6
+ class Base
7
+ attr_reader :namespace, :caller_namespace, :file, :line
8
+
9
+ def initialize(namespace, caller_namespace, file: nil, line: nil)
10
+ @namespace = namespace
11
+ @caller_namespace = caller_namespace
12
+ @file = file
13
+ @line = line
14
+ end
15
+
16
+ def namespace
17
+ Definition::Base.new(@namespace, file: file, line: line)
18
+ end
19
+
20
+ def caller_namespace
21
+ Definition::Base.new(@caller_namespace, file: file, line: line)
22
+ end
23
+
24
+ def resolve(definitions)
25
+ possibilities.find do |possibility|
26
+ definitions.any? { |definition| definition == possibility }
27
+ end || possibilities.last
28
+ end
29
+
30
+ def ==(other)
31
+ namespace == other.namespace &&
32
+ caller_namespace == other.caller_namespace
33
+ end
34
+
35
+ private
36
+
37
+ def possibilities
38
+ return [
39
+ Definition::Base.new(@namespace.compact, file: file, line: line)
40
+ ] if absolute?
41
+
42
+ possible_parent_namespaces
43
+ .map { |possible_parent| possible_parent + @namespace }
44
+ .push(@namespace)
45
+ .map { |i| Definition::Base.new(i, file: file, line: line) }
46
+ end
47
+
48
+ def possible_parent_namespaces
49
+ (@caller_namespace.size - 1)
50
+ .downto(0)
51
+ .map { |i| @caller_namespace[0..i] }
52
+ end
53
+
54
+ def absolute?
55
+ @caller_namespace.empty? || @namespace.first.nil?
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/rubrowser.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rubrowser
2
- VERSION = '0.1.6'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/server.rb CHANGED
@@ -1,38 +1,50 @@
1
1
  require 'webrick'
2
2
  require 'data'
3
- require 'json'
3
+ require 'formatter/json'
4
4
  require 'erb'
5
5
 
6
6
  module Rubrowser
7
7
  class Server < WEBrick::HTTPServer
8
8
  include ERB::Util
9
9
 
10
+ ROUTES = {
11
+ '/' => :root,
12
+ '/data.json' => :data
13
+ }
14
+
10
15
  def self.start(options = {})
11
16
  new(options).start
12
17
  end
13
18
 
14
19
  def initialize(options)
15
20
  super Port: options[:port]
16
-
17
- @data = Rubrowser::Data.new(options[:files])
18
- @data.parse
21
+ @files = options[:files]
19
22
 
20
23
  mount_proc '/' do |req, res|
21
- res.body = root(req.path)
24
+ res.body = router(req.path)
22
25
  end
23
-
24
- trap('INT') { shutdown }
25
26
  end
26
27
 
27
28
  private
28
29
 
29
- attr_reader :data
30
+ attr_reader :files
30
31
 
31
- def root(path)
32
+ def router(path)
32
33
  return file(path) if file?(path)
34
+ return send(ROUTES[path]) if ROUTES.key?(path)
35
+ 'Route not found.'
36
+ end
37
+
38
+ def root
33
39
  erb :index
34
40
  end
35
41
 
42
+ def data
43
+ data = Data.new(files)
44
+ formatter = Formatter::JSON.new(data)
45
+ formatter.call
46
+ end
47
+
36
48
  def file?(path)
37
49
  path = resolve_file_path("/public#{path}")
38
50
  File.file?(path)
@@ -1,16 +1,18 @@
1
- html, body{
1
+ html, body, .dependency_graph, .dependency_graph svg{
2
2
  width: 100%;
3
3
  height: 100%;
4
4
  padding: 0px;
5
5
  margin: 0px;
6
- }
7
- .dependency_graph, .dependency_graph svg{
8
- width: 100%;
9
- height: 100%;
6
+ font: 10px sans-serif;
10
7
  }
11
8
 
12
- line{
13
- stroke: #888;
9
+ .loading{
10
+ position: absolute;
11
+ top: 30px;
12
+ left: 0px;
13
+ text-align: center;
14
+ width: 100%;
15
+ font-size: 3rem;
14
16
  }
15
17
 
16
18
  .link {
@@ -20,13 +22,13 @@ line{
20
22
  }
21
23
 
22
24
  circle {
23
- fill: #ccc;
25
+ fill: #fff;
24
26
  stroke: #333;
25
27
  stroke-width: 1.5px;
26
28
  }
27
29
 
28
30
  .fixed circle {
29
- fill: #f00;
31
+ stroke-width: 3px;
30
32
  }
31
33
 
32
34
  text {
@@ -1,19 +1,25 @@
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
- var width = $svg.width(),
1
+ d3.json("/data.json", function(error, data) {
2
+ parseGraph(data);
3
+ $('.loading').hide();
4
+ });
5
+
6
+ var parseGraph = function(data){
7
+ var svg = d3.select(".dependency_graph svg"),
8
+ $svg = $('.dependency_graph svg'),
9
+ width = $svg.width(),
10
10
  height = $svg.height(),
11
- constants = JSON.parse(svg.attr('data-constants')),
12
- occurences = JSON.parse(svg.attr('data-occurences')),
13
11
  drag = d3.drag()
14
12
  .on("start", dragstarted)
15
13
  .on("drag", dragged)
16
- .on("end", dragended);
14
+ .on("end", dragended),
15
+ constants = _.uniqWith(data.definitions.map(function(d){ return {id: d.namespace}; }), _.isEqual),
16
+ namespaces = constants.map(function(d){ return d.id; }),
17
+ relations = data.relations.map(function(d){ return {source: d.caller, target: d.resolved_namespace }; });
18
+
19
+ relations = relations.filter(function(d){
20
+ return namespaces.indexOf(d.source) >= 0 && namespaces.indexOf(d.target) >= 0;
21
+ });
22
+ relations = _.uniqWith(relations, _.isEqual);
17
23
 
18
24
  var zoom = d3.zoom()
19
25
  .on("zoom", function () {
@@ -23,9 +29,8 @@ var ParseGraph = function(){
23
29
  svg.call(zoom)
24
30
  .on("dblclick.zoom", null);
25
31
 
26
- var container = svg.append('g');
27
-
28
- var simulation = d3.forceSimulation()
32
+ var container = svg.append('g'),
33
+ simulation = d3.forceSimulation()
29
34
  .force("link", d3.forceLink().id(function(d) { return d.id; }))
30
35
  .force("charge", d3.forceManyBody())
31
36
  .force("center", d3.forceCenter(width / 2, height / 2))
@@ -36,36 +41,33 @@ var ParseGraph = function(){
36
41
  .on("tick", ticked);
37
42
 
38
43
  simulation.force("link")
39
- .links(occurences);
44
+ .links(relations);
40
45
 
41
46
  var link = container.append("g")
42
47
  .attr("class", "links")
43
48
  .selectAll("path")
44
- .data(occurences)
49
+ .data(relations)
45
50
  .enter().append("path")
46
51
  .attr("class", 'link')
47
- .attr("marker-end", "url(#occurence)");
48
-
49
- var node = container.append("g")
52
+ .attr("marker-end", "url(#relation)"),
53
+ node = container.append("g")
50
54
  .attr("class", "nodes")
51
55
  .selectAll("g")
52
56
  .data(constants)
53
57
  .enter().append("g")
54
58
  .call(drag)
55
- .on("dblclick", dblclick);
56
-
57
- var circle = node
59
+ .on("dblclick", dblclick),
60
+ circle = node
58
61
  .append("circle")
59
- .attr("r", 6);
60
-
61
- var text = node
62
+ .attr("r", 6),
63
+ text = node
62
64
  .append("text")
63
65
  .attr("x", 8)
64
66
  .attr("y", ".31em")
65
67
  .text(function(d) { return d.id; });
66
68
 
67
69
  container.append("defs").selectAll("marker")
68
- .data(['occurence'])
70
+ .data(['relation'])
69
71
  .enter().append("marker")
70
72
  .attr("id", function(d) { return d; })
71
73
  .attr("viewBox", "0 -5 10 10")
@@ -144,5 +146,3 @@ var ParseGraph = function(){
144
146
  return true;
145
147
 
146
148
  };
147
-
148
- $(function(){ParseGraph();});