ansi-sys 0.2.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.
- 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
|