Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

executable file 196 lines (157 sloc) 4.713 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
#!/usr/bin/env ruby
require 'logger'
require 'optparse'

require 'readline'
require 'shellwords'

require 'rubygems'
require 'einhorn'

module Einhorn
  class EinhornSH

    class EinhornSHExit < StandardError
    end

    def initialize(path_to_socket)
      @path_to_socket = path_to_socket
      @request_id = 0
      reconnect
    end

    def send_command(hash)
      begin
        @client.send_command(hash)
        while response = @client.receive_message
          if response.kind_of?(Hash)
            yield response['message']
            return unless response['wait']
          else
            puts "Invalid response type #{response.class}: #{response.inspect}"
          end
        end
      rescue Errno::EPIPE => e
        emit("einhornsh: Error communicating with Einhorn: #{e} (#{e.class})")
        emit("einhornsh: Attempting to reconnect...")
        reconnect

        retry
      end
    end

    def run
      emit("Enter 'help' if you're not sure what to do.")
      emit
      emit('Type "quit" or "exit" to quit at any time')

      while command_line_sequence = Readline.readline('> ', true)
        run_command_line_sequence(command_line_sequence)
      end
    end

    def run_command_line_sequence(command_line_sequence)
      command_lines = command_line_sequence.split(";")
      command_lines.each do |command_line|
        run_command_line(command_line)
      end
    end

    def run_command_line(command_line)
      command, args = parse_command_line(command_line)
      if command.nil?
        return
      elsif ['quit', 'exit'].include?(command)
        emit("Goodbye!")
        raise EinhornSHExit
      end
      send_command({'id' => request_id, 'command' => command, 'args' => args}) do |message|
        $stdout.puts message
        $stdout.flush
      end
    end

    def request_id
      @request_id += 1
    end

    def parse_command_line(command_line)
      command, *args = Shellwords.shellsplit(command_line)
      [command, args]
    end

    def reconnect
      begin
        @client = Einhorn::Client.for_path(@path_to_socket)
      rescue Errno::ENOENT => e
        # TODO: The exit here is a biit of a layering violation.
        Einhorn::EinhornSH.emit(<<EOF, true)
Could not connect to Einhorn master process:

#{e}

HINT: Are you sure you are running an Einhorn master? If so, you
should pass einhornsh the cmd_name (-c argument) provided to Einhorn.
EOF
        exit(1)
      end
      ehlo if interactive?
    end

    def ehlo
      send_command({'command' => "ehlo", 'user' => ENV['USER']}) do |message|
        emit(message)
      end
    end

    def self.emit(message=nil, force=false)
      $stderr.puts(message || '') if interactive? || force
    end

    def self.interactive?
      $stdin.isatty
    end

    def emit(*args)
      self.class.emit(*args)
    end

    def interactive?
      self.class.interactive?
    end
  end
end

def main
  options = {}
  optparse = OptionParser.new do |opts|
    opts.banner = "Usage: #{$0} [options] [cmd_name]

Welcome to Einhornsh: the Einhorn shell.

Pass the cmd_name of the Einhorn master you are connecting to either
as a positional argument or using `-c`. If you're running your Einhorn
with a `-d`, provide the same argument here."

    opts.on('-h', '--help', 'Display this message') do
      Einhorn::EinhornSH.emit(opts, true)
      exit(1)
    end

    opts.on('-c CMD_NAME', '--command-name CMD_NAME', 'Connect to the Einhorn master with this cmd_name') do |cmd_name|
      options[:cmd_name] = cmd_name
    end

    opts.on('-d PATH', '--socket-path PATH', 'Path to the Einhorn command socket') do |path|
      options[:socket_path] = path
    end

    opts.on('-e CMD_LINE_SEQ', '--execute CMD_LINE_SEQ', 'Execute this command outside of interactive mode') do |cmd_line_seq|
      options[:cmd_line_seq] = cmd_line_seq
    end

  end
  optparse.parse!

  if ARGV.length > 1
    Einhorn::EinhornSH.emit(optparse, true)
    return 1
  end

  Signal.trap("INT") do
    Einhorn::EinhornSH.emit
    exit(0)
  end

  path_to_socket = options[:socket_path]

  unless path_to_socket
    cmd_name = options[:cmd_name] || ARGV[0]
    path_to_socket = Einhorn::Command::Interface.default_socket_path(cmd_name)
  end

  sh = Einhorn::EinhornSH.new(path_to_socket)

  cmd_line_seq = options[:cmd_line_seq]

  begin
    cmd_line_seq ? sh.run_command_line_sequence(cmd_line_seq) : sh.run
  rescue Einhorn::EinhornSH::EinhornSHExit => e
    return 0
  end

  return 0
end

# Would be nice if this could be loadable rather than always
# executing, but when run under gem it's a bit hard to do so.
if true # $0 == __FILE__
  ret = main
  begin
    exit(ret)
  rescue TypeError
    exit(0)
  end
end
Something went wrong with that request. Please try again.