mirror of
https://github.com/jekyll/jekyll.git
synced 2026-04-06 03:01:43 -04:00
Merge branch 'master' into permalink-special-characters
Conflicts: lib/jekyll/page.rb lib/jekyll/post.rb
This commit is contained in:
130
lib/jekyll.rb
130
lib/jekyll.rb
@@ -24,10 +24,13 @@ require 'English'
|
||||
# 3rd party
|
||||
require 'liquid'
|
||||
require 'maruku'
|
||||
require 'pygments'
|
||||
require 'colorator'
|
||||
|
||||
# internal requires
|
||||
require 'jekyll/core_ext'
|
||||
require 'jekyll/stevenson'
|
||||
require 'jekyll/deprecator'
|
||||
require 'jekyll/configuration'
|
||||
require 'jekyll/site'
|
||||
require 'jekyll/convertible'
|
||||
require 'jekyll/url'
|
||||
@@ -38,6 +41,7 @@ require 'jekyll/draft'
|
||||
require 'jekyll/filters'
|
||||
require 'jekyll/static_file'
|
||||
require 'jekyll/errors'
|
||||
require 'jekyll/related_posts'
|
||||
|
||||
# extensions
|
||||
require 'jekyll/plugin'
|
||||
@@ -47,123 +51,45 @@ require 'jekyll/command'
|
||||
|
||||
require_all 'jekyll/commands'
|
||||
require_all 'jekyll/converters'
|
||||
require_all 'jekyll/converters/markdown'
|
||||
require_all 'jekyll/generators'
|
||||
require_all 'jekyll/tags'
|
||||
|
||||
SafeYAML::OPTIONS[:suppress_warnings] = true
|
||||
|
||||
module Jekyll
|
||||
VERSION = '1.0.0.beta4'
|
||||
|
||||
# Default options. Overriden by values in _config.yml.
|
||||
# Strings rather than symbols are used for compatability with YAML.
|
||||
DEFAULTS = {
|
||||
'source' => Dir.pwd,
|
||||
'destination' => File.join(Dir.pwd, '_site'),
|
||||
'plugins' => '_plugins',
|
||||
'layouts' => '_layouts',
|
||||
'keep_files' => ['.git','.svn'],
|
||||
|
||||
'future' => true, # remove and make true just default
|
||||
'pygments' => true, # remove and make true just default
|
||||
|
||||
'markdown' => 'maruku',
|
||||
'permalink' => 'date',
|
||||
'baseurl' => '/',
|
||||
'include' => ['.htaccess'],
|
||||
'paginate_path' => 'page:num',
|
||||
|
||||
'markdown_ext' => 'markdown,mkd,mkdn,md',
|
||||
'textile_ext' => 'textile',
|
||||
|
||||
'port' => '4000',
|
||||
'host' => '0.0.0.0',
|
||||
|
||||
'excerpt_separator' => "\n\n",
|
||||
|
||||
'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
|
||||
}
|
||||
}
|
||||
VERSION = '1.0.3'
|
||||
|
||||
# Public: Generate a Jekyll configuration Hash by merging the default
|
||||
# options with anything in _config.yml, and adding the given options on top.
|
||||
#
|
||||
# override - A Hash of config directives that override any options in both
|
||||
# the defaults and the config file. See Jekyll::DEFAULTS for a
|
||||
# the defaults and the config file. See Jekyll::Configuration::DEFAULTS for a
|
||||
# list of option names and their defaults.
|
||||
#
|
||||
# Returns the final configuration Hash.
|
||||
def self.configuration(override)
|
||||
# Convert any symbol keys to strings and remove the old key/values
|
||||
override = override.reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }
|
||||
|
||||
# _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']
|
||||
|
||||
# Get configuration from <source>/_config.yml or <source>/<config_file>
|
||||
config_file = override.delete('config')
|
||||
config_file = File.join(source, "_config.yml") if config_file.to_s.empty?
|
||||
|
||||
begin
|
||||
config = YAML.safe_load_file(config_file)
|
||||
raise "Configuration file: (INVALID) #{config_file}" if !config.is_a?(Hash)
|
||||
$stdout.puts "Configuration file: #{config_file}"
|
||||
rescue SystemCallError
|
||||
# Errno:ENOENT = file not found
|
||||
$stderr.puts "Configuration file: none"
|
||||
config = {}
|
||||
rescue => err
|
||||
$stderr.puts " " +
|
||||
"WARNING: Error reading configuration. " +
|
||||
"Using defaults (and options)."
|
||||
$stderr.puts "#{err}"
|
||||
config = {}
|
||||
end
|
||||
|
||||
# Provide backwards-compatibility
|
||||
if config['auto']
|
||||
$stderr.puts "Deprecation: ".rjust(20) + "'auto' has been changed to " +
|
||||
"'watch'. Please update your configuration to use 'watch'."
|
||||
config['watch'] = config['auto']
|
||||
end
|
||||
config = Configuration[Configuration::DEFAULTS]
|
||||
override = Configuration[override].stringify_keys
|
||||
config = config.read_config_files(config.config_files(override))
|
||||
|
||||
# Merge DEFAULTS < _config.yml < override
|
||||
Jekyll::DEFAULTS.deep_merge(config).deep_merge(override)
|
||||
config = config.deep_merge(override).stringify_keys
|
||||
set_timezone(config['timezone']) if config['timezone']
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
# Static: Set the TZ environment variable to use the timezone specified
|
||||
#
|
||||
# timezone - the IANA Time Zone
|
||||
#
|
||||
# Returns nothing
|
||||
def self.set_timezone(timezone)
|
||||
ENV['TZ'] = timezone
|
||||
end
|
||||
|
||||
def self.logger
|
||||
@logger ||= Stevenson.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,5 +8,20 @@ module Jekyll
|
||||
dirs += ['*']
|
||||
end
|
||||
end
|
||||
|
||||
# Static: Run Site#process and catch errors
|
||||
#
|
||||
# site - the Jekyll::Site object
|
||||
#
|
||||
# Returns nothing
|
||||
def self.process_site(site)
|
||||
site.process
|
||||
rescue Jekyll::FatalException => e
|
||||
puts
|
||||
Jekyll.logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:"
|
||||
Jekyll.logger.error "", "------------------------------------"
|
||||
Jekyll.logger.error "", e.message
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,18 +17,10 @@ module Jekyll
|
||||
def self.build(site, options)
|
||||
source = options['source']
|
||||
destination = options['destination']
|
||||
puts " Source: #{source}"
|
||||
puts " Destination: #{destination}"
|
||||
print " Generating... "
|
||||
begin
|
||||
site.process
|
||||
rescue Jekyll::FatalException => e
|
||||
puts
|
||||
puts "ERROR: YOUR SITE COULD NOT BE BUILT:"
|
||||
puts "------------------------------------"
|
||||
puts e.message
|
||||
exit(1)
|
||||
end
|
||||
Jekyll.logger.info "Source:", source
|
||||
Jekyll.logger.info "Destination:", destination
|
||||
print Jekyll.logger.formatted_topic "Generating..."
|
||||
self.process_site(site)
|
||||
puts "done."
|
||||
end
|
||||
|
||||
@@ -44,23 +36,15 @@ module Jekyll
|
||||
source = options['source']
|
||||
destination = options['destination']
|
||||
|
||||
puts " Auto-regeneration: enabled"
|
||||
Jekyll.logger.info "Auto-regeneration:", "enabled"
|
||||
|
||||
dw = DirectoryWatcher.new(source, :glob => self.globs(source, destination), :pre_load => true)
|
||||
dw.interval = 1
|
||||
|
||||
dw.add_observer do |*args|
|
||||
t = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
print " Regenerating: #{args.size} files at #{t} "
|
||||
begin
|
||||
site.process
|
||||
rescue Jekyll::FatalException => e
|
||||
puts
|
||||
puts "ERROR: YOUR SITE COULD NOT BE BUILT:"
|
||||
puts "------------------------------------"
|
||||
puts e.message
|
||||
exit(1)
|
||||
end
|
||||
print Jekyll.logger.formatted_topic("Regenerating:") + "#{args.size} files at #{t} "
|
||||
self.process_site(site)
|
||||
puts "...done."
|
||||
end
|
||||
|
||||
|
||||
29
lib/jekyll/commands/doctor.rb
Normal file
29
lib/jekyll/commands/doctor.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module Jekyll
|
||||
module Commands
|
||||
class Doctor < Command
|
||||
class << self
|
||||
def process(options)
|
||||
site = Jekyll::Site.new(options)
|
||||
site.read
|
||||
|
||||
unless deprecated_relative_permalinks(site)
|
||||
Jekyll.logger.info "Your test results", "are in. Everything looks fine."
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_relative_permalinks(site)
|
||||
contains_deprecated_pages = false
|
||||
site.pages.each do |page|
|
||||
if page.uses_relative_permalinks
|
||||
Jekyll.logger.warn "Deprecation:", "'#{page.path}' uses relative" +
|
||||
" permalinks which will be deprecated in" +
|
||||
" Jekyll v1.1 and beyond."
|
||||
contains_deprecated_pages = true
|
||||
end
|
||||
end
|
||||
contains_deprecated_pages
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,20 +3,36 @@ require 'erb'
|
||||
module Jekyll
|
||||
module Commands
|
||||
class New < Command
|
||||
def self.process(args)
|
||||
def self.process(args, options = {})
|
||||
raise ArgumentError.new('You must specify a path.') if args.empty?
|
||||
|
||||
new_blog_path = File.expand_path(args.join(" "), Dir.pwd)
|
||||
FileUtils.mkdir_p new_blog_path
|
||||
|
||||
create_sample_files new_blog_path
|
||||
|
||||
File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f|
|
||||
f.write(self.scaffold_post_content(site_template))
|
||||
if preserve_source_location?(new_blog_path, options)
|
||||
Jekyll.logger.error "Conflict:", "#{new_blog_path} exists and is not empty."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
if options[:blank]
|
||||
create_blank_site new_blog_path
|
||||
else
|
||||
create_sample_files new_blog_path
|
||||
|
||||
File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f|
|
||||
f.write(self.scaffold_post_content(site_template))
|
||||
end
|
||||
end
|
||||
|
||||
puts "New jekyll site installed in #{new_blog_path}."
|
||||
end
|
||||
|
||||
def self.create_blank_site(path)
|
||||
Dir.chdir(path) do
|
||||
FileUtils.mkdir(%w(_layouts _posts _drafts))
|
||||
FileUtils.touch("index.html")
|
||||
end
|
||||
end
|
||||
|
||||
def self.scaffold_post_content(template_site)
|
||||
ERB.new(File.read(File.expand_path(scaffold_path, site_template))).result
|
||||
end
|
||||
@@ -29,6 +45,11 @@ module Jekyll
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.preserve_source_location?(path, options)
|
||||
!options[:force] && !Dir["#{path}/**/*"].empty?
|
||||
end
|
||||
|
||||
def self.create_sample_files(path)
|
||||
FileUtils.cp_r site_template + '/.', path
|
||||
FileUtils.rm File.expand_path(scaffold_path, path)
|
||||
|
||||
201
lib/jekyll/configuration.rb
Normal file
201
lib/jekyll/configuration.rb
Normal file
@@ -0,0 +1,201 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
module Jekyll
|
||||
class Configuration < Hash
|
||||
|
||||
# Default options. Overridden by values in _config.yml.
|
||||
# Strings rather than symbols are used for compatibility with YAML.
|
||||
DEFAULTS = {
|
||||
'source' => Dir.pwd,
|
||||
'destination' => File.join(Dir.pwd, '_site'),
|
||||
'plugins' => '_plugins',
|
||||
'layouts' => '_layouts',
|
||||
'keep_files' => ['.git','.svn'],
|
||||
|
||||
'timezone' => nil, # use the local timezone
|
||||
|
||||
'safe' => false,
|
||||
'show_drafts' => nil,
|
||||
'limit_posts' => 0,
|
||||
'lsi' => false,
|
||||
'future' => true, # remove and make true just default
|
||||
'pygments' => true,
|
||||
|
||||
'relative_permalinks' => true, # backwards-compatibility with < 1.0
|
||||
# will be set to false once 1.1 hits
|
||||
|
||||
'markdown' => 'maruku',
|
||||
'permalink' => 'date',
|
||||
'baseurl' => '/',
|
||||
'include' => ['.htaccess'],
|
||||
'exclude' => [],
|
||||
'paginate_path' => '/page:num',
|
||||
|
||||
'markdown_ext' => 'markdown,mkd,mkdn,md',
|
||||
'textile_ext' => 'textile',
|
||||
|
||||
'port' => '4000',
|
||||
'host' => '0.0.0.0',
|
||||
|
||||
'excerpt_separator' => "\n\n",
|
||||
|
||||
'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: Turn all keys into string
|
||||
#
|
||||
# Return a copy of the hash where all its keys are strings
|
||||
def stringify_keys
|
||||
reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }
|
||||
end
|
||||
|
||||
# Public: Directory of the Jekyll source folder
|
||||
#
|
||||
# override - the command-line options hash
|
||||
#
|
||||
# Returns the path to the Jekyll source directory
|
||||
def source(override)
|
||||
override['source'] || self['source'] || DEFAULTS['source']
|
||||
end
|
||||
|
||||
# Public: Generate list of configuration files from the override
|
||||
#
|
||||
# override - the command-line options hash
|
||||
#
|
||||
# Returns an Array of config files
|
||||
def config_files(override)
|
||||
# Get configuration from <source>/_config.yml or <source>/<config_file>
|
||||
config_files = override.delete('config')
|
||||
config_files = File.join(source(override), "_config.yml") if config_files.to_s.empty?
|
||||
config_files = [config_files] unless config_files.is_a? Array
|
||||
config_files
|
||||
end
|
||||
|
||||
# Public: Read configuration and return merged Hash
|
||||
#
|
||||
# file - the path to the YAML file to be read in
|
||||
#
|
||||
# Returns this configuration, overridden by the values in the file
|
||||
def read_config_file(file)
|
||||
next_config = YAML.safe_load_file(file)
|
||||
raise "Configuration file: (INVALID) #{file}".yellow if !next_config.is_a?(Hash)
|
||||
Jekyll.logger.info "Configuration file:", file
|
||||
next_config
|
||||
end
|
||||
|
||||
# Public: Read in a list of configuration files and merge with this hash
|
||||
#
|
||||
# files - the list of configuration file paths
|
||||
#
|
||||
# Returns the full configuration, with the defaults overridden by the values in the
|
||||
# configuration files
|
||||
def read_config_files(files)
|
||||
configuration = clone
|
||||
|
||||
begin
|
||||
files.each do |config_file|
|
||||
new_config = read_config_file(config_file)
|
||||
configuration = configuration.deep_merge(new_config)
|
||||
end
|
||||
rescue SystemCallError
|
||||
# Errno:ENOENT = file not found
|
||||
Jekyll.logger.warn "Configuration file:", "none"
|
||||
rescue => err
|
||||
Jekyll.logger.warn "WARNING:", "Error reading configuration. " +
|
||||
"Using defaults (and options)."
|
||||
$stderr.puts "#{err}"
|
||||
end
|
||||
|
||||
configuration.backwards_compatibilize
|
||||
end
|
||||
|
||||
# Public: Split a CSV string into an array containing its values
|
||||
#
|
||||
# csv - the string of comma-separated values
|
||||
#
|
||||
# Returns an array of the values contained in the CSV
|
||||
def csv_to_array(csv)
|
||||
csv.split(",").map(&:strip)
|
||||
end
|
||||
|
||||
# Public: Ensure the proper options are set in the configuration to allow for
|
||||
# backwards-compatibility with Jekyll pre-1.0
|
||||
#
|
||||
# Returns the backwards-compatible configuration
|
||||
def backwards_compatibilize
|
||||
config = clone
|
||||
# Provide backwards-compatibility
|
||||
if config.has_key?('auto') || config.has_key?('watch')
|
||||
Jekyll.logger.warn "Deprecation:", "Auto-regeneration can no longer" +
|
||||
" be set from your configuration file(s). Use the"+
|
||||
" --watch/-w command-line option instead."
|
||||
config.delete('auto')
|
||||
config.delete('watch')
|
||||
end
|
||||
|
||||
if config.has_key? 'server'
|
||||
Jekyll.logger.warn "Deprecation:", "The 'server' configuration option" +
|
||||
" is no longer accepted. Use the 'jekyll serve'" +
|
||||
" subcommand to serve your site with WEBrick."
|
||||
config.delete('server')
|
||||
end
|
||||
|
||||
if config.has_key? 'server_port'
|
||||
Jekyll.logger.warn "Deprecation:", "The 'server_port' configuration option" +
|
||||
" has been renamed to 'port'. Please update your config" +
|
||||
" file accordingly."
|
||||
# copy but don't overwrite:
|
||||
config['port'] = config['server_port'] unless config.has_key?('port')
|
||||
config.delete('server_port')
|
||||
end
|
||||
|
||||
%w[include exclude].each do |option|
|
||||
if config.fetch(option, []).is_a?(String)
|
||||
Jekyll.logger.warn "Deprecation:", "The '#{option}' configuration option" +
|
||||
" must now be specified as an array, but you specified" +
|
||||
" a string. For now, we've treated the string you provided" +
|
||||
" as a list of comma-separated values."
|
||||
config[option] = csv_to_array(config[option])
|
||||
end
|
||||
end
|
||||
config
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -8,87 +8,23 @@ module Jekyll
|
||||
|
||||
def setup
|
||||
return if @setup
|
||||
case @config['markdown']
|
||||
@parser = case @config['markdown']
|
||||
when 'redcarpet'
|
||||
begin
|
||||
require 'redcarpet'
|
||||
|
||||
@renderer ||= Class.new(Redcarpet::Render::HTML) do
|
||||
def block_code(code, lang)
|
||||
lang = lang && lang.split.first || "text"
|
||||
output = add_code_tags(
|
||||
Pygments.highlight(code, :lexer => lang, :options => { :encoding => 'utf-8' }),
|
||||
lang
|
||||
)
|
||||
end
|
||||
|
||||
def add_code_tags(code, lang)
|
||||
code = code.sub(/<pre>/,'<pre><code class="' + lang + '">')
|
||||
code = code.sub(/<\/pre>/,"</code></pre>")
|
||||
end
|
||||
end
|
||||
|
||||
@redcarpet_extensions = {}
|
||||
@config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true }
|
||||
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")
|
||||
end
|
||||
RedcarpetParser.new @config
|
||||
when 'kramdown'
|
||||
begin
|
||||
require 'kramdown'
|
||||
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")
|
||||
end
|
||||
KramdownParser.new @config
|
||||
when 'rdiscount'
|
||||
begin
|
||||
require 'rdiscount'
|
||||
@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
|
||||
RDiscountParser.new @config
|
||||
when 'maruku'
|
||||
begin
|
||||
require 'maruku'
|
||||
|
||||
if @config['maruku']['use_divs']
|
||||
require 'maruku/ext/div'
|
||||
STDERR.puts 'Maruku: Using extended syntax for div elements.'
|
||||
end
|
||||
|
||||
if @config['maruku']['use_tex']
|
||||
require 'maruku/ext/math'
|
||||
STDERR.puts "Maruku: Using LaTeX extension. Images in `#{@config['maruku']['png_dir']}`."
|
||||
|
||||
# Switch off MathML output
|
||||
MaRuKu::Globals[:html_math_output_mathml] = false
|
||||
MaRuKu::Globals[:html_math_engine] = 'none'
|
||||
|
||||
# Turn on math to PNG support with blahtex
|
||||
# Resulting PNGs stored in `images/latex`
|
||||
MaRuKu::Globals[:html_math_output_png] = true
|
||||
MaRuKu::Globals[:html_png_engine] = @config['maruku']['png_engine']
|
||||
MaRuKu::Globals[:html_png_dir] = @config['maruku']['png_dir']
|
||||
MaRuKu::Globals[:html_png_url] = @config['maruku']['png_url']
|
||||
end
|
||||
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")
|
||||
end
|
||||
MarukuParser.new @config
|
||||
else
|
||||
STDERR.puts "Invalid Markdown processor: #{@config['markdown']}"
|
||||
STDERR.puts " Valid options are [ maruku | rdiscount | kramdown ]"
|
||||
STDERR.puts " Valid options are [ maruku | rdiscount | kramdown | redcarpet ]"
|
||||
raise FatalException.new("Invalid Markdown process: #{@config['markdown']}")
|
||||
end
|
||||
@setup = true
|
||||
end
|
||||
|
||||
|
||||
def matches(ext)
|
||||
rgx = '(' + @config['markdown_ext'].gsub(',','|') +')'
|
||||
ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
|
||||
@@ -100,49 +36,7 @@ module Jekyll
|
||||
|
||||
def convert(content)
|
||||
setup
|
||||
case @config['markdown']
|
||||
when 'redcarpet'
|
||||
@redcarpet_extensions[:fenced_code_blocks] = !@redcarpet_extensions[:no_fenced_code_blocks]
|
||||
@renderer.send :include, Redcarpet::Render::SmartyPants if @redcarpet_extensions[:smart]
|
||||
markdown = Redcarpet::Markdown.new(@renderer.new(@redcarpet_extensions), @redcarpet_extensions)
|
||||
markdown.render(content)
|
||||
when 'kramdown'
|
||||
# Check for use of coderay
|
||||
if @config['kramdown']['use_coderay']
|
||||
Kramdown::Document.new(content, {
|
||||
:auto_ids => @config['kramdown']['auto_ids'],
|
||||
:footnote_nr => @config['kramdown']['footnote_nr'],
|
||||
:entity_output => @config['kramdown']['entity_output'],
|
||||
:toc_levels => @config['kramdown']['toc_levels'],
|
||||
:smart_quotes => @config['kramdown']['smart_quotes'],
|
||||
|
||||
:coderay_wrap => @config['kramdown']['coderay']['coderay_wrap'],
|
||||
:coderay_line_numbers => @config['kramdown']['coderay']['coderay_line_numbers'],
|
||||
:coderay_line_number_start => @config['kramdown']['coderay']['coderay_line_number_start'],
|
||||
:coderay_tab_width => @config['kramdown']['coderay']['coderay_tab_width'],
|
||||
:coderay_bold_every => @config['kramdown']['coderay']['coderay_bold_every'],
|
||||
:coderay_css => @config['kramdown']['coderay']['coderay_css']
|
||||
}).to_html
|
||||
else
|
||||
# not using coderay
|
||||
Kramdown::Document.new(content, {
|
||||
:auto_ids => @config['kramdown']['auto_ids'],
|
||||
:footnote_nr => @config['kramdown']['footnote_nr'],
|
||||
:entity_output => @config['kramdown']['entity_output'],
|
||||
:toc_levels => @config['kramdown']['toc_levels'],
|
||||
:smart_quotes => @config['kramdown']['smart_quotes']
|
||||
}).to_html
|
||||
end
|
||||
when 'rdiscount'
|
||||
rd = RDiscount.new(content, *@rdiscount_extensions)
|
||||
html = rd.to_html
|
||||
if rd.generate_toc and html.include?(@config['rdiscount']['toc_token'])
|
||||
html.gsub!(@config['rdiscount']['toc_token'], rd.toc_content.force_encoding('utf-8'))
|
||||
end
|
||||
html
|
||||
when 'maruku'
|
||||
Maruku.new(content).to_html
|
||||
end
|
||||
@parser.convert(content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
44
lib/jekyll/converters/markdown/kramdown_parser.rb
Normal file
44
lib/jekyll/converters/markdown/kramdown_parser.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
module Jekyll
|
||||
module Converters
|
||||
class Markdown
|
||||
class KramdownParser
|
||||
def initialize(config)
|
||||
require 'kramdown'
|
||||
@config = config
|
||||
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")
|
||||
end
|
||||
|
||||
def convert(content)
|
||||
# Check for use of coderay
|
||||
kramdown_configs = if @config['kramdown']['use_coderay']
|
||||
base_kramdown_configs.merge({
|
||||
:coderay_wrap => @config['kramdown']['coderay']['coderay_wrap'],
|
||||
:coderay_line_numbers => @config['kramdown']['coderay']['coderay_line_numbers'],
|
||||
:coderay_line_number_start => @config['kramdown']['coderay']['coderay_line_number_start'],
|
||||
:coderay_tab_width => @config['kramdown']['coderay']['coderay_tab_width'],
|
||||
:coderay_bold_every => @config['kramdown']['coderay']['coderay_bold_every'],
|
||||
:coderay_css => @config['kramdown']['coderay']['coderay_css']
|
||||
})
|
||||
else
|
||||
# not using coderay
|
||||
base_kramdown_configs
|
||||
end
|
||||
Kramdown::Document.new(content, kramdown_configs).to_html
|
||||
end
|
||||
|
||||
def base_kramdown_configs
|
||||
{
|
||||
:auto_ids => @config['kramdown']['auto_ids'],
|
||||
:footnote_nr => @config['kramdown']['footnote_nr'],
|
||||
:entity_output => @config['kramdown']['entity_output'],
|
||||
:toc_levels => @config['kramdown']['toc_levels'],
|
||||
:smart_quotes => @config['kramdown']['smart_quotes']
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
lib/jekyll/converters/markdown/maruku_parser.rb
Normal file
51
lib/jekyll/converters/markdown/maruku_parser.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
module Jekyll
|
||||
module Converters
|
||||
class Markdown
|
||||
class MarukuParser
|
||||
def initialize(config)
|
||||
require 'maruku'
|
||||
@config = config
|
||||
@errors = []
|
||||
load_divs_library if @config['maruku']['use_divs']
|
||||
load_blahtext_library if @config['maruku']['use_tex']
|
||||
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")
|
||||
end
|
||||
|
||||
def load_divs_library
|
||||
require 'maruku/ext/div'
|
||||
STDERR.puts 'Maruku: Using extended syntax for div elements.'
|
||||
end
|
||||
|
||||
def load_blahtext_library
|
||||
require 'maruku/ext/math'
|
||||
STDERR.puts "Maruku: Using LaTeX extension. Images in `#{@config['maruku']['png_dir']}`."
|
||||
|
||||
# Switch off MathML output
|
||||
MaRuKu::Globals[:html_math_output_mathml] = false
|
||||
MaRuKu::Globals[:html_math_engine] = 'none'
|
||||
|
||||
# Turn on math to PNG support with blahtex
|
||||
# Resulting PNGs stored in `images/latex`
|
||||
MaRuKu::Globals[:html_math_output_png] = true
|
||||
MaRuKu::Globals[:html_png_engine] = @config['maruku']['png_engine']
|
||||
MaRuKu::Globals[:html_png_dir] = @config['maruku']['png_dir']
|
||||
MaRuKu::Globals[:html_png_url] = @config['maruku']['png_url']
|
||||
end
|
||||
|
||||
def print_errors_and_fail
|
||||
print @errors.join
|
||||
raise MaRuKu::Exception, "MaRuKu encountered problem(s) while converting your markup."
|
||||
end
|
||||
|
||||
def convert(content)
|
||||
converted = Maruku.new(content, :error_stream => @errors).to_html
|
||||
print_errors_and_fail unless @errors.empty?
|
||||
converted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
35
lib/jekyll/converters/markdown/rdiscount_parser.rb
Normal file
35
lib/jekyll/converters/markdown/rdiscount_parser.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
module Jekyll
|
||||
module Converters
|
||||
class Markdown
|
||||
class RDiscountParser
|
||||
def initialize(config)
|
||||
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)
|
||||
rd = RDiscount.new(content, *@rdiscount_extensions)
|
||||
html = rd.to_html
|
||||
if @config['rdiscount']['toc_token']
|
||||
html = replace_generated_toc(rd, html, @config['rdiscount']['toc_token'])
|
||||
end
|
||||
html
|
||||
end
|
||||
|
||||
private
|
||||
def replace_generated_toc(rd, html, toc_token)
|
||||
if rd.generate_toc && html.include?(toc_token)
|
||||
html.gsub(toc_token, rd.toc_content.force_encoding('utf-8'))
|
||||
else
|
||||
html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
70
lib/jekyll/converters/markdown/redcarpet_parser.rb
Normal file
70
lib/jekyll/converters/markdown/redcarpet_parser.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
module Jekyll
|
||||
module Converters
|
||||
class Markdown
|
||||
class RedcarpetParser
|
||||
|
||||
module CommonMethods
|
||||
def add_code_tags(code, lang)
|
||||
code = code.sub(/<pre>/, "<pre><code class=\"#{lang} language-#{lang}\" data-lang=\"#{lang}\">")
|
||||
code = code.sub(/<\/pre>/,"</code></pre>")
|
||||
end
|
||||
end
|
||||
|
||||
module WithPygments
|
||||
include CommonMethods
|
||||
def block_code(code, lang)
|
||||
require 'pygments'
|
||||
lang = lang && lang.split.first || "text"
|
||||
output = add_code_tags(
|
||||
Pygments.highlight(code, :lexer => lang, :options => { :encoding => 'utf-8' }),
|
||||
lang
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
module WithoutPygments
|
||||
require 'cgi'
|
||||
|
||||
include CommonMethods
|
||||
|
||||
def code_wrap(code)
|
||||
"<div class=\"highlight\"><pre>#{CGI::escapeHTML(code)}</pre></div>"
|
||||
end
|
||||
|
||||
def block_code(code, lang)
|
||||
lang = lang && lang.split.first || "text"
|
||||
output = add_code_tags(code_wrap(code), lang)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(config)
|
||||
require 'redcarpet'
|
||||
@config = config
|
||||
@redcarpet_extensions = {}
|
||||
@config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true }
|
||||
|
||||
@renderer ||= if @config['pygments']
|
||||
Class.new(Redcarpet::Render::HTML) do
|
||||
include WithPygments
|
||||
end
|
||||
else
|
||||
Class.new(Redcarpet::Render::HTML) do
|
||||
include WithoutPygments
|
||||
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")
|
||||
end
|
||||
|
||||
def convert(content)
|
||||
@redcarpet_extensions[:fenced_code_blocks] = !@redcarpet_extensions[:no_fenced_code_blocks]
|
||||
@renderer.send :include, Redcarpet::Render::SmartyPants if @redcarpet_extensions[:smart]
|
||||
markdown = Redcarpet::Markdown.new(@renderer.new(@redcarpet_extensions), @redcarpet_extensions)
|
||||
markdown.render(content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'set'
|
||||
|
||||
# Convertible provides methods for converting a pagelike item
|
||||
@@ -32,10 +34,10 @@ module Jekyll
|
||||
self.content = $POSTMATCH
|
||||
self.data = YAML.safe_load($1)
|
||||
end
|
||||
rescue => e
|
||||
puts "Error reading file #{File.join(base, name)}: #{e.message}"
|
||||
rescue SyntaxError => e
|
||||
puts "YAML Exception reading #{File.join(base, name)}: #{e.message}"
|
||||
rescue Exception => e
|
||||
puts "Error reading file #{File.join(base, name)}: #{e.message}"
|
||||
end
|
||||
|
||||
self.data ||= {}
|
||||
@@ -46,6 +48,10 @@ module Jekyll
|
||||
# Returns nothing.
|
||||
def transform
|
||||
self.content = converter.convert(self.content)
|
||||
rescue => e
|
||||
Jekyll.logger.error "Conversion error:", "There was an error converting" +
|
||||
" '#{self.path}'."
|
||||
raise e
|
||||
end
|
||||
|
||||
# Determine the extension depending on content_type.
|
||||
@@ -64,34 +70,28 @@ module Jekyll
|
||||
@converter ||= self.site.converters.find { |c| c.matches(self.ext) }
|
||||
end
|
||||
|
||||
# Add any necessary layouts to this convertible document.
|
||||
# Render Liquid in the content
|
||||
#
|
||||
# payload - The site payload Hash.
|
||||
# layouts - A Hash of {"name" => "layout"}.
|
||||
# content - the raw Liquid content to render
|
||||
# payload - the payload for Liquid
|
||||
# info - the info for Liquid
|
||||
#
|
||||
# Returns nothing.
|
||||
def do_layout(payload, layouts)
|
||||
info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
|
||||
|
||||
# render and transform content (this becomes the final content of the object)
|
||||
payload["pygments_prefix"] = converter.pygments_prefix
|
||||
payload["pygments_suffix"] = converter.pygments_suffix
|
||||
|
||||
begin
|
||||
self.content = Liquid::Template.parse(self.content).render!(payload, info)
|
||||
rescue => e
|
||||
puts "Liquid Exception: #{e.message} in #{self.name}"
|
||||
e.backtrace.each do |backtrace|
|
||||
puts backtrace
|
||||
end
|
||||
abort("Build Failed")
|
||||
end
|
||||
|
||||
self.transform
|
||||
|
||||
# output keeps track of what will finally be written
|
||||
self.output = self.content
|
||||
# Returns the converted content
|
||||
def render_liquid(content, payload, info)
|
||||
Liquid::Template.parse(content).render!(payload, info)
|
||||
rescue Exception => e
|
||||
Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{payload[:file]}"
|
||||
raise e
|
||||
end
|
||||
|
||||
# Recursively render layouts
|
||||
#
|
||||
# layouts - a list of the layouts
|
||||
# payload - the payload for Liquid
|
||||
# info - the info for Liquid
|
||||
#
|
||||
# Returns nothing
|
||||
def render_all_layouts(layouts, payload, info)
|
||||
# recursively render layouts
|
||||
layout = layouts[self.data["layout"]]
|
||||
used = Set.new([layout])
|
||||
@@ -99,15 +99,9 @@ module Jekyll
|
||||
while layout
|
||||
payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
|
||||
|
||||
begin
|
||||
self.output = Liquid::Template.parse(layout.content).render!(payload, info)
|
||||
rescue => e
|
||||
puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
|
||||
e.backtrace.each do |backtrace|
|
||||
puts backtrace
|
||||
end
|
||||
abort("Build Failed")
|
||||
end
|
||||
self.output = self.render_liquid(layout.content,
|
||||
payload.merge({:file => layout.name}),
|
||||
info)
|
||||
|
||||
if layout = layouts[layout.data["layout"]]
|
||||
if used.include?(layout)
|
||||
@@ -118,5 +112,42 @@ module Jekyll
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Add any necessary layouts to this convertible document.
|
||||
#
|
||||
# payload - The site payload Hash.
|
||||
# layouts - A Hash of {"name" => "layout"}.
|
||||
#
|
||||
# Returns nothing.
|
||||
def do_layout(payload, layouts)
|
||||
info = { :filters => [Jekyll::Filters], :registers => { :site => self.site, :page => payload['page'] } }
|
||||
|
||||
# render and transform content (this becomes the final content of the object)
|
||||
payload["pygments_prefix"] = converter.pygments_prefix
|
||||
payload["pygments_suffix"] = converter.pygments_suffix
|
||||
|
||||
self.content = self.render_liquid(self.content,
|
||||
payload.merge({:file => self.name}),
|
||||
info)
|
||||
self.transform
|
||||
|
||||
# output keeps track of what will finally be written
|
||||
self.output = self.content
|
||||
|
||||
self.render_all_layouts(layouts, payload, info)
|
||||
end
|
||||
|
||||
# Write the generated page file to the destination directory.
|
||||
#
|
||||
# dest - The String path to the destination dir.
|
||||
#
|
||||
# Returns nothing.
|
||||
def write(dest)
|
||||
path = destination(dest)
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.open(path, 'w') do |f|
|
||||
f.write(self.output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
32
lib/jekyll/deprecator.rb
Normal file
32
lib/jekyll/deprecator.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
module Jekyll
|
||||
class Deprecator
|
||||
def self.process(args)
|
||||
no_subcommand(args)
|
||||
deprecation_message args, "--server", "The --server command has been replaced by the \
|
||||
'serve' subcommand."
|
||||
deprecation_message args, "--no-server", "To build Jekyll without launching a server, \
|
||||
use the 'build' subcommand."
|
||||
deprecation_message args, "--auto", "The switch '--auto' has been replaced with '--watch'."
|
||||
deprecation_message args, "--no-auto", "To disable auto-replication, simply leave off \
|
||||
the '--watch' switch."
|
||||
deprecation_message args, "--pygments", "The 'pygments' setting can only be set in \
|
||||
your config files."
|
||||
deprecation_message args, "--paginate", "The 'paginate' setting can only be set in your \
|
||||
config files."
|
||||
deprecation_message args, "--url", "The 'url' setting can only be set in your config files."
|
||||
end
|
||||
|
||||
def self.no_subcommand(args)
|
||||
if args.size > 0 && args.first =~ /^--/ && !%w[--help --version].include?(args.first)
|
||||
Jekyll.logger.error "Deprecation:", "Jekyll now uses subcommands instead of just \
|
||||
switches. Run `jekyll help' to find out more."
|
||||
end
|
||||
end
|
||||
|
||||
def self.deprecation_message(args, deprecated_argument, message)
|
||||
if args.include?(deprecated_argument)
|
||||
Jekyll.logger.error "Deprecation:", message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -30,7 +30,7 @@ module Jekyll
|
||||
#
|
||||
# Returns the formatting String.
|
||||
def date_to_string(date)
|
||||
date.strftime("%d %b %Y")
|
||||
time(date).strftime("%d %b %Y")
|
||||
end
|
||||
|
||||
# Format a date in long format e.g. "27 January 2011".
|
||||
@@ -39,7 +39,7 @@ module Jekyll
|
||||
#
|
||||
# Returns the formatted String.
|
||||
def date_to_long_string(date)
|
||||
date.strftime("%d %B %Y")
|
||||
time(date).strftime("%d %B %Y")
|
||||
end
|
||||
|
||||
# Format a date for use in XML.
|
||||
@@ -53,7 +53,7 @@ module Jekyll
|
||||
#
|
||||
# Returns the formatted String.
|
||||
def date_to_xmlschema(date)
|
||||
date.xmlschema
|
||||
time(date).xmlschema
|
||||
end
|
||||
|
||||
# Format a date according to RFC-822
|
||||
@@ -67,7 +67,7 @@ module Jekyll
|
||||
#
|
||||
# Returns the formatted String.
|
||||
def date_to_rfc822(date)
|
||||
date.rfc822
|
||||
time(date).rfc822
|
||||
end
|
||||
|
||||
# XML escape a string for use. Replaces any special characters with
|
||||
@@ -99,7 +99,17 @@ module Jekyll
|
||||
def cgi_escape(input)
|
||||
CGI::escape(input)
|
||||
end
|
||||
|
||||
|
||||
# URI escape a string.
|
||||
#
|
||||
# input - The String to escape.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# uri_escape('foo, bar \\baz?')
|
||||
# # => "foo,%20bar%20%5Cbaz?"
|
||||
#
|
||||
# Returns the escaped String.
|
||||
def uri_escape(input)
|
||||
URI.escape(input)
|
||||
end
|
||||
@@ -137,5 +147,18 @@ module Jekyll
|
||||
"#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def time(input)
|
||||
case input
|
||||
when Time
|
||||
input
|
||||
when String
|
||||
Time.parse(input)
|
||||
else
|
||||
Jekyll.logger.error "Invalid Date:", "'#{input}' is not a valid datetime."
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,8 +10,13 @@ module Jekyll
|
||||
#
|
||||
# Returns nothing.
|
||||
def generate(site)
|
||||
site.pages.dup.each do |page|
|
||||
paginate(site, page) if Pager.pagination_enabled?(site.config, page.name)
|
||||
if Pager.pagination_enabled?(site)
|
||||
if template = template_page(site)
|
||||
paginate(site, template)
|
||||
else
|
||||
Jekyll.logger.warn "Pagination:", "Pagination is enabled, but I couldn't find" +
|
||||
"an index.html page to use as the pagination template. Skipping pagination."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,11 +38,11 @@ module Jekyll
|
||||
all_posts = site.site_payload['site']['posts']
|
||||
pages = Pager.calculate_pages(all_posts, site.config['paginate'].to_i)
|
||||
(1..pages).each do |num_page|
|
||||
pager = Pager.new(site.config, num_page, all_posts, pages)
|
||||
pager = Pager.new(site, num_page, all_posts, pages)
|
||||
if num_page > 1
|
||||
newpage = Page.new(site, site.source, page.dir, page.name)
|
||||
newpage.pager = pager
|
||||
newpage.dir = File.join(page.dir, Pager.paginate_path(site.config, num_page))
|
||||
newpage.dir = Pager.paginate_path(site, num_page)
|
||||
site.pages << newpage
|
||||
else
|
||||
page.pager = pager
|
||||
@@ -45,6 +50,32 @@ module Jekyll
|
||||
end
|
||||
end
|
||||
|
||||
# Static: Fetch the URL of the template page. Used to determine the
|
||||
# path to the first pager in the series.
|
||||
#
|
||||
# site - the Jekyll::Site object
|
||||
#
|
||||
# Returns the url of the template page
|
||||
def self.first_page_url(site)
|
||||
if page = Pagination.new.template_page(site)
|
||||
page.url
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Find the Jekyll::Page which will act as the pager template
|
||||
#
|
||||
# site - the Jekyll::Site object
|
||||
#
|
||||
# Returns the Jekyll::Page which will act as the pager template
|
||||
def template_page(site)
|
||||
site.pages.dup.select do |page|
|
||||
Pager.pagination_candidate?(site.config, page)
|
||||
end.sort do |one, two|
|
||||
two.path.size <=> one.path.size
|
||||
end.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,26 +93,78 @@ module Jekyll
|
||||
(all_posts.size.to_f / per_page.to_i).ceil
|
||||
end
|
||||
|
||||
# Determine if pagination is enabled for a given file.
|
||||
# Determine if pagination is enabled the site.
|
||||
#
|
||||
# config - The configuration Hash.
|
||||
# file - The String filename of the file.
|
||||
# site - the Jekyll::Site object
|
||||
#
|
||||
# Returns true if pagination is enabled, false otherwise.
|
||||
def self.pagination_enabled?(config, file)
|
||||
file == 'index.html' && !config['paginate'].nil?
|
||||
def self.pagination_enabled?(site)
|
||||
!site.config['paginate'].nil? &&
|
||||
site.pages.size > 0
|
||||
end
|
||||
|
||||
# Static: Determine if a page is a possible candidate to be a template page.
|
||||
# Page's name must be `index.html` and exist in any of the directories
|
||||
# between the site source and `paginate_path`.
|
||||
#
|
||||
# config - the site configuration hash
|
||||
# page - the Jekyll::Page about which we're inquiring
|
||||
#
|
||||
# Returns true if the
|
||||
def self.pagination_candidate?(config, page)
|
||||
page_dir = File.dirname(File.expand_path(remove_leading_slash(page.path), config['source']))
|
||||
paginate_path = remove_leading_slash(config['paginate_path'])
|
||||
paginate_path = File.expand_path(paginate_path, config['source'])
|
||||
page.name == 'index.html' &&
|
||||
in_hierarchy(config['source'], page_dir, File.dirname(paginate_path))
|
||||
end
|
||||
|
||||
# Determine if the subdirectories of the two paths are the same relative to source
|
||||
#
|
||||
# source - the site source
|
||||
# page_dir - the directory of the Jekyll::Page
|
||||
# paginate_path - the absolute paginate path (from root of FS)
|
||||
#
|
||||
# Returns whether the subdirectories are the same relative to source
|
||||
def self.in_hierarchy(source, page_dir, paginate_path)
|
||||
return false if paginate_path == File.dirname(paginate_path)
|
||||
return false if paginate_path == Pathname.new(source).parent
|
||||
page_dir == paginate_path ||
|
||||
in_hierarchy(source, page_dir, File.dirname(paginate_path))
|
||||
end
|
||||
|
||||
# Static: Return the pagination path of the page
|
||||
#
|
||||
# site_config - the site config
|
||||
# site - the Jekyll::Site object
|
||||
# num_page - the pagination page number
|
||||
#
|
||||
# Returns the pagination path as a string
|
||||
def self.paginate_path(site_config, num_page)
|
||||
return nil if num_page.nil? || num_page <= 1
|
||||
format = site_config['paginate_path']
|
||||
format.sub(':num', num_page.to_s)
|
||||
def self.paginate_path(site, num_page)
|
||||
return nil if num_page.nil?
|
||||
return Generators::Pagination.first_page_url(site) if num_page <= 1
|
||||
format = site.config['paginate_path']
|
||||
format = format.sub(':num', num_page.to_s)
|
||||
ensure_leading_slash(format)
|
||||
end
|
||||
|
||||
# Static: Return a String version of the input which has a leading slash.
|
||||
# If the input already has a forward slash in position zero, it will be
|
||||
# returned unchanged.
|
||||
#
|
||||
# path - a String path
|
||||
#
|
||||
# Returns the path with a leading slash
|
||||
def self.ensure_leading_slash(path)
|
||||
path[0..0] == "/" ? path : "/#{path}"
|
||||
end
|
||||
|
||||
# Static: Return a String version of the input without a leading slash.
|
||||
#
|
||||
# path - a String path
|
||||
#
|
||||
# Returns the input without the leading slash
|
||||
def self.remove_leading_slash(path)
|
||||
ensure_leading_slash(path)[1..-1]
|
||||
end
|
||||
|
||||
# Initialize a new Pager.
|
||||
@@ -91,9 +174,9 @@ module Jekyll
|
||||
# all_posts - The Array of all the site's Posts.
|
||||
# num_pages - The Integer number of pages or nil if you'd like the number
|
||||
# of pages calculated.
|
||||
def initialize(config, page, all_posts, num_pages = nil)
|
||||
def initialize(site, page, all_posts, num_pages = nil)
|
||||
@page = page
|
||||
@per_page = config['paginate'].to_i
|
||||
@per_page = site.config['paginate'].to_i
|
||||
@total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
|
||||
|
||||
if @page > @total_pages
|
||||
@@ -106,9 +189,9 @@ module Jekyll
|
||||
@total_posts = all_posts.size
|
||||
@posts = all_posts[init..offset]
|
||||
@previous_page = @page != 1 ? @page - 1 : nil
|
||||
@previous_page_path = Pager.paginate_path(config, @previous_page)
|
||||
@previous_page_path = Pager.paginate_path(site, @previous_page)
|
||||
@next_page = @page != @total_pages ? @page + 1 : nil
|
||||
@next_page_path = Pager.paginate_path(config, @next_page)
|
||||
@next_page_path = Pager.paginate_path(site, @next_page)
|
||||
end
|
||||
|
||||
# Convert this Pager's data to a Hash suitable for use by Liquid.
|
||||
|
||||
@@ -5,6 +5,9 @@ module Jekyll
|
||||
# Gets the Site object.
|
||||
attr_reader :site
|
||||
|
||||
# Gets the name of this layout.
|
||||
attr_reader :name
|
||||
|
||||
# Gets/Sets the extension of this layout.
|
||||
attr_accessor :ext
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ application/postscript ps eps ai
|
||||
application/rdf+xml rdf
|
||||
application/rtf rtf
|
||||
text/vcard vcf vcard
|
||||
application/vnd.apple.pkpass pkpass
|
||||
application/vnd.ms-excel xls
|
||||
application/vnd.ms-powerpoint ppt
|
||||
application/vnd.wap.wmlc wmlc
|
||||
|
||||
@@ -24,18 +24,6 @@ module Jekyll
|
||||
self.read_yaml(File.join(base, dir), name)
|
||||
end
|
||||
|
||||
# Read the YAML frontmatter.
|
||||
#
|
||||
# base - The String path to the dir containing the file.
|
||||
# name - The String filename of the file.
|
||||
#
|
||||
# Returns nothing.
|
||||
def read_yaml(base, name)
|
||||
super(base, name)
|
||||
self.data['layout'] = 'page' unless self.data.has_key?('layout')
|
||||
self.data
|
||||
end
|
||||
|
||||
# The generated directory into which the page will be placed
|
||||
# upon generation. This is derived from the permalink or, if
|
||||
# permalink is absent, we be '/'
|
||||
@@ -50,7 +38,12 @@ module Jekyll
|
||||
#
|
||||
# Returns the String permalink or nil if none has been set.
|
||||
def permalink
|
||||
self.data && self.data['permalink']
|
||||
return nil if self.data.nil? || self.data['permalink'].nil?
|
||||
if site.config['relative_permalinks']
|
||||
File.join(@dir, self.data['permalink'])
|
||||
else
|
||||
self.data['permalink']
|
||||
end
|
||||
end
|
||||
|
||||
# The template of the permalink.
|
||||
@@ -110,7 +103,15 @@ module Jekyll
|
||||
def to_liquid
|
||||
self.data.deep_merge({
|
||||
"url" => self.url,
|
||||
"content" => self.content })
|
||||
"content" => self.content,
|
||||
"path" => self.data['path'] || path })
|
||||
end
|
||||
|
||||
# The path to the source file
|
||||
#
|
||||
# Returns the path to the source file
|
||||
def path
|
||||
File.join(@dir, @name).sub(/\A\//, '')
|
||||
end
|
||||
|
||||
# Obtain destination path.
|
||||
@@ -119,26 +120,11 @@ module Jekyll
|
||||
#
|
||||
# Returns the destination file path String.
|
||||
def destination(dest)
|
||||
# The url needs to be unescaped in order to preserve the correct
|
||||
# filename.
|
||||
path = File.join(dest, CGI.unescape(self.url))
|
||||
path = File.join(dest, self.url)
|
||||
path = File.join(path, "index.html") if self.url =~ /\/$/
|
||||
path
|
||||
end
|
||||
|
||||
# Write the generated page file to the destination directory.
|
||||
#
|
||||
# dest - The String path to the destination dir.
|
||||
#
|
||||
# Returns nothing.
|
||||
def write(dest)
|
||||
path = destination(dest)
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.open(path, 'w') do |f|
|
||||
f.write(self.output)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the object as a debug String.
|
||||
def inspect
|
||||
"#<Jekyll:Page @name=#{self.name.inspect}>"
|
||||
@@ -153,5 +139,9 @@ module Jekyll
|
||||
def index?
|
||||
basename == 'index'
|
||||
end
|
||||
|
||||
def uses_relative_permalinks
|
||||
permalink && @dir != "" && site.config['relative_permalinks']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,21 @@ module Jekyll
|
||||
# Valid post name regex.
|
||||
MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
|
||||
|
||||
# Attributes for Liquid templates
|
||||
ATTRIBUTES_FOR_LIQUID = %w[
|
||||
title
|
||||
url
|
||||
date
|
||||
id
|
||||
categories
|
||||
next
|
||||
previous
|
||||
tags
|
||||
content
|
||||
excerpt
|
||||
path
|
||||
]
|
||||
|
||||
# Post name validator. Post filenames must be like:
|
||||
# 2008-11-05-my-awesome-post.textile
|
||||
#
|
||||
@@ -20,7 +35,7 @@ module Jekyll
|
||||
end
|
||||
|
||||
attr_accessor :site
|
||||
attr_accessor :data, :excerpt, :content, :output, :ext
|
||||
attr_accessor :data, :extracted_excerpt, :content, :output, :ext
|
||||
attr_accessor :date, :slug, :published, :tags, :categories
|
||||
|
||||
attr_reader :name
|
||||
@@ -34,40 +49,43 @@ module Jekyll
|
||||
# Returns the new Post.
|
||||
def initialize(site, source, dir, name)
|
||||
@site = site
|
||||
@dir = dir
|
||||
@base = self.containing_dir(source, dir)
|
||||
@name = name
|
||||
|
||||
self.categories = dir.downcase.split('/').reject { |x| x.empty? }
|
||||
self.process(name)
|
||||
begin
|
||||
self.read_yaml(@base, name)
|
||||
rescue Exception => msg
|
||||
raise FatalException.new("#{msg} in #{@base}/#{name}")
|
||||
end
|
||||
self.read_yaml(@base, name)
|
||||
|
||||
# If we've added a date and time to the YAML, use that instead of the
|
||||
# filename date. Means we'll sort correctly.
|
||||
if self.data.has_key?('date')
|
||||
# ensure Time via to_s and reparse
|
||||
self.date = Time.parse(self.data["date"].to_s)
|
||||
end
|
||||
|
||||
self.published = self.published?
|
||||
|
||||
self.populate_categories
|
||||
self.populate_tags
|
||||
end
|
||||
|
||||
def published?
|
||||
if self.data.has_key?('published') && self.data['published'] == false
|
||||
self.published = false
|
||||
false
|
||||
else
|
||||
self.published = true
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
self.tags = self.data.pluralized_array("tag", "tags")
|
||||
|
||||
def populate_categories
|
||||
if self.categories.empty?
|
||||
self.categories = self.data.pluralized_array('category', 'categories').map {|c| c.downcase}
|
||||
self.categories = self.data.pluralized_array('category', 'categories').map {|c| c.to_s.downcase}
|
||||
end
|
||||
|
||||
self.tags.flatten!
|
||||
self.categories.flatten!
|
||||
end
|
||||
|
||||
def populate_tags
|
||||
self.tags = self.data.pluralized_array("tag", "tags").flatten
|
||||
end
|
||||
|
||||
# Get the full path to the directory containing the post files
|
||||
def containing_dir(source, dir)
|
||||
return File.join(source, dir, '_posts')
|
||||
@@ -81,8 +99,36 @@ module Jekyll
|
||||
# Returns nothing.
|
||||
def read_yaml(base, name)
|
||||
super(base, name)
|
||||
self.excerpt = self.extract_excerpt
|
||||
self.data['layout'] = 'post' unless self.data.has_key?('layout')
|
||||
self.extracted_excerpt = self.extract_excerpt
|
||||
end
|
||||
|
||||
# The post excerpt. This is either a custom excerpt
|
||||
# set in YAML front matter or the result of extract_excerpt.
|
||||
#
|
||||
# Returns excerpt string.
|
||||
def excerpt
|
||||
if self.data.has_key? 'excerpt'
|
||||
self.data['excerpt']
|
||||
else
|
||||
self.extracted_excerpt
|
||||
end
|
||||
end
|
||||
|
||||
# Public: the Post title, from the YAML Front-Matter or from the slug
|
||||
#
|
||||
# Returns the post title
|
||||
def title
|
||||
self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' ')
|
||||
end
|
||||
|
||||
# Public: the path to the post relative to the site source,
|
||||
# from the YAML Front-Matter or from a combination of
|
||||
# the directory it's in, "_posts", and the name of the
|
||||
# post file
|
||||
#
|
||||
# Returns the path to the file relative to the site source
|
||||
def path
|
||||
self.data['path'] || File.join(@dir, '_posts', @name).sub(/\A\//, '')
|
||||
end
|
||||
|
||||
# Compares Post objects. First compares the Post date. If the dates are
|
||||
@@ -118,7 +164,7 @@ module Jekyll
|
||||
# Returns nothing.
|
||||
def transform
|
||||
super
|
||||
self.excerpt = converter.convert(self.excerpt)
|
||||
self.extracted_excerpt = converter.convert(self.extracted_excerpt)
|
||||
end
|
||||
|
||||
# The generated directory into which the post will be placed
|
||||
@@ -182,25 +228,7 @@ module Jekyll
|
||||
#
|
||||
# Returns an Array of related Posts.
|
||||
def related_posts(posts)
|
||||
return [] unless posts.size > 1
|
||||
|
||||
if self.site.lsi
|
||||
self.class.lsi ||= begin
|
||||
puts "Starting the classifier..."
|
||||
lsi = Classifier::LSI.new(:auto_rebuild => false)
|
||||
$stdout.print(" Populating LSI... ");$stdout.flush
|
||||
posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
|
||||
$stdout.print("\n Rebuilding LSI index... ")
|
||||
lsi.build_index
|
||||
puts ""
|
||||
lsi
|
||||
end
|
||||
|
||||
related = self.class.lsi.find_related(self.content, 11)
|
||||
related - [self]
|
||||
else
|
||||
(posts - [self])[0..9]
|
||||
end
|
||||
Jekyll::RelatedPosts.new(self).build
|
||||
end
|
||||
|
||||
# Add any necessary layouts to this post.
|
||||
@@ -218,7 +246,7 @@ module Jekyll
|
||||
|
||||
do_layout(payload, layouts)
|
||||
end
|
||||
|
||||
|
||||
# Obtain destination path.
|
||||
#
|
||||
# dest - The String path to the destination dir.
|
||||
@@ -231,34 +259,14 @@ module Jekyll
|
||||
path
|
||||
end
|
||||
|
||||
# Write the generated post file to the destination directory.
|
||||
#
|
||||
# dest - The String path to the destination dir.
|
||||
#
|
||||
# Returns nothing.
|
||||
def write(dest)
|
||||
path = destination(dest)
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.open(path, 'w') do |f|
|
||||
f.write(self.output)
|
||||
end
|
||||
end
|
||||
|
||||
# Convert this post into a Hash for use in Liquid templates.
|
||||
#
|
||||
# Returns the representative Hash.
|
||||
def to_liquid
|
||||
self.data.deep_merge({
|
||||
"title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
|
||||
"url" => self.url,
|
||||
"date" => self.date,
|
||||
"id" => self.id,
|
||||
"categories" => self.categories,
|
||||
"next" => self.next,
|
||||
"previous" => self.previous,
|
||||
"tags" => self.tags,
|
||||
"content" => self.content,
|
||||
"excerpt" => self.excerpt })
|
||||
further_data = Hash[ATTRIBUTES_FOR_LIQUID.map { |attribute|
|
||||
[attribute, send(attribute)]
|
||||
}]
|
||||
data.deep_merge(further_data)
|
||||
end
|
||||
|
||||
# Returns the shorthand String identifier of this Post.
|
||||
|
||||
59
lib/jekyll/related_posts.rb
Normal file
59
lib/jekyll/related_posts.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
module Jekyll
|
||||
class RelatedPosts
|
||||
|
||||
class << self
|
||||
attr_accessor :lsi
|
||||
end
|
||||
|
||||
attr_reader :post, :site
|
||||
|
||||
def initialize(post)
|
||||
@post = post
|
||||
@site = post.site
|
||||
require 'classifier' if site.lsi
|
||||
end
|
||||
|
||||
def build
|
||||
return [] unless self.site.posts.size > 1
|
||||
|
||||
if self.site.lsi
|
||||
build_index
|
||||
lsi_related_posts
|
||||
else
|
||||
most_recent_posts
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def build_index
|
||||
self.class.lsi ||= begin
|
||||
lsi = Classifier::LSI.new(:auto_rebuild => false)
|
||||
display("Populating LSI...")
|
||||
|
||||
self.site.posts.each do |x|
|
||||
lsi.add_item(x)
|
||||
end
|
||||
|
||||
display("Rebuilding index...")
|
||||
lsi.build_index
|
||||
display("")
|
||||
lsi
|
||||
end
|
||||
end
|
||||
|
||||
def lsi_related_posts
|
||||
self.class.lsi.find_related(post.content, 11) - [self.post]
|
||||
end
|
||||
|
||||
def most_recent_posts
|
||||
recent_posts = self.site.posts.reverse - [self.post]
|
||||
recent_posts.first(10)
|
||||
end
|
||||
|
||||
def display(output)
|
||||
$stdout.print("\n")
|
||||
$stdout.print(Jekyll.logger.formatted_topic(output))
|
||||
$stdout.flush
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -23,12 +23,12 @@ module Jekyll
|
||||
self.pygments = config['pygments']
|
||||
self.baseurl = config['baseurl']
|
||||
self.permalink_style = config['permalink'].to_sym
|
||||
self.exclude = config['exclude'] || []
|
||||
self.include = config['include'] || []
|
||||
self.exclude = config['exclude']
|
||||
self.include = config['include']
|
||||
self.future = config['future']
|
||||
self.show_drafts = config['show_drafts'] || nil
|
||||
self.limit_posts = config['limit_posts'] || nil
|
||||
self.keep_files = config['keep_files'] || []
|
||||
self.show_drafts = config['show_drafts']
|
||||
self.limit_posts = config['limit_posts']
|
||||
self.keep_files = config['keep_files']
|
||||
|
||||
self.reset
|
||||
self.setup
|
||||
@@ -62,8 +62,8 @@ module Jekyll
|
||||
self.categories = Hash.new { |hash, key| hash[key] = [] }
|
||||
self.tags = Hash.new { |hash, key| hash[key] = [] }
|
||||
|
||||
if !self.limit_posts.nil? && self.limit_posts < 1
|
||||
raise ArgumentError, "Limit posts must be nil or >= 1"
|
||||
if self.limit_posts < 0
|
||||
raise ArgumentError, "limit_posts must be a non-negative number"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,8 +71,6 @@ module Jekyll
|
||||
#
|
||||
# Returns nothing.
|
||||
def setup
|
||||
require 'classifier' if self.lsi
|
||||
|
||||
# Check that the destination dir isn't the source dir or a directory
|
||||
# parent to the source dir.
|
||||
if self.source =~ /^#{self.dest}/
|
||||
@@ -97,7 +95,7 @@ module Jekyll
|
||||
#
|
||||
# Returns an Array of plugin search paths
|
||||
def plugins_path
|
||||
if (config['plugins'] == Jekyll::DEFAULTS['plugins'])
|
||||
if (config['plugins'] == Jekyll::Configuration::DEFAULTS['plugins'])
|
||||
[File.join(self.source, config['plugins'])]
|
||||
else
|
||||
Array(config['plugins']).map { |d| File.expand_path(d) }
|
||||
@@ -148,7 +146,7 @@ module Jekyll
|
||||
self.posts.sort!
|
||||
|
||||
# limit the posts if :limit_posts option is set
|
||||
if limit_posts
|
||||
if limit_posts > 0
|
||||
limit = self.posts.length < limit_posts ? self.posts.length : limit_posts
|
||||
self.posts = self.posts[-limit, limit]
|
||||
end
|
||||
@@ -231,6 +229,7 @@ module Jekyll
|
||||
end
|
||||
|
||||
self.pages.each do |page|
|
||||
relative_permalinks_deprecation_method if page.uses_relative_permalinks
|
||||
page.render(self.layouts, payload)
|
||||
end
|
||||
|
||||
@@ -271,7 +270,11 @@ module Jekyll
|
||||
files.each { |file| dirs << File.dirname(file) }
|
||||
files.merge(dirs)
|
||||
|
||||
obsolete_files = dest_files - files
|
||||
# files that are replaced by dirs should be deleted
|
||||
files_to_delete = Set.new
|
||||
dirs.each { |dir| files_to_delete << dir if File.file?(dir) }
|
||||
|
||||
obsolete_files = dest_files - files + files_to_delete
|
||||
FileUtils.rm_rf(obsolete_files.to_a)
|
||||
end
|
||||
|
||||
@@ -418,5 +421,18 @@ module Jekyll
|
||||
post.categories.each { |c| self.categories[c] << post }
|
||||
post.tags.each { |c| self.tags[c] << post }
|
||||
end
|
||||
|
||||
def relative_permalinks_deprecation_method
|
||||
if config['relative_permalinks'] && !@deprecated_relative_permalinks
|
||||
$stderr.puts # Places newline after "Generating..."
|
||||
Jekyll.logger.warn "Deprecation:", "Starting in 1.1, permalinks for pages" +
|
||||
" in subfolders must be relative to the" +
|
||||
" site source directory, not the parent" +
|
||||
" directory. Check http://jekyllrb.com/docs/upgrading/"+
|
||||
" for more info."
|
||||
$stderr.print Jekyll.logger.formatted_topic("") + "..." # for "done."
|
||||
@deprecated_relative_permalinks = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
68
lib/jekyll/stevenson.rb
Normal file
68
lib/jekyll/stevenson.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
module Jekyll
|
||||
class Stevenson
|
||||
attr_accessor :log_level
|
||||
|
||||
DEBUG = 0
|
||||
INFO = 1
|
||||
WARN = 2
|
||||
ERROR = 3
|
||||
|
||||
# Public: Create a new instance of Stevenson, Jekyll's logger
|
||||
#
|
||||
# level - (optional, integer) the log level
|
||||
#
|
||||
# Returns nothing
|
||||
def initialize(level = INFO)
|
||||
@log_level = level
|
||||
end
|
||||
|
||||
# Public: Print a jekyll message to stdout
|
||||
#
|
||||
# topic - the topic of the message, e.g. "Configuration file", "Deprecation", etc.
|
||||
# message - the message detail
|
||||
#
|
||||
# Returns nothing
|
||||
def info(topic, message)
|
||||
$stdout.puts(message(topic, message)) if log_level <= INFO
|
||||
end
|
||||
|
||||
# Public: Print a jekyll message to stderr
|
||||
#
|
||||
# topic - the topic of the message, e.g. "Configuration file", "Deprecation", etc.
|
||||
# message - the message detail
|
||||
#
|
||||
# Returns nothing
|
||||
def warn(topic, message)
|
||||
$stderr.puts(message(topic, message).yellow) if log_level <= WARN
|
||||
end
|
||||
|
||||
# Public: Print a jekyll error message to stderr
|
||||
#
|
||||
# topic - the topic of the message, e.g. "Configuration file", "Deprecation", etc.
|
||||
# message - the message detail
|
||||
#
|
||||
# Returns nothing
|
||||
def error(topic, message)
|
||||
$stderr.puts(message(topic, message).red) if log_level <= ERROR
|
||||
end
|
||||
|
||||
# Public: Build a Jekyll topic method
|
||||
#
|
||||
# topic - the topic of the message, e.g. "Configuration file", "Deprecation", etc.
|
||||
# message - the message detail
|
||||
#
|
||||
# Returns the formatted message
|
||||
def message(topic, message)
|
||||
formatted_topic(topic) + message.gsub(/\s+/, ' ')
|
||||
end
|
||||
|
||||
# Public: Format the topic
|
||||
#
|
||||
# topic - the topic of the message, e.g. "Configuration file", "Deprecation", etc.
|
||||
#
|
||||
# Returns the formatted topic statement
|
||||
def formatted_topic(topic)
|
||||
"#{topic} ".rjust(20)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
module Jekyll
|
||||
class GistTag < Liquid::Tag
|
||||
|
||||
def render(context)
|
||||
if tag_contents = @markup.strip.match(/\A(\d+) ?(\S*)\Z/)
|
||||
gist_id, filename = tag_contents[1].strip, tag_contents[2].strip
|
||||
if tag_contents = determine_arguments(@markup.strip)
|
||||
gist_id, filename = tag_contents[0], tag_contents[1]
|
||||
gist_script_tag(gist_id, filename)
|
||||
else
|
||||
"Error parsing gist id"
|
||||
@@ -17,7 +18,16 @@ module Jekyll
|
||||
|
||||
private
|
||||
|
||||
def gist_script_tag(gist_id, filename=nil)
|
||||
def determine_arguments(input)
|
||||
matched = if input.include?("/")
|
||||
input.match(/\A([a-zA-Z0-9\/\-_]+) ?(\S*)\Z/)
|
||||
else
|
||||
input.match(/\A(\d+) ?(\S*)\Z/)
|
||||
end
|
||||
[matched[1].strip, matched[2].strip] if matched && matched.length >= 3
|
||||
end
|
||||
|
||||
def gist_script_tag(gist_id, filename = nil)
|
||||
if filename.empty?
|
||||
"<script src=\"https://gist.github.com/#{gist_id}.js\"> </script>"
|
||||
else
|
||||
|
||||
@@ -49,6 +49,8 @@ eos
|
||||
end
|
||||
|
||||
def render_pygments(context, code)
|
||||
require 'pygments'
|
||||
|
||||
@options[:encoding] = 'utf-8'
|
||||
|
||||
output = add_code_tags(
|
||||
|
||||
@@ -1,9 +1,51 @@
|
||||
module Jekyll
|
||||
module Tags
|
||||
class IncludeTag < Liquid::Tag
|
||||
def initialize(tag_name, file, tokens)
|
||||
|
||||
MATCHER = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
super
|
||||
@file = file.strip
|
||||
@file, @params = markup.strip.split(' ', 2);
|
||||
end
|
||||
|
||||
def parse_params(context)
|
||||
validate_syntax
|
||||
|
||||
params = {}
|
||||
markup = @params
|
||||
|
||||
while match = MATCHER.match(markup) do
|
||||
markup = markup[match.end(0)..-1]
|
||||
|
||||
value = if match[2]
|
||||
match[2].gsub(/\\"/, '"')
|
||||
elsif match[3]
|
||||
match[3].gsub(/\\'/, "'")
|
||||
elsif match[4]
|
||||
context[match[4]]
|
||||
end
|
||||
|
||||
params[match[1]] = value
|
||||
end
|
||||
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
|
||||
Invalid syntax for include tag:
|
||||
|
||||
#{@params}
|
||||
|
||||
Valid syntax:
|
||||
|
||||
{% include file.ext param='value' param2="value" %}
|
||||
|
||||
eos
|
||||
end
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@@ -22,7 +64,9 @@ module Jekyll
|
||||
if choices.include?(@file)
|
||||
source = File.read(@file)
|
||||
partial = Liquid::Template.parse(source)
|
||||
|
||||
context.stack do
|
||||
context['include'] = parse_params(context) if @params
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
|
||||
@@ -6,10 +6,32 @@ module Jekyll
|
||||
attr_accessor :date, :slug
|
||||
|
||||
def initialize(name)
|
||||
who, cares, date, slug = *name.match(MATCHER)
|
||||
@slug = slug
|
||||
all, path, date, slug = *name.sub(/^\//, "").match(MATCHER)
|
||||
@slug = path ? path + slug : slug
|
||||
@date = Time.parse(date)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
slug == post_slug(other) &&
|
||||
date.year == other.date.year &&
|
||||
date.month == other.date.month &&
|
||||
date.day == other.date.day
|
||||
end
|
||||
|
||||
private
|
||||
# Construct the directory-aware post slug for a Jekyll::Post
|
||||
#
|
||||
# other - the Jekyll::Post
|
||||
#
|
||||
# Returns the post slug with the subdirectory (relative to _posts)
|
||||
def post_slug(other)
|
||||
path = other.name.split("/")[0...-1].join("/")
|
||||
if path.nil? || path == ""
|
||||
other.slug
|
||||
else
|
||||
path + '/' + other.slug
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PostUrl < Liquid::Tag
|
||||
@@ -23,11 +45,7 @@ module Jekyll
|
||||
site = context.registers[:site]
|
||||
|
||||
site.posts.each do |p|
|
||||
if p.slug == @post.slug \
|
||||
and p.date.year == @post.date.year \
|
||||
and p.date.month == @post.date.month \
|
||||
and p.date.day == @post.date.day
|
||||
|
||||
if @post == p
|
||||
return p.url
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,6 +47,9 @@ module Jekyll
|
||||
|
||||
# Append a trailing slash to the URL if the unsanitized URL had one
|
||||
url += "/" if in_url =~ /\/$/
|
||||
|
||||
# Always add a leading slash
|
||||
url.gsub!(/\A([^\/])/, '/\1')
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
1
lib/site_template/.gitignore
vendored
Normal file
1
lib/site_template/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_site
|
||||
@@ -1,2 +1,3 @@
|
||||
markdown: rdiscount
|
||||
name: Your New Jekyll Site
|
||||
markdown: redcarpet
|
||||
pygments: true
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>{{ page.title }}</title>
|
||||
<!-- syntax highlighting CSS -->
|
||||
<link rel="stylesheet" href="/css/syntax.css" type="text/css" />
|
||||
<!-- Homepage CSS -->
|
||||
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="site">
|
||||
<div class="title">
|
||||
<a href="/">Your Name</a>
|
||||
<a class="extra" href="/">home</a>
|
||||
</div>
|
||||
|
||||
{{ content }}
|
||||
|
||||
<div class="footer">
|
||||
<div class="contact">
|
||||
<p>
|
||||
Your Name<br />
|
||||
What You Are<br />
|
||||
your@email.com
|
||||
</p>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<p>
|
||||
<a href="http://github.com/yourusername/">github.com/yourusername</a><br />
|
||||
<a href="http://twitter.com/yourusername/">twitter.com/yourusername</a><br />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="http://github.com/yourusername"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" /></a>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{ page.title }}</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- syntax highlighting CSS -->
|
||||
<link rel="stylesheet" href="/css/syntax.css">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="site">
|
||||
<div class="header">
|
||||
<h1 class="title"><a href="/">{{ site.name }}</a></h1>
|
||||
<a class="extra" href="/">home</a>
|
||||
</div>
|
||||
|
||||
{{ content }}
|
||||
|
||||
<div class="footer">
|
||||
<div class="contact">
|
||||
<p>
|
||||
Your Name<br />
|
||||
What You Are<br />
|
||||
your@email.com
|
||||
</p>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<p>
|
||||
<a href="http://github.com/yourusername/">github.com/yourusername</a><br />
|
||||
<a href="http://twitter.com/yourusername/">twitter.com/yourusername</a><br />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
<div id="post">
|
||||
<h2>{{ page.title }}</h2>
|
||||
<p class="meta">{{ page.date | date_to_string }}</p>
|
||||
|
||||
<div class="post">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
165
lib/site_template/css/main.css
Executable file
165
lib/site_template/css/main.css
Executable file
@@ -0,0 +1,165 @@
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Common
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Global Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body { height: 100%; }
|
||||
|
||||
body {
|
||||
background-color: #FFF;
|
||||
font: 13.34px Helvetica, Arial, sans-serif;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: 100%; }
|
||||
|
||||
h1 { margin-bottom: 1em; }
|
||||
p { margin: 1em 0; }
|
||||
|
||||
a { color: #00a; }
|
||||
a:hover { color: #000; }
|
||||
a:visited { color: #a0a; }
|
||||
|
||||
table {
|
||||
font-size: inherit;
|
||||
font: 100%;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Home
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
ul.posts {
|
||||
list-style-type: none;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
ul.posts li {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
ul.posts span {
|
||||
color: #aaa;
|
||||
font-family: Monaco, "Courier New", monospace;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Site
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
.site {
|
||||
font-size: 115%;
|
||||
text-align: justify;
|
||||
width: 42em;
|
||||
margin: 3em auto 2em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.site .header a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site .header h1.title {
|
||||
display: inline-block;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.site .header h1.title a {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
.site .header h1.title a:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.site .header a.extra {
|
||||
color: #aaa;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.site .header a.extra:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.site .meta {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.site .footer {
|
||||
font-size: 80%;
|
||||
color: #666;
|
||||
border-top: 4px solid #eee;
|
||||
margin-top: 2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.site .footer .contact {
|
||||
float: left;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
.site .footer .contact a {
|
||||
color: #8085C1;
|
||||
}
|
||||
|
||||
.site .footer .rss {
|
||||
margin-top: 1.1em;
|
||||
margin-right: -.2em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.site .footer .rss img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Posts
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
/* standard */
|
||||
.post pre {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #eef;
|
||||
padding: 0 .4em;
|
||||
}
|
||||
|
||||
.post ul, .post ol {
|
||||
margin-left: 1.35em;
|
||||
}
|
||||
|
||||
.post code {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #eef;
|
||||
padding: 0 .2em;
|
||||
}
|
||||
|
||||
.post pre code {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* terminal */
|
||||
.post pre.terminal {
|
||||
border: 1px solid #000;
|
||||
background-color: #333;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.post pre.terminal code {
|
||||
background-color: #333;
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Common
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Global Reset */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
font: 13.34px helvetica, arial, clean, sans-serif;
|
||||
*font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00a;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #a0a;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: inherit;
|
||||
font: 100%;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Home
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
ul.posts {
|
||||
list-style-type: none;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
ul.posts li {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
ul.posts span {
|
||||
color: #aaa;
|
||||
font-family: Monaco, "Courier New", monospace;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Site
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
.site {
|
||||
font-size: 110%;
|
||||
text-align: justify;
|
||||
width: 42em;
|
||||
margin: 3em auto 2em auto;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #a00;
|
||||
font-weight: bold;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.site .title a {
|
||||
color: #a00;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site .title a:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.site .title a.extra {
|
||||
color: #aaa;
|
||||
text-decoration: none;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.site .title a.extra:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.site .meta {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.site .footer {
|
||||
font-size: 80%;
|
||||
color: #666;
|
||||
border-top: 4px solid #eee;
|
||||
margin-top: 2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.site .footer .contact {
|
||||
float: left;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
.site .footer .contact a {
|
||||
color: #8085C1;
|
||||
}
|
||||
|
||||
.site .footer .rss {
|
||||
margin-top: 1.1em;
|
||||
margin-right: -.2em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.site .footer .rss img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
/* Posts
|
||||
/*
|
||||
/*****************************************************************************/
|
||||
|
||||
#post {
|
||||
|
||||
}
|
||||
|
||||
/* standard */
|
||||
|
||||
#post pre {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #eef;
|
||||
padding: 0 .4em;
|
||||
}
|
||||
|
||||
#post ul,
|
||||
#post ol {
|
||||
margin-left: 1.35em;
|
||||
}
|
||||
|
||||
#post code {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #eef;
|
||||
font-size: 85%;
|
||||
padding: 0 .2em;
|
||||
}
|
||||
|
||||
#post pre code {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* terminal */
|
||||
|
||||
#post pre.terminal {
|
||||
border: 1px solid black;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#post pre.terminal code {
|
||||
background-color: #333;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
Reference in New Issue
Block a user