blib/lib/Vroom.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 43 | 379 | 11.3 |
branch | 0 | 170 | 0.0 |
condition | 0 | 15 | 0.0 |
subroutine | 15 | 49 | 30.6 |
pod | 1 | 31 | 3.2 |
total | 59 | 644 | 9.1 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | 2 | 2 | 41631 | use strict; use warnings; | |||
2 | 2 | 4 | |||||
2 | 109 | ||||||
2 | 12 | ||||||
2 | 4 | ||||||
2 | 104 | ||||||
2 | package Vroom; | ||||||
3 | our $VERSION = '0.37'; | ||||||
4 | 2 | 2 | 1043 | use Vroom::Mo; | |||
2 | 12 | ||||||
2 | 12 | ||||||
5 | |||||||
6 | 2 | 2 | 3043 | use File::HomeDir; | |||
2 | 14636 | ||||||
2 | 159 | ||||||
7 | 2 | 2 | 1798 | use IO::All; | |||
2 | 32803 | ||||||
2 | 21 | ||||||
8 | 2 | 2 | 2074 | use Template::Toolkit::Simple; | |||
2 | 147757 | ||||||
2 | 120 | ||||||
9 | 2 | 2 | 2011 | use Term::Size; | |||
2 | 7023 | ||||||
2 | 107 | ||||||
10 | 2 | 2 | 19 | use YAML::XS; | |||
2 | 4 | ||||||
2 | 100 | ||||||
11 | |||||||
12 | 2 | 2 | 11 | use Getopt::Long; | |||
2 | 3 | ||||||
2 | 20 | ||||||
13 | 2 | 2 | 296 | use Cwd; | |||
2 | 4 | ||||||
2 | 116 | ||||||
14 | 2 | 2 | 11 | use Carp; | |||
2 | 5 | ||||||
2 | 96 | ||||||
15 | |||||||
16 | 2 | 2 | 33 | use Encode; | |||
2 | 5 | ||||||
2 | 2451 | ||||||
17 | |||||||
18 | has input => 'slides.vroom'; | ||||||
19 | has notesfile => 'notes.txt'; | ||||||
20 | has has_notes => 0; | ||||||
21 | has stream => ''; | ||||||
22 | has ext => ''; | ||||||
23 | has help => 0; | ||||||
24 | has clean => 0; | ||||||
25 | has compile => 0; | ||||||
26 | has sample => 0; | ||||||
27 | has run => 0; | ||||||
28 | has html => 0; | ||||||
29 | has text => 0; | ||||||
30 | has ghpublish => 0; | ||||||
31 | has start => 0; | ||||||
32 | has digits => 0; | ||||||
33 | has skip => 0; | ||||||
34 | has config => { | ||||||
35 | title => 'Untitled Presentation', | ||||||
36 | height => 24, | ||||||
37 | width => 80, | ||||||
38 | list_indent => 10, | ||||||
39 | skip => 0, | ||||||
40 | vim => 'vim', | ||||||
41 | vim_opts => '-u NONE', | ||||||
42 | vimrc => '', | ||||||
43 | gvimrc => '', | ||||||
44 | script => '', | ||||||
45 | auto_size => 0, | ||||||
46 | }; | ||||||
47 | |||||||
48 | sub usage { | ||||||
49 | 0 | 0 | 0 | return <<'...'; | |||
50 | Usage: vroom |
||||||
51 | |||||||
52 | Commands: | ||||||
53 | new - Create a sample 'slides.vroom' file | ||||||
54 | vroom - Start slideshow | ||||||
55 | compile - Generate slides | ||||||
56 | html - Publish slides as HTML | ||||||
57 | text - Publish slides as plain text | ||||||
58 | clean - Delete generated files | ||||||
59 | help - Get help! | ||||||
60 | |||||||
61 | Options: | ||||||
62 | --skip=# - Skip # of slides | ||||||
63 | --input=name - Specify an input file name | ||||||
64 | |||||||
65 | ... | ||||||
66 | } | ||||||
67 | |||||||
68 | sub vroom { | ||||||
69 | 0 | 0 | 0 | 1 | my $self = ref($_[0]) ? shift : (shift)->new; | ||
70 | |||||||
71 | 0 | $self->getOptions; | |||||
72 | |||||||
73 | 0 | 0 | if ($self->sample) { | ||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
74 | 0 | $self->sampleSlides; | |||||
75 | } | ||||||
76 | elsif ($self->run) { | ||||||
77 | 0 | $self->runSlide; | |||||
78 | } | ||||||
79 | elsif ($self->clean) { | ||||||
80 | 0 | $self->cleanAll; | |||||
81 | } | ||||||
82 | elsif ($self->compile) { | ||||||
83 | 0 | $self->makeSlides; | |||||
84 | } | ||||||
85 | elsif ($self->start) { | ||||||
86 | 0 | $self->makeSlides; | |||||
87 | 0 | $self->startUp; | |||||
88 | } | ||||||
89 | elsif ($self->html) { | ||||||
90 | 0 | $self->makeHTML; | |||||
91 | } | ||||||
92 | elsif ($self->text) { | ||||||
93 | 0 | $self->makeText; | |||||
94 | } | ||||||
95 | elsif ($self->ghpublish) { | ||||||
96 | 0 | $self->makePublisher; | |||||
97 | } | ||||||
98 | elsif ($self->help) { | ||||||
99 | 0 | warn $self->usage; | |||||
100 | } | ||||||
101 | else { | ||||||
102 | 0 | warn $self->usage; | |||||
103 | } | ||||||
104 | } | ||||||
105 | |||||||
106 | sub getOptions { | ||||||
107 | 0 | 0 | 0 | my $self = shift; | |||
108 | |||||||
109 | 0 | 0 | die <<'...' if cwd eq File::HomeDir->my_home; | ||||
110 | |||||||
111 | Don't run vroom in your home directory. | ||||||
112 | |||||||
113 | Create a new directory for your slides and run vroom from there. | ||||||
114 | ... | ||||||
115 | |||||||
116 | 0 | 0 | my $cmd = shift(@ARGV) or die $self->usage; | ||||
117 | 0 | 0 | die $self->usage unless $cmd =~ s/ | ||||
118 | ^-{0,2}( | ||||||
119 | help | | ||||||
120 | new | | ||||||
121 | vroom | | ||||||
122 | compile | | ||||||
123 | run | | ||||||
124 | html | | ||||||
125 | text | | ||||||
126 | clean | | ||||||
127 | ghpublish | ||||||
128 | )$ | ||||||
129 | /$1/x; | ||||||
130 | 0 | 0 | $cmd = 'start' if $cmd eq 'vroom'; | ||||
131 | 0 | 0 | $cmd = 'sample' if $cmd eq 'new'; | ||||
132 | 0 | $self->{$cmd} = 1; | |||||
133 | |||||||
134 | 0 | 0 | GetOptions( | ||||
135 | "input=s" => \$self->{input}, | ||||||
136 | "skip=i" => \$self->{skip}, | ||||||
137 | ) or die $self->usage; | ||||||
138 | |||||||
139 | 0 | 0 | do { delete $self->{$_} unless defined $self->{$_} } | ||||
140 | 0 | for qw(clean compile input vroom); | |||||
141 | } | ||||||
142 | |||||||
143 | sub cleanUp { | ||||||
144 | 0 | 0 | 0 | my $self = shift; | |||
145 | 0 | unlink(glob "0*"); | |||||
146 | 0 | unlink('.help'); | |||||
147 | 0 | unlink('.vimrc'); | |||||
148 | 0 | unlink('.gvimrc'); | |||||
149 | 0 | unlink('run.slide'); | |||||
150 | 0 | unlink($self->notesfile); | |||||
151 | 0 | io->dir('bin')->rmtree; | |||||
152 | 0 | io->dir('done')->rmtree; | |||||
153 | } | ||||||
154 | |||||||
155 | sub cleanAll { | ||||||
156 | 0 | 0 | 0 | my $self = shift; | |||
157 | 0 | $self->cleanUp; | |||||
158 | 0 | io->dir('html')->rmtree; | |||||
159 | 0 | io->dir('text')->rmtree; | |||||
160 | } | ||||||
161 | |||||||
162 | sub runSlide { | ||||||
163 | 0 | 0 | 0 | my $self = shift; | |||
164 | 0 | my $slide = $ARGV[0]; | |||||
165 | |||||||
166 | 0 | 0 | if ($slide =~ /\.pl$/) { | ||||
167 | 0 | exec "clear; $^X $slide"; | |||||
168 | } | ||||||
169 | |||||||
170 | 0 | $self->trim_slide; | |||||
171 | |||||||
172 | 0 | 0 | if ($slide =~ /\.py$/) { | ||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
173 | 0 | exec "clear; python run.slide"; | |||||
174 | } | ||||||
175 | elsif ($slide =~ /\.rb$/) { | ||||||
176 | 0 | exec "clear; ruby run.slide"; | |||||
177 | } | ||||||
178 | elsif ($slide =~ /\.php$/) { | ||||||
179 | 0 | exec "clear; php run.slide"; | |||||
180 | } | ||||||
181 | elsif ($slide =~ /\.js$/) { | ||||||
182 | 0 | exec "clear; js run.slide"; | |||||
183 | } | ||||||
184 | elsif ($slide =~ /\.hs$/) { | ||||||
185 | 0 | exec "clear; runghc run.slide"; | |||||
186 | } | ||||||
187 | elsif ($slide =~ /\.yaml$/) { | ||||||
188 | 0 | exec "clear; $^X -MYAML::XS -MData::Dumper -e '\$Data::Dumper::Terse = 1; \$Data::Dumper::Indent = 1; print Dumper YAML::XS::LoadFile(shift)' run.slide"; | |||||
189 | } | ||||||
190 | elsif ($slide =~ /\.sh$/) { | ||||||
191 | 0 | exec "clear; $ENV{SHELL} -i run.slide"; | |||||
192 | } | ||||||
193 | } | ||||||
194 | |||||||
195 | sub trim_slide { | ||||||
196 | 0 | 0 | 0 | my $self = shift; | |||
197 | 0 | my $slide = $ARGV[0]; | |||||
198 | |||||||
199 | 0 | my $text < io($slide); | |||||
200 | 0 | $text =~ s/^\s*\n//; | |||||
201 | 0 | $text =~ s/\n\s*$/\n/; | |||||
202 | 0 | while ($text !~ /^\S/m) { | |||||
203 | 0 | $text =~ s/^ //mg; | |||||
204 | } | ||||||
205 | 0 | $text > io('run.slide'); | |||||
206 | } | ||||||
207 | |||||||
208 | sub makeSlides { | ||||||
209 | 0 | 0 | 0 | my $self = shift; | |||
210 | 0 | $self->cleanUp; | |||||
211 | 0 | $self->getInput; | |||||
212 | 0 | $self->buildSlides; | |||||
213 | 0 | $self->writeVimrc; | |||||
214 | 0 | $self->writeScriptRunner; | |||||
215 | 0 | $self->writeHelp; | |||||
216 | } | ||||||
217 | |||||||
218 | sub makeText { | ||||||
219 | 0 | 0 | 0 | my $self = shift; | |||
220 | 0 | $self->cleanAll; | |||||
221 | 0 | $self->makeSlides; | |||||
222 | 0 | io('text')->mkdir; | |||||
223 | 0 | my @slides = glob('0*'); | |||||
224 | 0 | for my $slide (@slides) { | |||||
225 | 0 | 0 | next unless $slide =~ /^(\d+)(\.\S+)?$/; | ||||
226 | 0 | my $num = $1; | |||||
227 | 0 | 0 | my $ext = $2 || ''; | ||||
228 | 0 | 0 | my $text = io(-e "${num}z$ext" ? "${num}z$ext" : "$num$ext")->all(); | ||||
229 | 0 | io("text/$slide")->print($text); | |||||
230 | } | ||||||
231 | 0 | eval { | |||||
232 | 0 | system("cp .vimrc text"); | |||||
233 | }; | ||||||
234 | 0 | $self->cleanUp; | |||||
235 | } | ||||||
236 | |||||||
237 | sub makeHTML { | ||||||
238 | 0 | 0 | 0 | my $self = shift; | |||
239 | 0 | $self->cleanAll; | |||||
240 | 0 | $self->makeSlides; | |||||
241 | 0 | io('html')->mkdir; | |||||
242 | 0 | my @slides = glob('0*'); | |||||
243 | 0 | my @notes = $self->parse_notesfile; | |||||
244 | 0 | for (my $i = 0; $i < @slides; $i++) { | |||||
245 | 0 | my $slide = $slides[$i]; | |||||
246 | 0 | 0 | my $prev = ($i > 0) ? $slides[$i - 1] : 'index'; | ||||
247 | 0 | 0 | my $next = ($i + 1 < @slides) ? $slides[$i + 1] : ''; | ||||
248 | 0 | my $text = io($slide)->all; | |||||
249 | 0 | $text = Template::Toolkit::Simple->new()->render( | |||||
250 | $self->slideTemplate, | ||||||
251 | { | ||||||
252 | title => $notes[$i]->{'title'}, | ||||||
253 | prev => $prev, | ||||||
254 | next => $next, | ||||||
255 | content => decode_utf8($text), | ||||||
256 | notes => $self->htmlize_note($notes[$i]->{'text'}), | ||||||
257 | } | ||||||
258 | ); | ||||||
259 | 0 | io("html/$slide.html")->print($text); | |||||
260 | } | ||||||
261 | |||||||
262 | 0 | my $index = []; | |||||
263 | 0 | for (my $i = 0; $i < @slides; $i++) { | |||||
264 | 0 | my $slide = $slides[$i]; | |||||
265 | 0 | 0 | next if $slide =~ /^\d+[a-z]/; | ||||
266 | 0 | my $title = io($slide)->all; | |||||
267 | 0 | $title =~ s/.*?((?-s:\S.*)).*/$1/s; | |||||
268 | 0 | push @$index, [$slide, decode_utf8($title)]; | |||||
269 | } | ||||||
270 | |||||||
271 | 0 | io("html/index.html")->print( | |||||
272 | Template::Toolkit::Simple->new()->render( | ||||||
273 | $self->indexTemplate, | ||||||
274 | { | ||||||
275 | config => $self->config, | ||||||
276 | index => $index, | ||||||
277 | |||||||
278 | } | ||||||
279 | ) | ||||||
280 | ); | ||||||
281 | 0 | $self->cleanUp; | |||||
282 | } | ||||||
283 | |||||||
284 | sub indexTemplate { | ||||||
285 | \ <<'...' | ||||||
286 | |||||||
287 | |||||||
288 | |
||||||
289 | |||||||
290 | |||||||
302 | |||||||
310 | |||||||
311 | |||||||
312 | Use SPACEBAR to peruse the slides or click one to start... |
||||||
313 | [% config.title | html %] |
||||||
314 | |
||||||
315 | [% FOR entry = index -%] | ||||||
316 | [% slide = entry.shift() -%] | ||||||
317 | [% title = entry.shift() -%] | ||||||
318 | |
||||||
319 | [% END -%] | ||||||
320 | |||||||
321 | |||||||
322 | href="http://ingydotnet.github.com/vroom-pm">Vroom. Use <SPACE> key to go | ||||||
323 | forward and <BACKSPACE> to go backwards. | ||||||
324 | |||||||
325 | |||||||
326 | |||||||
327 | ... | ||||||
328 | 0 | 0 | 0 | } | |||
329 | |||||||
330 | sub slideTemplate { | ||||||
331 | \ <<'...' | ||||||
332 | |||||||
333 | |||||||
334 | |
||||||
335 | |||||||
336 | |||||||
360 | |||||||
361 | |||||||
362 | |
||||||
363 | |
||||||
364 | [%- content | html -%] | ||||||
365 | |||||||
366 | |||||||
367 | |
||||||
368 | |
||||||
369 | [% notes %] |
||||||
370 | |||||||
371 | |||||||
372 | |||||||
373 | ... | ||||||
374 | 0 | 0 | 0 | } | |||
375 | |||||||
376 | sub getInput { | ||||||
377 | 0 | 0 | 0 | my $self = shift; | |||
378 | 0 | 0 | my @stream = io($self->input)->slurp | ||||
379 | or croak "No input provided. Make a file called 'slides.vroom'"; | ||||||
380 | 0 | 0 | my $stream = join '', map { | ||||
381 | 0 | /^----\s+include\s+(\S+)/ | |||||
382 | ? scalar(io($1)->all) | ||||||
383 | : $_ | ||||||
384 | } @stream; | ||||||
385 | 0 | $self->stream($stream); | |||||
386 | } | ||||||
387 | |||||||
388 | my $TRANSITION = qr/^\+/m; | ||||||
389 | my $SLIDE_MARKER = qr/^={4}\n/m; | ||||||
390 | my $TITLE_MARKER = qr/^%\s*(.*?)\n/m; | ||||||
391 | |||||||
392 | sub buildSlides { | ||||||
393 | 0 | 0 | 0 | my $self = shift; | |||
394 | 0 | my @split = split /^(----\ *.*)\n/m, $self->stream; | |||||
395 | 0 | shift @split; | |||||
396 | 0 | @split = grep length, @split; | |||||
397 | 0 | 0 | push @split, '----' if $split[0] =~ /\n/; | ||||
398 | 0 | my (@raw_configs, @raw_slides); | |||||
399 | 0 | while (@split) { | |||||
400 | 0 | my ($config, $slide) = splice(@split, 0, 2); | |||||
401 | 0 | $config =~ s/^----\s*(.*?)\s*$/$1/; | |||||
402 | 0 | push @raw_configs, $config; | |||||
403 | 0 | push @raw_slides, $slide; | |||||
404 | 0 | 0 | $self->has_notes(1) if $slide =~ $SLIDE_MARKER; | ||||
405 | } | ||||||
406 | 0 | $self->{digits} = int(log(@raw_slides)/log(10)) + 2; | |||||
407 | |||||||
408 | 0 | my $number = 0; | |||||
409 | |||||||
410 | 0 | 0 | '' > io($self->notesfile) if $self->has_notes; # start with a blank file so we can append | ||||
411 | 0 | for my $raw_slide (@raw_slides) { | |||||
412 | 0 | my $config = $self->parseSlideConfig(shift @raw_configs); | |||||
413 | |||||||
414 | 0 | 0 | next if $config->{skip}; | ||||
415 | |||||||
416 | # could move the increment of $number up here, but then we'd count config slides | ||||||
417 | # and we don't really want to do that | ||||||
418 | # so just use $number + 1 for now, and we'll increment below | ||||||
419 | 0 | my ($title, $notes) = $self->extract_notes($raw_slide, $number + 1); | |||||
420 | |||||||
421 | 0 | 0 | $raw_slide = $self->applyOptions($raw_slide, $config) | ||||
422 | or next; | ||||||
423 | |||||||
424 | 0 | $number++; | |||||
425 | |||||||
426 | 0 | 0 | 0 | if ($self->config->{skip} or $self->skip) { | |||
427 | 0 | 0 | $self->config->{skip}-- if $self->config->{skip}; | ||||
428 | 0 | 0 | $self->{skip}-- if $self->{skip}; | ||||
429 | 0 | next; | |||||
430 | } | ||||||
431 | |||||||
432 | 0 | 0 | $self->print_notes($title, $number, $notes) if $self->has_notes; | ||||
433 | |||||||
434 | 0 | $raw_slide = $self->padVertical($raw_slide); | |||||
435 | |||||||
436 | 0 | my @slides; | |||||
437 | my @scripts; | ||||||
438 | 0 | my $slide = ''; | |||||
439 | 0 | for my $part (split /$TRANSITION/, $raw_slide) { | |||||
440 | 0 | 0 | $slide = '' if $config->{replace}; | ||||
441 | 0 | my $script = ''; | |||||
442 | 0 | 0 | if ($self->config->{script}) { | ||||
443 | 0 | ($part, $script) = $self->parseScript($part); | |||||
444 | } | ||||||
445 | 0 | $slide .= $part; | |||||
446 | 0 | 0 | $slide = $self->padVertical($slide) | ||||
447 | if $config->{replace}; | ||||||
448 | 0 | push @slides, $slide; | |||||
449 | 0 | push @scripts, $script; | |||||
450 | } | ||||||
451 | |||||||
452 | 0 | my $base_name = $self->formatNumber($number); | |||||
453 | |||||||
454 | 0 | my $suffix = 'a'; | |||||
455 | 0 | for (my $i = 1; $i <= @slides; $i++) { | |||||
456 | 0 | my $slide = $self->padFullScreen($slides[$i - 1]); | |||||
457 | 0 | chomp $slide; | |||||
458 | 0 | $slide .= "\n"; | |||||
459 | 0 | 0 | if ($slide =~ s/^\ *!(.*\n)//m) { | ||||
460 | 0 | $slide .= $1; | |||||
461 | } | ||||||
462 | # this option can't be applied ahead of time | ||||||
463 | 0 | 0 | if ($config->{undent}) { | ||||
464 | 0 | my $undent = $config->{undent}; | |||||
465 | 0 | $slide =~ s/^.{$undent}//gm; | |||||
466 | } | ||||||
467 | 0 | $slide =~ s{^\ *==\ +(.*?)\ *$} | |||||
468 | 0 | {' ' x (($self->config->{width} - length($1)) / 2) . $1}gem; | |||||
469 | 0 | my $suf = $suffix++; | |||||
470 | 0 | 0 | $suf = $suf eq 'a' | ||||
0 | |||||||
471 | ? '' | ||||||
472 | : $i == @slides | ||||||
473 | ? 'z' | ||||||
474 | : $suf; | ||||||
475 | 0 | my $file_name = "$base_name$suf" . $self->ext; | |||||
476 | 0 | io($file_name)->print($slide); | |||||
477 | 0 | 0 | if (my $script = shift @scripts) { | ||||
478 | 0 | io("bin/$file_name")->assert->print($script); | |||||
479 | } | ||||||
480 | } | ||||||
481 | } | ||||||
482 | } | ||||||
483 | |||||||
484 | my $NEXT_SLIDE = ' |
||||||
485 | |||||||
486 | sub extract_notes { | ||||||
487 | 0 | 0 | 0 | my $self = shift; | |||
488 | # have to deal with the slide argument in $_[0] directly so we can modify it | ||||||
489 | 0 | my $number = $_[1]; | |||||
490 | |||||||
491 | 0 | 0 | my $title = $_[0] =~ s/$TITLE_MARKER// ? $1 : "Slide $number"; | ||||
492 | 0 | 0 | my $notes = $_[0] =~ s/$SLIDE_MARKER(.*)\s*\Z//s ? $1 : ''; | ||||
493 | |||||||
494 | # verify that the number of transitions in the notes matches the number of transitions in the slide | ||||||
495 | # if not, do something about it | ||||||
496 | # (note: using a secret operator here; see http://www.catonmat.net/blog/secret-perl-operators/#goatse ) | ||||||
497 | 0 | my $num_slide_transitions =()= $_[0] =~ /$TRANSITION/g; | |||||
498 | 0 | my $num_notes_transitions =()= $notes =~ /$TRANSITION/g; | |||||
499 | 0 | 0 | if ($num_notes_transitions < $num_slide_transitions) | ||||
0 | |||||||
500 | { | ||||||
501 | # add more transitions | ||||||
502 | 0 | $notes .= "\n+" x ($num_slide_transitions - $num_notes_transitions); | |||||
503 | } | ||||||
504 | elsif ($num_notes_transitions > $num_slide_transitions) | ||||||
505 | { | ||||||
506 | # warn, and then remove transitions | ||||||
507 | # we'll reverse the string so we can remove transitions from back to front | ||||||
508 | 0 | warn("too many transitions for slide $title"); | |||||
509 | 0 | $notes = reverse $notes; | |||||
510 | 0 | $notes =~ s/\+\n/\n/ for 1..($num_notes_transitions - $num_slide_transitions); | |||||
511 | 0 | $notes = reverse $notes; | |||||
512 | } | ||||||
513 | |||||||
514 | 0 | $notes =~ s/$TRANSITION/$NEXT_SLIDE\n/g; | |||||
515 | |||||||
516 | 0 | return ($title, $notes); | |||||
517 | } | ||||||
518 | |||||||
519 | sub print_notes { | ||||||
520 | 0 | 0 | 0 | my $self = shift; | |||
521 | 0 | my ($title, $number, $notes) = @_; | |||||
522 | |||||||
523 | 0 | 0 | "\n" . ($number == 1 ? ' ' x length($NEXT_SLIDE) : $NEXT_SLIDE) . " -- $title --\n\n$notes\n" >> io($self->notesfile); | ||||
524 | } | ||||||
525 | |||||||
526 | sub parse_notesfile | ||||||
527 | { | ||||||
528 | 0 | 0 | 0 | my $self = shift; | |||
529 | |||||||
530 | 0 | 0 | return () unless -r $self->notesfile; | ||||
531 | 0 | my $notes = io($self->notesfile)->slurp; | |||||
532 | |||||||
533 | # first slide doesn't have a marker, so we'll add one, for consistency | ||||||
534 | 0 | $notes = $NEXT_SLIDE . $notes; | |||||
535 | |||||||
536 | 0 | my @notes; | |||||
537 | 0 | my @stream = split(/\Q$NEXT_SLIDE\E(?:\s+-- (.*?) --)?\s*/, $notes); | |||||
538 | # skipping 0 because, since we're starting with what we're splitting on, the first field will | ||||||
539 | # always be empty | ||||||
540 | 0 | for (1..$#stream) | |||||
541 | { | ||||||
542 | 0 | 0 | if ($_ % 2) | ||||
543 | { | ||||||
544 | 0 | my $title = $stream[$_]; | |||||
545 | 0 | 0 | $title = $notes[-1]->{'title'} unless defined $title; | ||||
546 | 0 | push @notes, { title => $title }; | |||||
547 | } | ||||||
548 | else | ||||||
549 | { | ||||||
550 | 0 | my $text = $stream[$_]; | |||||
551 | 0 | $text =~ s/\s+\Z//; | |||||
552 | 0 | $notes[-1]->{'text'} = $stream[$_]; | |||||
553 | } | ||||||
554 | } | ||||||
555 | |||||||
556 | 0 | return @notes; | |||||
557 | } | ||||||
558 | |||||||
559 | 2 | 2 | 5340 | my %inline_tags; BEGIN { %inline_tags = ( BQ => 'code', IT => 'i', BO => 'b', ); } | |||
560 | 0 | 0 | 0 | sub inline_element { my $t = $_[1]; $t =~ s/^.//; $t =~ s/.$//; return "<$inline_tags{$_[0]}>$t$inline_tags{$_[0]}>" } | |||
0 | |||||||
0 | |||||||
0 | |||||||
561 | sub htmlize_note | ||||||
562 | { | ||||||
563 | 2 | 2 | 2530 | use Text::Balanced qw< extract_multiple extract_delimited >; | |||
2 | 60635 | ||||||
2 | 4029 | ||||||
564 | |||||||
565 | 0 | 0 | 0 | my $self = shift; | |||
566 | 0 | my ($note) = @_; | |||||
567 | 0 | 0 | $note = '' unless defined $note; | ||||
568 | |||||||
569 | 0 | $note =~ s{((^\s*\*\s+.+?\n)+)}{"
|
|||||
0 | |||||||
0 | |||||||
0 | |||||||
570 | |||||||
571 | 0 | my @bits; | |||||
572 | 0 | 0 | $note = join('', map { ref $_ ? scalar((push @bits, inline_element(ref $_, $$_)), "{X$#bits}") : $_ } | ||||
573 | extract_multiple($note, | ||||||
574 | [ | ||||||
575 | 0 | 0 | { BQ => sub { extract_delimited($_[0], q{`}, '', q{`}) } }, | ||||
576 | 0 | 0 | { IT => sub { extract_delimited($_[0], q{_}, '', q{_}) } }, | ||||
577 | 0 | 0 | { BO => sub { extract_delimited($_[0], q{*}, '', q{*}) } }, | ||||
578 | 0 | qr/[^`_*]+/, | |||||
579 | ]) | ||||||
580 | ); | ||||||
581 | 0 | $note =~ s{--}{—}g; | |||||
582 | 0 | $note =~ s{ \.\.\.}{ ...}g; | |||||
583 | 0 | $note =~ s{\n+}{ }g; |
|||||
584 | 0 | $note =~ s/{X(\d+)}/$bits[$1]/g; | |||||
585 | |||||||
586 | 0 | return $note; | |||||
587 | } | ||||||
588 | |||||||
589 | sub parseScript { | ||||||
590 | 0 | 0 | 0 | my $self = shift; | |||
591 | 0 | my $text = shift; | |||||
592 | 0 | chomp $text; | |||||
593 | 0 | $text .= "\n"; | |||||
594 | 0 | my $script = ''; | |||||
595 | 0 | my $delim = $self->config->{script}; | |||||
596 | 0 | while ($text =~ s/^[\ \t]*\Q$delim\E(.*\n)//m) { | |||||
597 | 0 | $script .= $1; | |||||
598 | } | ||||||
599 | 0 | return ($text, $script); | |||||
600 | } | ||||||
601 | |||||||
602 | sub formatNumber { | ||||||
603 | 0 | 0 | 0 | my $self = shift; | |||
604 | 0 | my $number = shift; | |||||
605 | 0 | my $digits = $self->digits; | |||||
606 | 0 | return sprintf "%0${digits}d", $number; | |||||
607 | } | ||||||
608 | |||||||
609 | my $types = { | ||||||
610 | # add pl6 and py3 | ||||||
611 | perl => 'pl', pl => 'pl', pm => 'pm', | ||||||
612 | ruby => 'rb', rb => 'rb', | ||||||
613 | python => 'py', py => 'py', | ||||||
614 | haskell => 'hs', hs => 'hs', | ||||||
615 | javascript => 'js', js => 'js', | ||||||
616 | actionscript => 'as', as => 'as', | ||||||
617 | shell => 'sh', sh => 'sh', | ||||||
618 | php => 'php', | ||||||
619 | java => 'java', | ||||||
620 | yaml => 'yaml', | ||||||
621 | xml => 'xml', | ||||||
622 | json => 'json', | ||||||
623 | html => 'html', | ||||||
624 | make => 'make', | ||||||
625 | diff => 'diff', | ||||||
626 | conf => 'conf', | ||||||
627 | }; | ||||||
628 | sub parseSlideConfig { | ||||||
629 | 0 | 0 | 0 | my $self = shift; | |||
630 | 0 | my $string = shift; | |||||
631 | 0 | my $config = {}; | |||||
632 | 0 | my $type_list = join '|', keys %$types; | |||||
633 | 0 | for my $option (split /\s*,\s*/, $string) { | |||||
634 | 0 | 0 | $config->{$option} = 1 | ||||
635 | if $option =~ /^cd/; | ||||||
636 | 0 | 0 | $config->{$1} = 1 | ||||
637 | if $option =~ /^(config|skip|center|replace|$type_list)$/; | ||||||
638 | 0 | 0 | $config->{$1} = 1 | ||||
639 | if $option =~ /^(\.\w+)$/; | ||||||
640 | 0 | 0 | $config->{indent} = $1 | ||||
641 | if $option =~ /i(\d+)/; | ||||||
642 | 0 | 0 | $config->{undent} = $1 | ||||
643 | if $option =~ /i-(\d+)/; | ||||||
644 | } | ||||||
645 | 0 | return $config; | |||||
646 | } | ||||||
647 | |||||||
648 | sub applyOptions { | ||||||
649 | 0 | 0 | 0 | my $self = shift; | |||
650 | 0 | my ($slide, $config) = @_; | |||||
651 | |||||||
652 | 0 | $config = { | |||||
653 | 0 | %{$self->config}, | |||||
654 | %$config, | ||||||
655 | }; | ||||||
656 | |||||||
657 | 0 | 0 | if ($config->{config}) { | ||||
658 | 0 | $config = { | |||||
659 | 0 | %{$self->config}, | |||||
660 | 0 | %{(YAML::XS::Load($slide))}, | |||||
661 | }; | ||||||
662 | |||||||
663 | 0 | 0 | if ($config->{auto_size}) { | ||||
664 | 0 | my ($columns, $rows) = Term::Size::chars *STDOUT{IO}; | |||||
665 | |||||||
666 | 0 | $config->{width} = $columns; | |||||
667 | 0 | $config->{height} = $rows; | |||||
668 | } | ||||||
669 | |||||||
670 | 0 | $self->config($config); | |||||
671 | 0 | return ''; | |||||
672 | } | ||||||
673 | |||||||
674 | 0 | 0 | $slide ||= ''; | ||||
675 | 0 | 0 | if ($config->{center}) { | ||||
0 | |||||||
0 | |||||||
676 | 0 | $slide =~ s{^(\+?)\ *(.*?)\ *$} | |||||
677 | 0 | {$1 . ' ' x (($self->config->{width} - length($2)) / 2) . $2}gem; | |||||
678 | 0 | $slide =~ s{^\s*$}{}gm; | |||||
679 | } | ||||||
680 | elsif (defined $config->{indent}) { | ||||||
681 | 0 | my $indent = $config->{indent}; | |||||
682 | 0 | $slide =~ s{^(\+?)}{$1 . ' ' x $indent}gem; | |||||
0 | |||||||
683 | } | ||||||
684 | elsif ($slide =~ /^\+?\*/m) { | ||||||
685 | 0 | my $indent = $config->{list_indent}; | |||||
686 | 0 | $slide =~ s{^(\+?)}{$1 . ' ' x $indent}gem; | |||||
0 | |||||||
687 | } | ||||||
688 | |||||||
689 | 0 | my $ext = ''; | |||||
690 | 0 | for my $key (keys %$config) { | |||||
691 | 0 | 0 | if (my $e = $types->{$key}) { | ||||
0 | |||||||
0 | |||||||
692 | 0 | $ext = ".$e"; | |||||
693 | 0 | last; | |||||
694 | } | ||||||
695 | elsif ($key =~ s/^cd//) { | ||||||
696 | 0 | 0 | if (my $e = $types->{$key}) { | ||||
697 | 0 | $ext = ".cd.$e"; | |||||
698 | 0 | last; | |||||
699 | } | ||||||
700 | } | ||||||
701 | elsif ($key =~ s/^\.(\w+)//) { | ||||||
702 | 0 | $ext = ".$1"; | |||||
703 | 0 | last; | |||||
704 | } | ||||||
705 | } | ||||||
706 | 0 | $self->ext($ext); | |||||
707 | |||||||
708 | 0 | return $slide; | |||||
709 | } | ||||||
710 | |||||||
711 | sub padVertical { | ||||||
712 | 0 | 0 | 0 | my $self = shift; | |||
713 | 0 | my $slide = shift; | |||||
714 | 0 | $slide =~ s/\A\s*\n//; | |||||
715 | 0 | $slide =~ s/\n\s*\z//; | |||||
716 | 0 | $slide =~ s/ +$//mg; | |||||
717 | 0 | my @lines = split /\n/, $slide; | |||||
718 | 0 | my $lines = @lines; | |||||
719 | 0 | my $before = int(($self->config->{height} - $lines) / 2) - 1; | |||||
720 | 0 | return "\n" x $before . $slide; | |||||
721 | } | ||||||
722 | |||||||
723 | sub padFullScreen { | ||||||
724 | 0 | 0 | 0 | my $self = shift; | |||
725 | 0 | my $slide = shift; | |||||
726 | 0 | chomp $slide; | |||||
727 | 0 | my @lines = split /\n/, $slide; | |||||
728 | 0 | my $lines = @lines; | |||||
729 | 0 | my $after = $self->config->{height} - $lines + 1; | |||||
730 | 0 | return $slide . "\n" x $after; | |||||
731 | } | ||||||
732 | |||||||
733 | sub writeVimrc { | ||||||
734 | 0 | 0 | 0 | my $self = shift; | |||
735 | |||||||
736 | 0 | my $home_vimrc = File::HomeDir->my_home . "/.vroom/vimrc"; | |||||
737 | 0 | 0 | my $home_vimrc_content = -e $home_vimrc ? io($home_vimrc)->all : ''; | ||||
738 | |||||||
739 | 0 | 0 | my $next_cmd = $self->config->{script} | ||||
740 | ? ':n |
||||||
741 | : ':n |
||||||
742 | 0 | 0 | my $script_functions = $self->config->{script} ? <<'...' : ''; | ||||
743 | function RunIf() | ||||||
744 | let script = "bin/" . expand("%") | ||||||
745 | let done = "done/" . expand("%") | ||||||
746 | if filereadable(done) | ||||||
747 | return | ||||||
748 | endif | ||||||
749 | if filereadable(script) | ||||||
750 | call system("sh " . script) | ||||||
751 | call system("touch " . done) | ||||||
752 | endif | ||||||
753 | return | ||||||
754 | endfunction | ||||||
755 | |||||||
756 | function RunNow() | ||||||
757 | let done = "done/" . expand("%") | ||||||
758 | call system("rm -f " . done) | ||||||
759 | call RunIf() | ||||||
760 | endfunction | ||||||
761 | ... | ||||||
762 | |||||||
763 | 0 | 0 | 0 | die <<'...' | |||
764 | The .vimrc in your current directory does not look like vroom created it. | ||||||
765 | |||||||
766 | If you are sure it can be overwritten, please delete it yourself this one | ||||||
767 | time, and rerun vroom. You should not get this message again. | ||||||
768 | |||||||
769 | ... | ||||||
770 | if -e '.vimrc' and io('.vimrc')->getline !~ /Vroom-\d\.\d\d/; | ||||||
771 | |||||||
772 | 0 | my $title = "%-20f " . $self->config->{title}; | |||||
773 | 0 | $title =~ s/\s/\\ /g; | |||||
774 | 2 | 2 | 28 | no strict 'refs'; | |||
2 | 5 | ||||||
2 | 1414 | ||||||
775 | 0 | io(".vimrc")->print(<<"..."); | |||||
776 | 0 | " This .vimrc file was created by Vroom-${"VERSION"} | |||||
0 | |||||||
777 | set nocompatible | ||||||
778 | syntax on | ||||||
779 | $script_functions | ||||||
780 | map |
||||||
781 | map |
||||||
782 | map R :!vroom -run % |
||||||
783 | map RR :!vroom -run % |
||||||
784 | map AA :call RunNow() |
||||||
785 | map VV :!vroom -vroom |
||||||
786 | map QQ :q! |
||||||
787 | map OO :!open |
||||||
788 | map EE :e |
||||||
789 | map !! G:!open |
||||||
790 | map ?? :e .help |
||||||
791 | set laststatus=2 | ||||||
792 | set statusline=$title | ||||||
793 | |||||||
794 | " Overrides from $home_vimrc | ||||||
795 | $home_vimrc_content | ||||||
796 | |||||||
797 | " Values from slides.vroom config section. (under 'vimrc') | ||||||
798 | ${\ $self->config->{vimrc}} | ||||||
799 | ... | ||||||
800 | |||||||
801 | 0 | 0 | if ($self->config->{vim} =~ /\bgvim\b/) { | ||||
802 | 0 | my $home_gvimrc = File::HomeDir->my_home . "/.vroom/gvimrc"; | |||||
803 | 0 | 0 | my $home_gvimrc_content = -e $home_gvimrc ? io($home_gvimrc)->all : ''; | ||||
804 | |||||||
805 | 0 | io(".gvimrc")->print(<<"..."); | |||||
806 | " Values from slides.vroom config section. (under 'gvimrc') | ||||||
807 | 0 | ${\ $self->config->{gvimrc}} | |||||
808 | |||||||
809 | " Overrides from $home_gvimrc | ||||||
810 | $home_gvimrc_content | ||||||
811 | ... | ||||||
812 | } | ||||||
813 | } | ||||||
814 | |||||||
815 | sub writeScriptRunner { | ||||||
816 | 0 | 0 | 0 | my $self = shift; | |||
817 | 0 | 0 | return unless $self->config->{script}; | ||||
818 | 0 | mkdir 'done'; | |||||
819 | } | ||||||
820 | |||||||
821 | sub writeHelp { | ||||||
822 | 0 | 0 | 0 | my $self = shift; | |||
823 | 0 | io('.help')->print(<<'...'); | |||||
824 | |||||||
825 | |
||||||
826 | |
||||||
827 | |||||||
828 | ?? Help | ||||||
829 | QQ Quit Vroom | ||||||
830 | |||||||
831 | RR Run slide as a program | ||||||
832 | VV vroom vroom | ||||||
833 | EE Edit file under cursor | ||||||
834 | OO Open file under cursor (Mac OS X) | ||||||
835 | |||||||
836 | |||||||
837 | (Press SPACE to leave Help screen and continue) | ||||||
838 | |||||||
839 | ... | ||||||
840 | } | ||||||
841 | |||||||
842 | sub startUp { | ||||||
843 | 0 | 0 | 0 | my $self = shift; | |||
844 | 0 | my $vim = $self->config->{vim}; | |||||
845 | 0 | 0 | my $vim_opts = $self->config->{vim_opts} || ''; | ||||
846 | 0 | exec "$vim $vim_opts '+source .vimrc' 0*"; | |||||
847 | } | ||||||
848 | |||||||
849 | sub sampleSlides { | ||||||
850 | 0 | 0 | 0 | my $self = shift; | |||
851 | 0 | my $file = $self->input; | |||||
852 | 0 | 0 | die <<"..." if -e $file; | ||||
853 | '$file' already exists. | ||||||
854 | |||||||
855 | If you really want to generate a new template slides file, | ||||||
856 | please delete or move this one. | ||||||
857 | ... | ||||||
858 | 0 | io($file)->print(<<'...'); | |||||
859 | # This is a sample Vroom input file. It should help you get started. | ||||||
860 | # | ||||||
861 | # Edit this file with your content. Then run `vroom vroom` to start | ||||||
862 | # the show! | ||||||
863 | # | ||||||
864 | # See `perldoc Vroom` for complete details. | ||||||
865 | # | ||||||
866 | ---- config | ||||||
867 | # Basic config options. | ||||||
868 | title: Vroom! | ||||||
869 | indent: 5 | ||||||
870 | auto_size: 1 | ||||||
871 | # height: 18 | ||||||
872 | # width: 69 | ||||||
873 | vim_opts: '-u NONE' | ||||||
874 | skip: 0 | ||||||
875 | |||||||
876 | # The following options are for Gvim usage. | ||||||
877 | # vim: gvim | ||||||
878 | # gvimrc: | | ||||||
879 | # set fuopt=maxhorz,maxvert | ||||||
880 | # set guioptions=egmLtT | ||||||
881 | # set guifont=Bitstream_Vera_Sans_Mono:h18 | ||||||
882 | # set guicursor=a:blinkon0-ver25-Cursor | ||||||
883 | # colorscheme default | ||||||
884 | |||||||
885 | ---- center | ||||||
886 | Vroom! | ||||||
887 | |||||||
888 | by Ingy döt Net | ||||||
889 | |||||||
890 | (hint: press the spacebar) | ||||||
891 | |||||||
892 | ---- | ||||||
893 | == Slideshows in Vim | ||||||
894 | |||||||
895 | * Hate using PowerPoint or HTML Slides for Talks? | ||||||
896 | +* Use Vroom! | ||||||
897 | |||||||
898 | +* You can write you slides in Vim... | ||||||
899 | * ...and present them in Vim! | ||||||
900 | |||||||
901 | ---- | ||||||
902 | == Getting Started | ||||||
903 | |||||||
904 | * Write a file called 'slides.vroom'. | ||||||
905 | * Do this in a new directory. | ||||||
906 | * Run 'vroom vroom'. | ||||||
907 | * Voilà! | ||||||
908 | |||||||
909 | ---- | ||||||
910 | == Navigation | ||||||
911 | |||||||
912 | * Hit |
||||||
913 | * Hit |
||||||
914 | * Hit 'Q' to quit. | ||||||
915 | |||||||
916 | ---- perl,i4 | ||||||
917 | # This is some Perl code. | ||||||
918 | # Notice the syntax highlighting. | ||||||
919 | # Run it with the |
||||||
920 | for my $word (qw(Vroom totally rocks!)) { | ||||||
921 | print "$word\n"; | ||||||
922 | } | ||||||
923 | |||||||
924 | ---- | ||||||
925 | == Get Vroom! | ||||||
926 | |||||||
927 | * http://search.cpan.org/dist/Vroom/ | ||||||
928 | * http://github.com/ingydotnet/vroom-pm/ | ||||||
929 | |||||||
930 | ---- | ||||||
931 | == Vroom as HTML | ||||||
932 | |||||||
933 | * http://ingydotnet.github.com/vroom-pm/ | ||||||
934 | |||||||
935 | ---- | ||||||
936 | == The End | ||||||
937 | ... | ||||||
938 | 0 | print "'$file' created.\n"; | |||||
939 | } | ||||||
940 | |||||||
941 | sub makePublisher { | ||||||
942 | 0 | 0 | 0 | my $self = shift; | |||
943 | 0 | my $input = $self->input; | |||||
944 | 0 | 0 | die "Error: This doesn't look like a Vroom directory.\n" | ||||
945 | unless -f $input; | ||||||
946 | 0 | 0 | die "Error: This doesn't look like a git repository.\n" | ||||
947 | unless -d '.git'; | ||||||
948 | 0 | 0 | 0 | die "Error: No writeable /tmp directory on this system.\n" | |||
949 | unless -d '/tmp' and -w '/tmp'; | ||||||
950 | 0 | 0 | die "Error: There is no git branch called 'gh-pages'.\n" . | ||||
951 | "Perhaps you should run `git branch gh-pages` first.\n" | ||||||
952 | unless `git branch` =~ /\bgh-pages\b/; | ||||||
953 | 0 | io('ghpublish')->print(<<'...'); | |||||
954 | #!/bin/sh | ||||||
955 | |||||||
956 | # This script is experimental. Please understand it before you run it on | ||||||
957 | # your system. Just because it works for Ingy, doesn't mean it will work | ||||||
958 | # for you. | ||||||
959 | |||||||
960 | if [ -e "/tmp/html" ]; then | ||||||
961 | echo "Error: /tmp/html already exists. Perhaps remove it." | ||||||
962 | exit 13 | ||||||
963 | fi | ||||||
964 | |||||||
965 | # Create HTML slides. | ||||||
966 | vroom html || exit 1 | ||||||
967 | # Move the html directory to /tmp | ||||||
968 | mv html /tmp || exit 1 | ||||||
969 | # Stash any local stuff that isn't committed. | ||||||
970 | git stash || exit 1 | ||||||
971 | # Switch to your gh-pages branch. (That you already created. Right?) | ||||||
972 | git checkout gh-pages || exit 1 | ||||||
973 | # Remove all the html files from the gh-pages branch. | ||||||
974 | rm -f *.html || exit 1 | ||||||
975 | # Move the HTML slides in here. | ||||||
976 | mv /tmp/html/* . || exit 1 | ||||||
977 | # Remove the html directory from /tmp | ||||||
978 | rmdir /tmp/html || exit 1 | ||||||
979 | # Add any new files to git. | ||||||
980 | git add 0* index.html || exit 1 | ||||||
981 | # Commit your changes. | ||||||
982 | git commit -am 'Publish my slides' || exit 1 | ||||||
983 | # Push them to GitHub. | ||||||
984 | git push origin gh-pages || exit 1 | ||||||
985 | # Switch back to the master branch. | ||||||
986 | git checkout master || exit 1 | ||||||
987 | # Get your uncommitted changes back. | ||||||
988 | git stash pop || exit 1 | ||||||
989 | |||||||
990 | # Voilà! (hopefully) | ||||||
991 | ... | ||||||
992 | |||||||
993 | 0 | chmod 0755, 'ghpublish'; | |||||
994 | |||||||
995 | 0 | print <<'...'; | |||||
996 | Created the shell script called 'ghpublish'. | ||||||
997 | |||||||
998 | This script is somewhat experimental, so please read the code to make sure | ||||||
999 | it makes sense on your system. | ||||||
1000 | |||||||
1001 | If it makes sense to you, run it. (at your own risk :) | ||||||
1002 | ... | ||||||
1003 | } | ||||||
1004 | |||||||
1005 | 1; |