rubrowser 0.2.7 → 0.3.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: fd6da7e91cc1f4b7e50ba33d062baa579f2054d5
4
- data.tar.gz: 400349b68d33f44c42c0b7571318eab0ec675c96
3
+ metadata.gz: 663f1d03a44347696b31955a262839e433933415
4
+ data.tar.gz: fcd25e95549c33597fc2635ab9304d3a8c6296da
5
5
  SHA512:
6
- metadata.gz: be689e37c1507f092ad03e735e78e6464b2f63100e42c134e70fda6350277881cb5ab87d812a34504928bd3e6be72db8565712c9b5731261804159d73ff5650a
7
- data.tar.gz: b21b259b207cabe0c822012a04b4e6ecdc5070685403c94d35f0712d61c47a8ad74b70465893d33e48f1e599ea91f8c95fa347ab2a273042f103a7edbbb3f9fd
6
+ metadata.gz: a950758287c25f65f648e029e180846ab6624c0df49b4a48cc5d3415bcca13c7de1f3ede57be426a6158b154f57089f7006dff2925c086c08357afb28baf6cf1
7
+ data.tar.gz: dafc101e84347031ac0f59eb14c5bdad3929e11f0956c4c822d34c363b4dbcfde14d207815eb72249f276deb78a9db5d93636b9f85b8543e8015734824c4a78d
data/.gitignore CHANGED
@@ -1,2 +1,5 @@
1
1
  tmp/
2
- pkg/
2
+ pkg/
3
+ .DS_Store
4
+ .idea
5
+ .byebug_history
data/.hound.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .rubocop.yml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - 'spec/**/*_spec.rb'
1
4
  Style/Documentation:
2
5
  Enabled: false
3
6
  Style/FrozenStringLiteralComment:
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
  gemspec
data/Gemfile.lock CHANGED
@@ -1,24 +1,55 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubrowser (0.2.7)
4
+ rubrowser (0.3.0)
5
5
  parser (~> 2.3, >= 2.3.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.2.0)
11
+ byebug (9.0.6)
12
+ diff-lcs (1.3)
13
+ parallel (1.12.0)
11
14
  parser (2.4.0.0)
12
15
  ast (~> 2.2)
16
+ powerpack (0.1.1)
17
+ rainbow (2.2.2)
18
+ rake
13
19
  rake (10.4.2)
20
+ rspec (3.6.0)
21
+ rspec-core (~> 3.6.0)
22
+ rspec-expectations (~> 3.6.0)
23
+ rspec-mocks (~> 3.6.0)
24
+ rspec-core (3.6.0)
25
+ rspec-support (~> 3.6.0)
26
+ rspec-expectations (3.6.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.6.0)
29
+ rspec-mocks (3.6.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.6.0)
32
+ rspec-support (3.6.0)
33
+ rubocop (0.49.1)
34
+ parallel (~> 1.10)
35
+ parser (>= 2.3.3.1, < 3.0)
36
+ powerpack (~> 0.1)
37
+ rainbow (>= 1.99.1, < 3.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (~> 1.0, >= 1.0.1)
40
+ ruby-progressbar (1.8.1)
41
+ unicode-display_width (1.3.0)
14
42
 
15
43
  PLATFORMS
16
44
  ruby
17
45
 
18
46
  DEPENDENCIES
19
47
  bundler (~> 1.14)
48
+ byebug
20
49
  rake (~> 10.0)
50
+ rspec
51
+ rubocop
21
52
  rubrowser!
22
53
 
23
54
  BUNDLED WITH
24
- 1.14.5
55
+ 1.15.2
data/Rakefile CHANGED
@@ -1,10 +1,18 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rubocop/rake_task'
3
+ require 'rspec/core/rake_task'
3
4
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
5
+ task :server do
6
+ ruby './bin/rubrowser'
8
7
  end
9
8
 
10
- task default: :test
9
+ RuboCop::RakeTask.new(:rubocop) do |t|
10
+ t.options = ['--display-cop-names']
11
+ end
12
+
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ task all: %I[spec rubocop] do
16
+ end
17
+
18
+ task default: :all
data/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-minimal
data/bin/rubrowser CHANGED
@@ -5,7 +5,7 @@ require 'optparse'
5
5
  require 'rubrowser'
6
6
  require 'rubrowser/server'
7
7
 
8
- OPTIONS = {
8
+ options = {
9
9
  port: 9000,
10
10
  files: ARGV.empty? ? ['.'] : ARGV
11
11
  }
@@ -13,8 +13,9 @@ OPTIONS = {
13
13
  OptionParser.new do |opts|
14
14
  opts.banner = "Usage: #{__FILE__} [options] [file] ..."
15
15
 
16
- opts.on('-pPORT', '--port=PORT', 'Specify port number for server, default = 9000') do |port|
17
- OPTIONS[:port] = port.to_i
16
+ message = 'Specify port number for server, default = 9000'
17
+ opts.on('-pPORT', '--port=PORT', message) do |port|
18
+ options[:port] = port.to_i
18
19
  end
19
20
 
20
21
  opts.on('-v', '--version', 'Print Rubrowser version') do
@@ -28,4 +29,4 @@ OptionParser.new do |opts|
28
29
  end
29
30
  end.parse!
30
31
 
31
- Rubrowser::Server.start(OPTIONS)
32
+ Rubrowser::Server.start(options)
@@ -1,30 +1,24 @@
1
1
  require 'rubrowser/parser/factory'
2
+ require 'rubrowser/graph'
2
3
 
3
4
  module Rubrowser
4
5
  class Data
6
+ attr_reader :definitions, :relations
7
+
5
8
  def initialize(files)
6
9
  @files = files
7
- @parsed = false
8
10
  parse
9
11
  end
10
12
 
11
- def definitions
12
- @_definitions ||= parsers.map(&:definitions).reduce(:+).to_a
13
- end
14
-
15
- def relations
16
- @_relations ||= parsers.map(&:relations).reduce(:+).to_a
17
- end
18
-
19
13
  private
20
14
 
21
- attr_reader :files, :parsed
22
- alias parsed? parsed
23
-
24
15
  def parse
25
- return if parsed?
26
16
  parsers.each(&:parse)
27
- @parsed = true
17
+
18
+ @definitions ||= parsers.map(&:definitions).reduce(:+).to_a
19
+ @relations ||= parsers.map(&:relations).reduce(:+).to_a
20
+
21
+ mark_circular_dependencies
28
22
  end
29
23
 
30
24
  def parsers
@@ -32,5 +26,44 @@ module Rubrowser
32
26
  Rubrowser::Parser::Factory.build(file)
33
27
  end
34
28
  end
29
+
30
+ def mark_circular_dependencies
31
+ components = make_components
32
+ @definitions.each do |definition|
33
+ if components.include?(definition.namespace.first.to_s)
34
+ definition.set_circular
35
+ end
36
+ end
37
+
38
+ @relations.each do |relation|
39
+ relation.set_circular if circular_relation?(components, relation)
40
+ end
41
+ end
42
+
43
+ def make_components
44
+ graph = Graph.new { |h, k| h[k] = [] }
45
+
46
+ @relations.each do |relation|
47
+ graph[relation.caller_namespace.to_s] <<
48
+ relation.resolve(definitions).to_s
49
+ end
50
+
51
+ find_coupled_components(graph)
52
+ end
53
+
54
+ def find_coupled_components(graph)
55
+ graph
56
+ .strongly_connected_components
57
+ .select { |c| c.length > 1 }
58
+ .flatten
59
+ .to_set
60
+ end
61
+
62
+ def circular_relation?(components, relation)
63
+ components.include?(relation.namespace.to_s) &&
64
+ components.include?(relation.caller_namespace.to_s)
65
+ end
66
+
67
+ attr_reader :files
35
68
  end
36
69
  end
@@ -10,7 +10,9 @@ module Rubrowser
10
10
  def call
11
11
  {
12
12
  definitions: data.definitions.map { |d| definition_as_json(d) },
13
- relations: data.relations.map { |r| relation_as_json(r, data.definitions) }
13
+ relations: data.relations.map do |r|
14
+ relation_as_json(r, data.definitions)
15
+ end
14
16
  }.to_json
15
17
  end
16
18
 
@@ -22,6 +24,7 @@ module Rubrowser
22
24
  {
23
25
  type: demoularize(definition.class.name),
24
26
  namespace: definition.to_s,
27
+ circular: definition.circular?,
25
28
  file: definition.file,
26
29
  line: definition.line,
27
30
  lines: definition.lines
@@ -35,6 +38,7 @@ module Rubrowser
35
38
  resolved_namespace: relation.resolve(definitions).to_s,
36
39
  caller: relation.caller_namespace.to_s,
37
40
  file: relation.file,
41
+ circular: relation.circular?,
38
42
  line: relation.line
39
43
  }
40
44
  end
@@ -0,0 +1,11 @@
1
+ require 'tsort'
2
+
3
+ class Graph < Hash
4
+ include TSort
5
+
6
+ alias tsort_each_node each_key
7
+
8
+ def tsort_each_child(node, &block)
9
+ fetch(node) { [] }.each(&block)
10
+ end
11
+ end
@@ -9,6 +9,7 @@ module Rubrowser
9
9
  @file = file
10
10
  @line = line
11
11
  @lines = lines
12
+ @circular = false
12
13
  end
13
14
 
14
15
  def name
@@ -23,6 +24,14 @@ module Rubrowser
23
24
  namespace.empty?
24
25
  end
25
26
 
27
+ def circular?
28
+ @circular
29
+ end
30
+
31
+ def set_circular
32
+ @circular = true
33
+ end
34
+
26
35
  def ==(other)
27
36
  namespace == other.namespace
28
37
  end
@@ -19,22 +19,29 @@ module Rubrowser
19
19
 
20
20
  def parse
21
21
  return unless valid_file?(file)
22
+ constants = constants_from_file
23
+
24
+ @definitions = constants[:definitions]
25
+ @relations = constants[:relations]
26
+ rescue ::Parser::SyntaxError
27
+ warn "SyntaxError in #{file}"
28
+ end
29
+
30
+ def constants_from_file
22
31
  contents = ::File.read(file)
23
32
 
24
33
  buffer = ::Parser::Source::Buffer.new(file, 1)
25
34
  buffer.source = contents.force_encoding(Encoding::UTF_8)
26
35
 
36
+ ast = parser.parse(buffer)
37
+ parse_block(ast)
38
+ end
39
+
40
+ def parser
27
41
  parser = ::Parser::CurrentRuby.new(Builder.new)
28
42
  parser.diagnostics.ignore_warnings = true
29
43
  parser.diagnostics.all_errors_are_fatal = false
30
-
31
- ast = parser.parse(buffer)
32
- constants = parse_block(ast)
33
-
34
- @definitions = constants[:definitions]
35
- @relations = constants[:relations]
36
- rescue ::Parser::SyntaxError
37
- warn "SyntaxError in #{file}"
44
+ parser
38
45
  end
39
46
 
40
47
  def valid_file?(file)
@@ -58,26 +65,25 @@ module Rubrowser
58
65
 
59
66
  def parse_module(node, parents = [])
60
67
  namespace = ast_consts_to_array(node.children.first, parents)
61
- definition = Definition::Module.new(
62
- namespace,
63
- file: file,
64
- line: node.loc.line,
65
- lines: node.loc.last_line - node.loc.line + 1
66
- )
68
+ definition = build_definition(Definition::Module, namespace, node)
67
69
  constants = { definitions: [definition] }
68
70
  children_constants = parse_array(node.children[1..-1], namespace)
69
71
 
70
72
  merge_constants(children_constants, constants)
71
73
  end
72
74
 
73
- def parse_class(node, parents = [])
74
- namespace = ast_consts_to_array(node.children.first, parents)
75
- definition = Definition::Class.new(
75
+ def build_definition(klass, namespace, node)
76
+ klass.new(
76
77
  namespace,
77
78
  file: file,
78
79
  line: node.loc.line,
79
80
  lines: node.loc.last_line - node.loc.line + 1
80
81
  )
82
+ end
83
+
84
+ def parse_class(node, parents = [])
85
+ namespace = ast_consts_to_array(node.children.first, parents)
86
+ definition = build_definition(Definition::Class, namespace, node)
81
87
  constants = { definitions: [definition] }
82
88
  children_constants = parse_array(node.children[1..-1], namespace)
83
89
 
@@ -111,7 +117,7 @@ module Rubrowser
111
117
 
112
118
  def ast_consts_to_array(node, parents = [])
113
119
  return parents unless valid_node?(node) &&
114
- [:const, :cbase].include?(node.type)
120
+ %I[const cbase].include?(node.type)
115
121
  ast_consts_to_array(node.children.first, parents) + [node.children.last]
116
122
  end
117
123
 
@@ -11,6 +11,7 @@ module Rubrowser
11
11
  @caller_namespace = caller_namespace
12
12
  @file = file
13
13
  @line = line
14
+ @is_circular = false
14
15
  end
15
16
 
16
17
  def namespace
@@ -21,6 +22,14 @@ module Rubrowser
21
22
  Definition::Base.new(@caller_namespace, file: file, line: line)
22
23
  end
23
24
 
25
+ def circular?
26
+ @is_circular
27
+ end
28
+
29
+ def set_circular
30
+ @is_circular = true
31
+ end
32
+
24
33
  def resolve(definitions)
25
34
  possibilities.find do |possibility|
26
35
  definitions.any? { |definition| definition == possibility }
@@ -29,7 +38,8 @@ module Rubrowser
29
38
 
30
39
  def ==(other)
31
40
  namespace == other.namespace &&
32
- caller_namespace == other.caller_namespace
41
+ caller_namespace == other.caller_namespace &&
42
+ circular? == other.circular?
33
43
  end
34
44
 
35
45
  private
@@ -1,3 +1,3 @@
1
1
  module Rubrowser
2
- VERSION = '0.2.7'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -27,6 +27,10 @@ circle {
27
27
  stroke-width: 1.5px;
28
28
  }
29
29
 
30
+ .circular {
31
+ stroke: #FF0000;
32
+ }
33
+
30
34
  .fixed circle {
31
35
  stroke-width: 3px;
32
36
  }
@@ -4,6 +4,10 @@ d3.json("/data.json", function(error, data) {
4
4
  $('.toolbox').show();
5
5
  });
6
6
 
7
+ var classForCircular = function(d) {
8
+ return d.circular ? 'circular' : '';
9
+ };
10
+
7
11
  var parseGraph = function(data){
8
12
  var svg = d3.select(".dependency_graph svg"),
9
13
  $svg = $('.dependency_graph svg'),
@@ -13,16 +17,17 @@ var parseGraph = function(data){
13
17
  .on("start", dragstarted)
14
18
  .on("drag", dragged)
15
19
  .on("end", dragended),
16
- dup_definitions = data.definitions.map(function(d){ return {id: d.namespace, type: d.type, lines: d.lines }; }),
20
+ dup_definitions = data.definitions.map(function(d){ return {id: d.namespace, type: d.type, lines: d.lines, circular: d.circular }; }),
17
21
  definitions = _(dup_definitions).groupBy('id').map(function(group) {
18
22
  return {
19
23
  id: group[0].id,
20
24
  type: group[0].type,
21
- lines: _(group).sumBy('lines')
25
+ lines: _(group).sumBy('lines'),
26
+ circular: group[0].circular
22
27
  };
23
28
  }).value(),
24
29
  namespaces = definitions.map(function(d){ return d.id; }),
25
- relations = data.relations.map(function(d){ return {source: d.caller, target: d.resolved_namespace }; }),
30
+ relations = data.relations.map(function(d){ return {source: d.caller, target: d.resolved_namespace, circular: d.circular}; }),
26
31
  max_lines = _.maxBy(definitions, 'lines').lines,
27
32
  max_circle_r = 50;
28
33
 
@@ -58,7 +63,7 @@ var parseGraph = function(data){
58
63
  .selectAll("path")
59
64
  .data(relations)
60
65
  .enter().append("path")
61
- .attr("class", 'link')
66
+ .attr("class", function(d) { return 'link ' + classForCircular(d); })
62
67
  .attr("marker-end", function(d){ return "url(#" + d.target.id + ")"; }),
63
68
  node = container.append("g")
64
69
  .attr("class", "nodes")
@@ -69,7 +74,8 @@ var parseGraph = function(data){
69
74
  .on("dblclick", dblclick),
70
75
  circle = node
71
76
  .append("circle")
72
- .attr("r", function(d) { return d.lines / max_lines * max_circle_r + 6; }),
77
+ .attr("r", function(d) { return d.lines / max_lines * max_circle_r + 6; })
78
+ .attr("class", function (d) { return classForCircular(d) ; }),
73
79
  type = node
74
80
  .append("text")
75
81
  .attr("class", "type")
data/readme.md CHANGED
@@ -60,11 +60,8 @@ it'll analyze the current directory and open port 9000, so you can access the gr
60
60
  * hide relations
61
61
  * change graph appearance (collision radius)
62
62
  * stop animation immediately
63
-
64
-
65
- ## Tests?
66
-
67
- What tests? :smile:
63
+ * Module/class circle size on the graph will be relative to module number of lines in your code
64
+ * cyclical dependencies are marked in red
68
65
 
69
66
  ## Why?
70
67
 
data/rubrowser.gemspec CHANGED
@@ -22,4 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.add_runtime_dependency 'parser', '~> 2.3', '>= 2.3.0'
23
23
  s.add_development_dependency 'bundler', '~> 1.14'
24
24
  s.add_development_dependency 'rake', '~> 10.0'
25
+ s.add_development_dependency 'byebug'
26
+ s.add_development_dependency 'rubocop'
27
+ s.add_development_dependency 'rspec'
25
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubrowser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emad Elsaid
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-27 00:00:00.000000000 Z
11
+ date: 2017-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -58,6 +58,48 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '10.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: byebug
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rubocop
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
61
103
  description: A ruby interactive dependency graph visualizer
62
104
  email:
63
105
  - blazeeboy@gmail.com
@@ -67,15 +109,19 @@ extensions: []
67
109
  extra_rdoc_files: []
68
110
  files:
69
111
  - ".gitignore"
112
+ - ".hound.yml"
113
+ - ".rspec"
70
114
  - ".rubocop.yml"
71
115
  - Gemfile
72
116
  - Gemfile.lock
73
117
  - MIT-LICENSE
74
118
  - Rakefile
119
+ - _config.yml
75
120
  - bin/rubrowser
76
121
  - lib/rubrowser.rb
77
122
  - lib/rubrowser/data.rb
78
123
  - lib/rubrowser/formatter/json.rb
124
+ - lib/rubrowser/graph.rb
79
125
  - lib/rubrowser/parser/definition/base.rb
80
126
  - lib/rubrowser/parser/definition/class.rb
81
127
  - lib/rubrowser/parser/definition/module.rb