# hx/commandline - Commandline interface for Hx # # Copyright (c) 2009-2010 MenTaLguY # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'hx' require 'ostruct' require 'optparse' require 'pathname' require 'tempfile' require 'webrick' require 'rack' require 'rack/mock' require 'rack/handler/webrick' require 'hx/rack/application' module Hx module CLI DEFAULT_CONFIG_FILENAME = "config.hx" def self.main(*args) options = OpenStruct.new options.config_file = nil OptionParser.new do |opts| opts.banner = < port || 0}) real_port = server.config[:Port] base_url = "http://localhost:#{real_port}/" server.logger.info "Serving on #{base_url}" config_file = site.options[:config_file] mtime = nil site_app = nil app = Proc.new { |env| unless site_app and File.mtime(config_file) == mtime # reload the site/config, folding in the new base URL site = Hx::Site.load_file(config_file, :base_url => base_url) mtime = File.mtime(config_file) site_app = Hx::Rack::Application.new(site, site.options) end site_app.call(env) } # force application load Rack::MockRequest.new(app).get("/") server.mount('/', ::Rack::Handler::WEBrick, app) %w(INT TERM).each { |s| trap(s) { server.shutdown } } server.start end def self.cmd_regen(site) do_gen(site, false) end def self.cmd_upgen(site) do_gen(site, true) end def self.do_gen(site, update_only) output_dir = Hx.get_pathname(site.options, :output_dir) Hx.cache_scope do site.each_entry(Path::ALL) do |path, entry| pathname = output_dir + path content = entry['content'] if update_only update_time = entry['updated'] else update_time = nil end written = Hx.refresh_file(pathname, content, update_time, entry['executable']) puts "===> #{path}" if written end end end def self.cmd_editup(site, entry_spec) cmd_edit(site, entry_spec) cmd_upgen(site) end def self.cmd_postup(site, entry_spec) cmd_post(site, entry_spec) cmd_upgen(site) end def self._parse_entry_spec(site, entry_spec) source_name, path = entry_spec.split(':', 2) source = site.sources[source_name] raise ArgumentError, "No such source #{source_name}" unless source return source, path end def self.parse_entry_spec(site, entry_spec) source, path = _parse_entry_spec(site, entry_spec) raise "Invalid entry specification #{entry_spec}" unless path return source, path end def self.parse_entry_pattern(site, entry_pattern) source, pattern = _parse_entry_spec(site, entry_pattern) pattern = "**" unless pattern selector = Hx::Path.parse_pattern(pattern) return source, selector end def self.cmd_edit(site, entry_spec) source, path = parse_entry_spec(site, entry_spec) do_edit(site, source, path, nil) end def self.cmd_post(site, entry_spec) source, path = parse_entry_spec(site, entry_spec) prototype = { 'title' => Hx.make_default_title(site.options, path), 'author' => Hx.get_default_author(site.options), 'content' => "" } do_edit(site, source, path, prototype) end def self.cmd_list(site, entry_spec) source, selector = parse_entry_pattern(site, entry_spec) source.each_entry_path(selector) { |path| puts path } end def self.do_edit(site, source, path, prototype) catch(:unchanged) do begin tempfile = Tempfile.new('hx-entry') original_text = nil loop do begin source.edit_entry(path, prototype) do |text| unless original_text File.open(tempfile.path, 'w') { |s| s << text } original_text = text end # TODO: deal with conflict if text != original_text editor = ENV['EDITOR'] || 'vi' system(editor, tempfile.path) new_text = File.open(tempfile.path, 'r') { |s| s.read } throw(:unchanged) if new_text == text new_text end break rescue Exception => e $stderr.puts e $stderr.print "Edit failed; retry? [Yn] " $stderr.flush response = $stdin.gets.strip raise unless response =~ /^y/i end end ensure tempfile.unlink end end end end end