fix merge

This commit is contained in:
Thomas Reynolds 2012-03-01 13:24:56 -08:00
commit 576463f361
37 changed files with 414 additions and 329 deletions

View file

@ -2,7 +2,6 @@ rvm:
- 1.8.7 - 1.8.7
- 1.9.2 - 1.9.2
- 1.9.3 - 1.9.3
- jruby
script: "bundle exec rake test" script: "bundle exec rake test"
branches: branches:

View file

@ -25,6 +25,7 @@
* Serve purely static folders directly (without source/ and config.rb) * Serve purely static folders directly (without source/ and config.rb)
* Set ignored files and disable directory_indexes from YAML frontmatter * Set ignored files and disable directory_indexes from YAML frontmatter
* Automatically load helper modules in helpers/ directory * Automatically load helper modules in helpers/ directory
* Add pid for cleanup
2.0.14 2.0.14
==== ====

View file

@ -6,7 +6,7 @@ group :development do
gem "rake", "~> 0.9.2" gem "rake", "~> 0.9.2"
gem "rspec", "~> 2.7" gem "rspec", "~> 2.7"
gem "rdoc", "~> 3.9" gem "rdoc", "~> 3.9"
gem "yard" # gem "yard"
end end
group :test do group :test do
@ -15,8 +15,9 @@ group :test do
gem "slim" gem "slim"
gem "coffee-filter", "~> 0.1.1" gem "coffee-filter", "~> 0.1.1"
gem "liquid", "~> 2.2" gem "liquid", "~> 2.2"
gem "jquery-rails" gem "jquery-rails", "~> 2.0.0"
gem "bootstrap-rails", "0.0.5" # gem "bootstrap-rails", "0.0.5"
# gem "zurb-foundation"
end end
gem "middleman-core", :path => "middleman-core" gem "middleman-core", :path => "middleman-core"

View file

@ -57,9 +57,9 @@ http://rubydoc.info/github/middleman/middleman
# Community # Community
The official community forum is available on Convore at: The official community forum is available at:
https://convore.com/middleman/ http://forum.middlemanapp.com/
# Bug Reports # Bug Reports

View file

@ -1,7 +1,7 @@
require 'rubygems' unless defined?(Gem) require 'rubygems' unless defined?(Gem)
# require 'fileutils' unless defined?(FileUtils) # require 'fileutils' unless defined?(FileUtils)
require 'rake' require 'rake'
require 'yard' # require 'yard'
require File.expand_path("../middleman-core/lib/middleman-core/version.rb", __FILE__) require File.expand_path("../middleman-core/lib/middleman-core/version.rb", __FILE__)
@ -84,7 +84,7 @@ end
desc "Run tests for all middleman gems" desc "Run tests for all middleman gems"
task :default => :test task :default => :test
desc "Generate documentation" # desc "Generate documentation"
task :doc do # task :doc do
YARD::CLI::Yardoc.new.run # YARD::CLI::Yardoc.new.run
end # end

View file

@ -1,7 +1,7 @@
require 'rubygems' unless defined?(Gem) require 'rubygems' unless defined?(Gem)
require 'rake' require 'rake'
require 'cucumber/rake/task' require 'cucumber/rake/task'
require 'yard' # require 'yard'
require 'bundler' require 'bundler'
Bundler::GemHelper.install_tasks :name => GEM_NAME Bundler::GemHelper.install_tasks :name => GEM_NAME
@ -20,6 +20,6 @@ Cucumber::Rake::Task.new(:test, 'Run features that should pass') do |t|
t.cucumber_opts = "--color --tags ~@wip --strict --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}" t.cucumber_opts = "--color --tags ~@wip --strict --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}"
end end
YARD::Rake::YardocTask.new # YARD::Rake::YardocTask.new
task :default => :test task :default => :test

View file

@ -17,7 +17,6 @@ Feature: Builder
Then the following files should not exist: Then the following files should not exist:
| _partial | | _partial |
| layout | | layout |
| other_layout |
| layouts/custom | | layouts/custom |
| layouts/content_for | | layouts/content_for |

View file

@ -0,0 +1,58 @@
Feature: Text Files Without Extensions Should Build and Preview
Scenario: Building Text Files without directory indexes
Given a fixture app "extensionless-text-files-app"
And a file named "config.rb" with:
"""
"""
And a successfully built app at "extensionless-text-files-app"
When I cd to "build"
Then the following files should exist:
| CNAME |
| LICENSE |
| README |
Scenario: Building Text Files with directory indexes
Given a fixture app "extensionless-text-files-app"
And a file named "config.rb" with:
"""
activate :directory_indexes
"""
And a successfully built app at "extensionless-text-files-app"
When I cd to "build"
Then the following files should exist:
| CNAME |
| LICENSE |
| README |
Then the following files should not exist:
| CNAME/index.html |
| LICENSE/index.html |
| README/index.html |
Scenario: Previewing Text Files without directory indexes
Given "directory_indexes" feature is "disabled"
Given the Server is running at "extensionless-text-files-app"
When I go to "/CNAME"
Then I should see "test.github.com"
When I go to "/LICENSE"
Then I should see "You have the right to remain classy."
When I go to "/README"
Then I should see "Bork bork bork"
Scenario: Previewing Text Files with directory indexes
Given "directory_indexes" feature is "enabled"
Given the Server is running at "extensionless-text-files-app"
When I go to "/CNAME"
Then I should see "test.github.com"
When I go to "/LICENSE"
Then I should see "You have the right to remain classy."
When I go to "/README"
Then I should see "Bork bork bork"
# When I go to "/CNAME/index.html"
# Then I should see "File Not Found"
# When I go to "/LICENSE/index.html"
# Then I should see "File Not Found"
# When I go to "/README/index.html"
# Then I should see "File Not Found"

View file

@ -0,0 +1 @@
test.github.com

View file

@ -0,0 +1 @@
You have the right to remain classy.

View file

@ -0,0 +1 @@
Bork bork bork

View file

@ -18,6 +18,8 @@ end
# Simple callback library # Simple callback library
require "middleman-core/vendor/hooks-0.2.0/lib/hooks" require "middleman-core/vendor/hooks-0.2.0/lib/hooks"
require "middleman-core/version"
# Top-level Middleman object # Top-level Middleman object
module Middleman module Middleman
WINDOWS = !!(RUBY_PLATFORM =~ /(mingw|bccwin|wince|mswin32)/i) unless const_defined?(:WINDOWS) WINDOWS = !!(RUBY_PLATFORM =~ /(mingw|bccwin|wince|mswin32)/i) unless const_defined?(:WINDOWS)

View file

@ -197,10 +197,11 @@ module Middleman::Cli
# Sort order, images, fonts, js/css and finally everything else. # Sort order, images, fonts, js/css and finally everything else.
sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css) sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css)
@app.sitemap.all_paths.select do |p| # Pre-request CSS to give Compass a chance to build sprites
File.extname(p) == ".css" @app.sitemap.pages.select do |p|
p.ext == ".css"
end.each do |p| end.each do |p|
Middleman::Cli::Build.shared_rack.get("/" + p.gsub(/\s/, "%20")) Middleman::Cli::Build.shared_rack.get(p.request_path.gsub(/\s/, "%20"))
end end
# Double-check for compass sprites # Double-check for compass sprites
@ -210,23 +211,17 @@ module Middleman::Cli
# find files in the build folder when it needs to generate sprites for the # find files in the build folder when it needs to generate sprites for the
# css files # css files
# TODO: deal with pages, not paths pages = @app.sitemap.pages.sort do |a, b|
paths = @app.sitemap.all_paths.sort do |a, b| a_idx = sort_order.index(a.ext) || 100
a_ext = File.extname(a) b_idx = sort_order.index(b.ext) || 100
b_ext = File.extname(b)
a_idx = sort_order.index(a_ext) || 100
b_idx = sort_order.index(b_ext) || 100
a_idx <=> b_idx a_idx <=> b_idx
end end
# Loop over all the paths and build them. # Loop over all the paths and build them.
paths.each do |path| pages.each do |page|
page = @app.sitemap.page(path)
next if page.ignored? next if page.ignored?
next if @config[:glob] && !File.fnmatch(@config[:glob], path) next if @config[:glob] && !File.fnmatch(@config[:glob], page.path)
base.tilt_template(page) base.tilt_template(page)

View file

@ -6,45 +6,7 @@ module Middleman::CoreExtensions::Builder
# @private # @private
def registered(app) def registered(app)
app.define_hook :after_build app.define_hook :after_build
app.extend ClassMethods
app.send :include, InstanceMethods
app.delegate :build_reroute, :to => :"self.class"
end end
alias :included :registered alias :included :registered
end end
# Build Class Methods
module ClassMethods
# Get a list of callbacks which can modify a files build path
# Each callback takes a destination path and a request path and
# returns a new destination path, or false if it doesn't want to reroute.
# @return [Array<Proc>]
def build_reroute(&block)
@build_rerouters ||= []
@build_rerouters << block if block_given?
@build_rerouters
end
end
# Build Instance Methods
module InstanceMethods
# Run through callbacks and get the new values
#
# @param [String] destination The current destination path of the built file
# @param [String] request_path The request path of the file
# @return [String] The new destination path
def reroute_builder(destination, request_path)
result = [destination, request_path]
build_reroute.each do |block|
output = block.call(destination, request_path)
if output
result = output
break
end
end
result
end
end
end end

View file

@ -187,6 +187,8 @@ module Middleman::CoreExtensions::FrontMatter
def parse_front_matter(content) def parse_front_matter(content)
yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
if content =~ yaml_regex if content =~ yaml_regex
content = content[($1.size + $2.size)..-1]
begin begin
data = YAML.load($1) data = YAML.load($1)
rescue => e rescue => e
@ -194,7 +196,6 @@ module Middleman::CoreExtensions::FrontMatter
return false return false
end end
content = content.split(yaml_regex).last
else else
return false return false
end end

View file

@ -74,7 +74,7 @@ module Middleman::CoreExtensions::Rendering
end end
# Certain output file types don't use layouts # Certain output file types don't use layouts
needs_layout = !%w(.js .css .txt).include?(extension) needs_layout = !%w(.js .json .css .txt).include?(extension)
# If we need a layout and have a layout, use it # If we need a layout and have a layout, use it
if needs_layout && layout_path = fetch_layout(engine, opts) if needs_layout && layout_path = fetch_layout(engine, opts)

View file

@ -19,8 +19,12 @@ module Middleman::CoreExtensions::Sitemap
# Files starting with an underscore, but not a double-underscore # Files starting with an underscore, but not a double-underscore
:partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) }, :partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) },
:layout => proc { |file, path|
file.match(/^source\/layout\./) || file.match(/^source\/layouts\//)
},
# Files without any output extension (layouts, partials) # Files without any output extension (layouts, partials)
:extensionless => proc { |file, path| !path.match(/\./) }, # :extensionless => proc { |file, path| !path.match(/\./) },
} }
# Include instance methods # Include instance methods

View file

@ -72,7 +72,7 @@ module Middleman::Sitemap
@_template ||= ::Middleman::Sitemap::Template.new(self) @_template ||= ::Middleman::Sitemap::Template.new(self)
end end
# Extension of the path # Extension of the path (i.e. '.js')
# @return [String] # @return [String]
def ext def ext
File.extname(path) File.extname(path)
@ -192,8 +192,9 @@ module Middleman::Sitemap
# This path can be affected by proxy callbacks. # This path can be affected by proxy callbacks.
# @return [String] # @return [String]
def destination_path def destination_path
# TODO: memoize this value # memoizing this means that reroute callbacks should be in place before the sitemap
store.reroute_callbacks.inject(self.path) do |destination, callback| # gets built
@destination_path ||= store.reroute_callbacks.inject(self.path) do |destination, callback|
callback.call(destination, self) callback.call(destination, self)
end end
end end
@ -235,18 +236,17 @@ module Middleman::Sitemap
if eponymous_directory? if eponymous_directory?
base_path = eponymous_directory_path base_path = eponymous_directory_path
prefix = /^#{base_path.sub("/", "\\/")}/ prefix = %r|^#{base_path.sub("/", "\\/")}|
else else
base_path = path.sub("#{app.index_file}", "") base_path = path.sub("#{app.index_file}", "")
prefix = /^#{base_path.sub("/", "\\/")}/ prefix = %r|^#{base_path.sub("/", "\\/")}|
end end
store.all_paths.select do |sub_path| store.pages.select do |sub_page|
sub_path =~ prefix if sub_page == self || sub_page.path !~ prefix || sub_page.ignored?
end.select do |sub_path| false
path != sub_path else
end.select do |sub_path| inner_path = sub_page.path.sub(prefix, "")
inner_path = sub_path.sub(prefix, "")
parts = inner_path.split("/") parts = inner_path.split("/")
if parts.length == 1 if parts.length == 1
true true
@ -255,9 +255,8 @@ module Middleman::Sitemap
else else
false false
end end
end.map do |p| end
store.page(p) end
end.reject { |p| p.ignored? }
end end
# This page's sibling pages # This page's sibling pages

View file

@ -24,6 +24,12 @@ module Middleman::Sitemap
@reroute_callbacks = [] @reroute_callbacks = []
end end
# A list of all pages
# @return [Array<Middleman::Sitemap::Page>]
def pages
@pages.values
end
# Check to see if we know about a specific path # Check to see if we know about a specific path
# @param [String] path # @param [String] path
# @return [Boolean] # @return [Boolean]
@ -82,22 +88,10 @@ module Middleman::Sitemap
def page_by_destination(destination_path) def page_by_destination(destination_path)
# TODO: memoize this # TODO: memoize this
destination_path = normalize_path(destination_path) destination_path = normalize_path(destination_path)
@pages.values.find {|p| p.destination_path == destination_path } pages.find do |p|
p.destination_path == destination_path ||
p.destination_path == destination_path.sub("/#{@app.index_file}", "")
end end
# Loop over known pages
# @yield [path, page]
# @return [void]
def each
@pages.each do |k, v|
yield k, v
end
end
# Get all known paths
# @return [Array<String>]
def all_paths
@pages.keys
end end
# Whether a path is ignored # Whether a path is ignored
@ -116,42 +110,6 @@ module Middleman::Sitemap
false false
end end
# Get a list of ignored paths
# @return [Array<String>]
def ignored_paths
@pages.values.select(&:ignored?).map(&:path)
end
# Whether the given path is generic
# @param [String] path
# @return [Boolean]
def generic?(path)
generic_paths.include?(normalize_path(path))
end
# Get a list of generic paths
# @return [Array<String>]
def generic_paths
app.cache.fetch :generic_paths do
@pages.values.select(&:generic?).map(&:path)
end
end
# Whether the given path is proxied
# @param [String] path
# @return [Boolean]
def proxied?(path)
proxied_paths.include?(normalize_path(path))
end
# Get a list of proxied paths
# @return [Array<String>]
def proxied_paths
app.cache.fetch :proxied_paths do
@pages.values.select(&:proxy?).map(&:path)
end
end
# Remove a file from the store # Remove a file from the store
# @param [String] file # @param [String] file
# @return [void] # @return [void]

View file

@ -4,7 +4,7 @@ require "rubygems"
module Middleman module Middleman
# Current Version # Current Version
# @return [String] # @return [String]
VERSION = '3.0.0.beta.1' unless const_defined?(:VERSION) VERSION = '3.0.0.beta.2' unless const_defined?(:VERSION)
# Parsed version for RubyGems # Parsed version for RubyGems
# @private # @private

View file

@ -3,6 +3,8 @@ require "net/http"
require "win32/process" if ::Middleman::WINDOWS require "win32/process" if ::Middleman::WINDOWS
require "fileutils"
module Middleman module Middleman
class Watcher class Watcher
class << self class << self
@ -63,12 +65,26 @@ module Middleman
if @options[:"disable-watcher"] if @options[:"disable-watcher"]
bootup bootup
else else
pid_name = ".mm-pid-#{@options[:port]||4567}"
if File.exists?(pid_name)
current_pid = File.open(pid_name, 'rb') { |f| f.read }
begin
Process.kill("INT", -current_pid.to_i)
rescue
ensure
FileUtils.rm(pid_name)
end
end
@server_job = fork { @server_job = fork {
trap("INT") { exit(0) } trap("INT") { exit(0) }
trap("TERM") { exit(0) } trap("TERM") { exit(0) }
trap("QUIT") { exit(0) } trap("QUIT") { exit(0) }
bootup bootup
} }
File.open(pid_name, "w+") { |f| f.write(Process.getpgrp) }
end end
end end

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |s|
s.require_path = "lib" s.require_path = "lib"
# Core # Core
s.add_dependency("rack", ["~> 1.3.5"]) s.add_dependency("rack", ["~> 1.4.0"])
s.add_dependency("tilt", ["~> 1.3.1"]) s.add_dependency("tilt", ["~> 1.3.1"])
# Builder # Builder
@ -29,7 +29,7 @@ Gem::Specification.new do |s|
s.add_dependency("thor", ["~> 0.14.0"]) s.add_dependency("thor", ["~> 0.14.0"])
# Helpers # Helpers
s.add_dependency("activesupport", ["~> 3.1.0"]) s.add_dependency("activesupport", ["~> 3.2.0"])
# Watcher # Watcher
s.add_dependency("fssm", ["~> 0.2.8"]) s.add_dependency("fssm", ["~> 0.2.8"])

View file

@ -1,4 +1,6 @@
Feature: Compass sprites should be generated on build and copied Feature: Compass sprites should be generated on build and copied
Scenario: Building a clean site with sprites Scenario: Building a clean site with sprites
Given a successfully built app at "compass-sprites-app" Given a successfully built app at "compass-sprites-app"
Then the output should contain "images/icon-" When I cd to "build"
Then the following files should exist:
| images/icon-s1a8aa64128.png |

View file

@ -5,13 +5,15 @@ Feature: Minify CSS
Given "minify_css" feature is "disabled" Given "minify_css" feature is "disabled"
And the Server is running at "minify-css-app" And the Server is running at "minify-css-app"
When I go to "/stylesheets/site.css" When I go to "/stylesheets/site.css"
Then I should see "55" lines Then I should see "60" lines
And I should see "only screen and (device-width"
Scenario: Rendering external css with the feature enabled Scenario: Rendering external css with the feature enabled
Given "minify_css" feature is "enabled" Given "minify_css" feature is "enabled"
And the Server is running at "minify-css-app" And the Server is running at "minify-css-app"
When I go to "/stylesheets/site.css" When I go to "/stylesheets/site.css"
Then I should see "1" lines Then I should see "1" lines
And I should see "only screen and (device-width"
Scenario: Rendering external css with passthrough compressor Scenario: Rendering external css with passthrough compressor
Given the Server is running at "passthrough-app" Given the Server is running at "passthrough-app"

View file

@ -8,3 +8,8 @@ Feature: Sprockets Gems
# Given the Server is running at "sprockets-app" # Given the Server is running at "sprockets-app"
# When I go to "/library/css/bootstrap_include.css" # When I go to "/library/css/bootstrap_include.css"
# Then I should see "Bootstrap" # Then I should see "Bootstrap"
Scenario: Sprockets can pull js from vendored assets
Given the Server is running at "sprockets-app"
When I go to "/library/js/vendored_include.js"
Then I should see "var vendored_js_included = true;"

View file

@ -1 +1,5 @@
@import "compass/reset" @import "compass/reset"
@media handheld, only screen and (device-width: 768px)
body
display: block

View file

@ -0,0 +1 @@
//= require "vendored_js"

View file

@ -0,0 +1 @@
var vendored_js_included = true;

View file

@ -34,11 +34,12 @@ module Middleman::CoreExtensions::Compass
end end
end end
# if build? if build?
# ::Compass.configuration do |config| ::Compass.configuration do |config|
# config.environment = :production config.environment = :production
# end config.project_path = File.join(root, build_dir)
# end end
end
run_hook :compass_config, ::Compass.configuration run_hook :compass_config, ::Compass.configuration
run_hook :after_compass_config run_hook :after_compass_config

View file

@ -13,70 +13,32 @@ module Middleman::CoreExtensions::Sprockets
app.set :js_compressor, false app.set :js_compressor, false
app.set :css_compressor, false app.set :css_compressor, false
# Cut off every extension after .js (which sprockets eats up)
app.build_reroute do |destination, request_path|
if !request_path.match(/\.js\./i)
false
else
[
destination.gsub(/\.js(\..*)$/, ".js"),
request_path.gsub(/\.js(\..*)$/, ".js")
]
end
end
# Once Middleman is setup # Once Middleman is setup
app.ready do app.ready do
# Create sprockets env for JS # Create sprockets env for JS and CSS
js_env = Middleman::CoreExtensions::Sprockets::JavascriptEnvironment.new(self) js_env = Middleman::CoreExtensions::Sprockets::JavascriptEnvironment.new(self)
css_env = Middleman::CoreExtensions::Sprockets::StylesheetEnvironment.new(self)
# Add any gems with vendor/assets/javascripts to paths # Add any gems with (vendor|app|.)/assets/javascripts to paths
vendor_dir = File.join("vendor", "assets", "javascripts") # also add similar directories from project root (like in rails)
gems_with_js = ::Middleman.rubygems_latest_specs.select do |spec| root_paths = [%w{ app }, %w{ assets }, %w{ vendor }, %w{ app assets }, %w{ vendor assets }]
::Middleman.spec_has_file?(spec, vendor_dir) try_js_paths = root_paths.map{|rp| File.join(rp, 'javascripts')}
end.each do |spec| try_css_paths = root_paths.map{|rp| File.join(rp, 'stylesheets')}
js_env.append_path File.join(spec.full_gem_path, vendor_dir)
{ try_js_paths => js_env, try_css_paths => css_env }.each do |paths, env|
([root] + ::Middleman.rubygems_latest_specs.map(&:full_gem_path)).each do |root_path|
paths.map{|p| File.join(root_path, p)}.
select{|p| File.directory?(p)}.
each{|path| env.append_path(path)}
end end
# Add any gems with app/assets/javascripts to paths
app_dir = File.join("app", "assets", "javascripts")
gems_with_js = ::Middleman.rubygems_latest_specs.select do |spec|
::Middleman.spec_has_file?(spec, app_dir)
end.each do |spec|
js_env.append_path File.join(spec.full_gem_path, app_dir)
end
# Intercept requests to /javascripts and pass to sprockets
map "/#{js_dir}" do
run js_env
end end
# Setup Sprockets Sass options # Setup Sprockets Sass options
sass.each { |k, v| ::Sprockets::Sass.options[k] = v } sass.each { |k, v| ::Sprockets::Sass.options[k] = v }
# Create sprockets env for CSS # Intercept requests to /javascripts and /stylesheets and pass to sprockets
css_env = Middleman::CoreExtensions::Sprockets::StylesheetEnvironment.new(self) map("/#{js_dir}") { run js_env }
map("/#{css_dir}"){ run css_env }
# Add any gems with vendor/assets/stylesheets to paths
vendor_dir = File.join("vendor", "assets", "stylesheets")
gems_with_css = ::Middleman.rubygems_latest_specs.select do |spec|
::Middleman.spec_has_file?(spec, vendor_dir)
end.each do |spec|
css_env.append_path File.join(spec.full_gem_path, vendor_dir)
end
# Add any gems with app/assets/stylesheets to paths
app_dir = File.join("app", "assets", "stylesheets")
gems_with_css = ::Middleman.rubygems_latest_specs.select do |spec|
::Middleman.spec_has_file?(spec, app_dir)
end.each do |spec|
css_env.append_path File.join(spec.full_gem_path, app_dir)
end
# Intercept requests to /stylesheets and pass to sprockets
map("/#{css_dir}") do
run css_env
end
end end
end end
alias :included :registered alias :included :registered

View file

@ -12,8 +12,8 @@ module Middleman::Extensions
# Tell Sprockets to use the built in CSSMin # Tell Sprockets to use the built in CSSMin
app.after_configuration do app.after_configuration do
if !css_compressor if !css_compressor
require "middleman-more/extensions/minify_css/cssmin" require "middleman-more/extensions/minify_css/rainpress"
set :css_compressor, ::CSSMin set :css_compressor, ::Rainpress
end end
end end
end end

View file

@ -1,60 +0,0 @@
#--
# Copyright (c) 2008 Ryan Grove <ryan@wonko.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of this project nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#++
# Minify CSS
module CSSMin
# Compress a CSS string
# @param [String] input
# @return [String]
def self.compress(input)
css = input.is_a?(IO) ? input.read : input.dup.to_s
css.gsub!(/\/\*[\s\S]*?\*\//, '')
css.gsub!(/\s+/, ' ')
css.gsub!(/"\\"\}\\""/, '___BMH___')
css.gsub!(/(?:^|\})[^\{:]+\s+:+[^\{]*\{/) do |match|
match.gsub(':', '___PSEUDOCLASSCOLON___')
end
css.gsub!(/\s+([!\{\};:>+\(\)\],])/, '\1')
css.gsub!('___PSEUDOCLASSCOLON___', ':')
css.gsub!(/([!\{\}:;>+\(\[,])\s+/, '\1')
css.gsub!(/([^;\}])\}/, '\1;}')
css.gsub!(/([\s:])([+-]?0)(?:%|em|ex|px|in|cm|mm|pt|pc)/i, '\1\2')
css.gsub!(/:(?:0 )+0;/, ':0;')
css.gsub!('background-position:0;', 'background-position:0 0;')
css.gsub!(/(:|\s)0+\.(\d+)/, '\1.\2')
css.gsub!(/rgb\s*\(\s*([0-9,\s]+)\s*\)/) do |match|
'#' << $1.scan(/\d+/).map{|n| n.to_i.to_s(16).rjust(2, '0') }.join
end
css.gsub!(/([^"'=\s])\s*#([0-9a-f])\2([0-9a-f])\3([0-9a-f])\4/i, '\1 #\2\3\4')
css.gsub!(/[^\}]+\{;\}\n/, '')
css.gsub!('___BMH___', '"\"}\""')
css.gsub!(/;;+/, ';')
css.strip
end
end

View file

@ -0,0 +1,168 @@
# == Information
#
# This is the main class of Rainpress, create an instance of it to compress
# your CSS-styles.
#
# Author:: Uwe L. Korn <uwelk@xhochy.org>
#
# <b>Options:</b>
#
# * <tt>:comments</tt> - if set to false, comments will not be removed
# * <tt>:newlines</tt> - if set to false, newlines will not be removed
# * <tt>:spaces</tt> - if set to false, spaces will not be removed
# * <tt>:colors</tt> - if set to false, colors will not be modified
# * <tt>:misc</tt> - if set to false, miscellaneous compression parts will be skipped
class Rainpress
# Quick-compress the styles.
# This eliminates the need to create an instance of the class
def self.compress(style, options = {})
self.new(style, options).compress!
end
def initialize(style, opts = {})
@style = style
@opts = {
:comments => true,
:newlines => true,
:spaces => true,
:colors => true,
:misc => true
}
@opts.merge! opts
end
# Run the compressions and return the newly compressed text
def compress!
remove_comments! if @opts[:comments]
remove_newlines! if @opts[:newlines]
remove_spaces! if @opts[:spaces]
shorten_colors! if @opts[:colors]
do_misc! if @opts[:misc]
@style
end
# Remove all comments out of the CSS-Document
#
# Only /* text */ comments are supported.
# Attention: If you are doing css hacks for IE using the comment tricks,
# they will be removed using this function. Please consider for IE css style
# corrections the usage of conditionals comments in your (X)HTML document.
def remove_comments!
input = @style
@style = ''
while input.length > 0 do
pos = input.index("/*");
# No more comments
if pos == nil
@style += input
input = '';
else # Comment beginning at pos
@style += input[0..(pos-1)] if pos > 0 # only append text if there is some
input = input[(pos+2)..-1]
# Comment ending at pos
pos = input.index("*/")
input = input[(pos+2)..-1]
end
end
end
# Remove all newline characters
#
# We take care of Windows(\r\n), Unix(\n) and Mac(\r) newlines.
def remove_newlines!
@style.gsub! /\n|\r/, ''
end
# Remove unneeded spaces
#
# 1. Turn mutiple spaces into a single
# 2. Remove spaces around ;:{},
# 3. Remove tabs
def remove_spaces!
@style.gsub! /\s*(\s|;|:|\}|\{|,)\s*/, '\1'
@style.gsub! "\t", ''
end
# Replace color values with their shorter equivalent
#
# 1. Turn rgb(,,)-colors into #-values
# 2. Shorten #AABBCC down to #ABC
# 3. Replace names with their shorter hex-equivalent
# * white -> #fff
# * black -> #000
# 4. Replace #-values with their shorter name
# * #f00 -> red
def shorten_colors!
# rgb(50,101,152) to #326598
@style.gsub! /rgb\s*\(\s*([0-9,\s]+)\s*\)/ do |match|
out = '#'
$1.split(',').each do |num|
out += '0' if num.to_i < 16
out += num.to_i.to_s(16) # convert to hex
end
out
end
# Convert #AABBCC to #ABC, keep if preceed by a '='
@style.gsub! /([^\"'=\s])(\s*)#([\da-f])\3([\da-f])\4([\da-f])\5/i, '\1#\3\4\5'
# At the moment we assume that colours only appear before ';' or '}' and
# after a ':', if there could be an occurence of a color before or after
# an other character, submit either a bug report or, better, a patch that
# enables Rainpress to take care of this.
# shorten several names to numbers
## shorten white -> #fff
@style.gsub! /:\s*white\s*(;|\})/, ':#fff\1'
## shorten black -> #000
@style.gsub! /:\s*black\s*(;|\})/, ':#000\1'
# shotern several numbers to names
## shorten #f00 or #ff0000 -> red
@style.gsub! /:\s*#f{1,2}0{2,4}(;|\})/i, ':red\1'
end
# Do miscellaneous compression methods on the style.
def do_misc!
# Replace 0(pt,px,em,%) with 0 but only when preceded by : or a white-space
@style.gsub! /([\s:]+)(0)(px|em|%|in|cm|mm|pc|pt|ex)/i, '\1\2'
# Replace :0 0 0 0(;|}) with :0(;|})
@style.gsub! /:0 0 0 0(;|\})/, ':0\1'
# Replace :0 0 0(;|}) with :0(;|})
@style.gsub! /:0 0 0(;|\})/, ':0\1'
# Replace :0 0(;|}) with :0(;|})
@style.gsub! /:0 0(;|\})/, ':0\1'
# Replace background-position:0; with background-position:0 0;
@style.gsub! 'background-position:0;', 'background-position:0 0;'
# Replace 0.6 to .6, but only when preceded by : or a white-space
@style.gsub! /[:\s]0+\.(\d+)/ do |match|
match.sub '0', '' # only first '0' !!
end
# Replace multiple ';' with a single ';'
@style.gsub! /[;]+/, ';'
# Replace ;} with }
@style.gsub! ';}', '}'
# Replace font-weight:normal; with 400
@style.gsub! /font-weight[\s]*:[\s]*normal[\s]*(;|\})/i,'font-weight:400\1'
@style.gsub! /font[\s]*:[\s]*normal[\s;\}]*/ do |match|
match.sub 'normal', '400'
end
# Replace font-weight:bold; with 700
@style.gsub! /font-weight[\s]*:[\s]*bold[\s]*(;|\})/,'font-weight:700\1'
@style.gsub! /font[\s]*:[\s]*bold[\s;\}]*/ do |match|
match.sub 'bold', '700'
end
end
end

View file

@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency("sass", ["~> 3.1.7"]) s.add_dependency("sass", ["~> 3.1.7"])
s.add_dependency("compass", ["0.12.rc.1"]) s.add_dependency("compass", ["0.12.rc.1"])
s.add_dependency("coffee-script", ["~> 2.2.0"]) s.add_dependency("coffee-script", ["~> 2.2.0"])
s.add_dependency("execjs", ["~> 1.2.7"]) s.add_dependency("execjs", ["~> 1.2"])
s.add_dependency("sprockets", ["~> 2.1.0"]) s.add_dependency("sprockets", ["~> 2.1.0"])
s.add_dependency("sprockets-sass", ["~> 0.6.0"]) s.add_dependency("sprockets-sass", ["~> 0.6.0"])
s.add_dependency("redcarpet", ["~> 2.0.0"]) s.add_dependency("redcarpet", ["~> 2.0.0"])