diff --git a/README.md b/README.md index 61866dc9..e7cc690d 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,7 @@ A full set of in-depth instructional guides are available on the official websit http://middlemanapp.com/ -The community maintains its own collection of tips and tricks in the GitHub wiki: - - https://github.com/middleman/middleman/wiki - -Finally, up-to-date generated code documentation is available on RubyDoc: +Additionally, up-to-date generated code documentation is available on RubyDoc: http://rubydoc.info/github/middleman/middleman # Community diff --git a/Rakefile b/Rakefile index 630c02ff..1801ea15 100644 --- a/Rakefile +++ b/Rakefile @@ -87,6 +87,13 @@ task :test do end end +desc "Run specs for all middleman gems" +task :spec do + GEM_PATHS.each do |g| + sh "cd #{File.join(ROOT, g)} && #{Gem.ruby} -S rake spec" + end +end + # desc "Rune cane for all middleman gems" # task :cane do # GEM_PATHS.each do |g| @@ -95,4 +102,4 @@ end # end desc "Run tests for all middleman gems" -task :default => :test \ No newline at end of file +task :default => :test diff --git a/middleman-core/lib/middleman-core/cli/build.rb b/middleman-core/lib/middleman-core/cli/build.rb index 608f450e..5f6fc1c3 100644 --- a/middleman-core/lib/middleman-core/cli/build.rb +++ b/middleman-core/lib/middleman-core/cli/build.rb @@ -233,7 +233,7 @@ module Middleman::Cli paths = ::Middleman::Util.all_files_under(@destination) @cleaning_queue += paths.select do |path| - !path.to_s.match(/\/\./) || path.to_s.match(/\.htaccess|\.htpasswd/) + path.to_s !~ /\/\./ || path.to_s =~ /\.(htaccess|htpasswd)/ end end diff --git a/middleman-core/lib/middleman-core/core_extensions/data.rb b/middleman-core/lib/middleman-core/core_extensions/data.rb index bc2778af..026e884f 100644 --- a/middleman-core/lib/middleman-core/core_extensions/data.rb +++ b/middleman-core/lib/middleman-core/core_extensions/data.rb @@ -24,12 +24,12 @@ module Middleman # Setup data files before anything else so they are available when # parsing config.rb def initialize - files.changed DataStore.matcher do |file| - data.touch_file(file) if file.match(%r{^#{config[:data_dir]}\/}) + self.files.changed DataStore.matcher do |file| + self.data.touch_file(file) if file.start_with?("#{config[data_dir]}/") end - files.deleted DataStore.matcher do |file| - data.remove_file(file) if file.match(%r{^#{config[data_dir]}\/}) + self.files.deleted DataStore.matcher do |file| + self.data.remove_file(file) if file.start_with?("#{config[data_dir]}/") end super 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 075010af..60b368a9 100644 --- a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb +++ b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb @@ -145,7 +145,8 @@ module Middleman # @param [Pathname] path # @return [Boolean] def ignored?(path) - IGNORE_LIST.any? { |r| path.to_s.match(r) } + path = path.to_s + IGNORE_LIST.any? { |r| path =~ r } end # Notify callbacks for a file given an array of callbacks diff --git a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb index d4cd009e..747302d7 100644 --- a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb @@ -1,4 +1,5 @@ require "active_support/core_ext/hash/keys" +require 'pathname' # Extensions namespace module Middleman::CoreExtensions @@ -130,7 +131,12 @@ module Middleman::CoreExtensions # @param [String] path # @return [Array] def frontmatter_and_content(path) - full_path = File.expand_path(File.join(@app.source_dir, path)) + full_path = if Pathname(path).relative? + File.join(@app.source_dir, path) + else + path + end + data = {} content = nil @@ -158,7 +164,7 @@ module Middleman::CoreExtensions end def normalize_path(path) - path.sub(@app.source_dir, "").sub(/^\//, "") + path.sub(%r{^#{@app.source_dir}\/}, "") end # Update the main sitemap resource list diff --git a/middleman-core/lib/middleman-core/core_extensions/rendering.rb b/middleman-core/lib/middleman-core/core_extensions/rendering.rb index 7962afe2..aaca0be6 100644 --- a/middleman-core/lib/middleman-core/core_extensions/rendering.rb +++ b/middleman-core/lib/middleman-core/core_extensions/rendering.rb @@ -246,15 +246,26 @@ module Middleman options = opts.merge(options_for_ext(extension)) options[:outvar] ||= '@_out_buf' + template_class = Tilt[path] + # Allow hooks to manipulate the template before render + self.class.callbacks_for_hook(:before_render).each do |callback| + newbody = callback.call(body, path, locs, template_class) + body = newbody if newbody # Allow the callback to return nil to skip it + end + # Read compiled template from disk or cache template = cache.fetch(:compiled_template, options, body) do ::Tilt.new(path, 1, options) { body } end # Render using Tilt - run_hook :before_render, template.data, template - content = template.render(context, locs, &block) - run_hook :after_render, content, template + content = template.render(context, path, locs, &block) + + # Allow hooks to manipulate the result after render + self.class.callbacks_for_hook(:after_render).each do |callback| + content = callback.call(content, locs, template_class) + end + return content ensure # Reset stored buffer @@ -408,7 +419,7 @@ module Middleman # Find the path by searching or using the cache request_path = request_path.to_s cache.fetch(:resolve_template, request_path, options) do - relative_path = request_path.sub(%r{^/}, "") + relative_path = Util.strip_leading_slash(request_path) on_disk_path = File.expand_path(relative_path, self.source_dir) # By default, any engine will do diff --git a/middleman-core/lib/middleman-core/preview_server.rb b/middleman-core/lib/middleman-core/preview_server.rb index b78b8761..41833415 100644 --- a/middleman-core/lib/middleman-core/preview_server.rb +++ b/middleman-core/lib/middleman-core/preview_server.rb @@ -194,7 +194,7 @@ module Middleman paths.any? do |path| match_against.any? do |matcher| - path.match(matcher) + path =~ matcher end end end diff --git a/middleman-core/lib/middleman-core/sitemap.rb b/middleman-core/lib/middleman-core/sitemap.rb index 341c1fd9..d3392f68 100644 --- a/middleman-core/lib/middleman-core/sitemap.rb +++ b/middleman-core/lib/middleman-core/sitemap.rb @@ -25,18 +25,18 @@ module Middleman # Setup callbacks which can exclude paths from the sitemap app.config.define_setting :ignored_sitemap_matchers, { # dotfiles and folders in the root - :root_dotfiles => proc { |file| file.match(%r{^\.}) }, + :root_dotfiles => proc { |file| file.start_with?('.') }, # Files starting with an dot, but not .htaccess :source_dotfiles => proc { |file| - file.match(%r{/\.}) && !file.match(%r{/\.htaccess|\.htpasswd}) + file =~ %r{/\.} && file !~ %r{/\.(htaccess|htpasswd)} }, # Files starting with an underscore, but not a double-underscore - :partials => proc { |file| file.match(%r{/_}) && !file.match(%r{/__}) }, + :partials => proc { |file| file =~ %r{/_} && file !~ %r{/__} }, :layout => proc { |file| - file.match(%r{^source/layout\.}) || file.match(%r{^source/layouts/}) + file.start_with?('source/layout.') || file.start_with?('source/layouts/') } }, 'Callbacks that can exclude paths from the sitemap' diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index a3dbc13a..5e3f3ea5 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -111,7 +111,7 @@ module Middleman blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } provides_metadata.inject(blank_metadata) do |result, (callback, matcher)| - next result if !matcher.nil? && !source_file.match(matcher) + next result if matcher && !source_file.match(matcher) metadata = callback.call(source_file) @@ -149,9 +149,9 @@ module Middleman @_cached_metadata[request_path] = provides_metadata_for_path.inject(blank_metadata) do |result, (callback, matcher)| case matcher when Regexp - next result unless request_path.match(matcher) + next result unless request_path =~ matcher when String - next result unless File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{request_path}") + next result unless File.fnmatch("/" + Util.strip_leading_slash(matcher), "/#{request_path}") end metadata = callback.call(request_path) diff --git a/middleman-core/lib/middleman-core/util.rb b/middleman-core/lib/middleman-core/util.rb index fba225ec..4328341e 100644 --- a/middleman-core/lib/middleman-core/util.rb +++ b/middleman-core/lib/middleman-core/util.rb @@ -10,7 +10,8 @@ require "thor" # Core Pathname library used for traversal require "pathname" -require "rack" +require "tilt" +require "rack/mime" module Middleman @@ -22,11 +23,19 @@ module Middleman # @return [Boolean] def self.binary?(filename) ext = File.extname(filename) + return true if ext == '.svgz' return false if Tilt.registered?(ext.sub('.','')) ext = ".#{ext}" unless ext.to_s[0] == ?. mime = ::Rack::Mime.mime_type(ext, nil) - return false unless mime + unless mime + binary_bytes = [0, 1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31] + s = File.read(filename, 4096) || '' + s.each_byte do |c| + return true if binary_bytes.include?(c) + end + return false + end return false if mime.start_with?('text/') return false if mime.include?('xml') return false if mime.include?('json') @@ -78,7 +87,13 @@ module Middleman # @return [String] def self.normalize_path(path) # The tr call works around a bug in Ruby's Unicode handling - path.sub(/^\//, "").tr('','') + path.sub(%r{^/}, "").tr('','') + end + + # This is a separate method from normalize_path in case we + # change how we normalize paths + def self.strip_leading_slash(path) + path.sub(%r{^/}, "") end # Extract the text of a Rack response as a string. diff --git a/middleman-core/middleman-core.gemspec b/middleman-core/middleman-core.gemspec index 643e79de..506f7137 100644 --- a/middleman-core/middleman-core.gemspec +++ b/middleman-core/middleman-core.gemspec @@ -26,7 +26,7 @@ Gem::Specification.new do |s| s.add_dependency("rack-test", ["~> 0.6.1"]) # CLI - s.add_dependency("thor", [">= 0.15.4", "~> 0.15"]) + s.add_dependency("thor", ["~> 0.15.4"]) # Helpers s.add_dependency("activesupport", ["~> 3.2.6"]) diff --git a/middleman-core/spec/middleman-core/binary_spec.rb b/middleman-core/spec/middleman-core/binary_spec.rb new file mode 100644 index 00000000..ad04efb7 --- /dev/null +++ b/middleman-core/spec/middleman-core/binary_spec.rb @@ -0,0 +1,15 @@ +require 'middleman-core/util' + +describe "Middleman::Util#binary?" do + %w(plain.txt unicode.txt unicode stars.svgz).each do |file| + it "recognizes #{file} as not binary" do + Middleman::Util.binary?(File.join(File.dirname(__FILE__), "binary_spec/#{file}")).should be_false + end + end + + %w(middleman.png middleman).each do |file| + it "recognizes #{file} as binary" do + Middleman::Util.binary?(File.join(File.dirname(__FILE__), "binary_spec/#{file}")).should be_true + end + end +end diff --git a/middleman-core/spec/middleman-core/binary_spec/middleman b/middleman-core/spec/middleman-core/binary_spec/middleman new file mode 100644 index 00000000..c685e564 Binary files /dev/null and b/middleman-core/spec/middleman-core/binary_spec/middleman differ diff --git a/middleman-core/spec/middleman-core/binary_spec/middleman.png b/middleman-core/spec/middleman-core/binary_spec/middleman.png new file mode 100644 index 00000000..c685e564 Binary files /dev/null and b/middleman-core/spec/middleman-core/binary_spec/middleman.png differ diff --git a/middleman-core/spec/middleman-core/binary_spec/plain.txt b/middleman-core/spec/middleman-core/binary_spec/plain.txt new file mode 100644 index 00000000..649adbeb --- /dev/null +++ b/middleman-core/spec/middleman-core/binary_spec/plain.txt @@ -0,0 +1 @@ +Some plain text diff --git a/middleman-core/spec/middleman-core/binary_spec/stars.svgz b/middleman-core/spec/middleman-core/binary_spec/stars.svgz new file mode 100644 index 00000000..a54ce7ff Binary files /dev/null and b/middleman-core/spec/middleman-core/binary_spec/stars.svgz differ diff --git a/middleman-core/spec/middleman-core/binary_spec/unicode b/middleman-core/spec/middleman-core/binary_spec/unicode new file mode 100644 index 00000000..13dc1361 --- /dev/null +++ b/middleman-core/spec/middleman-core/binary_spec/unicode @@ -0,0 +1 @@ +明日がある。 diff --git a/middleman-core/spec/middleman-core/binary_spec/unicode.txt b/middleman-core/spec/middleman-core/binary_spec/unicode.txt new file mode 100644 index 00000000..13dc1361 --- /dev/null +++ b/middleman-core/spec/middleman-core/binary_spec/unicode.txt @@ -0,0 +1 @@ +明日がある。 diff --git a/middleman-more/features/cache_buster.feature b/middleman-more/features/cache_buster.feature index 7c094f7f..11040ba5 100644 --- a/middleman-more/features/cache_buster.feature +++ b/middleman-more/features/cache_buster.feature @@ -25,3 +25,18 @@ Feature: Generate mtime-based query string for busting browser caches When I go to "/cache-buster.html" Then I should see "site.css?" Then I should see "blank.gif?" + + Scenario: Rendering css with the feature and relative_assets enabled + Given "relative_assets" feature is "enabled" + Given "cache_buster" feature is "enabled" + And the Server is running at "cache-buster-app" + When I go to "/stylesheets/relative_assets.css" + Then I should see "blank.gif?" + + Scenario: Rendering html with the feature and relative_assets enabled + Given "relative_assets" feature is "enabled" + Given "cache_buster" feature is "enabled" + And the Server is running at "cache-buster-app" + When I go to "/cache-buster.html" + Then I should see "site.css?" + Then I should see "blank.gif?" \ No newline at end of file diff --git a/middleman-more/features/helpers_form_tag.feature b/middleman-more/features/helpers_form_tag.feature new file mode 100644 index 00000000..643ca18a --- /dev/null +++ b/middleman-more/features/helpers_form_tag.feature @@ -0,0 +1,26 @@ +Feature: form_tag helper + + Scenario: form_tag produces relative links + Given a fixture app "indexable-app" + And an empty file named "config.rb" + And a file named "source/form_tag.html.erb" with: + """ + absolute: <% form_tag "/needs_index.html#absolute", :relative => true do %> + <% end %> + relative: <% form_tag "needs_index.html#relative", :relative => true do %> + <% end %> + """ + And a file named "source/form_tag/sub.html.erb" with: + """ + absolute: <% form_tag "/needs_index.html#absolute", :relative => true do %> + <% end %> + relative: <% form_tag "../needs_index.html#relative", :relative => true do %> + <% end %> + """ + And the Server is running at "indexable-app" + When I go to "/form_tag.html" + Then I should see 'action="needs_index.html#absolute"' + Then I should see 'action="needs_index.html#relative"' + When I go to "/form_tag/sub.html" + Then I should see 'action="../needs_index.html#absolute"' + Then I should see 'action="../needs_index.html#relative"' diff --git a/middleman-more/features/helpers_url_for.feature b/middleman-more/features/helpers_url_for.feature new file mode 100644 index 00000000..c2b5a5a8 --- /dev/null +++ b/middleman-more/features/helpers_url_for.feature @@ -0,0 +1,152 @@ +Feature: url_for helper + + Scenario: url_for produces relative links + Given a fixture app "indexable-app" + And an empty file named "config.rb" + And a file named "source/url_for.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html", :relative => true %> + relative: <%= url_for "needs_index.html", :relative => true %> + """ + And a file named "source/url_for/sub.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html", :relative => true %> + relative: <%= url_for "../needs_index.html", :relative => true %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for.html" + Then I should see 'absolute: needs_index.html' + Then I should see 'relative: needs_index.html' + When I go to "/url_for/sub.html" + Then I should see 'absolute: ../needs_index.html' + Then I should see 'relative: ../needs_index.html' + + Scenario: url_for relative works with strip_index_file + Given a fixture app "indexable-app" + And a file named "config.rb" with: + """ + set :relative_links, true + set :strip_index_file, true + helpers do + def menu_items(path='url_for.html') + sitemap.find_resource_by_destination_path(path).children + end + end + """ + And a file named "source/url_for.html.erb" with: + """ + <% menu_items.each do |item| %> + "<%= url_for(item.url) %>" + "<%= url_for(item) %>" + <% end %> + """ + And a file named "source/url_for/sub.html.erb" with: + """ + <% menu_items.each do |item| %> + "<%= url_for(item.url) %>" + "<%= url_for(item) %>" + <% end %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for.html" + Then I should see '"url_for/sub.html"' + Then I should not see "/url_for/sub.html" + When I go to "/url_for/sub.html" + Then I should see '"sub.html"' + Then I should not see "/url_for/sub.html" + + Scenario: url_for produces relative links when :relative_links is set to true + Given a fixture app "indexable-app" + And a file named "config.rb" with: + """ + set :relative_links, true + """ + And a file named "source/url_for.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html" %> + relative: <%= url_for "needs_index.html", :relative => false %> + unknown: <%= url_for "foo.html" %> + """ + And a file named "source/url_for/sub.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html" %> + relative: <%= url_for "../needs_index.html" %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for.html" + Then I should see 'absolute: needs_index.html' + Then I should see 'relative: /needs_index.html' + Then I should see 'unknown: foo.html' + When I go to "/url_for/sub.html" + Then I should see 'absolute: ../needs_index.html' + Then I should see 'relative: ../needs_index.html' + + Scenario: url_for knows about directory indexes + Given a fixture app "indexable-app" + And a file named "source/url_for.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html", :relative => true %> + relative: <%= url_for "needs_index.html", :relative => true %> + """ + And a file named "source/url_for/sub.html.erb" with: + """ + absolute: <%= url_for "/needs_index.html", :relative => true %> + relative: <%= url_for "../needs_index.html", :relative => true %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for/" + Then I should see 'absolute: ../needs_index/' + Then I should see 'relative: ../needs_index/' + When I go to "/url_for/sub/" + Then I should see 'absolute: ../../needs_index/' + Then I should see 'relative: ../../needs_index/' + + Scenario: url_for can take a Resource + Given a fixture app "indexable-app" + And a file named "source/url_for.html.erb" with: + """ + "<%= url_for sitemap.find_resource_by_path("/needs_index.html") %>" + """ + And the Server is running at "indexable-app" + When I go to "/url_for/" + Then I should see '"/needs_index/"' + + Scenario: Setting http_prefix + Given a fixture app "indexable-app" + And a file named "config.rb" with: + """ + set :http_prefix, "/foo" + """ + And a file named "source/url_for.html.erb" with: + """ + <%= url_for "/needs_index.html" %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for.html" + Then I should see '/foo/needs_index.html' + + Scenario: url_for preserves query string and anchor and isn't messed up by them + Given a fixture app "indexable-app" + And a file named "source/url_for.html.erb" with: + """ + Needs Index Anchor <%= url_for "/needs_index.html#foo" %> + Needs Index Query <%= url_for "/needs_index.html?foo" %> + Needs Index Query and Anchor <%= url_for "/needs_index.html?foo#foo" %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for/" + Then I should see 'Needs Index Anchor /needs_index/#foo' + Then I should see 'Needs Index Query /needs_index/?foo' + Then I should see 'Needs Index Query and Anchor /needs_index/?foo#foo' + + Scenario: url_for accepts a :query option that appends a query string + Given a fixture app "indexable-app" + And a file named "source/url_for.html.erb" with: + """ + Needs Index String <%= url_for "/needs_index.html", :query => "foo" %> + Needs Index Hash <%= url_for "/needs_index.html", :query => { :foo => :bar } %> + """ + And the Server is running at "indexable-app" + When I go to "/url_for/" + Then I should see 'Needs Index String /needs_index/?foo' + Then I should see 'Needs Index Hash /needs_index/?foo=bar' diff --git a/middleman-more/features/i18n_preview.feature b/middleman-more/features/i18n_preview.feature index 81360e23..bfc591ef 100644 --- a/middleman-more/features/i18n_preview.feature +++ b/middleman-more/features/i18n_preview.feature @@ -103,6 +103,51 @@ Feature: i18n Preview Then I should see "Como Esta?" When I go to "/spanish/hola.html" Then I should see "Hola World" + + Scenario: Running localize with a non-English mount config + Given a fixture app "i18n-test-app" + And a file named "config.rb" with: + """ + activate :i18n, :mount_at_root => :es + """ + Given the Server is running at "i18n-test-app" + When I go to "/en/index.html" + Then I should see "Howdy" + When I go to "/en/hello.html" + Then I should see "Hello World" + When I go to "/" + Then I should see "Como Esta?" + When I go to "/hola.html" + Then I should see "Hola World" + When I go to "/hello.html" + Then I should see "File Not Found" + When I go to "/es/index.html" + Then I should see "File Not Found" + When I go to "/es/hola.html" + Then I should see "File Not Found" + + Scenario: Running localize with a non-English lang subset + Given a fixture app "i18n-test-app" + And a file named "config.rb" with: + """ + activate :i18n, :langs => :es + """ + Given the Server is running at "i18n-test-app" + When I go to "/en/index.html" + Then I should see "File Not Found" + When I go to "/en/hello.html" + Then I should see "File Not Found" + When I go to "/" + Then I should see "Como Esta?" + When I go to "/hola.html" + Then I should see "Hola World" + When I go to "/hello.html" + Then I should see "File Not Found" + When I go to "/es/index.html" + Then I should see "File Not Found" + When I go to "/es/hola.html" + Then I should see "File Not Found" + Scenario: Running localize with the no mount config Given a fixture app "i18n-test-app" diff --git a/middleman-more/lib/middleman-more/core_extensions/default_helpers.rb b/middleman-more/lib/middleman-more/core_extensions/default_helpers.rb index 95ddae8a..ffc5c4e6 100644 --- a/middleman-more/lib/middleman-more/core_extensions/default_helpers.rb +++ b/middleman-more/lib/middleman-more/core_extensions/default_helpers.rb @@ -64,7 +64,7 @@ module Middleman # If the basename of the request as no extension, assume we are serving a # directory and join index_file to the path. path = File.join(asset_dir, current_path) - path = path.gsub(File.extname(path), ".#{asset_ext}") + path = path.sub(/#{File.extname(path)}$/, ".#{asset_ext}") yield path if sitemap.find_resource_by_path(path) end @@ -74,11 +74,11 @@ module Middleman # @return [String] def page_classes path = current_path.dup - path << index_file if path.match(%r{/$}) - path = path.gsub(%r{^/}, '') + path << index_file if path.end_with?('/') + path = Util.strip_leading_slash(path) classes = [] - parts = path.split('.')[0].split('/') + parts = path.split('.').first.split('/') parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') } classes.join(' ') @@ -90,22 +90,93 @@ module Middleman # @param [String] source The path to the file # @return [String] def asset_path(kind, source) - return source if source =~ /^http/ + return source if source.to_s.include?('//') asset_folder = case kind when :css then css_dir when :js then js_dir when :images then images_dir else kind.to_s end - source = source.to_s.gsub(/\s/, '') + source = source.to_s.tr(' ', '') ignore_extension = (kind == :images) # don't append extension - source << ".#{kind}" unless ignore_extension or source =~ /\.#{kind}/ - if source =~ %r{^/} # absolute path - asset_folder = "" - end + source << ".#{kind}" unless ignore_extension || source.end_with?(".#{kind}") + asset_folder = "" if source.start_with?('/') # absolute path + asset_url(source, asset_folder) end + # Given a source path (referenced either absolutely or relatively) + # or a Resource, this will produce the nice URL configured for that + # path, respecting :relative_links, directory indexes, etc. + def url_for(path_or_resource, options={}) + # Handle Resources and other things which define their own url method + url = path_or_resource.respond_to?(:url) ? path_or_resource.url : path_or_resource + + begin + uri = URI(url) + rescue URI::InvalidURIError + # Nothing we can do with it, it's not really a URI + return url + end + + relative = options.delete(:relative) + raise "Can't use the relative option with an external URL" if relative && uri.host + + # Allow people to turn on relative paths for all links with + # set :relative_links, true + # but still override on a case by case basis with the :relative parameter. + effective_relative = relative || false + effective_relative = true if relative.nil? && config[:relative_links] + + # Try to find a sitemap resource corresponding to the desired path + this_resource = current_resource # store in a local var to save work + if path_or_resource.is_a?(Sitemap::Resource) + resource = path_or_resource + resource_url = url + elsif this_resource && uri.path + # Handle relative urls + url_path = Pathname(uri.path) + current_source_dir = Pathname('/' + this_resource.path).dirname + url_path = current_source_dir.join(url_path) if url_path.relative? + resource = sitemap.find_resource_by_path(url_path.to_s) + resource_url = resource.url if resource + end + + if resource + # Switch to the relative path between this_resource and the given resource + # if we've been asked to. + if effective_relative + # Output urls relative to the destination path, not the source path + current_dir = Pathname('/' + this_resource.destination_path).dirname + relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s + + # Put back the trailing slash to avoid unnecessary Apache redirects + if resource_url.end_with?('/') && !relative_path.end_with?('/') + relative_path << '/' + end + + uri.path = relative_path + else + uri.path = resource_url + end + else + # If they explicitly asked for relative links but we can't find a resource... + raise "No resource exists at #{url}" if relative + end + + # Support a :query option that can be a string or hash + if query = options.delete(:query) + uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s + end + + # Support a :fragment or :anchor option just like Padrino + fragment = options.delete(:anchor) || options.delete(:fragment) + uri.fragment = fragment.to_s if fragment + + # Finally make the URL back into a string + uri.to_s + end + # Overload the regular link_to to be sitemap-aware - if you # reference a source path, either absolutely or relatively, # you'll get that resource's nice URL. Also, there is a @@ -123,75 +194,27 @@ module Middleman url_arg_index = block_given? ? 0 : 1 options_index = block_given? ? 1 : 2 + if block_given? && args.size > 2 + raise ArgumentError.new("Too many arguments to link_to(url, options={}, &block)") + end + if url = args[url_arg_index] options = args[options_index] || {} - relative = options.delete(:relative) - - # Handle Resources, which define their own url method - if url.respond_to? :url - url = args[url_arg_index] = url.url - end - - if url.include? '://' - raise "Can't use the relative option with an external URL" if relative - elsif current_resource - # Handle relative urls - current_source_dir = Pathname('/' + current_resource.path).dirname - - begin - uri = URI(url) - url_path = uri.path - rescue - end - - if url_path - path = Pathname(url_path) - url_path = current_source_dir.join(path).to_s if path.relative? - - resource = sitemap.find_resource_by_path(url_path) - - # Allow people to turn on relative paths for all links with config[:relative_links] = true - # but still override on a case by case basis with the :relative parameter. - effective_relative = relative || false - if relative.nil? && config[:relative_links] - effective_relative = true - end - - if resource - if effective_relative - resource_url = resource.url - - # Output urls relative to the destination path, not the source path - current_dir = Pathname('/' + current_resource.destination_path).dirname - new_url = Pathname(resource_url).relative_path_from(current_dir).to_s - - # Put back the trailing slash to avoid unnecessary Apache redirects - if resource_url.end_with?('/') && !new_url.end_with?('/') - new_url << '/' - end - else - new_url = resource.url - end - - uri.path = new_url - - args[url_arg_index] = uri.to_s - else - raise "No resource exists at #{url}" if relative - end - end - end - - # Support a :query option that can be a string or hash - if query = options.delete(:query) - uri = URI(args[url_arg_index]) - uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s - args[url_arg_index] = uri.to_s - end + raise ArgumentError.new("Options must be a hash") unless options.is_a?(Hash) + + # Transform the url through our magic url_for method + args[url_arg_index] = url_for(url, options) end super(*args, &block) end + + # Modified Padrino form_for that uses Middleman's url_for + # to transform the URL. + def form_tag(url, options={}, &block) + url = url_for(url, options) + super + end end end end diff --git a/middleman-more/lib/middleman-more/core_extensions/i18n.rb b/middleman-more/lib/middleman-more/core_extensions/i18n.rb index 1653a384..057a0789 100644 --- a/middleman-more/lib/middleman-more/core_extensions/i18n.rb +++ b/middleman-more/lib/middleman-more/core_extensions/i18n.rb @@ -28,6 +28,7 @@ module Middleman @app = app @locales_glob = File.join(app.locales_dir, "**", "*.{rb,yml,yaml}") + # File.fnmatch doesn't support brackets: {rb,yml,yaml} regex = @locales_glob.sub(/\./, '\.').sub(File.join("**", "*"), ".*").sub(/\//, '\/').sub("{rb,yml,yaml}", "rb|ya?ml") @locales_regex = %r{^#{regex}} @@ -43,7 +44,7 @@ module Middleman @mount_at_root = @options.has_key?(:mount_at_root) ? @options[:mount_at_root] : langs.first if !@app.build? - logger.info "== Locales: #{langs.join(", ")}" + logger.info "== Locales: #{langs.join(", ")} (Default #{@mount_at_root})" end # Don't output localizable files @@ -52,15 +53,20 @@ module Middleman @app.sitemap.provides_metadata_for_path do |url| if d = get_localization_data(url) lang, page_id = d - instance_vars = Proc.new { - ::I18n.locale = lang - @lang = lang - @page_id = page_id - } - { :blocks => [instance_vars] } else - {} + # Default to the @mount_at_root lang + page_id = nil + lang = @mount_at_root end + + instance_vars = Proc.new do + ::I18n.locale = lang + @lang = lang + @page_id = page_id + end + + locals = { :lang => lang, :page_id => page_id } + { :blocks => [instance_vars], :locals => locals } end @app.sitemap.register_resource_list_manipulator( @@ -73,13 +79,15 @@ module Middleman end def on_file_changed(file) - if @locales_regex.match(file) + if @locales_regex =~ file ::I18n.reload! end end def langs - @options[:langs] || begin + if @options[:langs] + Array(@options[:langs]).map(&:to_sym) + else Dir[File.join(@app.root, @locales_glob)].map { |file| File.basename(file).sub(/\.ya?ml$/, "").sub(/\.rb$/, "") }.sort.map(&:to_sym) diff --git a/middleman-more/lib/middleman-more/extensions/automatic_image_sizes.rb b/middleman-more/lib/middleman-more/extensions/automatic_image_sizes.rb index 00f48e03..da7e5a39 100644 --- a/middleman-more/lib/middleman-more/extensions/automatic_image_sizes.rb +++ b/middleman-more/lib/middleman-more/extensions/automatic_image_sizes.rb @@ -38,7 +38,7 @@ module Middleman params[:alt] ||= "" real_path = path - real_path = File.join(images_dir, real_path) unless real_path =~ %r{^/} + real_path = File.join(images_dir, real_path) unless real_path.start_with?('/') full_path = File.join(source_dir, real_path) if File.exists?(full_path) diff --git a/middleman-more/lib/middleman-more/extensions/relative_assets.rb b/middleman-more/lib/middleman-more/extensions/relative_assets.rb index 7e457cd4..0bb0a777 100644 --- a/middleman-more/lib/middleman-more/extensions/relative_assets.rb +++ b/middleman-more/lib/middleman-more/extensions/relative_assets.rb @@ -36,7 +36,7 @@ module Middleman path else current_dir = Pathname('/' + current_resource.destination_path) - Pathname(path).relative_path_from(current_dir.dirname) + Pathname(path).relative_path_from(current_dir.dirname).to_s end end end diff --git a/vendor.yml b/vendor.yml new file mode 100644 index 00000000..417247e2 --- /dev/null +++ b/vendor.yml @@ -0,0 +1,2 @@ +- fixtures +- templates