Skip to content

Instantly share code, notes, and snippets.

@jbgutierrez
Last active August 29, 2015 14:15
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 jbgutierrez/c6f770708f1db05dba95 to your computer and use it in GitHub Desktop.
Save jbgutierrez/c6f770708f1db05dba95 to your computer and use it in GitHub Desktop.
Plotting a dependency graph
#!/usr/bin/env ruby
# coding: UTF-8
require 'yaml'
ALIAS = {
text: '/vendor/requirejs-text/text',
jquery: '/vendor/jquery/dist/jquery',
dust: '/vendor/dustjs-linkedin/dist/dust-full',
dustHelpers: '/vendor/dustjs-linkedin-helpers/dist/dust-helpers',
pagejs: '/vendor/page.js/page',
history: '/vendor/html5-history-api/history.iegte8',
ractive: '/vendor/ractive/ractive-legacy',
es5Shim: '/vendor/es5-shim/es5-shim',
html5shiv: '/vendor/html5shiv/dist/html5shiv'
}
module IdentityMap
def self.included base
base.class_eval do
@instances = {}
end
base.extend ClassMethods
end
module ClassMethods
def find_or_new *args
id = args.first
instance = find(id) || new(*args)
@instances[id] = instance
end
def find conditions=nil, &block
return @instances[conditions] if [String, Fixnum].include? conditions.class
predicate = build_predicate conditions, &block
@instances.values.find &predicate
end
def find_all conditions=nil, &block
predicate = build_predicate conditions, &block
@instances.values.select &predicate
end
private
def build_predicate conditions, &block
if conditions.nil?
if block_given?
Proc.new { |instance| block.call instance }
else
Proc.new { |instance| true }
end
else
->(instance) {
conditions.all? do |column, value|
instance.send(column) == value
end
}
end
end
end
end
class JSFile
include IdentityMap
attr_reader :name, :path, :templates, :files
def initialize root
@name=root
root = ALIAS.fetch(root.to_sym, '/js/' + root)
@path = "public#{root}.js"
@templates = []
@files = IO.read(@path).
scan(/define\s*\(\s*\[([^\]]+)\s*\]/m).
map(&:first).uniq.join(' ').
scan(/[^'^\s^,]+/m).
reject{ |dependency| text = dependency =~ /text!/; puts dependency if text; text }.
uniq.
map { |dependency| JSFile.find_or_new dependency }
end
def connect_to template
@templates.push template unless connected? template
@files.each{ |file| file.connect_to template }
end
def connected? template
@templates.include? template
end
def common_dependency?
@common_dependency ||=
Template.layout.js_dependencies.include?(self) || JSFile.find_all { |f| f.files.include? self }.length > 1
end
def to_s
@name
end
end
class Template
include IdentityMap
attr_reader :name, :controllers, :partials
def initialize root
@name = root
content = IO.read root
partials = (@content.scan /\{> "(.*)" \/\}/).map(&:first).uniq rescue []
@partials = partials.map { |dependency| Template.find_or_new "views/#{dependency}.dust" }
content += partials.map(&:controllers).join('')
@controllers = (content.scan /data-controller="([^"]+)"/).map(&:first).uniq.map do |controller|
file = JSFile.find_or_new "controllers/#{controller}"
file.connect_to self
file
end
end
def js_dependencies
JSFile.find_all { |file| file.connected? self }
end
def to_s
@name
end
class << self
attr_accessor :layout
end
end
class Plot
def initialize template
all = Template.layout == template
name = template.name.gsub '/', '-'
files = template.js_dependencies
lines = []
files.each do |file|
included = all || !file.common_dependency?
next unless included
lines << "\"#{file.name}\""
file.files.each do |dependency|
included = all || !dependency.common_dependency?
next unless included
lines << "\"#{file.name}\"->\"#{dependency.name}\""
end
end
if lines.empty?
puts "#{template} without dependencies"
return
end
content=<<-EOS
digraph G {
splines=ortho;
#{lines.join("\n")}
}
EOS
fd = File.open "/tmp/#{name}.gv", "w"
fd.puts content
fd.close
`dot -Tpng /tmp/#{name}.gv -o #{name}.png`
end
end
# main
Dir['views/*.dust'].each { |path| Template.find_or_new path }
controller = JSFile.find_or_new "main"
template = Template.find_or_new "views/layouts/application.dust"
controller.connect_to template
template.controllers << controller
Template.layout = template
Template.find_all { |template| Plot.new template }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment