New compass subcommand: stats. Emits details about your stylesheets.

This commit is contained in:
Chris Eppstein 2009-10-29 01:05:41 -07:00
parent 0d45a3b4aa
commit d1e1c1756d
7 changed files with 258 additions and 6 deletions

View File

@ -5,7 +5,7 @@ require 'compass/commands/registry'
%w(base generate_grid_background help list_frameworks project_base
update_project watch_project create_project installer_command
print_version stamp_pattern validate_project
print_version project_stats stamp_pattern validate_project
write_configuration).each do |lib|
require "compass/commands/#{lib}"
end

View File

@ -0,0 +1,141 @@
require 'compass/commands/project_base'
require 'compass/commands/update_project'
module Compass
module Commands
module StatsOptionsParser
def set_options(opts)
opts.banner = %Q{
Usage: compass stats [path/to/project] [options]
Description:
Compile project at the path specified (or the current
directory if not specified) and then compute statistics
for the sass and css files in the project.
Options:
}.strip.split("\n").map{|l| l.gsub(/^ {0,10}/,'')}.join("\n")
super
end
end
class ProjectStats < UpdateProject
register :stats
def initialize(working_path, options)
super
assert_project_directory_exists!
end
def perform
super
require 'compass/stats'
compiler = new_compiler_instance
sass_files = sorted_sass_files(compiler)
rows = [[ :-, :-, :-, :-, :- ],
[ 'Filename', 'Rules', 'Properties', 'Mixins Defs', 'Mixins Used' ],
[ :-, :-, :-, :-, :- ]]
maximums = [ 8, 5, 10, 14, 11 ]
alignments = [ :left, :right, :right, :right, :right ]
delimiters = [ ['| ', ' |'], [' ', ' |'], [' ', ' |'], [' ', ' |'], [' ', ' |'] ]
totals = [ "Total (#{sass_files.size} files):", 0, 0, 0, 0 ]
sass_files.each do |sass_file|
css_file = compiler.corresponding_css_file(sass_file) unless sass_file[0..0] == '_'
row = filename_columns(sass_file)
row += sass_columns(sass_file)
row.each_with_index do |c, i|
maximums[i] = [maximums[i].to_i, c.size].max
totals[i] = totals[i] + c.to_i if i > 0
end
rows << row
end
rows << [:-, :-, :-, :-, :-]
rows << totals.map{|t| t.to_s}
rows << [:-, :-, :-, :-, :-]
rows.each do |row|
row.each_with_index do |col, i|
print pad(col, maximums[i], :align => alignments[i], :left => delimiters[i].first, :right => delimiters[i].last)
end
print "\n"
end
end
def pad(c, max, options = {})
options[:align] ||= :left
if c == :-
filler = '-'
c = ''
else
filler = ' '
end
spaces = max - c.size
filled = filler * [spaces,0].max
"#{options[:left]}#{filled if options[:align] == :right}#{c}#{filled if options[:align] == :left}#{options[:right]}"
end
def sorted_sass_files(compiler)
sass_files = compiler.sass_files(:exclude_partials => false)
sass_files.map! do |s|
filename = Compass.deprojectize(s, File.join(Compass.configuration.project_path, Compass.configuration.sass_dir))
[s, File.dirname(filename), File.basename(filename)]
end
sass_files = sass_files.sort_by do |s,d,f|
File.join(d, f[0] == ?_ ? f[1..-1] : f)
end
sass_files.map!{|s,d,f| s}
end
def filename_columns(sass_file)
filename = Compass.deprojectize(sass_file, working_path)
[filename]
end
def sass_columns(sass_file)
sf = Compass::Stats::SassFile.new(sass_file)
sf.analyze!
%w(rule_count prop_count mixin_def_count mixin_count).map do |t|
sf.send(t).to_s
end
end
class << self
def option_parser(arguments)
parser = Compass::Exec::CommandOptionParser.new(arguments)
parser.extend(Compass::Exec::GlobalOptionsParser)
parser.extend(Compass::Exec::ProjectOptionsParser)
parser.extend(StatsOptionsParser)
end
def usage
option_parser([]).to_s
end
def description(command)
"Report statistics about your stylesheets"
end
def parse!(arguments)
parser = option_parser(arguments)
parser.parse!
parse_arguments!(parser, arguments)
parser.options
end
def parse_arguments!(parser, arguments)
if arguments.size == 1
parser.options[:project_name] = arguments.shift
elsif arguments.size == 0
# default to the current directory.
else
raise Compass::Error, "Too many arguments were specified."
end
end
end
end
end
end

View File

@ -13,8 +13,9 @@ module Compass
self.options[:cache_location] ||= File.join(from, ".sass-cache")
end
def sass_files
@sass_files || Dir.glob(separate("#{from}/**/[^_]*.sass"))
def sass_files(options = {})
exclude_partials = options.fetch(:exclude_partials, true)
@sass_files || Dir.glob(separate("#{from}/**/#{'[^_]' if exclude_partials}*.sass"))
end
def stylesheet_name(sass_file)
@ -51,4 +52,4 @@ module Compass
end
end
end
end
end

View File

@ -66,6 +66,15 @@ module Compass
File.join(project_path, *path.split('/'))
end
def deprojectize(path, project_path = nil)
project_path ||= configuration.project_path
if path[0..(project_path.size - 1)] == project_path
path[(project_path.size + 1)..-1]
else
path
end
end
# TODO: Deprecate the src/config.rb location.
KNOWN_CONFIG_LOCATIONS = [".compass/config.rb", "config/compass.config", "config.rb", "src/config.rb"]

View File

@ -1,3 +1,3 @@
%w(stylesheet_updating).each do |patch|
%w(stylesheet_updating traversal).each do |patch|
require "compass/sass_extensions/monkey_patches/#{patch}"
end
end

View File

@ -0,0 +1,23 @@
module Sass
module Tree
class Node
unless method_defined?(:visit_depth_first)
def visit_depth_first(visitor)
visitor.visit(self)
visitor.down(self) if children.any? and visitor.respond_to?(:down)
if is_a?(ImportNode) && visitor.import?(self)
root = Sass::Files.tree_for(import, @options)
imported_children = root.children
end
(imported_children || children).each do |child|
break if visitor.respond_to?(:stop?) && visitor.stop?
child.visit_depth_first(visitor)
end
visitor.up(self) if children.any?
end
end
end
end
end

78
lib/compass/stats.rb Normal file
View File

@ -0,0 +1,78 @@
module Compass
module Stats
class StatsVisitor
attr_accessor :rule_count, :prop_count, :mixin_def_count, :mixin_count
def initialize
self.rule_count = 0
self.prop_count = 0
self.mixin_def_count = 0
self.mixin_count = 0
end
def visit(node)
self.prop_count += 1 if node.is_a?(Sass::Tree::PropNode) && !node.children.any?
if node.is_a?(Sass::Tree::RuleNode)
self.rule_count += node.rules.map{|r| r.split(/,/)}.flatten.compact.size
end
self.mixin_def_count += 1 if node.is_a?(Sass::Tree::MixinDefNode)
self.mixin_count += 1 if node.is_a?(Sass::Tree::MixinNode)
end
def up(node)
end
def down(node)
end
def import?(node)
return false
full_filename = node.send(:import)
full_filename != Compass.deprojectize(full_filename)
end
end
class CssFile
attr_accessor :path
def initialize(path)
self.path = path
end
def contents
@contents ||= File.read(path)
end
def lines
contents.inject(0){|m,c| m + 1 }
end
end
class SassFile
attr_accessor :path
attr_reader :visitor
def initialize(path)
self.path = path
end
def contents
@contents ||= File.read(path)
end
def tree
@tree = Sass::Engine.new(contents, Compass.configuration.to_sass_engine_options).to_tree
end
def visit_tree!
@visitor = StatsVisitor.new
tree.visit_depth_first(@visitor)
@visitor
end
def analyze!
visit_tree!
end
def lines
contents.inject(0){|m,c| m + 1 }
end
def rule_count
visitor.rule_count
end
def prop_count
visitor.prop_count
end
def mixin_def_count
visitor.mixin_def_count
end
def mixin_count
visitor.mixin_count
end
end
end
end