goncalossilva-showoff 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +473 -0
  3. data/Rakefile +7 -0
  4. data/bin/showoff +136 -0
  5. data/lib/princely.rb +84 -0
  6. data/lib/showoff.rb +418 -0
  7. data/lib/showoff_utils.rb +361 -0
  8. data/public/css/fg.menu.css +114 -0
  9. data/public/css/onepage.css +60 -0
  10. data/public/css/pdf.css +12 -0
  11. data/public/css/reset.css +53 -0
  12. data/public/css/sh_style.css +66 -0
  13. data/public/css/showoff.css +350 -0
  14. data/public/css/spinner_bar.gif +0 -0
  15. data/public/css/theme/images/ui-bg_diagonals-small_100_f0efea_40x40.png +0 -0
  16. data/public/css/theme/images/ui-bg_flat_35_f0f0f0_40x100.png +0 -0
  17. data/public/css/theme/images/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
  18. data/public/css/theme/images/ui-bg_glow-ball_25_2e2e28_600x600.png +0 -0
  19. data/public/css/theme/images/ui-bg_highlight-soft_100_f0efea_1x100.png +0 -0
  20. data/public/css/theme/images/ui-bg_highlight-soft_25_327E04_1x100.png +0 -0
  21. data/public/css/theme/images/ui-bg_highlight-soft_25_5A9D1A_1x100.png +0 -0
  22. data/public/css/theme/images/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
  23. data/public/css/theme/images/ui-bg_inset-soft_22_3b3b35_1x100.png +0 -0
  24. data/public/css/theme/images/ui-icons_808080_256x240.png +0 -0
  25. data/public/css/theme/images/ui-icons_8DC262_256x240.png +0 -0
  26. data/public/css/theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  27. data/public/css/theme/images/ui-icons_e7e6e4_256x240.png +0 -0
  28. data/public/css/theme/images/ui-icons_eeeeee_256x240.png +0 -0
  29. data/public/css/theme/images/ui-icons_ffffff_256x240.png +0 -0
  30. data/public/css/theme/ui.accordion.css +9 -0
  31. data/public/css/theme/ui.all.css +2 -0
  32. data/public/css/theme/ui.base.css +9 -0
  33. data/public/css/theme/ui.core.css +37 -0
  34. data/public/css/theme/ui.datepicker.css +62 -0
  35. data/public/css/theme/ui.dialog.css +13 -0
  36. data/public/css/theme/ui.progressbar.css +4 -0
  37. data/public/css/theme/ui.resizable.css +13 -0
  38. data/public/css/theme/ui.slider.css +17 -0
  39. data/public/css/theme/ui.tabs.css +9 -0
  40. data/public/css/theme/ui.theme.css +245 -0
  41. data/public/favicon.ico +0 -0
  42. data/public/js/core.js +79 -0
  43. data/public/js/fg.menu.js +645 -0
  44. data/public/js/jTypeWriter.js +26 -0
  45. data/public/js/jquery-1.4.2.min.js +154 -0
  46. data/public/js/jquery-print.js +109 -0
  47. data/public/js/jquery.batchImageLoad.js +56 -0
  48. data/public/js/jquery.cookie.js +96 -0
  49. data/public/js/jquery.cycle.all.js +1284 -0
  50. data/public/js/jquery.doubletap-0.1.js +105 -0
  51. data/public/js/jquery.uuid.js +24 -0
  52. data/public/js/jquery.ws-0.3pre.js +201 -0
  53. data/public/js/onepage.js +5 -0
  54. data/public/js/sh_lang/sh_bison.min.js +1 -0
  55. data/public/js/sh_lang/sh_c.min.js +1 -0
  56. data/public/js/sh_lang/sh_caml.min.js +1 -0
  57. data/public/js/sh_lang/sh_changelog.min.js +1 -0
  58. data/public/js/sh_lang/sh_cpp.min.js +1 -0
  59. data/public/js/sh_lang/sh_csharp.min.js +1 -0
  60. data/public/js/sh_lang/sh_css.min.js +1 -0
  61. data/public/js/sh_lang/sh_cucumber.min.js +2 -0
  62. data/public/js/sh_lang/sh_desktop.min.js +1 -0
  63. data/public/js/sh_lang/sh_diff.min.js +1 -0
  64. data/public/js/sh_lang/sh_flex.min.js +1 -0
  65. data/public/js/sh_lang/sh_glsl.min.js +1 -0
  66. data/public/js/sh_lang/sh_haxe.min.js +1 -0
  67. data/public/js/sh_lang/sh_html.min.js +1 -0
  68. data/public/js/sh_lang/sh_java.min.js +1 -0
  69. data/public/js/sh_lang/sh_javascript.min.js +1 -0
  70. data/public/js/sh_lang/sh_javascript_dom.min.js +1 -0
  71. data/public/js/sh_lang/sh_latex.min.js +1 -0
  72. data/public/js/sh_lang/sh_ldap.min.js +1 -0
  73. data/public/js/sh_lang/sh_log.min.js +1 -0
  74. data/public/js/sh_lang/sh_lsm.min.js +1 -0
  75. data/public/js/sh_lang/sh_m4.min.js +1 -0
  76. data/public/js/sh_lang/sh_makefile.min.js +1 -0
  77. data/public/js/sh_lang/sh_oracle.min.js +1 -0
  78. data/public/js/sh_lang/sh_pascal.min.js +1 -0
  79. data/public/js/sh_lang/sh_perl.min.js +1 -0
  80. data/public/js/sh_lang/sh_php.min.js +1 -0
  81. data/public/js/sh_lang/sh_prolog.min.js +1 -0
  82. data/public/js/sh_lang/sh_properties.min.js +1 -0
  83. data/public/js/sh_lang/sh_python.min.js +1 -0
  84. data/public/js/sh_lang/sh_ruby.min.js +1 -0
  85. data/public/js/sh_lang/sh_scala.min.js +1 -0
  86. data/public/js/sh_lang/sh_sh.min.js +1 -0
  87. data/public/js/sh_lang/sh_slang.min.js +1 -0
  88. data/public/js/sh_lang/sh_sml.min.js +1 -0
  89. data/public/js/sh_lang/sh_spec.min.js +1 -0
  90. data/public/js/sh_lang/sh_sql.min.js +1 -0
  91. data/public/js/sh_lang/sh_tcl.min.js +1 -0
  92. data/public/js/sh_lang/sh_xml.min.js +1 -0
  93. data/public/js/sh_lang/sh_xorg.min.js +1 -0
  94. data/public/js/sh_main.min.js +4 -0
  95. data/public/js/showoff.js +629 -0
  96. data/public/js/showoffcore.js +13 -0
  97. data/views/index.erb +81 -0
  98. data/views/onepage.erb +35 -0
  99. metadata +217 -0
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'mg'
3
+ rescue LoadError
4
+ abort "Please `gem install mg`"
5
+ end
6
+
7
+ MG.new("showoff.gemspec")
data/bin/showoff ADDED
@@ -0,0 +1,136 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'showoff'
5
+ require 'rubygems'
6
+ require 'gli'
7
+
8
+ include GLI
9
+
10
+ version ShowOff::Version
11
+
12
+ desc 'Create new showoff presentation'
13
+ arg_name 'dir_name'
14
+ long_desc '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.'
15
+ command [:create,:init] do |c|
16
+
17
+ c.desc 'Don''t create sample slides'
18
+ c.switch [:n,:nosamples]
19
+
20
+ c.desc 'sample slide directory name'
21
+ c.default_value 'one'
22
+ c.flag [:d,:slidedir]
23
+
24
+ c.action do |global_options,options,args|
25
+ raise "dir_name is required" if args.empty?
26
+ ShowOffUtils.create(args[0],!options[:n],options[:d])
27
+ end
28
+ end
29
+
30
+ desc 'Puts your showoff presentation into a gh-pages branch'
31
+ long_desc 'Generates a static version of your presentation into your gh-pages branch for publishing to GitHub Pages'
32
+ command :github do |c|
33
+ c.action do |global_options,options,args|
34
+ ShowOffUtils.github
35
+ end
36
+ end
37
+
38
+ desc 'Serves the showoff presentation in the current directory'
39
+ desc 'Setup your presentation to serve on Heroku'
40
+ arg_name 'heroku_name'
41
+ long_desc '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.'
42
+ command :heroku do |c|
43
+
44
+ c.desc 'add password protection to your heroku site'
45
+ c.flag [:p,:password]
46
+
47
+ c.desc 'force overwrite of existing Gemfile/.gems and config.ru files if they exist'
48
+ c.switch [:f,:force]
49
+
50
+ c.desc 'Use older-style .gems file instead of bundler-style Gemfile'
51
+ c.switch [:g,:dotgems]
52
+
53
+ c.action do |global_options,options,args|
54
+ raise "heroku_name is required" if args.empty?
55
+ ShowOffUtils.heroku(args[0],options[:f],options[:p],options[:g])
56
+ end
57
+ end
58
+
59
+ desc 'Serves the showoff presentation in the current directory'
60
+ command :serve do |c|
61
+
62
+ c.desc 'Port on which to run'
63
+ c.default_value "9090"
64
+ c.flag [:p,:port]
65
+
66
+ c.desc 'Host or ip to run on'
67
+ c.default_value "localhost"
68
+ c.flag [:h,:host]
69
+
70
+ c.action do |global_options,options,args|
71
+ ShowOff.run! :host => options[:h], :port => options[:p].to_i
72
+ end
73
+ end
74
+
75
+ desc 'Add a new slide at the end in a given dir'
76
+ arg_name '[title]'
77
+ long_desc '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'
78
+ command [:add,:new] do |c|
79
+ c.desc 'Don''t number the slide, use the given name verbatim'
80
+ c.switch [:u,:nonumber]
81
+
82
+ c.desc 'Include code from the given file as the slide body'
83
+ c.arg_name 'path to file'
84
+ c.flag [:s,:source]
85
+
86
+ c.desc 'Slide Type/Style'
87
+ c.arg_name 'valid showoff style/type'
88
+ c.default_value 'title'
89
+ c.flag [:t,:type,:style]
90
+
91
+ c.desc 'Slide dir (where to put a new slide file)'
92
+ c.arg_name 'dir'
93
+ c.flag [:d,:dir]
94
+
95
+ c.desc 'Slide name (name of the new slide file)'
96
+ c.arg_name 'basename'
97
+ c.flag [:n,:name]
98
+
99
+ c.action do |global_options,options,args|
100
+ title = args.join(" ")
101
+ ShowOffUtils.add_slide(:dir => options[:d],
102
+ :name => options[:n],
103
+ :title => title,
104
+ :number => !options[:u],
105
+ :code => options[:s],
106
+ :type => options[:t])
107
+ end
108
+ end
109
+
110
+ desc 'Generate static version of presentation'
111
+ arg_name 'name'
112
+ long_desc 'Creates a static, one page version of the presentation as {name}.html'
113
+ command [:static] do |c|
114
+ c.action do |global_options,options,args|
115
+ ShowOff.do_static(args[0])
116
+ end
117
+ end
118
+
119
+ pre do |global,command,options,args|
120
+ # Pre logic here
121
+ # Return true to proceed; false to abourt and not call the
122
+ # chosen command
123
+ true
124
+ end
125
+
126
+ post do |global,command,options,args|
127
+ # Post logic here
128
+ end
129
+
130
+ on_error do |exception|
131
+ # Error logic here
132
+ # return false to skip default error handling
133
+ true
134
+ end
135
+
136
+ exit GLI.run(ARGV)
data/lib/princely.rb ADDED
@@ -0,0 +1,84 @@
1
+ # PrinceXML Ruby interface.
2
+ # http://www.princexml.com
3
+ #
4
+ # Library by Subimage Interactive - http://www.subimage.com
5
+ #
6
+ #
7
+ # USAGE
8
+ # -----------------------------------------------------------------------------
9
+ # princely = Princely.new()
10
+ # html_string = render_to_string(:template => 'some_document')
11
+ # send_data(
12
+ # princely.pdf_from_string(html_string),
13
+ # :filename => 'some_document.pdf'
14
+ # :type => 'application/pdf'
15
+ # )
16
+ #
17
+ $:.unshift(File.dirname(__FILE__))
18
+
19
+ class Princely
20
+ VERSION = "1.0.0" unless const_defined?("VERSION")
21
+
22
+ attr_accessor :exe_path, :style_sheets, :log_file
23
+
24
+ # Initialize method
25
+ #
26
+ def initialize()
27
+ # Finds where the application lives, so we can call it.
28
+ @exe_path = `which prince`.chomp
29
+ raise "Cannot find prince command-line app in $PATH" if @exe_path.length == 0
30
+ @style_sheets = ''
31
+ @log_file = "/tmp/prince.log"
32
+ end
33
+
34
+ # Sets stylesheets...
35
+ # Can pass in multiple paths for css files.
36
+ #
37
+ def add_style_sheets(*sheets)
38
+ for sheet in sheets do
39
+ @style_sheets << " -s #{sheet} "
40
+ end
41
+ end
42
+
43
+ # Returns fully formed executable path with any command line switches
44
+ # we've set based on our variables.
45
+ #
46
+ def exe_path
47
+ # Add any standard cmd line arguments we need to pass
48
+ @exe_path << " --input=html --server --log=#{@log_file} "
49
+ @exe_path << @style_sheets
50
+ return @exe_path
51
+ end
52
+
53
+ # Makes a pdf from a passed in string.
54
+ #
55
+ # Returns PDF as a stream, so we can use send_data to shoot
56
+ # it down the pipe using Rails.
57
+ #
58
+ def pdf_from_string(string, output_file = '-')
59
+ path = self.exe_path()
60
+ # Don't spew errors to the standard out...and set up to take IO
61
+ # as input and output
62
+ path << ' --silent - -o -'
63
+
64
+ # Actually call the prince command, and pass the entire data stream back.
65
+ pdf = IO.popen(path, "w+")
66
+ pdf.puts(string)
67
+ pdf.close_write
68
+ result = pdf.gets(nil)
69
+ pdf.close_read
70
+ return result
71
+ end
72
+
73
+ def pdf_from_string_to_file(string, output_file)
74
+ path = self.exe_path()
75
+ # Don't spew errors to the standard out...and set up to take IO
76
+ # as input and output
77
+ path << " --silent - -o '#{output_file}' >> '#{@log_file}' 2>> '#{@log_file}'"
78
+
79
+ # Actually call the prince command, and pass the entire data stream back.
80
+ pdf = IO.popen(path, "w+")
81
+ pdf.puts(string)
82
+ pdf.close
83
+ end
84
+ end
data/lib/showoff.rb ADDED
@@ -0,0 +1,418 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+ require 'json'
4
+ require 'nokogiri'
5
+ require 'fileutils'
6
+
7
+ here = File.expand_path(File.dirname(__FILE__))
8
+ require "#{here}/showoff_utils"
9
+ require "#{here}/princely"
10
+
11
+ begin
12
+ require 'RMagick'
13
+ rescue LoadError
14
+ $stderr.puts 'image sizing disabled - install rmagick'
15
+ end
16
+
17
+ begin
18
+ require 'pdfkit'
19
+ rescue LoadError
20
+ $stderr.puts 'pdf generation disabled - install pdfkit'
21
+ end
22
+
23
+ begin
24
+ require 'rdiscount'
25
+ rescue LoadError
26
+ require 'bluecloth'
27
+ Object.send(:remove_const,:Markdown)
28
+ Markdown = BlueCloth
29
+ end
30
+ require 'pp'
31
+
32
+ class ShowOff < Sinatra::Application
33
+
34
+ Version = VERSION = '0.4.2'
35
+
36
+ attr_reader :cached_image_size
37
+
38
+ set :views, File.dirname(__FILE__) + '/../views'
39
+ set :public, File.dirname(__FILE__) + '/../public'
40
+ set :pres_dir, 'example'
41
+
42
+ def initialize(app=nil)
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'
47
+ @root_path = "."
48
+ else
49
+ options.pres_dir = Dir.pwd
50
+ @root_path = ".."
51
+ end
52
+ @cached_image_size = {}
53
+ puts options.pres_dir
54
+ @pres_name = options.pres_dir.split('/').pop
55
+ require_ruby_files
56
+ end
57
+
58
+ def require_ruby_files
59
+ Dir.glob("#{options.pres_dir}/*.rb").map { |path| require path }
60
+ end
61
+
62
+ helpers do
63
+ def load_section_files(section)
64
+ section = File.join(options.pres_dir, section)
65
+ files = Dir.glob("#{section}/**/*").sort
66
+ pp files
67
+ files
68
+ end
69
+
70
+ def css_files
71
+ Dir.glob("#{options.pres_dir}/*.css").map { |path| File.basename(path) }
72
+ end
73
+
74
+ def js_files
75
+ Dir.glob("#{options.pres_dir}/*.js").map { |path| File.basename(path) }
76
+ end
77
+
78
+
79
+ def preshow_files
80
+ Dir.glob("#{options.pres_dir}/_preshow/*").map { |path| File.basename(path) }.to_json
81
+ end
82
+
83
+ def process_markdown(name, content, static=false, pdf=false)
84
+ slides = content.split(/^<?!SLIDE/)
85
+ slides.delete('')
86
+ final = ''
87
+ if slides.size > 1
88
+ seq = 1
89
+ end
90
+ slides.each do |slide|
91
+ md = ''
92
+ # extract content classes
93
+ lines = slide.split("\n")
94
+ content_classes = lines.shift.strip.chomp('>').split rescue []
95
+ slide = lines.join("\n")
96
+ # add content class too
97
+ content_classes.unshift "content"
98
+ # extract transition, defaulting to none
99
+ transition = 'none'
100
+ content_classes.delete_if { |x| x =~ /^transition=(.+)/ && transition = $1 }
101
+ # extract id, defaulting to none
102
+ id = nil
103
+ content_classes.delete_if { |x| x =~ /^#([\w-]+)/ && id = $1 }
104
+ puts "id: #{id}" if id
105
+ puts "classes: #{content_classes.inspect}"
106
+ puts "transition: #{transition}"
107
+ # create html
108
+ md += "<div"
109
+ md += " id=\"#{id}\"" if id
110
+ md += " class=\"slide\" data-transition=\"#{transition}\">"
111
+ if seq
112
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
113
+ seq += 1
114
+ else
115
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
116
+ end
117
+ sl = Markdown.new(slide).to_html
118
+ sl = update_image_paths(name, sl, static, pdf)
119
+ md += sl
120
+ md += "</div>\n"
121
+ md += "</div>\n"
122
+ final += update_commandline_code(md)
123
+ final = update_p_classes(final)
124
+ end
125
+ final
126
+ end
127
+
128
+ # find any lines that start with a <p>.(something) and turn them into <p class="something">
129
+ def update_p_classes(markdown)
130
+ markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
131
+ end
132
+
133
+ def update_image_paths(path, slide, static=false, pdf=false)
134
+ paths = path.split('/')
135
+ paths.pop
136
+ path = paths.join('/')
137
+ replacement_prefix = static ?
138
+ ( pdf ? %(img src="file://#{options.pres_dir}/#{path}) : %(img src="./file/#{path}) ) :
139
+ %(img src="/image/#{path})
140
+ slide.gsub(/img src=\"(.*?)\"/) do |s|
141
+ img_path = File.join(path, $1)
142
+ w, h = get_image_size(img_path)
143
+ src = %(#{replacement_prefix}/#{$1}")
144
+ if w && h
145
+ src << %( width="#{w}" height="#{h}")
146
+ end
147
+ src
148
+ end
149
+ end
150
+
151
+ if defined?(Magick)
152
+ def get_image_size(path)
153
+ if !cached_image_size.key?(path)
154
+ img = Magick::Image.ping(path).first
155
+ cached_image_size[path] = [img.columns, img.rows]
156
+ end
157
+ cached_image_size[path]
158
+ end
159
+ else
160
+ def get_image_size(path)
161
+ end
162
+ end
163
+
164
+ def update_commandline_code(slide)
165
+ html = Nokogiri::XML.parse(slide)
166
+
167
+ html.css('pre').each do |pre|
168
+ pre.css('code').each do |code|
169
+ out = code.text
170
+ lines = out.split("\n")
171
+ if lines.first[0, 3] == '@@@'
172
+ lang = lines.shift.gsub('@@@', '').strip
173
+ pre.set_attribute('class', 'sh_' + lang.downcase)
174
+ code.content = lines.join("\n")
175
+ end
176
+ end
177
+ end
178
+
179
+ html.css('.commandline > pre > code').each do |code|
180
+ out = code.text
181
+ lines = out.split(/^\$(.*?)$/)
182
+ lines.delete('')
183
+ code.content = ''
184
+ while(lines.size > 0) do
185
+ command = lines.shift
186
+ result = lines.shift
187
+ c = Nokogiri::XML::Node.new('code', html)
188
+ c.set_attribute('class', 'command')
189
+ c.content = '$' + command
190
+ code << c
191
+ c = Nokogiri::XML::Node.new('code', html)
192
+ c.set_attribute('class', 'result')
193
+ c.content = result
194
+ code << c
195
+ end
196
+ end
197
+ html.root.to_s
198
+ end
199
+
200
+ def get_slides_html(static=false, pdf=false)
201
+ sections = ShowOffUtils.showoff_sections(options.pres_dir)
202
+ files = []
203
+ if sections
204
+ sections.each do |section|
205
+ files << load_section_files(section)
206
+ end
207
+ files = files.flatten
208
+ files = files.select { |f| f =~ /.md$/ }
209
+ data = ''
210
+ files.each do |f|
211
+ fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
212
+ data += process_markdown(fname, File.read(f), static, pdf)
213
+ end
214
+ end
215
+ data
216
+ end
217
+
218
+ def inline_css(csses, pre = nil)
219
+ css_content = '<style type="text/css">'
220
+ csses.each do |css_file|
221
+ if pre
222
+ css_file = File.join(File.dirname(__FILE__), '..', pre, css_file)
223
+ else
224
+ css_file = File.join(options.pres_dir, css_file)
225
+ end
226
+ css_content += File.read(css_file)
227
+ end
228
+ css_content += '</style>'
229
+ css_content
230
+ end
231
+
232
+ def inline_js(jses, pre = nil)
233
+ js_content = '<script type="text/javascript">'
234
+ jses.each do |js_file|
235
+ if pre
236
+ js_file = File.join(File.dirname(__FILE__), '..', pre, js_file)
237
+ else
238
+ js_file = File.join(options.pres_dir, js_file)
239
+ end
240
+ js_content += File.read(js_file)
241
+ end
242
+ js_content += '</script>'
243
+ js_content
244
+ end
245
+
246
+ def inline_all_js(jses_directory)
247
+ inline_js(Dir.entries(File.join(File.dirname(__FILE__), '..', jses_directory)).find_all{|filename| filename.length > 2 }, jses_directory)
248
+ end
249
+
250
+ def index(static=false)
251
+ if static
252
+ @title = ShowOffUtils.showoff_title
253
+ @slides = get_slides_html(static)
254
+ @asset_path = "./"
255
+ end
256
+ erb :index
257
+ end
258
+
259
+ def clean_link(href)
260
+ if href && href[0, 1] == '/'
261
+ href = href[1, href.size]
262
+ end
263
+ href
264
+ end
265
+
266
+ def assets_needed
267
+ assets = ["index", "slides"]
268
+
269
+ index = erb :index
270
+ html = Nokogiri::XML.parse(index)
271
+ html.css('head link').each do |link|
272
+ href = clean_link(link['href'])
273
+ assets << href if href
274
+ end
275
+ html.css('head script').each do |link|
276
+ href = clean_link(link['src'])
277
+ assets << href if href
278
+ end
279
+
280
+ slides = get_slides_html
281
+ html = Nokogiri::XML.parse("<slides>" + slides + "</slides>")
282
+ html.css('img').each do |link|
283
+ href = clean_link(link['src'])
284
+ assets << href if href
285
+ end
286
+
287
+ css = Dir.glob("#{options.public}/**/*.css").map { |path| path.gsub(options.public + '/', '') }
288
+ assets << css
289
+
290
+ js = Dir.glob("#{options.public}/**/*.js").map { |path| path.gsub(options.public + '/', '') }
291
+ assets << js
292
+
293
+ assets.uniq.join("\n")
294
+ end
295
+
296
+ def slides(static=false)
297
+ get_slides_html(static)
298
+ end
299
+
300
+ def onepage(static=false)
301
+ @slides = get_slides_html(static)
302
+ erb :onepage
303
+ end
304
+
305
+ def pdf(static=true)
306
+ @slides = get_slides_html(static, true)
307
+ @no_js = false
308
+ html = erb :onepage
309
+ # TODO make a random filename
310
+
311
+ # PDFKit.new takes the HTML and any options for wkhtmltopdf
312
+ # run `wkhtmltopdf --extended-help` for a full list of options
313
+ kit = PDFKit.new(html, :page_size => 'Letter', :orientation => 'Landscape')
314
+
315
+ # Save the PDF to a file
316
+ file = kit.to_file('/tmp/preso.pdf')
317
+ end
318
+
319
+ end
320
+
321
+
322
+ def self.do_static(what)
323
+ what = "index" if !what
324
+
325
+ # Nasty hack to get the actual ShowOff module
326
+ showoff = ShowOff.new
327
+ while !showoff.is_a?(ShowOff)
328
+ showoff = showoff.instance_variable_get(:@app)
329
+ end
330
+ name = showoff.instance_variable_get(:@pres_name)
331
+ path = showoff.instance_variable_get(:@root_path)
332
+ data = showoff.send(what, true)
333
+ if data.is_a?(File)
334
+ FileUtils.cp(data.path, "#{name}.pdf")
335
+ else
336
+ out = "#{path}/#{name}/static"
337
+ # First make a directory
338
+ FileUtils.makedirs(out)
339
+ # Then write the html
340
+ file = File.new("#{out}/index.html", "w")
341
+ file.puts(data)
342
+ file.close
343
+ # Now copy all the js and css
344
+ my_path = File.join( File.dirname(__FILE__), '..', 'public')
345
+ ["js", "css"].each { |dir|
346
+ FileUtils.copy_entry("#{my_path}/#{dir}", "#{out}/#{dir}")
347
+ }
348
+ # And copy the directory
349
+ Dir.glob("#{my_path}/#{name}/*").each { |subpath|
350
+ base = File.basename(subpath)
351
+ next if "static" == base
352
+ next unless File.directory?(subpath) || base.match(/\.(css|js)$/)
353
+ FileUtils.copy_entry(subpath, "#{out}/#{base}")
354
+ }
355
+
356
+ # Set up file dir
357
+ file_dir = File.join(out, 'file')
358
+ FileUtils.makedirs(file_dir)
359
+ pres_dir = showoff.options.pres_dir
360
+
361
+ # ..., copy all user-defined styles and javascript files
362
+ Dir.glob("#{pres_dir}/*.{css,js}").each { |path|
363
+ FileUtils.copy(path, File.join(file_dir, File.basename(path)))
364
+ }
365
+
366
+ # ... and copy all needed image files
367
+ data.scan(/img src=\".\/file\/(.*?)\"/).flatten.each do |path|
368
+ dir = File.dirname(path)
369
+ FileUtils.makedirs(File.join(file_dir, dir))
370
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
371
+ end
372
+ # copy images from css too
373
+ Dir.glob("#{pres_dir}/*.css").each do |css_path|
374
+ File.open(css_path) do |file|
375
+ data = file.read
376
+ data.scan(/url\((.*)\)/).flatten.each do |path|
377
+ p path
378
+ dir = File.dirname(path)
379
+ FileUtils.makedirs(File.join(file_dir, dir))
380
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ def eval_ruby code
388
+ eval(code).to_s
389
+ rescue => e
390
+ e.message
391
+ end
392
+
393
+ get '/eval_ruby' do
394
+ return eval_ruby(params[:code]) if ENV['SHOWOFF_EVAL_RUBY']
395
+
396
+ return "Ruby Evaluation is off. To turn it on set ENV['SHOWOFF_EVAL_RUBY']"
397
+ end
398
+
399
+ get %r{(?:image|file)/(.*)} do
400
+ path = params[:captures].first
401
+ full_path = File.join(options.pres_dir, path)
402
+ send_file full_path
403
+ end
404
+
405
+ get %r{/(.*)} do
406
+ @title = ShowOffUtils.showoff_title
407
+ what = params[:captures].first
408
+ what = 'index' if "" == what
409
+ if (what != "favicon.ico")
410
+ data = send(what)
411
+ if data.is_a?(File)
412
+ send_file data.path
413
+ else
414
+ data
415
+ end
416
+ end
417
+ end
418
+ end