From 2130e902c21a23d8802fa255fa229f58e751f39a Mon Sep 17 00:00:00 2001 From: Thomas Reynolds Date: Sat, 14 Apr 2012 13:51:02 -0700 Subject: [PATCH] Begin splitting our requires into correct places instead of autoload Disable autoload on watcher finish moving requires out --- middleman-core/bin/middleman | 2 + middleman-core/lib/middleman-core.rb | 249 +--- middleman-core/lib/middleman-core/base.rb | 1011 +++++++++-------- middleman-core/lib/middleman-core/cli.rb | 6 + middleman-core/lib/middleman-core/cli/init.rb | 2 + .../lib/middleman-core/cli/server.rb | 2 + .../core_extensions/file_watcher.rb | 1 + .../core_extensions/rendering.rb | 2 + .../lib/middleman-core/extensions.rb | 179 +++ .../lib/middleman-core/renderers/erb.rb | 51 +- 10 files changed, 757 insertions(+), 748 deletions(-) create mode 100644 middleman-core/lib/middleman-core/extensions.rb diff --git a/middleman-core/bin/middleman b/middleman-core/bin/middleman index 51e12112..aca9cd6f 100755 --- a/middleman-core/bin/middleman +++ b/middleman-core/bin/middleman @@ -49,6 +49,8 @@ end # Automatically discover extensions in RubyGems Middleman.load_extensions_in_path +require "middleman-core/cli" + # Change directory to the root Dir.chdir(ENV["MM_ROOT"] || Dir.pwd) do diff --git a/middleman-core/lib/middleman-core.rb b/middleman-core/lib/middleman-core.rb index 8e109b2b..0e6f0e48 100755 --- a/middleman-core/lib/middleman-core.rb +++ b/middleman-core/lib/middleman-core.rb @@ -5,251 +5,10 @@ require "active_support" libdir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) -# Simple callback library -require "middleman-core/vendor/hooks-0.2.0/lib/hooks" +# Top-level Middleman namespace +module Middleman; end require "middleman-core/version" require "middleman-core/util" - -# Top-level Middleman object -module Middleman - # Auto-load modules on-demand - autoload :Base, "middleman-core/base" - autoload :Cache, "middleman-core/cache" - autoload :Templates, "middleman-core/templates" - autoload :Watcher, "middleman-core/watcher" - - module Cli - autoload :Base, "middleman-core/cli" - autoload :Build, "middleman-core/cli/build" - autoload :Init, "middleman-core/cli/init" - autoload :Server, "middleman-core/cli/server" - end - - # Custom Renderers - module Renderers - autoload :ERb, "middleman-core/renderers/erb" - end - - autoload :Sitemap, "middleman-core/sitemap" - - module CoreExtensions - # File Change Notifier - autoload :FileWatcher, "middleman-core/core_extensions/file_watcher" - - # In-memory Sitemap - autoload :Sitemap, "middleman-core/core_extensions/sitemap" - - # Add Builder callbacks - autoload :Builder, "middleman-core/core_extensions/builder" - - # Custom Feature API - autoload :Extensions, "middleman-core/core_extensions/extensions" - - # Asset Path Pipeline - autoload :Assets, "middleman-core/core_extensions/assets" - - # Data looks at the data/ folder for YAML files and makes them available - # to dynamic requests. - autoload :Data, "middleman-core/core_extensions/data" - - # Parse YAML from templates - autoload :FrontMatter, "middleman-core/core_extensions/front_matter" - - # External helpers looks in the helpers/ folder for helper modules - autoload :ExternalHelpers, "middleman-core/core_extensions/external_helpers" - - # DefaultHelpers are the built-in dynamic template helpers. - autoload :DefaultHelpers, "middleman-core/core_extensions/default_helpers" - - # Extended version of Padrino's rendering - autoload :Rendering, "middleman-core/core_extensions/rendering" - - # Pass custom options to views - autoload :Routing, "middleman-core/core_extensions/routing" - - # Catch and show exceptions at the Rack level - autoload :ShowExceptions, "middleman-core/core_extensions/show_exceptions" - - # i18n - autoload :I18n, "middleman-core/core_extensions/i18n" - end - - module Extensions - # Provide Apache-style index.html files for directories - autoload :DirectoryIndexes, "middleman-core/extensions/directory_indexes" - - # Lorem provides a handful of helpful prototyping methods to generate - # words, paragraphs, fake images, names and email addresses. - autoload :Lorem, "middleman-core/extensions/lorem" - - # AutomaticImageSizes inspects the images used in your dynamic templates - # and automatically adds width and height attributes to their HTML - # elements. - autoload :AutomaticImageSizes, "middleman-core/extensions/automatic_image_sizes" - - # AssetHost allows you to setup multiple domains to host your static - # assets. Calls to asset paths in dynamic templates will then rotate - # through each of the asset servers to better spread the load. - autoload :AssetHost, "middleman-core/extensions/asset_host" - end - - # Backwards compatibility namespace - module Features - end - - module Extensions - class << self - def registered - @_registered ||= {} - end - - # Register a new extension. Choose a name which will be - # used to activate the extension in config.rb, like this: - # - # activate :my_extension - # - # Provide your extension module either as the namespace - # parameter, or return it from the block: - # - # @param [Symbol] name The name of the extension - # @param [Module] namespace The extension module - # @param [String] version A RubyGems-style version string stating - # the versions of middleman this extension - # is compatible with. - # @yield Instead of passing a module in namespace, you can provide - # a block which returns your extension module. This gives - # you the ability to require other files only when the - # extension is activated. - def register(name, namespace=nil, version=nil, &block) - # If we've already got a matching extension that passed the - # version check, bail out. - return if registered.has_key?(name.to_sym) && - !registered[name.to_sym].is_a?(String) - - if block_given? - version = namespace - end - - passed_version_check = true - if !version.nil? - requirement = ::Gem::Requirement.create(version) - if !requirement.satisfied_by?(Middleman::GEM_VERSION) - passed_version_check = false - end - end - - registered[name.to_sym] = if !passed_version_check - "== #{name} failed version check. Requested #{version}, got #{Middleman::VERSION}" - elsif block_given? - block - elsif namespace - namespace - end - end - - def load(name) - name = name.to_sym - return nil unless registered.has_key?(name) - - extension = registered[name] - if extension.is_a?(Proc) - extension = extension.call() || nil - registered[name] = extension - end - - extension - end - end - end - - # Where to look in gems for extensions to auto-register - EXTENSION_FILE = File.join("lib", "middleman_extension.rb") unless const_defined?(:EXTENSION_FILE) - - class << self - # Automatically load extensions from available RubyGems - # which contain the EXTENSION_FILE - # - # @private - def load_extensions_in_path - if defined?(Bundler) - Bundler.require - else - extensions = rubygems_latest_specs.select do |spec| - spec_has_file?(spec, EXTENSION_FILE) - end - - extensions.each do |spec| - require spec.name - end - end - end - - # Backwards compatible means of finding all the latest gemspecs - # available on the system - # - # @private - # @return [Array] Array of latest Gem::Specification - def rubygems_latest_specs - # If newer Rubygems - if ::Gem::Specification.respond_to? :latest_specs - ::Gem::Specification.latest_specs - else - ::Gem.source_index.latest_specs - end - end - - # Where a given Gem::Specification has a specific file. Used - # to discover extensions and Sprockets-supporting gems. - # - # @private - # @param [Gem::Specification] - # @param [String] Path to look for - # @return [Boolean] Whether the file exists - def spec_has_file?(spec, path) - full_path = File.join(spec.full_gem_path, path) - File.exists?(full_path) - end - - # Create a new Class which is based on Middleman::Base - # Used to create a safe sandbox into which extensions and - # configuration can be included later without impacting - # other classes and instances. - # - # @return [Class] - def server(&block) - @@servercounter ||= 1 - @@servercounter += 1 - const_set("MiddlemanBase#{@@servercounter}", Class.new(Middleman::Base)) - end - - # Creates a new Rack::Server - # - # @param [Hash] options to pass to Rack::Server.new - # @return [Rack::Server] - def start_server(options={}) - require "webrick" - - opts = { - :Port => options[:port] || 4567, - :Host => options[:host] || "0.0.0.0", - :AccessLog => [] - } - - # opts[:Logger] = WEBrick::Log::new("/dev/null", 7) if !options[:logging] - - app_class = options[:app] ||= ::Middleman.server.inst - opts[:app] = app_class - - # Disable for Beta 1. See if people notice. - require "thin" - ::Thin::Logging.silent = !options[:logging] - opts[:server] = 'thin' - # opts[:server] = 'webrick' - - server = ::Rack::Server.new(opts) - server.start - server - end - end -end \ No newline at end of file +require "middleman-core/extensions" +require "middleman-core/base" \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/base.rb b/middleman-core/lib/middleman-core/base.rb index 9a945d12..767d4fa7 100644 --- a/middleman-core/lib/middleman-core/base.rb +++ b/middleman-core/lib/middleman-core/base.rb @@ -7,502 +7,555 @@ require "tilt" # Use ActiveSupport JSON require "active_support/json" + +# Simple callback library +require "middleman-core/vendor/hooks-0.2.0/lib/hooks" + +# Using a cache +require "middleman-core/cache" + +require "middleman-core/sitemap" # Core Middleman Class -class Middleman::Base - # Uses callbacks - include Hooks +module Middleman + class Base + # Uses callbacks + include Hooks - # Before request hook - define_hook :before + # Before request hook + define_hook :before - # Ready (all loading and parsing of extensions complete) hook - define_hook :ready + # Ready (all loading and parsing of extensions complete) hook + define_hook :ready - class << self + class << self - # Reset Rack setup - # - # @private - def reset! - @app = nil - @prototype = nil - end - - # The shared Rack instance being build - # - # @private - # @return [Rack::Builder] - def app - @app ||= Rack::Builder.new - end - - # Get the static instance - # - # @private - # @return [Middleman::Base] - def inst(&block) - @inst ||= begin - mm = new(&block) - mm.run_hook :ready - mm + # Reset Rack setup + # + # @private + def reset! + @app = nil + @prototype = nil end - end - # Set the shared instance - # - # @private - # @param [Middleman::Base] inst - # @return [void] - def inst=(inst) - @inst = inst - end - - # Return built Rack app - # - # @private - # @return [Rack::Builder] - def to_rack_app(&block) - inner_app = inst(&block) - - (@middleware || []).each do |m| - app.use(m[0], *m[1], &m[2]) + # The shared Rack instance being build + # + # @private + # @return [Rack::Builder] + def app + @app ||= Rack::Builder.new end - - app.map("/") { run inner_app } - - (@mappings || []).each do |m| - app.map(m[0], &m[1]) - end - - app - end - # Prototype app. Used in config.ru - # - # @private - # @return [Rack::Builder] - def prototype - @prototype ||= to_rack_app - end - - # Call prototype, use in config.ru - # - # @private - def call(env) - prototype.call(env) - end - - # Use Rack middleware - # - # @param [Class] Middleware - # @return [void] - def use(middleware, *args, &block) - @middleware ||= [] - @middleware << [middleware, args, block] - end - - # Add Rack App mapped to specific path - # - # @param [String] Path to map - # @return [void] - def map(map, &block) - @mappings ||= [] - @mappings << [map, block] - end - - # Mix-in helper methods. Accepts either a list of Modules - # and/or a block to be evaluated - # @return [void] - def helpers(*extensions, &block) - class_eval(&block) if block_given? - include(*extensions) if extensions.any? - end - - # Access class-wide defaults - # - # @private - # @return [Hash] Hash of default values - def defaults - @defaults ||= {} - end - - # Set class-wide defaults - # - # @param [Symbol] Unique key name - # @param Default value - # @return [void] - def set(key, value=nil, &block) - @defaults ||= {} - @defaults[key] = value - - @inst.set(key, value, &block) if @inst - end - end - - # Set attributes (global variables) - # - # @param [Symbol] Name of the attribue - # @param Attribute value - # @return [void] - def set(key, value=nil, &block) - setter = "#{key}=".to_sym - self.class.send(:attr_accessor, key) if !respond_to?(setter) - value = block if block_given? - send(setter, value) - end - - # Root project directory (overwritten in middleman build/server) - # @return [String] - set :root, ENV["MM_ROOT"] || Dir.pwd - - # Name of the source directory - # @return [String] - set :source, "source" - - # Middleman environment. Defaults to :development, set to :build by the build process - # @return [String] - set :environment, (ENV['MM_ENV'] && ENV['MM_ENV'].to_sym) || :development - - # Whether logging is active, disabled by default - # @return [String] - set :logging, false - - # Which file should be used for directory indexes - # @return [String] - set :index_file, "index.html" - - # Location of javascripts within source. Used by Sprockets. - # @return [String] - set :js_dir, "javascripts" - - # Location of stylesheets within source. Used by Compass. - # @return [String] - set :css_dir, "stylesheets" - - # Location of images within source. Used by HTML helpers and Compass. - # @return [String] - set :images_dir, "images" - - # Where to build output files - # @return [String] - set :build_dir, "build" - - # Default prefix for building paths. Used by HTML helpers and Compass. - # @return [String] - set :http_prefix, "/" - - # Whether to catch and display exceptions - # @return [Boolean] - set :show_exceptions, true - - # Automatically loaded extensions - # @return [Array] - set :default_extensions, [ :lorem ] - - # Default layout name - # @return [String, Symbold] - set :layout, :_auto_layout - - # Activate custom features and extensions - include Middleman::CoreExtensions::Extensions - - # Handle exceptions - register Middleman::CoreExtensions::ShowExceptions - - # Add Builder Callbacks - register Middleman::CoreExtensions::Builder - - # Add Watcher Callbacks - register Middleman::CoreExtensions::FileWatcher - - # Activate Data package - register Middleman::CoreExtensions::Data - - # Setup custom rendering - register Middleman::CoreExtensions::Rendering - - # Sitemap - register Middleman::Sitemap - - # Setup external helpers - register Middleman::CoreExtensions::ExternalHelpers - - # Setup default helpers - register Middleman::CoreExtensions::DefaultHelpers - - # Setup asset path pipeline - register Middleman::CoreExtensions::Assets - - # with_layout and page routing - register Middleman::CoreExtensions::Routing - - # Parse YAML from templates - register Middleman::CoreExtensions::FrontMatter - - # i18n - register Middleman::CoreExtensions::I18n - - # Built-in Extensions - Middleman::Extensions.register(:directory_indexes) { - Middleman::Extensions::DirectoryIndexes } - Middleman::Extensions.register(:lorem) { - Middleman::Extensions::Lorem } - Middleman::Extensions.register(:automatic_image_sizes) { - Middleman::Extensions::AutomaticImageSizes } - Middleman::Extensions.register(:asset_host) { - Middleman::Extensions::AssetHost } - - # Backwards-compatibility with old request.path signature - attr :request - - # Accessor for current path - # @return [String] - def current_path - @_current_path - end - - # Set the current path - # - # @param [String] path The new current path - # @return [void] - def current_path=(path) - @_current_path = path - @request = ::Thor::CoreExt::HashWithIndifferentAccess.new({ - :path => path, - :params => req ? ::Thor::CoreExt::HashWithIndifferentAccess.new(req.params) : {} - }) - end - - # Initialize the Middleman project - def initialize(&block) - # Current path defaults to nil, used in views. - self.current_path = nil - - # Clear the static class cache - cache.clear - - # Setup the default values from calls to set before initialization - self.class.superclass.defaults.each { |k,v| set(k,v) } - - # Evaluate a passed block if given - instance_exec(&block) if block_given? - - # Build expanded source path once paths have been parsed - path = root.dup - source_path = ENV["MM_SOURCE"] || self.source - path = File.join(root, source_path) unless source_path.empty? - set :source_dir, path - - super - end - - # Shared cache instance - # - # @private - # @return [Middleman::Cache] The cache - def self.cache - @_cache ||= ::Middleman::Cache.new - end - delegate :cache, :to => :"self.class" - - # Rack env - attr :env - - # Rack request - # @return [Rack::Request] - attr :req - - # Rack response - # @return [Rack::Response] - attr :res - - # Rack Interface - # - # @private - # @param Rack environment - def call(env) - # Store environment, request and response for later - @env = env - @req = Rack::Request.new(env) - @res = Rack::Response.new - - if env["PATH_INFO"] == "/__middleman__" - if env["REQUEST_METHOD"] == "POST" - if req.params.has_key?("change") - self.files.did_change(req.params["change"]) - elsif req.params.has_key?("delete") - self.files.did_delete(req.params["delete"]) + # Get the static instance + # + # @private + # @return [Middleman::Base] + def inst(&block) + @inst ||= begin + mm = new(&block) + mm.run_hook :ready + mm end end - - res.status = 200 - return res.finish - end - - puts "== Request: #{env["PATH_INFO"]}" if logging? - # Catch :halt exceptions and use that response if given - catch(:halt) do - process_request - - res.status = 404 - res.finish - end - end - - # Halt the current request and return a response - # - # @private - # @param [String] Response value - def halt(response) - throw :halt, response - end - - # Whether we're in development mode - # @return [Boolean] If we're in dev mode - def development?; environment == :development; end - - # Whether we're in build mode - # @return [Boolean] If we're in build mode - def build?; environment == :build; end - - # Core response method. We process the request, check with the sitemap, - # and return the correct file, response or status message. - # - # @private - def process_request - # Normalize the path and add index if we're looking at a directory - @original_path = env["PATH_INFO"].dup - @escaped_path = @original_path.gsub("%20", " ") - @request_path = full_path(@escaped_path) - - # Run before callbacks - run_hook :before - - if @escaped_path != @request_path - # Get the resource object for this path - resource = sitemap.find_resource_by_destination_path(@escaped_path) - end - - # Get the resource object for this full path - resource ||= sitemap.find_resource_by_destination_path(@request_path) - - # Return 404 if not in sitemap - return not_found unless resource && !resource.ignored? - - # If this path is a static file, send it immediately - return send_file(resource.source_file) unless resource.template? - - # Set the current path for use in helpers - self.current_path = @request_path.dup - - # Set a HTTP content type based on the request's extensions - content_type resource.mime_type - - begin - # Write out the contents of the page - res.write resource.render - - # Valid content is a 200 status - res.status = 200 - rescue Middleman::CoreExtensions::Rendering::TemplateNotFound => e - res.write "Error: #{e.message}" - res.status = 500 - end - - # End the request - puts "== Finishing Request: #{self.current_path}" if logging? - halt res.finish - end - - # Backwards compatibilty with old Sinatra template interface - # - # @return [Middleman::Base] - def settings - self - end - - # Whether we're logging - # - # @return [Boolean] If we're logging - def logging? - logging - end - - # Expand a path to include the index file if it's a directory - # - # @private - # @param [String] path Request path - # @return [String] Path with index file if necessary - def full_path(path) - cache.fetch(:full_path, path) do - parts = path ? path.split('/') : [] - if parts.last.nil? || parts.last.split('.').length == 1 - path = File.join(path, index_file) + # Set the shared instance + # + # @private + # @param [Middleman::Base] inst + # @return [void] + def inst=(inst) + @inst = inst end - "/" + path.sub(%r{^/}, '') - end - end - - # Add a new mime-type for a specific extension - # - # @param [Symbol] type File extension - # @param [String] value Mime type - # @return [void] - def mime_type(type, value=nil) - return type if type.nil? || type.to_s.include?('/') - type = ".#{type}" unless type.to_s[0] == ?. - return ::Rack::Mime.mime_type(type, nil) unless value - ::Rack::Mime::MIME_TYPES[type] = value - end - -protected - - # Halt request and return 404 - def not_found - @res.status == 404 - @res.write "

File Not Found

#{@request_path}

" - @res.finish - end - - delegate :helpers, :use, :map, :to => :"self.class" - - # Immediately send static file - # - # @param [String] path File to send - def send_file(path) - extension = File.extname(path) - matched_mime = mime_type(extension) - matched_mime = "application/octet-stream" if matched_mime.nil? - content_type matched_mime - file = ::Rack::File.new nil - file.path = path - response = file.serving(env) - response[1]['Content-Encoding'] = 'gzip' if %w(.svgz).include?(extension) - halt response + # Return built Rack app + # + # @private + # @return [Rack::Builder] + def to_rack_app(&block) + inner_app = inst(&block) + + (@middleware || []).each do |m| + app.use(m[0], *m[1], &m[2]) + end + + app.map("/") { run inner_app } + + (@mappings || []).each do |m| + app.map(m[0], &m[1]) + end + + app + end + + # Prototype app. Used in config.ru + # + # @private + # @return [Rack::Builder] + def prototype + @prototype ||= to_rack_app + end + + # Call prototype, use in config.ru + # + # @private + def call(env) + prototype.call(env) + end + + # Use Rack middleware + # + # @param [Class] Middleware + # @return [void] + def use(middleware, *args, &block) + @middleware ||= [] + @middleware << [middleware, args, block] + end + + # Add Rack App mapped to specific path + # + # @param [String] Path to map + # @return [void] + def map(map, &block) + @mappings ||= [] + @mappings << [map, block] + end + + # Mix-in helper methods. Accepts either a list of Modules + # and/or a block to be evaluated + # @return [void] + def helpers(*extensions, &block) + class_eval(&block) if block_given? + include(*extensions) if extensions.any? + end + + # Access class-wide defaults + # + # @private + # @return [Hash] Hash of default values + def defaults + @defaults ||= {} + end + + # Set class-wide defaults + # + # @param [Symbol] Unique key name + # @param Default value + # @return [void] + def set(key, value=nil, &block) + @defaults ||= {} + @defaults[key] = value + + @inst.set(key, value, &block) if @inst + end + end + + # Set attributes (global variables) + # + # @param [Symbol] Name of the attribue + # @param Attribute value + # @return [void] + def set(key, value=nil, &block) + setter = "#{key}=".to_sym + self.class.send(:attr_accessor, key) if !respond_to?(setter) + value = block if block_given? + send(setter, value) + end + + # Root project directory (overwritten in middleman build/server) + # @return [String] + set :root, ENV["MM_ROOT"] || Dir.pwd + + # Name of the source directory + # @return [String] + set :source, "source" + + # Middleman environment. Defaults to :development, set to :build by the build process + # @return [String] + set :environment, (ENV['MM_ENV'] && ENV['MM_ENV'].to_sym) || :development + + # Whether logging is active, disabled by default + # @return [String] + set :logging, false + + # Which file should be used for directory indexes + # @return [String] + set :index_file, "index.html" + + # Location of javascripts within source. Used by Sprockets. + # @return [String] + set :js_dir, "javascripts" + + # Location of stylesheets within source. Used by Compass. + # @return [String] + set :css_dir, "stylesheets" + + # Location of images within source. Used by HTML helpers and Compass. + # @return [String] + set :images_dir, "images" + + # Where to build output files + # @return [String] + set :build_dir, "build" + + # Default prefix for building paths. Used by HTML helpers and Compass. + # @return [String] + set :http_prefix, "/" + + # Whether to catch and display exceptions + # @return [Boolean] + set :show_exceptions, true + + # Automatically loaded extensions + # @return [Array] + set :default_extensions, [ :lorem ] + + # Default layout name + # @return [String, Symbold] + set :layout, :_auto_layout + + # Activate custom features and extensions + include Middleman::CoreExtensions::Extensions + + # Handle exceptions + register Middleman::CoreExtensions::ShowExceptions + + # Add Builder Callbacks + register Middleman::CoreExtensions::Builder + + # Add Watcher Callbacks + register Middleman::CoreExtensions::FileWatcher + + # Activate Data package + register Middleman::CoreExtensions::Data + + # Setup custom rendering + register Middleman::CoreExtensions::Rendering + + # Sitemap + register Middleman::Sitemap + + # Setup external helpers + register Middleman::CoreExtensions::ExternalHelpers + + # Setup default helpers + register Middleman::CoreExtensions::DefaultHelpers + + # Setup asset path pipeline + register Middleman::CoreExtensions::Assets + + # with_layout and page routing + register Middleman::CoreExtensions::Routing + + # Parse YAML from templates + register Middleman::CoreExtensions::FrontMatter + + # i18n + register Middleman::CoreExtensions::I18n + + # Built-in Extensions + Middleman::Extensions.register(:directory_indexes) { + Middleman::Extensions::DirectoryIndexes } + Middleman::Extensions.register(:lorem) { + Middleman::Extensions::Lorem } + Middleman::Extensions.register(:automatic_image_sizes) { + Middleman::Extensions::AutomaticImageSizes } + Middleman::Extensions.register(:asset_host) { + Middleman::Extensions::AssetHost } + + # Backwards-compatibility with old request.path signature + attr :request + + # Accessor for current path + # @return [String] + def current_path + @_current_path + end + + # Set the current path + # + # @param [String] path The new current path + # @return [void] + def current_path=(path) + @_current_path = path + @request = ::Thor::CoreExt::HashWithIndifferentAccess.new({ + :path => path, + :params => req ? ::Thor::CoreExt::HashWithIndifferentAccess.new(req.params) : {} + }) + end + + # Initialize the Middleman project + def initialize(&block) + # Current path defaults to nil, used in views. + self.current_path = nil + + # Clear the static class cache + cache.clear + + # Setup the default values from calls to set before initialization + self.class.superclass.defaults.each { |k,v| set(k,v) } + + # Evaluate a passed block if given + instance_exec(&block) if block_given? + + # Build expanded source path once paths have been parsed + path = root.dup + source_path = ENV["MM_SOURCE"] || self.source + path = File.join(root, source_path) unless source_path.empty? + set :source_dir, path + + super + end + + # Shared cache instance + # + # @private + # @return [Middleman::Cache] The cache + def self.cache + @_cache ||= ::Middleman::Cache.new + end + delegate :cache, :to => :"self.class" + + # Rack env + attr :env + + # Rack request + # @return [Rack::Request] + attr :req + + # Rack response + # @return [Rack::Response] + attr :res + + # Rack Interface + # + # @private + # @param Rack environment + def call(env) + # Store environment, request and response for later + @env = env + @req = Rack::Request.new(env) + @res = Rack::Response.new + + if env["PATH_INFO"] == "/__middleman__" + if env["REQUEST_METHOD"] == "POST" + if req.params.has_key?("change") + self.files.did_change(req.params["change"]) + elsif req.params.has_key?("delete") + self.files.did_delete(req.params["delete"]) + end + end + + res.status = 200 + return res.finish + end + + puts "== Request: #{env["PATH_INFO"]}" if logging? + + # Catch :halt exceptions and use that response if given + catch(:halt) do + process_request + + res.status = 404 + res.finish + end + end + + # Halt the current request and return a response + # + # @private + # @param [String] Response value + def halt(response) + throw :halt, response + end + + # Whether we're in development mode + # @return [Boolean] If we're in dev mode + def development?; environment == :development; end + + # Whether we're in build mode + # @return [Boolean] If we're in build mode + def build?; environment == :build; end + + # Core response method. We process the request, check with the sitemap, + # and return the correct file, response or status message. + # + # @private + def process_request + # Normalize the path and add index if we're looking at a directory + @original_path = env["PATH_INFO"].dup + @escaped_path = @original_path.gsub("%20", " ") + @request_path = full_path(@escaped_path) + + # Run before callbacks + run_hook :before + + if @escaped_path != @request_path + # Get the resource object for this path + resource = sitemap.find_resource_by_destination_path(@escaped_path) + end + + # Get the resource object for this full path + resource ||= sitemap.find_resource_by_destination_path(@request_path) + + # Return 404 if not in sitemap + return not_found unless resource && !resource.ignored? + + # If this path is a static file, send it immediately + return send_file(resource.source_file) unless resource.template? + + # Set the current path for use in helpers + self.current_path = @request_path.dup + + # Set a HTTP content type based on the request's extensions + content_type resource.mime_type + + begin + # Write out the contents of the page + res.write resource.render + + # Valid content is a 200 status + res.status = 200 + rescue Middleman::CoreExtensions::Rendering::TemplateNotFound => e + res.write "Error: #{e.message}" + res.status = 500 + end + + # End the request + puts "== Finishing Request: #{self.current_path}" if logging? + halt res.finish + end + + # Backwards compatibilty with old Sinatra template interface + # + # @return [Middleman::Base] + def settings + self + end + + # Whether we're logging + # + # @return [Boolean] If we're logging + def logging? + logging + end + + # Expand a path to include the index file if it's a directory + # + # @private + # @param [String] path Request path + # @return [String] Path with index file if necessary + def full_path(path) + cache.fetch(:full_path, path) do + parts = path ? path.split('/') : [] + if parts.last.nil? || parts.last.split('.').length == 1 + path = File.join(path, index_file) + end + "/" + path.sub(%r{^/}, '') + end + end + + # Add a new mime-type for a specific extension + # + # @param [Symbol] type File extension + # @param [String] value Mime type + # @return [void] + def mime_type(type, value=nil) + return type if type.nil? || type.to_s.include?('/') + type = ".#{type}" unless type.to_s[0] == ?. + return ::Rack::Mime.mime_type(type, nil) unless value + ::Rack::Mime::MIME_TYPES[type] = value + end + + protected + + # Halt request and return 404 + def not_found + @res.status == 404 + @res.write "

File Not Found

#{@request_path}

" + @res.finish + end + + delegate :helpers, :use, :map, :to => :"self.class" + + # Immediately send static file + # + # @param [String] path File to send + def send_file(path) + extension = File.extname(path) + matched_mime = mime_type(extension) + matched_mime = "application/octet-stream" if matched_mime.nil? + content_type matched_mime + + file = ::Rack::File.new nil + file.path = path + response = file.serving(env) + response[1]['Content-Encoding'] = 'gzip' if %w(.svgz).include?(extension) + halt response + end + + # Set the content type for the current request + # + # @param [String] type Content type + # @param [Hash] params + # @return [void] + def content_type(type = nil, params={}) + return res['Content-Type'] unless type + default = params.delete :default + mime_type = mime_type(type) || default + throw "Unknown media type: %p" % type if mime_type.nil? + mime_type = mime_type.dup + unless params.include? :charset + params[:charset] = params.delete('charset') || "utf-8" + end + params.delete :charset if mime_type.include? 'charset' + unless params.empty? + mime_type << (mime_type.include?(';') ? ', ' : ';') + mime_type << params.map { |kv| kv.join('=') }.join(', ') + end + res['Content-Type'] = mime_type + end end - # Set the content type for the current request - # - # @param [String] type Content type - # @param [Hash] params - # @return [void] - def content_type(type = nil, params={}) - return res['Content-Type'] unless type - default = params.delete :default - mime_type = mime_type(type) || default - throw "Unknown media type: %p" % type if mime_type.nil? - mime_type = mime_type.dup - unless params.include? :charset - params[:charset] = params.delete('charset') || "utf-8" + class << self + # Create a new Class which is based on Middleman::Base + # Used to create a safe sandbox into which extensions and + # configuration can be included later without impacting + # other classes and instances. + # + # @return [Class] + def server(&block) + @@servercounter ||= 1 + @@servercounter += 1 + const_set("MiddlemanBase#{@@servercounter}", Class.new(Middleman::Base)) end - params.delete :charset if mime_type.include? 'charset' - unless params.empty? - mime_type << (mime_type.include?(';') ? ', ' : ';') - mime_type << params.map { |kv| kv.join('=') }.join(', ') + + # Creates a new Rack::Server + # + # @param [Hash] options to pass to Rack::Server.new + # @return [Rack::Server] + def start_server(options={}) + require "webrick" + + opts = { + :Port => options[:port] || 4567, + :Host => options[:host] || "0.0.0.0", + :AccessLog => [] + } + + # opts[:Logger] = WEBrick::Log::new("/dev/null", 7) if !options[:logging] + + app_class = options[:app] ||= ::Middleman.server.inst + opts[:app] = app_class + + # Disable for Beta 1. See if people notice. + require "thin" + ::Thin::Logging.silent = !options[:logging] + opts[:server] = 'thin' + # opts[:server] = 'webrick' + + server = ::Rack::Server.new(opts) + server.start + server end - res['Content-Type'] = mime_type end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/cli.rb b/middleman-core/lib/middleman-core/cli.rb index 694e077d..50a70942 100644 --- a/middleman-core/lib/middleman-core/cli.rb +++ b/middleman-core/lib/middleman-core/cli.rb @@ -5,6 +5,12 @@ require "thor/group" # CLI Module module Middleman::Cli + module Cli + autoload :Build, "middleman-core/cli/build" + autoload :Init, "middleman-core/cli/init" + autoload :Server, "middleman-core/cli/server" + end + # The base task from which everything else etends class Base < Thor diff --git a/middleman-core/lib/middleman-core/cli/init.rb b/middleman-core/lib/middleman-core/cli/init.rb index 6da690e5..51d734f1 100644 --- a/middleman-core/lib/middleman-core/cli/init.rb +++ b/middleman-core/lib/middleman-core/cli/init.rb @@ -1,3 +1,5 @@ +require "middleman-core/templates" + # CLI Module module Middleman::Cli diff --git a/middleman-core/lib/middleman-core/cli/server.rb b/middleman-core/lib/middleman-core/cli/server.rb index 49c87c64..7c8fc375 100644 --- a/middleman-core/lib/middleman-core/cli/server.rb +++ b/middleman-core/lib/middleman-core/cli/server.rb @@ -1,3 +1,5 @@ +require "middleman-core/watcher" + # CLI Module module Middleman::Cli diff --git a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb index 7f6843e9..8348d8ad 100644 --- a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb +++ b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb @@ -1,4 +1,5 @@ require "find" +require "middleman-core/watcher" # API for watching file change events module Middleman::CoreExtensions::FileWatcher diff --git a/middleman-core/lib/middleman-core/core_extensions/rendering.rb b/middleman-core/lib/middleman-core/core_extensions/rendering.rb index 73f38f96..abadb725 100644 --- a/middleman-core/lib/middleman-core/core_extensions/rendering.rb +++ b/middleman-core/lib/middleman-core/core_extensions/rendering.rb @@ -1,3 +1,5 @@ +require "middleman-core/renderers/erb" + # Shutup Tilt Warnings # @private class Tilt::Template diff --git a/middleman-core/lib/middleman-core/extensions.rb b/middleman-core/lib/middleman-core/extensions.rb new file mode 100644 index 00000000..21a55127 --- /dev/null +++ b/middleman-core/lib/middleman-core/extensions.rb @@ -0,0 +1,179 @@ +module Middleman + + # Backwards compatibility namespace + module Features + end + + module CoreExtensions + # File Change Notifier + autoload :FileWatcher, "middleman-core/core_extensions/file_watcher" + + # In-memory Sitemap + autoload :Sitemap, "middleman-core/core_extensions/sitemap" + + # Add Builder callbacks + autoload :Builder, "middleman-core/core_extensions/builder" + + # Custom Feature API + autoload :Extensions, "middleman-core/core_extensions/extensions" + + # Asset Path Pipeline + autoload :Assets, "middleman-core/core_extensions/assets" + + # Data looks at the data/ folder for YAML files and makes them available + # to dynamic requests. + autoload :Data, "middleman-core/core_extensions/data" + + # Parse YAML from templates + autoload :FrontMatter, "middleman-core/core_extensions/front_matter" + + # External helpers looks in the helpers/ folder for helper modules + autoload :ExternalHelpers, "middleman-core/core_extensions/external_helpers" + + # DefaultHelpers are the built-in dynamic template helpers. + autoload :DefaultHelpers, "middleman-core/core_extensions/default_helpers" + + # Extended version of Padrino's rendering + autoload :Rendering, "middleman-core/core_extensions/rendering" + + # Pass custom options to views + autoload :Routing, "middleman-core/core_extensions/routing" + + # Catch and show exceptions at the Rack level + autoload :ShowExceptions, "middleman-core/core_extensions/show_exceptions" + + # i18n + autoload :I18n, "middleman-core/core_extensions/i18n" + end + + module Extensions + # Provide Apache-style index.html files for directories + autoload :DirectoryIndexes, "middleman-core/extensions/directory_indexes" + + # Lorem provides a handful of helpful prototyping methods to generate + # words, paragraphs, fake images, names and email addresses. + autoload :Lorem, "middleman-core/extensions/lorem" + + # AutomaticImageSizes inspects the images used in your dynamic templates + # and automatically adds width and height attributes to their HTML + # elements. + autoload :AutomaticImageSizes, "middleman-core/extensions/automatic_image_sizes" + + # AssetHost allows you to setup multiple domains to host your static + # assets. Calls to asset paths in dynamic templates will then rotate + # through each of the asset servers to better spread the load. + autoload :AssetHost, "middleman-core/extensions/asset_host" + + class << self + def registered + @_registered ||= {} + end + + # Register a new extension. Choose a name which will be + # used to activate the extension in config.rb, like this: + # + # activate :my_extension + # + # Provide your extension module either as the namespace + # parameter, or return it from the block: + # + # @param [Symbol] name The name of the extension + # @param [Module] namespace The extension module + # @param [String] version A RubyGems-style version string stating + # the versions of middleman this extension + # is compatible with. + # @yield Instead of passing a module in namespace, you can provide + # a block which returns your extension module. This gives + # you the ability to require other files only when the + # extension is activated. + def register(name, namespace=nil, version=nil, &block) + # If we've already got a matching extension that passed the + # version check, bail out. + return if registered.has_key?(name.to_sym) && + !registered[name.to_sym].is_a?(String) + + if block_given? + version = namespace + end + + passed_version_check = true + if !version.nil? + requirement = ::Gem::Requirement.create(version) + if !requirement.satisfied_by?(Middleman::GEM_VERSION) + passed_version_check = false + end + end + + registered[name.to_sym] = if !passed_version_check + "== #{name} failed version check. Requested #{version}, got #{Middleman::VERSION}" + elsif block_given? + block + elsif namespace + namespace + end + end + + def load(name) + name = name.to_sym + return nil unless registered.has_key?(name) + + extension = registered[name] + if extension.is_a?(Proc) + extension = extension.call() || nil + registered[name] = extension + end + + extension + end + end + end + + # Where to look in gems for extensions to auto-register + EXTENSION_FILE = File.join("lib", "middleman_extension.rb") unless const_defined?(:EXTENSION_FILE) + + class << self + # Automatically load extensions from available RubyGems + # which contain the EXTENSION_FILE + # + # @private + def load_extensions_in_path + if defined?(Bundler) + Bundler.require + else + extensions = rubygems_latest_specs.select do |spec| + spec_has_file?(spec, EXTENSION_FILE) + end + + extensions.each do |spec| + require spec.name + end + end + end + + # Backwards compatible means of finding all the latest gemspecs + # available on the system + # + # @private + # @return [Array] Array of latest Gem::Specification + def rubygems_latest_specs + # If newer Rubygems + if ::Gem::Specification.respond_to? :latest_specs + ::Gem::Specification.latest_specs + else + ::Gem.source_index.latest_specs + end + end + + # Where a given Gem::Specification has a specific file. Used + # to discover extensions and Sprockets-supporting gems. + # + # @private + # @param [Gem::Specification] + # @param [String] Path to look for + # @return [Boolean] Whether the file exists + def spec_has_file?(spec, path) + full_path = File.join(spec.full_gem_path, path) + File.exists?(full_path) + end + end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/renderers/erb.rb b/middleman-core/lib/middleman-core/renderers/erb.rb index d7990dee..ea5b9d7a 100644 --- a/middleman-core/lib/middleman-core/renderers/erb.rb +++ b/middleman-core/lib/middleman-core/renderers/erb.rb @@ -1,33 +1,36 @@ # ERb renderer -module Middleman::Renderers::ERb - - # Setup extension - class << self +module Middleman + module Renderers + module ERb + # Setup extension + class << self - # once registered - def registered(app) - # Setup a default ERb engine - app.set :erb_engine, :erb - app.set :erb_engine_prefix, ::Tilt + # once registered + def registered(app) + # Setup a default ERb engine + app.set :erb_engine, :erb + app.set :erb_engine_prefix, ::Tilt - app.before_configuration do - template_extensions :erb => :html - end + app.before_configuration do + template_extensions :erb => :html + end - # After config - app.after_configuration do - # Find the user's prefered engine - # Convert symbols to classes - if erb_engine.is_a? Symbol - engine = engine.to_s - engine = engine == "erb" ? "ERB" : engine.camelize - erb_engine = erb_engine_prefix.const_get("#{engine}Template") - end + # After config + app.after_configuration do + # Find the user's prefered engine + # Convert symbols to classes + if erb_engine.is_a? Symbol + engine = engine.to_s + engine = engine == "erb" ? "ERB" : engine.camelize + erb_engine = erb_engine_prefix.const_get("#{engine}Template") + end - # Tell Tilt to use the preferred engine - ::Tilt.prefer(erb_engine) + # Tell Tilt to use the preferred engine + ::Tilt.prefer(erb_engine) + end + end + alias :included :registered end end - alias :included :registered end end \ No newline at end of file