terminal 0.2 → 0.3.beta1

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.
@@ -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