middleman/middleman-core/lib/middleman-core/core_extensions/data.rb

220 lines
6.1 KiB
Ruby
Raw Normal View History

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
2014-04-29 01:02:18 +02:00
alias_method :included, :registered
2011-11-18 09:34:56 +01:00
end
# Instance methods
module InstanceMethods
# Setup data files before anything else so they are available when
# parsing config.rb
def initialize
2014-04-29 01:02:18 +02:00
files.changed DataStore.matcher do |file|
data.touch_file(file) if file.start_with?("#{config[:data_dir]}/")
end
2014-04-29 01:02:18 +02:00
files.deleted DataStore.matcher do |file|
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
2013-03-22 10:14:10 +01:00
# @return [Hash]
def store(name=nil, content=nil)
@_local_sources ||= {}
@_local_sources[name.to_s] = content unless name.nil? || content.nil?
@_local_sources
end
2011-11-21 06:21:19 +01:00
# Store callback-based data
#
# @param [Symbol] name Name of the data, used for namespacing
# @param [Proc] proc The callback which will return data
2013-03-22 10:14:10 +01:00
# @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)
2013-03-23 09:13:05 +01:00
root = Pathname(@app.root)
full_path = root + file
extension = File.extname(file)
basename = File.basename(file, extension)
2013-03-23 09:13:05 +01:00
data_path = full_path.relative_path_from(root + @app.config[:data_dir])
if %w(.yaml .yml).include?(extension)
2013-03-23 09:13:05 +01:00
data = YAML.load_file(full_path)
elsif extension == '.json'
2013-03-23 09:13:05 +01:00
data = ActiveSupport::JSON.decode(full_path.read)
else
return
end
2011-11-10 06:19:11 +01:00
2013-03-23 09:13:05 +01:00
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)
2013-03-23 09:13:05 +01:00
root = Pathname(@app.root)
full_path = root + file
extension = File.extname(file)
basename = File.basename(file, extension)
2013-03-23 09:13:05 +01:00
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
2014-04-29 01:02:18 +02:00
data_branch.delete(basename) if data_branch.key?(basename)
end
2013-03-22 10:14:10 +01:00
# 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 ||= {}
2014-04-29 01:02:18 +02:00
if store.key?(path.to_s)
response = store[path.to_s]
elsif callbacks.key?(path.to_s)
response = 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)
2014-04-29 01:02:18 +02:00
if @local_data.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
2014-04-29 01:02:18 +02:00
def respond_to?(method, include_private=false)
super || key?(method)
end
2013-12-28 19:14:15 +01:00
# 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)
2014-04-29 01:02:18 +02:00
__send__(key) if key?(key)
end
def has_key?(key)
2014-04-29 01:02:18 +02:00
@local_data.key?(key.to_s) || !!(data_for_path(key))
end
2014-04-29 01:02:18 +02:00
alias_method :key?, :has_key?
# Convert all the data into a static hash
#
# @return [Hash]
def to_h
data = {}
2014-04-29 01:02:18 +02:00
store.each do |k, _|
data[k] = data_for_path(k)
end
2014-04-29 01:02:18 +02:00
callbacks.each do |k, _|
data[k] = data_for_path(k)
end
(@local_data || {}).each do |k, v|
data[k] = v
end
data
end
end
end
2011-04-15 00:35:41 +02:00
end
end