updated on Sat Jan 21 20:03:50 UTC 2012
[aur-mirror.git] / rmate / rmate.rb
blob4c750d8ec7e8f11ae580c037a79344b7377ca6b3
1 #!/usr/bin/env ruby
2 # encoding: UTF-8
4 $VERBOSE = true                         # -w
5 $KCODE   = "U" if RUBY_VERSION < "1.9"  # -KU
7 require 'optparse'
8 require 'socket'
9 require 'fileutils'
11 VERSION_STRING = 'rmate version 1.3 (2011-10-18)'
13 class Settings
14   attr_accessor :host, :port, :wait, :force, :verbose
16   def initialize
17     @host, @port = 'localhost', 52698
19     @host = ENV['RMATE_HOST'].to_s if ENV.has_key? 'RMATE_HOST'
20     @port = ENV['RMATE_PORT'].to_i if ENV.has_key? 'RMATE_PORT'
22     if @host == 'auto' and (conn = ENV['SSH_CONNECTION'])
23       @host = conn.split(' ').first
24     end
26     @wait    = false
27     @force   = false
28     @verbose = false
30     read_disk_settings
31     parse_cli_options
32   end
34   def read_disk_settings
35     # TODO
36   end
38   def parse_cli_options
39     OptionParser.new do |o|
40       o.on(           '--host=name',       "Connect to host.", "Use 'auto' to detect the host from SSH.", "Defaults to #{@host}.") { |v| @host    = v          }
41       o.on('-p',      '--port=#', Integer, "Port number to use for connection.", "Defaults to #{@port}.")                       { |v| @port    = v          }
42       o.on('-w',      '--[no-]wait',       'Wait for file to be closed by TextMate.')                                        { |v| @wait    = v          }
43       o.on('-f',      '--force',           'Open even if the file is not writable.')                                         { |v| @force   = v          }
44       o.on('-v',      '--verbose',         'Verbose logging messages.')                                                      { |v| @verbose = v          }
45       o.on_tail('-h', '--help',            'Show this message.')                                                             { puts o; exit              }
46       o.on_tail(      '--version',         'Show version.')                                                                  { puts VERSION_STRING; exit }
47       o.parse!
48     end
49   end
50 end
52 class Command
53    def initialize(name)
54      @command   = name
55      @variables = {}
56      @data      = nil
57      @size      = nil
58    end
60    def []=(name, value)
61      @variables[name] = value
62    end
64    def read_file(path)
65      @size = File.size(path)
66      @data = File.open(path, "rb") { |io| io.read(@size) }
67    end
69    def send(socket)
70      socket.puts @command
71      @variables.each_pair do |name, value|
72        value = 'yes' if value === true
73        socket.puts "#{name}: #{value}"
74      end
75      if @data
76        socket.puts "data: #{@size}"
77        socket.puts @data
78      end
79      socket.puts
80    end
81 end
83 def handle_save(socket, variables, data)
84   path = variables["token"]
85   $stderr.puts "Saving #{path}" if $settings.verbose
86   begin
87     FileUtils.cp(path, "#{path}~") if File.exist? path
88     File.open(path, 'wb') { |file| file << data }
89     File.unlink("#{path}~")        if File.exist? "#{path}~"
90   rescue
91     # TODO We probably want some way to notify the server app that the save failed
92     $stderr.puts "Save failed! #{$!}" if $settings.verbose
93   end
94 end
96 def handle_close(socket, variables, data)
97   path = variables["token"]
98   $stderr.puts "Closed #{path}" if $settings.verbose
99 end
101 def handle_cmd(socket)
102   cmd = socket.readline.chomp
104   variables = {}
105   data = ""
107   while line = socket.readline.chomp
108     break if line.empty?
109     name, value     = line.split(': ', 2)
110     variables[name] = value
111     data << socket.read(value.to_i) if name == "data"
112   end
113   variables.delete("data")
115   case cmd
116   when "save"   then handle_save(socket, variables, data)
117   when "close"  then handle_close(socket, variables, data)
118   else          abort "Received unknown command “#{cmd}”, exiting."
119   end
122 def connect_and_handle_cmds(host, port, cmds)
123   socket = TCPSocket.new(host, port)
124   server_info = socket.readline.chomp
125   $stderr.puts "Connect: ‘#{server_info}’" if $settings.verbose
127   cmds.each { |cmd| cmd.send(socket) }
129   socket.puts "."
130   handle_cmd(socket) while !socket.eof?
131   socket.close
132   $stderr.puts "Done" if $settings.verbose
135 ## MAIN
137 $settings = Settings.new
139 ## Parse arguments.
140 cmds = []
141 ARGV.each do |path|
142   abort "File #{path} is not writable! Use -f/--force to open anyway." unless $settings.force or File.writable? path or not File.exists? path
143   $stderr.puts "File #{path} is not writable. Opening anyway." if not File.writable? path and File.exists? path and $settings.verbose
144   cmd                 = Command.new("open")
145   cmd['display-name'] = "#{Socket.gethostname}:#{path}"
146   cmd['real-path']    = File.expand_path(path)
147   cmd['data-on-save'] = true
148   cmd['re-activate']  = true
149   cmd['token']        = path
150   cmd.read_file(path)               if File.exist? path
151   cmd['data']         = "0"     unless File.exist? path
152   cmds << cmd
155 unless $settings.wait
156   pid = fork do
157     connect_and_handle_cmds($settings.host, $settings.port, cmds)
158   end
159   Process.detach(pid)
160 else
161   connect_and_handle_cmds($settings.host, $settings.port, cmds)