Merge pull request #2865 from jekyll/some-kind-of-bundler-thingy

This commit is contained in:
Parker Moore 2014-11-05 22:01:37 -08:00
commit 13bb7360c0
10 changed files with 166 additions and 36 deletions

View File

@ -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"

View File

@ -13,6 +13,8 @@ require 'mercenary'
end
end
Jekyll::PluginManager.require_from_bundler
Jekyll::Deprecator.process(ARGV)
Mercenary.program(:jekyll) do |p|

34
features/plugins.feature Normal file
View File

@ -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"

View File

@ -243,37 +243,6 @@ Feature: Site configuration
And I should see "Post Layout: <p>content for entry1.</p>" in "_site/2007/12/31/entry1.html"
And I should see "Post Layout: <p>content for entry2.</p>" 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'"

View File

@ -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)

View File

@ -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)
POSIX::Spawn::Child.new *args, :out => [JEKYLL_COMMAND_OUTPUT_FILE, "w"]
end
def slug(title)
if title
title.downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-')

View File

@ -17,6 +17,7 @@ module Jekyll
def conscientious_require
require_plugin_files
require_gems
self.class.require_from_bundler
end
# Require each of the gem plugins specified.
@ -25,11 +26,26 @@ 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
if ENV["JEKYLL_NO_BUNDLER_REQUIRE"]
false
else
require "bundler"
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
end
# Check whether a gem plugin is allowed to be used during this build.
#
# gem_name - the name of the gem

View File

@ -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
<div class="note info">
<h5>
@ -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:
<div class="mobile-side-scroller">
<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<p><code>init_with_program</code></p>
</td>
<td><p>
This method accepts one parameter, the
<code><a href="http://github.com/jekyll/mercenary#readme">Mercenary::Program</a></code>
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.
</p></td>
</tr>
</tbody>
</table>
</div>
## Tags
If youd like to include custom liquid tags in your site, you can do so by

View File

@ -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

View File

@ -0,0 +1,16 @@
require 'helper'
class TestPluginManager < Test::Unit::TestCase
def test_requiring_from_bundler
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