Deep freeze IndifferentAccess.

This commit is contained in:
Thomas Reynolds 2014-07-14 13:19:34 -07:00
parent 332ce2bebc
commit 1f3e2043cb
9 changed files with 74 additions and 49 deletions

View file

@ -17,6 +17,16 @@ if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
end end
end end
class Frozen < CallableClass
def initialize(contract)
@contract = contract
end
def valid?(val)
val.frozen? && Contract.valid?(val, @contract)
end
end
class ArrayOf class ArrayOf
def initialize(contract) def initialize(contract)
@contract = contract.is_a?(String) ? IsA[contract] : contract @contract = contract.is_a?(String) ? IsA[contract] : contract
@ -128,6 +138,9 @@ else
class SetOf < Callable class SetOf < Callable
end end
class Frozen < Callable
end
end end
end end

View file

@ -97,11 +97,11 @@ module Middleman
path = data_path.to_s.split(File::SEPARATOR)[0..-2] path = data_path.to_s.split(File::SEPARATOR)[0..-2]
path.each do |dir| path.each do |dir|
data_branch[dir] ||= ::Middleman::Util.recursively_enhance({}) data_branch[dir] ||= {}
data_branch = data_branch[dir] data_branch = data_branch[dir]
end end
data_branch[basename] = data && ::Middleman::Util.recursively_enhance(data) data_branch[basename] = data
end end
# Remove a given file from the internal cache # Remove a given file from the internal cache
@ -132,14 +132,13 @@ module Middleman
# @return [Hash, nil] # @return [Hash, nil]
Contract Or[String, Symbol] => Maybe[Hash] Contract Or[String, Symbol] => Maybe[Hash]
def data_for_path(path) def data_for_path(path)
response = nil response = if store.key?(path.to_s)
store[path.to_s]
if store.key?(path.to_s)
response = store[path.to_s]
elsif callbacks.key?(path.to_s) elsif callbacks.key?(path.to_s)
response = callbacks[path.to_s].call callbacks[path.to_s].call
end end
response = ::Middleman::Util.recursively_enhance(response)
response response
end end
@ -149,10 +148,11 @@ module Middleman
# @return [Hash, nil] # @return [Hash, nil]
def method_missing(path) def method_missing(path)
if @local_data.key?(path.to_s) 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] return @local_data[path.to_s]
else else
result = data_for_path(path) result = data_for_path(path)
return ::Middleman::Util.recursively_enhance(result) if result return result if result
end end
super super

View file

@ -51,7 +51,7 @@ module Middleman
# Merge per-extension options from config # Merge per-extension options from config
extension = File.extname(path) extension = File.extname(path)
options = opts.dup.merge(options_for_ext(extension)) options = opts.merge(options_for_ext(extension))
options[:outvar] ||= '@_out_buf' options[:outvar] ||= '@_out_buf'
options.delete(:layout) options.delete(:layout)

View file

@ -17,7 +17,7 @@ module Middleman
# Start an instance of Middleman::Application # Start an instance of Middleman::Application
# @return [void] # @return [void]
def start(opts={}) def start(opts={})
@options = opts @options = opts.dup.freeze
@host = @options[:host] || '0.0.0.0' @host = @options[:host] || '0.0.0.0'
@port = @options[:port] || DEFAULT_PORT @port = @options[:port] || DEFAULT_PORT
@ -94,7 +94,7 @@ module Middleman
private private
def new_app def new_app
opts = @options.dup opts = @options
::Middleman::Logger.singleton( ::Middleman::Logger.singleton(
opts[:debug] ? 0 : 1, opts[:debug] ? 0 : 1,

View file

@ -79,7 +79,7 @@ module Middleman
Contract None => IsA['Middleman::Util::HashWithIndifferentAccess'] Contract None => IsA['Middleman::Util::HashWithIndifferentAccess']
def data def data
# TODO: Should this really be a HashWithIndifferentAccess? # TODO: Should this really be a HashWithIndifferentAccess?
::Middleman::Util.recursively_enhance(metadata[:page]).freeze ::Middleman::Util.recursively_enhance(metadata[:page])
end end
# Options about how this resource is rendered, such as its :layout, # Options about how this resource is rendered, such as its :layout,

View file

@ -30,10 +30,10 @@ module Middleman
# @param [Middleman::Application] app # @param [Middleman::Application] app
# @param [Hash] locs # @param [Hash] locs
# @param [Hash] opts # @param [Hash] opts
def initialize(app, locs={}, opts={}) def initialize(app, locs={}.freeze, opts={}.freeze)
@app = app @app = app
@locs = locs.dup.freeze @locs = locs
@opts = opts.dup.freeze @opts = opts
end end
# Return the current buffer to the caller and clear the value internally. # Return the current buffer to the caller and clear the value internally.

View file

@ -31,16 +31,19 @@ module Middleman
Contract Hash, Hash => String Contract Hash, Hash => String
def render(locs={}, opts={}) def render(locs={}, opts={})
path = @path.dup path = @path.dup
locals = locs.dup.freeze
options = opts.dup
extension = File.extname(path) extension = File.extname(path)
engine = extension[1..-1].to_sym engine = extension[1..-1].to_sym
if defined?(::I18n) if defined?(::I18n)
old_locale = ::I18n.locale old_locale = ::I18n.locale
::I18n.locale = opts[:lang] if opts[:lang] ::I18n.locale = options[:lang] if options[:lang]
end end
# Sandboxed class for template eval # 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 # TODO: Only for HAML files
context.init_haml_helpers if context.respond_to?(:init_haml_helpers) context.init_haml_helpers if context.respond_to?(:init_haml_helpers)
@ -50,10 +53,10 @@ module Middleman
content = nil content = nil
while ::Tilt[path] while ::Tilt[path]
begin begin
opts[:template_body] = content if content options[:template_body] = content if content
content_renderer = ::Middleman::FileRenderer.new(@app, path) 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)) path = File.basename(path, File.extname(path))
rescue LocalJumpError rescue LocalJumpError
@ -62,9 +65,9 @@ module Middleman
end end
# If we need a layout and have a layout, use it # 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) 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 end
# Return result # Return result

View file

@ -15,11 +15,11 @@ require 'rack/mime'
require 'middleman-core/contracts' require 'middleman-core/contracts'
module Middleman module Middleman
module Util module Util
extend self
include Contracts include Contracts
module_function
# Whether the source file is binary. # Whether the source file is binary.
# #
# @param [String] filename The file to check. # @param [String] filename The file to check.
@ -74,32 +74,16 @@ module Middleman
# @private # @private
# @param [Hash] data Normal hash # @param [Hash] data Normal hash
# @return [Middleman::Util::HashWithIndifferentAccess] # @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) def recursively_enhance(data)
if data.is_a? Hash if data.is_a? HashWithIndifferentAccess
enhanced = ::Middleman::Util::HashWithIndifferentAccess.new(data) data
elsif data.is_a? Hash
enhanced.each do |key, val| HashWithIndifferentAccess.new(data)
enhanced[key] = if val.is_a?(Hash) || val.is_a?(Array)
recursively_enhance(val)
else
val
end
end
enhanced
elsif data.is_a? Array elsif data.is_a? Array
enhanced = data.dup data.map(&method(:recursively_enhance))
enhanced.each_with_index do |val, i|
enhanced[i] = if val.is_a?(Hash) || val.is_a?(Array)
recursively_enhance(val)
else else
val nil
end
end
enhanced
end end
end end

View file

@ -1,3 +1,5 @@
require 'middleman-core/contracts'
module Middleman module Middleman
module Util module Util
# A hash with indifferent access and magic predicates. # A hash with indifferent access and magic predicates.
@ -10,11 +12,17 @@ module Middleman
# hash.foo? #=> true # hash.foo? #=> true
# #
class HashWithIndifferentAccess < ::Hash #:nodoc: class HashWithIndifferentAccess < ::Hash #:nodoc:
include Contracts
Contract Hash => Any
def initialize(hash={}) def initialize(hash={})
super() super()
hash.each do |key, value|
self[convert_key(key)] = value hash.each do |key, val|
self[key] = recursively_enhance(val)
end end
freeze
end end
def [](key) def [](key)
@ -73,6 +81,23 @@ module Middleman
self[method] self[method]
end end
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 end
end end