module Plate # The CLI class controls the behavior of plate when it is used as a command line interface. class CLI require 'optparse' attr_accessor :source, :destination, :args, :options def initialize(args = []) @args = String === args ? args.split(' ') : args.dup @options = {} # Some defaults @source = Dir.pwd @destination = File.join(@source, 'public') end def builder @builder ||= Builder.new(self.source, self.destination, self.options) end # The current command to be run. Pulled from the args attribute. def command self.args.size > 0 ? self.args[0] : 'build' end def parse_options! options = {} opts = OptionParser.new do |opts| banner = "Usage: plate [command] [options]" opts.on('--category [CATEGORY]', '-c', 'Pass in a category for creating a new post.') do |c| options[:category] = c end opts.on('--config [PATH]', '-C', 'Set the config file location for the site.') do |c| options[:config] = c end opts.on('--destination [PATH]', '-d', 'Set the destination directory for this build.') do |d| @destination = File.expand_path(d) end opts.on('--layout [LAYOUT]', '-l', 'Pass in a layout for creating a new post.') do |l| options[:layout] = l end opts.on('--source [PATH]', '-s', 'Set the source directory for this build.') do |s| @source = File.expand_path(s) end opts.on('--verbose', '-V', 'Show output about the generation of the site.') do options[:verbose] = true end opts.on('--version', '-v', 'Show the current Plate version number.') do puts "You're running Plate version #{Plate::VERSION}!" exit 0 end opts.on('--watch', '-w', 'Watch the source directory for changes.') do options[:watch] = true end end opts.parse!(self.args) @options = options end # Run the given command. If the command does not exist, nothing will be run. def run parse_options! command_name = "run_#{command}_command".to_sym if self.respond_to?(command_name) # remove command name self.args.shift self.send(command_name) else puts "Command #{command} not found" return false end true end class << self def run! new(ARGV).run end end protected def process_file_change(event) relative_path = builder.relative_path(event.path) unless relative_path.start_with?('/public/') if builder.reloadable?(relative_path) case event.type when :added puts " -> New File: #{relative_path}" builder.rebuild! when :modified puts " -> Changed File: #{relative_path}" builder.render_file!(relative_path) when :removed puts " -> Deleted File: #{relative_path}" builder.rebuild! end else puts " -> Modified file [#{relative_path}]" puts " Re-run `plate build` to see the changes." end end end def run_build_command puts "Building your site from #{source} to #{destination}" builder.enable_logging = true if options[:verbose] builder.render! if builder.items? # If we want to watch directories, keep the chain open if options[:watch] puts "Initial site build complete. Watching #{source} for changes..." dw = DirectoryWatcher.new(source, :pre_load => true, :glob => '/**/*') dw.interval = 1 dw.add_observer { |*args| args.each { |event| process_file_change(event) } } dw.start trap('INT') { dw.stop and exit 0 } loop { sleep 100 } else puts "Site build complete." end else puts "** There seems to be no site content in this folder. Did you mean to create a new site?\n\n plate new .\n\n" end end def run_new_command # Set the base root root = './' if args.size > 0 root = args[0] end puts "Generating new plate site at #{root}..." # The starting path for the new site root_path = File.expand_path(root) # Create all folders needed for a base site. %w( / config content layouts posts ).each do |dir| action = File.directory?(File.join(root_path, dir)) ? "exists" : "create" puts " #{action} #{File.join(root, dir)}" if action == "create" FileUtils.mkdir_p(File.join(root_path, dir)) end end # Create a blank layout file create_template('layouts/default.erb', 'layout.erb', root, root_path) # Config file create_template('config/plate.yml', 'config.yml', root, root_path) # Index page create_template('content/index.md', 'index.md', root, root_path) puts "New site generated!" end def run_post_command title = args.size == 0 ? "" : args[0] slug = title.parameterize date = Time.now # if there are any post defaults in the config file, use those as the default options if builder.config.has_key?(:post_defaults) options.reverse_merge!(builder.config[:post_defaults]) end category = options[:category] ? "\ncategory: #{options[:category]}" : "" layout = options[:layout] ? "\nlayout: #{options[:layout]}" : "" filename = File.join(self.source, 'posts', date.strftime('%Y/%m'), "#{date.strftime('%Y-%m-%d')}-#{slug}.md") content = %Q(---\ntitle: "#{title}"\ndate: #{date.strftime('%Y-%m-%d %H:%M:%S')}#{category}#{layout}\ntags: []\n\n# #{title}\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.) FileUtils.mkdir_p(File.dirname(filename)) File.open(filename, 'w') { |f| f.write(content) } puts "New post file added [#{filename}]" end def create_template(path, template, root, root_path) action = File.exist?(File.join(root_path, path)) ? "exists" : "create" puts " #{action} #{File.join(root, path)}" if action == "create" template_root = File.expand_path(File.join(File.dirname(__FILE__), '..', 'templates')) contents = File.read(File.join(template_root, template)) unless contents.to_s.blank? File.open(File.join(root_path, path), 'w') { |f| f.write(contents) } end end end end end