diff --git a/.gitignore b/.gitignore index 9f6bba4c..06a67829 100755 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ fixtures/generator-test build doc .yardoc +tmp diff --git a/.yardopts b/.yardopts index 59b574b0..5f7ae80d 100644 --- a/.yardopts +++ b/.yardopts @@ -1,7 +1,11 @@ lib/**/*.rb ---exclude lib/middleman/vendor +--exclude lib/middleman/vendor/ --exclude lib/middleman/extensions/automatic_image_sizes/fastimage.rb --exclude lib/middleman/extensions/minify_css/cssmin.rb --exclude lib/middleman/step_definitions ---exclude lib/middleman/step_definitions.rb ---no-private \ No newline at end of file +--exclude lib/middleman/templates/default/ +--exclude lib/middleman/templates/html5/ +--exclude lib/middleman/templates/mobile/ +--exclude lib/middleman/templates/shared/ +--no-private +--hide-void-return \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c064fd7..8cdf4b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,13 @@ * Sitemap object representing the known world * FileWatcher proxies file change events * Unified callback solution -* Removed Slim and Maruku from base install. Will need to be installed and required by the user (in - config.rb) +* Removed Slim from base install. Will need to be installed and required by the user (in - config.rb) * Activate mobile html5boilerplate template * Update to Redcarpet for Markdown (breaks Haml :markdown filter) +* Return correct exit codes (0 for success, 1 for failure) from CLI +* Yard code docs: http://rubydoc.info/github/tdreyno/middleman +* config.rb and extensions can add command-line commands +* Nested layouts using `wrap_layout` helper 2.0.14 ==== diff --git a/bin/middleman b/bin/middleman index b6b64011..e055fbe3 100755 --- a/bin/middleman +++ b/bin/middleman @@ -9,7 +9,7 @@ require 'rubygems' module Middleman module ProjectLocator class << self - def locate_middleman_root!(args) + def locate_middleman_root! cwd = Dir.pwd if !in_middleman_project? && !in_middleman_project_subdirectory? @@ -18,14 +18,14 @@ module Middleman end if in_middleman_project? - did_locate_middleman_project(cwd, args) + did_locate_middleman_project(cwd) return end Dir.chdir("..") do # Recurse in a chdir block: if the search fails we want to be sure # the application is generated in the original working directory. - locate_middleman_root!(args) unless cwd == Dir.pwd + locate_middleman_root! unless cwd == Dir.pwd end rescue SystemCallError # could not chdir, no problem just return @@ -39,39 +39,27 @@ module Middleman File.exists?(File.join(path, 'config.rb')) || !path.root? && in_middleman_project_subdirectory?(path.parent) end - def did_locate_middleman_project(path, args) + def did_locate_middleman_project(path) # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', path) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) - start_cli!(args) + start_cli! end - def start_cli!(args) + def start_cli! require 'middleman' - Middleman::CLI.start(args) + Middleman::Cli::Base.start end end end end -args = ARGV.dup +ARGV << "server" if ARGV.length < 1 -ARG_ALIASES = { - "s" => "server", - "b" => "build", - "i" => "init", - "new" => "init", - "n" => "init" -} - -if ARG_ALIASES.has_key?(args[0]) - args[0] = ARG_ALIASES[args[0]] -end - -if args.length < 1 || %w(server build migrate).include?(args.first) - Middleman::ProjectLocator.locate_middleman_root!(args) +if %w(server build migrate).include?(ARGV) + Middleman::ProjectLocator.locate_middleman_root! else - Middleman::ProjectLocator.start_cli!(args) + Middleman::ProjectLocator.start_cli! end \ No newline at end of file diff --git a/features/3rd_party_cli.feature b/features/3rd_party_cli.feature new file mode 100644 index 00000000..297040b5 --- /dev/null +++ b/features/3rd_party_cli.feature @@ -0,0 +1,5 @@ +Feature: Allow config.rb and extensions to add CLI commands + Scenario: Test 3rd Party Command + Given a fixture app "3rd-party-command" + When I run `middleman hello` + Then the output should contain "Hello World" \ No newline at end of file diff --git a/features/builder.feature b/features/builder.feature index e779302c..c5b23f20 100644 --- a/features/builder.feature +++ b/features/builder.feature @@ -2,33 +2,51 @@ Feature: Builder In order to output static html and css for delivery Scenario: Checking built folder for content - Given a built app at "test-app" - Then "index.html" should exist at "test-app" and include "Comment in layout" - Then "javascripts/coffee_test.js" should exist at "test-app" and include "Array.prototype.slice" - Then "index.html" should exist at "test-app" and include "

Welcome

" - Then "static.html" should exist at "test-app" and include "Static, no code!" - Then "services/index.html" should exist at "test-app" and include "Services" - Then "stylesheets/site.css" should exist at "test-app" and include "html, body, div, span" - Then "stylesheets/site_scss.css" should exist at "test-app" and include "html, body, div, span" - Then "stylesheets/static.css" should exist at "test-app" and include "body" - Then "_partial.html" should not exist at "test-app" - Then "spaces in file.html" should exist at "test-app" and include "spaces" - Then "images/blank.gif" should exist at "test-app" - Then "images/Read me (example).txt" should exist at "test-app" - Then "images/Child folder/regular_file(example).txt" should exist at "test-app" - Then ".htaccess" should exist at "test-app" - Then the last exit code should be "0" + Given a successfully built app at "test-app" + When I cd to "build" + Then the following files should exist: + | index.html | + | javascripts/coffee_test.js | + | static.html | + | services/index.html | + | stylesheets/site.css | + | stylesheets/site_scss.css | + | stylesheets/static.css | + | spaces in file.html | + | images/blank.gif | + | images/Read me (example).txt | + | images/Child folder/regular_file(example).txt | + | .htaccess | + Then the following files should not exist: + | _partial | + | _liquid_partial | + | layout | + | layouts/custom | + | layouts/content_for | + + And the file "index.html" should contain "Comment in layout" + And the file "index.html" should contain "

Welcome

" + And the file "javascripts/coffee_test.js" should contain "Array.prototype.slice" + And the file "static.html" should contain "Static, no code!" + And the file "services/index.html" should contain "Services" + And the file "stylesheets/site.css" should contain "html, body, div, span" + And the file "stylesheets/site_scss.css" should contain "html, body, div, span" + And the file "stylesheets/static.css" should contain "body" + And the file "spaces in file.html" should contain "spaces" Scenario: Build glob - Given a built app at "glob-app" with flags "--glob '*.css'" - Then "stylesheets/site.css" should exist at "glob-app" and include "html" - Then "index.html" should not exist at "glob-app" - Then the last exit code should be "0" + Given a successfully built app at "glob-app" with flags "--glob '*.css'" + When I cd to "build" + Then the following files should not exist: + | index.html | + Then the following files should exist: + | stylesheets/site.css | Scenario: Build with errors Given a built app at "build-with-errors-app" - Then the last exit code should be "1" - - # Scenario: Force relative assets - # Given a built app at "relative-app" with flags "--relative" - # Then "stylesheets/relative_assets.css" should exist at "relative-app" and include "../" + Then the exit status should be 1 + + Scenario: Build alias (b) + Given a fixture app "test-app" + When I run `middleman b` + Then was successfully built \ No newline at end of file diff --git a/features/chained_templates.feature b/features/chained_templates.feature index e9c968e4..b7d4e83e 100644 --- a/features/chained_templates.feature +++ b/features/chained_templates.feature @@ -9,7 +9,11 @@ Feature: Templates should be chainable And I should see "Sup" Scenario: Build chained template - Given a built app at "chained-app" - Then "index.html" should exist at "chained-app" and include "Title" - Then "index.html" should exist at "chained-app" and include "Subtitle" - Then "index.html" should exist at "chained-app" and include "Sup" \ No newline at end of file + Given a successfully built app at "chained-app" + When I cd to "build" + Then the following files should exist: + | index.html | + + And the file "index.html" should contain "Title" + And the file "index.html" should contain "Subtitle" + And the file "index.html" should contain "Sup" \ No newline at end of file diff --git a/features/clean_build.feature b/features/clean_build.feature index 268d65db..e70d5d3e 100644 --- a/features/clean_build.feature +++ b/features/clean_build.feature @@ -1,20 +1,31 @@ Feature: Build Clean Scenario: Build and Clean an app - Given app "clean-app" is using config "empty" - And a built app at "clean-app" + Given a fixture app "clean-app" + And app "clean-app" is using config "empty" + And a successfully built app at "clean-app" And app "clean-app" is using config "complications" - And a built app at "clean-app" with flags "--clean" - Then "should_be_ignored.html" should not exist at "clean-app" - And "should_be_ignored2.html" should not exist at "clean-app" - And "should_be_ignored3.html" should not exist at "clean-app" + + Given a successfully built app at "clean-app" with flags "--clean" + When I cd to "build" + Then the following files should not exist: + | should_be_ignored.html | + | should_be_ignored2.html | + | should_be_ignored3.html | + And the file "index.html" should contain "Comment in layout" Scenario: Clean an app with directory indexes - Given a built app at "clean-dir-app" - Then "about/index.html" should exist at "clean-dir-app" - Given a built app at "clean-dir-app" with flags "--clean" - Then "about/index.html" should exist at "clean-dir-app" - Then cleanup built app at "clean-dir-app" + Given a successfully built app at "clean-dir-app" + When I cd to "build" + Then the following files should exist: + | about/index.html | + + Given a successfully built app at "clean-dir-app" with flags "--clean" + When I cd to "build" + Then the following files should exist: + | about/index.html | Scenario: Clean build an app that's never been built - Given a built app at "clean-dir-app" with flags "--clean" - Then "about/index.html" should exist at "clean-dir-app" + Given a successfully built app at "clean-dir-app" with flags "--clean" + When I cd to "build" + Then the following files should exist: + | about/index.html | diff --git a/features/cli.feature b/features/cli.feature new file mode 100644 index 00000000..dcb6e6a4 --- /dev/null +++ b/features/cli.feature @@ -0,0 +1,121 @@ +Feature: Middleman CLI + + Scenario: Create a new project + When I run `middleman init MY_PROJECT` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + Then the following files should not exist: + | config.ru | + | Gemfile | + Then a directory named "source" should exist + When I cd to "source" + Then the following files should exist: + | index.html.erb | + | layout.erb | + | stylesheets/site.css.scss | + + Scenario: Create a new project (alias i) + When I run `middleman i MY_PROJECT` + Then a directory named "MY_PROJECT" should exist + + Scenario: Create a new project (alias i) + When I run `middleman new MY_PROJECT` + Then a directory named "MY_PROJECT" should exist + + Scenario: Create a new project (alias i) + When I run `middleman n MY_PROJECT` + Then a directory named "MY_PROJECT" should exist + + Scenario: Create a new project with Rack + When I run `middleman init MY_PROJECT --rack` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | config.ru | + Then the following files should not exist: + | Gemfile | + + Scenario: Create a new project with Bundler + When I run `middleman init MY_PROJECT --bundler` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | Gemfile | + Then the following files should not exist: + | config.ru | + + Scenario: Create a new HTML5 project + When I run `middleman init MY_PROJECT --template=html5` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + Then the following files should not exist: + | config.ru | + | Gemfile | + Then a directory named "source" should exist + When I cd to "source" + Then the following files should exist: + | index.html | + | humans.txt | + | js/script.js | + + Scenario: Create a new HTML5 project with Rack + When I run `middleman init MY_PROJECT --rack --template=html5` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | config.ru | + Then the following files should not exist: + | Gemfile | + + Scenario: Create a new HTML5 project with Bundler + When I run `middleman init MY_PROJECT --bundler --template=html5` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | Gemfile | + Then the following files should not exist: + | config.ru | + + Scenario: Create a new Mobile HTML5 project + When I run `middleman init MY_PROJECT --template=mobile` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + Then the following files should not exist: + | config.ru | + | Gemfile | + Then a directory named "source" should exist + When I cd to "source" + Then the following files should exist: + | index.html | + | humans.txt | + | js/libs/respond.min.js | + + Scenario: Create a new Mobile HTML5 project with Rack + When I run `middleman init MY_PROJECT --rack --template=mobile` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | config.ru | + Then the following files should not exist: + | Gemfile | + + Scenario: Create a new Mobile HTML5 project with Bundler + When I run `middleman init MY_PROJECT --bundler --template=mobile` + Then a directory named "MY_PROJECT" should exist + When I cd to "MY_PROJECT" + Then the following files should exist: + | config.rb | + | Gemfile | + Then the following files should not exist: + | config.ru | \ No newline at end of file diff --git a/features/coffee-script.feature b/features/coffee-script.feature index 08496916..ed2c6a1b 100644 --- a/features/coffee-script.feature +++ b/features/coffee-script.feature @@ -14,4 +14,4 @@ Feature: Support coffee-script Scenario: Rendering broken coffee Given the Server is running at "test-app" When I go to "/javascripts/broken-coffee.js" - Then I should see "ProgramError" \ No newline at end of file + Then I should see "Error" \ No newline at end of file diff --git a/features/custom_layout_engines.feature b/features/custom_layout_engines.feature index 5963efd1..4d0a5618 100644 --- a/features/custom_layout_engines.feature +++ b/features/custom_layout_engines.feature @@ -1,8 +1,11 @@ Feature: Custom Layout Engine Scenario: Checking built folder for content - Given a built app at "custom-layout-app" - Then "index.html" should exist at "custom-layout-app" and include "Comment in layout" + Given a successfully built app at "custom-layout-app" + When I cd to "build" + Then the following files should exist: + | index.html | + And the file "index.html" should contain "Comment in layout" Scenario: Checking server for content Given the Server is running at "test-app" diff --git a/features/directory_index.feature b/features/directory_index.feature index 72e76921..9f74daac 100644 --- a/features/directory_index.feature +++ b/features/directory_index.feature @@ -2,16 +2,23 @@ Feature: Directory Index In order output Apache-friendly directories and indexes Scenario: Checking built folder for content - Given a built app at "indexable-app" - Then "needs_index/index.html" should exist at "indexable-app" and include "Indexable" - Then "a_folder/needs_index/index.html" should exist at "indexable-app" and include "Indexable" - Then "leave_me_alone.html" should exist at "indexable-app" and include "Stay away" - Then "regular/index.html" should exist at "indexable-app" and include "Regular" - Then "regular/index/index.html" should not exist at "indexable-app" - Then "needs_index.html" should not exist at "indexable-app" - Then "a_folder/needs_index.html" should not exist at "indexable-app" - Then "leave_me_alone/index.html" should not exist at "indexable-app" - Then ".htaccess" should exist at "indexable-app" + Given a successfully built app at "indexable-app" + When I cd to "build" + Then the following files should exist: + | needs_index/index.html | + | a_folder/needs_index/index.html | + | leave_me_alone.html | + | regular/index.html | + | .htaccess | + Then the following files should not exist: + | egular/index/index.html | + | needs_index.html | + | a_folder/needs_index.html | + | leave_me_alone/index.html | + And the file "needs_index/index.html" should contain "Indexable" + And the file "a_folder/needs_index/index.html" should contain "Indexable" + And the file "leave_me_alone.html" should contain "Stay away" + And the file "regular/index.html" should contain "Regular" Scenario: Preview normal file Given the Server is running at "indexable-app" diff --git a/features/dynamic_pages.feature b/features/dynamic_pages.feature index 47d0c6a1..c55e963a 100644 --- a/features/dynamic_pages.feature +++ b/features/dynamic_pages.feature @@ -2,14 +2,17 @@ Feature: Dynamic Pages In order to use a single view to generate multiple output files Scenario: Checking built folder for content - Given a built app at "test-app" - Then "fake.html" should exist at "test-app" and include "I am real" - Then "fake/one.html" should exist at "test-app" and include "I am real: one" - Then "fake/two.html" should exist at "test-app" and include "I am real: two" - Then "target_ignore.html" should exist at "test-app" and include "Ignore me" - Then "should_be_ignored.html" should not exist at "test-app" - Then "should_be_ignored2.html" should not exist at "test-app" - Then "should_be_ignored3.html" should not exist at "test-app" + Given a successfully built app at "test-app" + When I cd to "build" + Then the following files should exist: + | fake.html | + | fake/one.html | + | fake/two.html | + | target_ignore.html | + Then the following files should not exist: + | should_be_ignored.html | + | should_be_ignored2.html | + | should_be_ignored3.html | Scenario: Preview basic proxy Given the Server is running at "test-app" diff --git a/features/fonts.feature b/features/fonts.feature index 1c673b28..b839a346 100644 --- a/features/fonts.feature +++ b/features/fonts.feature @@ -1,8 +1,11 @@ Feature: Web Fonts Scenario: Checking built folder for content - Given a built app at "fonts-app" - Then "stylesheets/fonts.css" should exist at "fonts-app" and include "/fonts/StMarie-Thin.otf" + Given a successfully built app at "fonts-app" + When I cd to "build" + Then the following files should exist: + | stylesheets/fonts.css | + And the file "stylesheets/fonts.css" should contain "/fonts/StMarie-Thin.otf" Scenario: Rendering scss Given the Server is running at "fonts-app" diff --git a/features/nested_layouts.feature b/features/nested_layouts.feature new file mode 100644 index 00000000..146443c5 --- /dev/null +++ b/features/nested_layouts.feature @@ -0,0 +1,9 @@ +Feature: Allow nesting of layouts + + Scenario: A page uses an inner layout when uses an outer layout + Given the Server is running at "nested-layout-app" + When I go to "/index.html" + Then I should see "Template" + And I should see "Inner" + And I should see "Outer" + And I should see "Master" \ No newline at end of file diff --git a/features/sprockets.feature b/features/sprockets.feature index 1d01b85d..debd034f 100644 --- a/features/sprockets.feature +++ b/features/sprockets.feature @@ -21,8 +21,10 @@ Feature: Sprockets Then I should see "Hello One" Scenario: Multiple engine files should build correctly - Given a built app at "test-app" - Then "javascripts/multiple_engines.js" should exist at "test-app" and include "Hello One" + Given a successfully built app at "test-app" + When I cd to "build" + Then a file named "javascripts/multiple_engines.js" should exist + And the file "javascripts/multiple_engines.js" should contain "Hello One" Scenario: Sprockets CSS require //require Given the Server is running at "test-app" diff --git a/fixtures/3rd-party-command/config.rb b/fixtures/3rd-party-command/config.rb new file mode 100644 index 00000000..f7bded87 --- /dev/null +++ b/fixtures/3rd-party-command/config.rb @@ -0,0 +1,10 @@ +class HelloWorld < Thor + default_task :say_hi + + desc "hello", "Say hello" + def say_hi + puts "Hello World" + end +end + +Middleman::Cli::Base.register(HelloWorld, :hello, "hello", "Say hello") \ No newline at end of file diff --git a/fixtures/nested-layout-app/config.rb b/fixtures/nested-layout-app/config.rb new file mode 100644 index 00000000..fe617582 --- /dev/null +++ b/fixtures/nested-layout-app/config.rb @@ -0,0 +1 @@ +set :layout, :inner \ No newline at end of file diff --git a/fixtures/nested-layout-app/source/index.html.erb b/fixtures/nested-layout-app/source/index.html.erb new file mode 100644 index 00000000..a1ab916e --- /dev/null +++ b/fixtures/nested-layout-app/source/index.html.erb @@ -0,0 +1 @@ +Template \ No newline at end of file diff --git a/fixtures/nested-layout-app/source/layouts/inner.erb b/fixtures/nested-layout-app/source/layouts/inner.erb new file mode 100644 index 00000000..62dfc12b --- /dev/null +++ b/fixtures/nested-layout-app/source/layouts/inner.erb @@ -0,0 +1,4 @@ +<% wrap_layout :outer do %> + Inner + <%= yield %> +<% end %> \ No newline at end of file diff --git a/fixtures/nested-layout-app/source/layouts/master.erb b/fixtures/nested-layout-app/source/layouts/master.erb new file mode 100644 index 00000000..18dcdbb7 --- /dev/null +++ b/fixtures/nested-layout-app/source/layouts/master.erb @@ -0,0 +1,2 @@ +Master +<%= yield %> \ No newline at end of file diff --git a/fixtures/nested-layout-app/source/layouts/outer.erb b/fixtures/nested-layout-app/source/layouts/outer.erb new file mode 100644 index 00000000..643d8efc --- /dev/null +++ b/fixtures/nested-layout-app/source/layouts/outer.erb @@ -0,0 +1,4 @@ +<% wrap_layout :master do %> + Outer + <%= yield %> +<% end %> \ No newline at end of file diff --git a/lib/middleman.rb b/lib/middleman.rb index 8944a11e..1cf7785b 100755 --- a/lib/middleman.rb +++ b/lib/middleman.rb @@ -1,17 +1,27 @@ +require "rbconfig" + # Setup our load paths libdir = File.dirname(__FILE__) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) # Top-level Middleman object module Middleman + WINDOWS = !!(RUBY_PLATFORM =~ /(mingw|bccwin|wince|mswin32)/i) + JRUBY = !!(RbConfig::CONFIG["RUBY_INSTALL_NAME"] =~ /^jruby/i) + # Auto-load modules on-demand autoload :Base, "middleman/base" autoload :Cache, "middleman/cache" - autoload :Builder, "middleman/builder" - autoload :CLI, "middleman/cli" autoload :Templates, "middleman/templates" autoload :Guard, "middleman/guard" + module Cli + autoload :Base, "middleman/cli" + autoload :Build, "middleman/cli/build" + autoload :Init, "middleman/cli/init" + autoload :Server, "middleman/cli/server" + end + # Custom Renderers module Renderers autoload :Haml, "middleman/renderers/haml" @@ -156,12 +166,6 @@ module Middleman class << self - # Where to look for custom templates - # @returns [String] - def templates_path - File.join(File.expand_path("~/"), ".middleman") - end - # Automatically load extensions from available RubyGems # which contain the EXTENSION_FILE # @@ -225,10 +229,13 @@ module Middleman app_class = options[:app] ||= ::Middleman.server.inst opts[:app] = app_class - opts[:server] = 'thin' - - # require "thin" - # ::Thin::Logging.silent = true if options[:debug] != "true" + opts[:server] = if ::Middleman::JRUBY + 'webrick' # Maybe Kirk? + else + require "thin" + ::Thin::Logging.silent = !options[:is_logging] + 'thin' + end server = ::Rack::Server.new(opts) server.start diff --git a/lib/middleman/base.rb b/lib/middleman/base.rb index 27ca2c88..1b68f182 100644 --- a/lib/middleman/base.rb +++ b/lib/middleman/base.rb @@ -91,6 +91,7 @@ class Middleman::Base # Use Rack middleware # # @param [Class] Middleware + # @return [void] def use(middleware, *args, &block) @middleware ||= [] @middleware << [middleware, args, block] @@ -99,6 +100,7 @@ class Middleman::Base # Add Rack App mapped to specific path # # @param [String] Path to map + # @return [void] def map(map, &block) @mappings ||= [] @mappings << [map, block] @@ -106,6 +108,7 @@ class Middleman::Base # 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? @@ -123,6 +126,7 @@ class Middleman::Base # # @param [Symbol] Unique key name # @param Default value + # @return [void] def set(key, value) @defaults ||= {} @defaults[key] = value @@ -133,6 +137,7 @@ class Middleman::Base # # @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) @@ -263,6 +268,10 @@ class Middleman::Base @_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 }) @@ -295,14 +304,7 @@ class Middleman::Base def self.cache @_cache ||= ::Middleman::Cache.new end - - # Cache accessor for instance, simply forwards to class - # - # @private - # @return [Middleman::Cache] The cache - def cache - self.class.cache - end + delegate :cache, :to => :"self.class" # Rack env attr :env @@ -423,7 +425,7 @@ class Middleman::Base # Expand a path to include the index file if it's a directory # # @private - # @param [String] Request path + # @param [String] path Request path # @return [String] Path with index file if necessary def full_path(path) cache.fetch(:full_path, path) do @@ -437,8 +439,9 @@ class Middleman::Base # Add a new mime-type for a specific extension # - # @param [Symbol] File extension - # @param [String] Mime type + # @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] == ?. @@ -455,24 +458,11 @@ protected @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 + delegate :helpers, :use, :map, :to => :"self.class" # Immediately send static file # - # @param [String] File to send + # @param [String] path File to send def send_file(path) extension = File.extname(path) matched_mime = mime_type(extension) @@ -488,7 +478,9 @@ protected # Set the content type for the current request # - # @param [String] Content type + # @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 diff --git a/lib/middleman/cli.rb b/lib/middleman/cli.rb index 5e2f7c69..f5160ace 100644 --- a/lib/middleman/cli.rb +++ b/lib/middleman/cli.rb @@ -1,123 +1,47 @@ require 'thor' +require "thor/group" -module Middleman - class CLI < Thor - include Thor::Actions - check_unknown_options! - default_task :server - - class_option "help", - :type => :boolean, - :default => false, - :aliases => "-h" - def initialize(*) - super - help_check if options[:help] - end - - desc "init NAME [options]", "Create new project NAME" - available_templates = Middleman::Templates.registered.keys.join(", ") - method_option "template", - :aliases => "-T", - :default => "default", - :desc => "Use a project template: #{available_templates}" - method_option "css_dir", - :default => "stylesheets", - :desc => 'The path to the css files' - method_option "js_dir", - :default => "javascripts", - :desc => 'The path to the javascript files' - method_option "images_dir", - :default => "images", - :desc => 'The path to the image files' - method_option "rack", - :type => :boolean, - :default => false, - :desc => 'Include a config.ru file' - method_option "bundler", - :type => :boolean, - :default => false, - :desc => 'Create a Gemfile and use Bundler to manage gems' - def init(name) - key = options[:template].to_sym - unless Middleman::Templates.registered.has_key?(key) - raise Thor::Error.new "Unknown project template '#{key}'" - end - - thor_group = Middleman::Templates.registered[key] - thor_group.new([name], options).invoke_all - end - - desc "server [options]", "Start the preview server" - method_option "environment", - :aliases => "-e", - :default => ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development', - :desc => "The environment Middleman will run under" - method_option :host, - :type => :string, - :aliases => "-h", - # :required => true, - :default => "0.0.0.0", - :desc => "Bind to HOST address" - method_option "port", - :aliases => "-p", - :default => "4567", - :desc => "The port Middleman will listen on" - method_option "debug", - :type => :boolean, - :default => false, - :desc => 'Print debug messages' - def server - params = { - :port => options["port"], - :host => options["host"], - :environment => options["environment"], - :debug => options["debug"] - } - - puts "== The Middleman is loading" - Middleman::Guard.start(params) - end - - desc "build", "Builds the static site for deployment" - method_option :relative, - :type => :boolean, - :aliases => "-r", - :default => false, - :desc => 'Force relative urls' - method_option :clean, - :type => :boolean, - :aliases => "-c", - :default => false, - :desc => 'Removes orpahand files or directories from build' - method_option :glob, - :type => :string, - :aliases => "-g", - :default => nil, - :desc => 'Build a subset of the project' - def build - thor_group = Middleman::Builder.new([], options).invoke_all - end - - desc "migrate", "Migrates an older project to the 2.0 structure" - def migrate - return if File.exists?("source") - `mv public source` - `cp -R views/* source/` - `rm -rf views` - end - +# CLI Module +module Middleman::Cli + + class Base < Thor desc "version", "Show version" def version require 'middleman/version' say "Middleman #{Middleman::VERSION}" end - private - - def help_check - help self.class.send(:retrieve_task_name, ARGV.dup) - exit 0 + def help(meth = nil, subcommand = false) + if meth && !self.respond_to?(meth) + klass, task = Thor::Util.find_class_and_task_by_namespace("#{meth}:#{meth}") + klass.start(["-h", task].compact, :shell => self.shell) + else + list = [] + Thor::Util.thor_classes_in(Middleman::Cli).each do |klass| + list += klass.printable_tasks(false) + end + list.sort!{ |a,b| a[0] <=> b[0] } + + shell.say "Tasks:" + shell.print_table(list, :ident => 2, :truncate => true) + shell.say + end + end + + def method_missing(meth, *args) + meth = meth.to_s + + if self.class.map.has_key?(meth) + meth = self.class.map[meth] + end + + klass, task = Thor::Util.find_class_and_task_by_namespace("#{meth}:#{meth}") + args.unshift(task) if task + klass.start(args, :shell => self.shell) end end end + +require "middleman/cli/init" +require "middleman/cli/server" +require "middleman/cli/build" \ No newline at end of file diff --git a/lib/middleman/builder.rb b/lib/middleman/cli/build.rb similarity index 79% rename from lib/middleman/builder.rb rename to lib/middleman/cli/build.rb index f065e7db..6a09fa7b 100644 --- a/lib/middleman/builder.rb +++ b/lib/middleman/cli/build.rb @@ -1,11 +1,44 @@ -require "thor" -require "thor/group" +require "rack" require "rack/test" -require "find" -module Middleman - class Builder < Thor::Group +module Middleman::Cli + class Build < Thor include Thor::Actions + check_unknown_options! + + namespace :build + + desc "build [options]", "Builds the static site for deployment" + method_option :relative, + :type => :boolean, + :aliases => "-r", + :default => false, + :desc => 'Force relative urls' + method_option :clean, + :type => :boolean, + :aliases => "-c", + :default => false, + :desc => 'Removes orpahand files or directories from build' + method_option :glob, + :type => :string, + :aliases => "-g", + :default => nil, + :desc => 'Build a subset of the project' + def build + if options.has_key?("relative") && options["relative"] + self.class.shared_instance.activate :relative_assets + end + + self.class.shared_rack + + opts = {} + opts[:glob] = options["glob"] if options.has_key?("glob") + opts[:clean] = options["clean"] if options.has_key?("clean") + + action GlobAction.new(self, self.class.shared_instance, opts) + + self.class.shared_instance.run_hook :after_build, self + end class << self def shared_instance @@ -13,11 +46,11 @@ module Middleman set :environment, :build end end - + def shared_server @_shared_server ||= shared_instance.class end - + def shared_rack @_shared_rack ||= begin mock = ::Rack::MockSession.new(shared_server.to_rack_app) @@ -28,6 +61,8 @@ module Middleman end end + source_root(shared_instance.root) + # @private module ThorActions # Render a template to a file. @@ -36,12 +71,12 @@ module Middleman config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source - request_path = destination.sub(/^#{::Middleman::Builder.shared_instance.build_dir}/, "") + request_path = destination.sub(/^#{self.class.shared_instance.build_dir}/, "") begin - destination, request_path = ::Middleman::Builder.shared_instance.reroute_builder(destination, request_path) + destination, request_path = self.class.shared_instance.reroute_builder(destination, request_path) - response = ::Middleman::Builder.shared_rack.get(request_path.gsub(/\s/, "%20")) + response = self.class.shared_rack.get(request_path.gsub(/\s/, "%20")) create_file(destination, response.body, config) @@ -52,36 +87,8 @@ module Middleman end end end + include ThorActions - - class_option :relative, :type => :boolean, :aliases => "-r", :default => false, :desc => 'Override the config.rb file and force relative urls' - class_option :glob, :type => :string, :aliases => "-g", :default => nil, :desc => 'Build a subset of the project' - - def initialize(*args) - super - - if options.has_key?("relative") && options["relative"] - self.class.shared_instance.activate :relative_assets - end - end - - def source_paths - @source_paths ||= [ - self.class.shared_instance.root - ] - end - - def build_all_files - self.class.shared_rack - - opts = { } - opts[:glob] = options["glob"] if options.has_key?("glob") - opts[:clean] = options["clean"] if options.has_key?("clean") - - action GlobAction.new(self, self.class.shared_instance, opts) - - self.class.shared_instance.run_hook :after_build, self - end end # @private @@ -92,9 +99,9 @@ module Middleman @app = app source = @app.source @destination = @app.build_dir - + @source = File.expand_path(base.find_in_source_paths(source.to_s)) - + super(base, destination, config) end @@ -109,7 +116,7 @@ module Middleman end protected - + def clean! files = @cleaning_queue.select { |q| File.file? q } directories = @cleaning_queue.select { |q| File.directory? q } @@ -124,7 +131,7 @@ module Middleman base.remove_file d, :force => true if directory_empty? d end end - + def cleaning? @config.has_key?(:clean) && @config[:clean] end @@ -142,20 +149,20 @@ module Middleman end end if File.exist?(@destination) end - + def execute! sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css) - + paths = @app.sitemap.all_paths.sort do |a, b| a_ext = File.extname(a) b_ext = File.extname(b) - + a_idx = sort_order.index(a_ext) || 100 b_idx = sort_order.index(b_ext) || 100 - + a_idx <=> b_idx end - + paths.each do |path| file_source = path file_destination = File.join(given_destination, file_source.gsub(source, '.')) @@ -168,15 +175,17 @@ module Middleman elsif @app.sitemap.ignored?(file_source) next end - + if @config[:glob] next unless File.fnmatch(@config[:glob], file_source) end - + file_destination = base.tilt_template(file_source, file_destination, { :force => true }) @cleaning_queue.delete(file_destination) if cleaning? end end end -end + + Base.map({ "b" => "build" }) +end \ No newline at end of file diff --git a/lib/middleman/cli/init.rb b/lib/middleman/cli/init.rb new file mode 100644 index 00000000..1ba260d9 --- /dev/null +++ b/lib/middleman/cli/init.rb @@ -0,0 +1,47 @@ +module Middleman::Cli + class Init < Thor + check_unknown_options! + + namespace :init + + desc "init NAME [options]", "Create new project NAME" + available_templates = ::Middleman::Templates.registered.keys.join(", ") + # argument :name + method_option "template", + :aliases => "-T", + :default => "default", + :desc => "Use a project template: #{available_templates}" + method_option "css_dir", + :default => "stylesheets", + :desc => 'The path to the css files' + method_option "js_dir", + :default => "javascripts", + :desc => 'The path to the javascript files' + method_option "images_dir", + :default => "images", + :desc => 'The path to the image files' + method_option "rack", + :type => :boolean, + :default => false, + :desc => 'Include a config.ru file' + method_option "bundler", + :type => :boolean, + :default => false, + :desc => 'Create a Gemfile and use Bundler to manage gems' + def init(name) + key = options[:template].to_sym + unless ::Middleman::Templates.registered.has_key?(key) + raise Thor::Error.new "Unknown project template '#{key}'" + end + + thor_group = ::Middleman::Templates.registered[key] + thor_group.new([name], options).invoke_all + end + end + + Base.map({ + "i" => "init", + "new" => "init", + "n" => "init" + }) +end \ No newline at end of file diff --git a/lib/middleman/cli/server.rb b/lib/middleman/cli/server.rb new file mode 100644 index 00000000..b9befb45 --- /dev/null +++ b/lib/middleman/cli/server.rb @@ -0,0 +1,40 @@ +module Middleman::Cli + class Server < Thor + check_unknown_options! + + namespace :server + + desc "server [options]", "Start the preview server" + method_option "environment", + :aliases => "-e", + :default => ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development', + :desc => "The environment Middleman will run under" + method_option :host, + :type => :string, + :aliases => "-h", + # :required => true, + :default => "0.0.0.0", + :desc => "Bind to HOST address" + method_option "port", + :aliases => "-p", + :default => "4567", + :desc => "The port Middleman will listen on" + method_option "debug", + :type => :boolean, + :default => false, + :desc => 'Print debug messages' + def server + params = { + :port => options["port"], + :host => options["host"], + :environment => options["environment"], + :debug => options["debug"] + } + + puts "== The Middleman is loading" + Middleman::Guard.start(params) + end + end + + Base.map({ "s" => "server" }) +end \ No newline at end of file diff --git a/lib/middleman/core_extensions/assets.rb b/lib/middleman/core_extensions/assets.rb index 49c46f54..8db7e1ba 100644 --- a/lib/middleman/core_extensions/assets.rb +++ b/lib/middleman/core_extensions/assets.rb @@ -21,6 +21,7 @@ module Middleman::CoreExtensions::Assets # # @param [String] path The path (such as "photo.jpg") # @param [String] prefix The type prefix (such as "images") + # @return [String] The fully qualified asset url def asset_url(path, prefix="") # Don't touch assets which already have a full path path.include?("://") ? path : File.join(http_prefix, prefix, path) diff --git a/lib/middleman/core_extensions/builder.rb b/lib/middleman/core_extensions/builder.rb index 97076638..5ba2b160 100644 --- a/lib/middleman/core_extensions/builder.rb +++ b/lib/middleman/core_extensions/builder.rb @@ -1,13 +1,23 @@ +# Convenience methods to allow config.rb to talk to the Builder module Middleman::CoreExtensions::Builder + + # Extension registered class << self + # @private def registered(app) app.define_hook :after_build app.extend ClassMethods app.send :include, InstanceMethods + app.delegate :build_reroute, :to => :"self.class" end + alias :included :registered end + # Build Class Methods module ClassMethods + # Get a list of callbacks which can modify a files build path + # + # @return [Array] def build_reroute(&block) @build_rerouters ||= [] @build_rerouters << block if block_given? @@ -15,11 +25,13 @@ module Middleman::CoreExtensions::Builder end end + # Build Instance Methods module InstanceMethods - def build_reroute(&block) - self.class.build_reroute(&block) - end - + # Run through callbacks and get the new values + # + # @param [String] destination The current destination of the built file + # @param [String] request_path The current request path of the file + # @return [Array] The new values def reroute_builder(destination, request_path) result = [destination, request_path] diff --git a/lib/middleman/core_extensions/compass.rb b/lib/middleman/core_extensions/compass.rb index 2b48fb68..97f8b5f1 100644 --- a/lib/middleman/core_extensions/compass.rb +++ b/lib/middleman/core_extensions/compass.rb @@ -1,5 +1,10 @@ +# Forward the settings on config.rb and the result of registered extensions +# to Compass module Middleman::CoreExtensions::Compass + + # Extension registered class << self + # @private def registered(app) require "compass" @@ -47,7 +52,7 @@ module Middleman::CoreExtensions::Compass config.output_style = :nested end - # Required for relative paths + # Change paths when in build mode. Required for relative paths configure :build do ::Compass.configuration do |config| config.environment = :production diff --git a/lib/middleman/core_extensions/data.rb b/lib/middleman/core_extensions/data.rb index c6f5190b..2bccb7e2 100755 --- a/lib/middleman/core_extensions/data.rb +++ b/lib/middleman/core_extensions/data.rb @@ -1,9 +1,17 @@ +# Data formats require "yaml" require "active_support/json" + +# Using Thor's indifferent hash access require "thor" +# The data extension parses YAML and JSON files in the data/ directory +# and makes them available to config.rb, templates and extensions module Middleman::CoreExtensions::Data + + # Extension registered class << self + # @private def registered(app) app.set :data_dir, "data" app.send :include, InstanceMethods @@ -11,7 +19,10 @@ module Middleman::CoreExtensions::Data alias :included :registered end + # Instance methods module InstanceMethods + # Setup data files before anything else so they are available when + # parsing config.rb def initialize file_changed DataStore.matcher do |file| data.touch_file(file) if file.match(%r{^#{data_dir}\/}) @@ -24,43 +35,77 @@ module Middleman::CoreExtensions::Data super end + # The data object + # + # @return [DataStore] def data @data ||= DataStore.new(self) end # Makes a hash available on the data var with a given name + # + # @param [Symbol] name Name of the data, used for namespacing + # @param [Hash] content The content for this data + # @return [void] def data_content(name, content) DataStore.data_content(name, content) end # Makes a hash available on the data var with a given name + # + # @param [Symbol] name Name of the data, used for namespacing + # @return [void] def data_callback(name, &block) DataStore.data_callback(name, block) end end + # The core logic behind the data extension. class DataStore + + # Static methods class << self + + # The regex which tells Middleman which files are for data + # + # @return [Regexp] def matcher %r{[\w-]+\.(yml|yaml|json)$} end + # Store static data hash + # + # @param [Symbol] name Name of the data, used for namespacing + # @param [Hash] content The content for this data + # @return [void] def data_content(name, content) @@local_sources ||= {} @@local_sources[name.to_s] = content end + # Store callback-based data + # + # @param [Symbol] name Name of the data, used for namespacing + # @param [Proc] proc The callback which will return data + # @return [void] def data_callback(name, proc) @@callback_sources ||= {} @@callback_sources[name.to_s] = proc end end + # Setup data store + # + # @param [Middleman::Base] app The current instance of Middleman def initialize(app) @app = app @local_data = {} end + # Update the internal cache for a given file path + # + # @param [String] file The file to be re-parsed + # @return [void] def touch_file(file) file = File.expand_path(file, @app.root) extension = File.extname(file) @@ -74,16 +119,23 @@ module Middleman::CoreExtensions::Data return end - # @app.logger.debug :data_update, Time.now, basename if @app.logging? @local_data[basename] = recursively_enhance(data) end + # Remove a given file from the internal cache + # + # @param [String] file The file to be cleared + # @return [void] def remove_file(file) extension = File.extname(file) basename = File.basename(file, extension) @local_data.delete(basename) if @local_data.has_key?(basename) end + # Get a hash hash from either internal static data or a callback + # + # @param [String, Symbol] path The name of the data namespace + # @return [Hash, nil] def data_for_path(path) response = nil @@ -99,6 +151,10 @@ module Middleman::CoreExtensions::Data response end + # "Magically" find namespaces of data if they exist + # + # @param [String] path The namespace to search for + # @return [Hash, nil] def method_missing(path) if @local_data.has_key?(path.to_s) return @local_data[path.to_s] @@ -113,6 +169,9 @@ module Middleman::CoreExtensions::Data super end + # Convert all the data into a static hash + # + # @return [Hash] def to_h data = {} @@ -134,7 +193,12 @@ module Middleman::CoreExtensions::Data data end - private + private + # Recursively convert a normal Hash into a HashWithIndifferentAccess + # + # @private + # @param [Hash] data Normal hash + # @return [Thor::CoreExt::HashWithIndifferentAccess] def recursively_enhance(data) if data.is_a? Hash data = Thor::CoreExt::HashWithIndifferentAccess.new(data) diff --git a/lib/middleman/core_extensions/default_helpers.rb b/lib/middleman/core_extensions/default_helpers.rb index db5665f2..cc5f1a6a 100644 --- a/lib/middleman/core_extensions/default_helpers.rb +++ b/lib/middleman/core_extensions/default_helpers.rb @@ -9,8 +9,12 @@ require 'active_support/inflector' # humanize FileSet.glob_require('../vendor/padrino-helpers-0.10.5/lib/padrino-helpers/**/*.rb', __FILE__) +# Built-in helpers module Middleman::CoreExtensions::DefaultHelpers + + # Extension registered class << self + # @private def registered(app) app.helpers ::Padrino::Helpers::OutputHelpers app.helpers ::Padrino::Helpers::TagHelpers @@ -30,19 +34,34 @@ module Middleman::CoreExtensions::DefaultHelpers alias :included :registered end + # The helpers module Helpers + # Output a stylesheet link tag based on the current path + # + # @param [String] separator How to break up path in parts + # @return [String] def auto_stylesheet_link_tag(separator="/") auto_tag(:css, separator) do |path| stylesheet_link_tag path end end + # Output a javascript tag based on the current path + # + # @param [String] separator How to break up path in parts + # @return [String] def auto_javascript_include_tag(separator="/") auto_tag(:js, separator) do |path| javascript_include_tag path end end + # Output a stylesheet link tag based on the current path + # + # @param [Symbol] asset_ext The type of asset + # @param [String] separator How to break up path in parts + # @param [String] asset_dir Where to look for assets + # @return [void] def auto_tag(asset_ext, separator="/", asset_dir=nil) if asset_dir.nil? asset_dir = case asset_ext @@ -61,6 +80,9 @@ module Middleman::CoreExtensions::DefaultHelpers yield path if sitemap.exists?(File.join(asset_dir, path)) end + # Generate body css classes based on the current path + # + # @return [String] def page_classes path = current_path.dup path << index_file if path.match(%r{/$}) @@ -73,7 +95,11 @@ module Middleman::CoreExtensions::DefaultHelpers classes.join(' ') end - # Padrino's asset handling needs to pass through ours + # Get the path of a file of a given type + # + # @param [Symbol] kind The type of file + # @param [String] source The path to the file + # @return [String] def asset_path(kind, source) return source if source =~ /^http/ asset_folder = case kind diff --git a/lib/middleman/core_extensions/extensions.rb b/lib/middleman/core_extensions/extensions.rb index c0644ef6..204a7eab 100644 --- a/lib/middleman/core_extensions/extensions.rb +++ b/lib/middleman/core_extensions/extensions.rb @@ -31,9 +31,12 @@ # Using for version parsing require "rubygems" +# Namespace extensions module module Middleman::CoreExtensions::Extensions + # Register extension class << self + # @private def included(app) # app.set :default_extensions, [] app.define_hook :after_configuration @@ -43,18 +46,31 @@ module Middleman::CoreExtensions::Extensions app.extend ClassMethods app.send :include, InstanceMethods + app.delegate :configure, :to => :"self.class" end end + # Class methods module ClassMethods + # Add a callback to run in a specific environment + # + # @param [String, Symbol] env The environment to run in + # @return [void] def configure(env, &block) send("#{env}_config", &block) end + # Alias `extensions` to access registered extensions + # + # @return [Array] new_extensions Extension modules to register + # @return [Array :"self.class" + + # Before parsing config, load the data/ directory app.before_configuration do data_path = File.join(root, data_dir) Find.find(data_path) do |path| @@ -14,6 +20,7 @@ module Middleman::CoreExtensions::FileWatcher end if File.exists?(data_path) end + # After config, load everything else app.ready do Find.find(root) do |path| next if File.directory?(path) @@ -24,13 +31,22 @@ module Middleman::CoreExtensions::FileWatcher alias :included :registered end + # Class methods module ClassMethods + # Add callback to be run on file change + # + # @param [nil,Regexp] matcher A Regexp to match the change path against + # @return [Array] def file_changed(matcher=nil, &block) @_file_changed ||= [] @_file_changed << [block, matcher] if block_given? @_file_changed end + # Add callback to be run on file deletion + # + # @param [nil,Regexp] matcher A Regexp to match the deleted path against + # @return [Array] def file_deleted(matcher=nil, &block) @_file_deleted ||= [] @_file_deleted << [block, matcher] if block_given? @@ -38,11 +54,12 @@ module Middleman::CoreExtensions::FileWatcher end end + # Instance methods module InstanceMethods - def file_changed(*args, &block) - self.class.file_changed(*args, &block) - end - + # Notify callbacks that a file changed + # + # @param [String] path The file that changed + # @return [void] def file_did_change(path) file_changed.each do |callback, matcher| next if path.match(%r{^#{build_dir}/}) @@ -50,11 +67,11 @@ module Middleman::CoreExtensions::FileWatcher instance_exec(path, &callback) end end - - def file_deleted(*args) - self.class.file_deleted(*args) - end + # Notify callbacks that a file was deleted + # + # @param [String] path The file that was deleted + # @return [void] def file_did_delete(path) file_deleted.each do |callback, matcher| next if path.match(%r{^#{build_dir}/}) diff --git a/lib/middleman/core_extensions/front_matter.rb b/lib/middleman/core_extensions/front_matter.rb index 1e4d3ddd..1d0cc331 100644 --- a/lib/middleman/core_extensions/front_matter.rb +++ b/lib/middleman/core_extensions/front_matter.rb @@ -7,6 +7,7 @@ module Middleman::CoreExtensions::FrontMatter app.set :frontmatter_extensions, %w(.htm .html .php) app.extend ClassMethods app.send :include, InstanceMethods + app.delegate :frontmatter_changed, :to => :"self.class" end alias :included :registered end @@ -55,10 +56,6 @@ module Middleman::CoreExtensions::FrontMatter { :options => data } end end - - def frontmatter_changed(*args, &block) - self.class.frontmatter_changed(*args, &block) - end def frontmatter_did_change(path) frontmatter_changed.each do |callback, matcher| diff --git a/lib/middleman/core_extensions/rendering.rb b/lib/middleman/core_extensions/rendering.rb index 5f066f8b..ba6e564e 100644 --- a/lib/middleman/core_extensions/rendering.rb +++ b/lib/middleman/core_extensions/rendering.rb @@ -44,6 +44,8 @@ module Middleman::CoreExtensions::Rendering # the template don't persist for other templates. context = self.dup + @current_locs = locs, @current_opts = opts + while ::Tilt[path] content = render_individual_file(path, locs, opts, context) path = File.basename(path, File.extname(path)) @@ -60,6 +62,8 @@ module Middleman::CoreExtensions::Rendering ensure @current_engine = engine_was @content_blocks = nil + @current_locs = nil + @current_opts = nil end # Sinatra/Padrino render method signature. @@ -198,6 +202,12 @@ module Middleman::CoreExtensions::Rendering layout_path end + + def wrap_layout(layout_name, &block) + content = capture(&block) if block_given? + layout_path = locate_layout(layout_name, current_engine) + concat render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content } + end def current_engine @current_engine ||= nil diff --git a/lib/middleman/core_extensions/sprockets.rb b/lib/middleman/core_extensions/sprockets.rb index 56af19f6..32bee098 100644 --- a/lib/middleman/core_extensions/sprockets.rb +++ b/lib/middleman/core_extensions/sprockets.rb @@ -1,5 +1,4 @@ require 'pathname' -require 'rbconfig' require "sprockets" module Middleman::CoreExtensions::Sprockets diff --git a/lib/middleman/guard.rb b/lib/middleman/guard.rb index c4093365..6473c4ce 100644 --- a/lib/middleman/guard.rb +++ b/lib/middleman/guard.rb @@ -6,8 +6,7 @@ require "guard/guard" require "net/http" # Support forking on Windows -require "rbconfig" -require "win32/process" if RbConfig::CONFIG['host_os'].downcase =~ %r{mingw} +require "win32/process" if Middleman::WINDOWS module Middleman::Guard def self.start(options={}) @@ -21,7 +20,8 @@ module Middleman::Guard watch(%r{(.*)}) end }, - :watch_all_modifications => true + :watch_all_modifications => true, + :no_interactions => true }) end end @@ -45,32 +45,40 @@ module Guard # Start Middleman in a fork def start - @server_job = fork do - env = (@options[:environment] || "development").to_sym - is_logging = @options.has_key?(:debug) && (@options[:debug] == "true") - app = ::Middleman.server.inst do - set :environment, env - set :logging, is_logging - end - - require "thin" - ::Thin::Logging.silent = !is_logging - - app_rack = app.class.to_rack_app - - opts = @options.dup - opts[:app] = app_rack - puts "== The Middleman is standing watch on port #{opts[:port]||4567}" - ::Middleman.start_server(opts) + if ::Middleman::JRUBY + thread = Thread.new { bootup } + thread.join + else + @server_job = fork { bootup } end end + def bootup + env = (@options[:environment] || "development").to_sym + is_logging = @options.has_key?(:debug) && (@options[:debug] == "true") + app = ::Middleman.server.inst do + set :environment, env + set :logging, is_logging + end + + app_rack = app.class.to_rack_app + + opts = @options.dup + opts[:app] = app_rack + opts[:logging] = is_logging + puts "== The Middleman is standing watch on port #{opts[:port]||4567}" + ::Middleman.start_server(opts) + end + # Stop the forked Middleman def stop puts "== The Middleman is shutting down" - Process.kill("KILL", @server_job) - Process.wait @server_job - @server_job = nil + if ::Middleman::JRUBY + else + Process.kill(self.class.kill_command, @server_job) + Process.wait @server_job + @server_job = nil + end end # Simply stop, then start @@ -99,6 +107,10 @@ module Guard paths.each { |path| tell_server(:delete => path) } end + def self.kill_command + ::Middleman::WINDOWS ? 1 : :INT + end + private # Whether the passed files are config.rb or lib/*.rb # @param [Array] paths Array of paths to check @@ -119,7 +131,7 @@ module Guard end # Trap the interupt signal and shut down Guard (and thus the server) smoothly -trap(:INT) do +trap(::Guard::Middleman.kill_command) do ::Guard.stop - exit + # exit!(0) end \ No newline at end of file diff --git a/lib/middleman/step_definitions.rb b/lib/middleman/step_definitions.rb index 3849750a..0fa95192 100644 --- a/lib/middleman/step_definitions.rb +++ b/lib/middleman/step_definitions.rb @@ -1,7 +1,13 @@ MIDDLEMAN_ROOT_PATH = File.dirname(File.dirname(File.dirname(__FILE__))) MIDDLEMAN_BIN_PATH = File.join(MIDDLEMAN_ROOT_PATH, "bin") +ENV['PATH'] = "#{MIDDLEMAN_BIN_PATH}#{File::PATH_SEPARATOR}#{ENV['PATH']}" +require "aruba/cucumber" require "middleman/step_definitions/middleman_steps" require "middleman/step_definitions/builder_steps" require "middleman/step_definitions/generator_steps" -require "middleman/step_definitions/server_steps" \ No newline at end of file +require "middleman/step_definitions/server_steps" + +Before do + @aruba_timeout_seconds = 30 +end \ No newline at end of file diff --git a/lib/middleman/step_definitions/builder_steps.rb b/lib/middleman/step_definitions/builder_steps.rb index 7cf02733..16fad844 100644 --- a/lib/middleman/step_definitions/builder_steps.rb +++ b/lib/middleman/step_definitions/builder_steps.rb @@ -3,48 +3,40 @@ require 'fileutils' Given /^app "([^\"]*)" is using config "([^\"]*)"$/ do |path, config_name| target = File.join(PROJECT_ROOT_PATH, "fixtures", path) config_path = File.join(target, "config-#{config_name}.rb") - config_dest = File.join(target, "config.rb") + config_dest = File.join(current_dir, "config.rb") FileUtils.cp(config_path, config_dest) end -Given /^a built app at "([^\"]*)"$/ do |path| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path) +Given /^a fixture app "([^\"]*)"$/ do |path| + step %Q{a directory named "#{path}"} + + target_path = File.join(PROJECT_ROOT_PATH, "fixtures", path) + FileUtils.cp_r(target_path, current_dir) - build_target = File.join(target, "build") - FileUtils.rm_rf(build_target) - - build_cmd = File.join(MIDDLEMAN_BIN_PATH, "middleman build") - `cd #{target} && #{build_cmd}` + step %Q{I cd to "#{path}"} end -Then /^cleanup built app at "([^\"]*)"$/ do |path| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path, "build") - FileUtils.rm_rf(target) +Given /^a built app at "([^\"]*)"$/ do |path| + step %Q{a fixture app "#{path}"} + step %Q{I run `middleman build`} +end + +Given /^was successfully built$/ do + step %Q{a directory named "build" should exist} + step %Q{the exit status should be 0} +end + +Given /^a successfully built app at "([^\"]*)"$/ do |path| + step %Q{a built app at "#{path}"} + step %Q{was successfully built} end Given /^a built app at "([^\"]*)" with flags "([^\"]*)"$/ do |path, flags| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path) - build_cmd = File.join(MIDDLEMAN_BIN_PATH, "middleman build") - `cd #{target} && #{build_cmd} #{flags}` + step %Q{a fixture app "#{path}"} + step %Q{I run `middleman build #{flags}`} end -Then /^"([^\"]*)" should exist at "([^\"]*)"$/ do |target_file, path| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path, "build", target_file) - File.exists?(target).should be_true -end - -Then /^"([^\"]*)" should exist at "([^\"]*)" and include "([^\"]*)"$/ do |target_file, path, expected| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path, "build", target_file) - File.exists?(target).should be_true - File.read(target).should include(expected) -end - -Then /^"([^\"]*)" should not exist at "([^\"]*)"$/ do |target_file, path| - target = File.join(PROJECT_ROOT_PATH, "fixtures", path, "build", target_file) - File.exists?(target).should be_false -end - -Then /^the last exit code should be "([^\"]*)"$/ do |exit_code| - exit_code = exit_code.to_i - $?.exitstatus.should == exit_code +Given /^a successfully built app at "([^\"]*)" with flags "([^\"]*)"$/ do |path, flags| + step %Q{a built app at "#{path}" with flags "#{flags}"} + step %Q{was successfully built} end diff --git a/lib/middleman/step_definitions/middleman_steps.rb b/lib/middleman/step_definitions/middleman_steps.rb index b0c9ed77..8e284a95 100644 --- a/lib/middleman/step_definitions/middleman_steps.rb +++ b/lib/middleman/step_definitions/middleman_steps.rb @@ -1,3 +1,5 @@ +require "fileutils" + Given /^a project at "([^\"]*)"$/ do |dirname| @target = File.join(PROJECT_ROOT_PATH, "fixtures", dirname) end diff --git a/lib/middleman/templates.rb b/lib/middleman/templates.rb index 4825833e..0433312a 100644 --- a/lib/middleman/templates.rb +++ b/lib/middleman/templates.rb @@ -9,18 +9,20 @@ module Middleman::Templates class << self # Get list of registered templates and add new ones + # + # Middleman::Templates.register(:ext_name, klass) # # @param [Symbol] name The name of the template # @param [Class] klass The class to be executed for this template # @return [Hash] List of registered templates - def registered(*args) + def register(*args) @_template_mappings ||= {} @_template_mappings[args[0]] = args[1] if args.length == 2 @_template_mappings end # Middleman::Templates.register(name, klass) - alias :register :registered + alias :registered :register end # Base Template class. Handles basic options and paths. @@ -44,14 +46,20 @@ module Middleman::Templates # Output a config.ru file for Rack if --rack is passed class_option :rack, :type => :boolean, :default => false - def generate_rack + + # Write a Rack config.ru file for project + # @return [void] + def generate_rack! return unless options[:rack] template "shared/config.ru", File.join(location, "config.ru") end # Output a Gemfile file for Bundler if --bundler is passed class_option :bundler, :type => :boolean, :default => false - def generate_bundler + + # Write a Bundler Gemfile file for project + # @return [void] + def generate_bundler! return unless options[:bundler] template "shared/Gemfile.tt", File.join(location, "Gemfile") diff --git a/lib/middleman/templates/default.rb b/lib/middleman/templates/default.rb index 9931aa41..a810bdd9 100644 --- a/lib/middleman/templates/default.rb +++ b/lib/middleman/templates/default.rb @@ -2,12 +2,14 @@ class Middleman::Templates::Default < Middleman::Templates::Base # Template files are relative to this file + # @return [String] def self.source_root File.dirname(__FILE__) end # Actually output the files - def build_scaffold + # @return [void] + def build_scaffold! template "shared/config.tt", File.join(location, "config.rb") copy_file "default/source/index.html.erb", File.join(location, "source/index.html.erb") copy_file "default/source/layout.erb", File.join(location, "source/layout.erb") diff --git a/lib/middleman/templates/html5.rb b/lib/middleman/templates/html5.rb index 72f11f86..d7872cda 100644 --- a/lib/middleman/templates/html5.rb +++ b/lib/middleman/templates/html5.rb @@ -7,12 +7,14 @@ class Middleman::Templates::Html5 < Middleman::Templates::Base class_option :images_dir, :default => "img" # Templates are relative to this file + # @return [String] def self.source_root File.dirname(__FILE__) end # Output the files - def build_scaffold + # @return [void] + def build_scaffold! template "shared/config.tt", File.join(location, "config.rb") directory "html5/source", File.join(location, "source") empty_directory File.join(location, "source") diff --git a/lib/middleman/templates/local.rb b/lib/middleman/templates/local.rb index 1ea76ad3..5d367cd9 100644 --- a/lib/middleman/templates/local.rb +++ b/lib/middleman/templates/local.rb @@ -2,18 +2,20 @@ class Middleman::Templates::Local < Middleman::Templates::Base # Look for templates in ~/.middleman + # @return [String] def self.source_root - Middleman.templates_path + File.join(File.expand_path("~/"), ".middleman") end # Just copy from the template path - def build_scaffold + # @return [void] + def build_scaffold! directory options[:template].to_s, location end end # Iterate over the directories in the templates path and register each one. -Dir[File.join(Middleman.templates_path, "*")].each do |dir| +Dir[File.join(Middleman::Templates::Local.source_root, "*")].each do |dir| next unless File.directory?(dir) Middleman::Templates.register(File.basename(dir).to_sym, Middleman::Templates::Local) end diff --git a/lib/middleman/templates/mobile.rb b/lib/middleman/templates/mobile.rb index bc9e6e35..0b88f100 100644 --- a/lib/middleman/templates/mobile.rb +++ b/lib/middleman/templates/mobile.rb @@ -7,12 +7,14 @@ class Middleman::Templates::Mobile < Middleman::Templates::Base class_option :images_dir, :default => "img" # Template files are relative to this file + # @return [String] def self.source_root File.dirname(__FILE__) end # Output the files - def build_scaffold + # @return [void] + def build_scaffold! template "shared/config.tt", File.join(location, "config.rb") directory "mobile/source", File.join(location, "source") empty_directory File.join(location, "source") diff --git a/lib/middleman/version.rb b/lib/middleman/version.rb index 8678de25..551a41cf 100644 --- a/lib/middleman/version.rb +++ b/lib/middleman/version.rb @@ -2,8 +2,12 @@ require "rubygems" module Middleman - VERSION = "3.0.0.alpha.4" + # Current Version + # @return [String] + VERSION = "3.0.0.alpha.5" + # Parsed version for RubyGems # @private + # @return [String] GEM_VERSION = ::Gem::Version.create(VERSION) end diff --git a/middleman-x86-mingw32.gemspec b/middleman-x86-mingw32.gemspec index 80348d4f..8c39cf68 100644 --- a/middleman-x86-mingw32.gemspec +++ b/middleman-x86-mingw32.gemspec @@ -1,6 +1,4 @@ # -*- encoding: utf-8 -*- -require "rbconfig" - $:.push File.expand_path("../lib", __FILE__) require "middleman/version" @@ -26,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency("tilt", ["~> 1.3.1"]) s.add_dependency("i18n", ["~> 0.6.0"]) s.add_dependency("rack-test", ["~> 0.6.1"]) - s.add_dependency("uglifier", ["~> 1.1.0"]) + s.add_dependency("uglifier", ["~> 1.2.0"]) s.add_dependency("haml", ["~> 3.1.0"]) s.add_dependency("sass", ["~> 3.1.7"]) s.add_dependency("activesupport", ["~> 3.1.0"]) @@ -34,22 +32,25 @@ Gem::Specification.new do |s| s.add_dependency("coffee-script", ["~> 2.2.0"]) s.add_dependency("execjs", ["~> 1.2.7"]) s.add_dependency("sprockets", ["~> 2.1.2"]) - s.add_dependency("sprockets-sass", ["~> 0.3.0"]) - s.add_dependency("guard", ["~> 0.8.8"]) + s.add_dependency("sprockets-sass", ["~> 0.6.0"]) + s.add_dependency("guard", ["~> 0.9.1"]) + s.add_dependency("redcarpet", ["~> 2.0.0"]) s.add_dependency("eventmachine", ["1.0.0.beta.4.1"]) s.add_dependency("win32-process", ["~> 0.6.5"]) - s.add_dependency("rb-fchange") # Development and test s.add_development_dependency("slim") - s.add_development_dependency("maruku") + s.add_development_dependency("sinatra") s.add_development_dependency("coffee-filter", ["~> 0.1.1"]) - s.add_development_dependency("liquid", ["~> 2.2.0"]) + s.add_development_dependency("liquid", ["~> 2.2"]) s.add_development_dependency("cucumber", ["~> 1.1.0"]) + s.add_development_dependency("aruba") s.add_development_dependency("rake", ["~> 0.9.2"]) - s.add_development_dependency("rspec", ["~> 2.7.0"]) + s.add_development_dependency("rspec", ["~> 2.7"]) + s.add_development_dependency("rdoc", ["~> 3.9"]) + s.add_development_dependency("yard") s.add_development_dependency("jquery-rails") - s.add_development_dependency("bootstrap-rails") + s.add_development_dependency("bootstrap-rails", ["0.0.5"]) end diff --git a/middleman.gemspec b/middleman.gemspec index 685096da..238e06e5 100644 --- a/middleman.gemspec +++ b/middleman.gemspec @@ -1,6 +1,4 @@ # -*- encoding: utf-8 -*- -require "rbconfig" - $:.push File.expand_path("../lib", __FILE__) require "middleman/version" @@ -26,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency("tilt", ["~> 1.3.1"]) s.add_dependency("i18n", ["~> 0.6.0"]) s.add_dependency("rack-test", ["~> 0.6.1"]) - s.add_dependency("uglifier", ["~> 1.1.0"]) + s.add_dependency("uglifier", ["~> 1.2.0"]) s.add_dependency("haml", ["~> 3.1.0"]) s.add_dependency("sass", ["~> 3.1.7"]) s.add_dependency("activesupport", ["~> 3.1.0"]) @@ -34,22 +32,20 @@ Gem::Specification.new do |s| s.add_dependency("coffee-script", ["~> 2.2.0"]) s.add_dependency("execjs", ["~> 1.2.7"]) s.add_dependency("sprockets", ["~> 2.1.2"]) - s.add_dependency("sprockets-sass", ["~> 0.5.0"]) - s.add_dependency("guard", ["~> 0.8.8"]) + s.add_dependency("sprockets-sass", ["~> 0.6.0"]) + s.add_dependency("guard", ["~> 0.9.1"]) s.add_dependency("redcarpet", ["~> 2.0.0"]) - # OSX - s.add_dependency("rb-fsevent") - # Development and test s.add_development_dependency("slim") s.add_development_dependency("sinatra") s.add_development_dependency("coffee-filter", ["~> 0.1.1"]) - s.add_development_dependency("liquid", ["~> 2.2.0"]) + s.add_development_dependency("liquid", ["~> 2.2"]) s.add_development_dependency("cucumber", ["~> 1.1.0"]) + s.add_development_dependency("aruba") 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("rspec", ["~> 2.7"]) + s.add_development_dependency("rdoc", ["~> 3.9"]) s.add_development_dependency("yard") s.add_development_dependency("jquery-rails") s.add_development_dependency("bootstrap-rails", ["0.0.5"])