repo_dependency_graph 0.1.5 → 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: 578cc8fe75735e91415c393a959f0aad7df3a510
4
- data.tar.gz: f2201b99a11c80a5d7d8d7b870c416dc1181c99b
3
+ metadata.gz: 2f7e2fef071a05082e7a36ae97805147a46337b2
4
+ data.tar.gz: cc00557bde2101f361a08113394076aaa95f36ae
5
5
  SHA512:
6
- metadata.gz: 6245f4ab5d69260041e10ec63614c78d5079eae348f19450d444f3a8bf2a7772daa8a40e5dd14791ed538cf2659766333ed4d057943257e75fd36bc928adc4c6
7
- data.tar.gz: 9d3d92fe452b30a8d38cf7c23ab17345b60542dd58fafd53329883ff6107d0f1194fb3bea2fc5d64a658b68aebe27937f87e16516706a6c3603be610cd12c4e3
6
+ metadata.gz: 41150cb68c3c85cbe4b07b0ff113549b533a89aed9958ef354fd347f4efd9c14f5526f1106f6d27dfc6afdaada8c0fb7a0aa05db7824318a0210326bd6a632dc
7
+ data.tar.gz: 8c2577f04f2d56c2930181ff221ddb59bd561c101fcd1068c9f0f2f953818225b204fa42814bc819de5ee40bddfe63a4945e40dc5792aea4fa53e7e6fe3f5183
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
- require "rubygems"
3
2
  require "optparse"
4
3
 
5
4
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
6
- require "repo_dependency_graph"
5
+ require "repo_dependency_graph/cli"
7
6
 
8
- exit RepoDependencyGraph.run(ARGV)
7
+ exit RepoDependencyGraph::CLI.run(ARGV)
@@ -0,0 +1,62 @@
1
+ require 'repo_dependency_graph'
2
+ require 'repo_dependency_graph/output'
3
+
4
+ module RepoDependencyGraph
5
+ module CLI
6
+ class << self
7
+ def run(argv)
8
+ options = parse_options(argv)
9
+ RepoDependencyGraph::Output.draw(
10
+ RepoDependencyGraph.dependencies(options), options
11
+ )
12
+ 0
13
+ end
14
+
15
+ private
16
+
17
+ def parse_options(argv)
18
+ options = {
19
+ :user => git_config("github.user")
20
+ }
21
+ OptionParser.new do |opts|
22
+ opts.banner = <<-BANNER.gsub(/^ /, "")
23
+ Draw repo dependency graph from your organization
24
+
25
+ Usage:
26
+ repo-dependency-graph
27
+
28
+ Options:
29
+ BANNER
30
+ opts.on("--token TOKEN", "Use token") { |token| options[:token] = token }
31
+ opts.on("--user USER", "Use user") { |user| options[:user] = user }
32
+ opts.on("--draw TYPE", "png, html, table (default: png)") { |draw| options[:draw] = draw }
33
+ opts.on("--organization ORGANIZATION", "Use organization") { |organization| options[:organization] = organization }
34
+ opts.on("--private", "Only show private repos") { options[:private] = true }
35
+ opts.on("--external", "Also include external projects in graph (can get super-messy)") { options[:external] = true }
36
+ opts.on("--map SEARCH=REPLACE", "Replace in project name to find them as internal: 'foo=bar' -> replace foo in repo names to bar") do |map|
37
+ options[:map] = map.split("=")
38
+ options[:map][0] = Regexp.new(options[:map][0])
39
+ options[:map][1] = options[:map][1].to_s
40
+ end
41
+ opts.on("--only TYPE", String, "Only this type (chef,gem), default: all") { |t| options[:only] = t }
42
+ opts.on("--select REGEX", "Only include repos with matching names") { |regex| options[:select] = Regexp.new(regex) }
43
+ opts.on("--reject REGEX", "Exclude repos with matching names") { |regex| options[:reject] = Regexp.new(regex) }
44
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
45
+ opts.on("-v", "--version", "Show Version"){ puts RepoDependencyGraph::VERSION; exit}
46
+ end.parse!(argv)
47
+
48
+ options[:token] ||= begin
49
+ token = `git config github.token`.strip
50
+ token if $?.success?
51
+ end
52
+
53
+ options
54
+ end
55
+
56
+ def git_config(thing)
57
+ result = `git config #{thing}`.strip
58
+ result.empty? ? nil : result
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,155 @@
1
+ module RepoDependencyGraph
2
+ module Output
3
+ MAX_HEX = 255
4
+
5
+ class << self
6
+ def draw(dependencies, options)
7
+ case options[:draw]
8
+ when "html"
9
+ draw_js(dependencies)
10
+ when "table"
11
+ draw_table(dependencies)
12
+ else
13
+ draw_png(dependencies)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def draw_js(dependencies)
20
+ nodes, edges = convert_to_graphviz(dependencies)
21
+ html = <<-HTML.gsub(/^ /, "")
22
+ <!doctype html>
23
+ <html>
24
+ <head>
25
+ <title>Network</title>
26
+ <style>
27
+ #mynetwork {
28
+ width: 2000px;
29
+ height: 2000px;
30
+ border: 1px solid lightgray;
31
+ background: #F3F3F3;
32
+ }
33
+ </style>
34
+
35
+ <script type="text/javascript" src="http://visjs.org/dist/vis.js"></script>
36
+ <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
37
+
38
+ <script type="text/javascript">
39
+ var nodes = null;
40
+ var edges = null;
41
+ var network = null;
42
+
43
+ function draw() {
44
+ nodes = #{nodes.values.to_json};
45
+ edges = #{edges.to_json};
46
+
47
+ var container = document.getElementById('mynetwork');
48
+ var data = {
49
+ nodes: nodes,
50
+ edges: edges
51
+ };
52
+ var options = {stabilize: false};
53
+
54
+ new vis.Network(container, data, options);
55
+ }
56
+ </script>
57
+ </head>
58
+
59
+ <body onload="draw()">
60
+ <div id="mynetwork"></div>
61
+ </body>
62
+ </html>
63
+ HTML
64
+ File.write("out.html", html)
65
+ end
66
+
67
+ def draw_table(dependencies)
68
+ tables = dependencies.map do |name, uses|
69
+ used = dependencies.map do |d, uses|
70
+ used = uses.detect { |d| d.first == name }
71
+ [d, used.last] if used
72
+ end.compact
73
+ size = [used.size, uses.size, 1].max
74
+ table = []
75
+ size.times do |i|
76
+ table[i] = [
77
+ (used[i] || []).join(": "),
78
+ (name if i == 0),
79
+ (uses[i] || []).join(": ")
80
+ ]
81
+ end
82
+ table.unshift ["Used", "", "Uses"]
83
+ table
84
+ end
85
+ tables.map! { |t| "<table>\n#{t.map { |t| "<tr>#{t.map { |t| "<td>#{t}</td>" }.join("")}</tr>" }.join("\n")}\n</table>" }
86
+
87
+ html = <<-HTML.gsub(/^ /, "")
88
+ <!doctype html>
89
+ <html>
90
+ <head>
91
+ <title>Network</title>
92
+ <style>
93
+ table { width: 600px; }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ #{tables.join("<br>\n<br>\n")}
98
+ </body>
99
+ </html>
100
+ HTML
101
+ File.write("out.html", html)
102
+ end
103
+
104
+ def draw_png(dependencies)
105
+ nodes, edges = convert_to_graphviz(dependencies)
106
+ require 'graphviz'
107
+ g = GraphViz.new(:G, :type => :digraph)
108
+
109
+ nodes = Hash[nodes.map do |_, data|
110
+ node = g.add_node(data[:id], :color => data[:color], :style => "filled")
111
+ [data[:id], node]
112
+ end]
113
+
114
+ edges.each do |edge|
115
+ g.add_edge(nodes[edge[:from]], nodes[edge[:to]], :label => edge[:label])
116
+ end
117
+
118
+ g.output(:png => "out.png")
119
+ end
120
+
121
+ def convert_to_graphviz(dependencies)
122
+ counts = dependency_counts(dependencies)
123
+ range = counts.values.min..counts.values.max
124
+ nodes = Hash[counts.each_with_index.map do |(name, count), i|
125
+ [name, {:id => name, :color => color(count, range)}]
126
+ end]
127
+ edges = dependencies.map do |name, dependencies|
128
+ dependencies.map do |dependency, version|
129
+ {:from => nodes[name][:id], :to => nodes[dependency][:id], :label => (version || '')}
130
+ end
131
+ end.flatten
132
+ [nodes, edges]
133
+ end
134
+
135
+
136
+ def color(value, range)
137
+ value -= range.min # lowest -> green
138
+ max = range.max - range.min
139
+
140
+ i = (value * MAX_HEX / max);
141
+ i *= 0.6 # green-blue gradient instead of green-green
142
+ half = MAX_HEX * 0.5
143
+ values = [0,2,4].map { |v| (Math.sin(0.024 * i + v) * half + half).round.to_s(16).rjust(2, "0") }
144
+ "##{values.join}"
145
+ end
146
+
147
+ def dependency_counts(dependencies)
148
+ all = (dependencies.keys + dependencies.values.map { |v| v.map(&:first) }).flatten.uniq
149
+ Hash[all.map do |k|
150
+ [k, dependencies.values.map(&:first).count { |name, _| name == k } ]
151
+ end]
152
+ end
153
+ end
154
+ end
155
+ end
@@ -1,3 +1,3 @@
1
1
  module RepoDependencyGraph
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -4,175 +4,6 @@ require "bundler" # get all dependency for lockfile_parser
4
4
 
5
5
  module RepoDependencyGraph
6
6
  class << self
7
- MAX_HEX = 255
8
-
9
- def run(argv)
10
- options = parse_options(argv)
11
- draw(dependencies(options), options)
12
- 0
13
- end
14
-
15
- private
16
-
17
- def parse_options(argv)
18
- options = {
19
- :user => git_config("github.user")
20
- }
21
- OptionParser.new do |opts|
22
- opts.banner = <<-BANNER.gsub(/^ {10}/, "")
23
- Draw repo dependency graph from your organization
24
-
25
- Usage:
26
- repo-dependency-graph
27
-
28
- Options:
29
- BANNER
30
- opts.on("--token TOKEN", "Use token") { |token| options[:token] = token }
31
- opts.on("--user USER", "Use user") { |user| options[:user] = user }
32
- opts.on("--draw TYPE", "png, html, table (default: png)") { |draw| options[:draw] = draw }
33
- opts.on("--organization ORGANIZATION", "Use organization") { |organization| options[:organization] = organization }
34
- opts.on("--private", "Only show private repos") { options[:private] = true }
35
- opts.on("--external", "Also include external projects in graph (can get super-messy)") { options[:external] = true }
36
- opts.on("--map SEARCH=REPLACE", "Replace in project name to find them as internal: 'foo=bar' -> replace foo in repo names to bar") do |map|
37
- options[:map] = map.split("=")
38
- options[:map][0] = Regexp.new(options[:map][0])
39
- options[:map][1] = options[:map][1].to_s
40
- end
41
- opts.on("--chef", "Parse chef metadata.rb files") { options[:chef] = true }
42
- opts.on("--select REGEX", "Only include repos with matching names") { |regex| options[:select] = Regexp.new(regex) }
43
- opts.on("--reject REGEX", "Exclude repos with matching names") { |regex| options[:reject] = Regexp.new(regex) }
44
- opts.on("-h", "--help", "Show this.") { puts opts; exit }
45
- opts.on("-v", "--version", "Show Version"){ puts RepoDependencyGraph::VERSION; exit}
46
- end.parse!(argv)
47
-
48
- options[:token] ||= begin
49
- token = `git config github.token`.strip
50
- token if $?.success?
51
- end
52
-
53
- options
54
- end
55
-
56
- def git_config(thing)
57
- result = `git config #{thing}`.strip
58
- result.empty? ? nil : result
59
- end
60
-
61
- def draw(dependencies, options)
62
- case options[:draw]
63
- when "html"
64
- nodes, edges = convert_to_graphviz(dependencies)
65
- html = <<-HTML.gsub(/^ /, "")
66
- <!doctype html>
67
- <html>
68
- <head>
69
- <title>Network</title>
70
- <style>
71
- #mynetwork {
72
- width: 2000px;
73
- height: 2000px;
74
- border: 1px solid lightgray;
75
- background: #F3F3F3;
76
- }
77
- </style>
78
-
79
- <script type="text/javascript" src="http://visjs.org/dist/vis.js"></script>
80
- <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
81
-
82
- <script type="text/javascript">
83
- var nodes = null;
84
- var edges = null;
85
- var network = null;
86
-
87
- function draw() {
88
- nodes = #{nodes.values.to_json};
89
- edges = #{edges.to_json};
90
-
91
- var container = document.getElementById('mynetwork');
92
- var data = {
93
- nodes: nodes,
94
- edges: edges
95
- };
96
- var options = {stabilize: false};
97
-
98
- new vis.Network(container, data, options);
99
- }
100
- </script>
101
- </head>
102
-
103
- <body onload="draw()">
104
- <div id="mynetwork"></div>
105
- </body>
106
- </html>
107
- HTML
108
- File.write("out.html", html)
109
- when "table"
110
- tables = dependencies.map do |name, uses|
111
- used = dependencies.map do |d, uses|
112
- used = uses.detect { |d| d.first == name }
113
- [d, used.last] if used
114
- end.compact
115
- size = [used.size, uses.size, 1].max
116
- table = []
117
- size.times do |i|
118
- table[i] = [
119
- (used[i] || []).join(": "),
120
- (name if i == 0),
121
- (uses[i] || []).join(": ")
122
- ]
123
- end
124
- table.unshift ["Used", "", "Uses"]
125
- table
126
- end
127
- tables.map!{ |t| "<table>\n#{t.map{|t| "<tr>#{t.map{|t| "<td>#{t}</td>" }.join("")}</tr>" }.join("\n")}\n</table>" }
128
-
129
- html = <<-HTML.gsub(/^ /, "")
130
- <!doctype html>
131
- <html>
132
- <head>
133
- <title>Network</title>
134
- <style>
135
- table { width: 600px; }
136
- </style>
137
- </head>
138
- <body>
139
- #{tables.join("<br>\n<br>\n")}
140
- </body>
141
- </html>
142
- HTML
143
- File.write("out.html", html)
144
- else
145
- nodes, edges = convert_to_graphviz(dependencies)
146
- require 'graphviz'
147
- g = GraphViz.new(:G, :type => :digraph)
148
-
149
- nodes = Hash[nodes.map do |_, data|
150
- node = g.add_node(data[:id], :color => data[:color], :style => "filled")
151
- [data[:id], node]
152
- end]
153
-
154
- edges.each do |edge|
155
- g.add_edge(nodes[edge[:from]], nodes[edge[:to]], :label => edge[:label])
156
- end
157
-
158
- g.output(:png => "out.png")
159
- end
160
- end
161
-
162
- def convert_to_graphviz(dependencies)
163
- counts = dependency_counts(dependencies)
164
- range = counts.values.min..counts.values.max
165
- nodes = Hash[counts.each_with_index.map do |(name, count), i|
166
- [name, {:id => name, :color => color(count, range)}]
167
- end]
168
- edges = dependencies.map do |name, dependencies|
169
- dependencies.map do |dependency, version|
170
- {:from => nodes[name][:id], :to => nodes[dependency][:id], :label => (version || '')}
171
- end
172
- end.flatten
173
- [nodes, edges]
174
- end
175
-
176
7
  def dependencies(options)
177
8
  if options[:map] && options[:external]
178
9
  raise ArgumentError, "Map only makes sense when searching for internal repos"
@@ -187,7 +18,7 @@ module RepoDependencyGraph
187
18
  possible.map! { |p| p.sub(options[:map][0], options[:map][1].to_s) } if options[:map]
188
19
 
189
20
  dependencies = all.map do |repo|
190
- found = dependent_repos(repo, options) || []
21
+ found = dependent_repos(repo, options)
191
22
  found.select! { |f| possible.include?(f.first) } unless options[:external]
192
23
  next if found.empty?
193
24
  puts "#{repo.name}: #{found.map { |n,v| "#{n}: #{v}" }.join(", ")}"
@@ -196,13 +27,19 @@ module RepoDependencyGraph
196
27
  Hash[dependencies]
197
28
  end
198
29
 
30
+ private
31
+
199
32
  def dependent_repos(repo, options)
200
- if options[:chef]
33
+ repos = []
34
+
35
+ if !options[:only] || options[:only] == "chef"
201
36
  if content = repo.content("metadata.rb")
202
- scan_chef_metadata(content)
37
+ repos.concat scan_chef_metadata(content)
203
38
  end
204
- else
205
- if repo.gem? && spec = load_spec(repo.gemspec_content)
39
+ end
40
+
41
+ if !options[:only] || options[:only] == "gem"
42
+ gems = if repo.gem? && spec = load_spec(repo.gemspec_content)
206
43
  spec.runtime_dependencies.map do |d|
207
44
  r = d.requirement.to_s
208
45
  r = nil if r == ">= 0"
@@ -213,7 +50,10 @@ module RepoDependencyGraph
213
50
  elsif content = repo.content("Gemfile")
214
51
  scan_gemfile(content)
215
52
  end
53
+ repos.concat gems if gems
216
54
  end
55
+
56
+ repos
217
57
  end
218
58
 
219
59
  def scan_chef_metadata(content)
@@ -239,23 +79,5 @@ module RepoDependencyGraph
239
79
  $stderr.puts "Error when parsing content:\n#{content}\n\n#{$!}"
240
80
  nil
241
81
  end
242
-
243
- def color(value, range)
244
- value -= range.min # lowest -> green
245
- max = range.max - range.min
246
-
247
- i = (value * MAX_HEX / max);
248
- i *= 0.6 # green-blue gradient instead of green-green
249
- half = MAX_HEX * 0.5
250
- values = [0,2,4].map { |v| (Math.sin(0.024 * i + v) * half + half).round.to_s(16).rjust(2, "0") }
251
- "##{values.join}"
252
- end
253
-
254
- def dependency_counts(dependencies)
255
- all = (dependencies.keys + dependencies.values.map { |v| v.map(&:first) }).flatten.uniq
256
- Hash[all.map do |k|
257
- [k, dependencies.values.map(&:first).count { |name, _| name == k } ]
258
- end]
259
- end
260
82
  end
261
83
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repo_dependency_graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-06 00:00:00.000000000 Z
11
+ date: 2014-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: organization_audit
@@ -33,6 +33,8 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - bin/repo-dependency-graph
35
35
  - lib/repo_dependency_graph.rb
36
+ - lib/repo_dependency_graph/cli.rb
37
+ - lib/repo_dependency_graph/output.rb
36
38
  - lib/repo_dependency_graph/version.rb
37
39
  homepage: http://github.com/grosser/repo_dependency_graph
38
40
  licenses: