oterm 1.0.0

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.
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: []