tkar 0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ require 'socket'
2
+ require 'thread'
3
+
4
+ module Tkar
5
+ module MessageStream
6
+ # Get a bidirectional stream for sending and receiving Tkar
7
+ # protocol messages in either binary or ascii format.
8
+ def self.for(argv, opts = {})
9
+ binary, client = opts["b"], opts["c"]
10
+ (binary ? Binary : Ascii).new(opts, *get_fds(argv, client))
11
+ end
12
+
13
+ def self.get_fds argv, client
14
+ case argv.size
15
+ when 0
16
+ [$stdin, $stdout]
17
+ when 1
18
+ case argv[0]
19
+ when /^\d+$/
20
+ if client
21
+ [TCPSocket.new("127.0.0.1", Integer(argv[0]))]
22
+ else
23
+ server = TCPServer.new("127.0.0.1", Integer(argv[0]))
24
+ flag = Socket.do_not_reverse_lookup
25
+ Socket.do_not_reverse_lookup = false
26
+ port = server.addr[1]
27
+ Socket.do_not_reverse_lookup = flag
28
+ puts "listening on port #{port}"
29
+ [server.accept]
30
+ end
31
+ else
32
+ if client
33
+ [UNIXSocket.new(argv[0])]
34
+ else
35
+ [UNIXServer.new(argv[0]).accept]
36
+ end
37
+ end
38
+ when 2
39
+ case argv[1]
40
+ when /^\d+$/
41
+ if client
42
+ [TCPSocket.new(argv[0], Integer(argv[1]))]
43
+ else
44
+ [TCPServer.new(argv[0], Integer(argv[1])).accept]
45
+ end
46
+ else
47
+ raise "Bad arguments--second arg must be port: #{argv.inspect}"
48
+ end
49
+ else
50
+ raise "Too many arguments: #{argv.inspect}"
51
+ end
52
+ end
53
+
54
+ class StreamClosed < IOError; end
55
+
56
+ class Base
57
+ def initialize(opts, fd_in, fd_out = fd_in)
58
+ @flip = opts["flip"]
59
+ @radians = opts["radians"]
60
+ @verbose = opts["v"]
61
+
62
+ @fd_in, @fd_out = fd_in, fd_out
63
+ @fd_in_stack = []
64
+ @fd_out_mutex = Mutex.new
65
+ end
66
+ end
67
+
68
+ # Translate ascii command text to ruby method calls.
69
+ class Ascii < Base
70
+ NORMALIZE = {}
71
+
72
+ %w{ a>dd d>el|delete m>ove>to r>ot|rotate p>ar>am s>h>ape u>p>date title
73
+ background|bg height width zoom_to|zoom view_at|view view_id wait
74
+ follow done bound>s load exit delete_all scale>_obj echo window_xy
75
+ }.each do |s|
76
+ alts = s.split("|")
77
+ cmd = alts.first.delete(">")
78
+ alts.each do |alt|
79
+ parts = alt.split(">")
80
+ parts.inject("") do |prefix, part|
81
+ prefix << part
82
+ NORMALIZE[prefix] = cmd
83
+ prefix
84
+ end
85
+ end
86
+ end
87
+
88
+ def conv_param(s)
89
+ s.slice!(/\.0+$/) # so that floats can be used for colors
90
+ (Integer(s) rescue Float(s)) rescue s
91
+ end
92
+
93
+ def flip_Float(s)
94
+ @flip ? -Float(s) : Float(s)
95
+ end
96
+
97
+ def conv_angle(s)
98
+ r = flip_Float(s)
99
+ @radians ? r : r * DEGREES_TO_RADIANS
100
+ end
101
+
102
+ ARG_CONVERSION = {
103
+ "add" => [nil, :Integer, nil, :Integer,
104
+ :Float, :flip_Float, :conv_angle, :conv_param],
105
+ "del" => [:Integer],
106
+ "moveto" => [:Integer, :Float, :flip_Float],
107
+ "rot" => [:Integer, :conv_angle],
108
+ "param" => [:Integer, :Integer, :conv_param], ## see below
109
+ "shape" => [nil, nil],
110
+ "update" => [],
111
+ "title" => [], ## see below
112
+ "background" => [:conv_param],
113
+ "height" => [:Float],
114
+ "width" => [:Float],
115
+ "zoom_to" => [:Float],
116
+ "view_at" => [:Float, :flip_Float],
117
+ "view_id" => [:Integer],
118
+ "wait" => [:Float],
119
+ "follow" => [:Integer],
120
+ "done" => [],
121
+ "bounds" => [:Float, :flip_Float, :Float, :flip_Float],
122
+ "load" => [], ## see below
123
+ "exit" => [],
124
+ "delete_all" => [],
125
+ "scale_obj" => [:Integer, :Float, :Float],
126
+ "echo" => [], ## see below
127
+ "window_xy" => [:Integer, :Integer],
128
+ }
129
+
130
+ def get_line
131
+ line = @fd_in.gets
132
+ while line == nil and not @fd_in_stack.empty?
133
+ @fd_in = @fd_in_stack.pop
134
+ line = @fd_in.gets
135
+ end
136
+ if line
137
+ while line =~ /(.*)\\\r?$/ # to allow continuation of long lines
138
+ next_line = @fd_in.gets
139
+ break unless next_line
140
+ line = $1 + next_line
141
+ end
142
+ end
143
+ line
144
+ end
145
+
146
+ class TryAgain < StandardError; end
147
+
148
+ # Returns next command in pipe, in form <tt>[:meth, arg, arg, ...]</tt>.
149
+ def get_cmd
150
+ begin
151
+ cmdline = get_line
152
+ raise StreamClosed, "Session ended" unless cmdline ## ?
153
+ end while cmdline =~ /^\s*(#|$)/
154
+ parse_cmd(cmdline)
155
+ rescue TryAgain
156
+ retry
157
+ rescue SystemCallError => ex
158
+ $stderr.puts ex.class, ex.message
159
+ raise StreamClosed, "Session ended"
160
+ end
161
+
162
+ def parse_cmd cmdline
163
+ $stderr.puts cmdline if @verbose
164
+ cmd, *args = cmdline.split
165
+ cmd = NORMALIZE[cmd] || (raise ArgumentError, "Bad command: #{cmd}")
166
+
167
+ case cmd
168
+ when "param"
169
+ return [cmd, Integer(args.shift), Integer(args.shift),
170
+ conv_param(args.join(" "))]
171
+ ## hacky, and loses multiple spaces
172
+ when "title"
173
+ return [cmd, args.join(" ")]
174
+ when "echo"
175
+ str = args.join(" ")
176
+ ## hacky, and loses multiple spaces
177
+ put_msg(str)
178
+ raise TryAgain # hm...
179
+ when "load"
180
+ filename = args.join(" ")
181
+ ## hacky, and loses multiple spaces
182
+ begin
183
+ new_fd_in = File.open(filename)
184
+ rescue Errno::ENOENT # if not absolute, try local
185
+ raise unless @fd_in_dir
186
+ new_fd_in = File.open(File.join(@fd_in_dir, filename))
187
+ end
188
+ @fd_in_dir ||= File.dirname(new_fd_in.path)
189
+ @fd_in_stack.push(@fd_in)
190
+ @fd_in = new_fd_in
191
+ raise TryAgain # hm...
192
+ end
193
+
194
+ conv = ARG_CONVERSION[cmd]
195
+ unless conv
196
+ raise "No argument conversion for command #{cmd.inspect}"
197
+ end
198
+ i = -1; last_i = conv.length - 1
199
+ args.map! do |arg|
200
+ i += 1 unless i == last_i # keep using the last conversion thereafter
201
+ (c = conv[i]) ? send(c, arg) : arg
202
+ end
203
+ [cmd, *args]
204
+ end
205
+
206
+ def put_msg(msg)
207
+ @fd_out_mutex.synchronize do ## why necessary?
208
+ @fd_out.puts msg
209
+ end
210
+ @fd_out.flush
211
+ rescue Errno::ECONNABORTED
212
+ end
213
+ end
214
+
215
+ # Translate binary command data to ruby method calls.
216
+ class Binary < Base
217
+ def get_cmd
218
+ lendata = @fd_in.recv(4)
219
+ raise "Session ended" if lendata.empty?
220
+
221
+ len = lendata.unpack("N")
222
+ if len < 4
223
+ raise ArgumentError, "Input too short: #{len}"
224
+ end
225
+ if len > 10000
226
+ raise ArgumentError, "Input too long: #{len}"
227
+ end
228
+
229
+ msg = ""
230
+ part = nil
231
+ while (delta = len - msg.length) > 0 and (part = @fd_in.recv(delta))
232
+ if part.length == 0
233
+ raise \
234
+ "Peer closed socket before finishing message --" +
235
+ " received #{msg.length} of #{len} bytes:\n" +
236
+ msg[0..99].unpack("H*")[0] + "..."
237
+ end
238
+ msg << part
239
+ end
240
+
241
+ raise StreamClosed, "Session ended" if msg.empty?
242
+ parse_cmd(msg)
243
+ end
244
+
245
+ CMD_DATA = {
246
+ 1 => ["add", "Z* N Z* N g3 N*"],
247
+ 2 => ["del", "N"],
248
+ 3 => ["moveto", "N g2"],
249
+ 4 => ["rot", "N g"],
250
+ 5 => ["param", "N n N"], ## fix to allow strings
251
+ 6 => ["shape", "Z* Z*"],
252
+ 7 => ["update", ""],
253
+ 8 => ["title", "Z*"],
254
+ 9 => ["background", "N"],
255
+ 10 => ["height", "g"],
256
+ 11 => ["width", "g"],
257
+ 12 => ["zoom_to", "g"],
258
+ 13 => ["view_at", "g2"],
259
+ 14 => ["view_id", "N"],
260
+ 15 => ["wait", "g"],
261
+ 16 => ["follow", "N"],
262
+ 17 => ["done", ""],
263
+ 18 => ["bounds", "NNNN"],
264
+ 19 => ["load", "Z*"],
265
+ 20 => ["exit", ""],
266
+ 21 => ["delete_all", ""],
267
+ 22 => ["scale_obj", "N g2"],
268
+ 23 => ["echo", "Z*"],
269
+ 24 => ["window_xy", "NN"],
270
+ }
271
+
272
+ def parse_cmd msg
273
+ cmd = msg[0..1].unpack("n")
274
+ cmd, fmt = CMD_DATA[cmd]
275
+ [cmd, *msg[2..-1].unpack(fmt)]
276
+ end
277
+
278
+ def put_msg(msg)
279
+ @fd_out.puts msg ## assume output ascii for now
280
+ @fd_out.flush
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,174 @@
1
+ # Ruby license. Copyright (C)2004-2009 Joel VanderWerf.
2
+ # Contact mailto:vjoel@users.sourceforge.net.
3
+ #
4
+ # A lightweight, non-drifting, self-correcting timer. Average error is bounded
5
+ # as long as, on average, there is enough time to complete the work done, and
6
+ # the timer is checked often enough. It is lightweight in the sense that no
7
+ # threads are created. Can be used either as an internal iterator (Timer.every)
8
+ # or as an external iterator (Timer.new). Obviously, the GC can cause a
9
+ # temporary slippage.
10
+ #
11
+ # Simple usage:
12
+ #
13
+ # require 'timer'
14
+ #
15
+ # Timer.every(0.1, 0.5) { |elapsed| puts elapsed }
16
+ #
17
+ # timer = Timer.new(0.1)
18
+ # 5.times do
19
+ # puts timer.elapsed
20
+ # timer.wait
21
+ # end
22
+ #
23
+ class Timer
24
+ # Yields to the supplied block every +period+ seconds. The value yielded is
25
+ # the total elapsed time (an instance of +Time+). If +expire+ is given, then
26
+ # #every returns after that amount of elapsed time.
27
+ def Timer.every(period, expire = nil)
28
+ target = time_start = Time.now
29
+ loop do
30
+ elapsed = Time.now - time_start
31
+ break if expire and elapsed > expire
32
+ yield elapsed
33
+ target += period
34
+ error = target - Time.now
35
+ sleep error if error > 0
36
+ end
37
+ end
38
+
39
+ # Make a Timer that can be checked when needed, using #wait or #if_ready. The
40
+ # advantage over Timer.every is that the timer can be checked on separate
41
+ # passes through a loop.
42
+ def initialize(period = 1)
43
+ @period = period
44
+ restart
45
+ end
46
+
47
+ attr_accessor :period
48
+
49
+ # Call this to restart the timer after a period of inactivity (e.g., the user
50
+ # hits the pause button, and then hits the go button).
51
+ def restart
52
+ @target = @time_start = Time.now
53
+ end
54
+
55
+ # Time on timer since instantiation or last #restart.
56
+ def elapsed
57
+ Time.now - @time_start
58
+ end
59
+
60
+ # Wait for the next cycle, if time remains in the current cycle. Otherwise,
61
+ # return immediately to caller.
62
+ def wait(per = nil)
63
+ @target += per || @period
64
+ error = @target - Time.now
65
+ sleep error if error > 0
66
+ true
67
+ end
68
+
69
+ # Yield to the block if no time remains in cycle. Otherwise, return
70
+ # immediately to caller
71
+ def if_ready
72
+ error = @target + @period - Time.now
73
+ if error <= 0
74
+ @target += @period
75
+ elapsed = Time.now - @time_start
76
+ yield elapsed
77
+ end
78
+ end
79
+ end
80
+
81
+ if __FILE__ == $0
82
+
83
+ require 'test/unit'
84
+
85
+ # These tests may not work on a slow machine or heavily loaded system; try
86
+ # adjusting FUDGE.
87
+ class Test_Timer < Test::Unit::TestCase # :nodoc:
88
+
89
+ STEPS = 100
90
+ PERIOD = 0.01
91
+ FUDGE = 0.01 # a constant independent of period.
92
+
93
+ def generic_test(steps = STEPS, period = PERIOD, fudge = FUDGE)
94
+ max_sleep = period/2
95
+
96
+ start_time = Time.now
97
+ yield steps, period, max_sleep
98
+ finish_time = Time.now
99
+
100
+ assert_in_delta(
101
+ start_time.to_f + steps*period,
102
+ finish_time.to_f,
103
+ period + fudge,
104
+ "delta = #{finish_time.to_f - (start_time.to_f + steps*period)}"
105
+ )
106
+ end
107
+
108
+ def test_every
109
+ generic_test do |steps, period, max_sleep|
110
+ Timer.every period do |elapsed|
111
+ s = rand()*max_sleep
112
+ #puts "#{elapsed} elapsed; sleeping #{s}"
113
+ sleep(s)
114
+ steps -= 1
115
+ break if steps == 0
116
+ end
117
+ end
118
+ end
119
+
120
+ def test_every_with_expire
121
+ generic_test do |steps, period, max_sleep|
122
+ Timer.every period, period*steps do
123
+ s = rand()*max_sleep
124
+ #puts "#{elapsed} elapsed; sleeping #{s}"
125
+ sleep(s)
126
+ end
127
+ end
128
+ end
129
+
130
+ def test_wait
131
+ generic_test do |steps, period, max_sleep|
132
+ timer = Timer.new(period)
133
+ steps.times do
134
+ s = rand()*max_sleep
135
+ #puts "#{timer.elapsed} elapsed; sleeping #{s}"
136
+ sleep(s)
137
+ timer.wait
138
+ end
139
+ end
140
+ end
141
+
142
+ def test_wait_per
143
+ offset = PERIOD / 2
144
+ generic_test(STEPS, PERIOD, FUDGE + offset) do |steps, period, max_sleep|
145
+ timer = Timer.new(period)
146
+ steps.times do |i|
147
+ s = rand()*max_sleep
148
+ #puts "#{timer.elapsed} elapsed; sleeping #{s}"
149
+ sleep(s)
150
+ timer.wait(i%2==0 ? period + offset : period - offset)
151
+ end
152
+ end
153
+ end
154
+
155
+ def test_if_ready
156
+ generic_test do |steps, period, max_sleep|
157
+ timer = Timer.new(period)
158
+ catch :done do
159
+ loop do
160
+ timer.if_ready do |elapsed|
161
+ s = rand()*max_sleep
162
+ #puts "#{elapsed} elapsed; sleeping #{s}"
163
+ sleep(s)
164
+ steps -= 1
165
+ throw :done if steps == 0
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ end