diff --git a/bin/jekyll b/bin/jekyll index b8467fb1..309ffc91 100755 --- a/bin/jekyll +++ b/bin/jekyll @@ -1,303 +1,70 @@ #!/usr/bin/env ruby -$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) +$:.unshift File.join(File.dirname(__FILE__), *%w{ .. lib }) -help = < ./_site - jekyll # . -> - jekyll # -> - jekyll import # imports posts using named import script - - Configuration is read from '/_config.yml' but can be overriden - using the following options: - -HELP - -require 'optparse' +require 'commander/import' require 'jekyll' +program :name, 'jekyll' +program :version, Jekyll::VERSION +program :description, 'Jekyll is a blog-aware, static site generator in Ruby' -exec = {} -options = {} -opts = OptionParser.new do |opts| - opts.banner = help +default_command :help - opts.on("--file [PATH]", "File to import from") do |import_file| - options['file'] = import_file - end - - opts.on("--dbname [TEXT]", "DB to import from") do |import_dbname| - options['dbname'] = import_dbname - end - - opts.on("--user [TEXT]", "Username to use when importing") do |import_user| - options['user'] = import_user - end - - opts.on("--pass [TEXT]", "Password to use when importing") do |import_pass| - options['pass'] = import_pass - end - - opts.on("--host [HOST ADDRESS]", "Host to import from") do |import_host| - options['host'] = import_host - end - - opts.on("--site [SITE NAME]", "Site to import from") do |import_site| - options['site'] = import_site - end - +global_option '-s', '--source [DIR]', 'Source directory (defaults to ./)' +global_option '-d', '--destination [DIR]', 'Destination directory (defaults to ./_site)' +global_option '--safe', 'Safe mode (defaults to false)' +global_option '--plugins', 'Plugins directory (defaults to ./_plugins)' +global_option '--layouts', 'Layouts directory (defaults to ./_layouts)' - opts.on("--[no-]safe", "Safe mode (default unsafe)") do |safe| - options['safe'] = safe - end +command :build do |c| + c.syntax = 'jekyll build [options]' + c.description = 'Build your site with the option of auto-renegeration' - opts.on("--[no-]auto", "Auto-regenerate") do |auto| - options['auto'] = auto - end + c.option '-w', '--watch', 'Watch for changes and rebuild' + c.option '--lsi', 'Use LSI for improved related posts' - opts.on("--server [PORT]", "Start web server (default port 4000)") do |port| - options['server'] = true - options['server_port'] = port unless port.nil? - end - - opts.on("--no-server", "Do not start a web server") do |part| - options['server'] = false - end - - opts.on("--base-url [BASE_URL]", "Serve website from a given base URL (default '/'") do |baseurl| - options['baseurl'] = baseurl - end - - opts.on("--default-mimetype [MT]", "Mimetype to use when no file extension (if --server)") do |mt| - options['default-mimetype'] = mt - end - - opts.on("--[no-]lsi", "Use LSI for better related posts") do |lsi| - options['lsi'] = lsi - end - - opts.on("--[no-]pygments", "Use pygments to highlight code") do |pygments| - options['pygments'] = pygments - end - - opts.on("--rdiscount", "Use rdiscount gem for Markdown") do - options['markdown'] = 'rdiscount' - end - - opts.on("--redcarpet", "Use redcarpet gem for Markdown") do - options['markdown'] = 'redcarpet' - end - - opts.on("--kramdown", "Use kramdown gem for Markdown") do - options['markdown'] = 'kramdown' - end - - opts.on("--time [TIME]", "Time to generate the site for") do |time| - options['time'] = Time.parse(time) - end - - opts.on("--[no-]future", "Render future dated posts") do |future| - options['future'] = future - end - - opts.on("--permalink [TYPE]", "Use 'date' (default) for YYYY/MM/DD") do |style| - options['permalink'] = style unless style.nil? - end - - opts.on("--paginate [POSTS_PER_PAGE]", "Paginate a blog's posts") do |per_page| - begin - options['paginate'] = per_page.to_i - raise ArgumentError if options['paginate'] == 0 - rescue - puts 'you must specify a number of posts by page bigger than 0' - exit 0 - end - end - - opts.on("--paginate_path [PAGINATED_URL_FORMAT]", "Leave blank for /page") do |paginate_path| - begin - options['paginate_path'] = paginate_path - raise ArgumentError if options['paginate_path'].nil? - rescue - puts 'You must specify a pagination url format' - exit 0 - end - end - - opts.on("--limit_posts [MAX_POSTS]", "Limit the number of posts to publish") do |limit_posts| - begin - options['limit_posts'] = limit_posts.to_i - raise ArgumentError if options['limit_posts'] < 1 - rescue - puts 'you must specify a number of posts by page bigger than 0' - exit 0 - end - end - - opts.on("--url [URL]", "Set custom site.url") do |url| - options['url'] = url - end - - opts.on("--version", "Display current version") do - puts "Jekyll " + Jekyll::VERSION - exit 0 - end - - opts.on( "--keep-files filename1,filename2", Array, "Whether to keep files that match the filename (default: .git)") do |names| - options['keep_files'] = names + c.action do |args, options| + options.defaults :serving => false + options = Jekyll.configuration(options.__hash__) + Jekyll::BuildCommand.process(options) end end -# Read command line options into `options` hash -opts.parse! +command :serve do |c| + c.syntax = 'jekyll serve [options]' + c.description = 'Serve your site locally with the option of auto-regeneration' + c.option '-w', '--watch', 'Watch for changes and rebuild' + c.option '--lsi', 'Use LSI for improved related posts' -# Check for import stuff -if ARGV.size > 0 - if ARGV[0] == 'import' - migrator = ARGV[1] + c.option '-p', '--port [PORT]', 'Port to listen on' + c.option '-h', '--host [HOST]', 'Host to bind to' + c.option '-b', '--baseurl [URL]', 'Base URL' - if migrator.nil? - puts "Invalid options. Run `jekyll --help` for assistance." - exit(1) - else - migrator = migrator.downcase - end + c.action do |args, options| + options.default :port => '4000', + :host => '0.0.0.0', + :baseurl => '/', + :serving => true - cmd_options = [] - ['file', 'dbname', 'user', 'pass', 'host', 'site'].each do |p| - cmd_options << "\"#{options[p]}\"" unless options[p].nil? - end - - # It's import time - puts "Importing..." - - # Ideally, this shouldn't be necessary. Maybe parse the actual - # src files for the migrator name? - migrators = { - :posterous => 'Posterous', - :wordpressdotcom => 'WordpressDotCom', - :wordpress => 'WordPress', - :csv => 'CSV', - :drupal => 'Drupal', - :enki => 'Enki', - :mephisto => 'Mephisto', - :mt => 'MT', - :textpattern => 'TextPattern', - :tumblr => 'Tumblr', - :typo => 'Typo' - } - - app_root = File.join(File.dirname(__FILE__), '..') - - require "#{app_root}/lib/jekyll/migrators/#{migrator}" - - if Jekyll.const_defined?(migrators[migrator.to_sym]) - migrator_class = Jekyll.const_get(migrators[migrator.to_sym]) - migrator_class.process(*cmd_options) - else - puts "Invalid migrator. Run `jekyll --help` for assistance." - exit(1) - end - - exit(0) + options = Jekyll.configuration(options.__hash__) + Jekyll::BuildCommand.process(options) + Jekyll::ServeCommand.process(options) end end +command :import do |c| + c.syntax = 'jekyll import [options]' + c.description = 'Import your old blog to Jekyll' + c.option '--file', 'File to migrate from' + c.option '--dbname', 'Database name to migrate from' + c.option '--user', 'Username to use when migrating' + c.option '--pass', 'Password to use when migrating' + c.option '--host', 'Host address to use when migrating' -# Get source and destination from command line -case ARGV.size - when 0 - when 1 - options['destination'] = ARGV[0] - when 2 - options['source'] = ARGV[0] - options['destination'] = ARGV[1] - else - puts "Invalid options. Run `jekyll --help` for assistance." - exit(1) -end - -options = Jekyll.configuration(options) - -# Get source and destination directories (possibly set by config file) -source = options['source'] -destination = options['destination'] - -# Files to watch -def globs(source, destination) - Dir.chdir(source) do - dirs = Dir['*'].select { |x| File.directory?(x) } - dirs -= [destination] - dirs = dirs.map { |x| "#{x}/**/*" } - dirs += ['*'] + c.action do |args, options| + Jekyll::MigrateCommand.process(args.first, options) end end - -# Create the Site -site = Jekyll::Site.new(options) - -# Run the directory watcher for auto-generation, if required -if options['auto'] - require 'directory_watcher' - - puts "Auto-regenerating enabled: #{source} -> #{destination}" - - dw = DirectoryWatcher.new(source) - dw.interval = 1 - dw.glob = globs(source, destination) - - dw.add_observer do |*args| - t = Time.now.strftime("%Y-%m-%d %H:%M:%S") - puts "[#{t}] regeneration: #{args.size} files changed" - site.process - end - - dw.start - - unless options['server'] - loop { sleep 1000 } - end -else - puts "Building site: #{source} -> #{destination}" - begin - site.process - rescue Jekyll::FatalException => e - puts - puts "ERROR: YOUR SITE COULD NOT BE BUILT:" - puts "------------------------------------" - puts e.message - exit(1) - end - puts "Successfully generated site: #{source} -> #{destination}" -end - -# Run the server on the specified port, if required -if options['server'] - require 'webrick' - include WEBrick - - FileUtils.mkdir_p(destination) - - mime_types = WEBrick::HTTPUtils::DefaultMimeTypes - mime_types.store 'js', 'application/javascript' - if options['default-mimetype'] - mime_types.store(nil, options['default-mimetype']) - end - - s = HTTPServer.new( - :Port => options['server_port'], - :MimeTypes => mime_types - ) - s.mount(options['baseurl'], HTTPServlet::FileHandler, destination) - t = Thread.new { - s.start - } - - trap("INT") { s.shutdown } - t.join() -end diff --git a/features/support/env.rb b/features/support/env.rb index 3166ce9c..1ed330a1 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -11,6 +11,7 @@ JEKYLL_PATH = File.join(ENV['PWD'], 'bin', 'jekyll') def run_jekyll(opts = {}) command = JEKYLL_PATH + command << " build" command << " >> /dev/null 2>&1" if opts[:debug].nil? system command end diff --git a/jekyll.gemspec b/jekyll.gemspec index dbbe4386..433fd78d 100644 --- a/jekyll.gemspec +++ b/jekyll.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('maruku', "~> 0.5") s.add_runtime_dependency('kramdown', "~> 0.13.4") s.add_runtime_dependency('pygments.rb', "~> 0.3.2") + s.add_runtime_dependency('commander', "~> 4.1.3") s.add_development_dependency('rake', "~> 0.9") s.add_development_dependency('rdoc', "~> 3.11") diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 2e5a681e..477247dd 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -41,6 +41,9 @@ require 'jekyll/errors' require 'jekyll/plugin' require 'jekyll/converter' require 'jekyll/generator' +require 'jekyll/command' + +require_all 'jekyll/commands' require_all 'jekyll/converters' require_all 'jekyll/generators' require_all 'jekyll/tags' @@ -48,27 +51,23 @@ require_all 'jekyll/tags' module Jekyll VERSION = '0.12.0' - # Default options. Overriden by values in _config.yml or command-line opts. + # Default options. Overriden by values in _config.yml. # Strings rather than symbols are used for compatability with YAML. DEFAULTS = { - 'safe' => false, - 'auto' => false, - 'server' => false, - 'server_port' => 4000, - 'source' => Dir.pwd, 'destination' => File.join(Dir.pwd, '_site'), + 'plugins' => File.join(Dir.pwd, '_plugins'), 'layouts' => '_layouts', 'keep_files' => ['.git','.svn'], - 'future' => true, - 'lsi' => false, - 'pygments' => false, - 'markdown' => 'maruku', - 'permalink' => 'date', - 'include' => ['.htaccess'], - 'paginate_path' => 'page:num', + 'future' => true, # remove and make true just default + 'pygments' => false, # remove and make true just default + + 'markdown' => 'maruku', # no longer a command option + 'permalink' => 'date', # no longer a command option + 'include' => ['.htaccess'], # no longer a command option + 'paginate_path' => 'page:num', # no longer a command option 'markdown_ext' => 'markdown,mkd,mkdn,md', 'textile_ext' => 'textile', @@ -121,6 +120,9 @@ module Jekyll # # 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'] diff --git a/lib/jekyll/command.rb b/lib/jekyll/command.rb new file mode 100644 index 00000000..340d457d --- /dev/null +++ b/lib/jekyll/command.rb @@ -0,0 +1,14 @@ +module Jekyll + + class Command + def self.globs(source) + Dir.chdir(source) do + dirs = Dir['*'].select { |x| File.directory?(x) } + dirs -= ['_site'] + dirs = dirs.map { |x| "#{x}/**/*" } + dirs += ['*'] + end + end + end + +end diff --git a/lib/jekyll/commands/build.rb b/lib/jekyll/commands/build.rb new file mode 100644 index 00000000..27461101 --- /dev/null +++ b/lib/jekyll/commands/build.rb @@ -0,0 +1,76 @@ +module Jekyll + + class BuildCommand < Command + def self.process(options) + site = Jekyll::Site.new(options) + + source = options['source'] + destination = options['destination'] + + if options['watch'] + self.watch(site, options) + else + self.build(site, options) + end + end + + # Private: Build the site from source into destination. + # + # site - A Jekyll::Site instance + # options - A Hash of options passed to the command + # + # Returns nothing. + def self.build(site, options) + source = options['source'] + destination = options['destination'] + puts "Building site: #{source} -> #{destination}" + begin + site.process + rescue Jekyll::FatalException => e + puts + puts "ERROR: YOUR SITE COULD NOT BE BUILT:" + puts "------------------------------------" + puts e.message + exit(1) + end + puts "Successfully generated site: #{source} -> #{destination}" + end + + # Private: Watch for file changes and rebuild the site. + # + # site - A Jekyll::Site instance + # options - A Hash of options passed to the command + # + # Returns nothing. + def self.watch(site, options) + require 'directory_watcher' + + source = options['source'] + destination = options['destination'] + + puts "Auto-Regenerating enabled: #{source} -> #{destination}" + + dw = DirectoryWatcher.new(source) + dw.interval = 1 + dw.glob = self.globs(source) + + dw.add_observer do |*args| + t = Time.now.strftime("%Y-%m-%d %H:%M:%S") + puts "[#{t}] regeneration: #{args.size} files changed" + site.process + end + + dw.start + + unless options['serving'] + trap("INT") do + puts "Stopping auto-regeneration..." + exit 0 + end + + loop { sleep 1000 } + end + end + end + +end diff --git a/lib/jekyll/commands/migrate.rb b/lib/jekyll/commands/migrate.rb new file mode 100644 index 00000000..26937357 --- /dev/null +++ b/lib/jekyll/commands/migrate.rb @@ -0,0 +1,47 @@ +module Jekyll + + class MigrateCommand < Command + MIGRATORS = { + :csv => 'CSV', + :drupal => 'Drupal', + :enki => 'Enki', + :mephisto => 'Mephisto', + :mt => 'MT', + :posterous => 'Posterous', + :textpattern => 'TextPattern', + :tumblr => 'Tumblr', + :typo => 'Typo', + :wordpressdotcom => 'WordpressDotCom', + :wordpress => 'WordPress' + } + + def self.process(migrator, options) + abort 'missing argument. Please specify a migrator' if migrator.nil? + migrator = migrator.downcase + + cmd_options = [] + [ :file, :dbname, :user, :pass, :host, :site ].each do |p| + cmd_options << "\"#{options[p]}\"" unless options[p].nil? + end + + + if MIGRATORS.keys.include?(migrator) + app_root = File.expand_path( + File.join(File.dirname(__FILE__), '..', '..', '..') + ) + + require "#{app_root}/lib/jekyll/migrators/#{migrator}" + + if Jekyll.const_defiend?(MIGRATORS[migrator.to_sym]) + puts 'Importing...' + migrator_class = Jekyll.const_get(MIGRATORS[migrator.to_sym]) + migrator_class.process(*cmd_options) + exit 0 + end + end + + abort 'invalid migrator. Please specify a valid migrator' + end + end + +end diff --git a/lib/jekyll/commands/serve.rb b/lib/jekyll/commands/serve.rb new file mode 100644 index 00000000..df717bea --- /dev/null +++ b/lib/jekyll/commands/serve.rb @@ -0,0 +1,28 @@ +module Jekyll + + class ServeCommand < Command + def self.process(options) + require 'webrick' + include WEBrick + + destination = options['destination'] + + FileUtils.mkdir_p(destination) + + mime_types = WEBrick::HTTPUtils::DefaultMimeTypes + mime_types.store 'js', 'application/javascript' + + s = HTTPServer.new( + :Port => options['port'], + :BindAddress => options['host'], + :MimeTypes => mime_types + ) + + s.mount(options['baseurl'], HTTPServlet::FileHandler, destination) + t = Thread.new { s.start } + trap("INT") { s.shutdown } + t.join() + end + end + +end diff --git a/lib/jekyll/migrators/posterous.rb b/lib/jekyll/migrators/posterous.rb index f1601ba3..0a2280f2 100644 --- a/lib/jekyll/migrators/posterous.rb +++ b/lib/jekyll/migrators/posterous.rb @@ -1,14 +1,11 @@ require 'rubygems' require 'jekyll' require 'fileutils' -require 'net/https' -require 'open-uri' +require 'net/http' require 'uri' require "json" -# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, api_token, blog, tags_key)' -# You can find your api token in posterous api page - http://posterous.com/api . Click on any of the 'view token' links to see your token. -# blog is optional, by default it is the primary one +# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, api_key, blog)' module Jekyll module Posterous @@ -17,9 +14,6 @@ module Jekyll raise ArgumentError, 'Stuck in a redirect loop. Please double check your email and password' if limit == 0 response = nil - - puts uri_str - puts '-------' Net::HTTP.start('posterous.com') do |http| req = Net::HTTP::Get.new(uri_str) req.basic_auth @email, @pass @@ -29,50 +23,36 @@ module Jekyll case response when Net::HTTPSuccess then response when Net::HTTPRedirection then fetch(response['location'], limit - 1) - when Net::HTTPForbidden then - retry_after = response.to_hash['retry-after'][0] - puts "We have been told to try again after #{retry_after} seconds" - sleep(retry_after.to_i + 1) - fetch(uri_str, limit - 1) else response.error! end end - def self.process(email, pass, api_token, blog = 'primary', tags_key = 'categories') + def self.process(email, pass, api_token, blog = 'primary') @email, @pass, @api_token = email, pass, api_token FileUtils.mkdir_p "_posts" - posts = JSON.parse(self.fetch("/api/2/sites/#{blog}/posts?api_token=#{@api_token}").body) + posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body) page = 1 while posts.any? posts.each do |post| title = post["title"] - slug = title.gsub(/[^[:alnum:]]+/, '-').gsub(/^-+|-+$/, '').downcase - posterous_slug = post["slug"] + slug = title.gsub(/[^[:alnum:]]+/, '-').downcase date = Date.parse(post["display_date"]) content = post["body_html"] published = !post["is_private"] name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day, slug] - tags = [] - post["tags"].each do |tag| - tags.push(tag["name"]) - end # Get the relevant fields as a hash, delete empty fields and convert # to YAML for the header data = { 'layout' => 'post', 'title' => title.to_s, - 'published' => published, - tags_key => tags, - 'posterous_url' => post["full_url"], - 'posterous_slug' => posterous_slug + 'published' => published }.delete_if { |k,v| v.nil? || v == ''}.to_yaml # Write out the data and content to file File.open("_posts/#{name}", "w") do |f| - puts name f.puts data f.puts "---" f.puts content @@ -80,7 +60,7 @@ module Jekyll end page += 1 - posts = JSON.parse(self.fetch("/api/2/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body) + posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body) end end end