require'middleman-core/contracts'require'middleman-core/util/data'moduleMiddlemanmoduleCoreExtensions# The data extension parses YAML and JSON files in the `data/` directory# and makes them available to `config.rb`, templates and extensionsclassData<Extensionattr_reader:data_storedefine_setting:data_dir,ENV['MM_DATA_DIR']||'data','The directory data files are stored in'# Make the internal `data_store` method available as `app.data`expose_to_applicationdata: :data_store# Exposes `data` to templatesexpose_to_templatedata: :data_store# The regex which tells Middleman which files are for dataDATA_FILE_MATCHER=/^(.*?)[\w-]+\.(yml|yaml|json|toml)$/definitialize(app,config={},&block)super@data_store=DataStore.new(app,DATA_FILE_MATCHER)start_watching(app.config[:data_dir])enddefstart_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),only: DATA_FILE_MATCHER# Setup data files before anything else so they are available when# parsing config.rbapp.files.on_change(:data,&@data_store.method(:update_files))enddefafter_configurationreturnunless@original_data_dir!=app.config[:data_dir]@watcher.update_path(app.config[:data_dir])end# The core logic behind the data extension.classDataStoreincludeContracts# Setup data store## @param [Middleman::Application] app The current instance of Middlemandefinitialize(app,data_file_matcher)@app=app@data_file_matcher=data_file_matcher@local_data={}@local_data_enhanced=nil@local_sources={}@callback_sources={}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]ContractSymbol,Or[Hash,Array]=>Hashdefstore(name=nil,content=nil)@local_sources[name.to_s]=contentunlessname.nil?||content.nil?@local_sourcesend# 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]ContractMaybe[Symbol],Maybe[Proc]=>Hashdefcallbacks(name=nil,proc=nil)@callback_sources[name.to_s]=procunlessname.nil?||proc.nil?@callback_sourcesendContractArrayOf[IsA['Middleman::SourceFile']],ArrayOf[IsA['Middleman::SourceFile']]=>Anydefupdate_files(updated_files,removed_files)updated_files.each(&method(:touch_file))removed_files.each(&method(:remove_file))@app.sitemap.rebuild_resource_list!(:touched_data_file)end# Update the internal cache for a given file path## @param [String] file The file to be re-parsed# @return [void]ContractIsA['Middleman::SourceFile']=>Anydeftouch_file(file)data_path=file[:relative_path]extension=File.extname(data_path)basename=File.basename(data_path,extension)returnunless%w(.yaml .yml .json .toml).include?(extension)if%w(.yaml .yml).include?(extension)data,postscript=::Middleman::Util::Data.parse(file,@app.config[:frontmatter_delims],:yaml)data[:postscript]=postscriptif!postscript.nil?&&data.is_a?(Hash)elsifextension=='.json'data,_postscript=::Middleman::Util::Data.parse(file,@app.config[:frontmatter_delims],:json)elsifextension=='.toml'data,_postscript=::Middleman::Util::Data.parse(file,@app.config[:frontmatter_delims],:toml)enddata_branch=@local_datapath=data_path.to_s.split(File::SEPARATOR)[0..-2]path.eachdo|dir|data_branch[dir]||={}data_branch=data_branch[dir]enddata_branch[basename]=data@local_data_enhanced=nilend# Remove a given file from the internal cache## @param [String] file The file to be cleared# @return [void]ContractIsA['Middleman::SourceFile']=>Anydefremove_file(file)data_path=file[:relative_path]extension=File.extname(data_path)basename=File.basename(data_path,extension)data_branch=@local_datapath=data_path.to_s.split(File::SEPARATOR)[0..-2]path.eachdo|dir|data_branch=data_branch[dir]enddata_branch.delete(basename)ifdata_branch.key?(basename)@local_data_enhanced=nilend# Get a hash from either internal static data or a callback## @param [String, Symbol] path The name of the data namespace# @return [Hash, nil]ContractOr[String,Symbol]=>Maybe[Or[Array,IsA['Middleman::Util::EnhancedHash']]]defdata_for_path(path)response=ifstore.key?(path.to_s)store[path.to_s]elsifcallbacks.key?(path.to_s)callbacks[path.to_s].callend::Middleman::Util.recursively_enhance(response)end# "Magically" find namespaces of data if they exist## @param [String] path The namespace to search for# @return [Hash, nil]defmethod_missing(path)if@local_data.key?(path.to_s)# Any way to cache this?@local_data_enhanced||=::Middleman::Util.recursively_enhance(@local_data)return@local_data_enhanced[path.to_s]elseresult=data_for_path(path)returnresultifresultendsuperend# Needed so that method_missing makes sensedefrespond_to?(method,include_private=false)super||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)ifkey?(key)enddefkey?(key)(@local_data.keys+@local_sources.keys+@callback_sources.keys).include?(key.to_s)endaliashas_key?key?# Convert all the data into a static hash## @return [Hash]ContractHashdefto_hdata={}store.each_keydo|k|data[k]=data_for_path(k)endcallbacks.each_keydo|k|data[k]=data_for_path(k)end(@local_data||{}).eachdo|k,v|data[k]=venddataendendendendend