showoff-alexch 0.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +558 -0
  3. data/Rakefile +27 -0
  4. data/bin/showoff +197 -0
  5. data/lib/commandline_parser.rb +67 -0
  6. data/lib/showoff.rb +510 -0
  7. data/lib/showoff_utils.rb +344 -0
  8. data/public/css/960.css +653 -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/presenter.css +76 -0
  13. data/public/css/reset.css +53 -0
  14. data/public/css/sh_style.css +66 -0
  15. data/public/css/showoff.css +399 -0
  16. data/public/css/spinner_bar.gif +0 -0
  17. data/public/css/theme/images/ui-bg_diagonals-small_100_f0efea_40x40.png +0 -0
  18. data/public/css/theme/images/ui-bg_flat_35_f0f0f0_40x100.png +0 -0
  19. data/public/css/theme/images/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
  20. data/public/css/theme/images/ui-bg_glow-ball_25_2e2e28_600x600.png +0 -0
  21. data/public/css/theme/images/ui-bg_highlight-soft_100_f0efea_1x100.png +0 -0
  22. data/public/css/theme/images/ui-bg_highlight-soft_25_327E04_1x100.png +0 -0
  23. data/public/css/theme/images/ui-bg_highlight-soft_25_5A9D1A_1x100.png +0 -0
  24. data/public/css/theme/images/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
  25. data/public/css/theme/images/ui-bg_inset-soft_22_3b3b35_1x100.png +0 -0
  26. data/public/css/theme/images/ui-icons_808080_256x240.png +0 -0
  27. data/public/css/theme/images/ui-icons_8DC262_256x240.png +0 -0
  28. data/public/css/theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  29. data/public/css/theme/images/ui-icons_e7e6e4_256x240.png +0 -0
  30. data/public/css/theme/images/ui-icons_eeeeee_256x240.png +0 -0
  31. data/public/css/theme/images/ui-icons_ffffff_256x240.png +0 -0
  32. data/public/css/theme/ui.accordion.css +9 -0
  33. data/public/css/theme/ui.all.css +2 -0
  34. data/public/css/theme/ui.base.css +9 -0
  35. data/public/css/theme/ui.core.css +37 -0
  36. data/public/css/theme/ui.datepicker.css +62 -0
  37. data/public/css/theme/ui.dialog.css +13 -0
  38. data/public/css/theme/ui.progressbar.css +4 -0
  39. data/public/css/theme/ui.resizable.css +13 -0
  40. data/public/css/theme/ui.slider.css +17 -0
  41. data/public/css/theme/ui.tabs.css +9 -0
  42. data/public/css/theme/ui.theme.css +245 -0
  43. data/public/favicon.ico +0 -0
  44. data/public/js/coffee-script.js +8 -0
  45. data/public/js/core.js +79 -0
  46. data/public/js/fg.menu.js +645 -0
  47. data/public/js/jTypeWriter.js +26 -0
  48. data/public/js/jquery-1.4.2.min.js +154 -0
  49. data/public/js/jquery-print.js +109 -0
  50. data/public/js/jquery.batchImageLoad.js +56 -0
  51. data/public/js/jquery.cookie.js +96 -0
  52. data/public/js/jquery.cycle.all.js +1284 -0
  53. data/public/js/jquery.doubletap-0.1.js +105 -0
  54. data/public/js/jquery.uuid.js +24 -0
  55. data/public/js/jquery.ws-0.3pre.js +201 -0
  56. data/public/js/onepage.js +5 -0
  57. data/public/js/presenter.js +193 -0
  58. data/public/js/sh_lang/sh_bison.min.js +1 -0
  59. data/public/js/sh_lang/sh_c.min.js +1 -0
  60. data/public/js/sh_lang/sh_caml.min.js +1 -0
  61. data/public/js/sh_lang/sh_changelog.min.js +1 -0
  62. data/public/js/sh_lang/sh_coffeescript.min.js +1 -0
  63. data/public/js/sh_lang/sh_cpp.min.js +1 -0
  64. data/public/js/sh_lang/sh_csharp.min.js +1 -0
  65. data/public/js/sh_lang/sh_css.min.js +1 -0
  66. data/public/js/sh_lang/sh_cucumber.min.js +2 -0
  67. data/public/js/sh_lang/sh_desktop.min.js +1 -0
  68. data/public/js/sh_lang/sh_diff.min.js +1 -0
  69. data/public/js/sh_lang/sh_erlang.min.js +1 -0
  70. data/public/js/sh_lang/sh_flex.min.js +1 -0
  71. data/public/js/sh_lang/sh_glsl.min.js +1 -0
  72. data/public/js/sh_lang/sh_haxe.min.js +1 -0
  73. data/public/js/sh_lang/sh_html.min.js +1 -0
  74. data/public/js/sh_lang/sh_java.min.js +1 -0
  75. data/public/js/sh_lang/sh_javascript.min.js +1 -0
  76. data/public/js/sh_lang/sh_javascript_dom.min.js +1 -0
  77. data/public/js/sh_lang/sh_latex.min.js +1 -0
  78. data/public/js/sh_lang/sh_ldap.min.js +1 -0
  79. data/public/js/sh_lang/sh_log.min.js +1 -0
  80. data/public/js/sh_lang/sh_lsm.min.js +1 -0
  81. data/public/js/sh_lang/sh_m4.min.js +1 -0
  82. data/public/js/sh_lang/sh_makefile.min.js +1 -0
  83. data/public/js/sh_lang/sh_oracle.min.js +1 -0
  84. data/public/js/sh_lang/sh_pascal.min.js +1 -0
  85. data/public/js/sh_lang/sh_perl.min.js +1 -0
  86. data/public/js/sh_lang/sh_php.min.js +1 -0
  87. data/public/js/sh_lang/sh_prolog.min.js +1 -0
  88. data/public/js/sh_lang/sh_properties.min.js +1 -0
  89. data/public/js/sh_lang/sh_python.min.js +1 -0
  90. data/public/js/sh_lang/sh_ruby.min.js +1 -0
  91. data/public/js/sh_lang/sh_scala.min.js +1 -0
  92. data/public/js/sh_lang/sh_sh.min.js +1 -0
  93. data/public/js/sh_lang/sh_slang.min.js +1 -0
  94. data/public/js/sh_lang/sh_sml.min.js +1 -0
  95. data/public/js/sh_lang/sh_spec.min.js +1 -0
  96. data/public/js/sh_lang/sh_sql.min.js +1 -0
  97. data/public/js/sh_lang/sh_tcl.min.js +1 -0
  98. data/public/js/sh_lang/sh_xml.min.js +1 -0
  99. data/public/js/sh_lang/sh_xorg.min.js +1 -0
  100. data/public/js/sh_main.min.js +4 -0
  101. data/public/js/showoff.js +643 -0
  102. data/public/js/showoffcore.js +13 -0
  103. data/views/header.erb +40 -0
  104. data/views/index.erb +46 -0
  105. data/views/onepage.erb +34 -0
  106. data/views/presenter.erb +70 -0
  107. metadata +245 -0
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rake/testtask'
2
+
3
+ begin
4
+ require 'mg'
5
+ rescue LoadError
6
+ abort "Please `gem install mg`"
7
+ end
8
+
9
+ MG.new("showoff-alexch.gemspec")
10
+
11
+ #
12
+ # Tests
13
+ #
14
+
15
+ task :default => :test
16
+
17
+ desc "Run tests"
18
+ task :turn do
19
+ suffix = "-n #{ENV['TEST']}" if ENV['TEST']
20
+ sh "turn test/*_test.rb #{suffix}"
21
+ end
22
+
23
+ Rake::TestTask.new do |t|
24
+ t.libs << 'lib'
25
+ t.pattern = 'test/**/*_test.rb'
26
+ t.verbose = false
27
+ end
data/bin/showoff ADDED
@@ -0,0 +1,197 @@
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
+ if !options[:n]
28
+ puts "done. run 'showoff serve' in #{options[:d]}/ dir to see slideshow"
29
+ else
30
+ puts "done. add slides, modify #{ShowOffUtils.presentation_config_file} and then run 'showoff serve' in #{dirname}/ dir to see slideshow"
31
+ end
32
+ end
33
+ end
34
+
35
+ desc 'Puts your showoff presentation into a gh-pages branch'
36
+ long_desc 'Generates a static version of your presentation into your gh-pages branch for publishing to GitHub Pages'
37
+ command :github do |c|
38
+ c.action do |global_options,options,args|
39
+ puts "Generating static content"
40
+ ShowOffUtils.github
41
+ puts "I've updated your 'gh-pages' branch with the static version of your presentation."
42
+ puts "Push it to GitHub to publish it. Probably something like:"
43
+ puts
44
+ puts " git push origin gh-pages"
45
+ puts
46
+ end
47
+ end
48
+
49
+ desc 'Serves the showoff presentation in the current directory'
50
+ desc 'Setup your presentation to serve on Heroku'
51
+ arg_name 'heroku_name'
52
+ 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.'
53
+ command :heroku do |c|
54
+
55
+ c.desc 'add password protection to your heroku site'
56
+ c.flag [:p,:password]
57
+
58
+ c.desc 'force overwrite of existing Gemfile/.gems and config.ru files if they exist'
59
+ c.switch [:f,:force]
60
+
61
+ c.desc 'Use older-style .gems file instead of bundler-style Gemfile'
62
+ c.switch [:g,:dotgems]
63
+
64
+ c.action do |global_options,options,args|
65
+ raise "heroku_name is required" if args.empty?
66
+ if ShowOffUtils.heroku(args[0],options[:f],options[:p],options[:g])
67
+ puts "herokuized. run something like this to launch your heroku presentation:
68
+
69
+ heroku create #{args[0]}"
70
+
71
+ if options[:g]
72
+ puts " git add .gems config.ru"
73
+ else
74
+ puts " bundle install"
75
+ puts " git add Gemfile Gemfile.lock config.ru"
76
+ end
77
+ puts " git commit -m 'herokuized'
78
+ git push heroku master
79
+ "
80
+
81
+ if options[:p]
82
+ puts "CAREFUL: you are commiting your access password - anyone with read access to the repo can access the preso\n\n"
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ desc 'Serves the showoff presentation in the specified (or current) directory'
89
+ arg_name "[pres_dir]"
90
+ default_value "."
91
+ command :serve do |c|
92
+
93
+ c.desc 'Show verbose messaging'
94
+ c.switch :verbose
95
+
96
+ c.desc 'Port on which to run'
97
+ c.default_value "9090"
98
+ c.flag [:p,:port]
99
+
100
+ c.desc 'Host or ip to run on'
101
+ c.default_value "localhost"
102
+ c.flag [:h,:host]
103
+
104
+ c.desc 'JSON file used to describe presentation'
105
+ c.default_value "showoff.json"
106
+ c.flag [:f, :pres_file]
107
+
108
+ c.desc 'every H1 (lone #) in the markdown file will become a new slide'
109
+ c.default_value false
110
+ c.switch [:s, :split]
111
+
112
+ c.action do |global_options,options,args|
113
+
114
+ url = "http://#{options[:h]}:#{options[:p].to_i}"
115
+ puts "
116
+ -------------------------
117
+
118
+ Your ShowOff presentation is now starting up.
119
+
120
+ To view it plainly, visit [ #{url} ]
121
+
122
+ To run it from presenter view, go to: [ #{url}/presenter ]
123
+
124
+ -------------------------
125
+
126
+ "
127
+ ShowOff.run! :host => options[:h],
128
+ :port => options[:p].to_i,
129
+ :pres_file => options[:f],
130
+ :pres_dir => args[0],
131
+ :verbose => options[:verbose],
132
+ :split_all => options[:split_all]
133
+ end
134
+ end
135
+
136
+ desc 'Add a new slide at the end in a given dir'
137
+ arg_name '[title]'
138
+ 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'
139
+ command [:add,:new] do |c|
140
+ c.desc 'Don''t number the slide, use the given name verbatim'
141
+ c.switch [:u,:nonumber]
142
+
143
+ c.desc 'Include code from the given file as the slide body'
144
+ c.arg_name 'path to file'
145
+ c.flag [:s,:source]
146
+
147
+ c.desc 'Slide Type/Style'
148
+ c.arg_name 'valid showoff style/type'
149
+ c.default_value 'title'
150
+ c.flag [:t,:type,:style]
151
+
152
+ c.desc 'Slide dir (where to put a new slide file)'
153
+ c.arg_name 'dir'
154
+ c.flag [:d,:dir]
155
+
156
+ c.desc 'Slide name (name of the new slide file)'
157
+ c.arg_name 'basename'
158
+ c.flag [:n,:name]
159
+
160
+ c.action do |global_options,options,args|
161
+ title = args.join(" ")
162
+ ShowOffUtils.add_slide(:dir => options[:d],
163
+ :name => options[:n],
164
+ :title => title,
165
+ :number => !options[:u],
166
+ :code => options[:s],
167
+ :type => options[:t])
168
+ end
169
+ end
170
+
171
+ desc 'Generate static version of presentation'
172
+ arg_name 'name'
173
+ long_desc 'Creates a static, one page version of the presentation as {name}.html'
174
+ command [:static] do |c|
175
+ c.action do |global_options,options,args|
176
+ ShowOff.do_static(args[0])
177
+ end
178
+ end
179
+
180
+ pre do |global,command,options,args|
181
+ # Pre logic here
182
+ # Return true to proceed; false to abourt and not call the
183
+ # chosen command
184
+ true
185
+ end
186
+
187
+ post do |global,command,options,args|
188
+ # Post logic here
189
+ end
190
+
191
+ on_error do |exception|
192
+ # Error logic here
193
+ # return false to skip default error handling
194
+ true
195
+ end
196
+
197
+ exit GLI.run(ARGV)
@@ -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 ADDED
@@ -0,0 +1,510 @@
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}/commandline_parser"
11
+
12
+ begin
13
+ require 'RMagick'
14
+ rescue LoadError
15
+ $stderr.puts 'image sizing disabled - install rmagick'
16
+ end
17
+
18
+ begin
19
+ require 'pdfkit'
20
+ rescue LoadError
21
+ $stderr.puts 'pdf generation disabled - install pdfkit'
22
+ end
23
+
24
+ begin
25
+ require 'rdiscount'
26
+ rescue LoadError
27
+ require 'bluecloth'
28
+ Object.send(:remove_const,:Markdown)
29
+ Markdown = BlueCloth
30
+ end
31
+
32
+ class ShowOff < Sinatra::Application
33
+
34
+ Version = VERSION = '0.7.0.1'
35
+
36
+ attr_reader :cached_image_size
37
+
38
+ set :views, File.dirname(__FILE__) + '/../views'
39
+ set :public, File.dirname(__FILE__) + '/../public'
40
+
41
+ set :verbose, false
42
+ set :pres_dir, '.'
43
+ set :pres_file, 'showoff.json'
44
+ set :split_all, false
45
+
46
+ def initialize(app=nil)
47
+ super(app)
48
+ @logger = Logger.new(STDOUT)
49
+ @logger.formatter = proc { |severity,datetime,progname,msg| "#{progname} #{msg}\n" }
50
+ @logger.level = options.verbose ? Logger::DEBUG : Logger::WARN
51
+
52
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
53
+ @logger.debug(dir)
54
+
55
+ showoff_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
56
+ options.pres_dir ||= Dir.pwd
57
+ @root_path = "."
58
+
59
+ options.pres_dir = File.expand_path(options.pres_dir)
60
+ if (options.pres_file)
61
+ ShowOffUtils.presentation_config_file = options.pres_file
62
+ end
63
+ @cached_image_size = {}
64
+ @logger.debug options.pres_dir
65
+ @pres_name = options.pres_dir.split('/').pop
66
+ require_ruby_files
67
+ end
68
+
69
+ def self.pres_dir_current
70
+ opt = {:pres_dir => Dir.pwd}
71
+ ShowOff.set opt
72
+ end
73
+
74
+ def require_ruby_files
75
+ Dir.glob("#{options.pres_dir}/*.rb").map { |path| require path }
76
+ end
77
+
78
+ helpers do
79
+ def load_section_files(section)
80
+ section = File.join(options.pres_dir, section)
81
+ files = if File.directory? section
82
+ Dir.glob("#{section}/**/*").sort
83
+ else
84
+ [section]
85
+ end
86
+ @logger.debug files
87
+ files
88
+ end
89
+
90
+ def css_files
91
+ Dir.glob("#{options.pres_dir}/*.css").map { |path| File.basename(path) }
92
+ end
93
+
94
+ def js_files
95
+ Dir.glob("#{options.pres_dir}/*.js").map { |path| File.basename(path) }
96
+ end
97
+
98
+ def preshow_files
99
+ Dir.glob("#{options.pres_dir}/_preshow/*").map { |path| File.basename(path) }.to_json
100
+ end
101
+
102
+ class Slide
103
+
104
+ # given a chunk of Markdown text, splits it into an array of Slide objects
105
+ def self.split content, options = {}
106
+ split_all_the_h1s = options[:split_all_the_h1s]
107
+
108
+ unless content =~ /^\<?!SLIDE/m
109
+ content = content.gsub(/^# /m, "<!SLIDE>\n# ")
110
+ end
111
+
112
+ lines = content.split("\n")
113
+ slides = []
114
+ slides << (slide = Slide.new)
115
+ until lines.empty?
116
+ line = lines.shift
117
+ if line =~ /^<?!SLIDE(.*)>?/
118
+ slides << (slide = Slide.new($1))
119
+
120
+ elsif split_all_the_h1s and line =~ /^# / and !slide.empty?
121
+ # every H1 defines a new slide, unless there's a !SLIDE before it
122
+ slides << (slide = Slide.new())
123
+ slide << line
124
+
125
+ else
126
+ slide << line
127
+ end
128
+ end
129
+
130
+ slides.delete_if {|slide| slide.empty? }
131
+
132
+ slides
133
+ end
134
+
135
+ ####
136
+
137
+ attr_reader :classes, :text
138
+ def initialize classes = ""
139
+ @classes = ["content"] + classes.strip.chomp('>').split
140
+ @text = ""
141
+ end
142
+ def <<(s)
143
+ @text << s
144
+ @text << "\n"
145
+ end
146
+ def empty?
147
+ @text.strip == ""
148
+ end
149
+ end
150
+
151
+ def process_markdown(name, content, static=false, pdf=false)
152
+ lines = content.split("\n")
153
+ @logger.debug "#{name}: #{lines.length} lines"
154
+
155
+ slides = Slide.split content, :split_all_the_h1s => options.split_all
156
+
157
+ final = ''
158
+ if slides.size > 1
159
+ seq = 1
160
+ end
161
+ slides.each do |slide|
162
+ md = ''
163
+ content_classes = slide.classes
164
+
165
+ # extract transition, defaulting to none
166
+ transition = 'none'
167
+ content_classes.delete_if { |x| x =~ /^transition=(.+)/ && transition = $1 }
168
+ # extract id, defaulting to none
169
+ id = nil
170
+ content_classes.delete_if { |x| x =~ /^#([\w-]+)/ && id = $1 }
171
+ @logger.debug "id: #{id}" if id
172
+ @logger.debug "classes: #{content_classes.inspect}"
173
+ @logger.debug "transition: #{transition}"
174
+ # create html
175
+ md += "<div"
176
+ md += " id=\"#{id}\"" if id
177
+ md += " class=\"slide\" data-transition=\"#{transition}\">"
178
+ if seq
179
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
180
+ seq += 1
181
+ else
182
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
183
+ end
184
+ sl = Markdown.new(slide.text).to_html
185
+ sl = update_image_paths(name, sl, static, pdf)
186
+ md += sl
187
+ md += "</div>\n"
188
+ md += "</div>\n"
189
+ final += update_commandline_code(md)
190
+ final = update_p_classes(final)
191
+ end
192
+ final
193
+ end
194
+
195
+ # find any lines that start with a <p>.(something) and turn them into <p class="something">
196
+ def update_p_classes(markdown)
197
+ markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
198
+ end
199
+
200
+ def update_image_paths(path, slide, static=false, pdf=false)
201
+ paths = path.split('/')
202
+ paths.pop
203
+ path = paths.join('/')
204
+ replacement_prefix = static ?
205
+ ( pdf ? %(img src="file://#{options.pres_dir}/#{path}) : %(img src="./file/#{path}) ) :
206
+ %(img src="/image/#{path})
207
+ slide.gsub(/img src=\"([^\/].*?)\"/) do |s|
208
+ img_path = File.join(path, $1)
209
+ w, h = get_image_size(img_path)
210
+ src = %(#{replacement_prefix}/#{$1}")
211
+ if w && h
212
+ src << %( width="#{w}" height="#{h}")
213
+ end
214
+ src
215
+ end
216
+ end
217
+
218
+ if defined?(Magick)
219
+ def get_image_size(path)
220
+ if !cached_image_size.key?(path)
221
+ img = Magick::Image.ping(path).first
222
+ # don't set a size for svgs so they can expand to fit their container
223
+ if img.mime_type == 'image/svg+xml'
224
+ cached_image_size[path] = [nil, nil]
225
+ else
226
+ cached_image_size[path] = [img.columns, img.rows]
227
+ end
228
+ end
229
+ cached_image_size[path]
230
+ end
231
+ else
232
+ def get_image_size(path)
233
+ end
234
+ end
235
+
236
+ def update_commandline_code(slide)
237
+ html = Nokogiri::XML.parse(slide)
238
+ parser = CommandlineParser.new
239
+
240
+ html.css('pre').each do |pre|
241
+ pre.css('code').each do |code|
242
+ out = code.text
243
+ lines = out.split("\n")
244
+ if lines.first.strip[0, 3] == '@@@'
245
+ lang = lines.shift.gsub('@@@', '').strip
246
+ pre.set_attribute('class', 'sh_' + lang.downcase)
247
+ code.content = lines.join("\n")
248
+ end
249
+ end
250
+ end
251
+
252
+ html.css('.commandline > pre > code').each do |code|
253
+ out = code.text
254
+ code.content = ''
255
+ tree = parser.parse(out)
256
+ transform = Parslet::Transform.new do
257
+ rule(:prompt => simple(:prompt), :input => simple(:input), :output => simple(:output)) do
258
+ command = Nokogiri::XML::Node.new('code', html)
259
+ command.set_attribute('class', 'command')
260
+ command.content = "#{prompt} #{input}"
261
+ code << command
262
+
263
+ # Add newline after the input so that users can
264
+ # advance faster than the typewriter effect
265
+ # and still keep inputs on separate lines.
266
+ code << "\n"
267
+
268
+ unless output.to_s.empty?
269
+
270
+ result = Nokogiri::XML::Node.new('code', html)
271
+ result.set_attribute('class', 'result')
272
+ result.content = output
273
+ code << result
274
+ end
275
+ end
276
+ end
277
+ transform.apply(tree)
278
+ end
279
+ html.root.to_s
280
+ end
281
+
282
+ def get_slides_html(static=false, pdf=false)
283
+ sections = ShowOffUtils.showoff_sections(options.pres_dir, @logger)
284
+ files = []
285
+ if sections
286
+ data = ''
287
+ sections.each do |section|
288
+ if section =~ /^#/
289
+ name = section.each_line.first.gsub(/^#*/,'').strip
290
+ data << process_markdown(name, "<!SLIDE subsection>\n" + section, static, pdf)
291
+ else
292
+ files = []
293
+ files << load_section_files(section)
294
+ files = files.flatten
295
+ files = files.select { |f| f =~ /.md$/ }
296
+ files.each do |f|
297
+ fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
298
+ data << process_markdown(fname, File.read(f), static, pdf)
299
+ end
300
+ end
301
+ end
302
+ end
303
+ data
304
+ end
305
+
306
+ def inline_css(csses, pre = nil)
307
+ css_content = '<style type="text/css">'
308
+ csses.each do |css_file|
309
+ if pre
310
+ css_file = File.join(File.dirname(__FILE__), '..', pre, css_file)
311
+ else
312
+ css_file = File.join(options.pres_dir, css_file)
313
+ end
314
+ css_content += File.read(css_file)
315
+ end
316
+ css_content += '</style>'
317
+ css_content
318
+ end
319
+
320
+ def inline_js(jses, pre = nil)
321
+ js_content = '<script type="text/javascript">'
322
+ jses.each do |js_file|
323
+ if pre
324
+ js_file = File.join(File.dirname(__FILE__), '..', pre, js_file)
325
+ else
326
+ js_file = File.join(options.pres_dir, js_file)
327
+ end
328
+ js_content += File.read(js_file)
329
+ end
330
+ js_content += '</script>'
331
+ js_content
332
+ end
333
+
334
+ def inline_all_js(jses_directory)
335
+ inline_js(Dir.entries(File.join(File.dirname(__FILE__), '..', jses_directory)).find_all{|filename| filename.length > 2 }, jses_directory)
336
+ end
337
+
338
+ def index(static=false)
339
+ if static
340
+ @title = ShowOffUtils.showoff_title
341
+ @slides = get_slides_html(static)
342
+ @asset_path = "./"
343
+ end
344
+ erb :index
345
+ end
346
+
347
+ def presenter
348
+ erb :presenter
349
+ end
350
+
351
+ def clean_link(href)
352
+ if href && href[0, 1] == '/'
353
+ href = href[1, href.size]
354
+ end
355
+ href
356
+ end
357
+
358
+ def assets_needed
359
+ assets = ["index", "slides"]
360
+
361
+ index = erb :index
362
+ html = Nokogiri::XML.parse(index)
363
+ html.css('head link').each do |link|
364
+ href = clean_link(link['href'])
365
+ assets << href if href
366
+ end
367
+ html.css('head script').each do |link|
368
+ href = clean_link(link['src'])
369
+ assets << href if href
370
+ end
371
+
372
+ slides = get_slides_html
373
+ html = Nokogiri::XML.parse("<slides>" + slides + "</slides>")
374
+ html.css('img').each do |link|
375
+ href = clean_link(link['src'])
376
+ assets << href if href
377
+ end
378
+
379
+ css = Dir.glob("#{options.public}/**/*.css").map { |path| path.gsub(options.public + '/', '') }
380
+ assets << css
381
+
382
+ js = Dir.glob("#{options.public}/**/*.js").map { |path| path.gsub(options.public + '/', '') }
383
+ assets << js
384
+
385
+ assets.uniq.join("\n")
386
+ end
387
+
388
+ def slides(static=false)
389
+ get_slides_html(static)
390
+ end
391
+
392
+ def onepage(static=false)
393
+ @slides = get_slides_html(static)
394
+ erb :onepage
395
+ end
396
+
397
+ def pdf(static=true)
398
+ @slides = get_slides_html(static, true)
399
+ @no_js = false
400
+ html = erb :onepage
401
+ # TODO make a random filename
402
+
403
+ # PDFKit.new takes the HTML and any options for wkhtmltopdf
404
+ # run `wkhtmltopdf --extended-help` for a full list of options
405
+ kit = PDFKit.new(html, :page_size => 'Letter', :orientation => 'Landscape')
406
+
407
+ # Save the PDF to a file
408
+ file = kit.to_file('/tmp/preso.pdf')
409
+ end
410
+
411
+ end
412
+
413
+
414
+ def self.do_static(what)
415
+ what = "index" if !what
416
+
417
+ # Nasty hack to get the actual ShowOff module
418
+ showoff = ShowOff.new
419
+ while !showoff.is_a?(ShowOff)
420
+ showoff = showoff.instance_variable_get(:@app)
421
+ end
422
+ name = showoff.instance_variable_get(:@pres_name)
423
+ path = showoff.instance_variable_get(:@root_path)
424
+ data = showoff.send(what, true)
425
+ if data.is_a?(File)
426
+ FileUtils.cp(data.path, "#{name}.pdf")
427
+ else
428
+ out = File.expand_path("#{path}/static")
429
+ # First make a directory
430
+ FileUtils.makedirs(out)
431
+ # Then write the html
432
+ file = File.new("#{out}/index.html", "w")
433
+ file.puts(data)
434
+ file.close
435
+ # Now copy all the js and css
436
+ my_path = File.join( File.dirname(__FILE__), '..', 'public')
437
+ ["js", "css"].each { |dir|
438
+ FileUtils.copy_entry("#{my_path}/#{dir}", "#{out}/#{dir}")
439
+ }
440
+ # And copy the directory
441
+ Dir.glob("#{my_path}/#{name}/*").each { |subpath|
442
+ base = File.basename(subpath)
443
+ next if "static" == base
444
+ next unless File.directory?(subpath) || base.match(/\.(css|js)$/)
445
+ FileUtils.copy_entry(subpath, "#{out}/#{base}")
446
+ }
447
+
448
+ # Set up file dir
449
+ file_dir = File.join(out, 'file')
450
+ FileUtils.makedirs(file_dir)
451
+ pres_dir = showoff.options.pres_dir
452
+
453
+ # ..., copy all user-defined styles and javascript files
454
+ Dir.glob("#{pres_dir}/*.{css,js}").each { |path|
455
+ FileUtils.copy(path, File.join(file_dir, File.basename(path)))
456
+ }
457
+
458
+ # ... and copy all needed image files
459
+ data.scan(/img src=\".\/file\/(.*?)\"/).flatten.each do |path|
460
+ dir = File.dirname(path)
461
+ FileUtils.makedirs(File.join(file_dir, dir))
462
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
463
+ end
464
+ # copy images from css too
465
+ Dir.glob("#{pres_dir}/*.css").each do |css_path|
466
+ File.open(css_path) do |file|
467
+ data = file.read
468
+ data.scan(/url\((.*)\)/).flatten.each do |path|
469
+ @logger.debug path
470
+ dir = File.dirname(path)
471
+ FileUtils.makedirs(File.join(file_dir, dir))
472
+ FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
473
+ end
474
+ end
475
+ end
476
+ end
477
+ end
478
+
479
+ def eval_ruby code
480
+ eval(code).to_s
481
+ rescue => e
482
+ e.message
483
+ end
484
+
485
+ get '/eval_ruby' do
486
+ return eval_ruby(params[:code]) if ENV['SHOWOFF_EVAL_RUBY']
487
+
488
+ return "Ruby Evaluation is off. To turn it on set ENV['SHOWOFF_EVAL_RUBY']"
489
+ end
490
+
491
+ get %r{(?:image|file)/(.*)} do
492
+ path = params[:captures].first
493
+ full_path = File.join(options.pres_dir, path)
494
+ send_file full_path
495
+ end
496
+
497
+ get %r{/(.*)} do
498
+ @title = ShowOffUtils.showoff_title
499
+ what = params[:captures].first
500
+ what = 'index' if "" == what
501
+ if (what != "favicon.ico")
502
+ data = send(what)
503
+ if data.is_a?(File)
504
+ send_file data.path
505
+ else
506
+ data
507
+ end
508
+ end
509
+ end
510
+ end