Rewritten core which makes better use of Tilt. Support for SCSS, Coffee and Less.

This commit is contained in:
tdreyno 2010-09-04 20:26:48 -07:00
parent bccc6d2d74
commit 5523a10d9b
40 changed files with 642 additions and 668 deletions

View file

@ -13,24 +13,21 @@ begin
gem.authors = ["Thomas Reynolds"] gem.authors = ["Thomas Reynolds"]
gem.rubyforge_project = "middleman" gem.rubyforge_project = "middleman"
gem.executables = %w(mm-init mm-build mm-server) gem.executables = %w(mm-init mm-build mm-server)
gem.add_dependency("rack") gem.add_dependency("rack", "~>1.0")
gem.add_dependency("thin") gem.add_dependency("thin", "~>1.2.0")
gem.add_dependency("shotgun", "~>0.8.0")
gem.add_dependency("shotgun", ">=0.8") gem.add_dependency("templater", "~>1.0.0")
gem.add_dependency("templater") gem.add_dependency("sprockets", "~>1.0.0")
gem.add_dependency("sprockets") gem.add_dependency("sinatra", "~>1.0")
gem.add_dependency("sinatra", ">=1.0") gem.add_dependency("sinatra-content-for", "~>0.2.0")
gem.add_dependency("sinatra-content-for") gem.add_dependency("rack-test", "~>0.5.0")
gem.add_dependency("less") gem.add_dependency("yui-compressor", "~>0.9.0")
gem.add_dependency("builder") gem.add_dependency("haml", "~>3.0")
gem.add_dependency("rack-test") gem.add_dependency("compass", "~>0.10.0")
gem.add_dependency("yui-compressor") gem.add_dependency("json_pure", "~>1.4.0")
gem.add_dependency("haml", ">=3.0") gem.add_dependency("smusher", "~>0.4.5")
gem.add_dependency("compass", ">=0.10") gem.add_dependency("compass-slickmap", "~>0.3.0")
gem.add_dependency("fancy-buttons") gem.add_dependency("livereload", "~>1.4.0")
gem.add_dependency("json_pure")
gem.add_dependency("smusher")
gem.add_dependency("compass-slickmap")
gem.add_development_dependency("rspec") gem.add_development_dependency("rspec")
gem.add_development_dependency("cucumber") gem.add_development_dependency("cucumber")

View file

@ -1,14 +1,14 @@
Feature: Automatically detect and insert image dimensions into tags Feature: Automatically detect and insert image dimensions into tags
In order to speed up development and appease YSlow In order to speed up development and appease YSlow
Scenario: Rendering an image with the feature enabled
Given "automatic_image_sizes" feature is "enabled"
When I go to "/auto-image-sizes.html"
Then I should see "width="
And I should see "height="
Scenario: Rendering an image with the feature disabled Scenario: Rendering an image with the feature disabled
Given "automatic_image_sizes" feature is "disabled" Given "automatic_image_sizes" feature is "disabled"
When I go to "/auto-image-sizes.html" When I go to "/auto-image-sizes.html"
Then I should not see "width=" Then I should not see "width="
And I should not see "height=" And I should not see "height="
Scenario: Rendering an image with the feature enabled
Given "automatic_image_sizes" feature is "enabled"
When I go to "/auto-image-sizes.html"
Then I should see "width="
And I should see "height="

View file

@ -1,15 +1,5 @@
Feature: Generate mtime-based query string for busting browser caches Feature: Generate mtime-based query string for busting browser caches
In order to display the most recent content for IE & CDNs and appease YSlow In order to display the most recent content for IE & CDNs and appease YSlow
Scenario: Rendering css with the feature enabled
Given "cache_buster" feature is "enabled"
When I go to "/stylesheets/relative_assets.css"
Then I should see "?"
Scenario: Rendering html with the feature enabled
Given "cache_buster" feature is "enabled"
When I go to "/cache-buster.html"
Then I should not see "?"
Scenario: Rendering css with the feature disabled Scenario: Rendering css with the feature disabled
Given "cache_buster" feature is "disabled" Given "cache_buster" feature is "disabled"
@ -19,4 +9,14 @@ Feature: Generate mtime-based query string for busting browser caches
Scenario: Rendering html with the feature disabled Scenario: Rendering html with the feature disabled
Given "cache_buster" feature is "disabled" Given "cache_buster" feature is "disabled"
When I go to "/cache-buster.html" When I go to "/cache-buster.html"
Then I should not see "?"
Scenario: Rendering css with the feature enabled
Given "cache_buster" feature is "enabled"
When I go to "/stylesheets/relative_assets.css"
Then I should see "?"
Scenario: Rendering html with the feature enabled
Given "cache_buster" feature is "enabled"
When I go to "/cache-buster.html"
Then I should not see "?" Then I should not see "?"

View file

@ -1,22 +1,22 @@
Feature: Minify CSS Feature: Minify CSS
In order reduce bytes sent to client and appease YSlow In order reduce bytes sent to client and appease YSlow
Scenario: Rendering inline css with the feature enabled
Given "minify_css" feature is "enabled"
When I go to "/inline-css.html"
Then I should see "1" lines
Scenario: Rendering inline css with the feature disabled Scenario: Rendering inline css with the feature disabled
Given "minify_css" feature is "disabled" Given "minify_css" feature is "disabled"
When I go to "/inline-css.html" When I go to "/inline-css.html"
Then I should see "4" lines Then I should see "4" lines
Scenario: Rendering external css with the feature enabled
Given "minify_css" feature is "enabled"
When I go to "/stylesheets/site.css"
Then I should see "1" lines
Scenario: Rendering external css with the feature disabled Scenario: Rendering external css with the feature disabled
Given "minify_css" feature is "disabled" Given "minify_css" feature is "disabled"
When I go to "/stylesheets/site.css" When I go to "/stylesheets/site.css"
Then I should see "51" lines Then I should see "51" lines
Scenario: Rendering inline css with the feature enabled
Given "minify_css" feature is "enabled"
When I go to "/inline-css.html"
Then I should see "1" lines
Scenario: Rendering external css with the feature enabled
Given "minify_css" feature is "enabled"
When I go to "/stylesheets/site.css"
Then I should see "1" lines

View file

@ -1,12 +1,12 @@
Feature: Minify Javascript Feature: Minify Javascript
In order reduce bytes sent to client and appease YSlow In order reduce bytes sent to client and appease YSlow
Scenario: Rendering inline js with the feature disabled
Given "minify_javascript" feature is "disabled"
When I go to "/inline-js.html"
Then I should see "10" lines
Scenario: Rendering inline js with the feature enabled Scenario: Rendering inline js with the feature enabled
Given "minify_javascript" feature is "enabled" Given "minify_javascript" feature is "enabled"
When I go to "/inline-js.html" When I go to "/inline-js.html"
Then I should see "1" lines Then I should see "1" lines
Scenario: Rendering inline js with the feature disabled
Given "minify_javascript" feature is "disabled"
When I go to "/inline-js.html"
Then I should see "10" lines

View file

@ -1,4 +1,5 @@
Given /^I am using an asset host$/ do Given /^I am using an asset host$/ do
Middleman::Base.enable :asset_host
Middleman::Base.set :asset_host do |asset| Middleman::Base.set :asset_host do |asset|
"http://assets%d.example.com" % (asset.hash % 4) "http://assets%d.example.com" % (asset.hash % 4)
end end

View file

@ -23,6 +23,6 @@ Then /^I should not see "([^\"]*)"$/ do |expected|
end end
Then /^I should see "([^\"]*)" lines$/ do |lines| Then /^I should see "([^\"]*)" lines$/ do |lines|
puts @browser.last_response.body $stderr.puts @browser.last_response.body
@browser.last_response.body.chomp.split($/).length.should == lines.to_i @browser.last_response.body.chomp.split($/).length.should == lines.to_i
end end

View file

@ -4,22 +4,5 @@ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
require 'rubygems' require 'rubygems'
module Middleman module Middleman
autoload :Base, "middleman/base"
module Rack
autoload :Sprockets, "middleman/rack/sprockets"
autoload :MinifyJavascript, "middleman/rack/minify_javascript"
autoload :MinifyCSS, "middleman/rack/minify_css"
end
module Renderers
autoload :ERb, "middleman/renderers/erb"
autoload :Builder, "middleman/renderers/builder"
autoload :Less, "middleman/renderers/less"
end
autoload :Base, "middleman/base"
autoload :Haml, "middleman/renderers/haml"
autoload :Sass, "middleman/renderers/sass"
autoload :Helpers, "middleman/helpers"
end end

33
lib/middleman/assets.rb Normal file
View file

@ -0,0 +1,33 @@
module Middleman
module Assets
@@asset_handler_map = []
@@asset_handler_stack = []
def self.register(handler_name, &block)
if block_given?
@@asset_handler_stack << block
@@asset_handler_map << handler_name
end
end
def self.get_url(path, prefix="", request=nil)
@@asset_handler_stack.last.call(path, prefix, request)
end
def self.before(position, *args)
current_index = @@asset_handler_map.index(position)
return nil unless current_index
previous = current_index - 1
if (previous >= 0) && (previous < @@asset_handler_map.length)
@@asset_handler_stack[previous].call(*args)
else
nil
end
end
end
end
Middleman::Assets.register :base do |path, prefix, request|
path.include?("://") ? path : File.join(Middleman::Base.http_prefix || "/", prefix, path)
end

View file

@ -7,7 +7,6 @@ class Sinatra::Request
end end
module Middleman module Middleman
module Rack; end
class Base < Sinatra::Base class Base < Sinatra::Base
set :app_file, __FILE__ set :app_file, __FILE__
set :root, ENV["MM_DIR"] || Dir.pwd set :root, ENV["MM_DIR"] || Dir.pwd
@ -42,10 +41,9 @@ module Middleman
def self.set(option, value=self, &block) def self.set(option, value=self, &block)
if block_given? if block_given?
value = Proc.new { block } value = Proc.new { block }
super(option, value, &nil)
else
super
end end
super(option, value, &nil)
end end
@@afters = [] @@afters = []
@ -58,22 +56,6 @@ module Middleman
ext = ".#{ext}" unless ext.to_s[0] == ?. ext = ".#{ext}" unless ext.to_s[0] == ?.
::Rack::Mime::MIME_TYPES[ext.to_s] = type ::Rack::Mime::MIME_TYPES[ext.to_s] = type
end end
# Convenience function to discover if a template exists for the requested renderer (haml, sass, etc)
def template_exists?(path, renderer=nil)
template_path = path.dup
template_path << ".#{renderer}" if renderer
File.readable? File.join(settings.views, template_path)
end
# Base case renderer (do nothing), Should be over-ridden
module StaticRender
def render_path(path, layout)
return false if !template_exists?(path, :erb)
erb(path.to_sym, :layout => layout)
end
end
include StaticRender
@@layout = nil @@layout = nil
def self.page(url, options={}, &block) def self.page(url, options={}, &block)
@ -106,7 +88,7 @@ module Middleman
not_found do not_found do
process_request process_request
end end
private private
def process_request(layout = :layout) def process_request(layout = :layout)
# Normalize the path and add index if we're looking at a directory # Normalize the path and add index if we're looking at a directory
@ -114,44 +96,33 @@ module Middleman
path << settings.index_file if path.match(%r{/$}) path << settings.index_file if path.match(%r{/$})
path.gsub!(%r{^/}, '') path.gsub!(%r{^/}, '')
# If the enabled renderers succeed, return the content, mime-type and an HTTP 200 template_path = locate_template_file(path)
if content = render_path(path, layout) if template_path
content_type mime_type(File.extname(path)), :charset => 'utf-8' content_type mime_type(File.extname(path)), :charset => 'utf-8'
status 200
content renderer = Middleman::Renderers.get_method(template_path)
else if respond_to? renderer
status 404 status 200
return send(renderer, path.to_sym, { :layout => layout })
end
end end
status 404
end
def locate_template_file(path)
template_path = File.join(settings.views, "#{path}.*")
Dir.glob(template_path).first
end end
end end
end end
require "middleman/renderers/erb" require "middleman/assets"
require "middleman/renderers/haml" require "middleman/renderers"
require "middleman/renderers/sass" require "middleman/features"
require "middleman/renderers/less"
require "middleman/renderers/builder"
# The Rack App
class Middleman::Base class Middleman::Base
helpers Middleman::Helpers
# Features disabled by default
enable :asset_host
disable :slickmap
disable :cache_buster
disable :minify_css
disable :minify_javascript
disable :relative_assets
disable :smush_pngs
disable :automatic_image_sizes
disable :relative_assets
disable :cache_buster
disable :ugly_haml
# Default build features
configure :build do
end
def self.new(*args, &block) def self.new(*args, &block)
# Check for and evaluate local configuration # Check for and evaluate local configuration
local_config = File.join(self.root, "init.rb") local_config = File.join(self.root, "init.rb")
@ -164,22 +135,11 @@ class Middleman::Base
# loop over enabled feature # loop over enabled feature
features.flatten.each do |feature_name| features.flatten.each do |feature_name|
next unless send(:"#{feature_name}?") next unless send(:"#{feature_name}?")
$stderr.puts "== Enabling: #{feature_name.to_s.capitalize}" if logging?
feature_path = "features/#{feature_name}" Middleman::Features.run(feature_name, self)
if File.exists? File.join(File.dirname(__FILE__), "#{feature_path}.rb")
puts "== Enabling: #{feature_name.to_s.capitalize}" if logging?
require "middleman/#{feature_path}"
end
end end
use ::Rack::ConditionalGet if environment == :development use ::Rack::ConditionalGet if environment == :development
use Middleman::Rack::MinifyJavascript if minify_javascript?
use Middleman::Rack::MinifyCSS if minify_css?
# Built-in javascript combination
use Middleman::Rack::Sprockets, :root => Middleman::Base.root,
:load_path => [ File.join("public", Middleman::Base.js_dir),
File.join("views", Middleman::Base.js_dir) ]
@@afters.each { |block| class_eval(&block) } @@afters.each { |block| class_eval(&block) }

40
lib/middleman/features.rb Normal file
View file

@ -0,0 +1,40 @@
module Middleman
module Features
# Top-level method to register a new feature
@@features = {}
def self.register(feature_name, feature_class=nil, options={})
@@features[feature_name] = feature_class
# Default to disabled, unless the class asks to auto-enable
activate_method = (options.has_key?(:auto_enable) && options[:auto_enable]) ? :enable : :disable
Middleman::Base.send(activate_method, feature_name)
end
def self.run(feature_name, scope)
feature_class = @@features[feature_name]
feature_class.new(scope) unless feature_class.nil?
end
def self.all
@@features
end
end
end
%w(asset_host
automatic_image_sizes
cache_buster
default_helpers
livereload
minify_css
minify_javascript
relative_assets
slickmap
smush_pngs
sprockets
ugly_haml).each do |feature|
require File.join("middleman/features", feature)
end

View file

@ -1,24 +1,21 @@
class Middleman::Base class Middleman::Features::AssetHost
after_feature_init do def initialize(app)
::Compass.configuration do |config| Middleman::Base.after_feature_init do
config.asset_host(&self.asset_host) if Middleman::Base.asset_host.is_a?(Proc)
::Compass.configuration.asset_host(&Middleman::Base.asset_host)
end
end end
end if self.enabled?(:asset_host) && self.asset_host && self.asset_host.is_a?(Proc)
end Middleman::Assets.register :asset_host do |path, prefix, request|
original_output = Middleman::Assets.before(:asset_host, path, prefix, request)
class << Middleman::Base valid_extensions = %w(.png .gif .jpg .jpeg .js .css)
alias_method :pre_asset_host_asset_url, :asset_url
def asset_url(path, prefix="", request=nil) asset_prefix = Middleman::Base.asset_host.call(original_output)
original_output = pre_asset_host_asset_url(path, prefix, request)
File.join(asset_prefix, original_output)
valid_extensions = %w(.png .gif .jpg .jpeg .js .css)
if !self.enabled?(:asset_host) || path.include?("://") || !valid_extensions.include?(File.extname(path)) || !self.asset_host || !self.asset_host.is_a?(Proc) || !self.asset_host.respond_to?(:call)
return original_output
end end
asset_prefix = self.asset_host.call(original_output)
return File.join(asset_prefix, original_output)
end end
end end
Middleman::Features.register :asset_host, Middleman::Features::AssetHost

View file

@ -1,31 +1,31 @@
require "middleman/fastimage" class Middleman::Features::AutomaticImageSizes
def initialize(app)
require "middleman/features/automatic_image_sizes/fastimage"
class Middleman::Base Middleman::Base.send :alias_method, :pre_automatic_image_tag, :image_tag
alias_method :pre_automatic_image_tag, :image_tag Middleman::Base.helpers do
helpers do def image_tag(path, params={})
def image_tag(path, params={}) if (!params[:width] || !params[:height]) && !path.include?("://")
if !self.enabled?(:automatic_image_sizes) params[:alt] ||= ""
return pre_automatic_image_tag(path, params) http_prefix = settings.http_images_path rescue settings.images_dir
end
if (!params[:width] || !params[:height]) && !path.include?("://") begin
params[:alt] ||= "" real_path = File.join(settings.public, settings.images_dir, path)
http_prefix = settings.http_images_path rescue settings.images_dir if File.exists? real_path
dimensions = Middleman::FastImage.size(real_path, :raise_on_failure => true)
begin params[:width] ||= dimensions[0]
real_path = File.join(settings.public, settings.images_dir, path) params[:height] ||= dimensions[1]
if File.exists? real_path end
dimensions = Middleman::FastImage.size(real_path, :raise_on_failure => true) rescue
params[:width] ||= dimensions[0]
params[:height] ||= dimensions[1]
end end
rescue
end
capture_haml { haml_tag(:img, params.merge(:src => asset_url(path, http_prefix))) } capture_haml { haml_tag(:img, params.merge(:src => asset_url(path, http_prefix))) }
else else
pre_automatic_image_tag(path, params) pre_automatic_image_tag(path, params)
end
end end
end end
end end
end end
Middleman::Features.register :automatic_image_sizes, Middleman::Features::AutomaticImageSizes

View file

@ -1,50 +1,43 @@
class Middleman::Base class Middleman::Features::CacheBuster
after_feature_init do def initialize(app)
::Compass.configuration do |config| Middleman::Assets.register :cache_buster do |path, prefix, request|
if self.enabled?(:cache_buster) http_path = Middleman::Assets.before(:cache_buster, path, prefix, request)
if http_path.include?("://") || !%w(.css .png .jpg .js .gif).include?(File.extname(http_path))
http_path
else
begin
prefix = Middleman::Base.images_dir if prefix == Middleman::Base.http_images_path
rescue
end
real_path_static = File.join(Middleman::Base.public, prefix, path)
if File.readable?(real_path_static)
http_path << "?" + File.mtime(real_path_static).strftime("%s")
elsif Middleman::Base.environment == "build"
real_path_dynamic = File.join(Middleman::Base.root, Middleman::Base.build_dir, prefix, path)
http_path << "?" + File.mtime(real_path_dynamic).strftime("%s") if File.readable?(real_path_dynamic)
end
http_path
end
end
Middleman::Base.after_feature_init do
::Compass.configuration do |config|
config.asset_cache_buster do |path, real_path| config.asset_cache_buster do |path, real_path|
real_path = real_path.path if real_path.is_a? File real_path = real_path.path if real_path.is_a? File
real_path = real_path.gsub(File.join(self.root, self.build_dir), self.public) real_path = real_path.gsub(File.join(Middleman::Base.root, Middleman::Base.build_dir), Middleman::Base.public)
if File.readable?(real_path) if File.readable?(real_path)
File.mtime(real_path).strftime("%s") File.mtime(real_path).strftime("%s")
else else
$stderr.puts "WARNING: '#{File.basename(path)}' was not found (or cannot be read) in #{File.dirname(real_path)}" $stderr.puts "WARNING: '#{File.basename(path)}' was not found (or cannot be read) in #{File.dirname(real_path)}"
end end
end end
else
config.asset_cache_buster { false }
end end
end end
::Compass.configure_sass_plugin!
end end
end end
class << Middleman::Base Middleman::Features.register :cache_buster, Middleman::Features::CacheBuster
alias_method :pre_cache_buster_asset_url, :asset_url
def asset_url(path, prefix="", request=nil)
http_path = pre_cache_buster_asset_url(path, prefix, request)
return http_path unless self.enabled?(:cache_buster)
if http_path.include?("://") || !%w(.css .png .jpg .js .gif).include?(File.extname(http_path))
http_path
else
begin
prefix = self.images_dir if prefix == self.http_images_path
rescue
end
real_path_static = File.join(self.public, prefix, path)
if File.readable?(real_path_static)
http_path << "?" + File.mtime(real_path_static).strftime("%s")
elsif Middleman::Base.environment == "build"
real_path_dynamic = File.join(self.root, self.build_dir, prefix, path)
http_path << "?" + File.mtime(real_path_dynamic).strftime("%s") if File.readable?(real_path_dynamic)
end
http_path
end
end
end

View file

@ -1,15 +1,9 @@
module Middleman class Middleman::Features::DefaultHelpers
class Base def initialize(app)
def self.asset_url(path, prefix="", request=nil) Middleman::Base.helpers Helpers
path.include?("://") ? path : File.join(self.http_prefix || "/", prefix, path)
end
end end
module Helpers module Helpers
def haml_partial(name, options = {})
haml name.to_sym, options.merge(:layout => false)
end
def auto_stylesheet_link_tag(separator="/") def auto_stylesheet_link_tag(separator="/")
path = request.path_info.dup path = request.path_info.dup
path << self.class.index_file if path.match(%r{/$}) path << self.class.index_file if path.match(%r{/$})
@ -19,8 +13,9 @@ module Middleman
css_file = File.join(self.class.public, self.class.css_dir, "#{path}.css") css_file = File.join(self.class.public, self.class.css_dir, "#{path}.css")
sass_file = File.join(self.class.views, self.class.css_dir, "#{path}.css.sass") sass_file = File.join(self.class.views, self.class.css_dir, "#{path}.css.sass")
scss_file = File.join(self.class.views, self.class.css_dir, "#{path}.css.scss")
if File.exists?(css_file) || File.exists?(sass_file) if File.exists?(css_file) || File.exists?(sass_file) || File.exists?(scss_file)
stylesheet_link_tag "#{path}.css" stylesheet_link_tag "#{path}.css"
end end
end end
@ -38,7 +33,7 @@ module Middleman
end end
def asset_url(path, prefix="") def asset_url(path, prefix="")
self.class.asset_url(path, prefix, request) Middleman::Assets.get_url(path, prefix, request)
end end
def link_to(title, url="#", params={}) def link_to(title, url="#", params={})
@ -69,3 +64,5 @@ module Middleman
end end
end end
end end
Middleman::Features.register :default_helpers, Middleman::Features::DefaultHelpers, { :auto_enable => true }

View file

@ -0,0 +1,19 @@
class Middleman::Features::LiveReload
def initialize(app)
return unless Middleman::Base.environment == :development
begin
require 'livereload'
rescue LoadError
puts "Livereload not available. Install it with: gem install livereload"
end
new_config = ::LiveReload::Config.new do |config|
config.exts = %w(haml sass scss coffee less builder)
end
::LiveReload.run [Middleman::Base.public, Middleman::Base.views], new_config
end
end
Middleman::Features.register :livereload, Middleman::Features::LiveReload

View file

@ -0,0 +1,9 @@
class Middleman::Features::MinifyCSS
def initialize(app)
Middleman::Base.after_feature_init do
::Compass.configuration.output_style = :compressed
end
end
end
Middleman::Features.register :minify_css, Middleman::Features::MinifyCSS

View file

@ -1,22 +1,20 @@
module Middleman class Middleman::Features::MinifyJavascript
module Minified def initialize(app)
Haml::Javascript.send :include, ::Haml::Filters::Base
require "middleman/features/minify_javascript/rack"
app.use Middleman::Rack::MinifyJavascript
end
module Haml
module Javascript module Javascript
include ::Haml::Filters::Base
def render_with_options(text, options) def render_with_options(text, options)
if Middleman::Base.respond_to?(:minify_javascript?) && Middleman::Base.minify_javascript? compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
compressor = ::YUI::JavaScriptCompressor.new(:munge => true) data = compressor.compress(text)
data = compressor.compress(text) %Q{<script type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}>#{data.chomp}</script>}
%Q{<script type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}>#{data.chomp}</script>}
else
<<END
<script type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}>
//<![CDATA[
#{text.rstrip.gsub("\n", "\n ")}
//]]>
</script>
END
end
end end
end end
end end
end end
Middleman::Features.register :minify_javascript, Middleman::Features::MinifyJavascript

View file

@ -0,0 +1,31 @@
begin
require "yui/compressor"
rescue LoadError
puts "YUI-Compressor not available. Install it with: gem install yui-compressor"
end
module Middleman
module Rack
class MinifyJavascript
def initialize(app, options={})
@app = app
end
def call(env)
status, headers, response = @app.call(env)
if env["PATH_INFO"].match(/\.js$/)
compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
uncompressed_source = response.is_a?(::Rack::File) ? File.read(response.path) : response
response = compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(response).to_s
end
[status, headers, response]
end
end
end
end

View file

@ -1,46 +1,35 @@
class Middleman::Base class Middleman::Features::RelativeAssets
after_feature_init do def initialize(app)
::Compass.configuration do |config| ::Compass.configuration.relative_assets = true
config.relative_assets = Proc.new do
Middleman::Base.enabled?(:relative_assets) Middleman::Assets.register :relative_assets do |path, prefix, request|
begin
prefix = Middleman::Base.images_dir if prefix == Middleman::Base.http_images_path
rescue
end
if path.include?("://")
Middleman::Assets.before(:relative_assets, path, prefix, request)
elsif path[0,1] == "/"
path
else
path = File.join(prefix, path) if prefix.length > 0
request_path = request.path_info.dup
request_path << Middleman::Base.index_file if path.match(%r{/$})
request_path.gsub!(%r{^/}, '')
parts = request_path.split('/')
if parts.length > 1
arry = []
(parts.length - 1).times { arry << ".." }
arry << path
File.join(*arry)
else
path
end
end end
end end
::Compass.configure_sass_plugin!
end end
end end
class << Middleman::Base Middleman::Features.register :relative_assets, Middleman::Features::RelativeAssets
alias_method :pre_relative_asset_url, :asset_url
def asset_url(path, prefix="", request=nil)
if !self.enabled?(:relative_assets)
return pre_relative_asset_url(path, prefix, request)
end
begin
prefix = self.images_dir if prefix == self.http_images_path
rescue
end
if path.include?("://")
pre_relative_asset_url(path, prefix, request)
elsif path[0,1] == "/"
path
else
path = File.join(prefix, path) if prefix.length > 0
request_path = request.path_info.dup
request_path << self.index_file if path.match(%r{/$})
request_path.gsub!(%r{^/}, '')
parts = request_path.split('/')
if parts.length > 1
arry = []
(parts.length - 1).times { arry << ".." }
arry << path
File.join(*arry)
else
path
end
end
end
end

View file

@ -1,92 +1,91 @@
begin
require 'slickmap'
::Compass.configure_sass_plugin!
rescue LoadError
puts "Slickmap not available. Install it with: gem install compass-slickmap"
end
if Middleman::Base.environment == "build"
Middleman::Builder.template :slickmap, "sitemap.html", "sitemap.html"
end
Entry = Struct.new(:dir, :children) Entry = Struct.new(:dir, :children)
class Middleman::Base class Middleman::Features::Slickmap
def build_sitemap(&block) def initialize(app)
@@utility = [] require 'slickmap'
[recurse_sitemap(Middleman::Base.views, &block), @@utility]
end
def recurse_sitemap(path, &block)
bad_ext = path.split('.html')[1]
path = path.gsub(bad_ext, '') if bad_ext
entry = Entry.new(path, [])
#no "." or ".." dirs if Middleman::Base.environment == "build"
Dir[File.join(path, "*")].each do |e| Middleman::Builder.template :slickmap, "sitemap.html", "sitemap.html"
next if !File.directory?(e) && !e.include?(".html")
if File.directory?(e)
entry.children << recurse_sitemap(e, &block)
elsif block_given?
how_to_handle = block.call(e)
if how_to_handle == :valid
entry.children << recurse_sitemap(e, &block)
elsif how_to_handle == :utility
bad_ext = e.split('.html')[1]
e = e.gsub(bad_ext, '') if bad_ext
@@utility << e.gsub(Middleman::Base.views + "/", '')
end
end
end end
entry def build_sitemap(&block)
end @@utility = []
[recurse_sitemap(Middleman::Base.views, &block), @@utility]
helpers do end
def sitemap_node(n, first=false)
if n.children.length < 1 def recurse_sitemap(path, &block)
if !first && File.extname(n.dir).length > 0 bad_ext = path.split('.html')[1]
haml_tag :li do path = path.gsub(bad_ext, '') if bad_ext
path = n.dir.gsub(self.class.views, '') entry = Entry.new(path, [])
haml_concat link_to(File.basename(path), path)
#no "." or ".." dirs
Dir[File.join(path, "*")].each do |e|
next if !File.directory?(e) && !e.include?(".html")
if File.directory?(e)
entry.children << recurse_sitemap(e, &block)
elsif block_given?
how_to_handle = block.call(e)
if how_to_handle == :valid
entry.children << recurse_sitemap(e, &block)
elsif how_to_handle == :utility
bad_ext = e.split('.html')[1]
e = e.gsub(bad_ext, '') if bad_ext
@@utility << e.gsub(Middleman::Base.views + "/", '')
end end
end end
else end
haml_tag(:li, :id => first ? "home" : nil) do
if first entry
haml_concat link_to("Homepage", "/" + self.class.index_file) end
else
# we are a dir Middleman::Base.helpers do
index = n.children.find { |c| c.dir.include?(self.class.index_file) } def sitemap_node(n, first=false)
haml_concat link_to(index.dir.gsub(self.class.views + "/", '').gsub("/" + File.basename(index.dir), '').capitalize, index.dir.gsub(self.class.views, '')) if n.children.length < 1
end if !first && File.extname(n.dir).length > 0
haml_tag :li do
other_children = n.children.select { |c| !c.dir.include?(self.class.index_file) } path = n.dir.gsub(self.class.views, '')
if other_children.length > 0 haml_concat link_to(File.basename(path), path)
if first
other_children.each { |i| sitemap_node(i) }
else
haml_tag :ul do
other_children.each { |i| sitemap_node(i) }
end
end end
end end
end else
haml_tag(:li, :id => first ? "home" : nil) do
if first
haml_concat link_to("Homepage", "/" + self.class.index_file)
else
# we are a dir
index = n.children.find { |c| c.dir.include?(self.class.index_file) }
haml_concat link_to(index.dir.gsub(self.class.views + "/", '').gsub("/" + File.basename(index.dir), '').capitalize, index.dir.gsub(self.class.views, ''))
end
other_children = n.children.select { |c| !c.dir.include?(self.class.index_file) }
if other_children.length > 0
if first
other_children.each { |i| sitemap_node(i) }
else
haml_tag :ul do
other_children.each { |i| sitemap_node(i) }
end
end
end
end
end
end end
end end
end
get '/sitemap.html' do
# Return :utility to put it util top menu. False to ignore
@tree, @utility = build_sitemap do |file_name|
:valid
end
haml :sitemap, :layout => false
end
use_in_file_templates! Middleman::Base.get '/sitemap.html' do
# Return :utility to put it util top menu. False to ignore
@tree, @utility = build_sitemap do |file_name|
:valid
end
haml :sitemap, :layout => false
end
Middleman::Base.use_in_file_templates!
end
end end
Middleman::Features.register :slickmap, Middleman::Features::Slickmap
__END__ __END__
@@ sitemap @@ sitemap
@ -97,7 +96,7 @@ __END__
%title Sitemap %title Sitemap
%style{ :type => "text/css" } %style{ :type => "text/css" }
:sass :sass
@import slickmap.sass @import "slickmap"
+slickmap +slickmap
:javascript :javascript
window.onload = function() { window.onload = function() {

View file

@ -1,9 +1,9 @@
require "middleman/builder" class Middleman::Features::SmushPngs
def initialize(app)
require "middleman/builder"
module Middleman Middleman::Base.alias_method :pre_smush_after_run, :after_run
class Builder Middleman::Base.define_method :after_run do
alias_method :pre_smush_after_run, :after_run
def after_run
pre_smush_after_run pre_smush_after_run
smush_dir = File.join(Middleman::Base.build_dir, Middleman::Base.images_dir) smush_dir = File.join(Middleman::Base.build_dir, Middleman::Base.images_dir)
@ -33,4 +33,6 @@ module Middleman
end end
end end
end end
end end
Middleman::Features.register :smush_pngs, Middleman::Features::SmushPngs

View file

@ -0,0 +1,13 @@
class Middleman::Features::Sprockets
def initialize(app)
require "middleman/features/sprockets/rack"
app.use Middleman::Rack::Sprockets,
:root => Middleman::Base.root,
:load_path => [ File.join("public", Middleman::Base.js_dir),
File.join("views", Middleman::Base.js_dir) ]
end
end
Middleman::Features.register :sprockets, Middleman::Features::Sprockets, { :auto_enable => true }

View file

@ -0,0 +1,64 @@
require 'sprockets'
module Middleman
module Rack
class Sprockets
def initialize(app, options={})
@app = app
@options = options
end
def call(env)
if env["PATH_INFO"].match(/\.js$/)
public_file_path = File.join(Middleman::Base.public, env["PATH_INFO"])
view_file_path = File.join(Middleman::Base.views, env["PATH_INFO"])
source_file = ::Rack::File.new(Middleman::Base.public) if File.exists?(public_file_path)
source_file = ::Rack::File.new(Middleman::Base.views) if File.exists?(view_file_path)
if source_file
status, headers, response = source_file.call(env)
secretary = ::Sprockets::Secretary.new(@options.merge( :source_files => [ response.path ] ))
response = secretary.concatenation.to_s
headers["Content-Length"] = ::Rack::Utils.bytesize(response).to_s
return [status, headers, response]
end
end
@app.call(env)
end
end
end
end
# Sprockets ruby 1.9 duckpunch
module Sprockets
class SourceFile
def source_lines
@lines ||= begin
lines = []
comments = []
File.open(pathname.absolute_location, 'rb') do |file|
file.each do |line|
lines << line = SourceLine.new(self, line, file.lineno)
if line.begins_pdoc_comment? || comments.any?
comments << line
end
if line.ends_multiline_comment?
if line.ends_pdoc_comment?
comments.each { |l| l.comment! }
end
comments.clear
end
end
end
lines
end
end
end
end

View file

@ -0,0 +1,7 @@
class Middleman::Features::UglyHaml
def initialize(app)
Middleman::Base.set :haml, Middleman::Base.settings.haml.merge({ :ugly_haml => true })
end
end
Middleman::Features.register :ugly_haml, Middleman::Features::UglyHaml

View file

@ -1,25 +0,0 @@
begin
require "yui/compressor"
rescue LoadError
puts "YUI-Compressor not available. Install it with: gem install yui-compressor"
end
class Middleman::Rack::MinifyCSS
def initialize(app, options={})
@app = app
end
def call(env)
status, headers, response = @app.call(env)
if Middleman::Base.enabled?(:minify_css) && env["PATH_INFO"].match(/\.css$/)
compressor = ::YUI::CssCompressor.new
uncompressed_source = response.is_a?(::Rack::File) ? File.read(response.path) : response
response = compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(response).to_s
end
[status, headers, response]
end
end

View file

@ -1,25 +0,0 @@
begin
require "yui/compressor"
rescue LoadError
puts "YUI-Compressor not available. Install it with: gem install yui-compressor"
end
class Middleman::Rack::MinifyJavascript
def initialize(app, options={})
@app = app
end
def call(env)
status, headers, response = @app.call(env)
if env["PATH_INFO"].match(/\.js$/)
compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
uncompressed_source = response.is_a?(::Rack::File) ? File.read(response.path) : response
response = compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(response).to_s
end
[status, headers, response]
end
end

View file

@ -1,61 +0,0 @@
require 'sprockets'
class Middleman::Rack::Sprockets
def initialize(app, options={})
@app = app
@options = options
end
def call(env)
if env["PATH_INFO"].match(/\.js$/)
public_file_path = File.join(Middleman::Base.public, env["PATH_INFO"])
view_file_path = File.join(Middleman::Base.views, env["PATH_INFO"])
source_file = Rack::File.new(Middleman::Base.public) if File.exists?(public_file_path)
source_file = Rack::File.new(Middleman::Base.views) if File.exists?(view_file_path)
if source_file
status, headers, response = source_file.call(env)
secretary = ::Sprockets::Secretary.new(@options.merge( :source_files => [ response.path ] ))
response = secretary.concatenation.to_s
headers["Content-Length"] = ::Rack::Utils.bytesize(response).to_s
return [status, headers, response]
end
end
@app.call(env)
end
end
Middleman::Base.supported_formats << "js"
# Sprockets ruby 1.9 duckpunch
module Sprockets
class SourceFile
def source_lines
@lines ||= begin
lines = []
comments = []
File.open(pathname.absolute_location, 'rb') do |file|
file.each do |line|
lines << line = SourceLine.new(self, line, file.lineno)
if line.begins_pdoc_comment? || comments.any?
comments << line
end
if line.ends_multiline_comment?
if line.ends_pdoc_comment?
comments.each { |l| l.comment! }
end
comments.clear
end
end
end
lines
end
end
end
end

View file

@ -0,0 +1,26 @@
module Middleman
module Renderers
@@render_method_for_template_types = {}
def self.register(method_name, template_type)
@@render_method_for_template_types[template_type.to_s] = method_name
end
def self.get_method(template_path)
template_type = Tilt[template_path].to_s
@@render_method_for_template_types[template_type]
end
end
end
# Types built into Sinatra
Middleman::Renderers.register(:less, Tilt::LessTemplate)
Middleman::Renderers.register(:haml, Tilt::HamlTemplate)
Middleman::Renderers.register(:builder, Tilt::BuilderTemplate)
Middleman::Renderers.register(:erb, Tilt::ERBTemplate)
%w(haml
sass
coffee).each { |renderer| require "middleman/renderers/#{renderer}" }

View file

@ -1,23 +0,0 @@
require "builder"
module Middleman
module Renderers
module Builder
def self.included(base)
base.supported_formats << "builder"
end
def render_path(path, layout)
if template_exists?(path, :builder)
builder(path.to_sym, :layout => layout)
else
super
end
end
end
end
end
class Middleman::Base
include Middleman::Renderers::Builder
end

View file

@ -0,0 +1,28 @@
class Middleman::Base
def coffee(template, options={}, locals={})
options[:layout] = false
render :coffee, template, options, locals
end
end
unless defined? Tilt::CoffeeTemplate
# CoffeeScript info:
# http://jashkenas.github.com/coffee-script/
class Tilt::CoffeeTemplate < Tilt::Template
def initialize_engine
return if defined? ::CoffeeScript
require_template_library 'coffee-script'
end
def prepare
@output = nil
end
def evaluate(scope, locals, &block)
@output ||= ::CoffeeScript::compile(data, options)
end
end
Tilt.register 'coffee', Tilt::CoffeeTemplate
end
Middleman::Renderers.register(:coffee, Tilt::CoffeeTemplate)

View file

@ -1,24 +0,0 @@
require "erb"
module Middleman
module Renderers
module ERb
def self.included(base)
base.supported_formats << "erb"
end
def render_path(path, layout)
if template_exists?(path, :erb)
layout = false if File.extname(path) == ".xml"
erb(path.to_sym, :layout => layout)
else
super
end
end
end
end
end
class Middleman::Base
include Middleman::Renderers::ERb
end

View file

@ -1,30 +1,7 @@
require "haml" require "haml"
module Middleman module Middleman
module Haml module Haml
module Renderer
def self.included(base)
base.supported_formats << "haml"
base.helpers Middleman::Haml::Helpers
end
def render_path(path, layout)
if template_exists?(path, :haml)
result = nil
begin
layout = false if File.extname(path) == ".xml"
result = haml(path.to_sym, :layout => layout, :ugly => Middleman::Base.enabled?(:ugly_haml))
rescue ::Haml::Error => e
result = "Haml Error: #{e}"
result << "<pre>Backtrace: #{e.backtrace.join("\n")}</pre>"
end
result
else
super
end
end
end
module Helpers module Helpers
def haml_partial(name, options = {}) def haml_partial(name, options = {})
item_name = name.to_sym item_name = name.to_sym
@ -66,5 +43,5 @@ module Middleman
end end
class Middleman::Base class Middleman::Base
include Middleman::Haml::Renderer helpers Middleman::Haml::Helpers
end end

View file

@ -1,23 +0,0 @@
require "less"
module Middleman
module Renderers
module Less
def self.included(base)
base.supported_formats << "less"
end
def render_path(path, layout)
if template_exists?(path, :less)
less(path.to_sym)
else
super
end
end
end
end
end
class Middleman::Base
include Middleman::Renderers::Less
end

View file

@ -1,93 +1,18 @@
require "sass" require "sass"
require "compass" require "compass"
require "fancy-buttons"
begin
require "yui/compressor"
rescue LoadError
puts "YUI-Compressor not available. Install it with: gem install yui-compressor"
end
module Middleman::Sass
def self.included(base)
base.supported_formats << "sass"
end
def render_path(path, layout)
if template_exists?(path, :sass)
begin
static_version = settings.public + request.path_info
send_file(static_version) if File.exists? static_version
location_of_sass_file = settings.environment == "build" ?
File.join(Dir.pwd, settings.build_dir) :
settings.public
css_filename = File.join(location_of_sass_file, request.path_info)
result = sass(path.to_sym, ::Compass.sass_engine_options.merge({ :css_filename => css_filename }))
if enabled?(:minify_css)
::YUI::CssCompressor.new.compress(result)
else
result
end
rescue Exception => e
sass_exception_string(e)
end
else
super
end
end
# Handle Sass errors
def sass_exception_string(e)
e_string = "#{e.class}: #{e.message}"
if e.is_a? ::Sass::SyntaxError
e_string << "\non line #{e.sass_line}"
if e.sass_filename
e_string << " of #{e.sass_filename}"
if File.exists?(e.sass_filename)
e_string << "\n\n"
min = [e.sass_line - 5, 0].max
begin
File.read(e.sass_filename).rstrip.split("\n")[
min .. e.sass_line + 5
].each_with_index do |line, i|
e_string << "#{min + i + 1}: #{line}\n"
end
rescue
e_string << "Couldn't read sass file: #{e.sass_filename}"
end
end
end
end
<<END
/*
#{e_string}
Backtrace:\n#{e.backtrace.join("\n")}
*/
body:before {
white-space: pre;
font-family: monospace;
content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
END
end
end
class Middleman::Base class Middleman::Base
include Middleman::Sass def scss(template, options={}, locals={})
options[:layout] = false
render :scss, template, options, locals
end
after_feature_init do after_feature_init do
::Compass.configuration do |config| ::Compass.configuration do |config|
config.cache_path = File.join(self.root, ".sassc") # For sassc files config.cache_path = File.join(self.root, ".sass-cache") # For sassc files
config.project_path = self.root config.project_path = self.root
config.sass_dir = File.join(File.basename(self.views), self.css_dir) config.sass_dir = File.join(File.basename(self.views), self.css_dir)
config.output_style = self.enabled?(:minify_css) ? :compressed : :nested config.output_style = :nested
config.fonts_dir = File.join(File.basename(self.public), self.fonts_dir) config.fonts_dir = File.join(File.basename(self.public), self.fonts_dir)
config.css_dir = File.join(File.basename(self.public), self.css_dir) config.css_dir = File.join(File.basename(self.public), self.css_dir)
config.images_dir = File.join(File.basename(self.public), self.images_dir) config.images_dir = File.join(File.basename(self.public), self.images_dir)
@ -98,17 +23,45 @@ class Middleman::Base
config.add_import_path(config.sass_dir) config.add_import_path(config.sass_dir)
end end
::Compass.configure_sass_plugin!
Sass::Plugin.options.update(:line_comments => true, :debug_info => true)
configure :build do configure :build do
::Compass.configuration do |config| ::Compass.configuration do |config|
config.css_dir = File.join(File.basename(self.build_dir), self.css_dir) config.css_dir = File.join(File.basename(self.build_dir), self.css_dir)
config.images_dir = File.join(File.basename(self.build_dir), self.images_dir) config.images_dir = File.join(File.basename(self.build_dir), self.images_dir)
end end
end
::Compass.configure_sass_plugin! end
Sass::Plugin.options.update(:line_comments => false, :debug_info => false) end
class Tilt::SassPlusCSSFilenameTemplate < Tilt::SassTemplate
def sass_options
location_of_sass_file = Middleman::Base.environment == "build" ?
File.join(Middleman::Base.root, Middleman::Base.build_dir) :
Middleman::Base.public
parts = basename.split('.')
parts.pop
css_filename = File.join(location_of_sass_file, Middleman::Base.css_dir, parts.join("."))
super.merge(::Compass.configuration.to_sass_engine_options).merge(:css_filename => css_filename)
end
end
Tilt.register 'sass', Tilt::SassPlusCSSFilenameTemplate
Middleman::Renderers.register(:sass, Tilt::SassPlusCSSFilenameTemplate)
class Tilt::ScssPlusCSSFilenameTemplate < Tilt::SassPlusCSSFilenameTemplate
def sass_options
super.merge(:syntax => :scss)
end
end
Tilt.register 'scss', Tilt::ScssPlusCSSFilenameTemplate
Middleman::Renderers.register(:scss, Tilt::ScssPlusCSSFilenameTemplate)
module Middleman::Haml
module Sass
include ::Haml::Filters::Base
def render(text)
::Sass::Engine.new(text, ::Compass.configuration.to_sass_engine_options).render
end end
end end
end end

View file

@ -1,6 +1,14 @@
<% if css_dir %>set :css_dir, "<%= css_dir -%>"<% end %> # Automatic sitemaps
<% if js_dir %>set :js_dir, "<%= js_dir -%>"<% end %> # enable :slickmap
<% if images_dir %>set :images_dir, "<%= images_dir -%>"<% end %>
# Automatic image dimension calculations
# enable :automatic_image_sizes
# Per-page layout changes
# With no layout
# page "/path/to/file.html", :layout => false
# With alternative layout
# page "/path/to/file.html", :layout => :otherlayout
# Helpers # Helpers
helpers do helpers do
@ -9,11 +17,26 @@ helpers do
end end
end end
# Automatic sitemaps <% if css_dir %>
# enable :slickmap set :css_dir, "<%= css_dir -%>"
<% else %>
# Change the CSS directory
# set :css_dir, "alternative_css_directory"
<% end %>
# Automatic image dimension calculations <% if js_dir %>
# enable :automatic_image_sizes set :js_dir, "<%= js_dir -%>"
<% else %>
# Change the JS directory
# set :js_dir, "alternative_js_directory"
<% end %>
<% if images_dir %>
set :images_dir, "<%= images_dir -%>"
<% else %>
# Change the images directory
# set :images_dir, "alternative_image_directory"
<% end %>
# Build-specific configuration # Build-specific configuration
configure :build do configure :build do

View file

@ -42,6 +42,16 @@ describe "Builder" do
File.read("#{@root_dir}/build/stylesheets/site.css").gsub(/\s/, "").should include("html,body,div,span,applet,object,iframe") File.read("#{@root_dir}/build/stylesheets/site.css").gsub(/\s/, "").should include("html,body,div,span,applet,object,iframe")
end end
it "should build less files" do
File.exists?("#{@root_dir}/build/stylesheets/test_less.css").should be_true
File.read("#{@root_dir}/build/stylesheets/test_less.css").should include("666")
end
it "should build scss files" do
File.exists?("#{@root_dir}/build/stylesheets/site_scss.css").should be_true
File.read("#{@root_dir}/build/stylesheets/site_scss.css").gsub(/\s/, "").should include("html,body,div,span,applet,object,iframe")
end
it "should build static css files" do it "should build static css files" do
File.exists?("#{@root_dir}/build/stylesheets/static.css").should be_true File.exists?("#{@root_dir}/build/stylesheets/static.css").should be_true
end end

View file

@ -0,0 +1 @@
@import "compass/reset";

View file

@ -0,0 +1,5 @@
@brand_color: #666666;
#header {
color: @brand_color;
}