mg_showoff 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +532 -0
  3. data/Rakefile +7 -0
  4. data/bin/showoff +145 -0
  5. data/lib/commandline_parser.rb +67 -0
  6. data/lib/princely.rb +84 -0
  7. data/lib/showoff.rb +452 -0
  8. data/lib/showoff_utils.rb +369 -0
  9. data/public/css/fg.menu.css +114 -0
  10. data/public/css/onepage.css +60 -0
  11. data/public/css/pdf.css +12 -0
  12. data/public/css/reset.css +53 -0
  13. data/public/css/sh_style.css +66 -0
  14. data/public/css/showoff.css +361 -0
  15. data/public/css/spinner_bar.gif +0 -0
  16. data/public/css/theme/images/ui-bg_diagonals-small_100_f0efea_40x40.png +0 -0
  17. data/public/css/theme/images/ui-bg_flat_35_f0f0f0_40x100.png +0 -0
  18. data/public/css/theme/images/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
  19. data/public/css/theme/images/ui-bg_glow-ball_25_2e2e28_600x600.png +0 -0
  20. data/public/css/theme/images/ui-bg_highlight-soft_100_f0efea_1x100.png +0 -0
  21. data/public/css/theme/images/ui-bg_highlight-soft_25_327E04_1x100.png +0 -0
  22. data/public/css/theme/images/ui-bg_highlight-soft_25_5A9D1A_1x100.png +0 -0
  23. data/public/css/theme/images/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
  24. data/public/css/theme/images/ui-bg_inset-soft_22_3b3b35_1x100.png +0 -0
  25. data/public/css/theme/images/ui-icons_808080_256x240.png +0 -0
  26. data/public/css/theme/images/ui-icons_8DC262_256x240.png +0 -0
  27. data/public/css/theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  28. data/public/css/theme/images/ui-icons_e7e6e4_256x240.png +0 -0
  29. data/public/css/theme/images/ui-icons_eeeeee_256x240.png +0 -0
  30. data/public/css/theme/images/ui-icons_ffffff_256x240.png +0 -0
  31. data/public/css/theme/ui.accordion.css +9 -0
  32. data/public/css/theme/ui.all.css +2 -0
  33. data/public/css/theme/ui.base.css +9 -0
  34. data/public/css/theme/ui.core.css +37 -0
  35. data/public/css/theme/ui.datepicker.css +62 -0
  36. data/public/css/theme/ui.dialog.css +13 -0
  37. data/public/css/theme/ui.progressbar.css +4 -0
  38. data/public/css/theme/ui.resizable.css +13 -0
  39. data/public/css/theme/ui.slider.css +17 -0
  40. data/public/css/theme/ui.tabs.css +9 -0
  41. data/public/css/theme/ui.theme.css +245 -0
  42. data/public/favicon.ico +0 -0
  43. data/public/js/core.js +79 -0
  44. data/public/js/fg.menu.js +645 -0
  45. data/public/js/jTypeWriter.js +26 -0
  46. data/public/js/jquery-1.4.2.min.js +154 -0
  47. data/public/js/jquery-print.js +109 -0
  48. data/public/js/jquery.batchImageLoad.js +56 -0
  49. data/public/js/jquery.cookie.js +96 -0
  50. data/public/js/jquery.cycle.all.js +1284 -0
  51. data/public/js/jquery.doubletap-0.1.js +105 -0
  52. data/public/js/jquery.uuid.js +24 -0
  53. data/public/js/jquery.ws-0.3pre.js +201 -0
  54. data/public/js/onepage.js +5 -0
  55. data/public/js/sh_lang/sh_bison.min.js +1 -0
  56. data/public/js/sh_lang/sh_c.min.js +1 -0
  57. data/public/js/sh_lang/sh_caml.min.js +1 -0
  58. data/public/js/sh_lang/sh_changelog.min.js +1 -0
  59. data/public/js/sh_lang/sh_cpp.min.js +1 -0
  60. data/public/js/sh_lang/sh_csharp.min.js +1 -0
  61. data/public/js/sh_lang/sh_css.min.js +1 -0
  62. data/public/js/sh_lang/sh_cucumber.min.js +2 -0
  63. data/public/js/sh_lang/sh_desktop.min.js +1 -0
  64. data/public/js/sh_lang/sh_diff.min.js +1 -0
  65. data/public/js/sh_lang/sh_flex.min.js +1 -0
  66. data/public/js/sh_lang/sh_glsl.min.js +1 -0
  67. data/public/js/sh_lang/sh_haxe.min.js +1 -0
  68. data/public/js/sh_lang/sh_html.min.js +1 -0
  69. data/public/js/sh_lang/sh_java.min.js +1 -0
  70. data/public/js/sh_lang/sh_javascript.min.js +1 -0
  71. data/public/js/sh_lang/sh_javascript_dom.min.js +1 -0
  72. data/public/js/sh_lang/sh_latex.min.js +1 -0
  73. data/public/js/sh_lang/sh_ldap.min.js +1 -0
  74. data/public/js/sh_lang/sh_log.min.js +1 -0
  75. data/public/js/sh_lang/sh_lsm.min.js +1 -0
  76. data/public/js/sh_lang/sh_m4.min.js +1 -0
  77. data/public/js/sh_lang/sh_makefile.min.js +1 -0
  78. data/public/js/sh_lang/sh_oracle.min.js +1 -0
  79. data/public/js/sh_lang/sh_pascal.min.js +1 -0
  80. data/public/js/sh_lang/sh_perl.min.js +1 -0
  81. data/public/js/sh_lang/sh_php.min.js +1 -0
  82. data/public/js/sh_lang/sh_prolog.min.js +1 -0
  83. data/public/js/sh_lang/sh_properties.min.js +1 -0
  84. data/public/js/sh_lang/sh_python.min.js +1 -0
  85. data/public/js/sh_lang/sh_ruby.min.js +1 -0
  86. data/public/js/sh_lang/sh_scala.min.js +1 -0
  87. data/public/js/sh_lang/sh_sh.min.js +1 -0
  88. data/public/js/sh_lang/sh_slang.min.js +1 -0
  89. data/public/js/sh_lang/sh_sml.min.js +1 -0
  90. data/public/js/sh_lang/sh_spec.min.js +1 -0
  91. data/public/js/sh_lang/sh_sql.min.js +1 -0
  92. data/public/js/sh_lang/sh_tcl.min.js +1 -0
  93. data/public/js/sh_lang/sh_xml.min.js +1 -0
  94. data/public/js/sh_lang/sh_xorg.min.js +1 -0
  95. data/public/js/sh_main.min.js +4 -0
  96. data/public/js/showoff.js +623 -0
  97. data/public/js/showoffcore.js +13 -0
  98. data/views/index.erb +85 -0
  99. data/views/onepage.erb +35 -0
  100. metadata +227 -0
data/lib/showoff.rb ADDED
@@ -0,0 +1,452 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+ require 'json'
4
+ require 'nokogiri'
5
+ require 'fileutils'
6
+ require 'logger'
7
+
8
+ here = File.expand_path(File.dirname(__FILE__))
9
+ require "#{here}/showoff_utils"
10
+ require "#{here}/princely"
11
+ require "#{here}/commandline_parser"
12
+
13
+ begin
14
+ require 'RMagick'
15
+ rescue LoadError
16
+ $stderr.puts 'image sizing disabled - install rmagick'
17
+ end
18
+
19
+ begin
20
+ require 'pdfkit'
21
+ rescue LoadError
22
+ $stderr.puts 'pdf generation disabled - install pdfkit'
23
+ end
24
+
25
+ begin
26
+ require 'rdiscount'
27
+ rescue LoadError
28
+ require 'bluecloth'
29
+ Object.send(:remove_const,:Markdown)
30
+ Markdown = BlueCloth
31
+ end
32
+
33
+ class ShowOff < Sinatra::Application
34
+
35
+ Version = VERSION = '0.5.1'
36
+
37
+ attr_reader :cached_image_size
38
+
39
+ set :views, File.dirname(__FILE__) + '/../views'
40
+ set :public, File.dirname(__FILE__) + '/../public'
41
+
42
+ def initialize(app=nil)
43
+ super(app)
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"
54
+ @root_path = "."
55
+ else
56
+ options.pres_dir ||= Dir.pwd
57
+ @root_path = ".."
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}"
65
+ @cached_image_size = {}
66
+ @logger.debug options.pres_dir
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 }
73
+ end
74
+
75
+ helpers do
76
+ def load_section_files(section)
77
+ section = File.join(options.pres_dir, section)
78
+ files = Dir.glob("#{section}/**/*").sort
79
+ @logger.debug files
80
+ files
81
+ end
82
+
83
+ def css_files
84
+ Dir.glob("#{options.pres_dir}/*.css").map { |path| File.basename(path) }
85
+ end
86
+
87
+ def js_files
88
+ Dir.glob("#{options.pres_dir}/*.js").map { |path| File.basename(path) }
89
+ end
90
+
91
+
92
+ def preshow_files
93
+ Dir.glob("#{options.pres_dir}/_preshow/*").map { |path| File.basename(path) }.to_json
94
+ end
95
+
96
+ def process_markdown(name, content, static=false, pdf=false)
97
+
98
+ # if there are no !SLIDE markers, then make every H1 define a new slide
99
+ unless content =~ /^\<?!SLIDE/m
100
+ content = content.gsub(/^# /m, "<!SLIDE bullets>\n# ")
101
+ end
102
+
103
+ slides = content.split(/^<?!SLIDE/)
104
+ slides.delete('')
105
+ final = ''
106
+ if slides.size > 1
107
+ seq = 1
108
+ end
109
+ slides.each do |slide|
110
+ md = ''
111
+ # extract content classes
112
+ lines = slide.split("\n")
113
+ content_classes = lines.shift.strip.chomp('>').split rescue []
114
+ slide = lines.join("\n")
115
+ # add content class too
116
+ content_classes.unshift "content"
117
+ # extract transition, defaulting to none
118
+ transition = 'none'
119
+ content_classes.delete_if { |x| x =~ /^transition=(.+)/ && transition = $1 }
120
+ # extract id, defaulting to none
121
+ id = nil
122
+ content_classes.delete_if { |x| x =~ /^#([\w-]+)/ && id = $1 }
123
+ @logger.debug "id: #{id}" if id
124
+ @logger.debug "classes: #{content_classes.inspect}"
125
+ @logger.debug "transition: #{transition}"
126
+ # create html
127
+ md += "<div"
128
+ md += " id=\"#{id}\"" if id
129
+ md += " class=\"slide\" data-transition=\"#{transition}\">"
130
+ if seq
131
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
132
+ seq += 1
133
+ else
134
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
135
+ end
136
+ sl = Markdown.new(slide).to_html
137
+ sl = update_image_paths(name, sl, static, pdf)
138
+ md += sl
139
+ md += "</div>\n"
140
+ md += "</div>\n"
141
+ final += update_commandline_code(md)
142
+ final = update_p_classes(final)
143
+ end
144
+ final
145
+ end
146
+
147
+ # find any lines that start with a <p>.(something) and turn them into <p class="something">
148
+ def update_p_classes(markdown)
149
+ markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
150
+ end
151
+
152
+ def update_image_paths(path, slide, static=false, pdf=false)
153
+ paths = path.split('/')
154
+ paths.pop
155
+ path = paths.join('/')
156
+ replacement_prefix = static ?
157
+ ( pdf ? %(img src="file://#{options.pres_dir}/#{path}) : %(img src="./file/#{path}) ) :
158
+ %(img src="/image/#{path})
159
+ slide.gsub(/img src=\"(.*?)\"/) do |s|
160
+ img_path = File.join(path, $1)
161
+ w, h = get_image_size(img_path)
162
+ src = %(#{replacement_prefix}/#{$1}")
163
+ if w && h
164
+ src << %( width="#{w}" height="#{h}")
165
+ end
166
+ src
167
+ end
168
+ end
169
+
170
+ if defined?(Magick)
171
+ def get_image_size(path)
172
+ if !cached_image_size.key?(path)
173
+ img = Magick::Image.ping(path).first
174
+ # don't set a size for svgs so they can expand to fit their container
175
+ if img.mime_type == 'image/svg+xml'
176
+ cached_image_size[path] = [nil, nil]
177
+ else
178
+ cached_image_size[path] = [img.columns, img.rows]
179
+ end
180
+ end
181
+ cached_image_size[path]
182
+ end
183
+ else
184
+ def get_image_size(path)
185
+ end
186
+ end
187
+
188
+ def update_commandline_code(slide)
189
+ html = Nokogiri::XML.parse(slide)
190
+ parser = CommandlineParser.new
191
+
192
+ html.css('pre').each do |pre|
193
+ pre.css('code').each do |code|
194
+ out = code.text
195
+ lines = out.split("\n")
196
+ if lines.first[0, 3] == '@@@'
197
+ lang = lines.shift.gsub('@@@', '').strip
198
+ pre.set_attribute('class', 'sh_' + lang.downcase)
199
+ code.content = lines.join("\n")
200
+ end
201
+ end
202
+ end
203
+
204
+ html.css('.commandline > pre > code').each do |code|
205
+ out = code.text
206
+ code.content = ''
207
+ tree = parser.parse(out)
208
+ transform = Parslet::Transform.new do
209
+ rule(:prompt => simple(:prompt), :input => simple(:input), :output => simple(:output)) do
210
+ command = Nokogiri::XML::Node.new('code', html)
211
+ command.set_attribute('class', 'command')
212
+ command.content = "#{prompt} #{input}"
213
+ code << command
214
+
215
+ # Add newline after the input so that users can
216
+ # advance faster than the typewriter effect
217
+ # and still keep inputs on separate lines.
218
+ code << "\n"
219
+
220
+ unless output.to_s.empty?
221
+
222
+ result = Nokogiri::XML::Node.new('code', html)
223
+ result.set_attribute('class', 'result')
224
+ result.content = output
225
+ code << result
226
+ end
227
+ end
228
+ end
229
+ transform.apply(tree)
230
+ end
231
+ html.root.to_s
232
+ end
233
+
234
+ def get_slides_html(static=false, pdf=false)
235
+ sections = ShowOffUtils.showoff_sections(options.pres_dir,@logger)
236
+ files = []
237
+ if sections
238
+ sections.each do |section|
239
+ files << load_section_files(section)
240
+ end
241
+ files = files.flatten
242
+ files = files.select { |f| f =~ /.md$/ }
243
+ data = ''
244
+ files.each do |f|
245
+ fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
246
+ data += process_markdown(fname, File.read(f), static, pdf)
247
+ end
248
+ end
249
+ data
250
+ end
251
+
252
+ def inline_css(csses, pre = nil)
253
+ css_content = '<style type="text/css">'
254
+ csses.each do |css_file|
255
+ if pre
256
+ css_file = File.join(File.dirname(__FILE__), '..', pre, css_file)
257
+ else
258
+ css_file = File.join(options.pres_dir, css_file)
259
+ end
260
+ css_content += File.read(css_file)
261
+ end
262
+ css_content += '</style>'
263
+ css_content
264
+ end
265
+
266
+ def inline_js(jses, pre = nil)
267
+ js_content = '<script type="text/javascript">'
268
+ jses.each do |js_file|
269
+ if pre
270
+ js_file = File.join(File.dirname(__FILE__), '..', pre, js_file)
271
+ else
272
+ js_file = File.join(options.pres_dir, js_file)
273
+ end
274
+ js_content += File.read(js_file)
275
+ end
276
+ js_content += '</script>'
277
+ js_content
278
+ end
279
+
280
+ def inline_all_js(jses_directory)
281
+ inline_js(Dir.entries(File.join(File.dirname(__FILE__), '..', jses_directory)).find_all{|filename| filename.length > 2 }, jses_directory)
282
+ end
283
+
284
+ def index(static=false)
285
+ if static
286
+ @title = ShowOffUtils.showoff_title
287
+ @slides = get_slides_html(static)
288
+ @asset_path = "./"
289
+ end
290
+ erb :index
291
+ end
292
+
293
+ def clean_link(href)
294
+ if href && href[0, 1] == '/'
295
+ href = href[1, href.size]
296
+ end
297
+ href
298
+ end
299
+
300
+ def assets_needed
301
+ assets = ["index", "slides"]
302
+
303
+ index = erb :index
304
+ html = Nokogiri::XML.parse(index)
305
+ html.css('head link').each do |link|
306
+ href = clean_link(link['href'])
307
+ assets << href if href
308
+ end
309
+ html.css('head script').each do |link|
310
+ href = clean_link(link['src'])
311
+ assets << href if href
312
+ end
313
+
314
+ slides = get_slides_html
315
+ html = Nokogiri::XML.parse("<slides>" + slides + "</slides>")
316
+ html.css('img').each do |link|
317
+ href = clean_link(link['src'])
318
+ assets << href if href
319
+ end
320
+
321
+ css = Dir.glob("#{options.public}/**/*.css").map { |path| path.gsub(options.public + '/', '') }
322
+ assets << css
323
+
324
+ js = Dir.glob("#{options.public}/**/*.js").map { |path| path.gsub(options.public + '/', '') }
325
+ assets << js
326
+
327
+ assets.uniq.join("\n")
328
+ end
329
+
330
+ def slides(static=false)
331
+ get_slides_html(static)
332
+ end
333
+
334
+ def onepage(static=false)
335
+ @slides = get_slides_html(static)
336
+ erb :onepage
337
+ end
338
+
339
+ def pdf(static=true)
340
+ @slides = get_slides_html(static, true)
341
+ @no_js = false
342
+ html = erb :onepage
343
+ # TODO make a random filename
344
+
345
+ # PDFKit.new takes the HTML and any options for wkhtmltopdf
346
+ # run `wkhtmltopdf --extended-help` for a full list of options
347
+ kit = PDFKit.new(html, :page_size => 'Letter', :orientation => 'Landscape')
348
+
349
+ # Save the PDF to a file
350
+ file = kit.to_file('/tmp/preso.pdf')
351
+ end
352
+
353
+ end
354
+
355
+
356
+ def self.do_static(what)
357
+ what = "index" if !what
358
+
359
+ # Nasty hack to get the actual ShowOff module
360
+ showoff = ShowOff.new
361
+ while !showoff.is_a?(ShowOff)
362
+ showoff = showoff.instance_variable_get(:@app)
363
+ end
364
+ name = showoff.instance_variable_get(:@pres_name)
365
+ path = showoff.instance_variable_get(:@root_path)
366
+ data = showoff.send(what, true)
367
+ if data.is_a?(File)
368
+ FileUtils.cp(data.path, "#{name}.pdf")
369
+ else
370
+ out = "#{path}/#{name}/static"
371
+ # First make a directory
372
+ FileUtils.makedirs(out)
373
+ # Then write the html
374
+ file = File.new("#{out}/index.html", "w")
375
+ file.puts(data)
376
+ file.close
377
+ # Now copy all the js and css
378
+ my_path = File.join( File.dirname(__FILE__), '..', 'public')
379
+ ["js", "css"].each { |dir|
380
+ FileUtils.copy_entry("#{my_path}/#{dir}", "#{out}/#{dir}")
381
+ }
382
+ # And copy the directory
383
+ Dir.glob("#{my_path}/#{name}/*").each { |subpath|
384
+ base = File.basename(subpath)
385
+ next if "static" == base
386
+ next unless File.directory?(subpath) || base.match(/\.(css|js)$/)
387
+ FileUtils.copy_entry(subpath, "#{out}/#{base}")
388
+ }
389
+
390
+ # Set up file dir
391
+ file_dir = File.join(out, 'file')
392
+ FileUtils.makedirs(file_dir)
393
+ pres_dir = showoff.options.pres_dir
394
+
395
+ # ..., copy all user-defined styles and javascript files
396
+ Dir.glob("#{pres_dir}/*.{css,js}").each { |path|
397
+ FileUtils.copy(path, File.join(file_dir, File.basename(path)))
398
+ }
399
+
400
+ # ... and copy all needed image files
401
+ data.scan(/img src=\".\/file\/(.*?)\"/).flatten.each do |path|
402
+ dir = File.dirname(path)
403
+ FileUtils.makedirs(File.join(file_dir, dir))
404
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
405
+ end
406
+ # copy images from css too
407
+ Dir.glob("#{pres_dir}/*.css").each do |css_path|
408
+ File.open(css_path) do |file|
409
+ data = file.read
410
+ data.scan(/url\((.*)\)/).flatten.each do |path|
411
+ @logger.debug path
412
+ dir = File.dirname(path)
413
+ FileUtils.makedirs(File.join(file_dir, dir))
414
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ def eval_ruby code
422
+ eval(code).to_s
423
+ rescue => e
424
+ e.message
425
+ end
426
+
427
+ get '/eval_ruby' do
428
+ return eval_ruby(params[:code]) if ENV['SHOWOFF_EVAL_RUBY']
429
+
430
+ return "Ruby Evaluation is off. To turn it on set ENV['SHOWOFF_EVAL_RUBY']"
431
+ end
432
+
433
+ get %r{(?:image|file)/(.*)} do
434
+ path = params[:captures].first
435
+ full_path = File.join(options.pres_dir, path)
436
+ send_file full_path
437
+ end
438
+
439
+ get %r{/(.*)} do
440
+ @title = ShowOffUtils.showoff_title
441
+ what = params[:captures].first
442
+ what = 'index' if "" == what
443
+ if (what != "favicon.ico")
444
+ data = send(what)
445
+ if data.is_a?(File)
446
+ send_file data.path
447
+ else
448
+ data
449
+ end
450
+ end
451
+ end
452
+ end