ansi-sys 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +188 -0
- data/History.txt +7 -0
- data/License.txt +34 -0
- data/Makefile +56 -0
- data/Manifest.txt +38 -0
- data/README.txt +37 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/gpl.rd.txt +674 -0
- data/lgpl.rd.txt +512 -0
- data/lib/ansisys.rb +595 -0
- data/log/debug.log +0 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1637 -0
- data/spec/attach/test_data.html +17 -0
- data/spec/attach/test_data.txt +16 -0
- data/spec/character_screen_spec.rb +32 -0
- data/spec/character_spec.rb +45 -0
- data/spec/cursor_spec.rb +165 -0
- data/spec/hiki_spec.rb +39 -0
- data/spec/lexer_spec.rb +95 -0
- data/spec/screen_spec.rb +164 -0
- data/spec/sgr_spec.rb +172 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/terminal_spec.rb +82 -0
- data/tasks/deployment.rake +27 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- data/website/index.html +113 -0
- data/website/index.txt +57 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +132 -0
- data/website/template.rhtml +48 -0
- metadata +91 -0
data/lib/ansisys.rb
ADDED
@@ -0,0 +1,595 @@
|
|
1
|
+
#
|
2
|
+
# = ansisys.rb
|
3
|
+
# ANSI terminal emulator
|
4
|
+
# based on http://en.wikipedia.org/wiki/ANSI_escape_code
|
5
|
+
#
|
6
|
+
# Copyright:: Copyright (C) 2007 zunda <zunda at freeshell.org>
|
7
|
+
# License:: GPL - GPL3 or later
|
8
|
+
#
|
9
|
+
require 'webrick'
|
10
|
+
|
11
|
+
module AnsiSys
|
12
|
+
module VERSION #:nodoc:
|
13
|
+
MAJOR = 0
|
14
|
+
MINOR = 2
|
15
|
+
TINY = 0
|
16
|
+
|
17
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
18
|
+
end
|
19
|
+
|
20
|
+
module CSSFormatter
|
21
|
+
def hash_to_styles(hash, separator = '; ')
|
22
|
+
unless hash.empty?
|
23
|
+
return hash.map{|e| "#{e[0]}: #{e[1].join(' ')}"}.join(separator)
|
24
|
+
else
|
25
|
+
return nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module_function :hash_to_styles
|
29
|
+
end
|
30
|
+
|
31
|
+
class AnsiSysError < StandardError; end
|
32
|
+
|
33
|
+
class Lexer
|
34
|
+
# Control Sequence Introducer and following code
|
35
|
+
PARAMETER_AND_LETTER = /\A([\d;]*)([[:alpha:]])/o
|
36
|
+
CODE_EQUIVALENT = {
|
37
|
+
"\r" => ['B'],
|
38
|
+
"\n" => ['E'],
|
39
|
+
}
|
40
|
+
|
41
|
+
attr_reader :buffer
|
42
|
+
|
43
|
+
def initialize(csis = ["\x1b["]) # CSI can also be "\x9B"
|
44
|
+
@code_start_re = Regexp.union(*(CODE_EQUIVALENT.keys + csis))
|
45
|
+
@buffer = ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def push(string)
|
49
|
+
@buffer += string
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns array of tokens while deleting the tokenized part of the string
|
53
|
+
def lex!
|
54
|
+
r = Array.new
|
55
|
+
@buffer.gsub!(/(?:\r\n|\n\r)/, "\n")
|
56
|
+
while @code_start_re =~ @buffer
|
57
|
+
r << [:string, $`] unless $`.empty?
|
58
|
+
if CODE_EQUIVALENT.has_key?($&)
|
59
|
+
CODE_EQUIVALENT[$&].each do |c|
|
60
|
+
r << [:code, c]
|
61
|
+
end
|
62
|
+
@buffer = $'
|
63
|
+
else
|
64
|
+
csi = $&
|
65
|
+
residual = $'
|
66
|
+
if PARAMETER_AND_LETTER =~ residual
|
67
|
+
r << [:code, $&]
|
68
|
+
@buffer = $'
|
69
|
+
else
|
70
|
+
@buffer = csi + residual
|
71
|
+
return r
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
r << [:string, @buffer] unless @buffer.empty?
|
76
|
+
@buffer = ''
|
77
|
+
return r
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Characters
|
82
|
+
WIDTHS = {
|
83
|
+
"\t" => 8,
|
84
|
+
}
|
85
|
+
|
86
|
+
attr_reader :string
|
87
|
+
attr_reader :sgr
|
88
|
+
attr_reader :initial_cursor
|
89
|
+
|
90
|
+
def initialize(string, sgr)
|
91
|
+
@string = string
|
92
|
+
@sgr = sgr
|
93
|
+
end
|
94
|
+
|
95
|
+
def echo_on(screen, cursor)
|
96
|
+
each_char do |c|
|
97
|
+
w = width(c)
|
98
|
+
screen.write(c, w, cursor.cur_col, cursor.cur_row, @sgr.dup)
|
99
|
+
cursor.advance!(w)
|
100
|
+
end
|
101
|
+
return self
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def each_char(&block)
|
106
|
+
@string.scan(/./).each do |c|
|
107
|
+
yield(c)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def width(char)
|
112
|
+
if WIDTHS.has_key?(char)
|
113
|
+
return WIDTHS[char]
|
114
|
+
end
|
115
|
+
case char.size # expecting number of bytes
|
116
|
+
when 1
|
117
|
+
return 1
|
118
|
+
else
|
119
|
+
return 2
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Cursor
|
125
|
+
CODE_LETTERS = %w(A B C D E F G H f)
|
126
|
+
|
127
|
+
attr_reader :cur_col
|
128
|
+
attr_reader :cur_row
|
129
|
+
attr_accessor :max_col
|
130
|
+
attr_accessor :max_row
|
131
|
+
|
132
|
+
def initialize(cur_col = 1, cur_row = 1, max_col = 80, max_row = 25)
|
133
|
+
@cur_col = cur_col
|
134
|
+
@cur_row = cur_row
|
135
|
+
@max_col = max_col
|
136
|
+
@max_row = max_row
|
137
|
+
end
|
138
|
+
|
139
|
+
def apply_code!(args)
|
140
|
+
letter = args.pop
|
141
|
+
pars = args.map do |arg|
|
142
|
+
case arg
|
143
|
+
when nil
|
144
|
+
nil
|
145
|
+
when ''
|
146
|
+
nil
|
147
|
+
else
|
148
|
+
Integer(arg)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
case letter
|
152
|
+
when 'A'
|
153
|
+
@cur_row -= pars[0] ? pars[0] : 1
|
154
|
+
@cur_row = @max_row if @max_row and @cur_row > @max_row
|
155
|
+
when 'B'
|
156
|
+
@cur_row += pars[0] ? pars[0] : 1
|
157
|
+
@cur_row = @max_row if @max_row and @cur_row > @max_row
|
158
|
+
when 'C'
|
159
|
+
@cur_col += pars[0] ? pars[0] : 1
|
160
|
+
when 'D'
|
161
|
+
@cur_col -= pars[0] ? pars[0] : 1
|
162
|
+
when 'E'
|
163
|
+
@cur_row += pars[0] ? pars[0] : 1
|
164
|
+
@cur_col = 1
|
165
|
+
@max_row = @cur_row if @max_row and @cur_row > @max_row
|
166
|
+
when 'F'
|
167
|
+
@cur_row -= pars[0] ? pars[0] : 1
|
168
|
+
@cur_col = 1
|
169
|
+
@max_row = @cur_row if @max_row and @cur_row > @max_row
|
170
|
+
when 'G'
|
171
|
+
@cur_col = pars[0] ? pars[0] : 1
|
172
|
+
when 'H'
|
173
|
+
@cur_row = pars[0] ? pars[0] : 1
|
174
|
+
@cur_col = pars[1] ? pars[1] : 1
|
175
|
+
@max_row = @cur_row if @max_row and @cur_row > @max_row
|
176
|
+
when 'f'
|
177
|
+
@cur_row = pars[0] ? pars[0] : 1
|
178
|
+
@cur_col = pars[1] ? pars[1] : 1
|
179
|
+
@max_row = @cur_row if @max_row and @cur_row > @max_row
|
180
|
+
end
|
181
|
+
if @cur_row < 1
|
182
|
+
@cur_row = 1
|
183
|
+
end
|
184
|
+
if @cur_col < 1
|
185
|
+
@cur_col = 1
|
186
|
+
elsif @cur_col > @max_col
|
187
|
+
@cur_col = @max_col
|
188
|
+
end
|
189
|
+
return self
|
190
|
+
end
|
191
|
+
|
192
|
+
def advance!(width = 1)
|
193
|
+
r = nil
|
194
|
+
@cur_col += width
|
195
|
+
if @cur_col > @max_col
|
196
|
+
r = "\n"
|
197
|
+
@cur_col = 1
|
198
|
+
@cur_row += 1
|
199
|
+
end
|
200
|
+
return r
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
# Select Graphic Rendition
|
206
|
+
class SGR
|
207
|
+
extend CSSFormatter
|
208
|
+
|
209
|
+
CODE_LETTERS = %w(m)
|
210
|
+
|
211
|
+
attr_reader :intensity # :normal, :bold, or :faint
|
212
|
+
attr_reader :italic # :off or :on
|
213
|
+
attr_reader :underline # :none, :single, or :double
|
214
|
+
attr_reader :blink # :off, :slow, or :rapid
|
215
|
+
attr_reader :image # :positive or :negative
|
216
|
+
attr_reader :conceal # :off or :on
|
217
|
+
attr_reader :foreground # :black, :red, :green, :yellow, :blue, :magenta, :cyan, or :white
|
218
|
+
attr_reader :background # :black, :red, :green, :yellow, :blue, :magenta, :cyan, or :white
|
219
|
+
|
220
|
+
def initialize
|
221
|
+
reset!
|
222
|
+
end
|
223
|
+
|
224
|
+
def ==(other)
|
225
|
+
instance_variables.each do |ivar|
|
226
|
+
return false unless instance_eval(ivar) == other.instance_eval(ivar)
|
227
|
+
end
|
228
|
+
return true
|
229
|
+
end
|
230
|
+
|
231
|
+
def reset!
|
232
|
+
apply_code!(%w(0 m))
|
233
|
+
end
|
234
|
+
|
235
|
+
def apply_code!(args = %w(0 m))
|
236
|
+
letter = args.pop
|
237
|
+
raise AnsiSysError, "Invalid code for SGR" unless 'm' == letter
|
238
|
+
pars = args.map{|arg| arg.to_i}
|
239
|
+
pars = [0] if pars.empty?
|
240
|
+
pars.each do |code|
|
241
|
+
case code
|
242
|
+
when 0
|
243
|
+
@intensity = :normal
|
244
|
+
@italic = :off
|
245
|
+
@underline = :none
|
246
|
+
@blink = :off
|
247
|
+
@image = :positive
|
248
|
+
@conceal = :off
|
249
|
+
@foreground = :white
|
250
|
+
@background = :black
|
251
|
+
when 1..28
|
252
|
+
apply_code_table!(code)
|
253
|
+
when 30..37
|
254
|
+
@foreground = COLOR[code - 30]
|
255
|
+
@intensity = :normal
|
256
|
+
when 39
|
257
|
+
reset!
|
258
|
+
when 40..47
|
259
|
+
@background = COLOR[code - 40]
|
260
|
+
@intensity = :normal
|
261
|
+
when 49
|
262
|
+
reset!
|
263
|
+
when 90..97
|
264
|
+
@foreground = COLOR[code - 90]
|
265
|
+
@intensity = :bold
|
266
|
+
when 99
|
267
|
+
reset!
|
268
|
+
when 100..107
|
269
|
+
@background = COLOR[code - 100]
|
270
|
+
@intensity = :bold
|
271
|
+
when 109
|
272
|
+
reset!
|
273
|
+
else
|
274
|
+
raise AnsiSysError, "Invalid SGR code #{code.inspect}" unless CODE.has_key?(code)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
return self
|
278
|
+
end
|
279
|
+
|
280
|
+
def render(format = :html, position = :prefix, colors = Screen.default_css_colors)
|
281
|
+
case format
|
282
|
+
when :html
|
283
|
+
case position
|
284
|
+
when :prefix
|
285
|
+
style_code = css_style(colors)
|
286
|
+
if style_code
|
287
|
+
return %Q|<span style="#{style_code}">|
|
288
|
+
else
|
289
|
+
return ''
|
290
|
+
end
|
291
|
+
when :postfix
|
292
|
+
style_code = css_style(colors)
|
293
|
+
if style_code
|
294
|
+
return '</span>'
|
295
|
+
else
|
296
|
+
return ''
|
297
|
+
end
|
298
|
+
end
|
299
|
+
when :text
|
300
|
+
return ''
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def css_style(colors = Screen.default_css_colors)
|
305
|
+
return CSSFormatter.hash_to_styles(css_styles(colors))
|
306
|
+
end
|
307
|
+
|
308
|
+
def css_styles(colors = Screen.default_css_colors)
|
309
|
+
r = Hash.new{|h, k| h[k] = Array.new}
|
310
|
+
# intensity is not (yet) implemented
|
311
|
+
r['font-style'] << 'italic' if @italic == :on
|
312
|
+
r['text-decoration'] << 'underline' unless @underline == :none
|
313
|
+
r['text-decoration'] << 'blink' unless @blink == :off
|
314
|
+
case @image
|
315
|
+
when :positive
|
316
|
+
fg = @foreground
|
317
|
+
bg = @background
|
318
|
+
when :negative
|
319
|
+
fg = @background
|
320
|
+
bg = @foreground
|
321
|
+
end
|
322
|
+
fg = bg if @conceal == :on
|
323
|
+
r['color'] << colors[@intensity][fg] unless fg == :white
|
324
|
+
r['background-color'] << colors[@intensity][bg] unless bg == :black
|
325
|
+
return r
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
def apply_code_table!(code)
|
330
|
+
raise AnsiSysError, "Invalid SGR code #{code.inspect}" unless CODE.has_key?(code)
|
331
|
+
ivar, value = CODE[code]
|
332
|
+
instance_variable_set("@#{ivar}", value)
|
333
|
+
return self
|
334
|
+
end
|
335
|
+
|
336
|
+
CODE = {
|
337
|
+
1 => [:intensity, :bold],
|
338
|
+
2 => [:intensity, :faint],
|
339
|
+
3 => [:italic, :on],
|
340
|
+
4 => [:underline, :single],
|
341
|
+
5 => [:blink, :slow],
|
342
|
+
6 => [:blink, :rapid],
|
343
|
+
7 => [:image, :negative],
|
344
|
+
8 => [:conceal, :on],
|
345
|
+
21 => [:underline, :double],
|
346
|
+
22 => [:intensity, :normal],
|
347
|
+
24 => [:underline, :none],
|
348
|
+
25 => [:blink, :off],
|
349
|
+
27 => [:image, :positive],
|
350
|
+
28 => [:conceal, :off],
|
351
|
+
}
|
352
|
+
|
353
|
+
COLOR = {
|
354
|
+
0 => :black,
|
355
|
+
1 => :red,
|
356
|
+
2 => :green,
|
357
|
+
3 => :yellow,
|
358
|
+
4 => :blue,
|
359
|
+
5 => :magenta,
|
360
|
+
6 => :cyan,
|
361
|
+
7 => :white,
|
362
|
+
}
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
class Screen
|
367
|
+
CODE_LETTERS = %w()
|
368
|
+
|
369
|
+
def self.default_foreground; :white; end
|
370
|
+
def self.default_background; :black; end
|
371
|
+
|
372
|
+
# intensity -> color
|
373
|
+
def self.default_css_colors(inverted = false, bright = false)
|
374
|
+
r = {
|
375
|
+
:normal => {
|
376
|
+
:black => 'black',
|
377
|
+
:red => 'maroon',
|
378
|
+
:green => 'green',
|
379
|
+
:yellow => 'olive',
|
380
|
+
:blue => 'navy',
|
381
|
+
:magenta => 'purple',
|
382
|
+
:cyan => 'teal',
|
383
|
+
:white => 'silver',
|
384
|
+
},
|
385
|
+
:bold => {
|
386
|
+
:black => 'gray',
|
387
|
+
:red => 'red',
|
388
|
+
:green => 'lime',
|
389
|
+
:yellow => 'yellow',
|
390
|
+
:blue => 'blue',
|
391
|
+
:magenta => 'fuchsia',
|
392
|
+
:cyan => 'cyan',
|
393
|
+
:white => 'white'
|
394
|
+
},
|
395
|
+
:faint => {
|
396
|
+
:black => 'black',
|
397
|
+
:red => 'maroon',
|
398
|
+
:green => 'green',
|
399
|
+
:yellow => 'olive',
|
400
|
+
:blue => 'navy',
|
401
|
+
:magenta => 'purple',
|
402
|
+
:cyan => 'teal',
|
403
|
+
:white => 'silver',
|
404
|
+
},
|
405
|
+
}
|
406
|
+
|
407
|
+
if bright
|
408
|
+
r[:bold][:black] = 'black'
|
409
|
+
[:normal, :faint].each do |i|
|
410
|
+
r[i] = r[:bold]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
if inverted
|
415
|
+
r.each_key do |i|
|
416
|
+
r[i][:black], r[i][:white] = r[i][:white], r[i][:black]
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
return r
|
421
|
+
end
|
422
|
+
|
423
|
+
def self.css_styles(colors = Screen.default_css_colors, max_col = nil, max_row = nil)
|
424
|
+
h = {
|
425
|
+
'color' => [colors[:normal][:white]],
|
426
|
+
'background-color' => [colors[:normal][:black]],
|
427
|
+
'padding' => ['0.5em'],
|
428
|
+
}
|
429
|
+
h['width'] = ["#{Float(max_col)/2}em"] if max_col
|
430
|
+
#h['height'] = ["#{max_row}em"] if max_row # could not find appropriate unit
|
431
|
+
return h
|
432
|
+
end
|
433
|
+
|
434
|
+
def self.css_style(*args)
|
435
|
+
return "pre.screen {\n\t" + CSSFormatter.hash_to_styles(self.css_styles(*args), ";\n\t") + ";\n}\n"
|
436
|
+
end
|
437
|
+
|
438
|
+
def initialize(colors = Screen.default_css_colors, max_col = nil, max_row = nil)
|
439
|
+
@colors = colors
|
440
|
+
@max_col = max_col
|
441
|
+
@max_row = max_row
|
442
|
+
@lines = Hash.new{|hash, key| hash[key] = Hash.new}
|
443
|
+
end
|
444
|
+
|
445
|
+
def css_style
|
446
|
+
self.class.css_style(@colors, @max_col, @max_row)
|
447
|
+
end
|
448
|
+
|
449
|
+
def write(char, char_width, col, row, sgr)
|
450
|
+
@lines[Integer(row)][Integer(col)] = [char, char_width, sgr.dup]
|
451
|
+
end
|
452
|
+
|
453
|
+
def render(format = :html, css_class = 'screen', css_style = nil)
|
454
|
+
result = case format
|
455
|
+
when :text
|
456
|
+
''
|
457
|
+
when :html
|
458
|
+
%Q|<pre#{css_class ? %Q[ class="#{css_class}"] : ''}#{css_style ? %Q| style="#{css_style}"| : ''}>\n|
|
459
|
+
else
|
460
|
+
raise AnsiSysError, "Invalid format option to render: #{format.inspect}"
|
461
|
+
end
|
462
|
+
|
463
|
+
unless @lines.keys.empty?
|
464
|
+
prev_sgr = nil
|
465
|
+
max_row = @lines.keys.max
|
466
|
+
(1..max_row).each do |row|
|
467
|
+
if @lines.has_key?(row) and not @lines[row].keys.empty?
|
468
|
+
col = 1
|
469
|
+
while col <= @lines[row].keys.max
|
470
|
+
if @lines[row].has_key?(col) and @lines[row][col]
|
471
|
+
char, width, sgr = @lines[row][col]
|
472
|
+
if prev_sgr != sgr
|
473
|
+
result += prev_sgr.render(format, :postfix, @colors) if prev_sgr
|
474
|
+
result += sgr.render(format, :prefix, @colors)
|
475
|
+
prev_sgr = sgr
|
476
|
+
end
|
477
|
+
case format
|
478
|
+
when :text
|
479
|
+
result += char
|
480
|
+
when :html
|
481
|
+
result += WEBrick::HTMLUtils.escape(char)
|
482
|
+
end
|
483
|
+
col += width
|
484
|
+
else
|
485
|
+
result += ' '
|
486
|
+
col += 1
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
result += "\n" if row < max_row
|
491
|
+
end
|
492
|
+
result += prev_sgr.render(format, :postfix, @colors) if prev_sgr
|
493
|
+
end
|
494
|
+
|
495
|
+
result += case format
|
496
|
+
when :text
|
497
|
+
''
|
498
|
+
when :html
|
499
|
+
'</pre>'
|
500
|
+
end
|
501
|
+
return result
|
502
|
+
end
|
503
|
+
|
504
|
+
def apply_code!(args) # TODO - some codes
|
505
|
+
return self
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
class Terminal
|
510
|
+
CODE_LETTERS = [] # TODO - make a new screen
|
511
|
+
|
512
|
+
def initialize(csis = ["\x1b["])
|
513
|
+
@lexer = Lexer.new(csis)
|
514
|
+
@stream = Array.new
|
515
|
+
end
|
516
|
+
|
517
|
+
def echo(data)
|
518
|
+
@lexer.push(data)
|
519
|
+
return self
|
520
|
+
end
|
521
|
+
|
522
|
+
def css_style(format = :html, max_col = 80, max_row = nil, colors = Screen.default_css_colors)
|
523
|
+
case format
|
524
|
+
when :html
|
525
|
+
Screen.css_style(colors, max_col, max_row)
|
526
|
+
when :text
|
527
|
+
''
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def render(format = :html, max_col = 80, max_row = nil, colors = Screen.default_css_colors, css_class = 'screen', css_style = nil)
|
532
|
+
screens = populate(format, max_col, max_row, colors)
|
533
|
+
separator = case format
|
534
|
+
when :html
|
535
|
+
"\n"
|
536
|
+
when :text
|
537
|
+
"\n---\n"
|
538
|
+
end
|
539
|
+
return screens.map{|screen| screen.render(format, css_class, css_style)}.join(separator)
|
540
|
+
end
|
541
|
+
|
542
|
+
private
|
543
|
+
def populate(format = :html, max_col = 80, max_row = nil, colors = Screen.default_css_colors)
|
544
|
+
cursor = Cursor.new(1, 1, max_col, max_row)
|
545
|
+
screens = [Screen.new(colors, max_col, max_row)]
|
546
|
+
sgr = SGR.new
|
547
|
+
@stream += @lexer.lex!
|
548
|
+
@stream.each do |type, payload|
|
549
|
+
case type
|
550
|
+
when :string
|
551
|
+
Characters.new(payload, sgr).echo_on(screens[-1], cursor)
|
552
|
+
when :code
|
553
|
+
unless Lexer::PARAMETER_AND_LETTER =~ payload
|
554
|
+
raise AnsiSysError, "Invalid code: #{payload.inspect}"
|
555
|
+
end
|
556
|
+
parameters = $1
|
557
|
+
letter = $2
|
558
|
+
args = parameters.split(/;/) + [letter]
|
559
|
+
applied = false
|
560
|
+
[sgr, cursor, screens[-1], self].each do |recv|
|
561
|
+
if recv.class.const_get(:CODE_LETTERS).include?(letter)
|
562
|
+
recv.apply_code!(args)
|
563
|
+
applied = true
|
564
|
+
end
|
565
|
+
end
|
566
|
+
raise AnsiSysError, "Invalid code or not implemented: #{payload.inspect}" unless applied
|
567
|
+
end
|
568
|
+
end
|
569
|
+
return screens
|
570
|
+
end
|
571
|
+
|
572
|
+
def apply_code!(args) # TODO - make a new screen
|
573
|
+
return self
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
if defined?(Hiki) and Hiki::Plugin == self.class
|
579
|
+
def ansi_screen(file_name, max_row = 80, invert = false, bright = true, page = @page)
|
580
|
+
return '' unless file_name =~ /\.(txt|rd|rb|c|pl|py|sh|java|html|htm|css|xml|xsl|sql|yaml)\z/i
|
581
|
+
page_file_name = "#{page.untaint.escape}/#{file_name.untaint.escape}"
|
582
|
+
path = "#{@conf.cache_path}/attach/#{page_file_name}"
|
583
|
+
unless File.exists?(path)
|
584
|
+
raise PluginError, "No such file:#{page_file_name}"
|
585
|
+
end
|
586
|
+
data = File.open(path){|f| f.read}
|
587
|
+
|
588
|
+
colors = AnsiSys::Screen.default_css_colors(invert, bright)
|
589
|
+
styles = AnsiSys::CSSFormatter.hash_to_styles(AnsiSys::Screen.css_styles(colors, max_row, nil), '; ')
|
590
|
+
|
591
|
+
terminal = AnsiSys::Terminal.new
|
592
|
+
terminal.echo(data)
|
593
|
+
return terminal.render(:html, max_row, nil, colors, 'screen', styles) + "\n"
|
594
|
+
end
|
595
|
+
end
|