showoff 0.4.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -7,7 +7,7 @@ hatred in my heart for Keynote and yet it is by far the best in the field.
7
7
 
8
8
  The idea is that you setup your markdown slide files in section subdirectories
9
9
  and then startup the showoff server in that directory. It will read in your
10
- showoff.json file for which sections go in which order and then will give
10
+ <tt>showoff.json</tt> file for which sections go in which order and then will give
11
11
  you a URL to present from.
12
12
 
13
13
  It can:
@@ -18,7 +18,7 @@ It can:
18
18
  * bullets with incremental advancing
19
19
  * re-enact command line interactions
20
20
  * call up a menu of sections/slides at any time to jump around
21
- * execute javascript or ruby live and display results
21
+ * execute Javascript, Coffeescript or Ruby live and display results
22
22
  * do simple transitions (instant, fade, slide in)
23
23
  * show a pre-show slideshow while you wait to start
24
24
 
@@ -26,13 +26,14 @@ It might will can:
26
26
 
27
27
  * show a timer - elapsed / remaining
28
28
  * perform simple animations of images moving between keyframes
29
- * show syncronized, hidden notes on another browser (like an iphone)
29
+ * show synchronized, hidden notes on another browser (like an iphone)
30
30
  * show audience questions / comments (twitter or direct)
31
31
  * let audience members go back / catch up as you talk
32
32
  * let audience members vote on sections (?)
33
33
  * broadcast itself on Bonjour
34
34
  * let audience members download slides, code samples or other supplementary material
35
35
  * let you write on the slide with your mouse, madden-style via canvas
36
+ * automatically resize text to fit screen [see Alex's shrink.js]
36
37
 
37
38
  Some of the nice things are that you can easily version control it, you
38
39
  can easily move sections between presentations, and you can rearrange or
@@ -40,21 +41,28 @@ remove sections easily.
40
41
 
41
42
  = Usage
42
43
 
43
- ShowOff is meant to be run in a ShowOff formatted repository - that means that it has a showoff.json file and a number of sections (subdirectories) with markdown files for the slides you're presenting.
44
+ ShowOff is meant to be run in a ShowOff formatted repository - that means that
45
+ it has a <tt>showoff.json</tt> file and a number of sections (subdirectories) with
46
+ markdown files for the slides you're presenting.
44
47
 
45
48
  $ gem install showoff
46
49
  $ git clone (showoff-repo)
47
50
  $ cd (showoff-repo)
48
51
  $ showoff serve
49
52
 
50
- If you run 'showoff' in the example subdirectory of ShowOff itself, it will
53
+ If you run 'showoff' in the example subdirectory of ShowOff itself, it will
51
54
  show an example presentation, so you can see what it's like.
52
55
 
56
+ You can also run 'showoff serve' inside a section subdirectory. If there is no
57
+ <tt>showoff.json</tt> file then it will make its best guess, creating a presentation
58
+ from all `.md` files in alphabetical order in the given (or current)
59
+ directory.
60
+
53
61
  = Slide Format
54
62
 
55
63
  You can break your slides up into sections of however many subdirectories deep
56
64
  you need. ShowOff will recursively check all the directories mentioned in
57
- your showoff.json file for any markdown files (.md). Each markdown file can
65
+ your <tt>showoff.json</tt> file for any markdown files (.md). Each markdown file can
58
66
  have any number of slides in it, separating each slide with the '!SLIDE'
59
67
  keyword and optional slide styles.
60
68
 
@@ -77,17 +85,31 @@ the following contents:
77
85
  That represents two slides, the first contains just a large title, and the
78
86
  second is faded into view showing the title and three bullets that are then
79
87
  incrementally shown. In order for ShowOff to see those slides, your
80
- showoff.json file needs to look something like this:
81
-
82
- [
83
- {"section":"one"}
84
- ]
88
+ <tt>showoff.json</tt> file needs to look something like this:
89
+
90
+ {
91
+ "name": "Something",
92
+ "description": "Example Presentation",
93
+ "sections": [
94
+ {"section":"one"}
95
+ ]
96
+ }
85
97
 
86
98
  If you have multiple sections in your talk, you can make this json array
87
99
  include all the sections you want to show in which order you want to show
88
100
  them.
89
101
 
90
- If you want to keep the ability to emit an HTML document from your
102
+ Instead of a hash, you can use a plain string as an entry in the `sections`
103
+ section of `showoff.json`.
104
+
105
+ And if that plain string starts with '#' then it is interpreted not as a
106
+ filename, but as markdown. This is used for inserting interstitial slides
107
+ or notes -- for instance, Alex Chaffee's
108
+ [Ruby Notes](http://github.com/alexch/ruby_notes)
109
+ uses it to insert lab instructions between lecture slide sections, which may
110
+ vary from venue to venue.
111
+
112
+ If you want to keep the ability to emit an HTML document from your
91
113
  Markdown source file -- say, for a TextMate preview or a GitHub rendering
92
114
  -- you can use angle brackets around the `!SLIDE` keyword and styles, e.g.
93
115
 
@@ -106,7 +128,7 @@ Some useful styles for each slide are:
106
128
  * incremental - can be used with 'bullets' and 'commandline' styles, will incrementally update elements on arrow key rather than switch slides
107
129
  * small - make all slide text 80%
108
130
  * smaller - make all slide text 70%
109
- * execute - on js highlighted code slides, you can click on the code to execute it and display the results on the slide
131
+ * execute - on Javascript, Coffeescript and Ruby highlighted code slides, you can click on the code to execute it and display the results on the slide
110
132
 
111
133
  Check out the example directory included to see examples of most of these.
112
134
 
@@ -147,13 +169,20 @@ The transitions are provided by jQuery Cycle plugin. See http://www.malsup.com/j
147
169
  You can manage the presentation with the following keys:
148
170
 
149
171
  * space, cursor right: next slide
150
- * cursor left: previous slide
172
+ * shift-space, cursor left: previous slide
151
173
  * d: debug mode
152
- * c: table of contents (vi)
174
+ * c, t: table of contents (vi)
153
175
  * f: toggle footer
154
- * z: toggle help
176
+ * z, ?: toggle help
155
177
  * p: toggle preshow
156
178
 
179
+ = Showing plain old markdown
180
+
181
+ If a markdown file has no !SLIDE keywords, then showoff will treat every line
182
+ beginning with a single hash -- i.e. every H1 -- as a new slide in "bullets"
183
+ style. Remember that you can't specify classes or transitions in this mode,
184
+ and as soon as you add one !SLIDE you need them everywhere.
185
+
157
186
  = Preshow
158
187
 
159
188
  If you want to show a slideshow while you wait to speak, you can run a preshow. Add a +_preshow+ directory
@@ -166,7 +195,7 @@ audience in the meantime. Press 'p' again to stop, or wait until the timer runs
166
195
 
167
196
  To insert custom JavaScript into your presentation you can either place it into
168
197
  a file (with extension .js) into the root directory of your presentation or you
169
- can embed a <script>-element directly into your slides. This JavaScript will be
198
+ can embed a <+script+> element directly into your slides. This JavaScript will be
170
199
  executed—as usually—as soon as it is loaded.
171
200
 
172
201
  If you want to trigger some JavaScript as soon as a certain page is shown or
@@ -222,8 +251,8 @@ The same applies to the *showoff:prev* event, of course.
222
251
  = Custom Stylesheets
223
252
 
224
253
  To insert custom Stylesheets into your presentation you can either place it into
225
- a file (with extension .css) or into the root directory of your presentation or
226
- you can embed a <+link+>-element directly into your slides. This stylesheet will
254
+ a file (with extension .css) into the root directory of your presentation or
255
+ you can embed a <+link+> element directly into your slides. This stylesheet will
227
256
  be applied as soon as it is loaded.
228
257
 
229
258
  The content generated by the slide is wrapped with a +div+ with the class .+content+ like this.
@@ -257,14 +286,31 @@ Note that the example above uses CSS3 styling with ::+after+ and the +content+
257
286
  = Language highlighting
258
287
 
259
288
  Showoff uses {shjs}[http://shjs.sourceforge.net/] to highlight code blocks.
260
- If you begin a code block with three @-signs followed by a
261
- {programming language name}[http://shjs.sourceforge.net/doc/documentation.html],
289
+ If you begin a code block with three @-signs followed by a
290
+ {programming language name}[http://shjs.sourceforge.net/doc/documentation.html],
262
291
  that line will be stripped and the rest of the block will become sparkly
263
292
  and colorful.
264
293
 
265
294
  @@@ ruby
266
295
  10.times { puts "Whee!" }
267
296
 
297
+ = Custom Ruby Files
298
+
299
+ If you want to have executable Ruby code on your slides you must set the
300
+ environment variable ENV['SHOWOFF_EVAL_RUBY']. This can be done with
301
+
302
+ export SHOWOFF_EVAL_RUBY=1
303
+
304
+ or
305
+
306
+ # On Heroku
307
+ heroku config:add SHOWOFF_EVAL_RUBY=1
308
+
309
+
310
+ If you need supporting libraries when you evaluate the code. You can do this by
311
+ putting Ruby files (*.rb) into the root directory of the presentation then they
312
+ will be required when the presentation loads.
313
+
268
314
  = Editor integration
269
315
 
270
316
  The "add slide" feature can allow you to add the necessary boilerplate from your editor. If you are using vim, you can
@@ -294,19 +340,24 @@ added where your cursor was. Binding this to a keybinding can allow you to add
294
340
  [<tt>help</tt>] Shows list of commands or help for one command
295
341
  [<tt>heroku</tt>] Setup your presentation to serve on Heroku
296
342
  [<tt>github</tt>] Setup your presentation to serve on GitHub Pages
297
- [<tt>serve</tt>] Serves the showoff presentation in the current directory
343
+ [<tt>serve</tt>] Serves the showoff presentation in the current directory (or a given dir)
298
344
  [<tt>static</tt>] Generate static version of presentation
299
345
 
300
- === <tt>add [title]</tt>
346
+
347
+ == <tt>showoff add [title]</tt>
301
348
 
302
349
  Add a new slide at the end in a given dir
303
350
 
304
351
  *Aliases*
305
352
  * <tt><b>new</b></tt>
306
353
 
307
- Outputs or creates a new slide. With -d and -n, a new slide is created in the given dir, numbered to appear as the last slide in that dir (use -u to avoid numbering). Without those, outputs the slide markdown to stdout (useful for shelling out from your editor). You may also specify a source file to use for a code slide
354
+ Outputs or creates a new slide. With -d and -n, a new slide is created in the given dir, numbered to appear
355
+ as the last slide in that dir (use -u to avoid numbering). Without those, outputs the slide markdown to
356
+ stdout (useful for shelling out from your editor). You may also specify a source file to use for a code
357
+ slide.
358
+
359
+ === options for add
308
360
 
309
- ==== Options
310
361
  These options are specified *after* the command.
311
362
 
312
363
  [<tt>-d, --dir=dir</tt>] Slide dir (where to put a new slide file)
@@ -314,7 +365,9 @@ These options are specified *after* the command.
314
365
  [<tt>-s, --source=path to file</tt>] Include code from the given file as the slide body
315
366
  [<tt>-t, --style, --type=valid showoff style/type</tt>] Slide Type/Style <i>( default: <tt>title</tt>)</i>
316
367
  [<tt>-u, --nonumber</tt>] Dont number the slide, use the given name verbatim
317
- === <tt>create dir_name</tt>
368
+
369
+
370
+ == <tt>showoff create dir_name</tt>
318
371
 
319
372
  Create new showoff presentation
320
373
 
@@ -323,57 +376,92 @@ Create new showoff presentation
323
376
 
324
377
  This command helps start a new showoff presentation by setting up the proper directory structure for you. It takes the directory name you would like showoff to create for you.
325
378
 
326
- ==== Options
379
+ === options for create
380
+
327
381
  These options are specified *after* the command.
328
382
 
329
383
  [<tt>-d, --slidedir=arg</tt>] sample slide directory name <i>( default: <tt>one</tt>)</i>
330
384
  [<tt>-n, --nosamples</tt>] Dont create sample slides
331
- === <tt>help [command]</tt>
385
+
386
+
387
+ == <tt>showoff help [command]</tt>
332
388
 
333
389
  Shows list of commands or help for one command
334
390
 
335
- === <tt>heroku heroku_name</tt>
391
+
392
+ == <tt>showoff heroku heroku_name</tt>
336
393
 
337
394
  Setup your presentation to serve on Heroku
338
395
 
339
396
  Creates the Gemfile and config.ru file needed to push a showoff pres to heroku. It will then run heroku create for you to register the new project on heroku and add the remote for you. Then all you need to do is commit the new created files and run git push heroku to deploy.
340
397
 
341
- === <tt>github</tt>
398
+
399
+ == <tt>showoff github</tt>
342
400
 
343
401
  Generates a static version of your site and puts it in a gh-pages branch for static serving on GitHub.
344
402
 
345
- ==== Options
403
+ === options for github
346
404
  These options are specified *after* the command.
347
405
 
348
406
  [<tt>-f, --force</tt>] force overwrite of existing Gemfile/.gems and config.ru files if they exist
349
407
  [<tt>-g, --dotgems</tt>] Use older-style .gems file instead of bundler-style Gemfile
350
408
  [<tt>-p, --password=arg</tt>] add password protection to your heroku site
351
- === <tt>serve </tt>
352
409
 
353
- Serves the showoff presentation in the current directory
354
410
 
411
+ == <tt>showoff serve </tt>
355
412
 
413
+ Serves the showoff presentation in the current directory
356
414
 
357
- ==== Options
415
+ ==== options for serve
358
416
  These options are specified *after* the command.
359
417
 
418
+ [<tt>-f, --pres_file=arg</tt>] Presentation file <i>(default: <tt>showoff.json</tt>)</i>
360
419
  [<tt>-h, --host=arg</tt>] Host or ip to run on <i>( default: <tt>localhost</tt>)</i>
361
420
  [<tt>-p, --port=arg</tt>] Port on which to run <i>( default: <tt>9090</tt>)</i>
362
- === <tt>static name</tt>
421
+
422
+
423
+ == <tt>showoff static name</tt>
363
424
 
364
425
  Generate static version of presentation
365
426
 
366
- === PDF
367
- Append a "/pdf" to the end of your presentation URL, and a PDF will be generated within the browser. For example,
368
- http://localhost:9090/pdf
427
+ = PDF Output
428
+
429
+ Showoff can produce a PDF version of your presentation. To do this, you must install a few things first:
430
+
431
+ gem install pdfkit
432
+
433
+ You'll then need to install a version of wkhtmltopdf available at the {wkhtmltopdf repo}[http://code.google.com/p/wkhtmltopdf/wiki/compilation] (or brew install wkhtmltopdf on a mac) and make sure that +wkhtmltopdf+ is in your path:
369
434
 
370
- === ZSH completion
435
+ export $PATH="/location/to/my/wkhtmltopdf/0.9.9:$PATH"
436
+
437
+
438
+
439
+ Then restart showoff, and navigate to <tt>/pdf</tt> (e.g. http://localhost/pdf) of your presentation and a PDF will be generated with the browser.
440
+
441
+ = Completion
442
+
443
+ == ZSH completion
371
444
  You can complete commands and options in ZSH, by installing a script:
372
445
 
373
446
  mkdir -p $HOME/.zsh/Completion
374
447
  cp script/_showoff $HOME/.zsh/Completion
375
448
  echo 'fpath=(~/.zsh/Completion $fpath)' >> $HOME/.zshrc
376
449
 
450
+ == <tt>bash</tt> completion
451
+
452
+ You can complete commands for showoff by putting the following in your <tt>.bashrc</tt> (or whatever
453
+ you use when starting <tt>bash</tt>):
454
+
455
+ complete -F get_showoff_commands
456
+ function get_showoff_commands()
457
+ {
458
+ if [ -z $2 ] ; then
459
+ COMPREPLY=(`showoff help -c`)
460
+ else
461
+ COMPREPLY=(`showoff help -c $2`)
462
+ fi
463
+ }
464
+
377
465
  = Real World Usage
378
466
 
379
467
  So far, ShowOff has been used in the following presentations (and many others):
@@ -407,6 +495,12 @@ So far, ShowOff has been used in the following presentations (and many others):
407
495
  http://github.com/nono/Presentations/tree/master/20100703_25_promising_projects_in_50_minutes/
408
496
  * 11th Libre Software Meeting 2010 - Ruby 1.9, The future of Ruby? - Bruno Michel
409
497
  http://github.com/nono/Presentations/tree/master/20100708_RMLL_Ruby_1.9/
498
+ * Lone Star PHP 2011 - Drupal - Chris Christensen
499
+ https://github.com/christianchristensen/Presentations/tree/master/20110611-lonestarphp-drupal
500
+ * Railsbridge Open Workshops - Sarah Allen, Sarah Mei, and Alex Chaffee
501
+ http://github.com/alexch/workshop
502
+ * SDRuby Lightning Talk - Readable Regexps - Ian Young
503
+ https://github.com/iangreenleaf/sdruby-lightningtalk-tregexp
410
504
 
411
505
 
412
506
  If you use it for something, please let me know so I can add it.
data/bin/showoff CHANGED
@@ -56,19 +56,28 @@ command :heroku do |c|
56
56
  end
57
57
  end
58
58
 
59
- desc 'Serves the showoff presentation in the current directory'
59
+ desc 'Serves the showoff presentation in the specified (or current) directory'
60
+ arg_name "[pres_dir]"
61
+ default_value "."
60
62
  command :serve do |c|
61
63
 
64
+ c.desc 'Show verbose messaging'
65
+ c.switch :verbose
66
+
62
67
  c.desc 'Port on which to run'
63
68
  c.default_value "9090"
64
69
  c.flag [:p,:port]
65
70
 
66
71
  c.desc 'Host or ip to run on'
67
72
  c.default_value "localhost"
68
- c.flag [:h,:host]
73
+ c.flag [:h,:host]
74
+
75
+ c.desc 'JSON file used to describe presentation'
76
+ c.default_value "showoff.json"
77
+ c.flag [:f, :pres_file]
69
78
 
70
79
  c.action do |global_options,options,args|
71
- ShowOff.run! :host => options[:h], :port => options[:p].to_i
80
+ ShowOff.run! :host => options[:h], :port => options[:p].to_i, :pres_file => options[:f], :pres_dir => args[0], :verbose => options[:verbose]
72
81
  end
73
82
  end
74
83
 
@@ -0,0 +1,67 @@
1
+ require 'parslet'
2
+
3
+ # For parsing commandline slide content.
4
+ class CommandlineParser < Parslet::Parser
5
+
6
+ rule(:prompt) do
7
+ str('$') | str('#')
8
+ end
9
+
10
+ rule(:text) do
11
+ match['[:print:]'].repeat
12
+ end
13
+
14
+ rule(:singleline_input) do
15
+ (str("\\\n").absent? >> match['[:print:]']).repeat
16
+ end
17
+
18
+ rule(:input) do
19
+ multiline_input | singleline_input
20
+ end
21
+
22
+ rule(:multiline_input) do
23
+
24
+ # some command \
25
+ # continued \
26
+ # \
27
+ # and stop
28
+ ( singleline_input >> str('\\') >> newline ).repeat(1) >> singleline_input
29
+ end
30
+
31
+ rule(:command) do
32
+
33
+ # $ some command
34
+ # some output
35
+ ( prompt.as(:prompt) >> space? >> input.as(:input) >> output? ).as(:command)
36
+ end
37
+
38
+ rule(:output) do
39
+
40
+ # output
41
+ prompt.absent? >> text
42
+ end
43
+
44
+ rule(:output?) do
45
+
46
+ #
47
+ # some text
48
+ # some text
49
+ #
50
+ # some text
51
+ ( newline >> ( ( output >> newline ).repeat >> output.maybe ).as(:output) ).maybe
52
+ end
53
+
54
+ rule(:commands) do
55
+ command.repeat
56
+ end
57
+
58
+ rule(:newline) do
59
+ str("\n") | str("\r\n")
60
+ end
61
+
62
+ rule(:space?) do
63
+ match['[:space:]'].repeat
64
+ end
65
+
66
+ root(:commands)
67
+ end
data/lib/showoff.rb CHANGED
@@ -3,10 +3,12 @@ require 'sinatra/base'
3
3
  require 'json'
4
4
  require 'nokogiri'
5
5
  require 'fileutils'
6
+ require 'logger'
6
7
 
7
8
  here = File.expand_path(File.dirname(__FILE__))
8
9
  require "#{here}/showoff_utils"
9
10
  require "#{here}/princely"
11
+ require "#{here}/commandline_parser"
10
12
 
11
13
  begin
12
14
  require 'RMagick'
@@ -27,38 +29,58 @@ rescue LoadError
27
29
  Object.send(:remove_const,:Markdown)
28
30
  Markdown = BlueCloth
29
31
  end
30
- require 'pp'
31
32
 
32
33
  class ShowOff < Sinatra::Application
33
34
 
34
- Version = VERSION = '0.4.2'
35
+ Version = VERSION = '0.6.0'
35
36
 
36
37
  attr_reader :cached_image_size
37
38
 
38
39
  set :views, File.dirname(__FILE__) + '/../views'
39
40
  set :public, File.dirname(__FILE__) + '/../public'
40
- set :pres_dir, 'example'
41
41
 
42
42
  def initialize(app=nil)
43
43
  super(app)
44
- puts dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
45
- if Dir.pwd == dir
46
- options.pres_dir = dir + '/example'
44
+ @logger = Logger.new(STDOUT)
45
+ @logger.formatter = proc { |severity,datetime,progname,msg| "#{progname} #{msg}\n" }
46
+ @logger.level = options.verbose ? Logger::DEBUG : Logger::WARN
47
+
48
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
49
+ @logger.debug(dir)
50
+
51
+ showoff_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
52
+ if Dir.pwd == showoff_dir
53
+ options.pres_dir = "#{showoff_dir}/example"
47
54
  @root_path = "."
48
55
  else
49
- options.pres_dir = Dir.pwd
56
+ options.pres_dir ||= Dir.pwd
50
57
  @root_path = ".."
51
58
  end
59
+ options.pres_dir = File.expand_path(options.pres_dir)
60
+ if (options.pres_file)
61
+ puts "Using #{options.pres_file}"
62
+ ShowOffUtils.presentation_config_file = options.pres_file
63
+ end
64
+ puts "Serving presentation from #{options.pres_dir}"
52
65
  @cached_image_size = {}
53
- puts options.pres_dir
66
+ @logger.debug options.pres_dir
54
67
  @pres_name = options.pres_dir.split('/').pop
68
+ require_ruby_files
69
+ end
70
+
71
+ def require_ruby_files
72
+ Dir.glob("#{options.pres_dir}/*.rb").map { |path| require path }
55
73
  end
56
74
 
57
75
  helpers do
58
76
  def load_section_files(section)
59
77
  section = File.join(options.pres_dir, section)
60
- files = Dir.glob("#{section}/**/*").sort
61
- pp files
78
+ files = if File.directory? section
79
+ Dir.glob("#{section}/**/*").sort
80
+ else
81
+ [section]
82
+ end
83
+ @logger.debug files
62
84
  files
63
85
  end
64
86
 
@@ -70,34 +92,68 @@ class ShowOff < Sinatra::Application
70
92
  Dir.glob("#{options.pres_dir}/*.js").map { |path| File.basename(path) }
71
93
  end
72
94
 
95
+
73
96
  def preshow_files
74
97
  Dir.glob("#{options.pres_dir}/_preshow/*").map { |path| File.basename(path) }.to_json
75
98
  end
76
99
 
77
- def process_markdown(name, content, static=false)
78
- slides = content.split(/^<?!SLIDE/)
79
- slides.delete('')
100
+ # todo: move more behavior into this class
101
+ class Slide
102
+ attr_reader :classes, :text
103
+ def initialize classes = ""
104
+ @classes = ["content"] + classes.strip.chomp('>').split
105
+ @text = ""
106
+ end
107
+ def <<(s)
108
+ @text << s
109
+ @text << "\n"
110
+ end
111
+ def empty?
112
+ @text.strip == ""
113
+ end
114
+ end
115
+
116
+
117
+ def process_markdown(name, content, static=false, pdf=false)
118
+
119
+ # if there are no !SLIDE markers, then make every H1 define a new slide
120
+ unless content =~ /^\<?!SLIDE/m
121
+ content = content.gsub(/^# /m, "<!SLIDE>\n# ")
122
+ end
123
+
124
+ # todo: unit test
125
+ lines = content.split("\n")
126
+ puts "#{name}: #{lines.length} lines"
127
+ slides = []
128
+ slides << (slide = Slide.new)
129
+ until lines.empty?
130
+ line = lines.shift
131
+ if line =~ /^<?!SLIDE(.*)>?/
132
+ slides << (slide = Slide.new($1))
133
+ else
134
+ slide << line
135
+ end
136
+ end
137
+
138
+ slides.delete_if {|slide| slide.empty? }
139
+
80
140
  final = ''
81
141
  if slides.size > 1
82
142
  seq = 1
83
143
  end
84
144
  slides.each do |slide|
85
145
  md = ''
86
- # extract content classes
87
- lines = slide.split("\n")
88
- content_classes = lines.shift.strip.chomp('>').split rescue []
89
- slide = lines.join("\n")
90
- # add content class too
91
- content_classes.unshift "content"
146
+ content_classes = slide.classes
147
+
92
148
  # extract transition, defaulting to none
93
149
  transition = 'none'
94
150
  content_classes.delete_if { |x| x =~ /^transition=(.+)/ && transition = $1 }
95
151
  # extract id, defaulting to none
96
152
  id = nil
97
153
  content_classes.delete_if { |x| x =~ /^#([\w-]+)/ && id = $1 }
98
- puts "id: #{id}" if id
99
- puts "classes: #{content_classes.inspect}"
100
- puts "transition: #{transition}"
154
+ @logger.debug "id: #{id}" if id
155
+ @logger.debug "classes: #{content_classes.inspect}"
156
+ @logger.debug "transition: #{transition}"
101
157
  # create html
102
158
  md += "<div"
103
159
  md += " id=\"#{id}\"" if id
@@ -108,8 +164,8 @@ class ShowOff < Sinatra::Application
108
164
  else
109
165
  md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
110
166
  end
111
- sl = Markdown.new(slide).to_html
112
- sl = update_image_paths(name, sl, static)
167
+ sl = Markdown.new(slide.text).to_html
168
+ sl = update_image_paths(name, sl, static, pdf)
113
169
  md += sl
114
170
  md += "</div>\n"
115
171
  md += "</div>\n"
@@ -124,14 +180,14 @@ class ShowOff < Sinatra::Application
124
180
  markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
125
181
  end
126
182
 
127
- def update_image_paths(path, slide, static=false)
183
+ def update_image_paths(path, slide, static=false, pdf=false)
128
184
  paths = path.split('/')
129
185
  paths.pop
130
186
  path = paths.join('/')
131
187
  replacement_prefix = static ?
132
- %(img src="file://#{options.pres_dir}/#{path}) :
188
+ ( pdf ? %(img src="file://#{options.pres_dir}/#{path}) : %(img src="./file/#{path}) ) :
133
189
  %(img src="/image/#{path})
134
- slide.gsub(/img src=\"(.*?)\"/) do |s|
190
+ slide.gsub(/img src=\"([^\/].*?)\"/) do |s|
135
191
  img_path = File.join(path, $1)
136
192
  w, h = get_image_size(img_path)
137
193
  src = %(#{replacement_prefix}/#{$1}")
@@ -146,7 +202,12 @@ class ShowOff < Sinatra::Application
146
202
  def get_image_size(path)
147
203
  if !cached_image_size.key?(path)
148
204
  img = Magick::Image.ping(path).first
149
- cached_image_size[path] = [img.columns, img.rows]
205
+ # don't set a size for svgs so they can expand to fit their container
206
+ if img.mime_type == 'image/svg+xml'
207
+ cached_image_size[path] = [nil, nil]
208
+ else
209
+ cached_image_size[path] = [img.columns, img.rows]
210
+ end
150
211
  end
151
212
  cached_image_size[path]
152
213
  end
@@ -157,14 +218,15 @@ class ShowOff < Sinatra::Application
157
218
 
158
219
  def update_commandline_code(slide)
159
220
  html = Nokogiri::XML.parse(slide)
221
+ parser = CommandlineParser.new
160
222
 
161
223
  html.css('pre').each do |pre|
162
224
  pre.css('code').each do |code|
163
225
  out = code.text
164
226
  lines = out.split("\n")
165
- if lines.first[0, 3] == '@@@'
227
+ if lines.first.strip[0, 3] == '@@@'
166
228
  lang = lines.shift.gsub('@@@', '').strip
167
- pre.set_attribute('class', 'sh_' + lang)
229
+ pre.set_attribute('class', 'sh_' + lang.downcase)
168
230
  code.content = lines.join("\n")
169
231
  end
170
232
  end
@@ -172,38 +234,53 @@ class ShowOff < Sinatra::Application
172
234
 
173
235
  html.css('.commandline > pre > code').each do |code|
174
236
  out = code.text
175
- lines = out.split(/^\$(.*?)$/)
176
- lines.delete('')
177
237
  code.content = ''
178
- while(lines.size > 0) do
179
- command = lines.shift
180
- result = lines.shift
181
- c = Nokogiri::XML::Node.new('code', html)
182
- c.set_attribute('class', 'command')
183
- c.content = '$' + command
184
- code << c
185
- c = Nokogiri::XML::Node.new('code', html)
186
- c.set_attribute('class', 'result')
187
- c.content = result
188
- code << c
238
+ tree = parser.parse(out)
239
+ transform = Parslet::Transform.new do
240
+ rule(:prompt => simple(:prompt), :input => simple(:input), :output => simple(:output)) do
241
+ command = Nokogiri::XML::Node.new('code', html)
242
+ command.set_attribute('class', 'command')
243
+ command.content = "#{prompt} #{input}"
244
+ code << command
245
+
246
+ # Add newline after the input so that users can
247
+ # advance faster than the typewriter effect
248
+ # and still keep inputs on separate lines.
249
+ code << "\n"
250
+
251
+ unless output.to_s.empty?
252
+
253
+ result = Nokogiri::XML::Node.new('code', html)
254
+ result.set_attribute('class', 'result')
255
+ result.content = output
256
+ code << result
257
+ end
258
+ end
189
259
  end
260
+ transform.apply(tree)
190
261
  end
191
262
  html.root.to_s
192
263
  end
193
264
 
194
- def get_slides_html(static=false)
195
- sections = ShowOffUtils.showoff_sections(options.pres_dir)
265
+ def get_slides_html(static=false, pdf=false)
266
+ sections = ShowOffUtils.showoff_sections(options.pres_dir, @logger)
196
267
  files = []
197
268
  if sections
198
- sections.each do |section|
199
- files << load_section_files(section)
200
- end
201
- files = files.flatten
202
- files = files.select { |f| f =~ /.md/ }
203
269
  data = ''
204
- files.each do |f|
205
- fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
206
- data += process_markdown(fname, File.read(f), static)
270
+ sections.each do |section|
271
+ if section =~ /^#/
272
+ name = section.each_line.first.gsub(/^#*/,'').strip
273
+ data << process_markdown(name, "<!SLIDE subsection>\n" + section, static, pdf)
274
+ else
275
+ files = []
276
+ files << load_section_files(section)
277
+ files = files.flatten
278
+ files = files.select { |f| f =~ /.md/ }
279
+ files.each do |f|
280
+ fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
281
+ data << process_markdown(fname, File.read(f), static, pdf)
282
+ end
283
+ end
207
284
  end
208
285
  end
209
286
  data
@@ -297,7 +374,7 @@ class ShowOff < Sinatra::Application
297
374
  end
298
375
 
299
376
  def pdf(static=true)
300
- @slides = get_slides_html(static)
377
+ @slides = get_slides_html(static, true)
301
378
  @no_js = false
302
379
  html = erb :onepage
303
380
  # TODO make a random filename
@@ -363,9 +440,33 @@ class ShowOff < Sinatra::Application
363
440
  FileUtils.makedirs(File.join(file_dir, dir))
364
441
  FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
365
442
  end
443
+ # copy images from css too
444
+ Dir.glob("#{pres_dir}/*.css").each do |css_path|
445
+ File.open(css_path) do |file|
446
+ data = file.read
447
+ data.scan(/url\((.*)\)/).flatten.each do |path|
448
+ @logger.debug path
449
+ dir = File.dirname(path)
450
+ FileUtils.makedirs(File.join(file_dir, dir))
451
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
452
+ end
453
+ end
454
+ end
366
455
  end
367
456
  end
368
457
 
458
+ def eval_ruby code
459
+ eval(code).to_s
460
+ rescue => e
461
+ e.message
462
+ end
463
+
464
+ get '/eval_ruby' do
465
+ return eval_ruby(params[:code]) if ENV['SHOWOFF_EVAL_RUBY']
466
+
467
+ return "Ruby Evaluation is off. To turn it on set ENV['SHOWOFF_EVAL_RUBY']"
468
+ end
469
+
369
470
  get %r{(?:image|file)/(.*)} do
370
471
  path = params[:captures].first
371
472
  full_path = File.join(options.pres_dir, path)