diff --git a/.gitignore b/.gitignore index ae325653..9f3f2a22 100755 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ docs fixtures/test-app/build .*.swp build +doc +.yardoc diff --git a/.yardopts b/.yardopts new file mode 100644 index 00000000..4265d2bf --- /dev/null +++ b/.yardopts @@ -0,0 +1,5 @@ +lib/**/*.rb +--exclude lib/middleman/vendor +--exclude lib/middleman/extensions/automatic_image_sizes/fastimage.rb +--exclude lib/middleman/extensions/minify_css/cssmin.rb +--no-private \ No newline at end of file diff --git a/lib/middleman.rb b/lib/middleman.rb index 67022a3b..b763205e 100755 --- a/lib/middleman.rb +++ b/lib/middleman.rb @@ -1,56 +1,3 @@ -# Middleman is a static site renderer that provides all the conveniences of -# a modern web stack, like Ruby on Rails, while remaining focused on building -# the fastest, most-professional sites possible -# -# Install Middleman: -# -# gem install middleman -# -# To accomplish its goals, Middleman supports provides access to: -# -#### Command-line tool: -# * **middleman init**: A tool for creating to new static sites. -# * **middleman server**: A tool for rapidly developing your static site. -# * **middleman build**: A tool for exporting your site into optimized HTML, CSS & JS. -# -#### Tons of templating languages including: -# * ERB (.erb) -# * Interpolated String (.str) -# * Sass (.sass) -# * Scss (.scss) -# * Haml (.haml) -# * Slim (.slim) -# * Less CSS (.less) -# * Builder (.builder) -# * Liquid (.liquid) -# * RDiscount (.markdown) -# * RedCloth (.textile) -# * RDoc (.rdoc) -# * Radius (.radius) -# * Markaby (.mab) -# * Nokogiri (.nokogiri) -# * Mustache (.mustache) -# * CoffeeScript (.coffee) -# -#### Compile-time Optimiztions -# * Javascript Minifiers: YUI, Google Closure & UglifyJS -# * Smush.it Image Compression -# * CSS Minification -# -#### Robust Extensions: -# Add your own runtime and build-time extensions! -# -#### Next Steps: -# * [Visit the website] -# * [Read the wiki] -# * [Email the users group] -# * [Submit bug reports] -# -# [Visit the website]: http://middlemanapp.com -# [Read the wiki]: https://github.com/tdreyno/middleman/wiki -# [Email the users group]: https://convore.com/middleman/ -# [Submit bug reports]: https://github.com/tdreyno/middleman/issues - # Setup our load paths libdir = File.dirname(__FILE__) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) @@ -78,6 +25,7 @@ module Middleman autoload :Page, "middleman/sitemap/page" autoload :Template, "middleman/sitemap/template" end + module CoreExtensions # File Change Notifier autoload :FileWatcher, "middleman/core_extensions/file_watcher" @@ -153,8 +101,15 @@ module Middleman autoload :SitemapTree, "middleman/extensions/sitemap_tree" end + # Where to look in gems for extensions to auto-register EXTENSION_FILE = File.join("lib", "middleman_extension.rb") + class << self + + # Automatically load extensions from available RubyGems + # which contain the EXTENSION_FILE + # + # @private def load_extensions_in_path extensions = rubygems_latest_specs.select do |spec| spec_has_file?(spec, EXTENSION_FILE) @@ -162,10 +117,14 @@ module Middleman extensions.each do |spec| require spec.name - # $stderr.puts "require: #{spec.name}" 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 @@ -175,15 +134,32 @@ module Middleman 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) 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={}) opts = { :Port => options[:port] || 4567, @@ -204,5 +180,8 @@ module Middleman end end +# Make the VERSION string available require "middleman/version" + +# Automatically discover extensions in RubyGems Middleman.load_extensions_in_path \ No newline at end of file diff --git a/lib/middleman/base.rb b/lib/middleman/base.rb index 24e19dca..f482a85a 100644 --- a/lib/middleman/base.rb +++ b/lib/middleman/base.rb @@ -1,68 +1,125 @@ +# Built on Rack require "rack" + +# Using Tilt for templating require "tilt" + +# Simple callback library require "middleman/vendor/hooks-0.2.0/lib/hooks" +# Use ActiveSupport JSON and Inflections require "active_support" require "active_support/json" require "active_support/core_ext/string/inflections" +# Core Middleman Class class Middleman::Base + # Uses callbacks include Hooks + + # Before request hook define_hook :before + + # Ready (all loading and parsing of extensions complete) hook define_hook :ready + + # Initialized (after initialized() runs) define_hook :initialized 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 ||= new(&block) end + # Return built Rack app + # + # @private + # @return [Rack::Builder] def to_rack_app(&block) inner_app = inst(&block) app.map("/") { run inner_app } 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 def use(middleware, *args, &block) app.use(middleware, *args, &block) end + # Add Rack App mapped to specific path + # + # @param [String] Path to map def map(map, &block) app.map(map, &block) end + # Mix-in helper methods. Accepts either a list of Modules + # and/or a block to be evaluated 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 def set(key, value) @defaults ||= {} @defaults[key] = value end end + # Set attributes + # + # @param [Symbol] Name of the attribue + # @param Attribute value def set(key, value=nil, &block) setter = "#{key}=".to_sym self.class.send(:attr_accessor, key) if !respond_to?(setter) @@ -70,27 +127,39 @@ class Middleman::Base send(setter, value) end - # Basic Sinatra config + # Root project directory (overwritten in middleman build/server) set :root, Dir.pwd + + # Name of the source directory set :source, "source" + + # Set the environment from the environment. Defaults to :development, set + # to :build by the build process set :environment, (ENV['MM_ENV'] && ENV['MM_ENV'].to_sym) || :development + + # Disable logging by default set :logging, false - # Middleman-specific options - set :index_file, "index.html" # What file responds to folder requests - # Such as the homepage (/) or subfolders (/about/) + # Which file should be used for directory indexes + set :index_file, "index.html" - # These directories are passed directly to Compass - set :js_dir, "javascripts" # Where to look for javascript files - set :css_dir, "stylesheets" # Where to look for CSS files - set :images_dir, "images" # Where to look for images - - set :build_dir, "build" # Which folder are builds output to - set :http_prefix, "/" # During build, add a prefix for absolute paths - - set :views, "source" + # Location of javascripts within source. Used by Sprockets. + set :js_dir, "javascripts" - set :default_features, [ + # Location of stylesheets within source. Used by Compass. + set :css_dir, "stylesheets" + + # Location of images within source. Used by HTML helpers and Compass. + set :images_dir, "images" + + # Where to build output files + set :build_dir, "build" + + # Default prefix for building paths. Used by HTML helpers and Compass. + set :http_prefix, "/" + + # Automatically loaded extensions + set :default_extensions, [ :lorem, # :sitemap_tree ] @@ -135,44 +204,76 @@ class Middleman::Base register Middleman::CoreExtensions::FrontMatter # Built-in Extensions - Middleman::Extensions.register(:asset_host) { Middleman::Extensions::AssetHost } - Middleman::Extensions.register(:automatic_image_sizes) { Middleman::Extensions::AutomaticImageSizes } - Middleman::Extensions.register(:cache_buster) { Middleman::Extensions::CacheBuster } - Middleman::Extensions.register(:directory_indexes) { Middleman::Extensions::DirectoryIndexes } - Middleman::Extensions.register(:lorem) { Middleman::Extensions::Lorem } - Middleman::Extensions.register(:minify_css) { Middleman::Extensions::MinifyCss } - Middleman::Extensions.register(:minify_javascript) { Middleman::Extensions::MinifyJavascript } - Middleman::Extensions.register(:relative_assets) { Middleman::Extensions::RelativeAssets } - Middleman::Extensions.register(:sitemap_tree) { Middleman::Extensions::SitemapTree } + Middleman::Extensions.register(:asset_host) { + Middleman::Extensions::AssetHost } + Middleman::Extensions.register(:automatic_image_sizes) { + Middleman::Extensions::AutomaticImageSizes } + Middleman::Extensions.register(:cache_buster) { + Middleman::Extensions::CacheBuster } + Middleman::Extensions.register(:directory_indexes) { + Middleman::Extensions::DirectoryIndexes } + Middleman::Extensions.register(:lorem) { + Middleman::Extensions::Lorem } + Middleman::Extensions.register(:minify_css) { + Middleman::Extensions::MinifyCss } + Middleman::Extensions.register(:minify_javascript) { + Middleman::Extensions::MinifyJavascript } + Middleman::Extensions.register(:relative_assets) { + Middleman::Extensions::RelativeAssets } + Middleman::Extensions.register(:sitemap_tree) { + Middleman::Extensions::SitemapTree } + # Accessor for current path + attr_accessor :current_path + + # Initialize the Middleman project def initialize(&block) + # Current path defaults to nil, used in views. @current_path = nil + # 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 set :source_dir, File.join(root, source) - + super + # Run initialized callbacks run_hook :initialized end + # Shared cache instance + # + # @private + # @return [Middleman::Cache] The cache def cache @_cache ||= ::Middleman::Cache.new end + # Rack env attr :env + + # Rack request attr :req + + # 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 + # Catch :halt exceptions and use that response if given catch(:halt) do process_request @@ -181,52 +282,81 @@ class Middleman::Base end end + # Halt the current request and return a response + # + # @private + # @param [String] Reponse value def halt(response) throw :halt, response end - # Convenience methods to check if we're in a mode + # 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 - # Internal method to look for templates and evaluate them if found + # Core repsonse 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 @request_path = full_path(env["PATH_INFO"].gsub("%20", " ")) + # Run before callbacks run_hook :before + # Return 404 if not in sitemap return not_found unless sitemap.exists?(@request_path) + # Get the page object for this path sitemap_page = sitemap.page(@request_path) + + # Return 404 if this path is specifically ignored return not_found if sitemap_page.ignored? - # Static File + # If this path is a static file, send it immediately return send_file(sitemap_page.source_file) unless sitemap_page.template? + # Set the current path for use in helpers @current_path = @request_path.dup + # Set a HTTP content type based on the request's extensions content_type sitemap_page.mime_type + + # Valid content is a 200 status res.status = 200 - rqp = @request_path + + # Write out the contents of the page res.write sitemap_page.render + + # End the request halt res.finish end - -public # 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 - - attr_accessor :current_path + # Expand a path to include the index file if it's a directory + # + # @private + # @param [String] Request path + # @return [String] Path with index file if necessary def full_path(path) cache.fetch(:full_path, path) do parts = path ? path.split('/') : [] @@ -237,12 +367,59 @@ public end end + # Sinatra/Padrino render method signature. Simply forwards to the sitemap + # + # @param [Symbol] Engine name + # @param [String] Path + # @param [Hash] Rendering options + # @param [Hash] Rendering locals + # @return [String] Output + def render(engine, data, options={}, locals={}, &block) + if sitemap.exists?(data) + sitemap.page(data).render(options, locals, &block) + else + throw "Could not find file to render: #{data}" + end + end + + # Add a new mime-type for a specific extension + # + # @param [Symbol] File extension + # @param [String] Mime type + 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 "
#{@request_path}
" @res.finish end + # Set helpers at the class level + def helpers(*extensions, &block) + self.class.helpers(*extensions, &block) + end + + # Set middleware at the class level + def use(middleware, *args, &block) + self.class.use(middleware, *args, &block) + end + + # Set mapped rack app at the class level + def map(map, &block) + self.class.map(map, &block) + end + + # Immediately send static file + # + # @param [String] File to send def send_file(path) matched_mime = mime_type(File.extname(path)) matched_mime = "application/octet-stream" if matched_mime.nil? @@ -253,22 +430,9 @@ public halt file.serving(env) end - # Sinatra/Padrino render method signature - def render(engine, data, options={}, locals={}, &block) - if sitemap.exists?(data) - sitemap.page(data).render(options, locals, &block) - else - throw "Could not find file to render: #{data}" - end - end - - 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 - + # Set the content type for the current request + # + # @param [String] Content type def content_type(type = nil, params={}) return res['Content-Type'] unless type default = params.delete :default @@ -285,17 +449,4 @@ public end res['Content-Type'] = mime_type end - - # Forward to class level - def helpers(*extensions, &block) - self.class.helpers(*extensions, &block) - end - - def use(middleware, *args, &block) - self.class.use(middleware, *args, &block) - end - - def map(map, &block) - self.class.map(map, &block) - end end \ No newline at end of file diff --git a/lib/middleman/builder.rb b/lib/middleman/builder.rb index 2d6775d6..6858ca63 100644 --- a/lib/middleman/builder.rb +++ b/lib/middleman/builder.rb @@ -77,7 +77,7 @@ module Middleman def initialize(base, app, config={}, &block) @app = app - source = @app.views + source = @app.source @destination = @app.build_dir @source = File.expand_path(base.find_in_source_paths(source.to_s)) diff --git a/lib/middleman/core_extensions/compass.rb b/lib/middleman/core_extensions/compass.rb index 15781e6d..2b48fb68 100644 --- a/lib/middleman/core_extensions/compass.rb +++ b/lib/middleman/core_extensions/compass.rb @@ -13,11 +13,11 @@ module Middleman::CoreExtensions::Compass config.project_path = root config.environment = :development config.cache_path = File.join(root, ".sass-cache") - config.sass_dir = File.join(views, css_dir) - config.css_dir = File.join(views, css_dir) - config.javascripts_dir = File.join(views, js_dir) - config.fonts_dir = File.join(views, fonts_dir) - config.images_dir = File.join(views, images_dir) + config.sass_dir = File.join(source, css_dir) + config.css_dir = File.join(source, css_dir) + config.javascripts_dir = File.join(source, js_dir) + config.fonts_dir = File.join(source, fonts_dir) + config.images_dir = File.join(source, images_dir) config.http_images_path = if respond_to? :http_images_path http_images_path diff --git a/lib/middleman/core_extensions/extensions.rb b/lib/middleman/core_extensions/extensions.rb index ab99d6ae..cee35ec4 100644 --- a/lib/middleman/core_extensions/extensions.rb +++ b/lib/middleman/core_extensions/extensions.rb @@ -32,7 +32,7 @@ module Middleman::CoreExtensions::Extensions class << self def included(app) - # app.set :default_features, [] + # app.set :default_extensions, [] app.define_hook :after_configuration app.define_hook :before_configuration app.define_hook :build_config @@ -131,7 +131,7 @@ module Middleman::CoreExtensions::Extensions run_hook :after_configuration # Add in defaults - default_features.each do |ext| + default_extensions.each do |ext| # activate ext end diff --git a/lib/middleman/core_extensions/front_matter.rb b/lib/middleman/core_extensions/front_matter.rb index a39b584d..786608bc 100644 --- a/lib/middleman/core_extensions/front_matter.rb +++ b/lib/middleman/core_extensions/front_matter.rb @@ -57,7 +57,7 @@ module Middleman::CoreExtensions::FrontMatter def initialize(app) @app = app - @source = File.expand_path(@app.views, @app.root) + @source = File.expand_path(@app.source, @app.root) @local_data = {} end diff --git a/lib/middleman/extensions/automatic_image_sizes.rb b/lib/middleman/extensions/automatic_image_sizes.rb index 4328b836..45003c01 100755 --- a/lib/middleman/extensions/automatic_image_sizes.rb +++ b/lib/middleman/extensions/automatic_image_sizes.rb @@ -16,7 +16,7 @@ module Middleman::Extensions http_prefix = http_images_path rescue images_dir begin - real_path = File.join(views, images_dir, path) + real_path = File.join(source, images_dir, path) full_path = File.expand_path(real_path, root) http_prefix = http_images_path rescue images_dir if File.exists? full_path diff --git a/lib/middleman/extensions/cache_buster.rb b/lib/middleman/extensions/cache_buster.rb index 379f84cf..e48eb885 100755 --- a/lib/middleman/extensions/cache_buster.rb +++ b/lib/middleman/extensions/cache_buster.rb @@ -7,7 +7,7 @@ module Middleman::Extensions app.compass_config do |config| config.asset_cache_buster do |path, real_path| real_path = real_path.path if real_path.is_a? File - real_path = real_path.gsub(File.join(root, build_dir), views) + real_path = real_path.gsub(File.join(root, build_dir), source) if File.readable?(real_path) File.mtime(real_path).strftime("%s") else diff --git a/lib/middleman/renderers/sass.rb b/lib/middleman/renderers/sass.rb index 04616bb1..9eb10614 100644 --- a/lib/middleman/renderers/sass.rb +++ b/lib/middleman/renderers/sass.rb @@ -26,7 +26,7 @@ module Middleman::Renderers::Sass location_of_sass_file = if @context.build? File.expand_path(@context.build_dir, @context.root) else - File.expand_path(@context.views, @context.root) + File.expand_path(@context.source, @context.root) end parts = basename.split('.') diff --git a/lib/middleman/sitemap/store.rb b/lib/middleman/sitemap/store.rb index 742c281c..00329a0b 100644 --- a/lib/middleman/sitemap/store.rb +++ b/lib/middleman/sitemap/store.rb @@ -5,7 +5,7 @@ module Middleman::Sitemap def initialize(app) @app = app @cache = ::Middleman::Cache.new - @source = File.expand_path(@app.views, @app.root) + @source = File.expand_path(@app.source, @app.root) @pages = {} end diff --git a/middleman.gemspec b/middleman.gemspec index 50ad5b22..2170a171 100644 --- a/middleman.gemspec +++ b/middleman.gemspec @@ -48,6 +48,8 @@ Gem::Specification.new do |s| s.add_development_dependency("cucumber", ["~> 1.1.0"]) s.add_development_dependency("rake", ["~> 0.9.2"]) s.add_development_dependency("rspec", ["~> 2.7.0"]) + s.add_development_dependency("rdoc", ["~> 3.9.4"]) + s.add_development_dependency("yard") s.add_development_dependency("jquery-rails") s.add_development_dependency("bootstrap-rails", ["0.0.5"]) end