Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 162 lines (134 sloc) 4.923 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
#!/usr/bin/env ruby
# encoding: UTF-8

$VERBOSE = true # -w
$KCODE = "U" if RUBY_VERSION < "1.9" # -KU

require 'optparse'
require 'socket'
require 'fileutils'

VERSION_STRING = 'rmate version 1.3 (2011-10-18)'

class Settings
  attr_accessor :host, :port, :wait, :force, :verbose

  def initialize
    @host, @port = 'localhost', 52698

    @host = ENV['RMATE_HOST'].to_s if ENV.has_key? 'RMATE_HOST'
    @port = ENV['RMATE_PORT'].to_i if ENV.has_key? 'RMATE_PORT'

    if @host == 'auto' and (conn = ENV['SSH_CONNECTION'])
      @host = conn.split(' ').first
    end

    @wait = false
    @force = false
    @verbose = false

    read_disk_settings
    parse_cli_options
  end

  def read_disk_settings
    # TODO
  end

  def parse_cli_options
    OptionParser.new do |o|
      o.on( '--host=name', "Connect to host.", "Use 'auto' to detect the host from SSH.", "Defaults to #{@host}.") { |v| @host = v }
      o.on('-p', '--port=#', Integer, "Port number to use for connection.", "Defaults to #{@port}.") { |v| @port = v }
      o.on('-w', '--[no-]wait', 'Wait for file to be closed by TextMate.') { |v| @wait = v }
      o.on('-f', '--force', 'Open even if the file is not writable.') { |v| @force = v }
      o.on('-v', '--verbose', 'Verbose logging messages.') { |v| @verbose = v }
      o.on_tail('-h', '--help', 'Show this message.') { puts o; exit }
      o.on_tail( '--version', 'Show version.') { puts VERSION_STRING; exit }
      o.parse!
    end
  end
end

class Command
   def initialize(name)
     @command = name
     @variables = {}
     @data = nil
     @size = nil
   end

   def []=(name, value)
     @variables[name] = value
   end

   def read_file(path)
     @size = File.size(path)
     @data = File.open(path, "rb") { |io| io.read(@size) }
   end

   def send(socket)
     socket.puts @command
     @variables.each_pair do |name, value|
       value = 'yes' if value === true
       socket.puts "#{name}: #{value}"
     end
     if @data
       socket.puts "data: #{@size}"
       socket.puts @data
     end
     socket.puts
   end
end

def handle_save(socket, variables, data)
  path = variables["token"]
  $stderr.puts "Saving #{path}" if $settings.verbose
  begin
    FileUtils.cp(path, "#{path}~") if File.exist? path
    File.open(path, 'wb') { |file| file << data }
    File.unlink("#{path}~") if File.exist? "#{path}~"
  rescue
    # TODO We probably want some way to notify the server app that the save failed
    $stderr.puts "Save failed! #{$!}" if $settings.verbose
  end
end

def handle_close(socket, variables, data)
  path = variables["token"]
  $stderr.puts "Closed #{path}" if $settings.verbose
end

def handle_cmd(socket)
  cmd = socket.readline.chomp

  variables = {}
  data = ""

  while line = socket.readline.chomp
    break if line.empty?
    name, value = line.split(': ', 2)
    variables[name] = value
    data << socket.read(value.to_i) if name == "data"
  end
  variables.delete("data")

  case cmd
  when "save" then handle_save(socket, variables, data)
  when "close" then handle_close(socket, variables, data)
  else abort "Received unknown command “#{cmd}”, exiting."
  end
end

def connect_and_handle_cmds(host, port, cmds)
  socket = TCPSocket.new(host, port)
  server_info = socket.readline.chomp
  $stderr.puts "Connect: ‘#{server_info}’" if $settings.verbose

  cmds.each { |cmd| cmd.send(socket) }

  socket.puts "."
  handle_cmd(socket) while !socket.eof?
  socket.close
  $stderr.puts "Done" if $settings.verbose
end

## MAIN

$settings = Settings.new

## Parse arguments.
cmds = []
ARGV.each do |path|
  abort "File #{path} is not writable! Use -f/--force to open anyway." unless $settings.force or File.writable? path or not File.exists? path
  $stderr.puts "File #{path} is not writable. Opening anyway." if not File.writable? path and File.exists? path and $settings.verbose
  cmd = Command.new("open")
  cmd['display-name'] = "#{Socket.gethostname}:#{path}"
  cmd['real-path'] = File.expand_path(path)
  cmd['data-on-save'] = true
  cmd['re-activate'] = true
  cmd['token'] = path
  cmd.read_file(path) if File.exist? path
  cmd['data'] = "0" unless File.exist? path
  cmds << cmd
end

unless $settings.wait
  pid = fork do
    connect_and_handle_cmds($settings.host, $settings.port, cmds)
  end
  Process.detach(pid)
else
  connect_and_handle_cmds($settings.host, $settings.port, cmds)
end
Something went wrong with that request. Please try again.