The main benefit of this is to add the ability to gracefully handle the case when data is not present.
222 lines
6.2 KiB
Ruby
222 lines
6.2 KiB
Ruby
module Middleman
|
|
module CoreExtensions
|
|
|
|
# The data extension parses YAML and JSON files in the data/ directory
|
|
# and makes them available to config.rb, templates and extensions
|
|
module Data
|
|
|
|
# Extension registered
|
|
class << self
|
|
# @private
|
|
def registered(app)
|
|
# Data formats
|
|
require "yaml"
|
|
require "active_support/json"
|
|
|
|
app.config.define_setting :data_dir, "data", "The directory data files are stored in"
|
|
app.send :include, InstanceMethods
|
|
end
|
|
alias :included :registered
|
|
end
|
|
|
|
# Instance methods
|
|
module InstanceMethods
|
|
# Setup data files before anything else so they are available when
|
|
# parsing config.rb
|
|
def initialize
|
|
self.files.changed DataStore.matcher do |file|
|
|
self.data.touch_file(file) if file.start_with?("#{config[:data_dir]}/")
|
|
end
|
|
|
|
self.files.deleted DataStore.matcher do |file|
|
|
self.data.remove_file(file) if file.start_with?("#{config[:data_dir]}/")
|
|
end
|
|
|
|
super
|
|
end
|
|
|
|
# The data object
|
|
#
|
|
# @return [DataStore]
|
|
def data
|
|
@_data ||= DataStore.new(self)
|
|
end
|
|
end
|
|
|
|
# The core logic behind the data extension.
|
|
class DataStore
|
|
|
|
# Static methods
|
|
class << self
|
|
|
|
# The regex which tells Middleman which files are for data
|
|
#
|
|
# @return [Regexp]
|
|
def matcher
|
|
%r{[\w-]+\.(yml|yaml|json)$}
|
|
end
|
|
end
|
|
|
|
# Store static data hash
|
|
#
|
|
# @param [Symbol] name Name of the data, used for namespacing
|
|
# @param [Hash] content The content for this data
|
|
# @return [Hash]
|
|
def store(name=nil, content=nil)
|
|
@_local_sources ||= {}
|
|
@_local_sources[name.to_s] = content unless name.nil? || content.nil?
|
|
@_local_sources
|
|
end
|
|
|
|
# Store callback-based data
|
|
#
|
|
# @param [Symbol] name Name of the data, used for namespacing
|
|
# @param [Proc] proc The callback which will return data
|
|
# @return [Hash]
|
|
def callbacks(name=nil, proc=nil)
|
|
@_callback_sources ||= {}
|
|
@_callback_sources[name.to_s] = proc unless name.nil? || proc.nil?
|
|
@_callback_sources
|
|
end
|
|
|
|
# Setup data store
|
|
#
|
|
# @param [Middleman::Application] app The current instance of Middleman
|
|
def initialize(app)
|
|
@app = app
|
|
@local_data = {}
|
|
end
|
|
|
|
# Update the internal cache for a given file path
|
|
#
|
|
# @param [String] file The file to be re-parsed
|
|
# @return [void]
|
|
def touch_file(file)
|
|
root = Pathname(@app.root)
|
|
full_path = root + file
|
|
extension = File.extname(file)
|
|
basename = File.basename(file, extension)
|
|
|
|
data_path = full_path.relative_path_from(root + @app.config[:data_dir])
|
|
|
|
if %w(.yaml .yml).include?(extension)
|
|
data = YAML.load_file(full_path)
|
|
elsif extension == ".json"
|
|
data = ActiveSupport::JSON.decode(full_path.read)
|
|
else
|
|
return
|
|
end
|
|
|
|
data_branch = @local_data
|
|
|
|
path = data_path.to_s.split(File::SEPARATOR)[0..-2]
|
|
path.each do |dir|
|
|
data_branch[dir] ||= ::Middleman::Util.recursively_enhance({})
|
|
data_branch = data_branch[dir]
|
|
end
|
|
|
|
data_branch[basename] = ::Middleman::Util.recursively_enhance(data)
|
|
end
|
|
|
|
# Remove a given file from the internal cache
|
|
#
|
|
# @param [String] file The file to be cleared
|
|
# @return [void]
|
|
def remove_file(file)
|
|
root = Pathname(@app.root)
|
|
full_path = root + file
|
|
extension = File.extname(file)
|
|
basename = File.basename(file, extension)
|
|
|
|
data_path = full_path.relative_path_from(root + @app.config[:data_dir])
|
|
|
|
data_branch = @local_data
|
|
|
|
path = data_path.to_s.split(File::SEPARATOR)[0..-2]
|
|
path.each do |dir|
|
|
data_branch = data_branch[dir]
|
|
end
|
|
|
|
data_branch.delete(basename) if data_branch.has_key?(basename)
|
|
end
|
|
|
|
# Get a hash from either internal static data or a callback
|
|
#
|
|
# @param [String, Symbol] path The name of the data namespace
|
|
# @return [Hash, nil]
|
|
def data_for_path(path)
|
|
response = nil
|
|
|
|
@@local_sources ||= {}
|
|
@@callback_sources ||= {}
|
|
|
|
if self.store.has_key?(path.to_s)
|
|
response = self.store[path.to_s]
|
|
elsif self.callbacks.has_key?(path.to_s)
|
|
response = self.callbacks[path.to_s].call()
|
|
end
|
|
|
|
response
|
|
end
|
|
|
|
# "Magically" find namespaces of data if they exist
|
|
#
|
|
# @param [String] path The namespace to search for
|
|
# @return [Hash, nil]
|
|
def method_missing(path)
|
|
if @local_data.has_key?(path.to_s)
|
|
return @local_data[path.to_s]
|
|
else
|
|
result = data_for_path(path)
|
|
|
|
if result
|
|
return ::Middleman::Util.recursively_enhance(result)
|
|
end
|
|
end
|
|
|
|
super
|
|
end
|
|
|
|
# Needed so that method_missing makes sense
|
|
def respond_to?(method, include_private = false)
|
|
super || has_key?(method)
|
|
end
|
|
|
|
# Make DataStore act like a hash. Return requested data, or
|
|
# nil if data does not exist
|
|
#
|
|
# @param [String, Symbol] key The name of the data namespace
|
|
# @return [Hash, nil]
|
|
def [](key)
|
|
__send__(key) if has_key?(key)
|
|
end
|
|
|
|
def has_key?(key)
|
|
@local_data.has_key?(key.to_s) || !!(data_for_path(key))
|
|
end
|
|
|
|
# Convert all the data into a static hash
|
|
#
|
|
# @return [Hash]
|
|
def to_h
|
|
data = {}
|
|
|
|
self.store.each do |k, v|
|
|
data[k] = data_for_path(k)
|
|
end
|
|
|
|
self.callbacks.each do |k, v|
|
|
data[k] = data_for_path(k)
|
|
end
|
|
|
|
(@local_data || {}).each do |k, v|
|
|
data[k] = v
|
|
end
|
|
|
|
data
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|