diff --git a/middleman-core/lib/middleman-core/contracts.rb b/middleman-core/lib/middleman-core/contracts.rb index 2f13bc1c..5fd05e11 100644 --- a/middleman-core/lib/middleman-core/contracts.rb +++ b/middleman-core/lib/middleman-core/contracts.rb @@ -17,6 +17,16 @@ if ENV['TEST'] || ENV['CONTRACTS'] == 'true' end end + class Frozen < CallableClass + def initialize(contract) + @contract = contract + end + + def valid?(val) + val.frozen? && Contract.valid?(val, @contract) + end + end + class ArrayOf def initialize(contract) @contract = contract.is_a?(String) ? IsA[contract] : contract @@ -128,6 +138,9 @@ else class SetOf < Callable end + + class Frozen < Callable + end 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 95fabdcd..e5824362 100644 --- a/middleman-core/lib/middleman-core/core_extensions/data.rb +++ b/middleman-core/lib/middleman-core/core_extensions/data.rb @@ -97,11 +97,11 @@ module Middleman path = data_path.to_s.split(File::SEPARATOR)[0..-2] path.each do |dir| - data_branch[dir] ||= ::Middleman::Util.recursively_enhance({}) + data_branch[dir] ||= {} data_branch = data_branch[dir] end - data_branch[basename] = data && ::Middleman::Util.recursively_enhance(data) + data_branch[basename] = data end # Remove a given file from the internal cache @@ -132,14 +132,13 @@ module Middleman # @return [Hash, nil] Contract Or[String, Symbol] => Maybe[Hash] def data_for_path(path) - response = nil - - if store.key?(path.to_s) - response = store[path.to_s] + response = if store.key?(path.to_s) + store[path.to_s] elsif callbacks.key?(path.to_s) - response = callbacks[path.to_s].call + callbacks[path.to_s].call end + response = ::Middleman::Util.recursively_enhance(response) response end @@ -149,10 +148,11 @@ module Middleman # @return [Hash, nil] def method_missing(path) if @local_data.key?(path.to_s) + @local_data[path.to_s] = ::Middleman::Util.recursively_enhance(@local_data[path.to_s]) return @local_data[path.to_s] else result = data_for_path(path) - return ::Middleman::Util.recursively_enhance(result) if result + return result if result end super diff --git a/middleman-core/lib/middleman-core/file_renderer.rb b/middleman-core/lib/middleman-core/file_renderer.rb index 6e43d712..a97eae7c 100644 --- a/middleman-core/lib/middleman-core/file_renderer.rb +++ b/middleman-core/lib/middleman-core/file_renderer.rb @@ -51,7 +51,7 @@ module Middleman # Merge per-extension options from config extension = File.extname(path) - options = opts.dup.merge(options_for_ext(extension)) + options = opts.merge(options_for_ext(extension)) options[:outvar] ||= '@_out_buf' options.delete(:layout) diff --git a/middleman-core/lib/middleman-core/preview_server.rb b/middleman-core/lib/middleman-core/preview_server.rb index c0d8f7c2..82b65acc 100644 --- a/middleman-core/lib/middleman-core/preview_server.rb +++ b/middleman-core/lib/middleman-core/preview_server.rb @@ -17,7 +17,7 @@ module Middleman # Start an instance of Middleman::Application # @return [void] def start(opts={}) - @options = opts + @options = opts.dup.freeze @host = @options[:host] || '0.0.0.0' @port = @options[:port] || DEFAULT_PORT @@ -94,7 +94,7 @@ module Middleman private def new_app - opts = @options.dup + opts = @options ::Middleman::Logger.singleton( opts[:debug] ? 0 : 1, diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index add3919d..5f9ecc67 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -79,7 +79,7 @@ module Middleman Contract None => IsA['Middleman::Util::HashWithIndifferentAccess'] def data # TODO: Should this really be a HashWithIndifferentAccess? - ::Middleman::Util.recursively_enhance(metadata[:page]).freeze + ::Middleman::Util.recursively_enhance(metadata[:page]) end # Options about how this resource is rendered, such as its :layout, diff --git a/middleman-core/lib/middleman-core/template_context.rb b/middleman-core/lib/middleman-core/template_context.rb index 78becaf5..1cca89aa 100644 --- a/middleman-core/lib/middleman-core/template_context.rb +++ b/middleman-core/lib/middleman-core/template_context.rb @@ -30,10 +30,10 @@ module Middleman # @param [Middleman::Application] app # @param [Hash] locs # @param [Hash] opts - def initialize(app, locs={}, opts={}) + def initialize(app, locs={}.freeze, opts={}.freeze) @app = app - @locs = locs.dup.freeze - @opts = opts.dup.freeze + @locs = locs + @opts = opts end # Return the current buffer to the caller and clear the value internally. diff --git a/middleman-core/lib/middleman-core/template_renderer.rb b/middleman-core/lib/middleman-core/template_renderer.rb index 0eb2fc8f..72fa9db2 100644 --- a/middleman-core/lib/middleman-core/template_renderer.rb +++ b/middleman-core/lib/middleman-core/template_renderer.rb @@ -31,16 +31,19 @@ module Middleman Contract Hash, Hash => String def render(locs={}, opts={}) path = @path.dup + locals = locs.dup.freeze + options = opts.dup + extension = File.extname(path) engine = extension[1..-1].to_sym if defined?(::I18n) old_locale = ::I18n.locale - ::I18n.locale = opts[:lang] if opts[:lang] + ::I18n.locale = options[:lang] if options[:lang] end # Sandboxed class for template eval - context = @app.template_context_class.new(@app, locs, opts) + context = @app.template_context_class.new(@app, locals, options) # TODO: Only for HAML files context.init_haml_helpers if context.respond_to?(:init_haml_helpers) @@ -50,10 +53,10 @@ module Middleman content = nil while ::Tilt[path] begin - opts[:template_body] = content if content + options[:template_body] = content if content content_renderer = ::Middleman::FileRenderer.new(@app, path) - content = content_renderer.render(locs, opts, context) + content = content_renderer.render(locals, options, context) path = File.basename(path, File.extname(path)) rescue LocalJumpError @@ -62,9 +65,9 @@ module Middleman end # If we need a layout and have a layout, use it - if layout_path = fetch_layout(engine, opts) + if layout_path = fetch_layout(engine, options) layout_renderer = ::Middleman::FileRenderer.new(@app, layout_path) - content = layout_renderer.render(locs, opts, context) { content } + content = layout_renderer.render(locals, options, context) { content } end # Return result diff --git a/middleman-core/lib/middleman-core/util.rb b/middleman-core/lib/middleman-core/util.rb index 7556b504..5ce84523 100644 --- a/middleman-core/lib/middleman-core/util.rb +++ b/middleman-core/lib/middleman-core/util.rb @@ -15,11 +15,11 @@ require 'rack/mime' require 'middleman-core/contracts' module Middleman - module Util - extend self include Contracts + module_function + # Whether the source file is binary. # # @param [String] filename The file to check. @@ -74,32 +74,16 @@ module Middleman # @private # @param [Hash] data Normal hash # @return [Middleman::Util::HashWithIndifferentAccess] - Contract Or[Hash, Array] => Or[HashWithIndifferentAccess, Array] + Contract Maybe[Or[Array, Hash, HashWithIndifferentAccess]] => Maybe[Frozen[Or[HashWithIndifferentAccess, Array]]] def recursively_enhance(data) - if data.is_a? Hash - enhanced = ::Middleman::Util::HashWithIndifferentAccess.new(data) - - enhanced.each do |key, val| - enhanced[key] = if val.is_a?(Hash) || val.is_a?(Array) - recursively_enhance(val) - else - val - end - end - - enhanced + if data.is_a? HashWithIndifferentAccess + data + elsif data.is_a? Hash + HashWithIndifferentAccess.new(data) elsif data.is_a? Array - enhanced = data.dup - - enhanced.each_with_index do |val, i| - enhanced[i] = if val.is_a?(Hash) || val.is_a?(Array) - recursively_enhance(val) - else - val - end - end - - enhanced + data.map(&method(:recursively_enhance)) + else + nil end end diff --git a/middleman-core/lib/middleman-core/util/hash_with_indifferent_access.rb b/middleman-core/lib/middleman-core/util/hash_with_indifferent_access.rb index bae61b95..4f2254e9 100644 --- a/middleman-core/lib/middleman-core/util/hash_with_indifferent_access.rb +++ b/middleman-core/lib/middleman-core/util/hash_with_indifferent_access.rb @@ -1,3 +1,5 @@ +require 'middleman-core/contracts' + module Middleman module Util # A hash with indifferent access and magic predicates. @@ -10,11 +12,17 @@ module Middleman # hash.foo? #=> true # class HashWithIndifferentAccess < ::Hash #:nodoc: + include Contracts + + Contract Hash => Any def initialize(hash={}) super() - hash.each do |key, value| - self[convert_key(key)] = value + + hash.each do |key, val| + self[key] = recursively_enhance(val) end + + freeze end def [](key) @@ -73,6 +81,23 @@ module Middleman self[method] end end + + private + + Contract Any => Frozen[Any] + def recursively_enhance(data) + if data.is_a? HashWithIndifferentAccess + data + elsif data.is_a? Hash + self.class.new(data) + elsif data.is_a? Array + data.map(&method(:recursively_enhance)).freeze + elsif data.frozen? + data + else + data.dup.freeze + end + end end end end