Initialize each command in its own class so we can be *magical*.

This commit is contained in:
Parker Moore 2014-03-13 14:07:05 -04:00
parent 7288176f65
commit e746b3bd5f
7 changed files with 299 additions and 218 deletions

View File

@ -8,17 +8,6 @@ require 'mercenary'
Jekyll::Deprecator.process(ARGV) Jekyll::Deprecator.process(ARGV)
def add_build_options(c)
c.option 'config', '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
c.option 'future', '--future', 'Publishes posts with a future date'
c.option 'limit_posts', '--limit_posts MAX_POSTS', Integer, 'Limits the number of posts to parse and publish'
c.option 'watch', '-w', '--watch', 'Watch for changes and rebuild'
c.option 'lsi', '--lsi', 'Use LSI for improved related posts'
c.option 'show_drafts', '-D', '--drafts', 'Render posts in the _drafts folder'
c.option 'quiet', '-q', '--quiet', 'Silence output.'
c.option 'verbose', '-V', '--verbose', 'Print verbose output.'
end
Mercenary.program(:jekyll) do |p| Mercenary.program(:jekyll) do |p|
p.version Jekyll::VERSION p.version Jekyll::VERSION
p.description 'Jekyll is a blog-aware, static site generator in Ruby' p.description 'Jekyll is a blog-aware, static site generator in Ruby'
@ -30,9 +19,11 @@ Mercenary.program(:jekyll) do |p|
p.option 'plugins', '-p', '--plugins PLUGINS_DIR1[,PLUGINS_DIR2[,...]]', Array, 'Plugins directory (defaults to ./_plugins)' p.option 'plugins', '-p', '--plugins PLUGINS_DIR1[,PLUGINS_DIR2[,...]]', Array, 'Plugins directory (defaults to ./_plugins)'
p.option 'layouts', '--layouts DIR', String, 'Layouts directory (defaults to ./_layouts)' p.option 'layouts', '--layouts DIR', String, 'Layouts directory (defaults to ./_layouts)'
Jekyll::Command.subclasses.each { |c| c.init_with_program(p) }
p.action do |args, options| p.action do |args, options|
if args.empty? if args.empty?
p.go(["-h"]) puts p
else else
unless p.has_command?(args.first) unless p.has_command?(args.first)
Jekyll.logger.abort_with "Invalid command. Use --help for more information" Jekyll.logger.abort_with "Invalid command. Use --help for more information"
@ -40,83 +31,8 @@ Mercenary.program(:jekyll) do |p|
end end
end end
p.command(:new) do |c|
c.syntax 'jekyll new PATH'
c.description 'Creates a new Jekyll site scaffold in PATH'
c.option 'force', '--force', 'Force creation even if PATH already exists'
c.option 'blank', '--blank', 'Creates scaffolding but with empty files'
c.action do |args, options|
Jekyll::Commands::New.process(args)
end
end
p.command(:build) do |c|
c.syntax 'jekyll build [options]'
c.description 'Build your site'
add_build_options(c)
c.action do |args, options|
options["serving"] = false
config = Jekyll.configuration(options)
Jekyll::Commands::Build.process(config)
end
end
p.command(:serve) do |c|
c.syntax 'jekyll serve [options]'
c.description 'Serve your site locally'
c.alias :server
add_build_options(c)
c.option 'detach', '-B', '--detach', 'Run the server in the background (detach)'
c.option 'port', '-P', '--port [PORT]', 'Port to listen on'
c.option 'host', '-H', '--host [HOST]', 'Host to bind to'
c.option 'baseurl', '-b', '--baseurl [URL]', 'Base URL'
c.action do |args, options|
options["serving"] ||= true
options = Jekyll.configuration(options)
Jekyll::Commands::Build.process(options)
Jekyll::Commands::Serve.process(options)
end
end
p.command(:doctor) do |c|
c.syntax 'jekyll doctor'
c.description 'Search site and print specific deprecation warnings'
c.alias(:hyde)
c.option '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
c.action do |args, options|
options = Jekyll.configuration(options)
Jekyll::Commands::Doctor.process(options)
end
end
p.command(:docs) do |c|
c.syntax 'jekyll docs'
c.description "Launch local server with docs for Jekyll v#{Jekyll::VERSION}"
c.option 'port', '-P', '--port [PORT]', 'Port to listen on'
c.option 'host', '-H', '--host [HOST]', 'Host to bind to'
c.action do |args, options|
options = Jekyll.configuration(options.merge!({
'source' => File.expand_path("../site", File.dirname(__FILE__)),
'destination' => File.expand_path("../site/_site", File.dirname(__FILE__))
}))
Jekyll::Commands::Build.process(options)
Jekyll::Commands::Serve.process(options)
end
end
p.command(:import) do |c| p.command(:import) do |c|
c.syntax 'jekyll import <platform> [options]' c.syntax 'import <platform> [options]'
c.description 'Import your old blog to Jekyll' c.description 'Import your old blog to Jekyll'
importers = [] importers = []

View File

@ -1,6 +1,31 @@
module Jekyll module Jekyll
class Command class Command
def self.globs(source, destination)
class << self
# A list of subclasses of Jekyll::Command
def subclasses
@subclasses ||= []
end
# Keep a list of subclasses of Jekyll::Command every time it's inherited
# Called automatically.
#
# base - the subclass
#
# Returns nothing
def inherited(base)
subclasses << base
super(base)
end
# Listing of all directories (globbed to include subfiles and folders)
#
# source - the source path
# destination - the destination path
#
# Returns an Array of directory globs in the source, excluding the destination
def globs(source, destination)
Dir.chdir(source) do Dir.chdir(source) do
dirs = Dir['*'].select { |x| File.directory?(x) } dirs = Dir['*'].select { |x| File.directory?(x) }
dirs -= [destination, File.expand_path(destination), File.basename(destination)] dirs -= [destination, File.expand_path(destination), File.basename(destination)]
@ -9,19 +34,46 @@ module Jekyll
end end
end end
# Static: Run Site#process and catch errors # Run Site#process and catch errors
# #
# site - the Jekyll::Site object # site - the Jekyll::Site object
# #
# Returns nothing # Returns nothing
def self.process_site(site) def process_site(site)
site.process site.process
rescue Jekyll::FatalException => e rescue Jekyll::FatalException => e
puts
Jekyll.logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:" Jekyll.logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:"
Jekyll.logger.error "", "------------------------------------" Jekyll.logger.error "", "------------------------------------"
Jekyll.logger.error "", e.message Jekyll.logger.error "", e.message
exit(1) exit(1)
end end
# Create a full Jekyll configuration with the options passed in as overrides
#
# options - the configuration overrides
#
# Returns a full Jekyll configuration
def configuration_from_options(options)
Jekyll.configuration(options)
end
# Add common options to a command for building configuration
#
# c - the Jekyll::Command to add these options to
#
# Returns nothing
def add_build_options(c)
c.option 'config', '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
c.option 'future', '--future', 'Publishes posts with a future date'
c.option 'limit_posts', '--limit_posts MAX_POSTS', Integer, 'Limits the number of posts to parse and publish'
c.option 'watch', '-w', '--watch', 'Watch for changes and rebuild'
c.option 'lsi', '--lsi', 'Use LSI for improved related posts'
c.option 'show_drafts', '-D', '--drafts', 'Render posts in the _drafts folder'
c.option 'quiet', '-q', '--quiet', 'Silence output.'
c.option 'verbose', '-V', '--verbose', 'Print verbose output.'
end
end
end end
end end

View File

@ -1,7 +1,28 @@
module Jekyll module Jekyll
module Commands module Commands
class Build < Command class Build < Command
def self.process(options)
class << self
# Create the Mercenary command for the Jekyll CLI for this Command
def init_with_program(prog)
prog.command(:build) do |c|
c.syntax 'build [options]'
c.description 'Build your site'
add_build_options(c)
c.action do |args, options|
options["serving"] = false
Jekyll::Commands::Build.process(options)
end
end
end
# Build your jekyll site
# Continuously watch if `watch` is set to true in the config.
def process(options)
options = configuration_from_options(options)
site = Jekyll::Site.new(options) site = Jekyll::Site.new(options)
Jekyll.logger.log_level = Jekyll::Stevenson::ERROR if options['quiet'] Jekyll.logger.log_level = Jekyll::Stevenson::ERROR if options['quiet']
@ -10,20 +31,20 @@ module Jekyll
watch(site, options) if options['watch'] watch(site, options) if options['watch']
end end
# Private: Build the site from source into destination. # Build your Jekyll site.
# #
# site - A Jekyll::Site instance # site - the Jekyll::Site instance to build
# options - A Hash of options passed to the command # options - the
# #
# Returns nothing. # Returns nothing.
def self.build(site, options) def build(site, options)
source = options['source'] source = options['source']
destination = options['destination'] destination = options['destination']
Jekyll.logger.info "Source:", source Jekyll.logger.info "Source:", source
Jekyll.logger.info "Destination:", destination Jekyll.logger.info "Destination:", destination
print Jekyll.logger.formatted_topic "Generating..." Jekyll.logger.info "Generating..."
process_site(site) process_site(site)
puts "done." Jekyll.logger.info "", "done."
end end
# Private: Watch for file changes and rebuild the site. # Private: Watch for file changes and rebuild the site.
@ -32,7 +53,7 @@ module Jekyll
# options - A Hash of options passed to the command # options - A Hash of options passed to the command
# #
# Returns nothing. # Returns nothing.
def self.watch(site, options) def watch(site, options)
require 'listen' require 'listen'
source = options['source'] source = options['source']
@ -67,6 +88,9 @@ module Jekyll
loop { sleep 1000 } loop { sleep 1000 }
end end
end end
end # end of class << self
end end
end end
end end

View File

@ -0,0 +1,30 @@
module Jekyll
module Commands
class Docs < Command
class << self
def init_with_program(prog)
prog.command(:docs) do |c|
c.syntax 'docs'
c.description "Launch local server with docs for Jekyll v#{Jekyll::VERSION}"
c.option 'port', '-P', '--port [PORT]', 'Port to listen on'
c.option 'host', '-H', '--host [HOST]', 'Host to bind to'
c.action do |args, options|
options.merge!({
'source' => File.expand_path("../../../site", File.dirname(__FILE__)),
'destination' => File.expand_path("../../../site/_site", File.dirname(__FILE__))
})
Jekyll::Commands::Build.process(options)
Jekyll::Commands::Serve.process(options)
end
end
end
end
end
end
end

View File

@ -2,8 +2,23 @@ module Jekyll
module Commands module Commands
class Doctor < Command class Doctor < Command
class << self class << self
def init_with_program(prog)
prog.command(:doctor) do |c|
c.syntax 'doctor'
c.description 'Search site and print specific deprecation warnings'
c.alias(:hyde)
c.option '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
c.action do |args, options|
Jekyll::Commands::Doctor.process(options)
end
end
end
def process(options) def process(options)
site = Jekyll::Site.new(options) site = Jekyll::Site.new(configuration_from_options(options))
site.read site.read
if healthy?(site) if healthy?(site)
@ -61,7 +76,9 @@ module Jekyll
end end
urls urls
end end
end
end
end end
end end
end end

View File

@ -3,23 +3,36 @@ require 'erb'
module Jekyll module Jekyll
module Commands module Commands
class New < Command class New < Command
def self.init_with_program(prog)
prog.command(:new) do |c|
c.syntax 'new PATH'
c.description 'Creates a new Jekyll site scaffold in PATH'
c.option 'force', '--force', 'Force creation even if PATH already exists'
c.option 'blank', '--blank', 'Creates scaffolding but with empty files'
c.action do |args, options|
Jekyll::Commands::New.process(args, options)
end
end
end
def self.process(args, options = {}) def self.process(args, options = {})
raise ArgumentError.new('You must specify a path.') if args.empty? raise ArgumentError.new('You must specify a path.') if args.empty?
new_blog_path = File.expand_path(args.join(" "), Dir.pwd) new_blog_path = File.expand_path(args.join(" "), Dir.pwd)
FileUtils.mkdir_p new_blog_path FileUtils.mkdir_p new_blog_path
if preserve_source_location?(new_blog_path, options) if preserve_source_location?(new_blog_path, options)
Jekyll.logger.error "Conflict:", "#{new_blog_path} exists and is not empty." Jekyll.logger.abort_with "Conflict:", "#{new_blog_path} exists and is not empty."
exit(1)
end end
if options[:blank] if options["blank"]
create_blank_site new_blog_path create_blank_site new_blog_path
else else
create_sample_files new_blog_path create_sample_files new_blog_path
File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f| File.open(File.expand_path(initialized_post_name, new_blog_path), "w") do |f|
f.write(self.scaffold_post_content) f.write(scaffold_post_content)
end end
end end
@ -47,7 +60,7 @@ module Jekyll
private private
def self.preserve_source_location?(path, options) def self.preserve_source_location?(path, options)
!options[:force] && !Dir["#{path}/**/*"].empty? !options["force"] && !Dir["#{path}/**/*"].empty?
end end
def self.create_sample_files(path) def self.create_sample_files(path)

View File

@ -2,9 +2,35 @@
module Jekyll module Jekyll
module Commands module Commands
class Serve < Command class Serve < Command
def self.process(options)
class << self
def init_with_program(prog)
prog.command(:serve) do |c|
c.syntax 'serve [options]'
c.description 'Serve your site locally'
c.alias :server
add_build_options(c)
c.option 'detach', '-B', '--detach', 'Run the server in the background (detach)'
c.option 'port', '-P', '--port [PORT]', 'Port to listen on'
c.option 'host', '-H', '--host [HOST]', 'Host to bind to'
c.option 'baseurl', '-b', '--baseurl [URL]', 'Base URL'
c.action do |args, options|
options["serving"] ||= true
Jekyll::Commands::Build.process(options)
Jekyll::Commands::Serve.process(options)
end
end
end
# Boot up a WEBrick server which points to the compiled site's root.
def process(options)
options = configuration_from_options(options)
require 'webrick' require 'webrick'
include WEBrick
destination = options['destination'] destination = options['destination']
@ -23,11 +49,11 @@ module Jekyll
fh_option = WEBrick::Config::FileHandler fh_option = WEBrick::Config::FileHandler
fh_option[:NondisclosureName] = ['.ht*','~*'] fh_option[:NondisclosureName] = ['.ht*','~*']
s = HTTPServer.new(webrick_options(options)) s = WEBrick::HTTPServer.new(webrick_options(options))
s.config.store(:DirectoryIndex, s.config[:DirectoryIndex] << "index.xml") s.config.store(:DirectoryIndex, s.config[:DirectoryIndex] << "index.xml")
s.mount(options['baseurl'], HTTPServlet::FileHandler, destination, fh_option) s.mount(options['baseurl'], WEBrick::HTTPServlet::FileHandler, destination, fh_option)
Jekyll.logger.info "Server address:", "http://#{s.config[:BindAddress]}:#{s.config[:Port]}" Jekyll.logger.info "Server address:", "http://#{s.config[:BindAddress]}:#{s.config[:Port]}"
@ -38,16 +64,16 @@ module Jekyll
else # create a new server thread, then join it with current terminal else # create a new server thread, then join it with current terminal
t = Thread.new { s.start } t = Thread.new { s.start }
trap("INT") { s.shutdown } trap("INT") { s.shutdown }
t.join() t.join
end end
end end
def self.webrick_options(config) def webrick_options(config)
opts = { opts = {
:DocumentRoot => config['destination'], :DocumentRoot => config['destination'],
:Port => config['port'], :Port => config['port'],
:BindAddress => config['host'], :BindAddress => config['host'],
:MimeTypes => self.mime_types, :MimeTypes => mime_types,
:DoNotReverseLookup => true, :DoNotReverseLookup => true,
:StartCallback => start_callback(config['detach']) :StartCallback => start_callback(config['detach'])
} }
@ -55,23 +81,26 @@ module Jekyll
if !config['verbose'] if !config['verbose']
opts.merge!({ opts.merge!({
:AccessLog => [], :AccessLog => [],
:Logger => Log::new([], Log::WARN) :Logger => WEBrick::Log.new([], WEBrick::Log::WARN)
}) })
end end
opts opts
end end
def self.start_callback(detached) def start_callback(detached)
unless detached unless detached
Proc.new { Jekyll.logger.info "Server running...", "press ctrl-c to stop." } Proc.new { Jekyll.logger.info "Server running...", "press ctrl-c to stop." }
end end
end end
def self.mime_types def mime_types
mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__)) mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__))
WEBrick::HTTPUtils::load_mime_types(mime_types_file) WEBrick::HTTPUtils::load_mime_types(mime_types_file)
end end
end
end end
end end
end end