mg_showoff 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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