From 34de676713b28586abfdbd23df75fb457cb4f364 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Mon, 1 Sep 2014 12:30:15 -0700 Subject: [PATCH 1/7] Require gems in :jekyll_plugins group in a Gemfile in unsafe mode. Replaces #1658. /cc @imathis --- lib/jekyll/plugin_manager.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/jekyll/plugin_manager.rb b/lib/jekyll/plugin_manager.rb index c52a5205..394aa9d6 100644 --- a/lib/jekyll/plugin_manager.rb +++ b/lib/jekyll/plugin_manager.rb @@ -17,6 +17,7 @@ module Jekyll def conscientious_require require_plugin_files require_gems + require_from_bundler end # Require each of the gem plugins specified. @@ -30,6 +31,15 @@ module Jekyll end end + def require_from_bundler + unless site.safe + require "bundler/setup" + Bundler.require(:jekyll_plugins) + end + rescue LoadError + false + end + # Check whether a gem plugin is allowed to be used during this build. # # gem_name - the name of the gem From 0ad2c338c4c199e5e7024d2e941f7af0d7e17711 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 3 Sep 2014 21:04:52 -0700 Subject: [PATCH 2/7] Reorganize tests for plugins. --- features/plugins.feature | 34 +++++++++++++++++++++++ features/site_configuration.feature | 31 --------------------- features/step_definitions/jekyll_steps.rb | 7 +++++ features/support/env.rb | 10 ++++++- 4 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 features/plugins.feature diff --git a/features/plugins.feature b/features/plugins.feature new file mode 100644 index 00000000..4e6d9d86 --- /dev/null +++ b/features/plugins.feature @@ -0,0 +1,34 @@ +Feature: Configuring and using plugins + As a hacker + I want to specify my own plugins that can modify Jekyll's behaviour + + Scenario: Add a gem-based plugin + Given I have an "index.html" file that contains "Whatever" + And I have a configuration file with "gems" set to "[jekyll_test_plugin]" + When I run jekyll build + Then the _site directory should exist + And I should see "Whatever" in "_site/index.html" + And I should see "this is a test" in "_site/test.txt" + + Scenario: Add an empty whitelist to restrict all gems + Given I have an "index.html" file that contains "Whatever" + And I have a configuration file with: + | key | value | + | gems | [jekyll_test_plugin] | + | whitelist | [] | + When I run jekyll build --safe + Then the _site directory should exist + And I should see "Whatever" in "_site/index.html" + And the "_site/test.txt" file should not exist + + Scenario: Add a whitelist to restrict some gems but allow others + Given I have an "index.html" file that contains "Whatever" + And I have a configuration file with: + | key | value | + | gems | [jekyll_test_plugin, jekyll_test_plugin_malicious] | + | whitelist | [jekyll_test_plugin] | + When I run jekyll build --safe + Then the _site directory should exist + And I should see "Whatever" in "_site/index.html" + And the "_site/test.txt" file should exist + And I should see "this is a test" in "_site/test.txt" \ No newline at end of file diff --git a/features/site_configuration.feature b/features/site_configuration.feature index 8aaa9e1d..1d067f0c 100644 --- a/features/site_configuration.feature +++ b/features/site_configuration.feature @@ -243,37 +243,6 @@ Feature: Site configuration And I should see "Post Layout:

content for entry1.

" in "_site/2007/12/31/entry1.html" And I should see "Post Layout:

content for entry2.

" in "_site/2020/01/31/entry2.html" - Scenario: Add a gem-based plugin - Given I have an "index.html" file that contains "Whatever" - And I have a configuration file with "gems" set to "[jekyll_test_plugin]" - When I run jekyll build - Then the _site directory should exist - And I should see "Whatever" in "_site/index.html" - And I should see "this is a test" in "_site/test.txt" - - Scenario: Add an empty whitelist to restrict all gems - Given I have an "index.html" file that contains "Whatever" - And I have a configuration file with: - | key | value | - | gems | [jekyll_test_plugin] | - | whitelist | [] | - When I run jekyll build --safe - Then the _site directory should exist - And I should see "Whatever" in "_site/index.html" - And the "_site/test.txt" file should not exist - - Scenario: Add a whitelist to restrict some gems but allow others - Given I have an "index.html" file that contains "Whatever" - And I have a configuration file with: - | key | value | - | gems | [jekyll_test_plugin, jekyll_test_plugin_malicious] | - | whitelist | [jekyll_test_plugin] | - When I run jekyll build --safe - Then the _site directory should exist - And I should see "Whatever" in "_site/index.html" - And the "_site/test.txt" file should exist - And I should see "this is a test" in "_site/test.txt" - Scenario: arbitrary file reads via layouts Given I have an "index.html" page with layout "page" that contains "FOO" And I have a "_config.yml" file that contains "layouts: '../../../../../../../../../../../../../../usr/include'" diff --git a/features/step_definitions/jekyll_steps.rb b/features/step_definitions/jekyll_steps.rb index 8be4ea2f..b75379f5 100644 --- a/features/step_definitions/jekyll_steps.rb +++ b/features/step_definitions/jekyll_steps.rb @@ -146,6 +146,13 @@ When /^I run jekyll(.*)$/ do |args| end end +When /^I run bundle(.*)$/ do |args| + status = run_bundle(args) + if args.include?("--verbose") || ENV['DEBUG'] + puts jekyll_run_output + end +end + When /^I change "(.*)" to contain "(.*)"$/ do |file, text| File.open(file, 'a') do |f| f.write(text) diff --git a/features/support/env.rb b/features/support/env.rb index 996c79ba..8bb0274d 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -21,11 +21,19 @@ def jekyll_run_output File.read(jekyll_output_file) if File.file?(jekyll_output_file) end +def run_bundle(args) + child = run_in_shell('bundle', *args.strip.split(' ')) +end + def run_jekyll(args) - child = POSIX::Spawn::Child.new JEKYLL_PATH, *args.strip.split(' '), "--trace", :out => [JEKYLL_COMMAND_OUTPUT_FILE, "w"] + child = run_in_shell(JEKYLL_PATH, *args.strip.split(' '), "--trace") child.status.exitstatus == 0 end +def run_in_shell(args, options) + POSIX::Spawn::Child.new *args, :out => [JEKYLL_COMMAND_OUTPUT_FILE, "w"]) +end + def slug(title) if title title.downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-') From 519b60d0121d529839fbc18a9d538b6fc4c462bb Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Tue, 21 Oct 2014 20:32:23 -0700 Subject: [PATCH 3/7] Fix 2 syntax errors. --- features/support/env.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index 8bb0274d..ba4555fe 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -30,8 +30,8 @@ def run_jekyll(args) child.status.exitstatus == 0 end -def run_in_shell(args, options) - POSIX::Spawn::Child.new *args, :out => [JEKYLL_COMMAND_OUTPUT_FILE, "w"]) +def run_in_shell(*args) + POSIX::Spawn::Child.new *args, :out => [JEKYLL_COMMAND_OUTPUT_FILE, "w"] end def slug(title) From b0a7f9c8c9694a6e324d5c0abde5b11fb685a755 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 5 Nov 2014 18:50:38 -0800 Subject: [PATCH 4/7] Require plugins from Gemfile :jekyll_plugins group. --- bin/jekyll | 2 ++ lib/jekyll/plugin_manager.rb | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/jekyll b/bin/jekyll index 194295c8..060c9125 100755 --- a/bin/jekyll +++ b/bin/jekyll @@ -13,6 +13,8 @@ require 'mercenary' end end +Jekyll::PluginManager.require_from_bundler + Jekyll::Deprecator.process(ARGV) Mercenary.program(:jekyll) do |p| diff --git a/lib/jekyll/plugin_manager.rb b/lib/jekyll/plugin_manager.rb index 394aa9d6..d484ef35 100644 --- a/lib/jekyll/plugin_manager.rb +++ b/lib/jekyll/plugin_manager.rb @@ -17,7 +17,7 @@ module Jekyll def conscientious_require require_plugin_files require_gems - require_from_bundler + self.require_from_bundler end # Require each of the gem plugins specified. @@ -31,10 +31,11 @@ module Jekyll end end - def require_from_bundler - unless site.safe + def self.require_from_bundler + unless ENV["JEKYLL_NO_BUNDLER_REQUIRE"] require "bundler/setup" Bundler.require(:jekyll_plugins) + ENV["JEKYLL_NO_BUNDLER_REQUIRE"] = true end rescue LoadError false From 8ee1b2a1fd5ffa56aa012e894b16252eb3bdda54 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 5 Nov 2014 19:29:54 -0800 Subject: [PATCH 5/7] Finish up the plugin manager And so it shall be. --- features/plugins.feature | 2 +- lib/jekyll/plugin_manager.rb | 6 +++--- test/test_plugin_manager.rb | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 test/test_plugin_manager.rb diff --git a/features/plugins.feature b/features/plugins.feature index 4e6d9d86..a36c6c82 100644 --- a/features/plugins.feature +++ b/features/plugins.feature @@ -31,4 +31,4 @@ Feature: Configuring and using plugins Then the _site directory should exist And I should see "Whatever" in "_site/index.html" And the "_site/test.txt" file should exist - And I should see "this is a test" in "_site/test.txt" \ No newline at end of file + And I should see "this is a test" in "_site/test.txt" diff --git a/lib/jekyll/plugin_manager.rb b/lib/jekyll/plugin_manager.rb index d484ef35..cf5860c5 100644 --- a/lib/jekyll/plugin_manager.rb +++ b/lib/jekyll/plugin_manager.rb @@ -17,7 +17,7 @@ module Jekyll def conscientious_require require_plugin_files require_gems - self.require_from_bundler + self.class.require_from_bundler end # Require each of the gem plugins specified. @@ -33,9 +33,9 @@ module Jekyll def self.require_from_bundler unless ENV["JEKYLL_NO_BUNDLER_REQUIRE"] - require "bundler/setup" + require "bundler" Bundler.require(:jekyll_plugins) - ENV["JEKYLL_NO_BUNDLER_REQUIRE"] = true + ENV["JEKYLL_NO_BUNDLER_REQUIRE"] = "true" end rescue LoadError false diff --git a/test/test_plugin_manager.rb b/test/test_plugin_manager.rb new file mode 100644 index 00000000..8cc1d8f8 --- /dev/null +++ b/test/test_plugin_manager.rb @@ -0,0 +1,8 @@ +require 'helper' + +class TestPluginManager < Test::Unit::TestCase + def test_requiring_from_bundler + Jekyll::PluginManager.require_from_bundler + assert ENV["JEKYLL_NO_BUNDLER_REQUIRE"], 'Gemfile plugins were not required.' + end +end From 5a350788e739adfef1acb8911828cf651367bcb3 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 5 Nov 2014 21:04:34 -0800 Subject: [PATCH 6/7] Test JEKYLL_NO_BUNDLER_REQUIRE. --- lib/jekyll/plugin_manager.rb | 9 +++++++-- test/helper.rb | 7 +++++++ test/test_plugin_manager.rb | 12 ++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/jekyll/plugin_manager.rb b/lib/jekyll/plugin_manager.rb index cf5860c5..ea964ce5 100644 --- a/lib/jekyll/plugin_manager.rb +++ b/lib/jekyll/plugin_manager.rb @@ -26,16 +26,21 @@ module Jekyll def require_gems site.gems.each do |gem| if plugin_allowed?(gem) + Jekyll.logger.debug("PluginManager:", "Requiring #{gem}") require gem end end end def self.require_from_bundler - unless ENV["JEKYLL_NO_BUNDLER_REQUIRE"] + if ENV["JEKYLL_NO_BUNDLER_REQUIRE"] + false + else require "bundler" - Bundler.require(:jekyll_plugins) + required_gems = Bundler.require(:jekyll_plugins) + Jekyll.logger.debug("PluginManager:", "Required #{required_gems.map(&:name).join(', ')}") ENV["JEKYLL_NO_BUNDLER_REQUIRE"] = "true" + true end rescue LoadError false diff --git a/test/helper.rb b/test/helper.rb index 3103e117..ea3777ed 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -58,6 +58,13 @@ class Test::Unit::TestCase File.open("#{path}/index.html", "w"){ |f| f.write("I was previously generated.") } end + def with_env(key, value) + old_value = ENV[key] + ENV[key] = value + yield + ENV[key] = old_value + end + def capture_stdout $old_stdout = $stdout $stdout = StringIO.new diff --git a/test/test_plugin_manager.rb b/test/test_plugin_manager.rb index 8cc1d8f8..1f7931ca 100644 --- a/test/test_plugin_manager.rb +++ b/test/test_plugin_manager.rb @@ -2,7 +2,15 @@ require 'helper' class TestPluginManager < Test::Unit::TestCase def test_requiring_from_bundler - Jekyll::PluginManager.require_from_bundler - assert ENV["JEKYLL_NO_BUNDLER_REQUIRE"], 'Gemfile plugins were not required.' + with_env("JEKYLL_NO_BUNDLER_REQUIRE", nil) do + Jekyll::PluginManager.require_from_bundler + assert ENV["JEKYLL_NO_BUNDLER_REQUIRE"], 'Gemfile plugins were not required.' + end + end + + def test_blocking_requiring_from_bundler + with_env("JEKYLL_NO_BUNDLER_REQUIRE", "true") do + assert_equal false, Jekyll::PluginManager.require_from_bundler, "Gemfile plugins were required but shouldn't have been" + end end end From 5d23760280c46ee134014c87fc754977994b0f0f Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Wed, 5 Nov 2014 21:53:48 -0800 Subject: [PATCH 7/7] Documentation for Bundler group. --- Rakefile | 10 +++++-- site/_docs/plugins.md | 69 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 439a5366..b16e743f 100644 --- a/Rakefile +++ b/Rakefile @@ -146,10 +146,14 @@ namespace :site do # Generate the site in server mode. puts "Running Jekyll..." - Jekyll::Commands::Serve.process({ + options = { "source" => File.expand_path("site"), - "destination" => File.expand_path("site/_site") - }) + "destination" => File.expand_path("site/_site"), + "watch" => true, + "serving" => true + } + Jekyll::Commands::Build.process(options) + Jekyll::Commands::Serve.process(options) end desc "Generate the site" diff --git a/site/_docs/plugins.md b/site/_docs/plugins.md index df1de0cb..c80081fb 100644 --- a/site/_docs/plugins.md +++ b/site/_docs/plugins.md @@ -25,7 +25,7 @@ having to modify the Jekyll source itself. ## Installing a plugin -You have 2 options for installing plugins: +You have 3 options for installing plugins: 1. In your site source root, make a `_plugins` directory. Place your plugins here. Any file ending in `*.rb` inside this directory will be loaded before Jekyll @@ -35,6 +35,12 @@ You have 2 options for installing plugins: gems: [jekyll-test-plugin, jekyll-jsonify, jekyll-assets] # This will require each of these gems automatically. +3. Add the relevant plugins to a Bundler group in your `Gemfile`. An + example: + + group :jekyll_plugins do + gem "my-jekyll-plugin" + end
@@ -247,6 +253,67 @@ In our example, `UpcaseConverter#matches` checks if our filename extension is simply uppercasing the entire content string. Finally, when it saves the page, it will do so with a `.html` extension. +## Command + +As of version 2.5.0, Jekyll can be extended with plugins which provide +subcommands for the `jekyll` executable. This is possible by including the +relevant plugins in a `Gemfile` group called `:jekyll_plugins`: + +{% highlight ruby %} +group :jekyll_plugins do + gem "my_fancy_jekyll_plugin" +end +{% endhighlight %} + +Each `Command` must be a subclass of the `Jekyll::Command` class and must +contain one class method: `init_with_program`. An example: + +{% highlight ruby %} +class MyNewCommand < Jekyll::Command + class << self + def init_with_program(prog) + prog.command(:new) do |c| + c.syntax "new [options]" + c.description 'Create a new Jekyll site.' + + c.option 'dest', '-d DEST, 'Where the site should go.' + + c.action do |args, options| + Jekyll::Site.new_site_at(options['dest']) + end + end + end + end +end +{% endhighlight %} + +Commands should implement this single class method: + +
+ + + + + + + + + + + + + +
MethodDescription
+

init_with_program

+

+ This method accepts one parameter, the + Mercenary::Program + instance, which is the Jekyll program itself. Upon the program, + commands may be created using the above syntax. For more details, + visit the Mercenary repository on GitHub.com. +

+
+ ## Tags If you’d like to include custom liquid tags in your site, you can do so by