Skip to content

Instantly share code, notes, and snippets.

@timgentry
Last active December 11, 2018 13:09
Show Gist options
  • Save timgentry/c6f1e10f74538d179e36c6e1b314dfc1 to your computer and use it in GitHub Desktop.
Save timgentry/c6f1e10f74538d179e36c6e1b314dfc1 to your computer and use it in GitHub Desktop.
Rake example
# Parses Graphviz .dot files
class DotFileParser
EDGE_PATTERN = /"([^"]*)" -> "([^"]*)"/
NODE_PATTERN = /\A\t"([^"]*)" \[/
def initialize(filename)
@filename = filename
end
def each_edge
readlines.each do |line|
matchdata = line.match(EDGE_PATTERN)
next if matchdata.nil?
yield(matchdata[1], matchdata[2])
end
end
def each_node
readlines.each do |line|
matchdata = line.match(NODE_PATTERN)
next if matchdata.nil?
yield(matchdata[1])
end
end
def all_nodes
return @all_nodes if @all_nodes
@all_nodes = Set.new
each_node do |node|
@all_nodes.add(node)
end
@all_nodes
end
def connections(node)
nodes = Set.new
each_edge do |from, to|
next unless from == node || to == node
nodes.add(from) if to == node
nodes.add(to) if from == node
end
nodes
end
def clusters
return @clusters if @clusters
remaining_nodes = all_nodes
@clusters = []
one_node_clusters = Set.new
until remaining_nodes.empty?
cluster_nodes = Set.new
current_nodes = Set[remaining_nodes.first]
current_connections = Set.new
until current_nodes.empty?
cluster_nodes += current_nodes
remaining_nodes -= current_nodes
current_connections = Set.new
current_nodes.each do |node|
current_connections += connections(node) & remaining_nodes
end
current_nodes = current_connections
end
if cluster_nodes.size == 1
one_node_clusters << cluster_nodes.first
else
@clusters << cluster_nodes
end
end
@clusters << one_node_clusters unless one_node_clusters.empty?
@clusters
end
def filtered_lines(nodes)
readlines.each.with_index do |line, i|
yield(line) if i < 3 || i > readlines.length - 2
yield(line) if line =~ NODE_PATTERN && nodes.include?(line.match(NODE_PATTERN)[1])
yield(line) if line =~ EDGE_PATTERN && nodes.include?(line.match(EDGE_PATTERN)[1]) &&
nodes.include?(line.match(EDGE_PATTERN)[2])
end
end
private
def readlines
@readlines ||= File.readlines(@filename)
end
end
require 'rake/clean'
require_relative 'dot_file_parser'
namespace :railroady_demo do
desc 'set up railroady demo prerequisites'
task :setup do
# Usage: bin/rake railroady_demo:setup
system 'brew install graphviz librsvg'
end
end
MODEL_PATTERN = %r{\Adoc/models_(brief|complete)\.dot\z}
rule MODEL_PATTERN => 'railroady_demo:setup' do |t|
# Usage: bin/rake doc/models_{brief,complete}.dot
require 'open3'
command = ['railroady', '-o', t.name, '-lamM']
command << '-b' if 'brief' == t.name.match(MODEL_PATTERN)[1]
stdin, stdout, stderr = Open3.popen3(*command)
stdin.close
puts stderr.read + stdout.read
end
CLEAN << FileList['doc/*.dot']
desc 'clusters'
task clusters: 'doc/models_brief.dot' do |t|
dot_parser = DotFileParser.new(t.source)
source_parts = t.source.split('.')
dot_parser.clusters.each.with_index do |cluster_nodes, i|
output_file = [source_parts[0..-2], "cluster#{i}", source_parts[-1]].join('.')
File.open(output_file, 'w') do |output|
dot_parser.filtered_lines(cluster_nodes) do |line|
output << line
end
end
end
end
rule %r{\Adoc/.*\.svg\z} => '.dot' do |t|
# Usage: bin/rake doc/<anything>.svg
require 'shellwords'
system "dot -Tsvg #{Shellwords.escape(t.source)} > " \
"#{Shellwords.escape(t.name)}"
end
CLEAN << FileList['doc/*.svg']
rule %r{\Adoc/.*\.pdf\z} => '.svg' do |t|
# Usage: bin/rake doc/<anything>.pdf
require 'shellwords'
system "rsvg-convert -f pdf -o #{Shellwords.escape(t.name)} " \
"#{Shellwords.escape(t.source)}"
end
CLOBBER << FileList['doc/*.pdf']
desc 'Generate cluster PDFs'
task generate_pdfs: FileList['doc/*.cluster*.dot'].ext('.pdf')
# Usage: bin/rake clusters generate_pdfs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment