Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added config includes to solve #514 and #703 #722

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 3 additions & 82 deletions lib/jekyll.rb
Expand Up @@ -27,6 +27,7 @@ def require_all(path)
require 'pygments' require 'pygments'


# internal requires # internal requires
require 'jekyll/configuration'
require 'jekyll/core_ext' require 'jekyll/core_ext'
require 'jekyll/site' require 'jekyll/site'
require 'jekyll/convertible' require 'jekyll/convertible'
Expand All @@ -48,69 +49,6 @@ def require_all(path)
module Jekyll module Jekyll
VERSION = '0.12.0' VERSION = '0.12.0'


# Default options. Overriden by values in _config.yml or command-line opts.
# Strings rather than symbols are used for compatability with YAML.
DEFAULTS = {
'safe' => false,
'auto' => false,
'server' => false,
'server_port' => 4000,

'source' => Dir.pwd,
'destination' => File.join(Dir.pwd, '_site'),
'plugins' => File.join(Dir.pwd, '_plugins'),
'layouts' => '_layouts',

'future' => true,
'lsi' => false,
'pygments' => false,
'markdown' => 'maruku',
'permalink' => 'date',
'include' => ['.htaccess'],
'paginate_path' => 'page:num',

'markdown_ext' => 'markdown,mkd,mkdn,md',
'textile_ext' => 'textile',

'maruku' => {
'use_tex' => false,
'use_divs' => false,
'png_engine' => 'blahtex',
'png_dir' => 'images/latex',
'png_url' => '/images/latex'
},

'rdiscount' => {
'extensions' => []
},

'redcarpet' => {
'extensions' => []
},

'kramdown' => {
'auto_ids' => true,
'footnote_nr' => 1,
'entity_output' => 'as_char',
'toc_levels' => '1..6',
'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo',
'use_coderay' => false,

'coderay' => {
'coderay_wrap' => 'div',
'coderay_line_numbers' => 'inline',
'coderay_line_number_start' => 1,
'coderay_tab_width' => 4,
'coderay_bold_every' => 10,
'coderay_css' => 'style'
}
},

'redcloth' => {
'hard_breaks' => true
}
}

# Public: Generate a Jekyll configuration Hash by merging the default # Public: Generate a Jekyll configuration Hash by merging the default
# options with anything in _config.yml, and adding the given options on top. # options with anything in _config.yml, and adding the given options on top.
# #
Expand All @@ -120,24 +58,7 @@ module Jekyll
# #
# Returns the final configuration Hash. # Returns the final configuration Hash.
def self.configuration(override) def self.configuration(override)
# _config.yml may override default source location, but until config = Configuration.new
# then, we need to know where to look for _config.yml config.configuration(override)
source = override['source'] || Jekyll::DEFAULTS['source']

# Get configuration from <source>/_config.yml
config_file = File.join(source, '_config.yml')
begin
config = YAML.load_file(config_file)
raise "Invalid configuration - #{config_file}" if !config.is_a?(Hash)
$stdout.puts "Configuration from #{config_file}"
rescue => err
$stderr.puts "WARNING: Could not read configuration. " +
"Using defaults (and options)."
$stderr.puts "\t" + err.to_s
config = {}
end

# Merge DEFAULTS < _config.yml < override
Jekyll::DEFAULTS.deep_merge(config).deep_merge(override)
end end
end end
148 changes: 148 additions & 0 deletions lib/jekyll/configuration.rb
@@ -0,0 +1,148 @@
module Jekyll
# Default options. Overriden by values in _config.yml or command-line opts.
# Strings rather than symbols are used for compatability with YAML.
DEFAULTS = {
'safe' => false,
'auto' => false,
'server' => false,
'server_port' => 4000,

'source' => Dir.pwd,
'destination' => File.join(Dir.pwd, '_site'),
'plugins' => File.join(Dir.pwd, '_plugins'),
'layouts' => '_layouts',

'future' => true,
'lsi' => false,
'pygments' => false,
'markdown' => 'maruku',
'permalink' => 'date',
'include' => ['.htaccess'],
'paginate_path' => 'page:num',

'markdown_ext' => 'markdown,mkd,mkdn,md',
'textile_ext' => 'textile',

'maruku' => {
'use_tex' => false,
'use_divs' => false,
'png_engine' => 'blahtex',
'png_dir' => 'images/latex',
'png_url' => '/images/latex'
},

'rdiscount' => {
'extensions' => []
},

'redcarpet' => {
'extensions' => []
},

'kramdown' => {
'auto_ids' => true,
'footnote_nr' => 1,
'entity_output' => 'as_char',
'toc_levels' => '1..6',
'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo',
'use_coderay' => false,

'coderay' => {
'coderay_wrap' => 'div',
'coderay_line_numbers' => 'inline',
'coderay_line_number_start' => 1,
'coderay_tab_width' => 4,
'coderay_bold_every' => 10,
'coderay_css' => 'style'
}
},

'redcloth' => {
'hard_breaks' => true
}
}

class Configuration

def initialize
# Keeps the config for further use
@merged_config_hash = Jekyll::DEFAULTS

# Remember all parsed paths to prevent circular inclusion
@parsed_paths = []
end

def configuration(override)

# _config.yml may override default source location, but until
# then, we need to know where to look for _config.yml
source = override['source'] || Jekyll::DEFAULTS['source']

# Might not be the best idea, needs discussion.
@merged_config_hash['source'] = source

# Get configuration from <source>/_config.yml
config_file = File.join(source, '_config.yml')
self.read_and_merge(config_file)

# finally merge merged_config and override
@merged_config_hash.deep_merge(override)
end

# Reads a configuration from a file and merges it into the instance variable.
#
# @param [String] config_file
# @return [Hash]
def read_and_merge(config_file)

begin
config = YAML.load_file(config_file)
raise "Invalid configuration - #{config_file}" if !config.is_a?(Hash)
$stdout.puts "Configuration from #{config_file}"

if config.has_key? 'additional_configs' and config['additional_configs'].is_a? Array
self.process_inclusions(config['additional_configs'], config_file)
end

rescue => err
$stderr.puts "WARNING: Could not read configuration. " +
"Using defaults (and options)."
$stderr.puts "\t" + err.to_s
config = {}
end
@merged_config_hash = @merged_config_hash.deep_merge(config)
end

# Expands a glob to an array of full qualified paths to each config file.
#
# @param [String] pattern
# @return [Array]
def glob_to_file_list(pattern, basedir = '')
basedir = @merged_config_hash['source'] unless (pattern[0] == '/' or basedir != '')
Dir[File.join(basedir, pattern)]
end

# Processes included files.
# the parent_file is just as a debug helper for detecting and solving circular references.
#
# @param [Array] list_of_inclusions
# @param [String] parent_file
def process_inclusions(list_of_inclusions, parent_file = '')
# This is a bit tricky: We want to prevent infinite inclusion loops.
# If we add it to the "parsed_path" list right at the top, we do not reflect the true state of the program.
# On the other hand, we cannot add it afterwards as the recursion could already happen in the path itself.
# Any better ideas than to add it at the start of the processing?
list_of_inclusions.each do |path_or_glob|
paths = self.glob_to_file_list(path_or_glob)
paths.each do |path|
if @parsed_paths.include? path
$stderr.puts "WARNING: Circular inclusion detected for #{path} in #{parent_file}. Skipping it."
next
end
@parsed_paths << path
self.read_and_merge(path)
end
end
end
end
end
11 changes: 11 additions & 0 deletions test/fixtures/configuration/one_inclusion/_config.yml
@@ -0,0 +1,11 @@
permalink: /blog/:year/:month/:day/:title/index.html
source: source
destination: public
plugins: plugins
code_dir: downloads/code
category_dir: blog/categories
markdown: rdiscount
pygments: false # default python pygments have been replaced by pygments.rb

additional_configs:
- additional_configs/_config_a.yml
@@ -0,0 +1 @@
root: /foobar
11 changes: 11 additions & 0 deletions test/fixtures/configuration/pattern_include/_config.yml
@@ -0,0 +1,11 @@
permalink: /blog/:year/:month/:day/:title/index.html
source: source
destination: public
plugins: plugins
code_dir: downloads/code
category_dir: blog/categories
markdown: rdiscount
pygments: false # default python pygments have been replaced by pygments.rb

additional_configs:
- additional_configs/_config_*.yml
@@ -0,0 +1 @@
root: /foobar
@@ -0,0 +1 @@
root: /buzz
12 changes: 12 additions & 0 deletions test/fixtures/configuration/root_config_override/_config.yml
@@ -0,0 +1,12 @@
root: /overruled
permalink: /blog/:year/:month/:day/:title/index.html
source: source
destination: public
plugins: plugins
code_dir: downloads/code
category_dir: blog/categories
markdown: rdiscount
pygments: false # default python pygments have been replaced by pygments.rb

additional_configs:
- additional_configs/_config_a.yml
@@ -0,0 +1 @@
root: /foobar
27 changes: 27 additions & 0 deletions test/test_configuration.rb
Expand Up @@ -26,4 +26,31 @@ class TestConfiguration < Test::Unit::TestCase
assert_equal Jekyll::DEFAULTS, Jekyll.configuration({}) assert_equal Jekyll::DEFAULTS, Jekyll.configuration({})
end end
end end

context "loading configuration with includes" do
setup do
@replacement_source_dir = File.join(Dir.pwd, 'test', 'fixtures', 'configuration')

end

should "include config_a when loading the main config file with one inclusion" do
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/one_inclusion/_config.yml")
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/one_inclusion/additional_configs/_config_a.yml")
config = Jekyll.configuration({"source" => File.join(@replacement_source_dir, 'one_inclusion')})
assert_equal "/foobar", config["root"]
end
should "include multiple config files when loading the main config file with one glob inclusion" do
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/pattern_include/_config.yml")
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/pattern_include/additional_configs/_config_a.yml")
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/pattern_include/additional_configs/_config_b.yml")
config = Jekyll.configuration({"source" => File.join(@replacement_source_dir, 'pattern_include')})
assert_equal "/buzz", config["root"]
end
should "let the root config overrule all specified configuration files" do
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/root_config_override/_config.yml")
mock($stdout).puts("Configuration from /Users/mario/Dev/Source/jekyll/test/fixtures/configuration/root_config_override/additional_configs/_config_a.yml")
config = Jekyll.configuration({"source" => File.join(@replacement_source_dir, 'root_config_override')})
assert_equal "/overruled", config["root"]
end
end
end end