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('--destination [PATH]', '-d', 'Set the destination directory for this build.') do |d| @destination = File.expand_path(d) 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 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')}\ntags: []\n\n# #{title}\n\n) 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