Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--color
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in partial_dependencies.gemspec
gemspec
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env rake
require "bundler/gem_tasks"
6 changes: 6 additions & 0 deletions lib/partial_dependencies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "partial_dependencies/version"

module PartialDependencies
require "partial_dependencies/railtie" if defined?(Rails)
require "partial_dependencies/graph"
end
102 changes: 102 additions & 0 deletions lib/partial_dependencies/graph.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'stringio'
module PartialDependencies
class Graph

def initialize(base_path = File.expand_path(File.join(Rails.root,"app", "views")))
@base_path = base_path
end

def base_path=(base_path)
@base_path = base_path
@views = nil
end

def dot(type = "png", view_set = "used", fn = "partial_dependencies")
prog = view_set == "unused" ? "neato" : "dot"
IO.popen("#{prog} -T#{type} -o #{fn}-#{view_set}.#{type}", "w") do |pipe|
pipe.puts dot_input(view_set)
end
end

private

def dot_input(view_set)
possible_view_sets = ["used", "unused", "all"]
unless possible_view_sets.include?(view_set)
raise "Wrong view_set. Only #{possible_view_sets.inspect} possible. Was #{view_set.inspect}"
end
str = StringIO.new
parse_files
str.puts "digraph partial_dependencies {"
name_to_node = {}
instance_variable_get("@#{view_set}_views").each_with_index do |view, index|
str.puts "Node#{index} [label=\"#{view}\"]"
name_to_node[view] = "Node#{index}"
end
if (["used", "all"].include?(view_set))
@edges.each do |view, partials|
partials.each do |partial|
str.puts "#{name_to_node[view]}->#{name_to_node[partial]}"
end
end
end
str.puts "}"
str.rewind
return str.read
end


def parse_files
@edges = Hash.new {|hash,key| hash[key] = []}
@used_views = {}
@all_views = []
views.each do |view|
@all_views << pwfe(view)
File.open("#{view[:path]}") do |contents|
contents.each do |line|
if partial_name = scan_line(line)
found_render partial_name, view

end
end
end
end
@used_views = @used_views.keys
@unused_views = @all_views - @used_views
end

def scan_line(line)
# Modified
# Fixed the line below to allow spaces between the opening ( and "partial" text.
# Also modified to allow Rails 3 symbol syntax.
if line =~ /=\s*render\s*\(?\s*((?::partial\s*=>)|(?:partial:))\s*["'](.*?)["']/
return $2
elsif line =~ /=\s*render\s*\s*["'](.*?)["']/
return $1
end
end

def found_render(partial_name, view)
if partial_name.index("/")
partial_name = partial_name.gsub(/\/([^\/]*)$/, "/_\\1")
else
partial_name = "#{File.dirname(view[:name])}/_#{partial_name}"
end
@edges[pwfe(view)] << partial_name
@used_views[pwfe(view)] = true
@used_views[partial_name] = true
end

def pwfe(view)
view[:name].split(".")[0]
end

def views
return @views if @views
@views = Dir.glob(File.join(@base_path, "**", "*")).reject do |vp|
File.directory?(vp)
end.map {|vp| {:name => vp.gsub(@base_path, "").gsub(/^\//,''), :path => vp}}
end
end
end

9 changes: 9 additions & 0 deletions lib/partial_dependencies/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'partial_dependencies'
require 'rails'
module PartialDependencies
class Railtie < Rails::Railtie
rake_tasks do
load File.expand_path('../../../tasks/partial_dependencies.rake', __FILE__)
end
end
end
3 changes: 3 additions & 0 deletions lib/partial_dependencies/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module PartialDependencies
VERSION = "0.0.1"
end
18 changes: 18 additions & 0 deletions partial_dependencies.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/partial_dependencies/version', __FILE__)

Gem::Specification.new do |gem|
gem.authors = ["mSALES GmbH", "George Shaw", "Niklas Hofer"]
gem.email = ["niklas+dev@lanpartei.de"]
gem.description = %q{Get a dependency graph of your Ruby on Rails views and partials}
gem.summary = %q{Get a dependency graph of your Ruby on Rails views and partials}
gem.homepage = ""

gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "partial_dependencies"
gem.require_paths = ["lib"]
gem.version = PartialDependencies::VERSION
gem.add_development_dependency "rspec", "~> 2.8.0"
end
32 changes: 32 additions & 0 deletions spec/graph_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'partial_dependencies'

describe PartialDependencies::Graph do
subject { described_class.new File.expand_path('../../', __FILE__) }
it "is there" do
subject.should_not be_nil
end

context '#scan_line' do
def scan(line)
subject.send(:scan_line, line) # don't make it public just for specs
end
it 'detects classic render :partial' do
line = %Q~= render :partial => 'foo/bar', :object => bar~
scan(line).should == 'foo/bar'
end

it 'detects 1.9 render partial:' do
line = %Q~= render partial: 'foo/bar', object: bar~
scan(line).should == 'foo/bar'
end

it 'detects lazy render (without partial option)' do
line = %Q~= render 'foo/bar', bar: bar~
scan(line).should == 'foo/bar'
end

it "ignores non-render lines" do
scan("<p>no render here</p>").should be_nil
end
end
end
93 changes: 2 additions & 91 deletions tasks/partial_dependencies.rake
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require 'stringio'

require 'partial_dependencies'
#
# Rake task will scan all the views and partials to generate a linkage graph.
# Options to select output file type and "used", "unused", or "all" partials
Expand All @@ -16,98 +15,10 @@ require 'stringio'
# values for <view_set> are used, unused, and all.
#

class PartialDependencies

def initialize(base_path = File.expand_path(File.join(Rails.root,"app", "views")))
@base_path = base_path
end

def base_path=(base_path)
@base_path = base_path
@views = nil
end

def dot(type = "png", view_set = "used", fn = "partial_dependencies")
prog = view_set == "unused" ? "neato" : "dot"
IO.popen("#{prog} -T#{type} -o #{fn}-#{view_set}.#{type}", "w") do |pipe|
pipe.puts dot_input(view_set)
end
end

private

def dot_input(view_set)
possible_view_sets = ["used", "unused", "all"]
unless possible_view_sets.include?(view_set)
raise "Wrong view_set. Only #{possible_view_sets.inspect} possible. Was #{view_set.inspect}"
end
str = StringIO.new
parse_files
str.puts "digraph partial_dependencies {"
name_to_node = {}
instance_variable_get("@#{view_set}_views").each_with_index do |view, index|
str.puts "Node#{index} [label=\"#{view}\"]"
name_to_node[view] = "Node#{index}"
end
if (["used", "all"].include?(view_set))
@edges.each do |view, partials|
partials.each do |partial|
str.puts "#{name_to_node[view]}->#{name_to_node[partial]}"
end
end
end
str.puts "}"
str.rewind
return str.read
end


def parse_files
@edges = Hash.new {|hash,key| hash[key] = []}
@used_views = {}
@all_views = []
views.each do |view|
@all_views << pwfe(view)
File.open("#{view[:path]}") do |contents|
contents.each do |line|
# Modified
# Fixed the line below to allow spaces between the opening ( and "partial" text.
# Also modified to allow Rails 3 symbol syntax.
if line =~ /=\s*render\s*\(?\s*((?::partial\s*=>)|(?:partial:))\s*["'](.*?)["']/
partial_name = $2
if partial_name.index("/")
partial_name = partial_name.gsub(/\/([^\/]*)$/, "/_\\1")
else
partial_name = "#{File.dirname(view[:name])}/_#{partial_name}"
end
@edges[pwfe(view)] << partial_name
@used_views[pwfe(view)] = true
@used_views[partial_name] = true
end
end
end
end
@used_views = @used_views.keys
@unused_views = @all_views - @used_views
end

def pwfe(view)
view[:name].split(".")[0]
end

def views
return @views if @views
@views = Dir.glob(File.join(@base_path, "**", "*")).reject do |vp|
File.directory?(vp)
end.map {|vp| {:name => vp.gsub(@base_path, "").gsub(/^\//,''), :path => vp}}
end
end


namespace :partial_dependencies do
desc "Generate a graphical (PNG) representation of the partial dependencies"
task :generate_picture, :file_type, :view_set do |t, args|
pd = PartialDependencies.new
pd = PartialDependencies::Graph.new
pd.dot(args.file_type || "png", args.view_set || "used")
end
end