wee-pm 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ class ColorizeFilter
2
+ def initialize(ruby_bin="ruby", colorize_bin=File.join(File.dirname(__FILE__), "colorize.rb"))
3
+ @ruby_bin, @colorize_bin = ruby_bin, colorize_bin
4
+ end
5
+
6
+ def run(lines, port, lang="__auto__", filename="")
7
+ lang ||= '__auto__'
8
+
9
+ cmd = "#{ @ruby_bin } #{ @colorize_bin } --cache --terminal=xterm-color --strip-ws --lang=#{ lang }"
10
+ if lines.nil?
11
+ cmd << " " + filename
12
+ end
13
+
14
+ IO.popen(cmd, "w+") { |f|
15
+ if lines
16
+ f << lines
17
+ f.close_write
18
+ end
19
+ port << f.read
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,104 @@
1
+ require 'wee-pm/colorize_filter'
2
+
3
+ class HtmlConverter
4
+ def count_overlays(content)
5
+ conv = converter()
6
+ conv.count = true
7
+ SlidesMarkup.new.convert(content, conv)
8
+ end
9
+
10
+ def render_on(r, overlay, content)
11
+ conv = converter()
12
+ conv.count = false
13
+ conv.show_number_of_overlays = overlay
14
+ if overlay > 0
15
+ @r = r
16
+ r << SlidesMarkup.new.convert(content, conv)
17
+ end
18
+ end
19
+
20
+ def render_annotations_on(r, annotations)
21
+ conv = converter()
22
+ conv.count = false
23
+ conv.show_number_of_overlays = nil
24
+ @r = r
25
+ r << SlidesMarkup.new.convert(annotations, conv)
26
+ end
27
+
28
+
29
+ private
30
+
31
+ def converter
32
+ conv = SlidesHtml.new
33
+ conv.block_processor = proc {|processor, option, lines|
34
+ port = ""
35
+ case processor
36
+ when 'colorize'
37
+ lang, *more = option.split(",")
38
+ h = {}
39
+ more.map {|i| k, v = i.split("="); h[k] = v}
40
+
41
+ lines = File.read(h['file']) if h['file']
42
+
43
+ if h['exec']
44
+ require 'tempfile'
45
+ f = Tempfile.new('pm')
46
+ f << "#!/bin/sh\n"
47
+ f << h['exec']
48
+ f << "\nread line\n"
49
+ f.close(false)
50
+ File.chmod(0755, f.path)
51
+ url = @r.url_for_callback(proc { `xterm #{ f.path }`; f.unlink })
52
+ port << %[<a class="codelink" href="#{ url }">execute</a>]
53
+ port << "<pre class='codefile'>"
54
+ else
55
+ port << "<pre>"
56
+ end
57
+ ColorizeFilter.new.run(lines, port, lang)
58
+ port << "</pre>\n"
59
+ when 'exec'
60
+ url = @r.url_for_callback(proc { `#{ lines }` })
61
+ port << %[<a class="codelink" href="#{ url }">execute</a>]
62
+ unless option == "hidden"
63
+ port << "<pre class='codefile'>"
64
+ port << lines
65
+ port << "</pre>\n"
66
+ end
67
+ #when 'file'
68
+ else
69
+ raise "unknown processor"
70
+ end
71
+ port
72
+ }
73
+ conv
74
+ end
75
+
76
+ end
77
+
78
+ class PsHtmlConverter < HtmlConverter
79
+
80
+ private
81
+
82
+ def converter
83
+ conv = SlidesHtml.new
84
+ conv.block_processor = proc {|processor, option, lines|
85
+ port = ""
86
+ case processor
87
+ when 'colorize'
88
+ port << "<pre>"
89
+ ColorizeFilter.new.run(lines, port, option)
90
+ port << "</pre>\n"
91
+ when 'exec'
92
+ port << "<pre class='codefile'>"
93
+ port << "!!#{processor}#{ option ? ':' + option : ''}\n"
94
+ port << lines
95
+ port << "</pre>\n"
96
+ else
97
+ raise "unknown processor"
98
+ end
99
+ port
100
+ }
101
+ conv
102
+ end
103
+
104
+ end
@@ -0,0 +1,152 @@
1
+ require 'wee-pm/slideshtml'
2
+ require 'wee-pm/utils'
3
+
4
+ # A presentation is mainly a collection of slides.
5
+
6
+ class Presentation
7
+ attr_accessor :title, :author, :email, :style
8
+ attr_reader :slides
9
+
10
+ class << self
11
+ def from_string(str)
12
+ positions = [0]
13
+ str.scan(/^=([^=].*)$/) { positions << $~.offset(0).first }
14
+ slides = split_at(str, positions)
15
+
16
+ new {|pres|
17
+ pres.title = $1.strip if slides[0] =~ /^title::(.*)$/
18
+ pres.author = $1.strip if slides[0] =~ /^author::(.*)$/
19
+ pres.email = $1.strip if slides[0] =~ /^email::(.*)$/
20
+ pres.style = $1.strip if slides[0] =~ /^style::(.*)^endstyle::$/m
21
+
22
+ slides[1..-1].each do |s|
23
+ pres.slides << Slide.from_string(s)
24
+ end
25
+ }
26
+ end
27
+
28
+ private
29
+
30
+ def split_at(s, pos)
31
+ (0..(pos.size-2)).collect {|i| s[pos[i]...pos[i+1]] } + [s[pos[-1]..-1]]
32
+ end
33
+ end
34
+
35
+ def initialize
36
+ @title = 'unnamed presentation'
37
+ @author = ''
38
+ @email = ''
39
+ @style = ''
40
+ @slides = []
41
+ yield self if block_given?
42
+ end
43
+
44
+ def to_s
45
+ str = "title:: #{ @title }\n" +
46
+ "author:: #{ @author }\n" +
47
+ "email:: #{ @email }\n"
48
+ unless @style.strip.empty?
49
+ str << "style::\n"
50
+ str << @style
51
+ str << "\n"
52
+ str << "endstyle::\n"
53
+ end
54
+
55
+ str << "\n"
56
+
57
+ @slides.each { |s| str << s.to_s }
58
+ str.gsub!("\r\n", "\n")
59
+ str
60
+ end
61
+
62
+ def save(filename)
63
+ File.open(filename, 'w+') {|f| f << self.to_s }
64
+ end
65
+ end
66
+
67
+ # Each slide consists of one or more overlays.
68
+ class Slide
69
+ attr_accessor :title
70
+ attr_accessor :content # the rdoc content of the slide (excluding the title)
71
+ attr_accessor :annotations
72
+ attr_accessor :converter
73
+ attr_accessor :style
74
+
75
+ def content=(str)
76
+ @content = remove_leading_and_trailing_empty_lines(str)
77
+ end
78
+
79
+ def annotations=(str)
80
+ @annotations = remove_leading_and_trailing_empty_lines(str)
81
+ end
82
+
83
+ def to_s
84
+ str = "= #{ @title }\n\n" + @content + "\n"
85
+ unless @annotations.strip.empty?
86
+ str << "annotations::\n"
87
+ str << @annotations
88
+ str << "\n"
89
+ str << "endannotations::\n"
90
+ end
91
+ unless @style.strip.empty?
92
+ str << "style::\n"
93
+ str << @style
94
+ str << "\n"
95
+ str << "endstyle::\n"
96
+ end
97
+ str << "\n"
98
+ str
99
+ end
100
+
101
+ def self.from_string(str)
102
+ new {|s|
103
+ if str =~ /^=([^=].*)$/
104
+ str = $~.post_match
105
+ s.title = $1.strip
106
+ end
107
+ if str =~ /^\w+::/
108
+ s.content = $~.pre_match
109
+ if str =~ /^annotations::(.*)^endannotations::$/m
110
+ s.annotations = $1.strip
111
+ end
112
+ if str =~ /^style::(.*)^endstyle::$/m
113
+ s.style = $1.strip
114
+ end
115
+ else
116
+ s.content = str
117
+ end
118
+ }
119
+ end
120
+
121
+ def initialize
122
+ @number_of_overlays = []
123
+ @title = 'unnamed slide'
124
+ @content = ''
125
+ @annotations = ''
126
+ @style = ''
127
+ yield self if block_given?
128
+ end
129
+
130
+ def number_of_overlays
131
+ if @content != @number_of_overlays[1]
132
+ # content has changed -> re-calculate the number of overlays
133
+ num =
134
+ if @content.strip.empty?
135
+ 0
136
+ else
137
+ converter().new.count_overlays(@content)
138
+ end
139
+ @number_of_overlays = [num, @content.dup]
140
+ end
141
+ @number_of_overlays[0]
142
+ end
143
+
144
+ def render_on(r, overlay)
145
+ converter().new.render_on(r, overlay, @content)
146
+ end
147
+
148
+ def render_annotations_on(r)
149
+ converter().new.render_annotations_on(r, @annotations)
150
+ end
151
+
152
+ end
@@ -0,0 +1,395 @@
1
+ require 'wee-pm/presentation'
2
+ require 'wee-pm/converter'
3
+
4
+ class PresentationMaker < Wee::Component
5
+
6
+ AREA_COLS = 90
7
+ DEFAULT_CONVERTER = HtmlConverter
8
+
9
+ def initialize(filename)
10
+ super()
11
+ @filename = filename
12
+ @presentation =
13
+ if File.exists? @filename
14
+ Presentation.from_string(File.read(@filename))
15
+ else
16
+ Presentation.new
17
+ end
18
+
19
+ if @presentation.slides.empty?
20
+ @presentation.slides << Slide.new
21
+ end
22
+
23
+ @presentation.slides.each do |sl|
24
+ sl.converter = DEFAULT_CONVERTER
25
+ end
26
+
27
+ @edit_mode = false
28
+
29
+ @show_title_page = true
30
+ end
31
+
32
+
33
+ # --------------------------------------------------------------------------
34
+ # Actions: Show
35
+ # --------------------------------------------------------------------------
36
+
37
+ def show_title_page
38
+ @show_title_page = true
39
+ @current_slide_index = @current_slide = @current_overlay = nil
40
+ end
41
+
42
+ def select_slide(index)
43
+ @show_title_page = false
44
+
45
+ @current_slide_index = index
46
+ @current_slide = @presentation.slides[@current_slide_index]
47
+ @current_overlay = 0
48
+ end
49
+
50
+ def show_all_overlays
51
+ @current_overlay = @current_slide.number_of_overlays
52
+ end
53
+
54
+ def next_slide_or_overlay
55
+ if @show_title_page
56
+ @show_title_page = false
57
+ select_slide(0)
58
+ elsif @current_overlay >= @current_slide.number_of_overlays
59
+ if @current_slide_index < @presentation.slides.size-1
60
+ select_slide(@current_slide_index + 1)
61
+ else
62
+ # TODO: show End-Box
63
+ end
64
+ else
65
+ @current_overlay += 1
66
+ end
67
+
68
+ unless @step_mode
69
+ @current_overlay = @current_slide.number_of_overlays
70
+ end
71
+ end
72
+
73
+ def prev_slide_or_overlay
74
+ # we are already on the first page -> do nothing!
75
+ return if @show_title_page
76
+
77
+ if @step_mode
78
+ @current_overlay -= 1
79
+ return if @current_overlay >= 0
80
+ end
81
+
82
+ @current_slide_index -= 1
83
+ if @current_slide_index < 0
84
+ show_title_page()
85
+ else
86
+ select_slide(@current_slide_index)
87
+ @current_overlay = @current_slide.number_of_overlays
88
+ end
89
+ end
90
+
91
+ # --------------------------------------------------------------------------
92
+ # Actions: Edit
93
+ # --------------------------------------------------------------------------
94
+
95
+ def add_new_slide_before
96
+ index = @current_slide_index
97
+ @presentation.slides[index,0] = Slide.new {|s|
98
+ s.title = @current_slide.title
99
+ s.converter = DEFAULT_CONVERTER
100
+ }
101
+ select_slide(index)
102
+ end
103
+
104
+ def add_new_slide_after
105
+ @current_slide_index += 1
106
+ add_new_slide_before
107
+ end
108
+
109
+ def delete_current_slide
110
+ return if @presentation.slides.size <= 1
111
+ @presentation.slides.delete_at(@current_slide_index)
112
+ select_slide([@current_slide_index, @presentation.slides.size-1].min)
113
+ end
114
+
115
+ def move_current_slide_up
116
+ sl = @presentation.slides
117
+ ci = @current_slide_index
118
+ return if ci <= 0
119
+ sl[ci-1], sl[ci] = sl[ci], sl[ci-1]
120
+ select_slide(ci-1)
121
+ end
122
+
123
+ def move_current_slide_down
124
+ sl = @presentation.slides
125
+ ci = @current_slide_index
126
+ return if ci >= sl.size-1
127
+ sl[ci+1], sl[ci] = sl[ci], sl[ci+1]
128
+ select_slide(ci+1)
129
+ end
130
+
131
+ def save_presentation
132
+ @presentation.save(@filename)
133
+ end
134
+
135
+ def toggle_mode
136
+ @edit_mode = !@edit_mode
137
+ end
138
+
139
+ def toggle_step_mode
140
+ @step_mode = !@step_mode
141
+ end
142
+
143
+ # --------------------------------------------------------------------------
144
+ # Rendering
145
+ # --------------------------------------------------------------------------
146
+
147
+ def render
148
+ r.html do
149
+ r.head do
150
+ r.title("Presentation Maker #{ @filename }")
151
+ r.style(@presentation.style)
152
+ if @current_slide
153
+ r.style(@current_slide.style)
154
+ end
155
+ end
156
+
157
+ body = r.body
158
+
159
+ #if not @edit_mode
160
+ # body.onclick(javascript_action {||next_slide_or_overlay})
161
+ #end
162
+
163
+ body.with do
164
+
165
+ if not @edit_mode
166
+ page_down = r.url_for_callback(proc {
167
+ next_slide_or_overlay()
168
+ })
169
+ page_up = r.url_for_callback(proc {
170
+ prev_slide_or_overlay()
171
+ })
172
+ toggle_mode_url = r.url_for_callback(proc {
173
+ toggle_mode()
174
+ })
175
+ toggle_step_mode_url = r.url_for_callback(proc {
176
+ toggle_step_mode()
177
+ })
178
+
179
+ r << %{
180
+ <script>
181
+ document.onkeypress = function(ev) {
182
+ switch (ev.keyCode) {
183
+ case 101: /* 'e' */
184
+ document.location.href='#{ toggle_mode_url }';
185
+ return false;
186
+ case 115: /* 's' */
187
+ document.location.href='#{ toggle_step_mode_url }';
188
+ return false;
189
+ case 34: /* page down */
190
+ document.location.href='#{ page_down }';
191
+ return false;
192
+ case 33: /* page up */
193
+ document.location.href='#{ page_up }';
194
+ return false;
195
+ };
196
+ return true;
197
+ };
198
+ </script>
199
+ }
200
+ end
201
+
202
+ r.span.id("navibutton").with do
203
+ r.anchor.callback { toggle_mode }.with do
204
+ r.space(2)
205
+ end
206
+ end
207
+ render_body
208
+ end
209
+ end
210
+ end
211
+
212
+ def render_slide
213
+ if @show_title_page
214
+ r.div.id('titlepage').css_class('slide').with do
215
+ r.h1.onclick(javascript_action {||next_slide_or_overlay}).
216
+ with(@presentation.title)
217
+
218
+ r.div.id('author_email').with do
219
+ r.text @presentation.author
220
+ r.text(" (")
221
+ r.anchor.href("mailto:#{ @presentation.email }").with(@presentation.email)
222
+ r.text(")")
223
+ end
224
+
225
+ end
226
+ elsif @current_slide
227
+ r.div.id('slides').css_class('slide'). with do
228
+ r.h1.onclick(javascript_action {||next_slide_or_overlay}).
229
+ with(@current_slide.title)
230
+ @current_slide.render_on(r, @current_overlay)
231
+ end
232
+ end
233
+ end
234
+
235
+ def render_navi
236
+ r.div.id('naviframe').with do
237
+ r.anchor.callback{toggle_mode}.with { r.text('Hide') }
238
+
239
+ r.text(" | ")
240
+
241
+ r.anchor.callback { toggle_step_mode }.with do
242
+ r.text('Change into Full-Slide mode') if @step_mode
243
+ r.text('Change into Step mode') if !@step_mode
244
+ end
245
+
246
+ r.text(" | ")
247
+ r.anchor.callback{save_presentation}.with { r.text('Save presentation') }
248
+ r.hr
249
+
250
+ r.text("Presentation: ")
251
+ if @show_title_page
252
+ r.text(@presentation.title)
253
+ else
254
+ r.anchor.callback{show_title_page}.with { r.text(@presentation.title) }
255
+ end
256
+ r.break
257
+
258
+ render_outline
259
+
260
+ r.hr
261
+
262
+ if @show_title_page
263
+ render_title_edit
264
+ else
265
+ render_slide_edit
266
+ end
267
+ end
268
+ end
269
+
270
+ def render_title_edit
271
+ r.form do
272
+ r.submit_button.value('Update')
273
+
274
+ r.table.with do
275
+ %w(title author email).each do |f|
276
+ r.table_row.with do
277
+ r.table_data("#{ f.capitalize }: ")
278
+ r.table_data.with {
279
+ r.text_input.size(35).
280
+ callback {|c| @presentation.send("#{ f }=", c.to_s) }.
281
+ value(@presentation.send("#{ f }"))
282
+ }
283
+ end
284
+ end
285
+ end # table
286
+
287
+ r.paragraph
288
+ r.text("Style:")
289
+ r.break
290
+ r.text_area.cols(AREA_COLS).rows(10).callback {|c| @presentation.style = c.to_s}.
291
+ with(@presentation.style)
292
+
293
+ end
294
+ end
295
+
296
+ def render_slide_edit
297
+ r.ul do
298
+ r.li {
299
+ r.text('Add new slide ')
300
+ r.anchor.callback{add_new_slide_before}.with { r.text('before') }; r.text(' / ')
301
+ r.anchor.callback{add_new_slide_after}.with { r.text('after') }; r.space
302
+ r.text('current')
303
+ }
304
+ r.li {
305
+ r.text('Current slide: ')
306
+ r.anchor.callback{move_current_slide_up}.with { r.text('move up') }; r.text(' / ')
307
+ r.anchor.callback{move_current_slide_down}.with { r.text('move down') }; r.text(' / ')
308
+ r.anchor.callback{delete_current_slide}.with { r.text('delete') }
309
+ }
310
+ end
311
+
312
+ r.hr
313
+
314
+ r.paragraph
315
+ r.form do
316
+ r.submit_button.value('Update').callback { @current_overlay = @current_slide.number_of_overlays }
317
+ r.space
318
+
319
+ r.text "Title: "
320
+ r.text_input.size(35).callback {|c| @current_slide.title = c.to_s}.value(@current_slide.title)
321
+
322
+ r.paragraph
323
+ r.text_area.cols(AREA_COLS).rows(30).callback {|c| @current_slide.content = c.to_s}.with(@current_slide.content)
324
+
325
+ r.paragraph
326
+ r.text "Annotations:"
327
+ r.break
328
+ r.text_area.cols(AREA_COLS).rows(10).callback {|c| @current_slide.annotations = c.to_s}.
329
+ with(@current_slide.annotations)
330
+
331
+ r.paragraph
332
+ r.text "Style:"
333
+ r.break
334
+ r.text_area.cols(AREA_COLS).rows(10).callback {|c| @current_slide.style = c.to_s}.
335
+ with(@current_slide.style)
336
+
337
+ end
338
+ end
339
+
340
+ def render_outline
341
+ r.ol {
342
+ @presentation.slides.each_with_index do |slide, i|
343
+ r.li do
344
+ if i == @current_slide_index
345
+ r.bold slide.title
346
+ r.break
347
+
348
+ (0 .. @current_slide.number_of_overlays).each do |j|
349
+ if j == @current_overlay
350
+ r.text "#{ j+1 }"
351
+ else
352
+ r.anchor.callback { @current_overlay = j }.with {
353
+ r.text "#{ j+1 }"
354
+ }
355
+ end
356
+ r.space
357
+ end
358
+ else
359
+ r.anchor.callback { select_slide(i); show_all_overlays() }.with {
360
+ r.text slide.title
361
+ }
362
+ end
363
+ end
364
+ end
365
+ }
366
+ end
367
+
368
+ def render_body
369
+ if not @edit_mode
370
+ render_slide
371
+ else
372
+ r.table do
373
+ r.table_row do
374
+
375
+ r.table_data.valign('top').with do
376
+ render_navi
377
+ end
378
+
379
+ r.table_data.valign('top').with do
380
+ render_slide
381
+ end
382
+
383
+ end
384
+ end
385
+ end
386
+ end
387
+
388
+ private
389
+
390
+ def javascript_action(&block)
391
+ url = r.url_for_callback(block)
392
+ "javascript: document.location.href='#{ url }';"
393
+ end
394
+
395
+ end