Merge branch 'master' into frontmatter-defaults

This commit is contained in:
maul.esel
2013-10-14 19:44:06 +02:00
53 changed files with 856 additions and 203 deletions

View File

@@ -20,6 +20,7 @@ require 'fileutils'
require 'time'
require 'safe_yaml'
require 'English'
require 'pathname'
# 3rd party
require 'liquid'
@@ -61,7 +62,7 @@ require_all 'jekyll/tags'
SafeYAML::OPTIONS[:suppress_warnings] = true
module Jekyll
VERSION = '1.2.0'
VERSION = '1.2.1'
# Public: Generate a Jekyll configuration Hash by merging the default
# options with anything in _config.yml, and adding the given options on top.

View File

@@ -2,7 +2,7 @@ require 'set'
module Jekyll
class Site
# Handles the cleanup of a site's destination before the site is built.
# Handles the cleanup of a site's destination before it is built.
class Cleaner
def initialize(site)
@site = site
@@ -15,14 +15,14 @@ module Jekyll
private
# Private: The list of files and directories to be deleted during the cleanup process
# Private: The list of files and directories to be deleted during cleanup process
#
# Returns an Array with the file and directory paths
# Returns an Array of the file and directory paths
def obsolete_files
(existing_files - new_files - new_dirs + replaced_files).to_a
end
# Private: The list of existing files, except those included in keep_files and hidden files.
# Private: The list of existing files, apart from those included in keep_files and hidden files.
#
# Returns a Set with the file paths
def existing_files
@@ -33,7 +33,7 @@ module Jekyll
files
end
# Private: The list of files to be created when the site is built.
# Private: The list of files to be created when site is built.
#
# Returns a Set with the file paths
def new_files
@@ -42,7 +42,7 @@ module Jekyll
files
end
# Private: The list of directories to be created when the site is built.
# Private: The list of directories to be created when site is built.
# These are the parent directories of the files in #new_files.
#
# Returns a Set with the directory paths
@@ -57,7 +57,7 @@ module Jekyll
new_dirs.select { |dir| File.file?(dir) }.to_set
end
# Private: creates a regular expression from the config's keep_files array
# Private: Creates a regular expression from the config's keep_files array
#
# Examples
# ['.git','.svn'] creates the following regex: /\/(\.git|\/.svn)/
@@ -70,4 +70,4 @@ module Jekyll
end
end
end
end
end

View File

@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- encoding: utf-8 -*-
module Jekyll
module Commands
class Serve < Command
@@ -10,32 +10,56 @@ module Jekyll
FileUtils.mkdir_p(destination)
mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__))
mime_types = WEBrick::HTTPUtils::load_mime_types(mime_types_file)
# recreate NondisclosureName under utf-8 circumstance
fh_option = WEBrick::Config::FileHandler
fh_option[:NondisclosureName] = ['.ht*','~*']
s = HTTPServer.new(
:Port => options['port'],
:BindAddress => options['host'],
:MimeTypes => mime_types,
:DoNotReverseLookup => true
)
s = HTTPServer.new(webrick_options(options))
s.mount(options['baseurl'], HTTPServlet::FileHandler, destination, fh_option)
Jekyll.logger.info "Server address:", "http://#{s.config[:BindAddress]}:#{s.config[:Port]}"
if options['detach'] # detach the server
pid = Process.fork {s.start}
pid = Process.fork { s.start }
Process.detach(pid)
pid
Jekyll.logger.info "Server detatched with pid '#{pid}'.", "Run `kill -9 #{pid}' to stop the server."
else # create a new server thread, then join it with current terminal
t = Thread.new { s.start }
trap("INT") { s.shutdown }
t.join()
end
end
def self.webrick_options(config)
opts = {
:Port => config['port'],
:BindAddress => config['host'],
:MimeTypes => self.mime_types,
:DoNotReverseLookup => true,
:StartCallback => start_callback(config['detach'])
}
if !config['verbose']
opts.merge!({
:AccessLog => [],
:Logger => Log::new([], Log::WARN)
})
end
opts
end
def self.start_callback(detached)
unless detached
Proc.new { Jekyll.logger.info "Server running...", "press ctrl-c to stop." }
end
end
def self.mime_types
mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__))
WEBrick::HTTPUtils::load_mime_types(mime_types_file)
end
end
end
end

View File

@@ -10,10 +10,13 @@ module Jekyll
'destination' => File.join(Dir.pwd, '_site'),
'plugins' => '_plugins',
'layouts' => '_layouts',
'data_source' => '_data',
'keep_files' => ['.git','.svn'],
'timezone' => nil, # use the local timezone
'encoding' => nil, # use the system encoding
'safe' => false,
'detach' => false, # default to not detaching the server
'show_drafts' => nil,

View File

@@ -21,16 +21,23 @@ module Jekyll
self.content || ''
end
# Returns merged optin hash for File.read of self.site (if exists)
# and a given param
def merged_file_read_opts(opts)
(self.site ? self.site.file_read_opts : {}).merge(opts)
end
# Read the YAML frontmatter.
#
# base - The String path to the dir containing the file.
# name - The String filename of the file.
# opts - optional parameter to File.read, default at site configs
#
# Returns nothing.
def read_yaml(base, name)
def read_yaml(base, name, opts = {})
begin
self.content = File.read(File.join(base, name))
self.content = File.read_with_options(File.join(base, name),
merged_file_read_opts(opts))
if self.content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
self.content = $POSTMATCH
self.data = YAML.safe_load($1)
@@ -78,10 +85,13 @@ module Jekyll
# info - the info for Liquid
#
# Returns the converted content
def render_liquid(content, payload, info)
def render_liquid(content, payload, info, path = nil)
Liquid::Template.parse(content).render!(payload, info)
rescue Tags::IncludeTagError => e
Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{e.path}"
raise e
rescue Exception => e
Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{self.path}"
Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{path || self.path}"
raise e
end
@@ -113,7 +123,8 @@ module Jekyll
self.output = self.render_liquid(layout.content,
payload,
info)
info,
File.join(self.site.config['layouts'], layout.name))
if layout = layouts[layout.data["layout"]]
if used.include?(layout)

View File

@@ -83,3 +83,18 @@ module Enumerable
any? { |exp| File.fnmatch?(exp, e) }
end
end
# Ruby 1.8's File.read don't support option.
# read_with_options ignore optional parameter for 1.8,
# and act as alias for 1.9 or later.
class File
if RUBY_VERSION < '1.9'
def self.read_with_options(path, opts = {})
self.read(path)
end
else
def self.read_with_options(path, opts = {})
self.read(path, opts)
end
end
end

View File

@@ -19,10 +19,10 @@ module Jekyll
]
# Attributes for Liquid templates
ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID.concat(%w[
ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID + %w[
content
excerpt
])
]
# Post name validator. Post filenames must be like:
# 2008-11-05-my-awesome-post.textile

View File

@@ -3,7 +3,7 @@ module Jekyll
attr_accessor :config, :layouts, :posts, :pages, :static_files,
:categories, :exclude, :include, :source, :dest, :lsi, :pygments,
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
:show_drafts, :keep_files, :baseurl
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts
attr_accessor :converters, :generators
@@ -22,6 +22,9 @@ module Jekyll
self.plugins = plugins_path
self.permalink_style = config['permalink'].to_sym
self.file_read_opts = {}
self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
self.reset
self.setup
end
@@ -53,6 +56,7 @@ module Jekyll
self.static_files = []
self.categories = Hash.new { |hash, key| hash[key] = [] }
self.tags = Hash.new { |hash, key| hash[key] = [] }
self.data = {}
if self.limit_posts < 0
raise ArgumentError, "limit_posts must be a non-negative number"
@@ -63,11 +67,7 @@ module Jekyll
#
# Returns nothing.
def setup
# Check that the destination dir isn't the source dir or a directory
# parent to the source dir.
if self.source =~ /^#{self.dest}/
raise FatalException.new "Destination directory cannot be or contain the Source directory."
end
ensure_not_in_dest
# If safe mode is off, load in any Ruby files under the plugins
# directory.
@@ -83,6 +83,17 @@ module Jekyll
self.generators = instantiate_subclasses(Jekyll::Generator)
end
# Check that the destination dir isn't the source dir or a directory
# parent to the source dir.
def ensure_not_in_dest
dest = Pathname.new(self.dest)
Pathname.new(self.source).ascend do |path|
if path == dest
raise FatalException.new "Destination directory cannot be or contain the Source directory."
end
end
end
# Internal: Setup the plugin search path
#
# Returns an Array of plugin search paths
@@ -100,6 +111,7 @@ module Jekyll
def read
self.read_layouts
self.read_directories
self.read_data(config['data_source'])
end
# Read all the files in <source>/<layouts> and create a new Layout object
@@ -110,7 +122,7 @@ module Jekyll
base = File.join(self.source, self.config['layouts'])
return unless File.exists?(base)
entries = []
Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
Dir.chdir(base) { entries = filter_entries(Dir['**/*.*']) }
entries.each do |f|
name = f.split(".")[0..-2].join(".")
@@ -187,6 +199,25 @@ module Jekyll
end
end
# Read and parse all yaml files under <source>/<dir>
#
# Returns nothing
def read_data(dir)
base = File.join(self.source, dir)
return unless File.directory?(base) && (!self.safe || !File.symlink?(base))
entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] }
entries.delete_if { |e| File.directory?(File.join(base, e)) }
entries.each do |entry|
path = File.join(self.source, dir, entry)
next if File.symlink?(path) && self.safe
key = sanitize_filename(File.basename(entry, '.*'))
self.data[key] = YAML.safe_load_file(path)
end
end
# Run each of the Generators.
#
# Returns nothing.
@@ -252,6 +283,14 @@ module Jekyll
hash
end
# Prepare site data for site payload. The method maintains backward compatibility
# if the key 'data' is already used in _config.yml.
#
# Returns the Hash to be hooked to site.data.
def site_data
self.config['data'] || self.data
end
# The Hash payload containing site-wide data.
#
# Returns the Hash: { "site" => data } where data is a Hash with keys:
@@ -273,7 +312,8 @@ module Jekyll
"pages" => self.pages,
"html_pages" => self.pages.reject { |page| !page.html? },
"categories" => post_attr_hash('categories'),
"tags" => post_attr_hash('tags')})}
"tags" => post_attr_hash('tags'),
"data" => site_data})}
end
# Filter out any files/directories that are hidden or backup files (start
@@ -387,5 +427,11 @@ module Jekyll
def site_cleaner
@site_cleaner ||= Cleaner.new(self)
end
def sanitize_filename(name)
name = name.gsub(/[^\w\s_-]+/, '')
name = name.gsub(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2')
name = name.gsub(/\s+/, '_')
end
end
end

View File

@@ -12,7 +12,15 @@ module Jekyll
gist_id, filename = tag_contents[0], tag_contents[1]
gist_script_tag(gist_id, filename)
else
"Error parsing gist id"
raise ArgumentError.new <<-eos
Syntax error in tag 'gist' while parsing the following markup:
#{@markup}
Valid syntax:
for public gists: {% gist 1234567 %}
for private gists: {% gist user/1234567 %}
eos
end
end

View File

@@ -14,7 +14,7 @@ module Jekyll
def initialize(tag_name, markup, tokens)
super
if markup.strip =~ SYNTAX
@lang = $1
@lang = $1.downcase
@options = {}
if defined?($2) && $2 != ''
$2.split.each do |opt|

View File

@@ -1,21 +1,33 @@
module Jekyll
module Tags
class IncludeTagError < StandardError
attr_accessor :path
def initialize(msg, path)
super(msg)
@path = path
end
end
class IncludeTag < Liquid::Tag
MATCHER = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
SYNTAX_EXAMPLE = "{% include file.ext param='value' param2='value' %}"
VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
INCLUDES_DIR = '_includes'
def initialize(tag_name, markup, tokens)
super
@file, @params = markup.strip.split(' ', 2);
validate_params if @params
end
def parse_params(context)
validate_syntax
params = {}
markup = @params
while match = MATCHER.match(markup) do
while match = VALID_SYNTAX.match(markup) do
markup = markup[match.end(0)..-1]
value = if match[2]
@@ -31,55 +43,91 @@ module Jekyll
params
end
# ensure the entire markup string from start to end is valid syntax, and params are separated by spaces
def validate_syntax
full_matcher = Regexp.compile('\A\s*(?:' + MATCHER.to_s + '(?=\s|\z)\s*)*\z')
unless @params =~ full_matcher
raise SyntaxError.new <<-eos
def validate_file_name
if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
raise ArgumentError.new <<-eos
Invalid syntax for include tag. File contains invalid characters or sequences:
#{@file}
Valid syntax:
#{SYNTAX_EXAMPLE}
eos
end
end
def validate_params
full_valid_syntax = Regexp.compile('\A\s*(?:' + VALID_SYNTAX.to_s + '(?=\s|\z)\s*)*\z')
unless @params =~ full_valid_syntax
raise ArgumentError.new <<-eos
Invalid syntax for include tag:
#{@params}
Valid syntax:
{% include file.ext param='value' param2="value" %}
#{SYNTAX_EXAMPLE}
eos
end
end
def render(context)
includes_dir = File.join(context.registers[:site].source, '_includes')
# Grab file read opts in the context
def file_read_opts(context)
context.registers[:site].file_read_opts
end
if error = validate_file(includes_dir)
return error
end
source = File.read(File.join(includes_dir, @file))
partial = Liquid::Template.parse(source)
context.stack do
context['include'] = parse_params(context) if @params
partial.render(context)
def retrieve_variable(context)
if /\{\{([\w\-\.]+)\}\}/ =~ @file
raise ArgumentError.new("No variable #{$1} was found in include tag") if context[$1].nil?
@file = context[$1]
end
end
def validate_file(includes_dir)
if File.symlink?(includes_dir)
return "Includes directory '#{includes_dir}' cannot be a symlink"
end
def render(context)
dir = File.join(context.registers[:site].source, INCLUDES_DIR)
validate_dir(dir, context.registers[:site].safe)
if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
return "Include file '#{@file}' contains invalid characters or sequences"
end
retrieve_variable(context)
validate_file_name
file = File.join(includes_dir, @file)
file = File.join(dir, @file)
validate_file(file, context.registers[:site].safe)
partial = Liquid::Template.parse(source(file, context))
context.stack do
context['include'] = parse_params(context) if @params
partial.render!(context)
end
rescue => e
raise IncludeTagError.new e.message, File.join(INCLUDES_DIR, @file)
end
def validate_dir(dir, safe)
if File.symlink?(dir) && safe
raise IOError.new "Includes directory '#{dir}' cannot be a symlink"
end
end
def validate_file(file, safe)
if !File.exists?(file)
return "Included file #{@file} not found in _includes directory"
elsif File.symlink?(file)
return "The included file '_includes/#{@file}' should not be a symlink"
raise IOError.new "Included file '#{@file}' not found in '#{INCLUDES_DIR}' directory"
elsif File.symlink?(file) && safe
raise IOError.new "The included file '#{INCLUDES_DIR}/#{@file}' should not be a symlink"
end
end
def blank?
false
end
# This method allows to modify the file content by inheriting from the class.
def source(file, context)
File.read_with_options(file, file_read_opts(context))
end
end
end
end

View File

@@ -50,9 +50,11 @@ module Jekyll
end
end
puts "ERROR: post_url: \"#{@orig_post}\" could not be found"
raise ArgumentError.new <<-eos
Could not find post "#{@orig_post}" in tag 'post_url'.
return "#"
Make sure the post exists and the name is correct.
eos
end
end
end

View File

@@ -34,16 +34,16 @@ a:visited { color: #a0a; }
/* Home
/*
/*****************************************************************************/
ul.posts {
.posts {
list-style-type: none;
margin-bottom: 2em;
}
ul.posts li {
.posts li {
line-height: 1.75em;
}
ul.posts span {
.posts span {
color: #aaa;
font-family: Monaco, "Courier New", monospace;
font-size: 80%;
@@ -63,38 +63,38 @@ ul.posts span {
line-height: 1.5em;
}
.site .header a {
.header a {
font-weight: bold;
text-decoration: none;
}
.site .header h1.title {
.title {
display: inline-block;
margin-bottom: 2em;
}
.site .header h1.title a {
.title a {
color: #a00;
}
.site .header h1.title a:hover {
.title a:hover {
color: #000;
}
.site .header a.extra {
.header a.extra {
color: #aaa;
margin-left: 1em;
}
.site .header a.extra:hover {
.header a.extra:hover {
color: #000;
}
.site .meta {
.meta {
color: #aaa;
}
.site .footer {
.footer {
font-size: 80%;
color: #666;
border-top: 4px solid #eee;
@@ -102,22 +102,22 @@ ul.posts span {
overflow: hidden;
}
.site .footer .contact {
.footer .contact {
float: left;
margin-right: 3em;
}
.site .footer .contact a {
.footer .contact a {
color: #8085C1;
}
.site .footer .rss {
.footer .rss {
margin-top: 1.1em;
margin-right: -.2em;
float: right;
}
.site .footer .rss img {
.footer .rss img {
border: 0;
}