wee-pm 0.1

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,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