terminal 0.2 → 0.3.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ # A fake terminal screen. Usage is like this:
2
+ #
3
+ # screen = Screen.new
4
+ # screen.x = 10
5
+ # screen.y = 10
6
+ # screen.write('h')
7
+ #
8
+ # Will essentially create a 10 x 10 grid with empty characters, and at the
9
+ # 10,10 spot element, there will be a 'h'. Co-ordinates start at 0,0
10
+ #
11
+ # It also supports writing colors. So if you were to change the color like so:
12
+ #
13
+ # screen.color("42")
14
+ #
15
+ # Ever new character that you write, will be stored with that color
16
+ # information.
17
+ #
18
+ # When turned into a string, the screen class creates ANSI escape characters,
19
+ # that the renderer class gsubs out.
20
+ #
21
+ # \e[fg32;bg42;
22
+ # \e[fgi91;;
23
+ # \e[fgx102;bgx102;
24
+ # \e[0m
25
+ #
26
+ # Are some of the examples of the escape sequences that this will render.
27
+
28
+ module Terminal
29
+ class Screen
30
+ class Node < Struct.new(:blob, :fg, :bg)
31
+ def ==(value)
32
+ blob == value
33
+ end
34
+
35
+ # Every node has a style, a foreground style, and a background
36
+ # style. This method returns what essentially becomes the escape
37
+ # sequence:
38
+ #
39
+ # \e[fg;bg;
40
+ #
41
+ # As the screen is turned into a string, the style is used to compare
42
+ # whether or not a new escape sequence is required.
43
+ def style
44
+ if fg || bg
45
+ "#{fg};#{bg};"
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ def to_s
52
+ blob
53
+ end
54
+ end
55
+
56
+ END_OF_LINE = :end_of_line
57
+ START_OF_LINE = :start_of_line
58
+ EMPTY = Node.new(" ")
59
+
60
+ attr_reader :x, :y
61
+
62
+ def initialize
63
+ @x = 0
64
+ @y = 0
65
+ @screen = []
66
+ @fg = nil
67
+ end
68
+
69
+ def write(character)
70
+ # Expand the screen if we need to
71
+ ((@y + 1) - @screen.length).times do
72
+ @screen << []
73
+ end
74
+
75
+ line = @screen[@y]
76
+ line_length = line.length
77
+
78
+ # Write empty slots until we reach the line
79
+ (@x - line_length).times do |i|
80
+ line[line_length + i] = EMPTY
81
+ end
82
+
83
+ # Write the character to the slot
84
+ line[@x] = Node.new(character, @fg, @bg)
85
+ end
86
+
87
+ def <<(character)
88
+ write(character)
89
+ @x += 1
90
+ character
91
+ end
92
+
93
+ def x=(value)
94
+ @x = value > 0 ? value : 0
95
+ end
96
+
97
+ def y=(value)
98
+ @y = value > 0 ? value : 0
99
+ end
100
+
101
+ def clear(y, x_start, x_end)
102
+ line = @screen[y]
103
+
104
+ # If the line isn't there, we can't clean it.
105
+ return if line.nil?
106
+
107
+ x_start = 0 if x_start == START_OF_LINE
108
+ x_end = line.length - 1 if x_end == END_OF_LINE
109
+
110
+ if x_start == START_OF_LINE && x_end == END_OF_LINE
111
+ @screen[y] = []
112
+ else
113
+ line.fill(EMPTY, x_start..x_end)
114
+ end
115
+ end
116
+
117
+ # Changes the current foreground color that all new characters
118
+ # will be written with.
119
+ def color(color_code)
120
+ # Reset all styles
121
+ if color_code == "0"
122
+ @fg = nil
123
+ @bg = nil
124
+ return color_code
125
+ end
126
+
127
+ # Reset foreground color only
128
+ if color_code == "39"
129
+ @fg = nil
130
+ return color_code
131
+ end
132
+
133
+ # Reset background color only
134
+ if color_code == "49"
135
+ @bg = nil
136
+ return color_code
137
+ end
138
+
139
+ colors = color_code.to_s.split(";")
140
+
141
+ # Extended set foreground x-term color
142
+ if colors[0] == "38" && colors[1] == "5"
143
+ return @fg = "fgx#{colors[2]}"
144
+ end
145
+
146
+ # Extended set background x-term color
147
+ if colors[0] == "48" && colors[1] == "5"
148
+ return @bg = "bgx#{colors[2]}"
149
+ end
150
+
151
+ # If multiple colors are defined, i.e. \e[30;42m\e
152
+ # then loop through each one, and assign it to @fg
153
+ # or @bg
154
+ colors.each do |cc|
155
+ # If the number is between 30–37, then it's a foreground color,
156
+ # if it's 40–47, then it's a background color. 90-97 is like the regular
157
+ # foreground 30-37, but it's high intensity
158
+ #
159
+ # I don't use ranges and a select because I've found that to be rather
160
+ # slow.
161
+ c_integer = cc.to_i
162
+ if c_integer >= 30 && c_integer <= 37
163
+ @fg = "fg#{cc}"
164
+ elsif c_integer >= 40 && c_integer <= 47
165
+ @bg = "bg#{cc}"
166
+ elsif c_integer >= 90 && c_integer <= 97
167
+ @fg = "fgi#{cc}"
168
+ end
169
+ end
170
+ end
171
+
172
+ def up(value = nil)
173
+ self.y -= parse_integer(value)
174
+ end
175
+
176
+ def down(value = nil)
177
+ new_y = @y + parse_integer(value)
178
+
179
+ # Only jump down if the line exists
180
+ if @screen[new_y]
181
+ self.y = new_y
182
+ else
183
+ false
184
+ end
185
+ end
186
+
187
+ def backward(value = nil)
188
+ self.x -= parse_integer(value)
189
+ end
190
+
191
+ def foward(value = nil)
192
+ self.x += parse_integer(value)
193
+ end
194
+
195
+ def to_a
196
+ @screen.to_a.map { |chars| chars.map(&:to_s) }
197
+ end
198
+
199
+ # Renders each node to a string. This looks at each node, and then inserts
200
+ # escape characters that will be gsubed into <span> elements.
201
+ #
202
+ # ANSI codes generally span across lines. So if you \e[12m\n\nhello, the hello will
203
+ # inhert the styles of \e[12m. This doesn't work so great in HTML, especially if you
204
+ # wrap divs around each line, so this method also copies any styles that are left open
205
+ # at the end of a line, to the begining of new lines, so you end up with something like this:
206
+ #
207
+ # \e[12m\n\e[12m\n\e[12mhello
208
+ #
209
+ # It also attempts to only insert escapes that are required. Given the following:
210
+ #
211
+ # \e[12mh\e[12me\e[12ml\e[12ml\e[12mo\e[0m
212
+ #
213
+ # A general purpose ANSI renderer will convert it to:
214
+ #
215
+ # <span class="c12">h<span class="c12">e<span class="c12">l<span class="c12">l<span class="c12">o</span></span></span></span>
216
+ #
217
+ # But ours is smart, and tries to do stuff like this:
218
+ #
219
+ # <span class="c12">hello</span>
220
+ def to_s
221
+ last_line_index = @screen.length - 1
222
+ buffer = []
223
+
224
+ @screen.each_with_index do |line, line_index|
225
+ previous = nil
226
+
227
+ # Keep track of every open style we have, so we know
228
+ # that we need to close any open ones at the end.
229
+ open_styles = 0
230
+
231
+ line.each do |node|
232
+ # If there is no previous node, and the current node has a color
233
+ # (first node in a line) then add the escape character.
234
+ if !previous && node.style
235
+ buffer << "\e[#{node.style}m"
236
+
237
+ # Increment the open style counter
238
+ open_styles += 1
239
+
240
+ # If we have a previous node, and the last node's style doesn't
241
+ # match this nodes, then we start a new escape character.
242
+ elsif previous && previous.style != node.style
243
+ # If the new node has no style, that means that all the styles
244
+ # have been closed.
245
+ if !node.style
246
+ # Add our reset escape character
247
+ buffer << "\e[0m"
248
+
249
+ # Decrement the open style counter
250
+ open_styles -= 1
251
+ else
252
+ buffer << "\e[#{node.style}m"
253
+
254
+ # Increment the open style counter
255
+ open_styles += 1
256
+ end
257
+ end
258
+
259
+ # Add the nodes blob to te buffer
260
+ buffer << node.blob
261
+
262
+ # Set this node as being the previous node
263
+ previous = node
264
+ end
265
+
266
+ # Be sure to close off any open styles for this line
267
+ open_styles.times { buffer << "\e[0m" }
268
+
269
+ # Add a new line as long as this line isn't the last
270
+ buffer << "\n" if line_index != last_line_index
271
+ end
272
+
273
+ buffer.join("")
274
+ end
275
+
276
+ private
277
+
278
+ def parse_integer(value)
279
+ value.nil? || value == "" ? 1 : value.to_i
280
+ end
281
+ end
282
+ end
@@ -1,99 +1,56 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
5
+ <link href="<%= asset_path("stylesheets/terminal.css") %>" media="screen" rel="stylesheet" />
4
6
  <style>
5
- .code {
6
- background: #171717;
7
- border-radius: 5px;
8
- color: white;
9
- padding: 15px;
10
- margin-bottom: 15px;
11
- word-break: break-word;
12
- white-space: pre-wrap;
13
- overflow-wrap: break-word;
14
- }
15
-
16
- .code pre {
17
- margin: 0;
18
- padding: 0;
19
- font-family: Monaco, courier;
20
- font-size: 12px;
21
- line-height: 20px;
22
- }
23
-
24
- /* yellow */
25
- .c33 {
26
- color: #c6c502;
27
- }
28
-
29
- /* green */
30
- .c32 {
31
- color: #00bd02;
32
- }
33
-
34
- /* intense green */
35
- .c1 {
36
- color: #5ef765;
37
- }
38
-
39
- /* red */
40
- .c31 {
41
- color: #e10c02;
42
- }
43
-
44
- /* blue */
45
- .c34 {
46
- color: #8db7e0;
47
- }
48
-
49
- /* magent */
50
- .c35 {
51
- color: #ceacde;
52
- }
53
-
54
- /* cyan */
55
- .c36 {
56
- color: #00cdd9;
57
- }
58
-
59
- /* black (but we can't use black, so a diff color) */
60
- .c30 {
61
- color: #666;
62
- }
63
-
64
- /* grey */
65
- .c90 {
66
- color: #838887;
67
- }
68
-
69
- body {
70
- margin: 0;
71
- padding: 0;
72
- }
73
-
74
- .previews {
75
- overflow: hidden;
76
- padding: 20px;
77
- }
78
-
79
- .preview-left, .preview-right {
80
- width: 50%;
81
- float: left;
82
- }
83
-
84
- .preview-left .code {
85
- margin-right: 10px;
86
- }
87
-
88
- .preview-right .code {
89
- margin-left: 10px;
90
- }
7
+ .code {
8
+ background: #171717;
9
+ border-radius: 5px;
10
+ color: white;
11
+ word-break: break-word;
12
+ overflow-wrap: break-word;
13
+ font-family: Monaco, courier;
14
+ font-size: 12px;
15
+ line-height: 20px;
16
+ padding: 14px 18px;
17
+ white-space: pre-wrap;
18
+ margin-bottom: 15px;
19
+ }
20
+
21
+ body {
22
+ margin: 0;
23
+ padding: 20px;
24
+ }
25
+
26
+ .controls {
27
+ margin-bottom: 5px;
28
+ }
29
+
30
+ a {
31
+ font-family: Arial, sans-serif;
32
+ font-size: 13px;
33
+ }
91
34
  </style>
35
+ <script>
36
+ function toggle(element) {
37
+ element.style.display = element.style.display === 'none' ? '' : 'none'
38
+ }
39
+
40
+ function switchViews() {
41
+ toggle(document.getElementById('rendered'));
42
+ toggle(document.getElementById('raw'));
43
+ }
44
+ </script>
92
45
  </head>
93
46
  <body>
94
- <div class="previews">
95
- <div class="preview-left"><div class="code"><pre><%= raw %></pre></div></div>
96
- <div class="preview-right"><div class="code"><pre><%= rendered %></pre></div></div>
47
+ <div id="rendered" class="preview">
48
+ <div class="controls"><a href="#" onclick="switchViews(); return false">Show Raw Output</a></div>
49
+ <div class="code"><%= rendered %></div>
50
+ </div>
51
+ <div id="raw" class="preview" style="display: none">
52
+ <div class="controls"><a href="#" onclick="switchViews(); return false">Show Rendered Output</a></div>
53
+ <div class="code raw"><%= raw %></div>
97
54
  </div>
98
55
  </body>
99
56
  </html>
@@ -1,3 +1,3 @@
1
1
  module Terminal
2
- VERSION = "0.2"
2
+ VERSION = "0.3.beta1"
3
3
  end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
4
+
5
+ require 'terminal'
6
+ require 'benchmark/ips'
7
+ require 'timeout'
8
+
9
+ Benchmark.ips do |bm|
10
+ Dir.glob("spec/fixtures/*.raw").each do |file|
11
+ output = File.read(file)
12
+ basename = File.basename(file)
13
+
14
+ bm.report basename do
15
+ Timeout.timeout(100) do
16
+ Terminal.render(output)
17
+ end
18
+ end
19
+ end
20
+ end