jekyll-data-1.1.0/0000755000004100000410000000000013562060104013737 5ustar www-datawww-datajekyll-data-1.1.0/README.md0000644000004100000410000001471213562060104015223 0ustar www-datawww-data# JekyllData [![Gem Version](https://img.shields.io/gem/v/jekyll-data.svg)](https://rubygems.org/gems/jekyll-data) [![Build Status](https://img.shields.io/travis/ashmaroli/jekyll-data/master.svg?label=Build%20Status)][travis] [travis]: https://travis-ci.org/ashmaroli/jekyll-data Introducing a plugin that reads data files within **jekyll theme-gems** and adds the resulting hash to the site's internal data hash. If a **`_config.yml`** is present at the root of the theme-gem, it will be evaluated and the extracted hash data will be incorporated into the site's existing config hash. ## Installation Simply add the plugin to your site's Gemfile and config file like every other jekyll plugin gem: ```ruby # Gemfile group :jekyll_plugins do gem "jekyll-data" end ``` ..and run bundle install > **Note: If the plugin has been marked as a `runtime_dependency` by the theme-gem's author it will be installed automatically with the theme-gem. Yet, it is recommended that the plugin be added to `:jekyll_plugins` group in the Gemfile rather than the `gems:` array in the config file while building or serving the site to avoid 'overriding' the `gems:` array data that may have been read-in from the theme-gem.** ## Usage As long as the plugin-gem has been installed properly, and is included in the Gemfile's `:jekyll_plugins` group, data files supported by Jekyll and present in the `_data` directory at the root of your theme-gem will be read. Their contents will be added to the site's internal data hash, provided, an identical data hash doesn't already exist at the site-source. If the theme-gem also includes a `_config.yml` at its root, then it will be read as well. The resulting config hash will be mixed into the site's existing config hash, filling in where the *keys* are not already defined. In other words, the config file at `source` will override corresponding identical keys in a `_config.yml` within the theme-gem which would in turn override corresponding `DEFAULTS` from Jekyll: **DEFAULTS** < **_config.yml in theme-gem** < **_config.yml at source** < **Override configs via command-line**. ### Theme Configuration Jekyll themes (built prior to `Jekyll 3.2`) usually ship with configuration settings defined in the config file, which are then used within the theme's template files directly under the `site` namespace (e.g. `{{ site.myvariable }}`). This is not possible with theme-gems as a config file and data files within gems are not natively read (as of Jekyll 3.3), and hence require end-users to inspect a *demo* or *example* directory to source those files. This plugin provides a solution to that hurdle: JekyllData now reads the config file (at present only `_config.yml`) present within the theme-gem and uses the data to modify the site's config hash. This allows the theme-gem to continue using `{{ site.myvariable }}` within its templates and work out-of-the-box as intended, with minimal user intervention. **Note: the plugins required by the theme may be listed under the `gems:` array and will be automatically `required` by Jekyll while building/serving, provided that the user doesn't have a different `gems:` array in the config file at source. Hence it is recommended to add all other plugins ( including `jekyll-data` ) via the Gemfile's `:jekyll_plugins` group.** #### The `theme` namespace From `v1.0`, JekyllData no longer supports reading theme configuration provided as a `[theme-name].***` file within the `_data` directory and instead the `theme` namespace points to a certain key in the bundled `_config.yml`. For `{{ theme.variable }}` to work, the config file should nest all such key-value pairs under the `[theme-name]` key, as outlined in the example below for a theme-gem called `solitude`: ```yaml # /_config.yml # the settings below have been used in this theme's templates via the `theme` # namespace. e.g. `{{ theme.recent_posts.style }}` instead of using the more # verbose `{{ site.solitude.recent_posts.style }}` though both are functionally # the same. # solitude: sidebar : true # enter 'false' to enable horizontal navbar instead. theme_variant : Charcoal # choose from 'Ocean', 'Grass', 'Charcoal' recent_posts : style : list # choose from 'list' and 'grid'. quantity : '4' # either '4' or '6' ``` ### Data files Data files may be used to supplement theme templates (e.g. locales and translated UI text) and can be named as desired. - Organize related small data files in sub-directories. (or) - Declare all related data as mapped data blocks within a single file. To illustrate with an example, consider a `locales.yml` that has mappings for `en:`, `fr:`, `it:`. ```yaml # /_data/locales.yml en: previous : previous next : next fr: previous : précédent next : prochain it: previous : precedente next : seguente ``` the Hash from above would be identical to one had the gem been shipped with a `_data/locales` directory containing individual files for each language data. ### Overriding Data Files To override data shipped with a theme-gem, simply have an identical hash at the site-source. Irrespective of whether the theme-gem ships with consolidated data files of related entities, or sub-directories containing individual files, the data can be overridden with a single file or with multiple files. For example, if a theme-gem contains the above sample `locales.yml`, then to override the `fr:` key-data simply have either of the following: - a **`_data/locales/fr.yml`** with identical subkey(s). - a **`_data/locales.yml`** with **`fr:`** with identical subkey(s). -- > **Note** - having an **empty** `_data/locales.yml` at `source` directory will override the **entire `["data"]["locales"]` payload** from the theme-gem as **`false`**. - having an **empty** `_data/locales/fr.yml` at `source` directory will override the **enire `["data"]["locales"]["fr"]` payload** from the theme-gem as **`false`** ## Contributing Bug reports and pull requests are welcome at the [GitHub Repo](https://github.com/ashmaroli/jekyll-data). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). jekyll-data-1.1.0/lib/0000755000004100000410000000000013562060104014505 5ustar www-datawww-datajekyll-data-1.1.0/lib/jekyll-data.rb0000644000004100000410000000370313562060104017236 0ustar www-datawww-data# frozen_string_literal: true require "jekyll" require "jekyll-data/version" module JekyllData autoload :Reader, "jekyll-data/reader" autoload :ThemedSiteDrop, "jekyll-data/themed_site_drop" autoload :ThemeDataReader, "jekyll-data/theme_data_reader" autoload :ThemeConfiguration, "jekyll-data/theme_configuration" end # Monkey-patches require_relative "jekyll/build_options" require_relative "jekyll/data_path" require_relative "jekyll/theme_drop" # ---------------------------------------------------------------------------- # Modify the current site instance only if it uses a gem-based theme. # # if a '_config.yml' is present at the root of theme-gem, it is evaluated and # the extracted hash data is incorprated into the site's config hash. # # *Jekyll 4.0 has this feature incorporated in its core.* # ---------------------------------------------------------------------------- unless Jekyll::VERSION.start_with?("4") Jekyll::Hooks.register :site, :after_reset do |site| if site.theme file = site.in_theme_dir("_config.yml") JekyllData::ThemeConfiguration.reconfigure(site) if File.exist?(file) end end end # --------------------------------------------------------------------------- # Replace Jekyll::Reader with a subclass JekyllData::Reader only if the # site uses a gem-based theme. # # If a _config.yml exists at the root of the theme-gem, output its path. # Placed here inorder to avoid outputting the path after every regeneration. # # *Jekyll 4.0 detects a theme-configuration natively.* # --------------------------------------------------------------------------- Jekyll::Hooks.register :site, :after_init do |site| if site.theme unless Jekyll::VERSION.start_with?("4") file = site.in_theme_dir("_config.yml") Jekyll.logger.info "Theme Config file:", file if File.exist?(file) end site.reader = JekyllData::Reader.new(site) end end jekyll-data-1.1.0/lib/jekyll/0000755000004100000410000000000013562060104015777 5ustar www-datawww-datajekyll-data-1.1.0/lib/jekyll/data_path.rb0000644000004100000410000000021613562060104020250 0ustar www-datawww-data# frozen_string_literal: true module Jekyll class Theme def data_path @data_path ||= path_for "_data" end end end jekyll-data-1.1.0/lib/jekyll/theme_drop.rb0000644000004100000410000000115013562060104020447 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module Drops class UnifiedPayloadDrop < Drop # Register a namespace to easily call subkeys under key # in the _config.yml within a theme-gem via its bundled templates. # e.g. with this drop, theme-specific variables usually called like # {{ site.minima.date_format }} can be shortened to simply # {{ theme.date_format }}. def theme @theme_drop ||= begin config = site.send(:fallback_data) config[config["theme"]] end end end end end jekyll-data-1.1.0/lib/jekyll/build_options.rb0000644000004100000410000000076013562060104021201 0ustar www-datawww-data# frozen_string_literal: true module Jekyll class Command class << self # # patch original method to inject a '--show-data' switch to display # merged data hash # alias_method :original_build_options, :add_build_options def add_build_options(cmd) original_build_options(cmd) cmd.option "show-data", "--show-data", "Print merged site-data hash when used with --verbose." end end end end jekyll-data-1.1.0/lib/jekyll-data/0000755000004100000410000000000013562060104016706 5ustar www-datawww-datajekyll-data-1.1.0/lib/jekyll-data/version.rb0000644000004100000410000000011613562060104020716 0ustar www-datawww-data# frozen_string_literal: true module JekyllData VERSION = "1.1.0" end jekyll-data-1.1.0/lib/jekyll-data/reader.rb0000644000004100000410000001431313562060104020477 0ustar www-datawww-data# frozen_string_literal: true require "csv" module JekyllData class Reader < Jekyll::Reader def initialize(site) @site = site @theme = site.theme @theme_data_files = Dir[File.join(site.theme.data_path, "**", "*.{yaml,yml,json,csv,tsv}")] end # Read data files within theme-gem. # # Returns nothing. def read super read_theme_data end # Read data files within a theme gem and add them to internal data # # Returns a hash appended with new data def read_theme_data if @theme.data_path # # show contents of "/_data/" dir being read while degugging. inspect_theme_data theme_data = ThemeDataReader.new(site).read(site.config["data_dir"]) @site.data = Jekyll::Utils.deep_merge_hashes(theme_data, @site.data) # # show contents of merged site.data hash while debugging with # additional --show-data switch. inspect_merged_hash if site.config["show-data"] && site.config["verbose"] end end private # Private: # (only while debugging) # # Print a list of data file(s) within the theme-gem def inspect_theme_data print_clear_line Jekyll.logger.debug "Reading:", "Theme Data Files..." @theme_data_files.each { |file| Jekyll.logger.debug "", file } print_clear_line Jekyll.logger.debug "Merging:", "Theme Data Hash..." unless site.config["show-data"] && site.config["verbose"] Jekyll.logger.debug "", "use --show-data with --verbose to output " \ "merged Data Hash.".cyan print_clear_line end end # Private: # (only while debugging) # # Print contents of the merged data hash def inspect_merged_hash Jekyll.logger.debug "Inspecting:", "Site Data >>" # the width of generated logger[message] @width = 50 @dashes = "-" * @width inspect_hash @site.data print_clear_line end # -------------------------------------------------------------------- # Private helper methods to inspect data hash and output contents # to logger at level debugging. # -------------------------------------------------------------------- # Dissect the (merged) site.data hash and print its contents # # - Print the key string(s) # - Individually analyse the hash[key] values and extract contents # to output. def inspect_hash(hash) hash.each do |key, value| print_key key if value.is_a? Hash inspect_inner_hash value elsif value.is_a? Array extract_hashes_and_print value else print_string value.to_s end end end # Analyse deeper hashes and extract contents to output def inspect_inner_hash(hash) hash.each do |key, value| if value.is_a? Array print_label key extract_hashes_and_print value elsif value.is_a? Hash print_subkey_and_value key, value else print_hash key, value end end end # If an array of strings, print. Otherwise assume as an # array of hashes (sequences) that needs further analysis. def extract_hashes_and_print(array) array.each do |entry| if entry.is_a? String print_list entry else inspect_inner_hash entry end end end # # -------------------------------------------------------------------- # Private methods for formatting log messages while debugging # -------------------------------------------------------------------- # Splits a string longer than the value of '@width' into smaller # strings and prints each line as a logger[message] # # string - the long string # # label - optional text to designate the printed lines. def print_long_string(string, label = "") strings = string.scan(%r!(.{1,#{@width}})(\s+|\W|\Z)!).map { |s| s.join.strip } first_line = strings.first.cyan label.empty? ? print_value(first_line) : print(label, first_line) strings[1..-1].each { |s| print_value s.cyan } end # Prints key as logger[topic] and value as [message] def print_hash(key, value) if value.length > @width print_long_string value, "#{key}:" else print "#{key}:", value.cyan end end def print_list(item) if item.length > @width print_long_string item, "-" else print "-", item.cyan end end def print_string(str) if str.length > @width print_long_string str else print_value str.inspect end end # Prints the site.data[key] in color def print_key(key) print_clear_line print "Data Key:", " #{key} ".center(@width, "=") print_clear_line end # Prints label, keys and values of mappings def print_subkey_and_value(key, value) print_label key value.each do |subkey, val| if val.is_a? Hash print_inner_subkey subkey inspect_inner_hash val elsif val.is_a? Array print_inner_subkey subkey extract_hashes_and_print val elsif val.is_a? String print_hash subkey, val end end end # Print only logger[message], [topic] = nil def print_value(value) if value.is_a? Array extract_hashes_and_print value else print "", value end end # Print only logger[topic] appended with a colon def print_label(key) print_value " #{key} ".center(@width, "-") end def print_inner_subkey(key) print "#{key}:", @dashes end def print_dashes print "", @dashes end def print_clear_line print "" end # Redefine Jekyll Loggers to have the [topic] indented by 30. # (rjust by just 29 to accomodate the additional whitespace added # by Jekyll) def print(topic, message = "") Jekyll.logger.debug topic.rjust(29), message end end end jekyll-data-1.1.0/lib/jekyll-data/theme_configuration.rb0000644000004100000410000000415613562060104023272 0ustar www-datawww-data# frozen_string_literal: true module JekyllData class ThemeConfiguration < Jekyll::Configuration class << self # Public: Establish a new site.config hash by reading an optional config # file within the theme-gem and appending the resulting hash to # existing site.config filling in keys not already defined. # # site: current Jekyll::Site instance. # # Returns a config Hash to be used by an 'after_reset' hook. def reconfigure(site) default_hash = Jekyll::Configuration::DEFAULTS theme_config = ThemeConfiguration.new(site).read_theme_config # Merge with existing site.config and strip any remaining defaults config = Jekyll::Utils.deep_merge_hashes( theme_config, site.config ).reject { |key, value| value == default_hash[key] } # Merge DEFAULTS < _config.yml in theme-gem < config file(s) from source # and redefine site.config site.config = Jekyll::Configuration.from( Jekyll::Utils.deep_merge_hashes(theme_config, config) ) end end # def initialize(site) @site = site end # Public: Read the '_config.yml' file within the theme-gem. # Additionally validates that the extracted config data and the # the 'value' of ' key', when present, is a Hash. # # Returns a Configuration Hash def read_theme_config file = @site.in_theme_dir("_config.yml") theme_name = @site.theme.name config = safe_load_file(file) check_config_is_hash!(config, file) validate_config_hash config[theme_name] unless config[theme_name].nil? config end private # Validate the key's value to be accessed via the `theme` # namespace. def validate_config_hash(value) unless value.is_a? Hash Jekyll.logger.abort_with "JekyllData:", "Theme Configuration should be a " \ "Hash of key:value pairs or mappings. But got #{value.class} instead." end end end end jekyll-data-1.1.0/lib/jekyll-data/theme_data_reader.rb0000644000004100000410000000207013562060104022647 0ustar www-datawww-data# frozen_string_literal: true module JekyllData class ThemeDataReader < Jekyll::DataReader attr_reader :site, :content def initialize(site) @site = site @content = {} @entry_filter = Jekyll::EntryFilter.new(site) end def read(dir) return unless site.theme && site.theme.data_path base = site.in_theme_dir(dir) read_data_to(base, @content) @content end def read_data_to(dir, data) return unless File.directory?(dir) && !@entry_filter.symlink?(dir) entries = Dir.chdir(dir) do Dir["*.{yaml,yml,json,csv,tsv}"] + Dir["*"].select { |fn| File.directory?(fn) } end entries.each do |entry| path = @site.in_theme_dir(dir, entry) next if @entry_filter.symlink?(path) if File.directory?(path) read_data_to(path, data[sanitize_filename(entry)] = {}) else key = sanitize_filename(File.basename(entry, ".*")) data[key] = read_data_file(path) end end end end end jekyll-data-1.1.0/lib/jekyll-data/themed_site_drop.rb0000644000004100000410000000045413562060104022554 0ustar www-datawww-data# frozen_string_literal: true module JekyllData class ThemedSiteDrop < Jekyll::Drops::SiteDrop extend Forwardable mutable false def_delegator :@obj, :site_data, :data def_delegators :@obj, :theme private def_delegator :@obj, :config, :fallback_data end end jekyll-data-1.1.0/jekyll-data.gemspec0000644000004100000410000000505513562060104017512 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: jekyll-data 1.1.0 ruby lib Gem::Specification.new do |s| s.name = "jekyll-data".freeze s.version = "1.1.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "allowed_push_host" => "https://rubygems.org" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Ashwin Maroli".freeze] s.date = "2019-09-09" s.email = ["ashmaroli@gmail.com".freeze] s.files = ["LICENSE.txt".freeze, "README.md".freeze, "lib/jekyll-data.rb".freeze, "lib/jekyll-data/reader.rb".freeze, "lib/jekyll-data/theme_configuration.rb".freeze, "lib/jekyll-data/theme_data_reader.rb".freeze, "lib/jekyll-data/themed_site_drop.rb".freeze, "lib/jekyll-data/version.rb".freeze, "lib/jekyll/build_options.rb".freeze, "lib/jekyll/data_path.rb".freeze, "lib/jekyll/theme_drop.rb".freeze] s.homepage = "https://github.com/ashmaroli/jekyll-data".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "2.5.2.1".freeze s.summary = "A plugin to read '_config.yml' and data files within Jekyll theme-gems".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, [">= 1.14.3"]) s.add_development_dependency(%q.freeze, ["~> 2.1"]) s.add_runtime_dependency(%q.freeze, ["< 5.0.0", ">= 3.3"]) s.add_development_dependency(%q.freeze, ["~> 5.0"]) s.add_development_dependency(%q.freeze, ["~> 10.0"]) s.add_development_dependency(%q.freeze, ["~> 0.10.0"]) else s.add_dependency(%q.freeze, [">= 1.14.3"]) s.add_dependency(%q.freeze, ["~> 2.1"]) s.add_dependency(%q.freeze, ["< 5.0.0", ">= 3.3"]) s.add_dependency(%q.freeze, ["~> 5.0"]) s.add_dependency(%q.freeze, ["~> 10.0"]) s.add_dependency(%q.freeze, ["~> 0.10.0"]) end else s.add_dependency(%q.freeze, [">= 1.14.3"]) s.add_dependency(%q.freeze, ["~> 2.1"]) s.add_dependency(%q.freeze, ["< 5.0.0", ">= 3.3"]) s.add_dependency(%q.freeze, ["~> 5.0"]) s.add_dependency(%q.freeze, ["~> 10.0"]) s.add_dependency(%q.freeze, ["~> 0.10.0"]) end end jekyll-data-1.1.0/LICENSE.txt0000644000004100000410000000211413562060104015560 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2016-2017 Ashwin Maroli & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.