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| %> <% current_page.children.each do |p| %>
<% if p.data %> <% if p.data %>
Data: <%= p.data %> Data: <%= p.data.inspect %>
<% end %> <% end %>
<% end %> <% end %>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,6 @@
# For instrumenting # For instrumenting
require 'active_support/notifications' require 'active_support/notifications'
# Indifferent hash access
require 'middleman-core/util/hash_with_indifferent_access'
# Core Pathname library used for traversal # Core Pathname library used for traversal
require 'pathname' require 'pathname'
@ -14,6 +11,9 @@ require 'rack/mime'
# DbC # DbC
require 'middleman-core/contracts' require 'middleman-core/contracts'
# Immutable Data
require 'hamster'
# For URI templating # For URI templating
require 'addressable/uri' require 'addressable/uri'
require 'addressable/template' require 'addressable/template'
@ -76,27 +76,48 @@ module Middleman
end end
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 # @private
# @param [Hash] data Normal hash # @param [Hash] data Normal hash
# @return [Middleman::Util::HashWithIndifferentAccess] # @return [Middleman::Util::IndifferentHash]
FrozenDataStructure = Frozen[Or[HashWithIndifferentAccess, Array, String, TrueClass, FalseClass, Fixnum]] FrozenDataStructure = Frozen[Or[IndifferentHash, Array, String, TrueClass, FalseClass, Fixnum]]
Contract Maybe[Or[String, Array, Hash, HashWithIndifferentAccess]] => Maybe[FrozenDataStructure] Contract Maybe[Or[String, Array, Hash, IndifferentHash]] => Maybe[FrozenDataStructure]
def recursively_enhance(data) def recursively_enhance(obj)
if data.is_a? HashWithIndifferentAccess case obj
data when ::Hash
elsif data.is_a? Hash res = obj.map { |key, value| [recursively_enhance(key), recursively_enhance(value)] }
HashWithIndifferentAccess.new(data) IndifferentHash.new(res)
elsif data.is_a? Array when IndifferentHash
data.map(&method(:recursively_enhance)).freeze obj.map { |key, value| [recursively_enhance(key), recursively_enhance(value)] }
elsif data.frozen? || data.nil? || [::TrueClass, ::FalseClass, ::Fixnum].include?(data.class) when ::Array
data 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 else
data.dup.freeze obj.dup.freeze
end end
end end
# Normalize a path to not include a leading slash # Normalize a path to not include a leading slash
# @param [String] path # @param [String] path
# @return [String] # @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 # Testing
s.add_dependency('contracts', ['~> 0.9.0']) s.add_dependency('contracts', ['~> 0.9.0'])
# Immutability
s.add_dependency('hamster', ['~> 1.0'])
end end

View file

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