Experiment with Hamster

This commit is contained in:
Thomas Reynolds 2015-03-20 13:58:23 -07:00
parent 55c5a46440
commit 22ce56492f
9 changed files with 57 additions and 120 deletions

View file

@ -16,6 +16,6 @@ Source: <%= current_page.source_file[:full_path].sub(root + "/", "") %>
<% current_page.children.each do |p| %>
<% if p.data %>
Data: <%= p.data %>
Data: <%= p.data.inspect %>
<% end %>
<% end %>

View file

@ -16,6 +16,6 @@ Source: <%= current_page.source_file[:full_path].sub(root + "/", "") %>
<% current_page.children.each do |p| %>
<% if p.data %>
Data: <%= p.data %>
Data: <%= p.data.inspect %>
<% end %>
<% end %>

View file

@ -162,8 +162,8 @@ 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]
# Any way to cache this?
return ::Middleman::Util.recursively_enhance(@local_data[path.to_s])
else
result = data_for_path(path)
return result if result

View file

@ -83,7 +83,7 @@ module Middleman::CoreExtensions
# Get the frontmatter and plain content from a file
# @param [String] path
# @return [Array<Middleman::Util::HashWithIndifferentAccess, String>]
# @return [Array<Middleman::Util::IndifferentHash, String>]
Contract Pathname => [Hash, Maybe[String]]
def frontmatter_and_content(full_path)
data = {}

View file

@ -87,10 +87,9 @@ module Middleman
end
# Data about this resource, populated from frontmatter or extensions.
# @return [HashWithIndifferentAccess]
Contract IsA['Middleman::Util::HashWithIndifferentAccess']
# @return [IndifferentHash]
Contract IsA['Middleman::Util::IndifferentHash']
def data
# TODO: Should this really be a HashWithIndifferentAccess?
::Middleman::Util.recursively_enhance(metadata[:page])
end

View file

@ -1,9 +1,6 @@
# For instrumenting
require 'active_support/notifications'
# Indifferent hash access
require 'middleman-core/util/hash_with_indifferent_access'
# Core Pathname library used for traversal
require 'pathname'
@ -14,6 +11,9 @@ require 'rack/mime'
# DbC
require 'middleman-core/contracts'
# Immutable Data
require 'hamster'
# For URI templating
require 'addressable/uri'
require 'addressable/template'
@ -76,27 +76,48 @@ module Middleman
end
end
# Recursively convert a normal Hash into a HashWithIndifferentAccess
class IndifferentHash < ::Hamster::Hash
def get(key)
key?(key.to_s) ? super(key.to_s) : super(key.to_sym)
end
alias_method :method_missing, :get
end
# Recursively convert a normal Hash into a IndifferentHash
#
# @private
# @param [Hash] data Normal hash
# @return [Middleman::Util::HashWithIndifferentAccess]
FrozenDataStructure = Frozen[Or[HashWithIndifferentAccess, Array, String, TrueClass, FalseClass, Fixnum]]
Contract Maybe[Or[String, Array, Hash, HashWithIndifferentAccess]] => Maybe[FrozenDataStructure]
def recursively_enhance(data)
if data.is_a? HashWithIndifferentAccess
data
elsif data.is_a? Hash
HashWithIndifferentAccess.new(data)
elsif data.is_a? Array
data.map(&method(:recursively_enhance)).freeze
elsif data.frozen? || data.nil? || [::TrueClass, ::FalseClass, ::Fixnum].include?(data.class)
data
# @return [Middleman::Util::IndifferentHash]
FrozenDataStructure = Frozen[Or[IndifferentHash, Array, String, TrueClass, FalseClass, Fixnum]]
Contract Maybe[Or[String, Array, Hash, IndifferentHash]] => Maybe[FrozenDataStructure]
def recursively_enhance(obj)
case obj
when ::Hash
res = obj.map { |key, value| [recursively_enhance(key), recursively_enhance(value)] }
IndifferentHash.new(res)
when IndifferentHash
obj.map { |key, value| [recursively_enhance(key), recursively_enhance(value)] }
when ::Array
res = obj.map { |element| recursively_enhance(element) }
Hamster::Vector.new(res)
when ::SortedSet
# This clause must go before ::Set clause, since ::SortedSet is a ::Set.
res = obj.map { |element| recursively_enhance(element) }
Hamster::SortedSet.new(res)
when ::Set
res = obj.map { |element| recursively_enhance(element) }
Hamster::Set.new(res)
when Hamster::Vector, Hamster::Set, Hamster::SortedSet
obj.map { |element| recursively_enhance(element) }
when ::TrueClass, ::FalseClass, ::Fixnum, ::Symbol
obj
else
data.dup.freeze
obj.dup.freeze
end
end
# Normalize a path to not include a leading slash
# @param [String] path
# @return [String]

View file

@ -1,86 +0,0 @@
require 'middleman-core/contracts'
module Middleman
module Util
# A hash with indifferent access and magic predicates.
# Copied from Thor
#
# hash = Middleman::Util::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
#
# hash[:foo] #=> 'bar'
# hash['foo'] #=> 'bar'
# hash.foo? #=> true
#
class HashWithIndifferentAccess < ::Hash #:nodoc:
include Contracts
Contract Hash => Any
def initialize(hash={})
super()
hash.each do |key, val|
self[key] = Util.recursively_enhance(val)
end
freeze
end
def [](key)
super(convert_key(key))
end
def []=(key, value)
super(convert_key(key), value)
end
def delete(key)
super(convert_key(key))
end
def values_at(*indices)
indices.map { |key| self[convert_key(key)] }
end
def merge(other)
dup.merge!(other)
end
def merge!(other)
other.each do |key, value|
self[convert_key(key)] = value
end
self
end
# Convert to a Hash with String keys.
def to_hash
Hash.new(default).merge!(self)
end
protected
def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end
# Magic predicates. For instance:
#
# options.force? # => !!options['force']
# options.shebang # => "/usr/lib/local/ruby"
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
# rubocop:disable DoubleNegation
def method_missing(method, *args)
method = method.to_s
if method =~ /^(\w+)\?$/
if args.empty?
!!self[$1]
else
self[$1] == args.first
end
else
self[method]
end
end
end
end
end

View file

@ -52,4 +52,7 @@ Gem::Specification.new do |s|
# Testing
s.add_dependency('contracts', ['~> 0.9.0'])
# Immutability
s.add_dependency('hamster', ['~> 1.0'])
end

View file

@ -54,30 +54,30 @@ describe Middleman::Util do
end
describe "::recursively_enhance" do
it "returns HashWithIndifferentAccess if given one" do
input = Middleman::Util::HashWithIndifferentAccess.new({test: "subject"})
it "returns IndifferentHash if given one" do
input = Middleman::Util::IndifferentHash.new({test: "subject"})
subject = Middleman::Util.recursively_enhance input
expect( subject ).to be_a Middleman::Util::HashWithIndifferentAccess
expect( subject ).to be_a Middleman::Util::IndifferentHash
expect( subject.test ).to eq "subject"
end
it "returns HashWithIndifferentAccess if given a hash" do
it "returns IndifferentHash if given a hash" do
input = {test: "subject"}
subject = Middleman::Util.recursively_enhance input
expect( subject ).to be_a Middleman::Util::HashWithIndifferentAccess
expect( subject ).to be_a Middleman::Util::IndifferentHash
expect( subject.test ).to eq "subject"
end
it "returns Array with strings, or HashWithIndifferentAccess, true, false" do
indifferent_hash = Middleman::Util::HashWithIndifferentAccess.new({test: "subject"})
it "returns Array with strings, or IndifferentHash, true, false" do
indifferent_hash = Middleman::Util::IndifferentHash.new({test: "subject"})
regular_hash = {regular: "hash"}
input = [ indifferent_hash, regular_hash, true, false ]
subject = Middleman::Util.recursively_enhance input
expect( subject[0] ).to be_a Middleman::Util::HashWithIndifferentAccess
expect( subject[1] ).to be_a Middleman::Util::HashWithIndifferentAccess
expect( subject[0] ).to be_a Middleman::Util::IndifferentHash
expect( subject[1] ).to be_a Middleman::Util::IndifferentHash
expect( subject[1].regular ).to eq "hash"
expect( subject[2] ).to eq true
expect( subject[3] ).to eq false