2014-07-03 04:04:34 +02:00
|
|
|
require 'middleman-core/contracts'
|
2015-06-17 00:30:37 +02:00
|
|
|
require 'middleman-core/util/data'
|
2014-05-26 03:43:49 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
module Middleman
|
|
|
|
module CoreExtensions
|
2014-05-26 03:43:49 +02:00
|
|
|
# The data extension parses YAML and JSON files in the `data/` directory
|
|
|
|
# and makes them available to `config.rb`, templates and extensions
|
|
|
|
class Data < Extension
|
2014-07-06 01:05:00 +02:00
|
|
|
attr_reader :data_store
|
|
|
|
|
2016-01-13 01:03:23 +01:00
|
|
|
define_setting :data_dir, 'data', 'The directory data files are stored in'
|
|
|
|
|
2015-05-02 20:48:21 +02:00
|
|
|
# Make the internal `data_store` method available as `app.data`
|
|
|
|
expose_to_application data: :data_store
|
2015-05-02 23:23:16 +02:00
|
|
|
|
2015-05-02 20:48:21 +02:00
|
|
|
# Exposes `data` to templates
|
|
|
|
expose_to_template data: :data_store
|
|
|
|
|
2014-07-24 07:03:54 +02:00
|
|
|
# The regex which tells Middleman which files are for data
|
|
|
|
DATA_FILE_MATCHER = /^(.*?)[\w-]+\.(yml|yaml|json)$/
|
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
def initialize(app, config={}, &block)
|
|
|
|
super
|
2015-04-26 22:22:58 +02:00
|
|
|
|
2014-07-24 07:03:54 +02:00
|
|
|
@data_store = DataStore.new(app, DATA_FILE_MATCHER)
|
2014-07-06 01:44:04 +02:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
start_watching(app.config[:data_dir])
|
|
|
|
end
|
|
|
|
|
|
|
|
def start_watching(dir)
|
|
|
|
@original_data_dir = dir
|
|
|
|
|
|
|
|
# Tell the file watcher to observe the :data_dir
|
|
|
|
@watcher = app.files.watch :data,
|
|
|
|
path: File.join(app.root, dir),
|
2015-02-27 02:08:40 +01:00
|
|
|
only: DATA_FILE_MATCHER
|
2014-07-06 01:44:04 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Setup data files before anything else so they are available when
|
|
|
|
# parsing config.rb
|
2014-12-23 23:54:21 +01:00
|
|
|
app.files.on_change(:data, &@data_store.method(:update_files))
|
2014-07-16 03:01:45 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
def after_configuration
|
|
|
|
return unless @original_data_dir != app.config[:data_dir]
|
2014-03-26 00:54:16 +01:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
@watcher.update_path(app.config[:data_dir])
|
2014-05-26 03:43:49 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# The core logic behind the data extension.
|
|
|
|
class DataStore
|
2014-07-03 04:04:34 +02:00
|
|
|
include Contracts
|
|
|
|
|
|
|
|
# Setup data store
|
|
|
|
#
|
|
|
|
# @param [Middleman::Application] app The current instance of Middleman
|
2014-07-16 03:01:45 +02:00
|
|
|
def initialize(app, data_file_matcher)
|
2014-07-03 04:04:34 +02:00
|
|
|
@app = app
|
2014-07-16 03:01:45 +02:00
|
|
|
@data_file_matcher = data_file_matcher
|
2014-07-03 04:04:34 +02:00
|
|
|
@local_data = {}
|
|
|
|
@local_sources = {}
|
|
|
|
@callback_sources = {}
|
|
|
|
end
|
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# 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]
|
2014-07-03 04:04:34 +02:00
|
|
|
Contract Symbol, Hash => Hash
|
2012-05-07 23:41:39 +02:00
|
|
|
def store(name=nil, content=nil)
|
2014-05-26 03:43:49 +02:00
|
|
|
@local_sources[name.to_s] = content unless name.nil? || content.nil?
|
|
|
|
@local_sources
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2011-11-21 06:21:19 +01:00
|
|
|
|
2012-05-07 23:41:39 +02: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]
|
2015-02-24 20:06:28 +01:00
|
|
|
Contract Maybe[Symbol], Maybe[Proc] => Hash
|
2012-05-07 23:41:39 +02:00
|
|
|
def callbacks(name=nil, proc=nil)
|
2014-05-26 03:43:49 +02:00
|
|
|
@callback_sources[name.to_s] = proc unless name.nil? || proc.nil?
|
|
|
|
@callback_sources
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
Contract ArrayOf[IsA['Middleman::SourceFile']], ArrayOf[IsA['Middleman::SourceFile']] => Any
|
|
|
|
def update_files(updated_files, removed_files)
|
|
|
|
updated_files.each(&method(:touch_file))
|
|
|
|
removed_files.each(&method(:remove_file))
|
|
|
|
end
|
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Update the internal cache for a given file path
|
|
|
|
#
|
|
|
|
# @param [String] file The file to be re-parsed
|
|
|
|
# @return [void]
|
2014-07-16 03:01:45 +02:00
|
|
|
Contract IsA['Middleman::SourceFile'] => Any
|
2012-05-07 23:41:39 +02:00
|
|
|
def touch_file(file)
|
2015-11-29 04:32:45 +01:00
|
|
|
data_path = file[:relative_path]
|
2014-07-16 03:01:45 +02:00
|
|
|
extension = File.extname(data_path)
|
|
|
|
basename = File.basename(data_path, extension)
|
2013-03-23 09:13:05 +01:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
if %w(.yaml .yml).include?(extension)
|
2015-10-23 11:08:09 +02:00
|
|
|
data, postscript = ::Middleman::Util::Data.parse(file[:full_path], @app.config[:frontmatter_delims], :yaml)
|
2015-06-17 00:30:37 +02:00
|
|
|
data[:postscript] = postscript if !postscript.nil? && data.is_a?(Hash)
|
2013-12-28 01:26:31 +01:00
|
|
|
elsif extension == '.json'
|
2015-10-23 11:08:09 +02:00
|
|
|
data, _postscript = ::Middleman::Util::Data.parse(file[:full_path], @app.config[:frontmatter_delims], :json)
|
2012-05-07 23:41:39 +02:00
|
|
|
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|
|
2014-07-14 22:19:34 +02:00
|
|
|
data_branch[dir] ||= {}
|
2013-03-23 09:13:05 +01:00
|
|
|
data_branch = data_branch[dir]
|
|
|
|
end
|
|
|
|
|
2014-07-14 22:19:34 +02:00
|
|
|
data_branch[basename] = data
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Remove a given file from the internal cache
|
|
|
|
#
|
|
|
|
# @param [String] file The file to be cleared
|
|
|
|
# @return [void]
|
2014-07-16 03:01:45 +02:00
|
|
|
Contract IsA['Middleman::SourceFile'] => Any
|
2012-05-07 23:41:39 +02:00
|
|
|
def remove_file(file)
|
2015-11-29 04:32:45 +01:00
|
|
|
data_path = file[:relative_path]
|
2014-07-16 03:01:45 +02:00
|
|
|
extension = File.extname(data_path)
|
|
|
|
basename = File.basename(data_path, extension)
|
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 = data_branch[dir]
|
|
|
|
end
|
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
data_branch.delete(basename) if data_branch.key?(basename)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-03-22 10:14:10 +01:00
|
|
|
# Get a hash from either internal static data or a callback
|
2012-05-07 23:41:39 +02:00
|
|
|
#
|
|
|
|
# @param [String, Symbol] path The name of the data namespace
|
|
|
|
# @return [Hash, nil]
|
2014-07-03 04:04:34 +02:00
|
|
|
Contract Or[String, Symbol] => Maybe[Hash]
|
2012-05-07 23:41:39 +02:00
|
|
|
def data_for_path(path)
|
2014-07-14 22:19:34 +02:00
|
|
|
response = if store.key?(path.to_s)
|
|
|
|
store[path.to_s]
|
2014-04-29 19:50:21 +02:00
|
|
|
elsif callbacks.key?(path.to_s)
|
2014-07-14 22:19:34 +02:00
|
|
|
callbacks[path.to_s].call
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-07-14 22:19:34 +02:00
|
|
|
response = ::Middleman::Util.recursively_enhance(response)
|
2012-05-07 23:41:39 +02:00
|
|
|
response
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# "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 19:50:21 +02:00
|
|
|
if @local_data.key?(path.to_s)
|
2015-03-20 21:58:23 +01:00
|
|
|
# Any way to cache this?
|
|
|
|
return ::Middleman::Util.recursively_enhance(@local_data[path.to_s])
|
2012-05-07 23:41:39 +02:00
|
|
|
else
|
|
|
|
result = data_for_path(path)
|
2014-07-14 22:19:34 +02:00
|
|
|
return result if result
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
super
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-05-01 03:27:19 +02:00
|
|
|
# Needed so that method_missing makes sense
|
2014-04-29 19:50:21 +02:00
|
|
|
def respond_to?(method, include_private=false)
|
|
|
|
super || key?(method)
|
2013-05-02 22:39:28 +02:00
|
|
|
end
|
2013-12-28 19:14:15 +01:00
|
|
|
|
2013-05-02 22:39:28 +02: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 19:50:21 +02:00
|
|
|
__send__(key) if key?(key)
|
2013-05-02 22:39:28 +02:00
|
|
|
end
|
|
|
|
|
2014-04-29 19:44:24 +02:00
|
|
|
def key?(key)
|
2015-02-07 22:38:29 +01:00
|
|
|
(@local_data.keys + @local_sources.keys + @callback_sources.keys).include?(key.to_s)
|
2013-05-01 03:27:19 +02:00
|
|
|
end
|
|
|
|
|
2016-01-14 20:21:42 +01:00
|
|
|
alias has_key? key?
|
2014-04-29 01:02:18 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Convert all the data into a static hash
|
|
|
|
#
|
|
|
|
# @return [Hash]
|
2015-04-24 19:26:42 +02:00
|
|
|
Contract Hash
|
2012-05-07 23:41:39 +02:00
|
|
|
def to_h
|
|
|
|
data = {}
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 01:02:18 +02:00
|
|
|
store.each do |k, _|
|
2012-05-07 23:41:39 +02:00
|
|
|
data[k] = data_for_path(k)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 01:02:18 +02:00
|
|
|
callbacks.each do |k, _|
|
2012-05-07 23:41:39 +02:00
|
|
|
data[k] = data_for_path(k)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
(@local_data || {}).each do |k, v|
|
|
|
|
data[k] = v
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
data
|
|
|
|
end
|
|
|
|
end
|
2011-04-29 20:58:07 +02:00
|
|
|
end
|
2011-04-15 00:35:41 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
end
|