New compass subcommand: stats. Emits details about your stylesheets.
This commit is contained in:
parent
0d45a3b4aa
commit
d1e1c1756d
@ -5,7 +5,7 @@ require 'compass/commands/registry'
|
|||||||
|
|
||||||
%w(base generate_grid_background help list_frameworks project_base
|
%w(base generate_grid_background help list_frameworks project_base
|
||||||
update_project watch_project create_project installer_command
|
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|
|
write_configuration).each do |lib|
|
||||||
require "compass/commands/#{lib}"
|
require "compass/commands/#{lib}"
|
||||||
end
|
end
|
||||||
|
141
lib/compass/commands/project_stats.rb
Normal file
141
lib/compass/commands/project_stats.rb
Normal 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
|
@ -13,8 +13,9 @@ module Compass
|
|||||||
self.options[:cache_location] ||= File.join(from, ".sass-cache")
|
self.options[:cache_location] ||= File.join(from, ".sass-cache")
|
||||||
end
|
end
|
||||||
|
|
||||||
def sass_files
|
def sass_files(options = {})
|
||||||
@sass_files || Dir.glob(separate("#{from}/**/[^_]*.sass"))
|
exclude_partials = options.fetch(:exclude_partials, true)
|
||||||
|
@sass_files || Dir.glob(separate("#{from}/**/#{'[^_]' if exclude_partials}*.sass"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def stylesheet_name(sass_file)
|
def stylesheet_name(sass_file)
|
||||||
|
@ -66,6 +66,15 @@ module Compass
|
|||||||
File.join(project_path, *path.split('/'))
|
File.join(project_path, *path.split('/'))
|
||||||
end
|
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.
|
# TODO: Deprecate the src/config.rb location.
|
||||||
KNOWN_CONFIG_LOCATIONS = [".compass/config.rb", "config/compass.config", "config.rb", "src/config.rb"]
|
KNOWN_CONFIG_LOCATIONS = [".compass/config.rb", "config/compass.config", "config.rb", "src/config.rb"]
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
%w(stylesheet_updating).each do |patch|
|
%w(stylesheet_updating traversal).each do |patch|
|
||||||
require "compass/sass_extensions/monkey_patches/#{patch}"
|
require "compass/sass_extensions/monkey_patches/#{patch}"
|
||||||
end
|
end
|
23
lib/compass/sass_extensions/monkey_patches/traversal.rb
Normal file
23
lib/compass/sass_extensions/monkey_patches/traversal.rb
Normal 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
78
lib/compass/stats.rb
Normal 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
|
Loading…
Reference in New Issue
Block a user