rubrowser 0.1.6 → 0.2.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 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();});