Merge branch 'drafts-fixup'
Add a drafts feature. This allows you to place draft posts into an _drafts folder in the site root while you're working on them, and then request that they be rendered as normal posts via a command line switch. Draft files should be named just like normal posts, except they must not contain the date prefix. When you're ready to make a post live, rename it with the date and move it to the proper place. To preview your drafts, run Jekyll with the `--drafts` switch. This will render all the posts with a date corresponding to the last time the file was modified.
This commit is contained in:
commit
3a9ca0a796
|
@ -2,6 +2,7 @@
|
||||||
* Major Enhancements
|
* Major Enhancements
|
||||||
* Refactored jekyll commands into subcommands: build, serve, and migrate. (#690)
|
* Refactored jekyll commands into subcommands: build, serve, and migrate. (#690)
|
||||||
* Removed importers/migrators from main project, migrated to jekyll-import sub-gem (#793)
|
* Removed importers/migrators from main project, migrated to jekyll-import sub-gem (#793)
|
||||||
|
* Added ability to render drafts in _drafts folder via command line (#833)
|
||||||
* Minor Enhancements
|
* Minor Enhancements
|
||||||
* Improve debugability of error message for a malformed highlight tag (#785)
|
* Improve debugability of error message for a malformed highlight tag (#785)
|
||||||
* Allow symlinked files in unsafe mode (#824)
|
* Allow symlinked files in unsafe mode (#824)
|
||||||
|
|
21
bin/jekyll
21
bin/jekyll
|
@ -17,6 +17,19 @@ global_option '--safe', 'Safe mode (defaults to false)'
|
||||||
global_option '--plugins', 'Plugins directory (defaults to ./_plugins)'
|
global_option '--plugins', 'Plugins directory (defaults to ./_plugins)'
|
||||||
global_option '--layouts', 'Layouts directory (defaults to ./_layouts)'
|
global_option '--layouts', 'Layouts directory (defaults to ./_layouts)'
|
||||||
|
|
||||||
|
# Option names don't always directly match the configuration value we'd like.
|
||||||
|
# This method will rename options to match what Jekyll configuration expects.
|
||||||
|
#
|
||||||
|
# options - The Hash of options from Commander.
|
||||||
|
#
|
||||||
|
# Returns the normalized Hash.
|
||||||
|
def normalize_options(options)
|
||||||
|
if drafts_state = options.delete(:drafts)
|
||||||
|
options[:show_drafts] = drafts_state
|
||||||
|
end
|
||||||
|
options
|
||||||
|
end
|
||||||
|
|
||||||
command :build do |c|
|
command :build do |c|
|
||||||
c.syntax = 'jekyll build [options]'
|
c.syntax = 'jekyll build [options]'
|
||||||
c.description = 'Build your site'
|
c.description = 'Build your site'
|
||||||
|
@ -25,10 +38,12 @@ command :build do |c|
|
||||||
c.option '--limit_posts MAX_POSTS', 'Limits the number of posts to parse and publish'
|
c.option '--limit_posts MAX_POSTS', 'Limits the number of posts to parse and publish'
|
||||||
c.option '-w', '--watch', 'Watch for changes and rebuild'
|
c.option '-w', '--watch', 'Watch for changes and rebuild'
|
||||||
c.option '--lsi', 'Use LSI for improved related posts'
|
c.option '--lsi', 'Use LSI for improved related posts'
|
||||||
|
c.option '--drafts', 'Render posts in the _drafts folder'
|
||||||
|
|
||||||
c.action do |args, options|
|
c.action do |args, options|
|
||||||
options.defaults :serving => false
|
options.defaults :serving => false
|
||||||
options = Jekyll.configuration(options.__hash__)
|
options = normalize_options(options.__hash__)
|
||||||
|
options = Jekyll.configuration(options)
|
||||||
Jekyll::Commands::Build.process(options)
|
Jekyll::Commands::Build.process(options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -41,6 +56,7 @@ command :serve do |c|
|
||||||
c.option '--limit_posts MAX_POSTS', 'Limits the number of posts to parse and publish'
|
c.option '--limit_posts MAX_POSTS', 'Limits the number of posts to parse and publish'
|
||||||
c.option '-w', '--watch', 'Watch for changes and rebuild'
|
c.option '-w', '--watch', 'Watch for changes and rebuild'
|
||||||
c.option '--lsi', 'Use LSI for improved related posts'
|
c.option '--lsi', 'Use LSI for improved related posts'
|
||||||
|
c.option '--drafts', 'Render posts in the _drafts folder'
|
||||||
|
|
||||||
c.option '-p', '--port [PORT]', 'Port to listen on'
|
c.option '-p', '--port [PORT]', 'Port to listen on'
|
||||||
c.option '-h', '--host [HOST]', 'Host to bind to'
|
c.option '-h', '--host [HOST]', 'Host to bind to'
|
||||||
|
@ -52,7 +68,8 @@ command :serve do |c|
|
||||||
:baseurl => '/',
|
:baseurl => '/',
|
||||||
:serving => true
|
:serving => true
|
||||||
|
|
||||||
options = Jekyll.configuration(options.__hash__)
|
options = normalize_options(options.__hash__)
|
||||||
|
options = Jekyll.configuration(options)
|
||||||
Jekyll::Commands::Build.process(options)
|
Jekyll::Commands::Build.process(options)
|
||||||
Jekyll::Commands::Serve.process(options)
|
Jekyll::Commands::Serve.process(options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -89,7 +89,7 @@ Feature: Create sites
|
||||||
And I have an "_includes/about.textile" file that contains "Generated by {% include jekyll.textile %}"
|
And I have an "_includes/about.textile" file that contains "Generated by {% include jekyll.textile %}"
|
||||||
And I have an "_includes/jekyll.textile" file that contains "Jekyll"
|
And I have an "_includes/jekyll.textile" file that contains "Jekyll"
|
||||||
And I have an "index.html" page that contains "Basic Site with include tag: {% include about.textile %}"
|
And I have an "index.html" page that contains "Basic Site with include tag: {% include about.textile %}"
|
||||||
When I debug jekyll
|
When I run jekyll
|
||||||
Then the _site directory should exist
|
Then the _site directory should exist
|
||||||
And I should see "Basic Site with include tag: Generated by Jekyll" in "_site/index.html"
|
And I should see "Basic Site with include tag: Generated by Jekyll" in "_site/index.html"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
Feature: Draft Posts
|
||||||
|
As a hacker who likes to blog
|
||||||
|
I want to be able to preview drafts locally
|
||||||
|
In order to see if they look alright before publishing
|
||||||
|
|
||||||
|
Scenario: Preview a draft
|
||||||
|
Given I have a configuration file with "permalink" set to "none"
|
||||||
|
And I have a _drafts directory
|
||||||
|
And I have the following draft:
|
||||||
|
| title | date | layout | content |
|
||||||
|
| Recipe | 3/27/2009 | default | Not baked yet. |
|
||||||
|
When I run jekyll with drafts
|
||||||
|
Then the _site directory should exist
|
||||||
|
And I should see "Not baked yet." in "_site/recipe.html"
|
||||||
|
|
||||||
|
Scenario: Don't preview a draft
|
||||||
|
Given I have a configuration file with "permalink" set to "none"
|
||||||
|
And I have an "index.html" page that contains "Totally index"
|
||||||
|
And I have a _drafts directory
|
||||||
|
And I have the following draft:
|
||||||
|
| title | date | layout | content |
|
||||||
|
| Recipe | 3/27/2009 | default | Not baked yet. |
|
||||||
|
When I run jekyll
|
||||||
|
Then the _site directory should exist
|
||||||
|
And the "_site/recipe.html" file should not exist
|
|
@ -50,9 +50,8 @@ Given /^I have an? (.*) directory$/ do |dir|
|
||||||
FileUtils.mkdir_p(dir)
|
FileUtils.mkdir_p(dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^I have the following posts?(?: (.*) "(.*)")?:$/ do |direction, folder, table|
|
Given /^I have the following (draft|post)s?(?: (.*) "(.*)")?:$/ do |status, direction, folder, table|
|
||||||
table.hashes.each do |post|
|
table.hashes.each do |post|
|
||||||
date = Date.strptime(post['date'], '%m/%d/%Y').strftime('%Y-%m-%d')
|
|
||||||
title = post['title'].downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-')
|
title = post['title'].downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-')
|
||||||
|
|
||||||
if direction && direction == "in"
|
if direction && direction == "in"
|
||||||
|
@ -61,7 +60,14 @@ Given /^I have the following posts?(?: (.*) "(.*)")?:$/ do |direction, folder, t
|
||||||
after = folder || '.'
|
after = folder || '.'
|
||||||
end
|
end
|
||||||
|
|
||||||
path = File.join(before || '.', '_posts', after || '.', "#{date}-#{title}.#{post['type'] || 'textile'}")
|
ext = post['type'] || 'textile'
|
||||||
|
|
||||||
|
if "draft" == status
|
||||||
|
path = File.join(before || '.', '_drafts', after || '.', "#{title}.#{ext}")
|
||||||
|
else
|
||||||
|
date = Date.strptime(post['date'], '%m/%d/%Y').strftime('%Y-%m-%d')
|
||||||
|
path = File.join(before || '.', '_posts', after || '.', "#{date}-#{title}.#{ext}")
|
||||||
|
end
|
||||||
|
|
||||||
matter_hash = {}
|
matter_hash = {}
|
||||||
%w(title layout tag tags category categories published author).each do |key|
|
%w(title layout tag tags category categories published author).each do |key|
|
||||||
|
@ -117,6 +123,10 @@ When /^I run jekyll$/ do
|
||||||
run_jekyll
|
run_jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
|
When /^I run jekyll with drafts$/ do
|
||||||
|
run_jekyll(:drafts => true)
|
||||||
|
end
|
||||||
|
|
||||||
When /^I debug jekyll$/ do
|
When /^I debug jekyll$/ do
|
||||||
run_jekyll(:debug => true)
|
run_jekyll(:debug => true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,8 +10,9 @@ TEST_DIR = File.join('/', 'tmp', 'jekyll')
|
||||||
JEKYLL_PATH = File.join(ENV['PWD'], 'bin', 'jekyll')
|
JEKYLL_PATH = File.join(ENV['PWD'], 'bin', 'jekyll')
|
||||||
|
|
||||||
def run_jekyll(opts = {})
|
def run_jekyll(opts = {})
|
||||||
command = JEKYLL_PATH
|
command = JEKYLL_PATH.clone
|
||||||
command << " build"
|
command << " build"
|
||||||
|
command << " --drafts" if opts[:drafts]
|
||||||
command << " >> /dev/null 2>&1" if opts[:debug].nil?
|
command << " >> /dev/null 2>&1" if opts[:debug].nil?
|
||||||
system command
|
system command
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,7 @@ require 'jekyll/convertible'
|
||||||
require 'jekyll/layout'
|
require 'jekyll/layout'
|
||||||
require 'jekyll/page'
|
require 'jekyll/page'
|
||||||
require 'jekyll/post'
|
require 'jekyll/post'
|
||||||
|
require 'jekyll/draft'
|
||||||
require 'jekyll/filters'
|
require 'jekyll/filters'
|
||||||
require 'jekyll/static_file'
|
require 'jekyll/static_file'
|
||||||
require 'jekyll/errors'
|
require 'jekyll/errors'
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
module Jekyll
|
||||||
|
|
||||||
|
class Draft < Post
|
||||||
|
|
||||||
|
# Valid post name regex (no date)
|
||||||
|
MATCHER = /^(.*)(\.[^.]+)$/
|
||||||
|
|
||||||
|
# Draft name validator. Draft filenames must be like:
|
||||||
|
# my-awesome-post.textile
|
||||||
|
#
|
||||||
|
# Returns true if valid, false if not.
|
||||||
|
def self.valid?(name)
|
||||||
|
name =~ MATCHER
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the full path to the directory containing the draft files
|
||||||
|
def containing_dir(source, dir)
|
||||||
|
File.join(source, dir, '_drafts')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extract information from the post filename.
|
||||||
|
#
|
||||||
|
# name - The String filename of the post file.
|
||||||
|
#
|
||||||
|
# Returns nothing.
|
||||||
|
def process(name)
|
||||||
|
m, slug, ext = *name.match(MATCHER)
|
||||||
|
self.date = File.mtime(File.join(@base, name))
|
||||||
|
self.slug = slug
|
||||||
|
self.ext = ext
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -29,12 +29,11 @@ module Jekyll
|
||||||
# site - The Site.
|
# site - The Site.
|
||||||
# base - The String path to the dir containing the post file.
|
# base - The String path to the dir containing the post file.
|
||||||
# name - The String filename of the post file.
|
# name - The String filename of the post file.
|
||||||
# categories - An Array of Strings for the categories for this post.
|
|
||||||
#
|
#
|
||||||
# Returns the new Post.
|
# Returns the new Post.
|
||||||
def initialize(site, source, dir, name)
|
def initialize(site, source, dir, name)
|
||||||
@site = site
|
@site = site
|
||||||
@base = File.join(source, dir, '_posts')
|
@base = self.containing_dir(source, dir)
|
||||||
@name = name
|
@name = name
|
||||||
|
|
||||||
self.categories = dir.split('/').reject { |x| x.empty? }
|
self.categories = dir.split('/').reject { |x| x.empty? }
|
||||||
|
@ -65,6 +64,11 @@ module Jekyll
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the full path to the directory containing the post files
|
||||||
|
def containing_dir(source, dir)
|
||||||
|
return File.join(source, dir, '_posts')
|
||||||
|
end
|
||||||
|
|
||||||
# Read the YAML frontmatter.
|
# Read the YAML frontmatter.
|
||||||
#
|
#
|
||||||
# base - The String path to the dir containing the file.
|
# base - The String path to the dir containing the file.
|
||||||
|
|
|
@ -5,7 +5,7 @@ module Jekyll
|
||||||
attr_accessor :config, :layouts, :posts, :pages, :static_files,
|
attr_accessor :config, :layouts, :posts, :pages, :static_files,
|
||||||
:categories, :exclude, :include, :source, :dest, :lsi, :pygments,
|
:categories, :exclude, :include, :source, :dest, :lsi, :pygments,
|
||||||
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
|
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
|
||||||
:keep_files
|
:show_drafts, :keep_files
|
||||||
|
|
||||||
attr_accessor :converters, :generators
|
attr_accessor :converters, :generators
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ module Jekyll
|
||||||
self.exclude = config['exclude'] || []
|
self.exclude = config['exclude'] || []
|
||||||
self.include = config['include'] || []
|
self.include = config['include'] || []
|
||||||
self.future = config['future']
|
self.future = config['future']
|
||||||
|
self.show_drafts = config['show_drafts'] || nil
|
||||||
self.limit_posts = config['limit_posts'] || nil
|
self.limit_posts = config['limit_posts'] || nil
|
||||||
self.keep_files = config['keep_files'] || []
|
self.keep_files = config['keep_files'] || []
|
||||||
|
|
||||||
|
@ -148,6 +149,18 @@ module Jekyll
|
||||||
|
|
||||||
self.read_posts(dir)
|
self.read_posts(dir)
|
||||||
|
|
||||||
|
if self.show_drafts
|
||||||
|
self.read_drafts(dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.posts.sort!
|
||||||
|
|
||||||
|
# limit the posts if :limit_posts option is set
|
||||||
|
if limit_posts
|
||||||
|
limit = self.posts.length < limit_posts ? self.posts.length : limit_posts
|
||||||
|
self.posts = self.posts[-limit, limit]
|
||||||
|
end
|
||||||
|
|
||||||
entries.each do |f|
|
entries.each do |f|
|
||||||
f_abs = File.join(base, f)
|
f_abs = File.join(base, f)
|
||||||
f_rel = File.join(dir, f)
|
f_rel = File.join(dir, f)
|
||||||
|
@ -190,13 +203,28 @@ module Jekyll
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.posts.sort!
|
# Read all the files in <source>/<dir>/_drafts and create a new Post
|
||||||
|
# object with each one.
|
||||||
|
#
|
||||||
|
# dir - The String relative path of the directory to read.
|
||||||
|
#
|
||||||
|
# Returns nothing.
|
||||||
|
def read_drafts(dir)
|
||||||
|
base = File.join(self.source, dir, '_drafts')
|
||||||
|
return unless File.exists?(base)
|
||||||
|
entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
|
||||||
|
|
||||||
# limit the posts if :limit_posts option is set
|
# first pass processes, but does not yet render draft content
|
||||||
if limit_posts
|
entries.each do |f|
|
||||||
limit = self.posts.length < limit_posts ? self.posts.length : limit_posts
|
if Draft.valid?(f)
|
||||||
self.posts = self.posts[-limit, limit]
|
draft = Draft.new(self, self.source, dir, f)
|
||||||
|
|
||||||
|
self.posts << draft
|
||||||
|
draft.categories.each { |c| self.categories[c] << draft }
|
||||||
|
draft.tags.each { |c| self.tags[c] << draft }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue