Skip to content

Instantly share code, notes, and snippets.

@timblair
Last active March 6, 2017 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timblair/ffc4cc7eb2556800d4aa87903c7597f5 to your computer and use it in GitHub Desktop.
Save timblair/ffc4cc7eb2556800d4aa87903c7597f5 to your computer and use it in GitHub Desktop.
require "gviz"
require "optparse"
# Usage: $0 PINFILE [OPTIONS]
#
# -i, --highlight APPS Highlight apps that depend on these
# -x, --exclude APPS Exclude these apps
# -o, --only APPS Only show dependencies and dependants of these
# -s, --hide-solitary Hide apps with no dependecies or dependants
# -f, --out-file FILE The filename to write output to (default: out.png)
# -h, --help Show this help message
class Graph
def initialize(source)
@source = source
@nodes = {}
self.load
end
def load
instance_eval(File.read(@source))
end
# Called as part of the Pinfile DSL from the instance_eval above.
def process(params)
label, deps = params, []
if params.is_a? Hash
label = params.keys.first
deps = Array(params.values.first)
end
safe_id = Node.safe_id(label)
@nodes[safe_id] = Node.new(label, deps)
end
def render(options = {})
vis = Gviz.new
vis.global(rankdir: "RL", rankseq: "equally")
[:highlight, :exclude, :only].each do |opt|
options[opt] = (options[opt] || []).map { |a| Node.safe_id(a) }
end
@nodes.each do |id, node|
next if options[:exclude].include?(id)
next if options[:loners] == false &&
node.dependencies.empty? && dependants_of(id).empty?
# For `only` options we need to check both dependants and dependees so we
# can include the correct nodes and labels for each side.
next if !options[:only].empty? &&
!options[:only].include?(id) &&
(options[:only] & node.dependencies).empty? &&
(options[:only] & dependants_of(id)).empty?
opts = { label: node.label }
if options[:highlight].include?(id)
opts[:peripheries] = 2
opts[:penwidth] = 2
opts[:color] = node.color
opts[:label] = "<B>#{opts[:label]}</B>"
end
fill = []
options[:highlight].each do |h|
fill << @nodes[h].color if node.dependencies.include?(h)
end
if !fill.empty?
opts[:fillcolor] = fill.join(":")
if fill.size > 1
opts[:style] = "wedged"
else
opts[:style] = "filled"
end
end
vis.node(id, opts)
deps = node.dependencies - options[:exclude]
if !options[:only].empty? && !options[:only].include?(id)
deps &= options[:only]
end
vis.route(id => deps)
end
vis
end
def dependants_of(id)
@nodes.values.inject([]) do |arr, node|
arr << node.safe_id if node.dependencies.include?(id)
arr
end
end
class Node
attr_reader :label, :dependencies
def initialize(label, deps = [])
@label = label.to_s
@dependencies = deps.map { |d| self.class.safe_id(d) }
end
def self.safe_id(label)
label.to_s.gsub("_", "").gsub("-", "").to_sym
end
def safe_id
@safe_id ||= self.class.safe_id(@label)
end
# Generate a random colour based on the provided object's String
# representation.
def color
hash = safe_id.to_s.gsub(" ", "").to_i(36)
hex_code = "%06x" % (hash & 0xffffff)
"##{hex_code}"
end
end
end
options = { out: "out" }
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} PINFILE [OPTIONS]"
opts.separator ""
opts.on("-iAPPS", "--highlight APPS", "Highlight apps that depend on these") do |arg|
options[:highlight] = arg.split(",")
end
opts.on("-xAPPS", "--exclude APPS", "Exclude these apps") do |arg|
options[:exclude] = arg.split(",")
end
opts.on("-oAPPS", "--only APPS", "Only show dependencies and dependants of these") do |arg|
options[:only] = arg.split(",")
end
opts.on("-s", "--hide-solitary", "Hide apps with no dependecies or dependants") do |arg|
options[:loners] = false
end
opts.on("-fFILE", "--out-file FILE", "The filename to write output to (default: #{options[:out]}.png)") do |arg|
options[:out] = arg.gsub(/\.png$/i, "")
end
opts.on("-h", "--help", "Show this help message") do |arg|
puts opts
exit
end
opts.separator ""
end.parse!
graph = Graph.new(ARGV.first)
vis = graph.render(options)
vis.save(options[:out], :png)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment