├── script ├── bootstrap ├── cucumber ├── fmt ├── cibuild ├── rubies ├── test-site └── test ├── test ├── fixtures │ ├── test-theme │ │ ├── _sass │ │ │ ├── test-theme-red.scss │ │ │ └── test-theme-black.scss │ │ ├── assets │ │ │ ├── application.coffee │ │ │ ├── style.scss │ │ │ └── logo.png │ │ ├── _data │ │ │ ├── locales │ │ │ │ ├── fr.yml │ │ │ │ └── en.yml │ │ │ ├── string.yml │ │ │ ├── navigation │ │ │ │ ├── footer-links.yml │ │ │ │ └── topnav.yml │ │ │ ├── blank.yml │ │ │ ├── sidemenu.yml │ │ │ └── config-hash.yml │ │ ├── _includes │ │ │ └── footer-links.html │ │ ├── _layouts │ │ │ ├── default.html │ │ │ ├── test.html │ │ │ ├── home.html │ │ │ └── page.html │ │ ├── test-theme.gemspec │ │ └── _config.yml │ ├── test-site │ │ ├── .gitignore │ │ ├── index.md │ │ ├── _config.yml │ │ ├── Gemfile │ │ └── _posts │ │ │ └── 2017-01-01-welcome-to-jekyll.markdown │ ├── test-plugin │ │ ├── test-plugin.gemspec │ │ └── lib │ │ │ └── test-plugin.rb │ ├── another-test-plugin │ │ ├── another-test-plugin.gemspec │ │ └── lib │ │ │ └── another-test-plugin.rb │ ├── no_data_output.html │ ├── no_data_config_output.html │ ├── same_data_output.html │ ├── same_data_override.html │ └── different_data_output.html ├── source │ ├── empty_config_override │ │ ├── _data │ │ │ └── test-theme.yml │ │ └── no_data.md │ ├── diff_data_keys │ │ ├── _data │ │ │ └── mu.yml │ │ └── different_data_key.md │ ├── same_data_files │ │ ├── _data │ │ │ └── locales │ │ │ │ └── en.yml │ │ └── same_data_key.md │ ├── no_data_files │ │ └── no_data.md │ ├── no_data_config │ │ └── no_data.md │ └── not_hash_config_override │ │ ├── no_data.md │ │ └── _data │ │ └── test-theme.yml ├── test_theme_configuration.rb ├── helper.rb └── test_theme_reader.rb ├── lib ├── jekyll-data │ ├── version.rb │ ├── themed_site_drop.rb │ ├── theme_data_reader.rb │ ├── theme_configuration.rb │ └── reader.rb ├── jekyll │ ├── data_path.rb │ ├── build_options.rb │ └── theme_drop.rb └── jekyll-data.rb ├── Rakefile ├── .gitignore ├── .travis.yml ├── Gemfile ├── .rubocop.yml ├── LICENSE.txt ├── jekyll-data.gemspec ├── features ├── theme_configuration.feature ├── support │ ├── helpers.rb │ └── formatter.rb ├── theme_data_reader.feature └── step_definitions.rb ├── README.md └── CHANGELOG.md /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | bundle install 5 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_sass/test-theme-red.scss: -------------------------------------------------------------------------------- 1 | .sample { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/source/empty_config_override/_data/test-theme.yml: -------------------------------------------------------------------------------- 1 | # empty theme config override 2 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_sass/test-theme-black.scss: -------------------------------------------------------------------------------- 1 | .sample { 2 | color: black; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/assets/application.coffee: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | alert "From your theme." 4 | -------------------------------------------------------------------------------- /script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | time ruby -S bundle exec cucumber \ 4 | --format progress "$@" 5 | -------------------------------------------------------------------------------- /test/source/diff_data_keys/_data/mu.yml: -------------------------------------------------------------------------------- 1 | greeting: Welcome to The Matrix! 2 | prev: Red Pill 3 | next: Blue Pill 4 | -------------------------------------------------------------------------------- /lib/jekyll-data/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllData 4 | VERSION = "1.1.1" 5 | end 6 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/locales/fr.yml: -------------------------------------------------------------------------------- 1 | greeting: "Bonjour, Bienvenue!" 2 | prev: précédent 3 | next: prochain 4 | -------------------------------------------------------------------------------- /test/source/same_data_files/_data/locales/en.yml: -------------------------------------------------------------------------------- 1 | greeting: "Welcome to the Future!" 2 | prev: older 3 | next: newer 4 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/assets/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | @import "test-theme-{{ site.theme-color | default: "red" }}"; 4 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "Rubocop $(bundle exec rubocop --version)" 5 | bundle exec rubocop -D 6 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-data/HEAD/test/fixtures/test-theme/assets/logo.png -------------------------------------------------------------------------------- /test/source/no_data_files/no_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | permalink: output.html 4 | --- 5 | 6 | This site does not have a _data directory at source. 7 | -------------------------------------------------------------------------------- /test/source/no_data_config/no_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | permalink: output.html 4 | --- 5 | 6 | This site does not have a _data directory at source. 7 | -------------------------------------------------------------------------------- /test/source/empty_config_override/no_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | permalink: output.html 4 | --- 5 | 6 | This site does not have a _data directory at source. 7 | -------------------------------------------------------------------------------- /test/source/not_hash_config_override/no_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | permalink: output.html 4 | --- 5 | 6 | This site does not have a _data directory at source. 7 | -------------------------------------------------------------------------------- /lib/jekyll/data_path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Theme 5 | def data_path 6 | @data_path ||= path_for "_data" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/string.yml: -------------------------------------------------------------------------------- 1 | > 2 | No keys. Just a file with a lone string. Is this a data file then? This 3 | string is still accessible via `{{ site.data.string }}` in the templates. 4 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_includes/footer-links.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ content }} 4 |
5 | 8 | -------------------------------------------------------------------------------- /test/source/not_hash_config_override/_data/test-theme.yml: -------------------------------------------------------------------------------- 1 | # theme config-override with data as an array (sequences) 2 | # 3 | - key: logo 4 | - value: logo.png 5 | - key: sidebar 6 | - value: disabled 7 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/navigation/footer-links.yml: -------------------------------------------------------------------------------- 1 | # Footer links 2 | 3 | - title: About 4 | url: /about/ 5 | - title: Contact Me 6 | url: /contact-me/ 7 | - title: Register 8 | url: /register/ 9 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/blank.yml: -------------------------------------------------------------------------------- 1 | # No parsable data. Just a file with a lone comment string. Is this a data 2 | # file then?. This string is not accessible via `{{ site.data.blank }}` either. 3 | # Will output "false". 4 | -------------------------------------------------------------------------------- /test/source/same_data_files/same_data_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | permalink: override.html 4 | --- 5 | 6 | This site has a _data directory at source root. It contains a `locale` subdirectory with a YAML file called `en.yml` 7 | -------------------------------------------------------------------------------- /test/source/diff_data_keys/different_data_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | permalink: output.html 4 | --- 5 | 6 | This site has a _data directory at source root. It contains a `locale` subdirectory with a YAML file called `mu.yml` 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rake" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new do |t| 7 | t.libs << "test" 8 | t.test_files = FileList["test/test*.rb"] 9 | t.verbose = true 10 | end 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | /.bundle/ 3 | /.yardoc 4 | /Gemfile.lock 5 | /_yardoc/ 6 | /coverage/ 7 | /doc/ 8 | /pkg/ 9 | /spec/reports/ 10 | /tmp/ 11 | .jekyll-metadata 12 | .ruby-version 13 | .jekyll-cache 14 | .sass-cache 15 | test/dest 16 | -------------------------------------------------------------------------------- /test/fixtures/test-site/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /Gemfile.lock 10 | _site 11 | .jekyll-metadata 12 | .ruby-version 13 | .sass-cache 14 | test/dest 15 | -------------------------------------------------------------------------------- /test/fixtures/test-site/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # You don't need to edit this file, it's empty on purpose. 3 | # Edit theme's home layout instead if you wanna make some changes 4 | # See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults 5 | 6 | layout: default 7 | --- 8 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ -z "$TEST_SUITE" ]] 6 | then 7 | script/fmt 8 | script/test ci 9 | elif [[ -x "script/$TEST_SUITE" ]] 10 | then 11 | script/$TEST_SUITE 12 | else 13 | echo "Unknown test suite." 14 | exit 1 15 | fi 16 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/navigation/topnav.yml: -------------------------------------------------------------------------------- 1 | # Menu navigation links 2 | 3 | - title: Home 4 | url: / 5 | - title: About 6 | url: /about/ 7 | - title: Contact 8 | url: /contact/ 9 | - title: Link 1 10 | url: /link-1.html 11 | - title: Link 2 12 | url: /link-2.html 13 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/sidemenu.yml: -------------------------------------------------------------------------------- 1 | # Side-menu navigation links 2 | 3 | - title: Home 4 | url: / 5 | - title: About 6 | url: /about/ 7 | - title: Contact 8 | url: /contact/ 9 | - title: Link 1 10 | url: http://an-absolute-url-to/a-really-deep/filecalled/some_page.html 11 | - title: Link 2 12 | url: http://another-absolute-url-to/a-page/ 13 | -------------------------------------------------------------------------------- /lib/jekyll-data/themed_site_drop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllData 4 | class ThemedSiteDrop < Jekyll::Drops::SiteDrop 5 | extend Forwardable 6 | 7 | mutable false 8 | 9 | def_delegator :@obj, :site_data, :data 10 | def_delegators :@obj, :theme 11 | 12 | private def_delegator :@obj, :config, :fallback_data 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_layouts/test.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ content }} 4 |
5 | {% unless theme.sidebar == 'disabled' %} 6 | {% else %}{% endunless %} 7 | 10 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/test-theme.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "test-theme" 5 | s.version = "0.1.0" 6 | s.licenses = ["MIT"] 7 | s.summary = "This is a theme used to test JekyllData Plugin" 8 | s.authors = ["JekyllData"] 9 | s.homepage = "https://github.com/ashmaroli/jekyll-data" 10 | end 11 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/locales/en.yml: -------------------------------------------------------------------------------- 1 | greeting: "Hello There, Welcome!" 2 | prev: previous 3 | next: next 4 | 5 | long_strings: 6 | - THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 7 | - THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG DOZING UNDER THE CANOPY 8 | - THEQUICKBROWNFOXJUMPSOVERTHELAZYDOGDOZINGUNDERTHECANOPY 9 | - THEQUICKBROWNFOX/JUMPSOVERTHELAZYDOG/DOZINGUNDERTHECANOPY 10 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 |

{{ site.data.locales[site.lang].greeting }}

6 | {{ content }} 7 |

8 | {{ site.data.locales[site.lang].prev }} 9 | {{ site.data.locales[site.lang].next }} 10 |

11 |
12 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: test 3 | --- 4 |
5 |

Site Details

6 | {{ content }} 7 |
8 |

Is logo enabled? {% unless theme.logo == 'disabled' %}{{ theme.logo }}{% endunless %}

9 |

Theme Variant: {{ theme.theme_variant }}

10 |

Show recent posts in a: {{ theme.recent_posts.style }}

11 |

Max no. of recent posts: {{ theme.recent_posts.quantity }}

12 |
13 |
14 | -------------------------------------------------------------------------------- /test/fixtures/test-plugin/test-plugin.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "test-plugin" 8 | s.version = "0.1.0" 9 | s.licenses = ["MIT"] 10 | s.summary = "A plugin that creates an empty 'feed.xml' to test JekyllData Plugin" 11 | s.authors = ["JekyllData"] 12 | s.require_paths = ["lib"] 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/another-test-plugin/another-test-plugin.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "another-test-plugin" 8 | s.version = "0.1.0" 9 | s.licenses = ["MIT"] 10 | s.summary = "A plugin that creates an empty 'sitemap.xml' to test JekyllData Plugin" 11 | s.authors = ["JekyllData"] 12 | s.require_paths = ["lib"] 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/test-plugin/lib/test-plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | 5 | # code abstracted from the official 'jekyll-feed' plugin 6 | # at https://github.com/jekyll/jekyll-feed/ 7 | 8 | Jekyll::Hooks.register :site, :pre_render do |site| 9 | page = TestPlugin::NoFile.new(site, "", "", "test-feed.xml") 10 | site.pages << page 11 | end 12 | 13 | module TestPlugin 14 | class NoFile < Jekyll::Page 15 | def read_yaml(*) 16 | @data ||= {} 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: script/cibuild 2 | cache: bundler 3 | language: ruby 4 | 5 | rvm: 6 | - &ruby1 2.7.1 7 | - &ruby2 2.5.8 8 | - &jruby jruby-9.2.14.0 9 | 10 | matrix: 11 | include: 12 | - rvm: *ruby1 13 | env: TEST_SUITE=fmt 14 | - rvm: *ruby1 15 | env: TEST_SUITE=test-site 16 | exclude: 17 | - rvm: *jruby 18 | env: TEST_SUITE=cucumber 19 | 20 | env: 21 | matrix: 22 | - TEST_SUITE=test 23 | - TEST_SUITE=cucumber 24 | 25 | before_install: 26 | - gem update --system --no-document 27 | -------------------------------------------------------------------------------- /script/rubies: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ----------------------------------------------------------------------------- 3 | # If you send us a ruby then we use that, if you do not then we test with 4 | # whatever we can detect, this way you can run both suites when you test out 5 | # your source, we expect full coverage now, not just MRI. 6 | # ----------------------------------------------------------------------------- 7 | 8 | rubies=() 9 | for r in ruby jruby; do 10 | if which "$r" > /dev/null 2>&1 11 | then 12 | echo $r 13 | fi 14 | done 15 | -------------------------------------------------------------------------------- /test/fixtures/another-test-plugin/lib/another-test-plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | 5 | # code abstracted from the official 'jekyll-feed' plugin 6 | # at https://github.com/jekyll/jekyll-feed/ 7 | 8 | Jekyll::Hooks.register :site, :pre_render do |site| 9 | page = AnotherTestPlugin::NoFile.new(site, "", "", "test-sitemap.xml") 10 | site.pages << page 11 | end 12 | 13 | module AnotherTestPlugin 14 | class NoFile < Jekyll::Page 15 | def read_yaml(*) 16 | @data ||= {} 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/fixtures/no_data_output.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Hello There, Welcome!

5 |

This site does not have a _data directory at source.

6 | 7 |

8 | previous 9 | next 10 |

11 |
12 | 13 |
14 | 22 | -------------------------------------------------------------------------------- /lib/jekyll/build_options.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Command 5 | class << self 6 | # 7 | # patch original method to inject a '--show-data' switch to display 8 | # merged data hash 9 | # 10 | alias_method :original_build_options, :add_build_options 11 | 12 | def add_build_options(cmd) 13 | original_build_options(cmd) 14 | cmd.option "show-data", "--show-data", 15 | "Print merged site-data hash when used with --verbose." 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | gemspec 5 | 6 | gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"] 7 | 8 | group :test do 9 | gem "activesupport", "~> 4.2" if RUBY_VERSION < "2.2.2" 10 | gem "another-test-plugin", :path => File.expand_path("test/fixtures/another-test-plugin", __dir__) 11 | gem "minitest-profile" 12 | gem "minitest-reporters" 13 | gem "rspec" 14 | gem "rspec-mocks" 15 | gem "shoulda" 16 | gem "test-plugin", :path => File.expand_path("test/fixtures/test-plugin", __dir__) 17 | gem "test-theme", :path => File.expand_path("test/fixtures/test-theme", __dir__) 18 | end 19 | -------------------------------------------------------------------------------- /lib/jekyll/theme_drop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Drops 5 | class UnifiedPayloadDrop < Drop 6 | # Register a namespace to easily call subkeys under key 7 | # in the _config.yml within a theme-gem via its bundled templates. 8 | # e.g. with this drop, theme-specific variables usually called like 9 | # {{ site.minima.date_format }} can be shortened to simply 10 | # {{ theme.date_format }}. 11 | def theme 12 | @theme_drop ||= begin 13 | config = site.send(:fallback_data) 14 | config[config["theme"]] 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/fixtures/no_data_config_output.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Site Details

5 |

This site does not have a _data directory at source.

6 | 7 |
8 |

Is logo enabled? logo.png

9 |

Theme Variant: Charcoal

10 |

Show recent posts in a: list

11 |

Max no. of recent posts: 4

12 |
13 |
14 | 15 |
16 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /test/fixtures/test-site/_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | 3 | title: Test Site 4 | email: your-email@domain.com 5 | description: > # 6 | This site is used to assess the 'jekyll-data' plugin. 7 | 8 | Notable change from the default config file generated by 'jekyll new' 9 | command is the absence of the 'gems:' array. The plugins required by 10 | this site's theme is handled by the 'config.yml' within the theme-gem. 11 | 12 | Additional plugins that may be required outside the theme's scope is 13 | added to the ':jekyll_plugins' group in the Gemfile. 14 | 15 | twitter_username: jekyllrb 16 | github_username: jekyll 17 | 18 | theme: test-theme 19 | exclude: 20 | - Gemfile 21 | - Gemfile.lock 22 | -------------------------------------------------------------------------------- /test/fixtures/same_data_output.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Welcome to the Future!

5 |

This site has a _data directory at source root. It contains a locale subdirectory with a YAML file called en.yml

6 | 7 |

8 | older 9 | newer 10 |

11 |
12 | 13 |
14 | 22 | -------------------------------------------------------------------------------- /test/fixtures/same_data_override.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Welcome to the Future!

5 |

This site has a _data directory at source root. It contains a locale subdirectory with a YAML file called en.yml

6 | 7 |

8 | older 9 | newer 10 |

11 |
12 | 13 |
14 | 22 | -------------------------------------------------------------------------------- /test/fixtures/different_data_output.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Hello There, Welcome!

5 |

This site has a _data directory at source root. It contains a locale subdirectory with a YAML file called mu.yml

6 | 7 |

8 | previous 9 | next 10 |

11 |
12 | 13 |
14 | 22 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_config.yml: -------------------------------------------------------------------------------- 1 | title: Test Theme 2 | email: tester@domain.com 3 | # the '>' means to ignore newlines until "baseurl:" 4 | description: > 5 | Powered by a test-theme. 6 | This text has been inserted by your theme gem. 7 | Override by defining a "description:" key in the config file at source. 8 | baseurl: "" 9 | markdown: kramdown 10 | post_excerpts: enabled # "enabled" / "disabled" 11 | 12 | # Theme configurations 13 | test-theme: 14 | logo: logo.png # enter 'disable' to use `site.title` instead. 15 | sidebar: enabled # enter 'disable' to activate horizontal navbar. 16 | theme_variant: Charcoal # choose from 'Ocean', 'Grass', 'Charcoal' 17 | recent_posts: 18 | style: list # choose from 'list' and 'grid'. 19 | quantity: '4' # either '4' or '6' 20 | gems: 21 | - test-plugin 22 | -------------------------------------------------------------------------------- /test/fixtures/test-theme/_data/config-hash.yml: -------------------------------------------------------------------------------- 1 | title: Test Hash 2 | email: tester@domain.com 3 | description: > 4 | This file is used to test the output of various hash types to the plugin's 5 | logger output. 6 | 7 | This particular folded (uses '>') block of text will showcase outputting 8 | long strings of text as wrapped to multiple lines of a set width. Another 9 | related block would be the 'non-folded' type (uses '|') and its lines are 10 | are new-line-aware. 11 | baseurl: "/blog" 12 | url: "" 13 | markdown: kramdown 14 | post_excerpts: enabled 15 | 16 | # Complex Hash object with nest hash-maps, lists and sequences. 17 | test-theme: 18 | logo: logo.png 19 | sidebar: enabled 20 | theme_variant: Charcoal 21 | recent_posts: 22 | style: list 23 | quantity: '4' 24 | plugins: 25 | - test-plugin 26 | - another-test-plugin 27 | navbar: 28 | - title: About Me 29 | page: about-me.html 30 | tooltip: Get to know me here. 31 | - title: Contact Us 32 | page: contact-us.html 33 | tooltip: Various ways to reach me. 34 | gems: 35 | - test-plugin 36 | - another-test-plugin 37 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-jekyll 2 | inherit_gem: 3 | rubocop-jekyll: '.rubocop.yml' 4 | 5 | AllCops: 6 | Exclude: 7 | - script/**/* 8 | - vendor/**/* 9 | - tmp/**/* 10 | 11 | Layout/ArrayAlignment: 12 | Exclude: 13 | - 'features/support/helpers.rb' 14 | Layout/ParameterAlignment: 15 | Exclude: 16 | - 'features/support/formatter.rb' 17 | Layout/LineLength: 18 | Exclude: 19 | - ./**/*.gemspec 20 | - ./**/Gemfile 21 | - !ruby/regexp /features\/.*.rb/ 22 | 23 | Naming/MemoizedInstanceVariableName: 24 | Exclude: 25 | - 'lib/jekyll/theme_drop.rb' 26 | - 'test/fixtures/another-test-plugin/lib/another-test-plugin.rb' 27 | - 'test/fixtures/test-plugin/lib/test-plugin.rb' 28 | 29 | Style/ClassAndModuleChildren: 30 | Exclude: 31 | - 'test/helper.rb' 32 | Style/SafeNavigation: 33 | Exclude: 34 | - 'lib/jekyll-data/theme_data_reader.rb' 35 | Style/SingleLineMethods: 36 | Exclude: 37 | - 'features/support/helpers.rb' 38 | Style/TrailingCommaInArrayLiteral: 39 | Exclude: 40 | - 'test/helper.rb' 41 | Style/TrailingCommaInHashLiteral: 42 | Exclude: 43 | - 'features/support/formatter.rb' 44 | -------------------------------------------------------------------------------- /script/test-site: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Runs the `jekyll build` command with various switches and builds the default site as a sanity check 3 | 4 | set -e 5 | 6 | cd test/fixtures/test-site 7 | 8 | if ! jekyll -v 9 | then 10 | echo "" 11 | echo "Installing site dependencies" 12 | echo "----------------------------" 13 | BUNDLE_GEMFILE=Gemfile bundle install 14 | fi 15 | 16 | echo "" 17 | echo "Building the site with default options" 18 | echo "--------------------------------------" 19 | 20 | if BUNDLE_GEMFILE=Gemfile bundle exec jekyll build --trace 21 | then 22 | echo "" 23 | echo "Build Success." 24 | echo "" 25 | echo "Building the site with --verbose" 26 | echo "--------------------------------" 27 | BUNDLE_GEMFILE=Gemfile bundle exec jekyll build --verbose --trace 28 | 29 | echo "" 30 | echo "Build Success." 31 | echo "" 32 | echo "Building the site with --verbose and --show-data" 33 | echo "------------------------------------------------" 34 | BUNDLE_GEMFILE=Gemfile bundle exec jekyll build --verbose --show-data --trace 35 | 36 | exit 0 37 | else 38 | echo "" 39 | echo "Error building site." 40 | 41 | exit 1 42 | fi 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Ashwin Maroli & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/jekyll-data/theme_data_reader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllData 4 | class ThemeDataReader < Jekyll::DataReader 5 | attr_reader :site, :content 6 | 7 | def initialize(site) 8 | super 9 | 10 | @source_dir = site.in_theme_dir("/") 11 | end 12 | 13 | def read(dir) 14 | return unless site.theme && site.theme.data_path 15 | 16 | base = site.in_theme_dir(dir) 17 | read_data_to(base, @content) 18 | @content 19 | end 20 | 21 | def read_data_to(dir, data) 22 | return unless File.directory?(dir) && !@entry_filter.symlink?(dir) 23 | 24 | entries = Dir.chdir(dir) do 25 | Dir["*.{yaml,yml,json,csv,tsv}"] + Dir["*"].select { |fn| File.directory?(fn) } 26 | end 27 | 28 | entries.each do |entry| 29 | path = @site.in_theme_dir(dir, entry) 30 | next if @entry_filter.symlink?(path) 31 | 32 | if File.directory?(path) 33 | read_data_to(path, data[sanitize_filename(entry)] = {}) 34 | else 35 | key = sanitize_filename(File.basename(entry, ".*")) 36 | data[key] = read_data_file(path) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Usage: 5 | # script/test 6 | # script/test [ruby|jruby] 7 | # script/test 8 | 9 | if [ -d test/dest ] 10 | then rm -r test/dest 11 | fi 12 | 13 | # ----------------------------------------------------------------------------- 14 | # If you send us a ruby then we use that, if you do not then we test with 15 | # whatever we can detect, this way you can run both suites when you test out 16 | # your source, we expect full coverage now, not just MRI. 17 | # ----------------------------------------------------------------------------- 18 | 19 | if [[ "$1" == "ci" ]] 20 | then 21 | rubies=( 22 | ruby 23 | ) 24 | 25 | shift 26 | elif [[ "$1" == "ruby" ]] || [[ "$1" == "jruby" ]] 27 | then 28 | rubies=( 29 | $1 30 | ) 31 | 32 | shift 33 | else 34 | rubies=($(script/rubies)) 35 | fi 36 | 37 | 38 | for ruby in $rubies; do 39 | if [[ "$ruby" == "jruby" ]] 40 | then 41 | testopts="" 42 | else 43 | testopts="--profile" 44 | fi 45 | 46 | if [[ $# -lt 1 ]] 47 | then 48 | set -x 49 | time $ruby -S bundle exec \ 50 | rake TESTOPTS=$testopts test 51 | else 52 | set -x 53 | time $ruby -S bundle exec ruby -I test \ 54 | "$@" $testops 55 | fi 56 | done 57 | -------------------------------------------------------------------------------- /jekyll-data.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "jekyll-data/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "jekyll-data" 9 | spec.version = JekyllData::VERSION 10 | spec.authors = ["Ashwin Maroli"] 11 | spec.email = ["ashmaroli@gmail.com"] 12 | 13 | spec.summary = "A plugin to read '_config.yml' and data files within Jekyll theme-gems" 14 | spec.homepage = "https://github.com/ashmaroli/jekyll-data" 15 | spec.license = "MIT" 16 | 17 | spec.metadata = { "allowed_push_host" => "https://rubygems.org" } 18 | 19 | spec.files = `git ls-files -z`.split("\x0").select do |f| 20 | f.match(%r!^(lib/|(LICENSE|README)((\.(txt|md|markdown)|$)))!i) 21 | end 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_runtime_dependency "jekyll", ">= 3.3", "< 5.0.0" 25 | 26 | spec.add_development_dependency "bundler", ">= 1.14.3" 27 | spec.add_development_dependency "cucumber", "~> 5.1.0" 28 | spec.add_development_dependency "minitest", "~> 5.0" 29 | spec.add_development_dependency "rake", "~> 13.0" 30 | spec.add_development_dependency "rubocop-jekyll", "~> 0.11.0" 31 | end 32 | -------------------------------------------------------------------------------- /test/fixtures/test-site/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "jekyll", ">= 3.3", "< 5.0.0" 6 | 7 | # use the local-versions of the gems for tests. 8 | # gem "test-plugin" is placed here to use the `:path` method to point to the local version. 9 | # 10 | # For real sites, there are following options: 11 | # 12 | # - If the theme-gem includes a '_config.yml' with the plugin listed under `gems:` 13 | # then: 14 | # - then there's no need to include the plugin in the Gemfile if (the plugin) 15 | # has been marked as a 'runtime_dependency'. 16 | # - otherwise, include it here, not necessarily under the ':jekyll_plugins' group 17 | # 18 | # - You'll have to include the plugin(s) either in your `_config.yml`, under 'gems:' 19 | # or in the Gemfile under ':jekyll_plugins' group 20 | gem "test-plugin", :path => File.expand_path("../test-plugin", __dir__) 21 | gem "test-theme", :path => File.expand_path("../test-theme", __dir__) 22 | 23 | # Recommended: all other plugins outside the theme's scope including 'jekyll-data' to be added under this group. 24 | group :jekyll_plugins do 25 | gem "another-test-plugin", :path => File.expand_path("../another-test-plugin", __dir__) 26 | gem "jekyll-data", :path => File.expand_path("../../../", __dir__) 27 | end 28 | -------------------------------------------------------------------------------- /test/fixtures/test-site/_posts/2017-01-01-welcome-to-jekyll.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: "Welcome to Jekyll!" 4 | date: 2016-01-01 09:00:00 5 | categories: jekyll update 6 | --- 7 | You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. 8 | 9 | To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works. 10 | 11 | Jekyll also offers powerful support for code snippets: 12 | 13 | {% highlight ruby %} 14 | def print_hi(name) 15 | puts "Hi, #{name}" 16 | end 17 | print_hi('Tom') 18 | #=> prints 'Hi, Tom' to STDOUT. 19 | {% endhighlight %} 20 | 21 | Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. 22 | 23 | [jekyll-docs]: https://jekyllrb.com/docs/home 24 | [jekyll-gh]: https://github.com/jekyll/jekyll 25 | [jekyll-talk]: https://talk.jekyllrb.com/ 26 | -------------------------------------------------------------------------------- /test/test_theme_configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "helper" 4 | 5 | class TestThemeConfiguration < JekyllDataTest 6 | context "site without data files but with a configured theme" do 7 | setup do 8 | @site = fixture_site( 9 | "source" => File.join(source_dir, "no_data_config"), 10 | "destination" => File.join(dest_dir, "no_data_config") 11 | ) 12 | assert @site.theme 13 | @theme_data = ThemeDataReader.new(@site).read("_data") 14 | @site.process 15 | end 16 | 17 | should "read and use data under the 'theme' namespace" do 18 | assert_equal( 19 | File.read(File.join(fixture_dir, "no_data_config_output.html")), 20 | File.read(@site.in_dest_dir("output.html")) 21 | ) 22 | end 23 | end 24 | 25 | context "site with theme configuration overrides" do 26 | setup do 27 | @site = fixture_site( 28 | "source" => File.join(source_dir, "empty_config_override"), 29 | "destination" => File.join(dest_dir, "empty_config_override") 30 | ) 31 | end 32 | 33 | should "alert if the override file is empty" do 34 | reader = double(@site) 35 | msg = "Cannot define or override Theme Configuration with an empty file!" 36 | expect(reader).to receive(:process).with(no_args).and_return(msg) 37 | assert_equal msg, reader.process 38 | end 39 | end 40 | 41 | context "site with theme configuration overrides" do 42 | setup do 43 | @site = fixture_site( 44 | "source" => File.join(source_dir, "not_hash_config_override"), 45 | "destination" => File.join(dest_dir, "not_hash_config_override") 46 | ) 47 | end 48 | 49 | should "alert if the override is not a Hash Object" do 50 | reader = double(@site) 51 | msg = "Theme Config or its override should be a Hash of key:value pairs " \ 52 | "or mappings. But got Array instead." 53 | expect(reader).to receive(:process).with(no_args).and_return(msg) 54 | assert_equal msg, reader.process 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/jekyll-data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | require "jekyll-data/version" 5 | 6 | module JekyllData 7 | autoload :Reader, "jekyll-data/reader" 8 | autoload :ThemedSiteDrop, "jekyll-data/themed_site_drop" 9 | autoload :ThemeDataReader, "jekyll-data/theme_data_reader" 10 | autoload :ThemeConfiguration, "jekyll-data/theme_configuration" 11 | end 12 | 13 | # Monkey-patches 14 | require_relative "jekyll/build_options" 15 | require_relative "jekyll/data_path" 16 | require_relative "jekyll/theme_drop" 17 | 18 | # ---------------------------------------------------------------------------- 19 | # Modify the current site instance only if it uses a gem-based theme. 20 | # 21 | # if a '_config.yml' is present at the root of theme-gem, it is evaluated and 22 | # the extracted hash data is incorprated into the site's config hash. 23 | # 24 | # *Jekyll 4.0 has this feature incorporated in its core.* 25 | # ---------------------------------------------------------------------------- 26 | unless Jekyll::VERSION.start_with?("4") 27 | Jekyll::Hooks.register :site, :after_reset do |site| 28 | if site.theme 29 | file = site.in_theme_dir("_config.yml") 30 | JekyllData::ThemeConfiguration.reconfigure(site) if File.exist?(file) 31 | end 32 | end 33 | end 34 | 35 | # --------------------------------------------------------------------------- 36 | # Replace Jekyll::Reader with a subclass JekyllData::Reader only if the 37 | # site uses a gem-based theme. 38 | # 39 | # If a _config.yml exists at the root of the theme-gem, output its path. 40 | # Placed here inorder to avoid outputting the path after every regeneration. 41 | # 42 | # *Jekyll 4.0 detects a theme-configuration natively.* 43 | # --------------------------------------------------------------------------- 44 | Jekyll::Hooks.register :site, :after_init do |site| 45 | if site.theme 46 | unless Jekyll::VERSION.start_with?("4") 47 | file = site.in_theme_dir("_config.yml") 48 | Jekyll.logger.info "Theme Config file:", file if File.exist?(file) 49 | end 50 | site.reader = JekyllData::Reader.new(site) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/jekyll-data/theme_configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllData 4 | class ThemeConfiguration < Jekyll::Configuration 5 | class << self 6 | # Public: Establish a new site.config hash by reading an optional config 7 | # file within the theme-gem and appending the resulting hash to 8 | # existing site.config filling in keys not already defined. 9 | # 10 | # site: current Jekyll::Site instance. 11 | # 12 | # Returns a config Hash to be used by an 'after_reset' hook. 13 | def reconfigure(site) 14 | default_hash = Jekyll::Configuration::DEFAULTS 15 | theme_config = ThemeConfiguration.new(site).read_theme_config 16 | 17 | # Merge with existing site.config and strip any remaining defaults 18 | config = Jekyll::Utils.deep_merge_hashes( 19 | theme_config, site.config 20 | ).reject { |key, value| value == default_hash[key] } 21 | 22 | # Merge DEFAULTS < _config.yml in theme-gem < config file(s) from source 23 | # and redefine site.config 24 | site.config = Jekyll::Configuration.from( 25 | Jekyll::Utils.deep_merge_hashes(theme_config, config) 26 | ) 27 | end 28 | end 29 | 30 | # 31 | 32 | def initialize(site) 33 | @site = site 34 | end 35 | 36 | # Public: Read the '_config.yml' file within the theme-gem. 37 | # Additionally validates that the extracted config data and the 38 | # the 'value' of ' key', when present, is a Hash. 39 | # 40 | # Returns a Configuration Hash 41 | def read_theme_config 42 | file = @site.in_theme_dir("_config.yml") 43 | theme_name = @site.theme.name 44 | 45 | config = safe_load_file(file) 46 | 47 | check_config_is_hash!(config, file) 48 | validate_config_hash config[theme_name] unless config[theme_name].nil? 49 | 50 | config 51 | end 52 | 53 | private 54 | 55 | # Validate the key's value to be accessed via the `theme` 56 | # namespace. 57 | def validate_config_hash(value) 58 | unless value.is_a? Hash 59 | Jekyll.logger.abort_with "JekyllData:", "Theme Configuration should be a " \ 60 | "Hash of key:value pairs or mappings. But got #{value.class} instead." 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rubygems" 4 | require "jekyll" 5 | require "minitest/autorun" 6 | require "minitest/reporters" 7 | require "minitest/profile" 8 | require "rspec/mocks" 9 | 10 | require_relative "../lib/jekyll-data.rb" 11 | 12 | Jekyll.logger = Logger.new(StringIO.new) 13 | 14 | require "kramdown" 15 | require "shoulda" 16 | 17 | # Report with color. 18 | Minitest::Reporters.use! [ 19 | Minitest::Reporters::DefaultReporter.new( 20 | :color => true 21 | ) 22 | ] 23 | 24 | module Minitest::Assertions 25 | def assert_exist(filename, msg = nil) 26 | msg = message(msg) { "Expected '#{filename}' to exist" } 27 | assert File.exist?(filename), msg 28 | end 29 | 30 | def refute_exist(filename, msg = nil) 31 | msg = message(msg) { "Expected '#{filename}' not to exist" } 32 | refute File.exist?(filename), msg 33 | end 34 | end 35 | 36 | module DirectoryHelpers 37 | def fixture_dir(*subdirs) 38 | test_dir("fixtures", *subdirs) 39 | end 40 | 41 | def dest_dir(*subdirs) 42 | test_dir("dest", *subdirs) 43 | end 44 | 45 | def source_dir(*subdirs) 46 | test_dir("source", *subdirs) 47 | end 48 | 49 | def test_dir(*subdirs) 50 | File.join(__dir__, *subdirs) 51 | end 52 | end 53 | 54 | class JekyllDataTest < Minitest::Test 55 | include Jekyll 56 | include JekyllData 57 | include DirectoryHelpers 58 | include ::RSpec::Mocks::ExampleMethods 59 | 60 | def mu_pp(obj) 61 | s = obj.is_a?(Hash) ? JSON.pretty_generate(obj) : obj.inspect 62 | s = s.encode Encoding.default_external if defined? Encoding 63 | s 64 | end 65 | 66 | def mocks_expect(*args) 67 | RSpec::Mocks::ExampleMethods::ExpectHost.instance_method(:expect)\ 68 | .bind(self).call(*args) 69 | end 70 | 71 | def before_setup 72 | RSpec::Mocks.setup 73 | super 74 | end 75 | 76 | def after_teardown 77 | super 78 | RSpec::Mocks.verify 79 | ensure 80 | RSpec::Mocks.teardown 81 | end 82 | 83 | def fixture_site(overrides = {}) 84 | Jekyll::Site.new(site_configuration(overrides)) 85 | end 86 | 87 | def default_configuration 88 | Marshal.load(Marshal.dump(Jekyll::Configuration::DEFAULTS)) 89 | end 90 | 91 | def build_configs(overrides, base_hash = default_configuration) 92 | Utils.deep_merge_hashes(base_hash, overrides) 93 | end 94 | 95 | def site_configuration(overrides = {}) 96 | full_overrides = build_configs( 97 | overrides, build_configs("incremental" => false) 98 | ) 99 | Configuration.from( 100 | full_overrides.merge( 101 | "theme" => "test-theme", 102 | "lang" => "en" 103 | ) 104 | ) 105 | end 106 | 107 | def clear_dest 108 | FileUtils.rm_rf(dest_dir) 109 | FileUtils.rm_rf(source_dir(".jekyll-metadata")) 110 | end 111 | 112 | def capture_output 113 | stderr = StringIO.new 114 | Jekyll.logger = Logger.new stderr 115 | yield 116 | stderr.rewind 117 | stderr.string.to_s 118 | end 119 | alias_method :capture_stdout, :capture_output 120 | alias_method :capture_stderr, :capture_output 121 | end 122 | -------------------------------------------------------------------------------- /features/theme_configuration.feature: -------------------------------------------------------------------------------- 1 | Feature: Configuring Gem-based Themes 2 | As a hacker who likes to share my expertise 3 | I want to be able to configure my gemified theme 4 | In order to make it easier for other Jekyllites to use my theme 5 | 6 | Scenario: A site not using a gem-based theme 7 | Given I have a configuration file with: 8 | | key | value | 9 | | exclude | [Gemfile, Gemfile.lock] | 10 | And I have a Gemfile with plugin: 11 | | name | path | 12 | | test-plugin | ../../test/fixtures/test-plugin | 13 | When I run bundle exec jekyll build 14 | Then I should get a zero exit status 15 | And the _site directory should exist 16 | And the "_site/test-feed.xml" file should exist 17 | 18 | Scenario: Theme-gem has a config file with valid 'gems' array 19 | Given I have a configuration file with: 20 | | key | value | 21 | | theme | test-theme | 22 | | exclude | [Gemfile, Gemfile.lock] | 23 | And I have a Gemfile with plugin: 24 | | name | path | 25 | | test-plugin | ../../test/fixtures/test-plugin | 26 | When I run bundle exec jekyll build 27 | Then I should get a zero exit status 28 | And the _site directory should exist 29 | And the "_site/test-feed.xml" file should exist 30 | 31 | Scenario: Overriding the 'gems' array in a config file within theme-gem 32 | Given I have a configuration file with: 33 | | key | value | 34 | | theme | test-theme | 35 | | gems | [jekyll-data, another-test-plugin] | 36 | | exclude | [Gemfile, Gemfile.lock] | 37 | And I have a Gemfile with plugin: 38 | | name | path | 39 | | another-test-plugin | ../../test/fixtures/another-test-plugin | 40 | When I run bundle exec jekyll build 41 | Then I should get a zero exit status 42 | And the _site directory should exist 43 | And the "_site/test-sitemap.xml" file should exist 44 | And the "_site/test-feed.xml" file should not exist 45 | 46 | Scenario: Theme-gem has a config file with valid '' object 47 | Given I have a configuration file with: 48 | | key | value | 49 | | theme | test-theme | 50 | | gems | [jekyll-data] | 51 | | exclude | [Gemfile, Gemfile.lock] | 52 | And I have a valid Gemfile 53 | And I have a "page.md" file with content: 54 | """ 55 | --- 56 | --- 57 | theme-logo : ![theme-logo]({{ theme.logo }}) 58 | theme-variant : {{ theme.theme_variant }} 59 | 60 | """ 61 | When I run bundle exec jekyll build 62 | Then I should get a zero exit status 63 | And the _site directory should exist 64 | And I should see "theme-logo : theme-logo" in "_site/page.html" 65 | And I should see "theme-variant : Charcoal" in "_site/page.html" 66 | -------------------------------------------------------------------------------- /test/test_theme_reader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "helper" 4 | 5 | class TestThemeReader < JekyllDataTest 6 | context "site without data files but with a valid theme" do 7 | setup do 8 | @site = fixture_site( 9 | "source" => File.join(source_dir, "no_data_files"), 10 | "destination" => File.join(dest_dir, "no_data_files") 11 | ) 12 | assert @site.theme 13 | @theme_data = ThemeDataReader.new(@site).read("_data") 14 | @site.process 15 | end 16 | should "read data files in theme gem" do 17 | assert_equal @site.data["navigation"]["topnav"], 18 | @theme_data["navigation"]["topnav"] 19 | end 20 | 21 | should "use data from theme gem" do 22 | assert_equal( 23 | File.read(File.join(fixture_dir, "no_data_output.html")), 24 | File.read(@site.in_dest_dir("output.html")) 25 | ) 26 | end 27 | end 28 | 29 | context "site with data keys different from a valid theme data hash" do 30 | setup do 31 | @site = fixture_site( 32 | "source" => File.join(source_dir, "diff_data_keys"), 33 | "destination" => File.join(dest_dir, "diff_data_keys") 34 | ) 35 | assert @site.theme 36 | @theme_data = ThemeDataReader.new(@site).read("_data") 37 | @site.process 38 | end 39 | should "read and use data from other keys in theme gem" do 40 | assert_equal( 41 | File.read(File.join(fixture_dir, "different_data_output.html")), 42 | File.read(@site.in_dest_dir("output.html")) 43 | ) 44 | end 45 | 46 | should "not override theme data" do 47 | assert_equal( 48 | File.read(File.join(fixture_dir, "different_data_output.html")), 49 | File.read(@site.in_dest_dir("output.html")) 50 | ) 51 | end 52 | end 53 | 54 | context "site with same data keys as a valid theme data hash" do 55 | setup do 56 | @site = fixture_site( 57 | "source" => File.join(source_dir, "same_data_files"), 58 | "destination" => File.join(dest_dir, "same_data_files") 59 | ) 60 | assert @site.theme 61 | @theme_data = ThemeDataReader.new(@site).read("_data") 62 | @site.process 63 | end 64 | should "override theme data" do 65 | assert_equal( 66 | File.read(File.join(fixture_dir, "same_data_override.html")), 67 | File.read(@site.in_dest_dir("override.html")) 68 | ) 69 | end 70 | 71 | should "also use data from other keys in theme gem" do 72 | assert_equal( 73 | File.read(File.join(fixture_dir, "same_data_output.html")), 74 | File.read(@site.in_dest_dir("override.html")) 75 | ) 76 | end 77 | end 78 | 79 | context "theme gem shipped with a '_config.yml'" do 80 | setup do 81 | @site = fixture_site( 82 | "title" => "Config Test" 83 | ) 84 | end 85 | 86 | should "have its hash appended to site's config hash" do 87 | assert_contains @site.config, %w(post_excerpts enabled) 88 | end 89 | 90 | should "have its hash added only where its not already set" do 91 | refute_equal "Test Theme", @site.config["title"] 92 | assert_equal "Config Test", @site.config["title"] 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /features/support/helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | require "jekyll" 5 | require "open3" 6 | require "time" 7 | require "safe_yaml/load" 8 | 9 | class Paths 10 | SOURCE_DIR = Pathname.new(File.expand_path("../..", __dir__)) 11 | def self.test_dir; source_dir.join("tmp", "jekyll-data"); end 12 | 13 | def self.output_file; test_dir.join("jekyll_output.txt"); end 14 | 15 | def self.status_file; test_dir.join("jekyll_status.txt"); end 16 | 17 | def self.source_dir; SOURCE_DIR; end 18 | end 19 | 20 | def file_content_from_hash(input_hash) 21 | matter_hash = input_hash.reject { |k, _v| k == "content" } 22 | matter = matter_hash.map { |k, v| "#{k}: #{v}\n" }.join 23 | matter.chomp! 24 | content = if input_hash["input"] && input_hash["filter"] 25 | "{{ #{input_hash["input"]} | #{input_hash["filter"]} }}" 26 | else 27 | input_hash["content"] 28 | end 29 | 30 | <<~RESULT 31 | --- 32 | #{matter} 33 | --- 34 | 35 | #{content} 36 | RESULT 37 | end 38 | 39 | def source_dir(*files) 40 | Paths.test_dir(*files) 41 | end 42 | 43 | def all_steps_to_path(path) 44 | source = source_dir 45 | dest = Pathname.new(path).expand_path 46 | paths = [] 47 | 48 | dest.ascend do |f| 49 | break if f == source 50 | 51 | paths.unshift f.to_s 52 | end 53 | 54 | paths 55 | end 56 | 57 | def jekyll_run_output 58 | Paths.output_file.read if Paths.output_file.file? 59 | end 60 | 61 | def jekyll_run_status 62 | Paths.status_file.read if Paths.status_file.file? 63 | end 64 | 65 | def run_bundle(args) 66 | run_in_shell("bundle", *args.strip.split(" ")) 67 | end 68 | 69 | def run_rubygem(args) 70 | run_in_shell("gem", *args.strip.split(" ")) 71 | end 72 | 73 | def run_jekyll(args) 74 | args = args.strip.split(" ") # Shellwords? 75 | process = run_in_shell("ruby", Paths.jekyll_bin.to_s, *args, "--trace") 76 | process.exitstatus.zero? 77 | end 78 | 79 | # rubocop:disable Metrics/AbcSize 80 | def run_in_shell(*args) 81 | i, o, e, p = Open3.popen3(*args) 82 | out = o.read.strip 83 | err = e.read.strip 84 | 85 | [i, o, e].each(&:close) 86 | 87 | File.write(Paths.status_file, p.value.exitstatus) 88 | File.open(Paths.output_file, "wb") do |f| 89 | f.print "$ " 90 | f.puts args.join(" ") 91 | f.puts out 92 | f.puts err 93 | f.puts "EXIT STATUS: #{p.value.exitstatus}" 94 | end 95 | 96 | p.value 97 | end 98 | # rubocop:enable Metrics/AbcSize 99 | 100 | def slug(title = nil) 101 | if !title 102 | Time.now.strftime("%s%9N") # nanoseconds since the Epoch 103 | else title.downcase.gsub(%r![^\w]!, " ").strip.gsub(%r!\s+!, "-") 104 | end 105 | end 106 | 107 | def location(folder, direction) 108 | if folder 109 | before = folder if direction == "in" 110 | after = folder if direction == "under" 111 | end 112 | 113 | [ 114 | before || ".", 115 | after || ".", 116 | ] 117 | end 118 | 119 | def file_contents(path) 120 | Pathname.new(path).read 121 | end 122 | 123 | def seconds_agnostic_datetime(datetime = Time.now) 124 | date, time, zone = datetime.to_s.split(" ") 125 | time = seconds_agnostic_time(time) 126 | 127 | [ 128 | Regexp.escape(date), 129 | "#{time}:\\d{2}", 130 | Regexp.escape(zone), 131 | ].join("\\ ") 132 | end 133 | 134 | def seconds_agnostic_time(time) 135 | time = time.strftime("%H:%M:%S") if time.is_a?(Time) 136 | hour, minutes, = time.split(":") 137 | "#{hour}:#{minutes}" 138 | end 139 | -------------------------------------------------------------------------------- /features/theme_data_reader.feature: -------------------------------------------------------------------------------- 1 | Feature: Reading Data files in Gem-based Themes 2 | As a hacker who likes to share my expertise 3 | I want to be able to include data files in my gemified theme 4 | In order to supplement the templates with default text-strings 5 | 6 | Scenario: A site not using a gem-based theme 7 | Given I have a configuration file with: 8 | | key | value | 9 | | exclude | [Gemfile, Gemfile.lock] | 10 | And I have a Gemfile with plugin: 11 | | name | path | 12 | | test-plugin | ../../test/fixtures/test-plugin | 13 | When I run bundle exec jekyll build 14 | Then I should get a zero exit status 15 | And the _site directory should exist 16 | And the "_site/test-feed.xml" file should exist 17 | 18 | Scenario: Theme-gem has a data file to support i18n 19 | Given I have a configuration file with: 20 | | key | value | 21 | | lang | fr | 22 | | theme | test-theme | 23 | | gems | [jekyll-data] | 24 | | exclude | [Gemfile, Gemfile.lock] | 25 | And I have a "locales.md" file with content: 26 | """ 27 | --- 28 | --- 29 | 30 | {% assign ui = site.data.locales[site.lang] %} 31 | {% assign user = "John Smith" %} 32 | 33 | {{ ui.greeting }} {{ user }} 34 | 35 | {{ ui.prev }} 36 | 37 | {{ ui.next }} 38 | """ 39 | And I have a valid Gemfile 40 | When I run bundle exec jekyll build 41 | Then I should get a zero exit status 42 | And the _site directory should exist 43 | And I should see "Bonjour, Bienvenue! John Smith" in "_site/locales.html" 44 | And I should see "précédent" in "_site/locales.html" 45 | And I should see "prochain" in "_site/locales.html" 46 | 47 | Scenario: Overriding a data file within theme-gem - Method I 48 | Given I have a configuration file with: 49 | | key | value | 50 | | lang | fr | 51 | | theme | test-theme | 52 | | gems | [jekyll-data] | 53 | | exclude | [Gemfile, Gemfile.lock] | 54 | And I have a "locales.md" file with content: 55 | """ 56 | --- 57 | --- 58 | 59 | {% assign ui = site.data.locales[site.lang] %} 60 | {% assign user = "John Smith" %} 61 | 62 | {{ ui.greeting }} {{ user }}! 63 | 64 | {{ ui.prev }} 65 | 66 | {{ ui.next }} 67 | """ 68 | And I have a _data directory 69 | And I have a "_data/locales.yml" file with content: 70 | """ 71 | fr: 72 | greeting: "Bonjour! Bienvenue" 73 | """ 74 | And I have a valid Gemfile 75 | When I run bundle exec jekyll build 76 | Then I should get a zero exit status 77 | And the _site directory should exist 78 | And I should see "Bonjour! Bienvenue John Smith!" in "_site/locales.html" 79 | And I should see "précédent" in "_site/locales.html" 80 | And I should see "prochain" in "_site/locales.html" 81 | 82 | Scenario: Overriding a data file within theme-gem - Method II 83 | Given I have a configuration file with: 84 | | key | value | 85 | | lang | fr | 86 | | theme | test-theme | 87 | | gems | [jekyll-data] | 88 | | exclude | [Gemfile, Gemfile.lock] | 89 | And I have a "locales.md" file with content: 90 | """ 91 | --- 92 | --- 93 | 94 | {% assign ui = site.data.locales[site.lang] %} 95 | {% assign user = "John Smith" %} 96 | 97 | {{ ui.greeting }} {{ user }}! 98 | 99 | {{ ui.prev }} 100 | 101 | {{ ui.next }} 102 | """ 103 | And I have a _data/locales directory 104 | And I have a "_data/locales/fr.yml" file with content: 105 | """ 106 | greeting: "Bonjour! Bienvenue" 107 | """ 108 | And I have a valid Gemfile 109 | When I run bundle exec jekyll build 110 | Then I should get a zero exit status 111 | And the _site directory should exist 112 | And I should see "Bonjour! Bienvenue John Smith!" in "_site/locales.html" 113 | And I should see "précédent" in "_site/locales.html" 114 | And I should see "prochain" in "_site/locales.html" 115 | -------------------------------------------------------------------------------- /features/step_definitions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Before do 4 | FileUtils.rm_rf(Paths.test_dir) if Paths.test_dir.exist? 5 | FileUtils.mkdir_p(Paths.test_dir) unless Paths.test_dir.directory? 6 | Dir.chdir(Paths.test_dir) 7 | end 8 | 9 | # 10 | 11 | After do 12 | FileUtils.rm_rf(Paths.test_dir) if Paths.test_dir.exist? 13 | Paths.output_file.delete if Paths.output_file.exist? 14 | Paths.status_file.delete if Paths.status_file.exist? 15 | Dir.chdir(Paths.test_dir.parent) 16 | end 17 | 18 | # 19 | 20 | Given(%r!^I have an? "(.*)" file that contains "(.*)"$!) do |file, text| 21 | File.write(file, text) 22 | end 23 | 24 | # 25 | 26 | Given(%r!^I have an? "(.*)" file with content:$!) do |file, text| 27 | File.write(file, text) 28 | end 29 | 30 | # 31 | 32 | Given(%r!^I have an? (.*) directory$!) do |dir| 33 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 34 | end 35 | 36 | # 37 | 38 | Given(%r!^I have a configuration file with "(.*)" set to "(.*)"$!) do |key, value| 39 | config = \ 40 | if source_dir.join("_config.yml").exist? 41 | SafeYAML.load_file(source_dir.join("_config.yml")) 42 | else 43 | {} 44 | end 45 | config[key] = YAML.safe_load(value) 46 | File.write("_config.yml", YAML.dump(config)) 47 | end 48 | 49 | # 50 | 51 | Given(%r!^I have a configuration file with:$!) do |table| 52 | table.hashes.each do |row| 53 | step %(I have a configuration file with "#{row["key"]}" set to "#{row["value"]}") 54 | end 55 | end 56 | 57 | # 58 | 59 | Given(%r!^I have a configuration file with "([^\"]*)" set to:$!) do |key, table| 60 | File.open("_config.yml", "w") do |f| 61 | f.write("#{key}:\n") 62 | table.hashes.each do |row| 63 | f.write("- #{row["value"]}\n") 64 | end 65 | end 66 | end 67 | 68 | # 69 | 70 | Given(%r!^I have a valid Gemfile$!) do 71 | File.write("Gemfile", <<~DATA) 72 | gem "test-theme", path: File.expand_path( 73 | "../../test/fixtures/test-theme", __dir__ 74 | ) 75 | 76 | group :jekyll_plugins do 77 | gem "jekyll-data", path: File.expand_path("../../", __dir__) 78 | # any other plugins 79 | end 80 | 81 | DATA 82 | end 83 | 84 | # 85 | 86 | Given(%r!^I have a Gemfile with plugins?:$!) do |table| 87 | step %(I have a valid Gemfile) 88 | table.hashes.each do |row| 89 | step %(I have a Gemfile with "#{row["name"]}" plugin set to "#{row["path"]}") 90 | end 91 | end 92 | 93 | # 94 | 95 | Given(%r!^I have a Gemfile with (.*) plugin set to (.*)$!) do |name, path| 96 | content = File.read("Gemfile") 97 | File.write( 98 | "Gemfile", 99 | content.gsub( 100 | "# any other plugins", 101 | "gem #{name}, path: File.expand_path(#{path}, __dir__)\n # any other plugins" 102 | ) 103 | ) 104 | end 105 | 106 | # 107 | 108 | When(%r!^I run jekyll(.*)$!) do |args| 109 | run_jekyll(args) 110 | warn "\n#{jekyll_run_output}\n" if args.include?("--verbose") || ENV["DEBUG"] 111 | end 112 | 113 | # 114 | 115 | When(%r!^I run bundle(.*)$!) do |args| 116 | ENV["BUNDLE_GEMFILE"] = Paths.test_dir.join("Gemfile").to_s 117 | run_bundle(args) 118 | warn "\n#{jekyll_run_output}\n" if args.include?("--verbose") || ENV["DEBUG"] 119 | end 120 | 121 | # 122 | 123 | When(%r!^I change "(.*)" to contain "(.*)"$!) do |file, text| 124 | File.open(file, "a") do |f| 125 | f.write(text) 126 | end 127 | end 128 | 129 | # 130 | 131 | When(%r!^I delete the file "(.*)"$!) do |file| 132 | File.delete(file) 133 | end 134 | 135 | # 136 | 137 | Then(%r!^the (.*) directory should +(not )?exist$!) do |dir, negative| 138 | if negative.nil? 139 | expect(Pathname.new(dir)).to exist 140 | else 141 | expect(Pathname.new(dir)).to_not exist 142 | end 143 | end 144 | 145 | # 146 | 147 | Then(%r!^I should (not )?see "(.*)" in "(.*)"$!) do |negative, text, file| 148 | step %(the "#{file}" file should exist) 149 | regexp = Regexp.new(text, Regexp::MULTILINE) 150 | if negative.nil? || negative.empty? 151 | expect(file_contents(file)).to match regexp 152 | else 153 | expect(file_contents(file)).not_to match regexp 154 | end 155 | end 156 | 157 | # 158 | 159 | Then(%r!^I should see exactly "(.*)" in "(.*)"$!) do |text, file| 160 | step %(the "#{file}" file should exist) 161 | expect(file_contents(file).strip).to eq text 162 | end 163 | 164 | # 165 | 166 | Then(%r!^the "(.*)" file should +(not )?exist$!) do |file, negative| 167 | if negative.nil? 168 | expect(Pathname.new(file)).to exist 169 | else 170 | expect(Pathname.new(file)).to_not exist 171 | end 172 | end 173 | 174 | # 175 | 176 | Then(%r!^I should (not )?see "(.*)" in the build output$!) do |negative, text| 177 | if negative.nil? || negative.empty? 178 | expect(jekyll_run_output).to match Regexp.new(text) 179 | else 180 | expect(jekyll_run_output).not_to match Regexp.new(text) 181 | end 182 | end 183 | 184 | # 185 | 186 | Then(%r!^I should get a zero exit(?:\-| )status$!) do 187 | step %(I should see "EXIT STATUS: 0" in the build output) 188 | end 189 | 190 | # 191 | 192 | Then(%r!^I should get a non-zero exit(?:\-| )status$!) do 193 | step %(I should not see "EXIT STATUS: 0" in the build output) 194 | end 195 | -------------------------------------------------------------------------------- /features/support/formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | require "colorator" 5 | require "cucumber/formatter/console" 6 | require "cucumber/formatter/io" 7 | 8 | module Jekyll 9 | module Cucumber 10 | class Formatter 11 | attr_accessor :indent, :runtime 12 | include ::Cucumber::Formatter::Console 13 | include ::Cucumber::Formatter::Io 14 | include FileUtils 15 | 16 | CHARS = { 17 | :failed => "\u2718".red, 18 | :pending => "\u203D".yellow, 19 | :undefined => "\u2718".red, 20 | :passed => "\u2714".green, 21 | :skipped => "\u203D".blue, 22 | }.freeze 23 | 24 | # 25 | 26 | def initialize(runtime, path_or_io, options) 27 | @runtime = runtime 28 | @snippets_input = [] 29 | @io = ensure_io(path_or_io) 30 | @prefixes = options[:prefixes] || {} 31 | @delayed_messages = [] 32 | @options = options 33 | @exceptions = [] 34 | @indent = 0 35 | @timings = {} 36 | end 37 | 38 | # 39 | 40 | def before_features(_features) 41 | print_profile_information 42 | end 43 | 44 | # 45 | 46 | def after_features(features) 47 | @io.puts 48 | print_worst_offenders 49 | print_summary(features) 50 | end 51 | 52 | # 53 | 54 | def before_feature(_feature) 55 | @exceptions = [] 56 | @indent = 0 57 | end 58 | 59 | # 60 | 61 | def feature_element_timing_key(feature_element) 62 | "\"#{feature_element.name}\" (#{feature_element.location})" 63 | end 64 | 65 | # 66 | 67 | def before_feature_element(feature_element) 68 | @indent = 2 69 | @scenario_indent = 2 70 | @timings[feature_element_timing_key(feature_element)] = Time.now 71 | end 72 | 73 | # 74 | 75 | def after_feature_element(feature_element) 76 | @timings[feature_element_timing_key(feature_element)] = Time.now - @timings[feature_element_timing_key(feature_element)] 77 | @io.print " (#{@timings[feature_element_timing_key(feature_element)]}s)" 78 | end 79 | 80 | # 81 | 82 | def tag_name(tag_name); end 83 | 84 | def comment_line(comment_line); end 85 | 86 | def after_tags(tags); end 87 | 88 | # 89 | 90 | def before_background(_background) 91 | @scenario_indent = 2 92 | @in_background = true 93 | @indent = 2 94 | end 95 | 96 | # 97 | 98 | def after_background(_background) 99 | @in_background = nil 100 | end 101 | 102 | # 103 | 104 | def background_name(keyword, name, source_line, indent) 105 | print_feature_element_name( 106 | keyword, name, source_line, indent 107 | ) 108 | end 109 | 110 | # 111 | 112 | def scenario_name(keyword, name, source_line, indent) 113 | print_feature_element_name( 114 | keyword, name, source_line, indent 115 | ) 116 | end 117 | 118 | # 119 | 120 | def before_step(step) 121 | @current_step = step 122 | end 123 | 124 | # 125 | 126 | # rubocop:disable Metrics/ParameterLists 127 | def before_step_result(_keyword, _step_match, _multiline_arg, status, exception, \ 128 | _source_indent, background, _file_colon_line) 129 | 130 | @hide_this_step = false 131 | if exception 132 | if @exceptions.include?(exception) 133 | @hide_this_step = true 134 | return 135 | end 136 | 137 | @exceptions << exception 138 | end 139 | 140 | if status != :failed && @in_background ^ background 141 | @hide_this_step = true 142 | return 143 | end 144 | 145 | @status = status 146 | end 147 | 148 | # 149 | 150 | def step_name(_keyword, _step_match, status, _source_indent, _background, _file_colon_line) 151 | @io.print CHARS[status] 152 | @io.print " " 153 | end 154 | # rubocop:enable Metrics/ParameterLists 155 | 156 | # 157 | 158 | def exception(exception, status) 159 | return if @hide_this_step 160 | 161 | @io.puts 162 | print_exception(exception, status, @indent) 163 | @io.flush 164 | end 165 | 166 | # 167 | 168 | def after_test_step(test_step, result) 169 | collect_snippet_data( 170 | test_step, result 171 | ) 172 | end 173 | 174 | # 175 | 176 | def print_feature_element_name(feature_element) 177 | @io.print "\n#{feature_element.location} Scenario: #{feature_element.name} " 178 | @io.flush 179 | end 180 | 181 | # 182 | 183 | def cell_prefix(status) 184 | @prefixes[status] 185 | end 186 | 187 | # 188 | 189 | def print_worst_offenders 190 | @io.puts 191 | @io.puts "Worst offenders:" 192 | @timings.sort_by { |_f, t| -t }.take(10).each do |(f, t)| 193 | @io.puts " #{t}s for #{f}" 194 | end 195 | @io.puts 196 | end 197 | 198 | # 199 | 200 | def print_summary(features) 201 | @io.puts 202 | print_stats(features, @options) 203 | print_snippets(@options) 204 | print_passing_wip(@options) 205 | end 206 | end 207 | end 208 | end 209 | 210 | AfterConfiguration do |config| 211 | f = Jekyll::Cucumber::Formatter.new(nil, $stdout, {}) 212 | 213 | config.on_event :test_case_started do |event| 214 | f.print_feature_element_name(event.test_case) 215 | f.before_feature_element(event.test_case) 216 | end 217 | 218 | config.on_event :test_case_finished do |event| 219 | f.after_feature_element(event.test_case) 220 | end 221 | 222 | config.on_event :test_run_finished do 223 | f.print_worst_offenders 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JekyllData 2 | 3 | [![Gem Version](https://img.shields.io/gem/v/jekyll-data.svg)](https://rubygems.org/gems/jekyll-data) 4 | [![Build Status](https://img.shields.io/travis/ashmaroli/jekyll-data/master.svg?label=Build%20Status)][travis] 5 | 6 | [travis]: https://travis-ci.org/ashmaroli/jekyll-data 7 | 8 | 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. 9 | 10 | 11 | ## Installation 12 | 13 | Simply add the plugin to your site's Gemfile and config file like every other jekyll plugin gem: 14 | 15 | ```ruby 16 | # Gemfile 17 | 18 | group :jekyll_plugins do 19 | gem "jekyll-data" 20 | end 21 | ``` 22 | ..and run 23 | 24 | bundle install 25 | 26 | 27 | > **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.** 28 | 29 | 30 | ## Usage 31 | 32 | 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. 33 | 34 | 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: 35 | 36 | **DEFAULTS** < **_config.yml in theme-gem** < **_config.yml at source** < **Override configs via command-line**. 37 | 38 | 39 | ### Theme Configuration 40 | 41 | 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. 42 | 43 | This plugin provides a solution to that hurdle: 44 | 45 | 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. 46 | 47 | **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.** 48 | 49 | #### The `theme` namespace 50 | 51 | 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`. 52 | 53 | 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`: 54 | 55 | ```yaml 56 | # /_config.yml 57 | 58 | # the settings below have been used in this theme's templates via the `theme` 59 | # namespace. e.g. `{{ theme.recent_posts.style }}` instead of using the more 60 | # verbose `{{ site.solitude.recent_posts.style }}` though both are functionally 61 | # the same. 62 | # 63 | solitude: 64 | sidebar : true # enter 'false' to enable horizontal navbar instead. 65 | theme_variant : Charcoal # choose from 'Ocean', 'Grass', 'Charcoal' 66 | recent_posts : 67 | style : list # choose from 'list' and 'grid'. 68 | quantity : '4' # either '4' or '6' 69 | 70 | ``` 71 | 72 | 73 | ### Data files 74 | 75 | Data files may be used to supplement theme templates (e.g. locales and translated UI text) and can be named as desired. 76 | - Organize related small data files in sub-directories. (or) 77 | - Declare all related data as mapped data blocks within a single file. 78 | 79 | To illustrate with an example, consider a `locales.yml` that has mappings for `en:`, `fr:`, `it:`. 80 | 81 | ```yaml 82 | # /_data/locales.yml 83 | 84 | en: 85 | previous : previous 86 | next : next 87 | 88 | fr: 89 | previous : précédent 90 | next : prochain 91 | 92 | it: 93 | previous : precedente 94 | next : successivo 95 | ``` 96 | 97 | 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. 98 | 99 | 100 | ### Overriding Data Files 101 | 102 | To override data shipped with a theme-gem, simply have an identical hash at the site-source. 103 | 104 | 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. 105 | 106 | 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: 107 | - a **`_data/locales/fr.yml`** with identical subkey(s). 108 | - a **`_data/locales.yml`** with **`fr:`** with identical subkey(s). 109 | 110 | -- 111 | > **Note** 112 | - having an **empty** `_data/locales.yml` at `source` directory will override the **entire `["data"]["locales"]` payload** from the theme-gem as **`false`**. 113 | - having an **empty** `_data/locales/fr.yml` at `source` directory will override the **enire `["data"]["locales"]["fr"]` payload** from the theme-gem as **`false`** 114 | 115 | 116 | ## Contributing 117 | 118 | 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. 119 | 120 | 121 | ## License 122 | 123 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 124 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.1.1 / 2021-01-24 2 | 3 | #### Bug Fixes 4 | 5 | * Don't fail if the theme doesn't have a data dir. **[[`#36`][]]** by [@fauno][] 6 | * Fix incompatibility with Jekyll 4.2. **[[`#38`][]]** by [@fauno][] 7 | 8 | [`#36`]: https://github.com/ashmaroli/jekyll-data/pull/36 9 | [`#38`]: https://github.com/ashmaroli/jekyll-data/pull/38 10 | [@fauno]: https://github.com/fauno 11 | 12 | 13 | #### Development Improvements 14 | 15 | * Bump development dependencies and tested Ruby versions 16 | 17 | 18 | #### Documentation 19 | 20 | * Use "successivo" as it's more widely used. **[[`#37`][]]** by [@Samplasion][] 21 | 22 | [`#37`]: https://github.com/ashmaroli/jekyll-data/pull/37 23 | [@Samplasion]: https://github.com/Samplasion 24 | 25 | 26 | ### 1.1.0 / 2019-09-09 27 | 28 | #### Minor Enhancements 29 | 30 | * Don't abort if the site doesn't use a gem-based theme. **[[`#25`][]]** 31 | * Replace `File.dirname(__FILE__)` with `__dir__`. **[[`#26`][], [`e3694c8`][]]** 32 | * Use frozen String literals. **[[`#27`][]]** 33 | * Add support for Jekyll 4.0. **[[`#30`][], [`#32`][]]** 34 | * Add support to read TSV files **[[`1c0b048`][]]** 35 | 36 | [`#25`]: https://github.com/ashmaroli/jekyll-data/pull/25 37 | [`#26`]: https://github.com/ashmaroli/jekyll-data/pull/26 38 | [`#27`]: https://github.com/ashmaroli/jekyll-data/pull/27 39 | [`#30`]: https://github.com/ashmaroli/jekyll-data/pull/30 40 | [`#32`]: https://github.com/ashmaroli/jekyll-data/pull/32 41 | [`e3694c8`]: https://github.com/ashmaroli/jekyll-data/commit/e3694c8c8bc2c064f549a7ecacad65b1bf7db7e0 42 | [`1c0b048`]: https://github.com/ashmaroli/jekyll-data/commit/1c0b048f7c1d8146b0808fee5263b7b06b1fa6de 43 | 44 | 45 | #### Bug Fixes 46 | 47 | * Base theme-drop on fallback-data. **[[`#29`][]]** 48 | * Properly designate `Jekyll::Drops::SiteDrop#fallback_data` as a `private` method. **[[`bb0447b`][]]** 49 | * Memoize Jekyll::Theme#data_path. **[[`7feb5d6`][]]** 50 | 51 | [`#29`]: https://github.com/ashmaroli/jekyll-data/pull/29 52 | [`bb0447b`]: https://github.com/ashmaroli/jekyll-data/commit/bb0447b242c36f2277c07b45a37b053b56aa8d37 53 | [`7feb5d6`]: https://github.com/ashmaroli/jekyll-data/commit/7feb5d6921e0bcadbed7841d7e0d76c2b141c468 54 | 55 | 56 | #### Development Improvements 57 | 58 | * Update Travis configuration. **[[`#31`][]]** 59 | * Use `rubocop-jekyll` to enforce Jekyll's style-guide **[[`20c0550`][]]** 60 | 61 | [`#31`]: https://github.com/ashmaroli/jekyll-data/pull/31 62 | [`20c0550`]: https://github.com/ashmaroli/jekyll-data/commit/20c0550bcb2dacf8a202522bd576fe3aebbfb8a0 63 | 64 | 65 | ### 1.0.0 / 2017-02-15 66 | 67 | #### Major Enhancements 68 | 69 | * `{{ theme.myvariable }}` now points to `site..myvariable` instead of `site.data..myvariable`. **[[`#08`][]]** 70 | * extracting a theme-gem's config hash and incorporating it into the site's internal config hash is handled by a new `ThemeConfiguration` class. **[[`#09`][], [`#11`][]]** 71 | * All new classes are now loaded under `JekyllData` module. The `Jekyll` namespace will only contain patches to the original `Jekyll` module or its classes. **[[`#15`][], [`#22`][]]** 72 | 73 | [`#08`]: https://github.com/ashmaroli/jekyll-data/pull/8 74 | [`#09`]: https://github.com/ashmaroli/jekyll-data/pull/9 75 | [`#11`]: https://github.com/ashmaroli/jekyll-data/pull/11 76 | [`#15`]: https://github.com/ashmaroli/jekyll-data/pull/15 77 | [`#22`]: https://github.com/ashmaroli/jekyll-data/pull/22 78 | 79 | 80 | #### Minor Enhancements 81 | 82 | * A new switch `--show-data` has been added to Jekyll's build-options as a supplement to existing `--verbose` switch. **[[`#13`][], [`#18`][], [`#20`][]]** 83 | * Debug data output from `--show-data` is now at a fixed width and wraps nicely to the next line. **[[`#17`][], [`#19`][]]** 84 | 85 | [`#13`]: https://github.com/ashmaroli/jekyll-data/pull/13 86 | [`#17`]: https://github.com/ashmaroli/jekyll-data/pull/17 87 | [`#18`]: https://github.com/ashmaroli/jekyll-data/pull/18 88 | [`#19`]: https://github.com/ashmaroli/jekyll-data/pull/19 89 | [`#20`]: https://github.com/ashmaroli/jekyll-data/pull/20 90 | 91 | 92 | #### Bug Fixes 93 | 94 | * A theme-gem's config hash is now incorporated via the `after-reset` hook to enable Jekyll `require` the necessary plugins listed in the theme-gem's config file. **[[`#12`][]]** 95 | 96 | [`#12`]: https://github.com/ashmaroli/jekyll-data/pull/12 97 | 98 | 99 | #### Development Improvements 100 | 101 | * Improved test-suite. **[[`#14`][], [`#16`][]]** 102 | * Added cucumber `features` and a script for build assessment. 103 | * Added a couple of dummy plugins to test loading of plugins listed in a theme-gem's config file. 104 | * Included testing with Ruby 2.4.0. 105 | * Document the main gems used for testing via `gemspec`. Upgrade Bundler to v1.14.3 and above. **[[`#23`][]]** 106 | 107 | [`#14`]: https://github.com/ashmaroli/jekyll-data/pull/14 108 | [`#16`]: https://github.com/ashmaroli/jekyll-data/pull/16 109 | [`#23`]: https://github.com/ashmaroli/jekyll-data/pull/23 110 | 111 | 112 | #### Documentation 113 | 114 | * Update and improve documentation. **[[`#10`][]]** 115 | * Added this `CHANGELOG.md`. **[[`#24`][]]** 116 | 117 | [`#10`]: https://github.com/ashmaroli/jekyll-data/pull/10 118 | [`#24`]: https://github.com/ashmaroli/jekyll-data/pull/24 119 | 120 | 121 | -- 122 | 123 | ### 0.4.0 / 2016-12-14 124 | 125 | #### Minor Improvements 126 | 127 | * Read a `_config.yml` within theme-gems, extract and merge the hash with the site's original config hash. **[[`#06`][]]** 128 | * Abort build process when the `theme` key has not been configured or has been commented out. 129 | 130 | [`#06`]: https://github.com/ashmaroli/jekyll-data/pull/6 131 | 132 | 133 | #### Documentation 134 | 135 | * Update README to document recent developments 136 | * Fix typos within comments in `.rb` file. 137 | 138 | 139 | -- 140 | 141 | ### 0.3.0 / 2016-11-21 142 | 143 | #### Minor Improvements 144 | 145 | * Validate theme configuration (`_data/.yml`) and its override. **[[`#05`][]]** 146 | 147 | #### Development Improvements 148 | 149 | * Add Continuous Integration with Travis CI. **[[`#03`][]]** 150 | * Add and update files to run Minitest. **[[`#04`][]]** 151 | 152 | [`#03`]: https://github.com/ashmaroli/jekyll-data/pull/3 153 | [`#04`]: https://github.com/ashmaroli/jekyll-data/pull/4 154 | [`#05`]: https://github.com/ashmaroli/jekyll-data/pull/5 155 | 156 | 157 | -- 158 | 159 | ### 0.2.1 / 2016-10-21 160 | 161 | * Alter methods for debugging. **[[`#02`][]]** 162 | * Clarify plugin's actions in README. 163 | 164 | [`#02`]: https://github.com/ashmaroli/jekyll-data/pull/2 165 | 166 | 167 | -- 168 | 169 | ### 0.2.0 / 2016-10-18 170 | 171 | * Flesh out README with proper installation & usage instructions. 172 | * Refactor private debugging methods. **[[`#01`][]]** 173 | * Add Gem-Version badge to README. 174 | 175 | [`#01`]: https://github.com/ashmaroli/jekyll-data/pull/1 176 | 177 | 178 | -- 179 | 180 | ### Initial Release / 2016-10-16 [YANKED] 181 | -------------------------------------------------------------------------------- /lib/jekyll-data/reader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "csv" 4 | 5 | module JekyllData 6 | class Reader < Jekyll::Reader 7 | def initialize(site) 8 | @site = site 9 | @theme = site.theme 10 | 11 | if @theme.data_path 12 | @theme_data_files = Dir[File.join(@theme.data_path, "**", "*.{yaml,yml,json,csv,tsv}")] 13 | end 14 | end 15 | 16 | # Read data files within theme-gem. 17 | # 18 | # Returns nothing. 19 | def read 20 | super 21 | read_theme_data 22 | end 23 | 24 | # Read data files within a theme gem and add them to internal data 25 | # 26 | # Returns a hash appended with new data 27 | def read_theme_data 28 | if @theme.data_path 29 | # 30 | # show contents of "/_data/" dir being read while degugging. 31 | inspect_theme_data 32 | theme_data = ThemeDataReader.new(site).read(site.config["data_dir"]) 33 | @site.data = Jekyll::Utils.deep_merge_hashes(theme_data, @site.data) 34 | # 35 | # show contents of merged site.data hash while debugging with 36 | # additional --show-data switch. 37 | inspect_merged_hash if site.config["show-data"] && site.config["verbose"] 38 | end 39 | end 40 | 41 | private 42 | 43 | # Private: 44 | # (only while debugging) 45 | # 46 | # Print a list of data file(s) within the theme-gem 47 | def inspect_theme_data 48 | print_clear_line 49 | Jekyll.logger.debug "Reading:", "Theme Data Files..." 50 | @theme_data_files.each { |file| Jekyll.logger.debug "", file } 51 | print_clear_line 52 | Jekyll.logger.debug "Merging:", "Theme Data Hash..." 53 | 54 | unless site.config["show-data"] && site.config["verbose"] 55 | Jekyll.logger.debug "", "use --show-data with --verbose to output " \ 56 | "merged Data Hash.".cyan 57 | print_clear_line 58 | end 59 | end 60 | 61 | # Private: 62 | # (only while debugging) 63 | # 64 | # Print contents of the merged data hash 65 | def inspect_merged_hash 66 | Jekyll.logger.debug "Inspecting:", "Site Data >>" 67 | 68 | # the width of generated logger[message] 69 | @width = 50 70 | @dashes = "-" * @width 71 | 72 | inspect_hash @site.data 73 | print_clear_line 74 | end 75 | 76 | # -------------------------------------------------------------------- 77 | # Private helper methods to inspect data hash and output contents 78 | # to logger at level debugging. 79 | # -------------------------------------------------------------------- 80 | 81 | # Dissect the (merged) site.data hash and print its contents 82 | # 83 | # - Print the key string(s) 84 | # - Individually analyse the hash[key] values and extract contents 85 | # to output. 86 | def inspect_hash(hash) 87 | hash.each do |key, value| 88 | print_key key 89 | if value.is_a? Hash 90 | inspect_inner_hash value 91 | elsif value.is_a? Array 92 | extract_hashes_and_print value 93 | else 94 | print_string value.to_s 95 | end 96 | end 97 | end 98 | 99 | # Analyse deeper hashes and extract contents to output 100 | def inspect_inner_hash(hash) 101 | hash.each do |key, value| 102 | if value.is_a? Array 103 | print_label key 104 | extract_hashes_and_print value 105 | elsif value.is_a? Hash 106 | print_subkey_and_value key, value 107 | else 108 | print_hash key, value 109 | end 110 | end 111 | end 112 | 113 | # If an array of strings, print. Otherwise assume as an 114 | # array of hashes (sequences) that needs further analysis. 115 | def extract_hashes_and_print(array) 116 | array.each do |entry| 117 | if entry.is_a? String 118 | print_list entry 119 | else 120 | inspect_inner_hash entry 121 | end 122 | end 123 | end 124 | 125 | # 126 | 127 | # -------------------------------------------------------------------- 128 | # Private methods for formatting log messages while debugging 129 | # -------------------------------------------------------------------- 130 | 131 | # Splits a string longer than the value of '@width' into smaller 132 | # strings and prints each line as a logger[message] 133 | # 134 | # string - the long string 135 | # 136 | # label - optional text to designate the printed lines. 137 | def print_long_string(string, label = "") 138 | strings = string.scan(%r!(.{1,#{@width}})(\s+|\W|\Z)!).map { |s| s.join.strip } 139 | first_line = strings.first.cyan 140 | 141 | label.empty? ? print_value(first_line) : print(label, first_line) 142 | strings[1..-1].each { |s| print_value s.cyan } 143 | end 144 | 145 | # Prints key as logger[topic] and value as [message] 146 | def print_hash(key, value) 147 | if value.length > @width 148 | print_long_string value, "#{key}:" 149 | else 150 | print "#{key}:", value.cyan 151 | end 152 | end 153 | 154 | def print_list(item) 155 | if item.length > @width 156 | print_long_string item, "-" 157 | else 158 | print "-", item.cyan 159 | end 160 | end 161 | 162 | def print_string(str) 163 | if str.length > @width 164 | print_long_string str 165 | else 166 | print_value str.inspect 167 | end 168 | end 169 | 170 | # Prints the site.data[key] in color 171 | def print_key(key) 172 | print_clear_line 173 | print "Data Key:", " #{key} ".center(@width, "=") 174 | print_clear_line 175 | end 176 | 177 | # Prints label, keys and values of mappings 178 | def print_subkey_and_value(key, value) 179 | print_label key 180 | value.each do |subkey, val| 181 | if val.is_a? Hash 182 | print_inner_subkey subkey 183 | inspect_inner_hash val 184 | elsif val.is_a? Array 185 | print_inner_subkey subkey 186 | extract_hashes_and_print val 187 | elsif val.is_a? String 188 | print_hash subkey, val 189 | end 190 | end 191 | end 192 | 193 | # Print only logger[message], [topic] = nil 194 | def print_value(value) 195 | if value.is_a? Array 196 | extract_hashes_and_print value 197 | else 198 | print "", value 199 | end 200 | end 201 | 202 | # Print only logger[topic] appended with a colon 203 | def print_label(key) 204 | print_value " #{key} ".center(@width, "-") 205 | end 206 | 207 | def print_inner_subkey(key) 208 | print "#{key}:", @dashes 209 | end 210 | 211 | def print_dashes 212 | print "", @dashes 213 | end 214 | 215 | def print_clear_line 216 | print "" 217 | end 218 | 219 | # Redefine Jekyll Loggers to have the [topic] indented by 30. 220 | # (rjust by just 29 to accomodate the additional whitespace added 221 | # by Jekyll) 222 | def print(topic, message = "") 223 | Jekyll.logger.debug topic.rjust(29), message 224 | end 225 | end 226 | end 227 | --------------------------------------------------------------------------------