oterm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c57083633eb77e165e8331ee3b28ce62a2a25c41
4
+ data.tar.gz: db9c45d79b15fa85e8c5a82c413e29108e7e1d41
5
+ SHA512:
6
+ metadata.gz: 59fb9a821cde9ba1c4974ecf18fa32cef38a1db7d3c187b8a274273c4459d225be3f308d4132cb4f4f126751d1a75eb786869542ffedb88f830ed63f2855d6df
7
+ data.tar.gz: 5702073e0ce8e0afe661019d0da7f8c42a845cbd2d9c09297332290996217b954ad7b063b944d4d1d13575225c0c55fef8ab941d9db8a1720e4c8369d224ac5c
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Peter Ohler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ oterm
2
+ =====
3
+
4
+ Operations Terminal
5
+
6
+ # OTerm gem
7
+
8
+ A VT100 terminal (mostly) server that can be placed in an application to allow
9
+ telnet access to exposed features. Some of the features includes are:
10
+
11
+ - Line editing
12
+
13
+ - VT100 support with graphics and color
14
+
15
+ - Configurable help and command registration
16
+
17
+ - Auto completion
18
+
19
+ - History
20
+
21
+ The driver for writing this gem was to be able to control, inspect, and debug
22
+ multi-threaded applications while the application is running. Look for it's use
23
+ soon in the ODisk application and later in a more interesting project.
24
+
25
+ ## <a name="installation">Installation</a>
26
+ gem install oterm
27
+
28
+ ## <a name="documentation">Documentation</a>
29
+
30
+ *Documentation*: http://www.ohler.com/oterm
31
+
32
+ ## <a name="source">Source</a>
33
+
34
+ *GitHub* *repo*: https://github.com/ohler55/oterm
35
+
36
+ *RubyGems* *repo*: https://rubygems.org/gems/oterm
37
+
38
+ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announcements and news about the Oj gem.
39
+
40
+ ## <a name="build_status">Build Status</a>
41
+
42
+ [![Build Status](https://secure.travis-ci.org/ohler55/oterm.png?branch=master)](http://travis-ci.org/ohler55/oterm)
43
+
44
+ ### Current Release 1.0.0 - December 22, 2013
45
+
46
+ - First release. There will be rough edges.
47
+
48
+ ## <a name="description">Description</a>
49
+
50
+ ## Examples
51
+
52
+ TBD for now, look for a sample in the test directory.
data/lib/oterm.rb ADDED
@@ -0,0 +1,13 @@
1
+
2
+ module OTerm
3
+ end # OTerm
4
+
5
+ require 'oterm/version'
6
+ require 'oterm/errors'
7
+ require 'oterm/telnet'
8
+ require 'oterm/vt100'
9
+ require 'oterm/output'
10
+ require 'oterm/listener'
11
+ require 'oterm/executor'
12
+ require 'oterm/server'
13
+ require 'oterm/mock100'
@@ -0,0 +1,10 @@
1
+
2
+ module OTerm
3
+ #
4
+ class CommandError < Exception
5
+ def initialize(cmd)
6
+ super("#{cmd} is not a valid command")
7
+ end
8
+ end # CommandError
9
+
10
+ end # OTerm
@@ -0,0 +1,117 @@
1
+
2
+ module OTerm
3
+
4
+ class Executor
5
+
6
+ def initialize()
7
+ @cmds = {}
8
+ register('help', self, :help, '[<command>] this help screen or help on a command',
9
+ %|Show help on a specific command or a list of all commands if a specific command is not specified.|)
10
+ register('shutdown', self, :shutdown, 'shuts down the application',
11
+ %|Shuts down the application.|)
12
+ register('close', self, :close, 'closes the connection',
13
+ %|Closes the connection to the application.|)
14
+ end
15
+
16
+ def greeting()
17
+ nil
18
+ end
19
+
20
+ def execute(cmd, listener)
21
+ name, args = cmd.split(' ', 2)
22
+ c = @cmds[name]
23
+ if nil == c
24
+ missing(cmd, listener)
25
+ return
26
+ end
27
+ c.target.send(c.op, listener, args)
28
+ end
29
+
30
+ def help(listener, arg=nil)
31
+ listener.out.pl()
32
+ if nil != arg
33
+ c = @cmds[arg]
34
+ if nil != c
35
+ listener.out.pl("#{arg} - #{c.summary}")
36
+ c.desc.each do |line|
37
+ listener.out.pl(" #{line}")
38
+ end
39
+ return
40
+ end
41
+ end
42
+ max = 1
43
+ @cmds.each do |name,cmd|
44
+ max = name.size if max < name.size
45
+ end
46
+ @cmds.each do |name,cmd|
47
+ listener.out.pl(" %1$*2$s - %3$s" % [name, -max, cmd.summary])
48
+ end
49
+ end
50
+
51
+ def close(listener, args)
52
+ listener.out.pl("Closing connection")
53
+ listener.close()
54
+ end
55
+
56
+ def shutdown(listener, args)
57
+ listener.out.pl("Shutting down")
58
+ listener.server.shutdown()
59
+ end
60
+
61
+ # This evaluates cmd as a Ruby expression. This is great for debugging but
62
+ # not a wise move for a public interface. To hide this just create a methid
63
+ # with the same name in the subclass of this one.
64
+ def missing(cmd, listener)
65
+ begin
66
+ result = "#{eval(cmd)}".split("\n")
67
+ rescue Exception => e
68
+ result = ["#{e.class}: #{e.message}"]
69
+ e.backtrace.each do |line|
70
+ break if line.include?('oterm/executor.rb')
71
+ result << "\t" + line
72
+ end
73
+ end
74
+ result.each do |line|
75
+ listener.out.pl(line)
76
+ end
77
+ end
78
+
79
+ def tab(cmd, listener)
80
+ comp = []
81
+ @cmds.each_key do |name|
82
+ comp << name if name.start_with?(cmd)
83
+ end
84
+ if 1 == comp.size
85
+ listener.move_col(1000)
86
+ listener.insert(comp[0][listener.buf.size..-1])
87
+ listener.out.prompt()
88
+ listener.out.p(listener.buf)
89
+ else
90
+ listener.out.pl()
91
+ comp.each do |name|
92
+ listener.out.pl(name)
93
+ end
94
+ listener.update_cmd(0)
95
+ end
96
+ end
97
+
98
+ def register(cmd, target, op, summary, desc)
99
+ @cmds[cmd] = Cmd.new(target, op, summary, desc)
100
+ end
101
+
102
+ class Cmd
103
+ attr_accessor :target
104
+ attr_accessor :op
105
+ attr_accessor :summary
106
+ attr_accessor :desc
107
+
108
+ def initialize(target, op, summary, desc)
109
+ @target = target
110
+ @op = op
111
+ @summary = summary
112
+ @desc = desc.split("\n")
113
+ end
114
+ end # Cmd
115
+
116
+ end # Executor
117
+ end # OTerm
@@ -0,0 +1,257 @@
1
+
2
+ module OTerm
3
+
4
+ class Listener
5
+
6
+ attr_accessor :server
7
+ attr_accessor :con
8
+ attr_accessor :executor
9
+ attr_accessor :buf
10
+ attr_accessor :history
11
+ attr_accessor :hp # history pointer
12
+ attr_accessor :out
13
+ attr_accessor :debug
14
+
15
+ def initialize(server, con, executor)
16
+ @debug = server.debug()
17
+ @server = server
18
+ @con = con
19
+ @executor = executor
20
+ @buf = ''
21
+ @kill_buf = nil
22
+ @history = []
23
+ @hp = 0
24
+ @out = Output.new(con)
25
+ @col = 0
26
+ @echo = false
27
+ @done = false
28
+
29
+ greeting = executor.greeting()
30
+ @out.pl(greeting) if nil != greeting
31
+
32
+ # initiate negotiations for single character mode and no echo
33
+ @out.p(Telnet.msg(Telnet::DO, Telnet::SGA) + Telnet.msg(Telnet::DO, Telnet::ECHO))
34
+
35
+ out.prompt()
36
+ while !@done
37
+ line = con.recv(100)
38
+ begin
39
+ len = line.size()
40
+ break if 0 == len
41
+ if @debug
42
+ line.each_byte { |x| print("#{x} ") }
43
+ plain = line.gsub(/./) { |c| c.ord < 32 || 127 <= c.ord ? '*' : c }
44
+ puts "[#{line.size()}] #{plain}"
45
+ end
46
+ # determine input type (telnet command, char mode, line mode)
47
+ o0 = line[0].ord()
48
+ case o0
49
+ when 255 # telnet command
50
+ process_telnet_cmd(line)
51
+ when 27 # escape, vt100 sequence
52
+ vt100_cmd(line)
53
+ when 13 # new line
54
+ exec_cmd(@buf)
55
+ when 0..12, 14..26, 28..31, 127 # other control character
56
+ process_ctrl_cmd(o0)
57
+ else
58
+ if 1 == len || (2 == len && "\000" == line[1]) # single char mode
59
+ @hp = 0
60
+ insert(line[0])
61
+ else # line mode
62
+ exec_cmd(line)
63
+ end
64
+ end
65
+ rescue Exception => e
66
+ puts "#{e.class}: #{e.message}"
67
+ e.backtrace.each { |bline| puts ' ' + bline }
68
+ end
69
+ end
70
+ end
71
+
72
+ def close()
73
+ @done = true
74
+ end
75
+
76
+ def process_telnet_cmd(line)
77
+ reply = ''
78
+ Telnet.parse(line).each do |v,f|
79
+ case f
80
+ when Telnet::ECHO
81
+ case v
82
+ when Telnet::WILL
83
+ @echo = false
84
+ reply += Telnet.msg(Telnet::WONT, f)
85
+ when Telnet::WONT
86
+ @echo = true
87
+ reply += Telnet.msg(Telnet::WILL, f)
88
+ end
89
+ when Telnet::SGA
90
+ case v
91
+ when Telnet::WILL
92
+ reply += Telnet.msg(Telnet::WILL, f)
93
+ when Telnet::WONT
94
+ reply += Telnet.msg(Telnet::WONT, f)
95
+ end
96
+ end
97
+ end
98
+ if 0 < reply.size
99
+ @con.print(reply)
100
+ else
101
+ # Done negotiating with telnet. Initiate negotiation for vt100 ANSI support.
102
+ @out.identify()
103
+ end
104
+ end
105
+
106
+ def vt100_cmd(line)
107
+ puts "*** vt100 command"
108
+ # TBD
109
+ end
110
+
111
+ def exec_cmd(cmd)
112
+ @hp = 0
113
+ cmd.strip!()
114
+ if 0 == cmd.size()
115
+ @out.pl()
116
+ @out.prompt()
117
+ @col = 0
118
+ return
119
+ end
120
+ @buf = ""
121
+ @out.pl()
122
+ @history << cmd if 0 < cmd.size() && (0 == @history.size() || @history[-1] != cmd)
123
+ executor.execute(cmd, self)
124
+ @out.prompt()
125
+ @col = 0
126
+ end
127
+
128
+ def process_ctrl_cmd(o)
129
+ case o
130
+ when 1 # ^a
131
+ move_col(-@col)
132
+ when 2 # ^b
133
+ move_col(-1)
134
+ when 4 # ^d
135
+ @hp = 0
136
+ if @col < @buf.size
137
+ @col += 1
138
+ delete_char()
139
+ end
140
+ when 5 # ^e
141
+ move_col(@buf.size() - @col)
142
+ when 6 # ^f
143
+ move_col(1)
144
+ when 8, 127 # backspace or delete
145
+ @hp = 0
146
+ delete_char()
147
+ when 9 # tab
148
+ @hp = 0
149
+ @executor.tab(@buf, self)
150
+ when 11 # ^k
151
+ @hp = 0
152
+ if @col < @buf.size()
153
+ @kill_buf = @buf[@col..-1]
154
+ blen = @buf.size()
155
+ @buf = @buf[0...@col]
156
+ update_cmd(blen)
157
+ end
158
+ when 14 # ^n
159
+ if 0 < @hp && @hp <= @history.size()
160
+ @hp -= 1
161
+ blen = @buf.size()
162
+ if 0 == @hp
163
+ @buf = ''
164
+ else
165
+ @buf = @history[-@hp]
166
+ end
167
+ update_cmd(blen)
168
+ end
169
+ when 16 # ^p
170
+ if @hp < @history.size()
171
+ @hp += 1
172
+ blen = @buf.size()
173
+ @buf = @history[-@hp]
174
+ update_cmd(blen)
175
+ end
176
+ when 21 # ^u
177
+ @hp -= 1
178
+ @buf = ''
179
+ when 25 # ^y
180
+ insert(@kill_buf)
181
+ end
182
+ end
183
+
184
+ def update_cmd(blen)
185
+ @out.cr()
186
+ @out.prompt()
187
+ @out.p(@buf)
188
+ # erase to end of line and come back
189
+ if @buf.size() < blen
190
+ dif = blen - @buf.size()
191
+ @out.p(' ' * dif + "\b" * dif)
192
+ end
193
+ @col = @buf.size
194
+ end
195
+
196
+ def move_col(dif)
197
+ if 0 > dif
198
+ while 0 > dif && 0 < @col
199
+ @col -= 1
200
+ @out.p("\b")
201
+ dif += 1
202
+ end
203
+ else
204
+ max = @buf.size
205
+ while 0 < dif && @col < max
206
+ @out.p(@buf[@col])
207
+ @col += 1
208
+ dif -= 1
209
+ end
210
+ end
211
+ end
212
+
213
+ def insert(str)
214
+ # TBD be smarter with vt100
215
+ if 0 == @col
216
+ @buf = str + @buf
217
+ @out.p("\r")
218
+ @out.prompt()
219
+ @out.p(@buf)
220
+ @out.p("\r")
221
+ @out.prompt()
222
+ @out.p(@buf[0...str.size])
223
+ elsif @buf.size <= @col
224
+ @buf << str
225
+ @out.pc(str)
226
+ @col = @buf.size
227
+ else
228
+ @buf = @buf[0...@col] + str + @buf[@col..-1]
229
+ @out.p("\r")
230
+ @out.prompt()
231
+ @out.p(@buf)
232
+ @out.p("\r")
233
+ @out.prompt()
234
+ @out.p(@buf[0...@col + str.size])
235
+ end
236
+ @col += str.size
237
+ end
238
+
239
+ def delete_char()
240
+ return if 0 == @col || 0 == @buf.size
241
+ if @buf.size <= @col
242
+ @buf.chop!()
243
+ @out.p("\x08 \x08")
244
+ else
245
+ @buf = @buf[0...@col - 1] + @buf[@col..-1]
246
+ @out.p("\r")
247
+ @out.prompt()
248
+ @out.p(@buf)
249
+ @out.p(" \r")
250
+ @out.prompt()
251
+ @out.p(@buf[0...@col - 1])
252
+ end
253
+ @col -= 1
254
+ end
255
+
256
+ end # Listener
257
+ end # OTerm
@@ -0,0 +1,11 @@
1
+
2
+ module OTerm
3
+
4
+ class Mock100
5
+
6
+ def initialize()
7
+
8
+ end
9
+
10
+ end # Mock100
11
+ end # OTerm
@@ -0,0 +1,31 @@
1
+
2
+ module OTerm
3
+
4
+ class Output < VT100
5
+ def initialize(con)
6
+ super(con)
7
+ end
8
+
9
+ def prompt()
10
+ @con.print("\r> ")
11
+ end
12
+
13
+ def p(str)
14
+ @con.print(str)
15
+ end
16
+
17
+ def pc(c)
18
+ @con.putc(c)
19
+ end
20
+
21
+ def pl(line='')
22
+ @con.puts(line + "\r")
23
+ end
24
+
25
+ def cr()
26
+ @con.print("\r")
27
+ end
28
+
29
+ end # Output
30
+ end # OTerm
31
+
@@ -0,0 +1,36 @@
1
+
2
+ require 'socket'
3
+
4
+ module OTerm
5
+
6
+ class Server
7
+
8
+ attr_accessor :acceptThread
9
+ attr_accessor :stop
10
+ attr_accessor :listeners
11
+ attr_accessor :debug
12
+
13
+ def initialize(executor, port=6060, debug=false)
14
+ @debug = debug
15
+ @stop = false
16
+ @listeners = []
17
+ @acceptThread = Thread.start() do
18
+ server = TCPServer.new(port)
19
+ while !stop do
20
+ Thread.start(server.accept()) do |con|
21
+ @listeners << Listener.new(self, con, executor)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def shutdown()
28
+ @acceptThread.exit()
29
+ end
30
+
31
+ def remove_listener(listener)
32
+ @listeners.delete(listener)
33
+ end
34
+
35
+ end # Server
36
+ end # OTerm
@@ -0,0 +1,37 @@
1
+
2
+ module OTerm
3
+
4
+ class Telnet
5
+ IAC = 255.chr
6
+
7
+ # verbs
8
+ WILL = 251.chr
9
+ WONT = 252.chr
10
+ DO = 253.chr
11
+ DONT = 254.chr
12
+
13
+ # features
14
+ BIN = 0.chr
15
+ ECHO = 1.chr
16
+ SGA = 3.chr # one character at a time
17
+
18
+ def self.msg(verb, feature)
19
+ [IAC, verb, feature].join('')
20
+ end
21
+
22
+ def self.parse(line)
23
+ msgs = []
24
+ v = nil
25
+ line.each_char do |c|
26
+ if nil == v
27
+ v = c unless IAC == c
28
+ else
29
+ msgs << [v, c]
30
+ v = nil
31
+ end
32
+ end
33
+ msgs
34
+ end
35
+
36
+ end # Telnet
37
+ end # OTerm
@@ -0,0 +1,4 @@
1
+ module OTerm
2
+ # Current version of the module.
3
+ VERSION = '1.0.0'
4
+ end
@@ -0,0 +1,282 @@
1
+
2
+ module OTerm
3
+
4
+ class VT100
5
+ ESC = 27.chr
6
+ MAX_DIM = 10000
7
+
8
+ # graphic font characters
9
+ BLANK = '_'
10
+ DIAMOND = '`'
11
+ CHECKER = 'a'
12
+ HT = 'b'
13
+ FF = 'c'
14
+ CR = 'd'
15
+ LF = 'e'
16
+ DEGREE = 'f'
17
+ PLUS_MINUS = 'g'
18
+ NL = 'h'
19
+ VT = 'i'
20
+ LOW_RIGHT = 'j'
21
+ UP_RIGHT = 'k'
22
+ UP_LEFT = 'l'
23
+ LOW_LEFT = 'm'
24
+ CROSS = 'n'
25
+ DASH1 = 'o'
26
+ DASH3 = 'p'
27
+ DASH5 = 'q'
28
+ DASH7 = 'r'
29
+ DASH9 = 's'
30
+ LEFT_T = 't'
31
+ RIGHT_T = 'u'
32
+ LOW_T = 'v'
33
+ UP_T = 'w'
34
+ BAR = 'x'
35
+ LTE = 'y'
36
+ GTE = 'z'
37
+ PI = '{'
38
+ NE = '|'
39
+ DOT = '~'
40
+
41
+ # colors
42
+ BLACK = 30
43
+ RED = 31
44
+ GREEN = 32
45
+ YELLOW = 33
46
+ BLUE = 34
47
+ MAGENTA = 35
48
+ CYAN = 36
49
+ WHITE = 37
50
+
51
+ attr_accessor :con
52
+
53
+ def initialize(con)
54
+ @con = con
55
+ @is_vt100 = false
56
+ end
57
+
58
+ def is_vt100?()
59
+ return @is_vt100
60
+ end
61
+
62
+ def identify()
63
+ @con.print("\x1b[c")
64
+ # expect: ^[[?1;<n>0c
65
+ rx = /^\x1b\[\?1;.+c/
66
+ m = recv_wait(10, 1.0, rx)
67
+ # Don't care about the type, just that the reply is valid for a vt100.
68
+ @is_vt100 = nil != m
69
+ end
70
+
71
+ def get_cursor()
72
+ v, h = 0, 0
73
+ if @is_vt100
74
+ @con.print("\x1b[6n")
75
+ # expect: ^[<v>;<h>R
76
+ rx = /^\x1b\[(\d+);(\d+)R/
77
+ m = recv_wait(16, 1.0, rx)
78
+ v, h = m.captures.map { |s| s.to_i }
79
+ end
80
+ return v, h
81
+ end
82
+
83
+ # Move cursor to screen location v, h.
84
+ def set_cursor(v, h)
85
+ @con.print("\x1b[#{v};#{h}H") if @is_vt100
86
+ end
87
+
88
+ # Save cursor position and attributes.
89
+ def save_cursor()
90
+ @con.print("\x1b7") if @is_vt100
91
+ end
92
+
93
+ # Restore cursor position and attributes.
94
+ def restore_cursor()
95
+ @con.print("\x1b8") if @is_vt100
96
+ end
97
+
98
+ # Reset terminal to initial state.
99
+ def reset()
100
+ @con.print("\x1bc") if @is_vt100
101
+ end
102
+
103
+ def graphic_font()
104
+ @con.print("\x1b(2") if @is_vt100
105
+ end
106
+
107
+ def default_font()
108
+ # TBD allow to set to specific character set for original terminal, for now US font
109
+ @con.print("\x1b(B") if @is_vt100
110
+ end
111
+
112
+ def clear_screen()
113
+ @con.print("\x1b[2J") if @is_vt100
114
+ end
115
+
116
+ def clear_line()
117
+ @con.print("\x1b[2K") if @is_vt100
118
+ end
119
+
120
+ def clear_to_end()
121
+ @con.print("\x1b[0K") if @is_vt100
122
+ end
123
+
124
+ def clear_to_start()
125
+ @con.print("\x1b[1K") if @is_vt100
126
+ end
127
+
128
+ def relative_origin()
129
+ @con.print("\x1b[?6h") if @is_vt100
130
+ end
131
+
132
+ def absolute_origin()
133
+ @con.print("\x1b[?6l") if @is_vt100
134
+ end
135
+
136
+ def attrs_off()
137
+ @con.print("\x1b[m") if @is_vt100
138
+ end
139
+
140
+ def bold()
141
+ @con.print("\x1b[1m") if @is_vt100
142
+ end
143
+ alias bright bold
144
+
145
+ def dim()
146
+ @con.print("\x1b[2m") if @is_vt100
147
+ end
148
+
149
+ def underline()
150
+ @con.print("\x1b[4m") if @is_vt100
151
+ end
152
+
153
+ def blink()
154
+ @con.print("\x1b[5m") if @is_vt100
155
+ end
156
+
157
+ def reverse()
158
+ @con.print("\x1b[7m") if @is_vt100
159
+ end
160
+
161
+ def big_top()
162
+ @con.print("\x1b#3") if @is_vt100
163
+ end
164
+
165
+ def big_bottom()
166
+ @con.print("\x1b#4") if @is_vt100
167
+ end
168
+
169
+ def narrow()
170
+ @con.print("\x1b#5") if @is_vt100
171
+ end
172
+
173
+ def wide()
174
+ @con.print("\x1b#6") if @is_vt100
175
+ end
176
+
177
+ def set_colors(fg, bg)
178
+ return unless @is_vt100
179
+ if nil == fg
180
+ if nil != bg
181
+ bg += 10
182
+ @con.print("\x1b[#{bg}m")
183
+ end
184
+ else
185
+ if nil != bg
186
+ bg += 10
187
+ @con.print("\x1b[#{fg};#{bg}m")
188
+ else
189
+ @con.print("\x1b[#{fg}m")
190
+ end
191
+ end
192
+ end
193
+
194
+ def up(n)
195
+ @con.print("\x1b[#{n}A") if @is_vt100
196
+ end
197
+
198
+ def down(n)
199
+ @con.print("\x1b[#{n}B") if @is_vt100
200
+ end
201
+
202
+ def left(n)
203
+ @con.print("\x1b[#{n}D") if @is_vt100
204
+ end
205
+
206
+ def right(n)
207
+ @con.print("\x1b[#{n}C") if @is_vt100
208
+ end
209
+
210
+ def home()
211
+ @con.print("\x1b[H") if @is_vt100
212
+ end
213
+
214
+ def scroll(n)
215
+ return unless @is_vt100
216
+ if 0 > n
217
+ n = -n
218
+ n.times { @con.print("\x1b[D") }
219
+ elsif 0 < n
220
+ n.times { @con.print("\x1b[M") }
221
+ end
222
+ end
223
+
224
+ def screen_size()
225
+ save_cursor()
226
+ set_cursor(MAX_DIM, MAX_DIM)
227
+ h, w = get_cursor()
228
+ restore_cursor()
229
+ return h, w
230
+ end
231
+
232
+ def frame(y, x, h, w)
233
+ return if 2 > h || 2 > w
234
+ graphic_font()
235
+ set_cursor(y, x)
236
+ @con.print(UP_LEFT + DASH5 * (w - 2) + UP_RIGHT)
237
+ (h - 2).times do |i|
238
+ i += 1
239
+ set_cursor(y + i, x)
240
+ @con.print(BAR)
241
+ set_cursor(y + i, x + w - 1)
242
+ @con.print(BAR)
243
+ end
244
+ set_cursor(y + h - 1, x)
245
+ @con.print(LOW_LEFT + DASH5 * (w - 2) + LOW_RIGHT)
246
+ default_font()
247
+ end
248
+
249
+ def recv_wait(max, timeout, pat)
250
+ giveup = Time.now + timeout
251
+ reply = ''
252
+ begin
253
+ while nil == pat.match(reply)
254
+ # just peek incase the string is not what we want
255
+ reply = @con.recv_nonblock(max, Socket::MSG_PEEK)
256
+
257
+ # DEBUG
258
+ # reply.each_byte { |x| print("#{x} ") }
259
+ # plain = reply.gsub(/./) { |c| c.ord < 32 || 127 <= c.ord ? '*' : c }
260
+ # puts "[#{reply.size()}] #{plain}"
261
+
262
+ end
263
+ rescue IO::WaitReadable
264
+ now = Time.now
265
+ if now < giveup
266
+ IO.select([@con], [], [], giveup - now)
267
+ retry
268
+ end
269
+ end
270
+ m = pat.match(reply)
271
+ if nil != m
272
+ # There was a match so read the characters we already peeked.
273
+ cnt = m.to_s().size
274
+ if 0 < cnt
275
+ @con.recv_nonblock(cnt)
276
+ end
277
+ end
278
+ m
279
+ end
280
+
281
+ end # VT100
282
+ end # OTerm
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is
5
+ # required. That can be set in the RUBYOPT environment variable.
6
+ # export RUBYOPT=-w
7
+
8
+ $VERBOSE = true
9
+
10
+ $: << File.join(File.dirname(__FILE__), "../lib")
11
+
12
+ require 'oterm'
13
+
14
+ class Ex < OTerm::Executor
15
+
16
+ def initialize()
17
+ super()
18
+ register('box', self, :box, 'draws a box',
19
+ %|Draws a box at the location described with the dimensions given.
20
+ > box <inset> <height> <width>|)
21
+ end
22
+
23
+ def greeting()
24
+ "Hello!"
25
+ end
26
+
27
+ def box(listener, args)
28
+ o = listener.out
29
+ # TBD get values for x, h, and w from args
30
+ x = 20
31
+ h = 4
32
+ w = 20
33
+ cy, _ = o.get_cursor()
34
+ (h - cy + 1).times { listener.out.pl() } if cy <= h
35
+ cy, _ = o.get_cursor()
36
+ o.save_cursor()
37
+ o.set_colors(OTerm::VT100::RED, nil)
38
+ o.frame(cy - h, x, h, w)
39
+ o.restore_cursor()
40
+ end
41
+
42
+ def missing(cmd, listener)
43
+ listerner.out.pl("'#{cmd}' is not a recognized command.")
44
+ end
45
+
46
+ end # Ex
47
+
48
+ executor = Ex.new()
49
+
50
+ server = OTerm::Server.new(executor, 6060, true)
51
+ server.acceptThread.join()
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oterm
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Ohler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'A remote terminal that can be used for interacting with an application
14
+ and invoking operations remotely. Telnet and VT100 over a telnet connection are
15
+ supported. '
16
+ email: peter@ohler.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - README.md
21
+ files:
22
+ - lib/oterm/errors.rb
23
+ - lib/oterm/executor.rb
24
+ - lib/oterm/listener.rb
25
+ - lib/oterm/mock100.rb
26
+ - lib/oterm/output.rb
27
+ - lib/oterm/server.rb
28
+ - lib/oterm/telnet.rb
29
+ - lib/oterm/version.rb
30
+ - lib/oterm/vt100.rb
31
+ - lib/oterm.rb
32
+ - test/server_test.rb
33
+ - LICENSE
34
+ - README.md
35
+ homepage: http://www.ohler.com/oterm
36
+ licenses:
37
+ - MIT
38
+ - GPL-3.0
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.md
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project: oterm
58
+ rubygems_version: 2.0.14
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: A operations terminal server.
62
+ test_files: []