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