rewrite and decouple

This commit is contained in:
tdreyno 2009-09-28 19:43:08 -07:00
parent 96e095215f
commit 4f11920bba
24 changed files with 522 additions and 327 deletions

View file

@ -13,15 +13,10 @@ begin
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
gem.executables = %w(mm-init mm-build mm-server)
gem.add_dependency("templater")
gem.add_dependency("yui-compressor")
gem.add_dependency("sprockets")
gem.add_dependency("sinatra")
gem.add_dependency("foca-sinatra-content-for")
gem.add_dependency("brynary-rack-test")
gem.add_dependency("markaby")
gem.add_dependency("sbfaulkner-sinatra-markaby")
gem.add_dependency("maruku")
gem.add_dependency("wbzyl-sinatra-maruku")
gem.add_dependency("haml", ">=2.1.0")
gem.add_dependency("chriseppstein-compass")
end
@ -30,7 +25,7 @@ begin
rubyforge.doc_task = "rdoc"
end
rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
end
require 'spec/rake/spectask'

View file

@ -1,12 +1,11 @@
#!/usr/bin/env ruby
# Require app
require 'templater'
MIDDLEMAN_BUILDER = true
require 'middleman'
require 'rack-test'
# Require app
require 'middleman/builder'
module Generators
extend Templater::Manifold
desc "Build a staticmatic site"
@ -69,50 +68,11 @@ module Generators
add :build, Builder
end
class BuildConfig
def self.render(source, destination)
renderer.render(source, destination)
end
def self.renderer
@@renderer ||= BuildRenderer
end
def self.renderer=(val)
@@renderer = val
end
end
# Monkey-patch to use a dynamic renderer
class Templater::Actions::Template
def render
BuildConfig.render(source, destination)
::Middleman::Builder.render(source, destination)
end
end
# Default render through middleman
class BuildRenderer
def self.render(source, destination)
request_path = destination.gsub(File.join(Dir.pwd, 'build'), "")
browser = Rack::Test::Session.new(Rack::MockSession.new(Middleman))
browser.get(request_path)
browser.last_response.body
end
end
class SprocketsRenderer < BuildRenderer
def self.render(source, destination)
if File.extname(source) == '.js'
secretary = Sprockets::Secretary.new( :asset_root => "public",
:load_path => ["public/assets/javascripts/**/*.js"],
:source_files => [source] )
compressor = YUI::JavaScriptCompressor.new(:munge => true)
compressor.compress(secretary.concatenation.to_s)
else
super
end
end
end
BuildConfig.renderer = SprocketsRenderer
Generators.run_cli(Dir.pwd, 'mm-build', 1, %w(build --force).concat(ARGV))

View file

@ -9,6 +9,9 @@ module Generators
desc "Creates a new staticmatic scaffold."
first_argument :location, :required => true, :desc => "Project location"
# css_dir
# images_dir
def destination_root
File.expand_path(location)
end

View file

@ -4,5 +4,6 @@
require File.join(File.dirname(__FILE__), '..', 'lib', 'middleman')
# Start Middleman
Middleman.set :server, %w[thin webrick]
Middleman.run!(:root => Dir.pwd)
Middleman::Base.set({ :server => %w[thin webrick],
:root => Dir.pwd })
Middleman::Base.init!

View file

@ -1,10 +1,7 @@
git://github.com/jnicklas/templater.git
git://github.com/sstephenson/ruby-yui-compressor.git
git://github.com/sstephenson/sprockets.git
git://github.com/sinatra/sinatra.git
git://github.com/nex3/haml.git
git://github.com/chriseppstein/compass.git
git://github.com/foca/sinatra-content-for.git
git://github.com/brynary/rack-test.git
git://github.com/sbfaulkner/sinatra-markaby.git
git://github.com/wbzyl/sinatra-maruku.git

View file

@ -1,136 +1,4 @@
require 'haml'
require 'compass' #must be loaded before sinatra
require 'sinatra/base'
libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
require 'sprockets'
# Sprockets ruby 1.9 hack
require 'middleman/sprockets+ruby19'
require "yui/compressor"
# Include content_for support
require 'sinatra-content-for'
class Middleman < Sinatra::Base
set :app_file, __FILE__
set :static, true
set :root, Dir.pwd
set :environment, defined?(MIDDLEMAN_BUILDER) ? :build : :development
set :default_ext, 'html'
set :supported_formats, %w(haml erb builder)
helpers Sinatra::ContentFor
def self.run!(options={}, &block)
set options
handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
puts "== The Middleman is standing watch on port #{port}"
handler.run self, :Host => host, :Port => port do |server|
trap(:INT) do
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
puts "\n== The Middleman has ended his patrol"
end
if block_given?
block.call
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
end
end
rescue Errno::EADDRINUSE => e
puts "== The Middleman is already standing watch on port #{port}!"
end
configure do
Compass.configuration do |config|
config.project_path = Dir.pwd
config.sass_dir = File.join(File.basename(self.views), "stylesheets")
config.output_style = :nested
config.css_dir = File.join(File.basename(self.public), "stylesheets")
config.images_dir = File.join(File.basename(self.public), "images")
config.http_images_path = "/images"
config.http_stylesheets_path = "/stylesheets"
config.add_import_path(config.sass_dir)
end
end
# include helpers
class_eval File.read(File.join(File.dirname(__FILE__), 'middleman', 'helpers.rb'))
# Check for local config
local_config = File.join(self.root, "init.rb")
if File.exists? local_config
puts "== Local config at: #{local_config}"
class_eval File.read(local_config)
end
configure do
Compass.configure_sass_plugin!
end
configure :build do
Compass.configuration do |config|
config.output_style = :compressed
end
module Minified
module Javascript
include ::Haml::Filters::Base
def render_with_options(text, options)
compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
data = compressor.compress(text)
<<END
<script type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}>#{data.chomp}</script>
END
end
end
end
end
# CSS files
get %r{/(.*).css} do |path|
content_type 'text/css', :charset => 'utf-8'
begin
static_version = File.join(Dir.pwd, 'public') + request.path_info
send_file(static_version) if File.exists? static_version
location_of_sass_file = defined?(MIDDLEMAN_BUILDER) ? "build" : "views"
css_filename = File.join(Dir.pwd, location_of_sass_file) + request.path_info
sass(path.to_sym, Compass.sass_engine_options.merge({ :css_filename => css_filename }))
rescue Exception => e
sass_exception_string(e)
end
end
# All other files
get /(.*)/ do |path|
path << "index.#{options.default_ext}" if path.match(%r{/$})
path.gsub!(%r{^/}, '')
path_without_ext = path.gsub(File.extname(path), '')
result = nil
begin
options.supported_formats.detect do |renderer|
if File.exists?(File.join(options.views, "#{path}.#{renderer}"))
result = send(renderer.to_sym, path.to_sym)
elsif File.exists?(File.join(options.views, "#{path_without_ext}.#{renderer}"))
result = send(renderer.to_sym, path_without_ext.to_sym)
else
false
end
end
rescue Haml::Error => e
result = "Haml Error: #{e}"
result << "<pre>Backtrace: #{e.backtrace.join("\n")}</pre>"
end
result || pass
end
get %r{/(.*\.xml)} do |path|
content_type 'text/xml', :charset => 'utf-8'
haml(path.to_sym, :layout => false)
end
end
require 'middleman/base'

118
lib/middleman/base.rb Normal file
View file

@ -0,0 +1,118 @@
require 'rubygems' unless ENV['NO_RUBYGEMS']
require 'haml'
require 'sinatra/base'
require 'middleman/helpers'
module Middleman
class Base < Sinatra::Base
set :app_file, __FILE__
set :root, Dir.pwd
set :environment, :development
set :index_file, 'index.html'
set :css_dir, "stylesheets"
set :images_dir, "images"
enable :compass
enable :content_for
enable :sprockets
#enable :slickmap
disable :cache_buster
disable :minify_css
disable :minify_javascript
disable :relative_assets
disable :markaby
disable :maruku
# include helpers
helpers Middleman::Helpers
configure :build do
enable :minify_css
enable :minify_javascript
enable :cache_buster
disable :slickmap
end
def template_exists?(path, renderer=nil)
template_path = path.dup
template_path << ".#{renderer}" if renderer
File.exists? File.join(options.views, template_path)
end
# Base case renderer (do nothing), Should be over-ridden
module StaticRender
def render_path(path)
false
end
end
include StaticRender
# All other files
disable :static
not_found do
path = request.path
path << options.index_file if path.match(%r{/$})
path.gsub!(%r{^/}, '')
if content = render_path(path)
content_type media_type(File.extname(path)), :charset => 'utf-8'
status 200
content
else
# Otherwise, send the static file
path = File.join(options.public, request.path)
if File.exists?(path)
status 200
send_file(path)
else
status 404
end
end
end
def self.run!(options={}, &block)
set options
handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
puts "== The Middleman is standing watch on port #{port}"
handler.run self, :Host => host, :Port => port do |server|
trap(:INT) do
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
puts "\n== The Middleman has ended his patrol"
end
if block_given?
block.call
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
end
end
rescue Errno::EADDRINUSE => e
puts "== The Middleman is already standing watch on port #{port}!"
end
def self.init!
# Check for local config
local_config = File.join(self.root, "init.rb")
if File.exists? local_config
puts "== Local config at: #{local_config}"
class_eval File.read(local_config)
end
require "middleman/features/haml"
# loop over enabled feature
features_path = File.expand_path("features/*.rb", File.dirname(__FILE__))
Dir[features_path].each do |f|
feature_name = File.basename(f, '.rb')
option_name = :"#{feature_name}?"
if respond_to?(option_name) && send(option_name) === true
require "middleman/features/#{feature_name}"
end
end
run!
end
end
end

14
lib/middleman/builder.rb Normal file
View file

@ -0,0 +1,14 @@
require 'middleman'
module Middleman
class Builder
def self.render_file(source, destination)
# Middleman.set :environment, :build
request_path = destination.gsub(File.join(Dir.pwd, 'build'), "")
browser = Rack::Test::Session.new(Rack::MockSession.new(Middleman))
browser.get(request_path)
browser.last_response.body
end
end
end

View file

@ -0,0 +1,9 @@
# def cache_buster
# if File.readable?(real_path)
# File.mtime(real_path).strftime("%s")
# else
# $stderr.puts "WARNING: '#{File.basename(path)}' was not found (or cannot be read) in #{File.dirname(real_path)}"
# end
# end

View file

@ -0,0 +1,26 @@
require 'compass'
class Middleman::Base
configure do
::Compass.configuration do |config|
config.project_path = Dir.pwd
config.sass_dir = File.join(File.basename(self.views), self.css_dir)
config.output_style = minify_css ? :compressed : :nested
config.css_dir = File.join(File.basename(self.public), self.css_dir)
config.images_dir = File.join(File.basename(self.public), self.images_dir)
# File.expand_path(self.images_dir, self.public)
if !cache_buster?
config.asset_cache_buster do
false
end
end
config.http_images_path = "/#{self.images_dir}"
config.http_stylesheets_path = "/#{self.css_dir}"
config.add_import_path(config.sass_dir)
end
::Compass.configure_sass_plugin!
end
end

View file

@ -0,0 +1,9 @@
begin
require 'sinatra/content_for'
class Middleman::Base
helpers Sinatra::ContentFor
end
rescue LoadError
puts "Sinatra::ContentFor not available. Install it with: gem install sinatra-content-for"
end

View file

@ -0,0 +1,2 @@
# Errors to growl
# Build complete to growl

View file

@ -0,0 +1,120 @@
module Middleman
module Haml
def render_path(path)
if template_exists?(path, :haml)
result = nil
begin
result = haml(path.to_sym, :layout => File.extname(path) != ".xml")
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
# def haml_partial(name, options = {})
# item_name = name.to_sym
# counter_name = "#{name}_counter".to_sym
# if collection = options.delete(:collection)
# collection.enum_for(:each_with_index).collect do |item,index|
# haml_partial name, options.merge(:locals => {item_name => item, counter_name => index+1})
# end.join
# elsif object = options.delete(:object)
# haml_partial name, options.merge(:locals => {item_name => object, counter_name => nil})
# else
# haml "_#{name}".to_sym, options.merge(:layout => false)
# end
# end
# end
# module Table
# include ::Haml::Filters::Base
#
# def render(text)
# output = '<div class="table"><table cellspacing="0" cellpadding="0">'
# line_num = 0
# text.each_line do |line|
# line_num += 1
# next if line.strip.empty?
# output << %Q{<tr class="#{(line_num % 2 == 0) ? "even" : "odd" }#{(line_num == 1) ? " first" : "" }">}
#
# columns = line.split("|").map { |p| p.strip }
# columns.each_with_index do |col, i|
# output << %Q{<td class="col#{i+1}">#{col}</td>}
# end
#
# output << "</tr>"
# end
# output + "</table></div>"
# end
# end
module Sass
def render_path(path)
path = path.dup.gsub('.css', '')
if template_exists?(path, :sass)
begin
static_version = options.public + request.path_info
send_file(static_version) if File.exists? static_version
location_of_sass_file = defined?(MIDDLEMAN_BUILDER) ? "build" : "views"
css_filename = File.join(Dir.pwd, location_of_sass_file) + request.path_info
sass(path.to_sym, Compass.sass_engine_options.merge({ :css_filename => css_filename }))
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
end
class Middleman::Base
include Middleman::Haml
include Middleman::Sass
end

View file

@ -0,0 +1,39 @@
begin
require 'markaby'
rescue LoadError
puts "Markaby not available. Install it with: gem install markaby"
end
module Middleman
module Markaby
def render_path(path)
if template_exists?(path, :mab)
markaby path.to_sym
else
super
end
end
def markaby(template=nil, options={}, locals = {}, &block)
options, template = template, nil if template.is_a?(Hash)
template = lambda { block } if template.nil?
render :mab, template, options, locals
end
protected
def render_mab(template, data, options, locals, &block)
filename = options.delete(:filename) || '<MARKABY>'
line = options.delete(:line) || 1
mab = ::Markaby::Builder.new(locals)
if data.respond_to?(:to_str)
eval(data.to_str, binding, filename, line)
elsif data.kind_of?(Proc)
data.call(mab)
end
end
end
class Base
include Middlman::Markaby
end
end

View file

@ -0,0 +1,38 @@
begin
require 'maruku'
rescue LoadError
puts "Maruku not available. Install it with: gem install maruku"
end
module Middleman
module Maruku
def render_path(path)
if template_exists?(path, :maruku)
maruku path.to_sym
else
super
end
end
def maruku(template, options={}, locals={})
render :maruku, template, options, locals
end
private
def render_maruku(data, options, locals, &block)
maruku_src = render_erb(data, options, locals, &block)
instance = ::Maruku.new(maruku_src, options)
if block_given?
# render layout
instance.to_html_document
else
# render template
instance.to_html
end
end
end
class Base
include Middlman::Maruku
end
end

View file

@ -0,0 +1,2 @@
# Otherwise use YUI
# Fine a way to minify inline/css

View file

@ -0,0 +1,31 @@
require "yui/compressor"
module Middleman
module Minified
module Javascript
include ::Haml::Filters::Base
def render_with_options(text, options)
compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
data = compressor.compress(text)
<<END
<script type=#{options[:attr_wrapper]}text/javascript#{options[:attr_wrapper]}>#{data.chomp}</script>
END
end
end
end
module Compressor
def render_path(path)
if template_exists?(path, :js)
compressor = YUI::JavaScriptCompressor.new(:munge => true)
compressor.compress(super)
else
super
end
end
end
class Base
include Middleman::Compressor
end
end

View file

@ -0,0 +1,20 @@
# config.relative_assets = true
def asset_url(path)
if path.include?("://")
path
else
request_path = request.path_info.dup
request_path << "index.html" if path.match(%r{/$})
request_path.gsub!(%r{^/}, '')
parts = request_path.split('/')
if parts.length > 1
"../" * (parts.length - 1) + path
else
path
end
end
end
end

View file

@ -0,0 +1,5 @@
require 'slickmap'
get '/sitemap.html' do
haml :sitemap, :layout => false
end

View file

@ -0,0 +1,25 @@
begin
require 'sprockets'
require 'middleman/features/sprockets+ruby19' # Sprockets ruby 1.9 duckpunch
rescue LoadError
puts "Sprockets not available. Install it with: gem install sprockets"
end
module Middleman
module Sprockets
def render_path(path)
source = File.join(options.public, path)
if File.extname(path) == '.js' && File.exists?(source)
secretary = ::Sprockets::Secretary.new( :asset_root => options.public,
:source_files => [source] )
secretary.concatenation.to_s
else
super
end
end
end
class Base
include Middleman::Sprockets
end
end

View file

@ -1,139 +1,60 @@
module Table
include Haml::Filters::Base
module Middleman
module Helpers
def find_and_include_related_css_file
path = request.path_info.dup
path << "index.html" if path.match(%r{/$})
path.gsub!(%r{^/}, '')
path.gsub!(File.extname(path), '')
path.gsub!('/', '-')
def render(text)
output = '<div class="table"><table cellspacing="0" cellpadding="0">'
line_num = 0
text.each_line do |line|
line_num += 1
next if line.strip.empty?
output << %Q{<tr class="#{(line_num % 2 == 0) ? "even" : "odd" }#{(line_num == 1) ? " first" : "" }">}
columns = line.split("|").map { |p| p.strip }
columns.each_with_index do |col, i|
output << %Q{<td class="col#{i+1}">#{col}</td>}
end
output << "</tr>"
end
output + "</table></div>"
end
end
def find_and_include_related_sass_file
path = request.path_info.dup
path << "index.html" if path.match(%r{/$})
path.gsub!(%r{^/}, '')
path.gsub!(File.extname(path), '')
path.gsub!('/', '-')
sass_file = File.join(File.basename(self.class.views), "stylesheets", "#{path}.sass")
if File.exists? sass_file
stylesheet_link_tag "stylesheets/#{path}.css"
end
end
def link_to(title, url="#", params={})
params.merge!(:href => url)
params = params.map { |k,v| %Q{#{k}="#{v}"}}.join(' ')
%Q{<a #{params}>#{title}</a>}
end
def page_classes(*additional)
path = request.path_info
path << "index.html" if path.match(%r{/$})
path.gsub!(%r{^/}, '')
classes = []
parts = path.split('.')[0].split('/')
parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') }
classes << "index" if classes.empty?
classes += additional unless additional.empty?
classes.join(' ')
end
def haml_partial(name, options = {})
item_name = name.to_sym
counter_name = "#{name}_counter".to_sym
if collection = options.delete(:collection)
collection.enum_for(:each_with_index).collect do |item,index|
haml_partial name, options.merge(:locals => {item_name => item, counter_name => index+1})
end.join
elsif object = options.delete(:object)
haml_partial name, options.merge(:locals => {item_name => object, counter_name => nil})
else
haml "_#{name}".to_sym, options.merge(:layout => false)
end
end
# def cache_buster
# if File.readable?(real_path)
# File.mtime(real_path).strftime("%s")
# else
# $stderr.puts "WARNING: '#{File.basename(path)}' was not found (or cannot be read) in #{File.dirname(real_path)}"
# end
# end
def asset_url(path)
path.include?("://") ? path : "/#{path}"
end
def image_tag(path, options={})
options[:alt] ||= ""
capture_haml do
haml_tag :img, options.merge(:src => asset_url(path))
end
end
def javascript_include_tag(path, options={})
capture_haml do
haml_tag :script, options.merge(:src => asset_url(path), :type => "text/javascript")
end
end
def stylesheet_link_tag(path, options={})
options[:rel] ||= "stylesheet"
capture_haml do
haml_tag :link, options.merge(:href => asset_url(path), :type => "text/css")
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
sass_file = File.join(File.basename(self.class.views), "stylesheets", "#{path}.sass")
if File.exists? sass_file
stylesheet_link_tag "stylesheets/#{path}.css"
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
def page_classes(*additional)
path = request.path_info
path << "index.html" if path.match(%r{/$})
path.gsub!(%r{^/}, '')
classes = []
parts = path.split('.')[0].split('/')
parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') }
classes << "index" if classes.empty?
classes += additional unless additional.empty?
classes.join(' ')
end
def link_to(title, url="#", params={})
params.merge!(:href => url)
params = params.map { |k,v| %Q{#{k}="#{v}"}}.join(' ')
%Q{<a #{params}>#{title}</a>}
end
def asset_url(path)
path.include?("://") ? path : "/#{path}"
end
def image_tag(path, options={})
options[:alt] ||= ""
params = options.merge(:src => asset_url(path))
params = params.map { |k,v| %Q{#{k}="#{v}"}}.join(' ')
"<img #{params} />"
end
def javascript_include_tag(path, options={})
params = options.merge(:src => asset_url(path), :type => "text/javascript")
params = params.map { |k,v| %Q{#{k}="#{v}"}}.join(' ')
"<script #{params}></script>"
end
def stylesheet_link_tag(path, options={})
options[:rel] ||= "stylesheet"
params = options.merge(:href => asset_url(path), :type => "text/css")
params = params.map { |k,v| %Q{#{k}="#{v}"}}.join(' ')
"<link #{params} />"
end
end
end

View file

@ -1,4 +0,0 @@
# Include markaby support
require 'sinatra-markaby'
Middleman.helpers Sinatra::Markaby
Middleman.supported_formats << "mab"

View file

@ -1,4 +0,0 @@
# Include maruku support
require 'sinatra-maruku'
Middleman.helpers Sinatra::Maruku
Middleman.supported_formats << "maruku"