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