Merge pull request #2591 from jekyll/extract-list

This commit is contained in:
Parker Moore 2014-07-12 19:22:10 -05:00
commit 5314bbf952
19 changed files with 277 additions and 241 deletions

View File

@ -28,13 +28,16 @@ Gem::Specification.new do |s|
s.rdoc_options = ["--charset=UTF-8"]
s.extra_rdoc_files = %w[README.markdown LICENSE]
s.add_runtime_dependency('liquid', "~> 2.6.1")
s.add_runtime_dependency('classifier', "~> 1.3")
s.add_runtime_dependency('kramdown', "~> 1.3")
s.add_runtime_dependency('pygments.rb', "~> 0.6.0")
s.add_runtime_dependency('liquid', "~> 2.6.1")
s.add_runtime_dependency('kramdown', "~> 1.3")
s.add_runtime_dependency('mercenary', "~> 0.3.3")
s.add_runtime_dependency('safe_yaml', "~> 1.0")
s.add_runtime_dependency('colorator', "~> 0.1")
# Before 3.0 drops, phase the following gems out as dev dependencies
# and gracefully handle their absence.
s.add_runtime_dependency('classifier', "~> 1.3")
s.add_runtime_dependency('pygments.rb', "~> 0.6.0")
s.add_runtime_dependency('redcarpet', "~> 3.1")
s.add_runtime_dependency('toml', '~> 0.1.0')
s.add_runtime_dependency('jekyll-paginate', '~> 1.0')

View File

@ -18,69 +18,56 @@ require 'rubygems'
# stdlib
require 'fileutils'
require 'time'
require 'safe_yaml/load'
require 'English'
require 'pathname'
require 'logger'
# 3rd party
require 'safe_yaml/load'
require 'liquid'
require 'kramdown'
require 'colorator'
require 'toml'
# internal requires
require 'jekyll/version'
require 'jekyll/utils'
require 'jekyll/log_adapter'
require 'jekyll/stevenson'
require 'jekyll/deprecator'
require 'jekyll/configuration'
require 'jekyll/document'
require 'jekyll/collection'
require 'jekyll/plugin_manager'
require 'jekyll/frontmatter_defaults'
require 'jekyll/site'
require 'jekyll/convertible'
require 'jekyll/url'
require 'jekyll/layout'
require 'jekyll/page'
require 'jekyll/post'
require 'jekyll/excerpt'
require 'jekyll/draft'
require 'jekyll/filters'
require 'jekyll/static_file'
require 'jekyll/errors'
require 'jekyll/related_posts'
require 'jekyll/cleaner'
require 'jekyll/entry_filter'
require 'jekyll/layout_reader'
require 'jekyll/publisher'
require 'jekyll/renderer'
# extensions
require 'jekyll/plugin'
require 'jekyll/converter'
require 'jekyll/generator'
require 'jekyll/command'
require 'jekyll/liquid_extensions'
require_all 'jekyll/commands'
require_all 'jekyll/converters'
require_all 'jekyll/converters/markdown'
require_all 'jekyll/generators'
require_all 'jekyll/tags'
# plugins
require 'jekyll-coffeescript'
require 'jekyll-sass-converter'
require 'jekyll-paginate'
require 'jekyll-gist'
SafeYAML::OPTIONS[:suppress_warnings] = true
module Jekyll
# internal requires
autoload :Cleaner, 'jekyll/cleaner'
autoload :Collection, 'jekyll/collection'
autoload :Configuration, 'jekyll/configuration'
autoload :Convertible, 'jekyll/convertible'
autoload :Deprecator, 'jekyll/deprecator'
autoload :Document, 'jekyll/document'
autoload :Draft, 'jekyll/draft'
autoload :EntryFilter, 'jekyll/entry_filter'
autoload :Errors, 'jekyll/errors'
autoload :Excerpt, 'jekyll/excerpt'
autoload :Filters, 'jekyll/filters'
autoload :FrontmatterDefaults, 'jekyll/frontmatter_defaults'
autoload :Layout, 'jekyll/layout'
autoload :LayoutReader, 'jekyll/layout_reader'
autoload :LogAdapter, 'jekyll/log_adapter'
autoload :Page, 'jekyll/page'
autoload :PluginManager, 'jekyll/plugin_manager'
autoload :Post, 'jekyll/post'
autoload :Publisher, 'jekyll/publisher'
autoload :RelatedPosts, 'jekyll/related_posts'
autoload :Renderer, 'jekyll/renderer'
autoload :Site, 'jekyll/site'
autoload :StaticFile, 'jekyll/static_file'
autoload :Stevenson, 'jekyll/stevenson'
autoload :URL, 'jekyll/url'
autoload :Utils, 'jekyll/utils'
autoload :VERSION, 'jekyll/version'
# extensions
require 'jekyll/plugin'
require 'jekyll/converter'
require 'jekyll/generator'
require 'jekyll/command'
require 'jekyll/liquid_extensions'
# Public: Tells you which Jekyll environment you are building in so you can skip tasks
# if you need to. This is useful when doing expensive compression tasks on css and
# images and allows you to skip that when working in development.
@ -143,3 +130,19 @@ module Jekyll
end
end
end
require_all 'jekyll/commands'
require_all 'jekyll/converters'
require_all 'jekyll/converters/markdown'
require_all 'jekyll/generators'
require_all 'jekyll/tags'
# Eventually remove these for 3.0 as non-core
Jekyll::Deprecator.gracefully_require(%w[
classifier
toml
jekyll-paginate
jekyll-gist
jekyll-coffeescript
jekyll-sass-converter
])

View File

@ -51,7 +51,7 @@ module Jekyll
# Returns nothing
def process_site(site)
site.process
rescue Jekyll::FatalException => e
rescue Jekyll::Errors::FatalException => e
Jekyll.logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:"
Jekyll.logger.error "", "------------------------------------"
Jekyll.logger.error "", e.message

View File

@ -58,7 +58,7 @@ module Jekyll
#
# Returns nothing.
def watch(site, options)
require 'jekyll-watch'
Deprecator.gracefully_require 'jekyll-watch'
Jekyll::Commands::Watch.watch(site, options)
end

View File

@ -11,22 +11,41 @@ module Jekyll
@parser =
case @config['markdown'].downcase
when 'redcarpet' then RedcarpetParser.new(@config)
when 'kramdown' then KramdownParser.new(@config)
when 'kramdown' then KramdownParser.new(@config)
when 'rdiscount' then RDiscountParser.new(@config)
when 'maruku' then MarukuParser.new(@config)
when 'maruku' then MarukuParser.new(@config)
else
# So they can't try some tricky bullshit or go down the ancestor chain, I hope.
if allowed_custom_class?(@config['markdown'])
self.class.const_get(@config['markdown']).new(@config)
else
Jekyll.logger.error "Invalid Markdown Processor:", "#{@config['markdown']}"
Jekyll.logger.error "", "Valid options are [ maruku | rdiscount | kramdown | redcarpet ]"
raise FatalException, "Invalid Markdown Processor: #{@config['markdown']}"
Jekyll.logger.error "", "Valid options are [ #{valid_processors.join(" | ")} ]"
raise Errors::FatalException, "Invalid Markdown Processor: #{@config['markdown']}"
end
end
@setup = true
end
def valid_processors
%w[
maruku
rdiscount
kramdown
redcarpet
] + third_party_processors
end
def third_party_processors
self.class.constants - %w[
KramdownParser
MarukuParser
RDiscountParser
RedcarpetParser
PRIORITIES
].map(&:to_sym)
end
def matches(ext)
rgx = '^\.(' + @config['markdown_ext'].gsub(',','|') +')$'
ext =~ Regexp.new(rgx, Regexp::IGNORECASE)

View File

@ -8,7 +8,7 @@ module Jekyll
rescue LoadError
STDERR.puts 'You are missing a library required for Markdown. Please run:'
STDERR.puts ' $ [sudo] gem install kramdown'
raise FatalException.new("Missing dependency: kramdown")
raise Errors::FatalException.new("Missing dependency: kramdown")
end
def convert(content)

View File

@ -15,7 +15,7 @@ module Jekyll
rescue LoadError
STDERR.puts 'You are missing a library required for Markdown. Please run:'
STDERR.puts ' $ [sudo] gem install maruku'
raise FatalException.new("Missing dependency: maruku")
raise Errors::FatalException.new("Missing dependency: maruku")
end
def load_divs_library

View File

@ -3,13 +3,9 @@ module Jekyll
class Markdown
class RDiscountParser
def initialize(config)
require 'rdiscount'
Jekyll::Deprecator.gracefully_require "rdiscount"
@config = config
@rdiscount_extensions = @config['rdiscount']['extensions'].map { |e| e.to_sym }
rescue LoadError
STDERR.puts 'You are missing a library required for Markdown. Please run:'
STDERR.puts ' $ [sudo] gem install rdiscount'
raise FatalException.new("Missing dependency: rdiscount")
end
def convert(content)

View File

@ -14,7 +14,7 @@ module Jekyll
module WithPygments
include CommonMethods
def block_code(code, lang)
require 'pygments'
Jekyll::Deprecator.gracefully_require("pygments")
lang = lang && lang.split.first || "text"
add_code_tags(
Pygments.highlight(code, :lexer => lang, :options => { :encoding => 'utf-8' }),
@ -55,45 +55,40 @@ module Jekyll
def initialize(config)
require 'redcarpet'
Deprecator.gracefully_require("redcarpet")
@config = config
@redcarpet_extensions = {}
@config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true }
@renderer ||= case @config['highlighter']
when 'pygments'
Class.new(Redcarpet::Render::HTML) do
include WithPygments
end
when 'rouge'
Class.new(Redcarpet::Render::HTML) do
begin
require 'rouge'
require 'rouge/plugins/redcarpet'
rescue LoadError => e
Jekyll.logger.error "You are missing the 'rouge' gem. Please run:"
Jekyll.logger.error " $ [sudo] gem install rouge"
Jekyll.logger.error "Or add 'rouge' to your Gemfile."
raise FatalException.new("Missing dependency: rouge")
end
@renderer ||= class_with_proper_highlighter(@config['highlighter'])
end
if Rouge.version < '1.3.0'
abort "Please install Rouge 1.3.0 or greater and try running Jekyll again."
end
def class_with_proper_highlighter(highlighter)
case highlighter
when "pygments"
Class.new(Redcarpet::Render::HTML) do
include WithPygments
end
when "rouge"
Class.new(Redcarpet::Render::HTML) do
Jekyll::Deprecator.gracefully_require(%w[
rouge
rouge/plugins/redcarpet
])
include Rouge::Plugins::Redcarpet
include CommonMethods
include WithRouge
end
else
Class.new(Redcarpet::Render::HTML) do
include WithoutHighlighting
end
end
rescue LoadError
STDERR.puts 'You are missing a library required for Markdown. Please run:'
STDERR.puts ' $ [sudo] gem install redcarpet'
raise FatalException.new("Missing dependency: redcarpet")
if Rouge.version < '1.3.0'
abort "Please install Rouge 1.3.0 or greater and try running Jekyll again."
end
include Rouge::Plugins::Redcarpet
include CommonMethods
include WithRouge
end
else
Class.new(Redcarpet::Render::HTML) do
include WithoutHighlighting
end
end
end
def convert(content)

View File

@ -13,7 +13,7 @@ module Jekyll
rescue LoadError
STDERR.puts 'You are missing a library required for Textile. Please run:'
STDERR.puts ' $ [sudo] gem install RedCloth'
raise FatalException.new("Missing dependency: RedCloth")
raise Errors::FatalException.new("Missing dependency: RedCloth")
end
def matches(ext)

View File

@ -1,5 +1,5 @@
module Jekyll
class Deprecator
module Deprecator
def self.process(args)
no_subcommand(args)
arg_is_present? args, "--server", "The --server command has been replaced by the \
@ -32,5 +32,23 @@ module Jekyll
def self.deprecation_message(message)
Jekyll.logger.error "Deprecation:", message
end
def self.gracefully_require(gem_name)
Array(gem_name).each do |name|
begin
require name
rescue LoadError => e
Jekyll.logger.error "Dependency Error:", <<-MSG
Yikes! It looks like you don't have #{name} or one of its dependencies installed.
In order to use Jekyll as currently contfigured, you'll need to install this gem.
The full error message from Ruby is: '#{e.message}'
If you run into trouble, you can find helpful resources at http://jekyllrb.com/help/!
MSG
raise Errors::MissingDependencyException.new(name)
end
end
end
end
end

View File

@ -1,4 +1,9 @@
module Jekyll
class FatalException < StandardError
module Errors
class FatalException < RuntimeError
end
class MissingDependencyException < FatalException
end
end
end

View File

@ -1,4 +1,3 @@
require 'jekyll/convertible'
require 'forwardable'
module Jekyll

View File

@ -1,147 +1,145 @@
module Jekyll
class Configuration
# This class handles custom defaults for YAML frontmatter settings.
# These are set in _config.yml and apply both to internal use (e.g. layout)
# and the data available to liquid.
# This class handles custom defaults for YAML frontmatter settings.
# These are set in _config.yml and apply both to internal use (e.g. layout)
# and the data available to liquid.
#
# It is exposed via the frontmatter_defaults method on the site class.
class FrontmatterDefaults
# Initializes a new instance.
def initialize(site)
@site = site
end
# Finds a default value for a given setting, filtered by path and type
#
# It is exposed via the frontmatter_defaults method on the site class.
class FrontmatterDefaults
# Initializes a new instance.
def initialize(site)
@site = site
end
# path - the path (relative to the source) of the page, post or :draft the default is used in
# type - a symbol indicating whether a :page, a :post or a :draft calls this method
#
# Returns the default value or nil if none was found
def find(path, type, setting)
value = nil
old_scope = nil
# Finds a default value for a given setting, filtered by path and type
#
# path - the path (relative to the source) of the page, post or :draft the default is used in
# type - a symbol indicating whether a :page, a :post or a :draft calls this method
#
# Returns the default value or nil if none was found
def find(path, type, setting)
value = nil
old_scope = nil
matching_sets(path, type).each do |set|
if set['values'].has_key?(setting) && has_precedence?(old_scope, set['scope'])
value = set['values'][setting]
old_scope = set['scope']
end
end
value
end
# Collects a hash with all default values for a page or post
#
# path - the relative path of the page or post
# type - a symbol indicating the type (:post, :page or :draft)
#
# Returns a hash with all default values (an empty hash if there are none)
def all(path, type)
defaults = {}
old_scope = nil
matching_sets(path, type).each do |set|
if has_precedence?(old_scope, set['scope'])
defaults = Utils.deep_merge_hashes(defaults, set['values'])
old_scope = set['scope']
else
defaults = Utils.deep_merge_hashes(set['values'], defaults)
end
end
defaults
end
private
# Checks if a given default setting scope matches the given path and type
#
# scope - the hash indicating the scope, as defined in _config.yml
# path - the path to check for
# type - the type (:post, :page or :draft) to check for
#
# Returns true if the scope applies to the given path and type
def applies?(scope, path, type)
applies_path?(scope, path) && applies_type?(scope, type)
end
def applies_path?(scope, path)
return true if scope['path'].empty?
scope_path = Pathname.new(scope['path'])
Pathname.new(sanitize_path(path)).ascend do |path|
if path == scope_path
return true
end
matching_sets(path, type).each do |set|
if set['values'].has_key?(setting) && has_precedence?(old_scope, set['scope'])
value = set['values'][setting]
old_scope = set['scope']
end
end
value
end
def applies_type?(scope, type)
!scope.has_key?('type') || scope['type'] == type.to_s
end
# Checks if a given set of default values is valid
#
# set - the default value hash, as defined in _config.yml
#
# Returns true if the set is valid and can be used in this class
def valid?(set)
set.is_a?(Hash) && set['scope'].is_a?(Hash) && set['scope']['path'].is_a?(String) && set['values'].is_a?(Hash)
end
# Determines if a new scope has precedence over an old one
#
# old_scope - the old scope hash, or nil if there's none
# new_scope - the new scope hash
#
# Returns true if the new scope has precedence over the older
def has_precedence?(old_scope, new_scope)
return true if old_scope.nil?
new_path = sanitize_path(new_scope['path'])
old_path = sanitize_path(old_scope['path'])
if new_path.length != old_path.length
new_path.length >= old_path.length
elsif new_scope.has_key? 'type'
true
# Collects a hash with all default values for a page or post
#
# path - the relative path of the page or post
# type - a symbol indicating the type (:post, :page or :draft)
#
# Returns a hash with all default values (an empty hash if there are none)
def all(path, type)
defaults = {}
old_scope = nil
matching_sets(path, type).each do |set|
if has_precedence?(old_scope, set['scope'])
defaults = Utils.deep_merge_hashes(defaults, set['values'])
old_scope = set['scope']
else
!old_scope.has_key? 'type'
defaults = Utils.deep_merge_hashes(set['values'], defaults)
end
end
defaults
end
# Collects a list of sets that match the given path and type
#
# Returns an array of hashes
def matching_sets(path, type)
valid_sets.select do |set|
applies?(set['scope'], path, type)
private
# Checks if a given default setting scope matches the given path and type
#
# scope - the hash indicating the scope, as defined in _config.yml
# path - the path to check for
# type - the type (:post, :page or :draft) to check for
#
# Returns true if the scope applies to the given path and type
def applies?(scope, path, type)
applies_path?(scope, path) && applies_type?(scope, type)
end
def applies_path?(scope, path)
return true if scope['path'].empty?
scope_path = Pathname.new(scope['path'])
Pathname.new(sanitize_path(path)).ascend do |path|
if path == scope_path
return true
end
end
end
# Returns a list of valid sets
#
# This is not cached to allow plugins to modify the configuration
# and have their changes take effect
#
# Returns an array of hashes
def valid_sets
sets = @site.config['defaults']
return [] unless sets.is_a?(Array)
def applies_type?(scope, type)
!scope.has_key?('type') || scope['type'] == type.to_s
end
sets.select do |set|
unless valid?(set)
Jekyll.logger.warn "Default:", "An invalid default set was found"
end
valid?(set)
end
# Checks if a given set of default values is valid
#
# set - the default value hash, as defined in _config.yml
#
# Returns true if the set is valid and can be used in this class
def valid?(set)
set.is_a?(Hash) && set['scope'].is_a?(Hash) && set['scope']['path'].is_a?(String) && set['values'].is_a?(Hash)
end
# Determines if a new scope has precedence over an old one
#
# old_scope - the old scope hash, or nil if there's none
# new_scope - the new scope hash
#
# Returns true if the new scope has precedence over the older
def has_precedence?(old_scope, new_scope)
return true if old_scope.nil?
new_path = sanitize_path(new_scope['path'])
old_path = sanitize_path(old_scope['path'])
if new_path.length != old_path.length
new_path.length >= old_path.length
elsif new_scope.has_key? 'type'
true
else
!old_scope.has_key? 'type'
end
end
# Sanitizes the given path by removing a leading and addding a trailing slash
def sanitize_path(path)
if path.nil? || path.empty?
""
else
path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1/')
# Collects a list of sets that match the given path and type
#
# Returns an array of hashes
def matching_sets(path, type)
valid_sets.select do |set|
applies?(set['scope'], path, type)
end
end
# Returns a list of valid sets
#
# This is not cached to allow plugins to modify the configuration
# and have their changes take effect
#
# Returns an array of hashes
def valid_sets
sets = @site.config['defaults']
return [] unless sets.is_a?(Array)
sets.select do |set|
unless valid?(set)
Jekyll.logger.warn "Default:", "An invalid default set was found"
end
valid?(set)
end
end
# Sanitizes the given path by removing a leading and addding a trailing slash
def sanitize_path(path)
if path.nil? || path.empty?
""
else
path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1/')
end
end
end

View File

@ -166,7 +166,7 @@ module Jekyll
path = File.join(@dir || "", name)
msg = "Post '#{path}' does not have a valid date.\n"
msg << "Fix the date, or exclude the file or directory from being processed"
raise FatalException.new(msg)
raise Errors::FatalException.new(msg)
end
# The generated directory into which the post will be placed

View File

@ -80,7 +80,7 @@ module Jekyll
dest_pathname = Pathname.new(dest)
Pathname.new(source).ascend do |path|
if path == dest_pathname
raise FatalException.new "Destination directory cannot be or contain the Source directory."
raise Errors::FatalException.new "Destination directory cannot be or contain the Source directory."
end
end
end
@ -443,7 +443,7 @@ module Jekyll
end
def frontmatter_defaults
@frontmatter_defaults ||= Configuration::FrontmatterDefaults.new(self)
@frontmatter_defaults ||= FrontmatterDefaults.new(self)
end
private

View File

@ -44,7 +44,7 @@ class TestCommand < Test::Unit::TestCase
context "when fatal error occurs" do
should "exit with non-zero error code" do
site = Object.new
stub(site).process { raise Jekyll::FatalException }
stub(site).process { raise Jekyll::Errors::FatalException }
error = assert_raise(SystemExit) { Command.process_site(site) }
assert_not_equal 0, error.status
end

View File

@ -83,7 +83,7 @@ class TestPost < Test::Unit::TestCase
end
should "raise a good error on invalid post date" do
assert_raise Jekyll::FatalException do
assert_raise Jekyll::Errors::FatalException do
@post.process("2009-27-03-foo-bar.textile")
end
end

View File

@ -225,7 +225,7 @@ class TestSite < Test::Unit::TestCase
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => source_dir})
end
assert_raise Jekyll::FatalException do
assert_raise Jekyll::Errors::FatalException do
site = Site.new(Jekyll.configuration)
end
end
@ -235,7 +235,7 @@ class TestSite < Test::Unit::TestCase
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => File.join(source_dir, "..")})
end
assert_raise Jekyll::FatalException do
assert_raise Jekyll::Errors::FatalException do
site = Site.new(Jekyll.configuration)
end
end
@ -332,7 +332,7 @@ class TestSite < Test::Unit::TestCase
bad_processor = "Custom::Markdown"
s = Site.new(Jekyll.configuration.merge({ 'markdown' => bad_processor }))
assert_raise Jekyll::FatalException do
assert_raise Jekyll::Errors::FatalException do
s.process
end
@ -352,7 +352,7 @@ class TestSite < Test::Unit::TestCase
should 'throw FatalException at process time' do
bad_processor = 'not a processor name'
s = Site.new(Jekyll.configuration.merge({ 'markdown' => bad_processor }))
assert_raise Jekyll::FatalException do
assert_raise Jekyll::Errors::FatalException do
s.process
end
end