rubrowser 0.2.7 → 0.3.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: 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