cheap-impression-presenter 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.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ CHIMP - Cheap IMpression Presenter
2
+ ==================================
3
+
4
+ __ _____
5
+ ___ / /___ / _ \ \ ___
6
+ / __| |/ _ \ | | | | |__ \
7
+ | (__| | (_) | | |_| | |__) |
8
+ \___| |\_!!(_|_)!!__| |___/
9
+ \_\ /_/
10
+
11
+ Make a cheap impression. Present via terminal.
12
+
13
+ ```gem install chimp```
14
+
15
+ It has a simple syntax:
16
+
17
+ #what: the title of presentation
18
+
19
+ --- splits slides
20
+
21
+ +++ slides parts to appear
22
+
23
+ !!Bold Text!!
24
+ ``Red Text``
25
+ %%Blue Text%%
26
+
27
+ Everything else is in plugins. The following plugins are existing for now:
28
+
29
+ * Sixel: display graphics in the terminal: `` ```sixel name.png``.
30
+ * Figlet: display fancy ascii text: `` ```figlet FANCY_TEXT``.
31
+ * Line Numbers: display line numbers beforce any text.
32
+ ~~~text
33
+ ```figlet
34
+ Line
35
+ Line
36
+ Line
37
+ ```
38
+ ~~~
39
+
40
+ During presenting you can use the usual button for navigating. In addition, you
41
+ can use ``q`` to quit the presentation, and ``r`` to refresh the screen.
42
+
43
+ Check out the examples.
44
+
45
+ History & Thanks
46
+ ================
47
+
48
+ Amy Hoy allowed me to use the logo for CHIMP all the way back in 2009. The
49
+ CHIMP was written as an icebreaker for the curucamp, which was a ruby
50
+ un-conference in 2009 in Vienna. We had fun, and many cool people from the
51
+ community attended.
52
+
53
+ I am still using the CHIMP for scientifc presentations, and as sixel terminals
54
+ finally are more common, I tried to polish it up (2025) and make it available
55
+ for easy consumption. Maybe you like it. I use it because I can create
56
+ presentations faster than in PP, KN, other programs. Most of the work goes
57
+ into graphics anyway, which I create with dedicated tools.
58
+
59
+ Drop me a note if you want to be a contributor, or send me merge request if you
60
+ have created some plugins. Creating plugins is very simple :-)
61
+
62
+ Missing Features
63
+ ================
64
+
65
+ Presenting is nice. A PDF output mode would be useful. To send the presentation
66
+ to people in one file.
67
+
68
+ LICENSE
69
+ =======
70
+
71
+ GPL 3.0 or later.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rake'
2
+ require 'rubygems/package_task'
3
+
4
+ spec = eval(File.read('chimp.gemspec'))
5
+ Gem::PackageTask.new(spec) do |pkg|
6
+ pkg.need_zip = true
7
+ pkg.need_tar = true
8
+ puts `ls -al pkg/*`
9
+ `rm pkg/* -rf`
10
+ `ln -sf #{pkg.name}.gem pkg/chimp.gem`
11
+ end
12
+
13
+ task :push => :gem do |r|
14
+ `gem push pkg/chimp.gem`
15
+ end
16
+
17
+ task :install => :gem do |r|
18
+ `gem install pkg/chimp.gem`
19
+ end
20
+
21
+ # task :release => :gem do |r|
22
+ # GIT_COMMITTER_DATE="$(git show develop --format=%aD | head -1)" \
23
+ # git tag -a "v1.7.0" develop -m "tag v1.7.0" \
24
+ # && git push --tags origin develop \
25
+ # && git --no-pager tag --list --format='%(refname) %(taggerdate)'
26
+ # end
data/chimp.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "cheap-impression-presenter"
3
+ s.version = "1.0.0"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.license = "GPL-3.0-or-later"
6
+ s.summary = "CHIMP - Make a cheap impression. Present via terminal."
7
+
8
+ s.description = "see https://github.com/etm/chimp"
9
+
10
+ s.files = Dir['{example/*.txt,example/*.png,example/*.svg,tools/**/*,lib/**/*,}'] + %w(LICENSE INSTALL.md Rakefile chimp.gemspec AUTHORS)
11
+ s.require_path = 'lib'
12
+ s.extra_rdoc_files = ['README.md','INSTALL.md']
13
+ s.bindir = 'tools'
14
+ s.executables = ['chimp']
15
+
16
+ s.required_ruby_version = '>=2.7.0'
17
+
18
+ s.authors = ['Juergen eTM Mangler']
19
+
20
+ s.email = 'juergen.mangler@gmail.com'
21
+ s.homepage = 'https://github.com/etm/chimp'
22
+
23
+ s.add_runtime_dependency 'tty-screen', '~> 0.8'
24
+ s.add_runtime_dependency 'pastel', '~> 0.8'
25
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'ChimpParser'
2
+
3
+ module Chimp
4
+ class Parser
5
+
6
+ class SimpleGrammar < Grammar
7
+ #### Patterns ####
8
+ #### Name of pattern has to be the first parameter to pattern ####
9
+ #### According to the names the methods are selected ####
10
+
11
+ P_SLIDES = Pattern.new("P_SLIDES", /---+.*?(\z|\n)|\A/, /^(?=---+)|\z/)
12
+ P_WHAT = Pattern.new("P_WHAT", /^#\s*what:\s*/, /[\t ]*(\n|\z)/)
13
+ P_INCREMENTAL = Pattern.new("P_INCREMENTAL", /\+\+\++.*?(\z|\n)|\A/, /^(?=\+\+\++)|\z/)
14
+ P_RANGE = Pattern.new("P_RANGE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]*/, /^```[\t ]*(\n|\z)/)
15
+ P_INCLUDE = Pattern.new("P_INCLUDE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]+/, /[\t ]*(\n|\z)/)
16
+ P_CENTER = Pattern.new("P_CENTER", /^~~/, /[\t ]*(\n|\z)/)
17
+ P_STRONG = Pattern.new("P_STRONG", /!!/, /!!/)
18
+ P_RED = Pattern.new("P_RED", /''/, /''/)
19
+ P_BLUE = Pattern.new("P_BLUE", /%%/, /%%/)
20
+ P_STRONGRED = Pattern.new("P_STRONGRED", /(!!''|''!!)/, /(''!!|!!'')/)
21
+ P_STRONGBLUE = Pattern.new("P_STRONGBLUE", /(!!%%|%%!!)/, /(%%!!|!!%%)/)
22
+
23
+ #### Grammar ####
24
+ #### ROOT must exist and holds the main first level patterns ####
25
+ #### G + patternname is selected to look up nested patterns ####
26
+ #### If G + patternname not exists, [] is assumend ####
27
+
28
+ ROOT = [ P_WHAT, P_SLIDES ]
29
+ GP_SLIDES = [ P_INCREMENTAL ]
30
+ GP_INCREMENTAL = [ P_CENTER, P_RANGE, P_INCLUDE, P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
31
+
32
+ ### Optional functions called when a pattern occurs (m + patternname) ####
33
+ def mP_CENTER(ts,ti,te)
34
+ @tree.last.data = ti.length
35
+ ti
36
+ end
37
+
38
+ def mP_INCLUDE(ts,ti,te)
39
+ @tree.last.data = {}
40
+ @tree.last.data[:name] = ts[3..-1].strip.split(',')[0]
41
+ @tree.last.data[:parameters] = ts[3..-1].strip.split(',')[1..-1]
42
+ @tree.last.data[:additional] = @parameters
43
+ @tree.last.data[:what] = ti.lstrip
44
+ ''
45
+ end
46
+ def mP_RANGE(ts,ti,te)
47
+ @tree.last.data = {}
48
+ @tree.last.data[:name] = ts[3..-1].strip.split(',')[0]
49
+ @tree.last.data[:parameters] = ts[3..-1].strip.split(',')[1..-1]
50
+ @tree.last.data[:what] = ti.lstrip
51
+ ''
52
+ end
53
+ def mP_WHAT(ts,ti,te)
54
+ @tree.last.data = ti
55
+ ''
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,215 @@
1
+ require 'strscan'
2
+ require 'pp'
3
+
4
+ module Chimp
5
+ class Parser
6
+ class OpenTag
7
+ attr_reader :ttype, :level
8
+ attr_accessor :data, :close, :userdata
9
+ def initialize(ttype,level)
10
+ @ttype = ttype
11
+ @data = nil
12
+ @close = -1
13
+ @userdata = nil
14
+ @level = level
15
+ end
16
+ end
17
+ class CloseTag
18
+ attr_reader :ttype, :open, :position_in_markup, :level
19
+ def initialize(ttype,open,position_in_markup,level)
20
+ @ttype = ttype
21
+ @open = open
22
+ @position_in_markup = position_in_markup
23
+ @level = level
24
+ end
25
+ end
26
+
27
+ class TagEvent < Exception; end
28
+ class TagSkipEvent < Exception; end
29
+ class TagMoveEvent < Exception; end
30
+ class TagContentSkipEvent < Exception; end
31
+
32
+ class Output
33
+ def finish_prepare
34
+ end
35
+ def finish_output
36
+ ""
37
+ end
38
+ def string(data)
39
+ data
40
+ end
41
+ end
42
+
43
+ class Grammar
44
+ attr_reader :tree # a list which pretends to be a tree :-)
45
+
46
+ class Pattern
47
+ attr_reader :ptype, :pstart, :pend, :bol
48
+ def initialize(ptype, pstart, pend, bol=false)
49
+ @ptype = ptype
50
+ @pstart = pstart
51
+ @pend = pend
52
+ @bol = bol
53
+ end
54
+ end
55
+
56
+ def initialize
57
+ @tree = []
58
+ @parameters = {}
59
+ end
60
+
61
+ def Grammar.parse(text,params = {})
62
+ new.parse(text,params)
63
+ end
64
+
65
+ def parse(text,params = {})
66
+ @parameters = params
67
+ gparse(text,self.class::constants.include?(:ROOT) ? self.class::ROOT : [])
68
+ self
69
+ end
70
+
71
+ def inspect
72
+ out = ""; indent = ""
73
+ @tree.each do |c|
74
+ indent = indent[0..-3] if c.class == CloseTag
75
+ out << indent + c.inspect + "\n"
76
+ indent << " " if c.class == OpenTag
77
+ end
78
+ out
79
+ end
80
+
81
+ def output(output)
82
+ out = ''; i = 0
83
+ while @tree.length > i
84
+ c = @tree[i]
85
+ begin
86
+ met = case
87
+ when c.class == String
88
+ data = c
89
+ output.method("string")
90
+ when c.class == OpenTag
91
+ data = c.data
92
+ output.method("mO" + c.ttype)
93
+ when c.class == CloseTag
94
+ data = @tree[c.open].data
95
+ output.method("mC" + c.ttype)
96
+ end
97
+ out << case met.arity
98
+ when 0; met.call
99
+ when 1; met.call(data)
100
+ when 2; met.call(c,@tree)
101
+ when 3; met.call(c,@tree,i)
102
+ when 4; met.call(c,@tree,i,data)
103
+ else
104
+ ""
105
+ end.to_s
106
+
107
+ i += 1
108
+ rescue NameError => e
109
+ i += 1
110
+ rescue TagSkipEvent
111
+ i = c.close + 1 if c.class == OpenTag
112
+ rescue TagContentSkipEvent
113
+ i = c.close if c.class == OpenTag
114
+ rescue TagMoveEvent => e
115
+ i = e.message.to_i
116
+ ensure
117
+ begin
118
+ unless @tree.length > i
119
+ met = output.method("finish_output")
120
+ out << case met.arity
121
+ when 0; met.call
122
+ when 1; met.call(@tree)
123
+ else
124
+ ""
125
+ end.to_s
126
+ end
127
+ rescue TagMoveEvent => e
128
+ i = e.message.to_i
129
+ end
130
+ end
131
+ end
132
+ out
133
+ end
134
+
135
+ def prepare(output)
136
+ @tree.each_with_index do |c,i|
137
+ begin
138
+ if c.class == OpenTag
139
+ met = output.method("mP" + c.ttype)
140
+ case met.arity
141
+ when 0; met.call
142
+ when 1; met.call(c.data)
143
+ when 2; met.call(c,@tree)
144
+ when 3; met.call(c,@tree,i)
145
+ end
146
+ end
147
+ rescue NameError
148
+ rescue TagSkipEvent
149
+ rescue TagContentSkipEvent
150
+ rescue TagMoveEvent
151
+ end
152
+ end
153
+ output.method("finish_prepare").call
154
+ self
155
+ end
156
+
157
+ def gparse(text,grammar,position_in_markup=0,level=0)
158
+ return if text.nil?
159
+ s = StringScanner.new(text)
160
+ while !s.eos?
161
+ success = false
162
+ grammar.each do |pat|
163
+ if s.match?(pat.pstart)
164
+ pos = s.pos
165
+ bol = (pos == 0) ? true : s.string[pos-1] == "\n"
166
+ ts = s.scan(pat.pstart)
167
+ te = if s.eos?
168
+ "" =~ pat.pend ? "" : nil
169
+ else
170
+ (pat.bol && !bol) ? nil : s.scan_until(pat.pend)
171
+ end
172
+
173
+ if te.nil?
174
+ s.pos = pos
175
+ else
176
+ ti = te.sub(pat.pend,"")
177
+ te = te[ti.length..-1]
178
+ @tree << ot = OpenTag.new(pat.ptype,level)
179
+ tpos = @tree.length-1
180
+ inner = begin
181
+ self.method("m" + pat.ptype).call(ts,ti,te)
182
+ rescue NameError
183
+ ti
184
+ rescue TagEvent
185
+ @tree.pop
186
+ s.pos = pos
187
+ next
188
+ rescue TagSkipEvent
189
+ @tree.pop
190
+ success = true
191
+ next
192
+ end
193
+ gparse(inner,self.class::constants.include?(("G" + pat.ptype).to_sym) ? self.class::const_get(("G" + pat.ptype).to_sym) : [],position_in_markup+pos+ts.length,level+1)
194
+ @tree << CloseTag.new(pat.ptype,tpos,position_in_markup+s.pos,level)
195
+ ot.close = @tree.length-1
196
+ success = true
197
+ break
198
+ end
199
+ end
200
+ end
201
+ unless success
202
+ if @tree.last.class == String
203
+ @tree.last << s.getch
204
+ else
205
+ @tree << s.getch
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ private :gparse
212
+ end
213
+
214
+ end
215
+ end
@@ -0,0 +1,7 @@
1
+ module Chimp
2
+ module Plugin
3
+ class Base
4
+ def initialize; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,248 @@
1
+ require 'pastel'
2
+ require 'tty-screen'
3
+ require 'io/console'
4
+
5
+ module Window #{{{
6
+ class WinchError < Exception; end
7
+ def self::init()
8
+ self::clear
9
+ $stdout << "\e[?25l"
10
+ $stdout.flush
11
+ Signal.trap("WINCH") do
12
+ raise WinchError, 'nope'
13
+ end
14
+ nil
15
+ end
16
+ def self::finish()
17
+ self::clear
18
+ self::set_pos 0, 0
19
+ $stdout << "\e[?25h"
20
+ $stdout.flush
21
+ nil
22
+ end
23
+ def self::getch()
24
+ loop do
25
+ case $stdin.getch
26
+ when 'r' then return :refresh
27
+ when 'q' then return :exit
28
+ when "\r" then return :enter
29
+ when "\e"
30
+ case $stdin.getch
31
+ when '['
32
+ case $stdin.getch
33
+ when 'A' then return :previous
34
+ when 'B' then return :next
35
+ when 'C' then return :next
36
+ when 'D' then return :previous
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ def self::lines()
43
+ TTY::Screen.lines
44
+ end
45
+ def self::columns()
46
+ TTY::Screen.columns
47
+ end
48
+ def self::clear()
49
+ $stdout << "\e[2J"
50
+ $stdout.flush
51
+ self
52
+ end
53
+ def self::set_pos(x,y)
54
+ $stdout << "\e[#{y};#{x}H"
55
+ $stdout.flush
56
+ self
57
+ end
58
+ def self::print(a)
59
+ $stdout.write a
60
+ $stdout.flush
61
+ end
62
+ def self::get_pos
63
+ res = ''
64
+ $stdin.raw do |stdin|
65
+ $stdout << "\e[6n"
66
+ $stdout.flush
67
+ while (c = $stdin.getc) != 'R'
68
+ res << c if c
69
+ end
70
+ end
71
+ m = res.match /(?<column>\d+);(?<row>\d+)/
72
+ [ Integer(m[:row]), Integer(m[:column]) ]
73
+ rescue WinchErrror
74
+ [0,0]
75
+ end
76
+ end #}}}
77
+
78
+ module Chimp
79
+ class Parser
80
+
81
+ class Screen < Output
82
+ #### Optional functions called when a tree is tranformed to output ####
83
+ #### mO + patternname for opening tags ####
84
+ #### mC + patternname for closing tags ####
85
+
86
+ def initialize() #{{{
87
+ @scounter = @ccounter = 0
88
+ @what = ''
89
+ @last = 0
90
+ @skip = -1
91
+
92
+ @format = Pastel.new
93
+ @screen = Window
94
+
95
+ @screen.init
96
+ end #}}}
97
+
98
+ def finish_output #{{{
99
+ @screen.set_pos 0, @screen.lines
100
+ @screen.print ' ' * (@screen.columns - 5)
101
+ @screen.set_pos 0, @screen.lines
102
+ end #}}}
103
+
104
+ def mPP_WHAT(data)
105
+ @what = data
106
+ end
107
+
108
+ def mPP_SLIDES(c,tree)
109
+ #{{{
110
+ @scounter += 1
111
+ @ccounter += 1
112
+ c.userdata = @ccounter
113
+ #}}}
114
+ end
115
+ def mOP_SLIDES(c,tree)
116
+ #{{{
117
+ @screen.clear
118
+ lines = @screen.lines
119
+ columns = @screen.columns
120
+ ### print mark
121
+ @screen.set_pos columns-1, 30
122
+ @screen.print "\e[1;37m^\e[0m"
123
+ ### print footer
124
+ @screen.set_pos 0, lines-1
125
+ @screen.print "-"*columns
126
+ @screen.set_pos 0, lines
127
+ @screen.print @what
128
+ num = "#{c.userdata}/#{@scounter}"
129
+ @screen.set_pos columns-num.length, lines
130
+ @screen.print num
131
+ @screen.set_pos 0, 0
132
+ #}}}
133
+ end
134
+
135
+ def mPP_INCREMENTAL(c,tree,i)
136
+ @last = c.close
137
+ end
138
+ def mOP_INCREMENTAL(c,tree)
139
+ x, y = @screen.get_pos
140
+ c.userdata = { :x => x, :y => y }
141
+ end
142
+ def mCP_INCREMENTAL(c,tree,i)
143
+ return if @skip > i
144
+ @skip = -1
145
+ begin
146
+ if @last == i
147
+ lines = @screen.lines
148
+ columns = @screen.columns
149
+ @screen.set_pos 0, lines
150
+ @screen.print "Press 'q' to finish making a cheap impression..."
151
+ end
152
+ begin
153
+ ch = @screen.getch
154
+ rescue Window::WinchError
155
+ ch = :refresh
156
+ end
157
+ end while @last == i && ![:previous, :exit, :refresh].include?(ch)
158
+ case ch
159
+ when :previous
160
+ #{{{
161
+ pos = c.open-1
162
+ tag = tree[pos]
163
+ if tag.ttype == "P_SLIDES"
164
+ if pos == 0
165
+ raise TagMoveEvent, pos
166
+ else
167
+ raise TagMoveEvent, tree[pos-1].open
168
+ end
169
+ end
170
+ if tag.ttype == "P_INCREMENTAL"
171
+ pos = tag.open
172
+ tag = tree[pos]
173
+ columns = @screen.columns
174
+ #### get current position
175
+ x, y = @screen.get_pos
176
+ #### how many spaces needed
177
+ howmuch = ((y - tag.userdata[:y] + 1) * columns) - (columns - x) - tag.userdata[:x]
178
+ @screen.set_pos tag.userdata[:x], tag.userdata[:y]
179
+ @screen.print x.to_s + ',' + y.to_s
180
+ @screen.print ' ' * howmuch
181
+ @screen.set_pos tag.userdata[:x], tag.userdata[:y]
182
+ ####
183
+ raise TagMoveEvent, pos
184
+ end
185
+ #}}}
186
+ when :refresh
187
+ #{{{
188
+ (c.open-1).downto(0) do |b|
189
+ if tree[b].class == OpenTag && tree[b].ttype == "P_SLIDES"
190
+ @skip = i
191
+ raise TagMoveEvent, b
192
+ end
193
+ end
194
+ #}}}
195
+ when :exit
196
+ @screen.finish
197
+ exit
198
+ end
199
+ end
200
+ def mOP_INCLUDE(data)
201
+ require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
202
+ eval('Chimp::Plugin::' + data[:name].upcase).new.process(data[:what],data[:parameters],@screen,data[:additional])
203
+ end
204
+ def mOP_RANGE(data)
205
+ require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
206
+ eval('Chimp::Plugin::' + data[:name].upcase).new.process(data[:what],data[:parameters],@screen,data[:additional])
207
+ end
208
+
209
+ def mOP_STRONG(data); @screen.print "\e[1m"; end
210
+ def mCP_STRONG(data); @screen.print "\e[0m"; end
211
+ def mOP_RED(data); @screen.print "\e[0;31m"; end
212
+ def mCP_RED(data); @screen.print "\e[0m"; end
213
+ def mOP_BLUE(data); @screen.print "\e[0;34m"; end
214
+ def mCP_BLUE(data); @screen.print "\e[0m"; end
215
+ def mOP_STRONGRED(data); @screen.print "\e[1;31m"; end
216
+ def mCP_STRONGRED(data); @screen.print "\e[0m"; end
217
+ def mOP_STRONGBLUE(data); @screen.print "\e[1;34m"; end
218
+ def mCP_STRONGBLUE(data); @screen.print "\e[0m"; end
219
+
220
+ def mOP_CENTER(data)
221
+ len = @screen.columns
222
+ if len-data > 0
223
+ @screen.print ' ' * ((len - data) / 2)
224
+ end
225
+ end
226
+ def mCP_CENTER(data)
227
+ @screen.print "\n"
228
+ end
229
+
230
+ def string(data)
231
+ @screen.print(data)
232
+ end
233
+
234
+ def p(what)
235
+ #{{{
236
+ lines = @screen.lines
237
+ @screen.set_pos 0, lines-1
238
+ @screen.print what.inspect
239
+ x, y = @screen.get_pos
240
+ @screen.set_pos x, y
241
+ @screen.getch
242
+ #}}}
243
+ end
244
+ private :p
245
+
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,23 @@
1
+ module Chimp
2
+ module Plugin
3
+ class FIGLET < Base
4
+ def process(what,parameters,screen,additional)
5
+ if parameters.include? 'right'
6
+ pos = screen.get_pos
7
+ len = screen.columns
8
+ `figlet #{what}`.each_line do |l|
9
+ screen.print " " * (len-l.length+1) + l
10
+ end
11
+ screen.set_pos *pos
12
+ elsif parameters.include? 'center'
13
+ len = screen.columns
14
+ `figlet #{what}`.each_line do |l|
15
+ screen.print " " * ((len-l.length+1)/2) + l
16
+ end
17
+ else
18
+ screen.print `figlet #{what}`
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Chimp
2
+ module Plugin
3
+ class LNUMS < Base
4
+ def process(what,parameters,screen,additional)
5
+ i = 0
6
+ res = what.each_line.map do |l|
7
+ i += 1
8
+ ("%02i: " % i) + l
9
+ end.join('')
10
+ screen.print res
11
+ end
12
+ end
13
+ end
14
+ end