mattmatt-showoff 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +280 -0
  3. data/Rakefile +15 -0
  4. data/bin/showoff +115 -0
  5. data/lib/princely.rb +84 -0
  6. data/lib/showoff.rb +346 -0
  7. data/lib/showoff_utils.rb +259 -0
  8. data/public/css/fg.menu.css +114 -0
  9. data/public/css/onepage.css +44 -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 +258 -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/fg.menu.js +645 -0
  43. data/public/js/jTypeWriter.js +26 -0
  44. data/public/js/jquery-1.4.min.js +151 -0
  45. data/public/js/jquery-print.js +109 -0
  46. data/public/js/jquery.batchImageLoad.js +56 -0
  47. data/public/js/jquery.cookie.js +96 -0
  48. data/public/js/jquery.cycle.all.js +1284 -0
  49. data/public/js/jquery.doubletap-0.1.js +105 -0
  50. data/public/js/jquery.uuid.js +24 -0
  51. data/public/js/jquery.ws-0.3pre.js +201 -0
  52. data/public/js/onepage.js +5 -0
  53. data/public/js/sh_lang/sh_bison.min.js +1 -0
  54. data/public/js/sh_lang/sh_c.min.js +1 -0
  55. data/public/js/sh_lang/sh_caml.min.js +1 -0
  56. data/public/js/sh_lang/sh_changelog.min.js +1 -0
  57. data/public/js/sh_lang/sh_cpp.min.js +1 -0
  58. data/public/js/sh_lang/sh_csharp.min.js +1 -0
  59. data/public/js/sh_lang/sh_css.min.js +1 -0
  60. data/public/js/sh_lang/sh_desktop.min.js +1 -0
  61. data/public/js/sh_lang/sh_diff.min.js +1 -0
  62. data/public/js/sh_lang/sh_flex.min.js +1 -0
  63. data/public/js/sh_lang/sh_glsl.min.js +1 -0
  64. data/public/js/sh_lang/sh_haxe.min.js +1 -0
  65. data/public/js/sh_lang/sh_html.min.js +1 -0
  66. data/public/js/sh_lang/sh_java.min.js +1 -0
  67. data/public/js/sh_lang/sh_javascript.min.js +1 -0
  68. data/public/js/sh_lang/sh_javascript_dom.min.js +1 -0
  69. data/public/js/sh_lang/sh_latex.min.js +1 -0
  70. data/public/js/sh_lang/sh_ldap.min.js +1 -0
  71. data/public/js/sh_lang/sh_log.min.js +1 -0
  72. data/public/js/sh_lang/sh_lsm.min.js +1 -0
  73. data/public/js/sh_lang/sh_m4.min.js +1 -0
  74. data/public/js/sh_lang/sh_makefile.min.js +1 -0
  75. data/public/js/sh_lang/sh_oracle.min.js +1 -0
  76. data/public/js/sh_lang/sh_pascal.min.js +1 -0
  77. data/public/js/sh_lang/sh_perl.min.js +1 -0
  78. data/public/js/sh_lang/sh_php.min.js +1 -0
  79. data/public/js/sh_lang/sh_prolog.min.js +1 -0
  80. data/public/js/sh_lang/sh_properties.min.js +1 -0
  81. data/public/js/sh_lang/sh_python.min.js +1 -0
  82. data/public/js/sh_lang/sh_ruby.min.js +1 -0
  83. data/public/js/sh_lang/sh_scala.min.js +1 -0
  84. data/public/js/sh_lang/sh_sh.min.js +1 -0
  85. data/public/js/sh_lang/sh_slang.min.js +1 -0
  86. data/public/js/sh_lang/sh_sml.min.js +1 -0
  87. data/public/js/sh_lang/sh_spec.min.js +1 -0
  88. data/public/js/sh_lang/sh_sql.min.js +1 -0
  89. data/public/js/sh_lang/sh_tcl.min.js +1 -0
  90. data/public/js/sh_lang/sh_xml.min.js +1 -0
  91. data/public/js/sh_lang/sh_xorg.min.js +1 -0
  92. data/public/js/sh_main.min.js +4 -0
  93. data/public/js/showoff.js +441 -0
  94. data/views/index.erb +73 -0
  95. data/views/onepage.erb +36 -0
  96. metadata +198 -0
@@ -0,0 +1,346 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+ require 'json'
4
+ require 'nokogiri'
5
+ require 'showoff_utils'
6
+ require 'princely'
7
+ require 'ftools'
8
+
9
+ begin
10
+ require 'RMagick'
11
+ rescue LoadError
12
+ puts 'image sizing disabled - install RMagick'
13
+ end
14
+
15
+ begin
16
+ require 'princely'
17
+ rescue LoadError
18
+ puts 'pdf generation disabled - install princely'
19
+ end
20
+
21
+ begin
22
+ require 'rdiscount'
23
+ rescue LoadError
24
+ require 'bluecloth'
25
+ Markdown = BlueCloth
26
+ end
27
+ require 'pp'
28
+
29
+ class ShowOff < Sinatra::Application
30
+
31
+ attr_reader :cached_image_size
32
+
33
+ set :views, File.dirname(__FILE__) + '/../views'
34
+ set :public, File.dirname(__FILE__) + '/../public'
35
+ set :pres_dir, 'example'
36
+
37
+ def initialize(app=nil)
38
+ super(app)
39
+ puts dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
40
+ if Dir.pwd == dir
41
+ options.pres_dir = dir + '/example'
42
+ @root_path = "."
43
+ else
44
+ options.pres_dir = Dir.pwd
45
+ @root_path = ".."
46
+ end
47
+ @cached_image_size = {}
48
+ puts options.pres_dir
49
+ @pres_name = options.pres_dir.split('/').pop
50
+ end
51
+
52
+ helpers do
53
+ def load_section_files(section)
54
+ section = File.join(options.pres_dir, section)
55
+ files = Dir.glob("#{section}/**/*").sort
56
+ pp files
57
+ files
58
+ end
59
+
60
+ def css_files
61
+ Dir.glob("#{options.pres_dir}/*.css").map { |path| File.basename(path) }
62
+ end
63
+
64
+ def js_files
65
+ Dir.glob("#{options.pres_dir}/*.js").map { |path| File.basename(path) }
66
+ end
67
+
68
+ def process_markdown(name, content, static=false)
69
+ slides = content.split(/^!SLIDE/)
70
+ slides.delete('')
71
+ final = ''
72
+ if slides.size > 1
73
+ seq = 1
74
+ end
75
+ slides.each do |slide|
76
+ md = ''
77
+ # extract content classes
78
+ lines = slide.split("\n")
79
+ content_classes = lines.shift.split rescue []
80
+ slide = lines.join("\n")
81
+ # add content class too
82
+ content_classes.unshift "content"
83
+ # extract transition, defaulting to none
84
+ transition = 'none'
85
+ content_classes.delete_if { |x| x =~ /^transition=(.+)/ && transition = $1 }
86
+ puts "classes: #{content_classes.inspect}"
87
+ puts "transition: #{transition}"
88
+ # create html
89
+ md += "<div class=\"slide\" data-transition=\"#{transition}\">"
90
+ if seq
91
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
92
+ seq += 1
93
+ else
94
+ md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
95
+ end
96
+ sl = Markdown.new(slide).to_html
97
+ sl = update_image_paths(name, sl, static)
98
+ md += sl
99
+ md += "</div>\n"
100
+ md += "</div>\n"
101
+ final += update_commandline_code(md)
102
+ final = update_p_classes(final)
103
+ end
104
+ final
105
+ end
106
+
107
+ # find any lines that start with a <p>.(something) and turn them into <p class="something">
108
+ def update_p_classes(markdown)
109
+ markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
110
+ end
111
+
112
+ def update_image_paths(path, slide, static=false)
113
+ paths = path.split('/')
114
+ paths.pop
115
+ path = paths.join('/')
116
+ replacement_prefix = static ?
117
+ %(img src="file://#{options.pres_dir}/static/#{path}) :
118
+ %(img src="/image/#{path})
119
+ slide.gsub(/img src=\"(.*?)\"/) do |s|
120
+ img_path = File.join(path, $1)
121
+ w, h = get_image_size(img_path)
122
+ src = %(#{replacement_prefix}/#{$1}")
123
+ if w && h
124
+ src << %( width="#{w}" height="#{h}")
125
+ end
126
+ src
127
+ end
128
+ end
129
+
130
+ if defined?(Magick)
131
+ def get_image_size(path)
132
+ if !cached_image_size.key?(path)
133
+ img = Magick::Image.ping(path).first
134
+ cached_image_size[path] = [img.columns, img.rows]
135
+ end
136
+ cached_image_size[path]
137
+ end
138
+ else
139
+ def get_image_size(path)
140
+ end
141
+ end
142
+
143
+ def update_commandline_code(slide)
144
+ html = Nokogiri::XML.parse(slide)
145
+
146
+ html.css('pre').each do |pre|
147
+ pre.css('code').each do |code|
148
+ out = code.text
149
+ lines = out.split("\n")
150
+ if lines.first[0, 3] == '@@@'
151
+ lang = lines.shift.gsub('@@@', '').strip
152
+ pre.set_attribute('class', 'sh_' + lang)
153
+ code.content = lines.join("\n")
154
+ end
155
+ end
156
+ end
157
+
158
+ html.css('.commandline > pre > code').each do |code|
159
+ out = code.text
160
+ lines = out.split(/^\$(.*?)$/)
161
+ lines.delete('')
162
+ code.content = ''
163
+ while(lines.size > 0) do
164
+ command = lines.shift
165
+ result = lines.shift
166
+ c = Nokogiri::XML::Node.new('code', html)
167
+ c.set_attribute('class', 'command')
168
+ c.content = '$' + command
169
+ code << c
170
+ c = Nokogiri::XML::Node.new('code', html)
171
+ c.set_attribute('class', 'result')
172
+ c.content = result
173
+ code << c
174
+ end
175
+ end
176
+ html.root.to_s
177
+ end
178
+
179
+ def get_slides_html(static=false)
180
+ sections = ShowOffUtils.showoff_sections(options.pres_dir)
181
+ files = []
182
+ if sections
183
+ sections.each do |section|
184
+ files << load_section_files(section)
185
+ end
186
+ files = files.flatten
187
+ files = files.select { |f| f =~ /.md/ }
188
+ data = ''
189
+ files.each do |f|
190
+ fname = f.gsub(options.pres_dir + '/', '').gsub('.md', '')
191
+ data += process_markdown(fname, File.read(f),static)
192
+ end
193
+ end
194
+ data
195
+ end
196
+
197
+ def inline_css(csses, pre = nil)
198
+ css_content = '<style type="text/css">'
199
+ csses.each do |css_file|
200
+ if pre
201
+ css_file = File.join(File.dirname(__FILE__), '..', pre, css_file)
202
+ else
203
+ css_file = File.join(options.pres_dir, css_file)
204
+ end
205
+ css_content += File.read(css_file)
206
+ end
207
+ css_content += '</style>'
208
+ css_content
209
+ end
210
+
211
+ def inline_js(jses, pre = nil)
212
+ js_content = '<script type="text/javascript">'
213
+ jses.each do |js_file|
214
+ if pre
215
+ js_file = File.join(File.dirname(__FILE__), '..', pre, js_file)
216
+ else
217
+ js_file = File.join(options.pres_dir, js_file)
218
+ end
219
+ js_content += File.read(js_file)
220
+ end
221
+ js_content += '</script>'
222
+ js_content
223
+ end
224
+
225
+ def index(static=false)
226
+ if static
227
+ @slides = get_slides_html(static)
228
+ @asset_path = "."
229
+ end
230
+ erb :index
231
+ end
232
+
233
+ def clean_link(href)
234
+ if href && href[0, 1] == '/'
235
+ href = href[1, href.size]
236
+ end
237
+ href
238
+ end
239
+
240
+ def assets_needed
241
+ assets = ["index", "slides"]
242
+
243
+ index = erb :index
244
+ html = Nokogiri::XML.parse(index)
245
+ html.css('head link').each do |link|
246
+ href = clean_link(link['href'])
247
+ assets << href if href
248
+ end
249
+ html.css('head script').each do |link|
250
+ href = clean_link(link['src'])
251
+ assets << href if href
252
+ end
253
+
254
+ slides = get_slides_html
255
+ html = Nokogiri::XML.parse("<slides>" + slides + "</slides>")
256
+ html.css('img').each do |link|
257
+ href = clean_link(link['src'])
258
+ assets << href if href
259
+ end
260
+
261
+ assets.join("\n")
262
+ end
263
+
264
+ def slides(static=false)
265
+ get_slides_html(static)
266
+ end
267
+
268
+ def onepage(static=false)
269
+ @slides = get_slides_html(static)
270
+ erb :onepage
271
+ end
272
+
273
+ def pdf(static=false)
274
+ @slides = get_slides_html(static)
275
+ @no_js = true
276
+ html = erb :onepage
277
+ p = Princely.new
278
+ # TODO make a random filename
279
+ p.pdf_from_string_to_file(html, '/tmp/preso.pdf')
280
+ File.new('/tmp/preso.pdf')
281
+ end
282
+
283
+ end
284
+
285
+
286
+ def self.do_static(what)
287
+ what = "index" if !what
288
+
289
+ # Nasty hack to get the actual ShowOff module
290
+ showoff = ShowOff.new
291
+ while !showoff.is_a?(ShowOff)
292
+ showoff = showoff.instance_variable_get(:@app)
293
+ end
294
+ name = showoff.instance_variable_get(:@pres_name)
295
+ path = showoff.instance_variable_get(:@root_path)
296
+ data = showoff.send(what, true)
297
+ if data.is_a?(File)
298
+ File.cp(data.path, "#{name}.pdf")
299
+ else
300
+ out = "#{path}/#{name}/static"
301
+ # First make a directory
302
+ File.makedirs("#{out}")
303
+ # Then write the html
304
+ file = File.new("#{out}/index.html", "w")
305
+ file.puts(data)
306
+ file.close
307
+ # Now copy all the js and css
308
+ my_path = File.join( File.dirname(__FILE__), '..', 'public')
309
+ ["js", "css"].each { |dir|
310
+ FileUtils.copy_entry("#{my_path}/#{dir}", "#{out}/#{dir}")
311
+ }
312
+ # And copy the directory
313
+ Dir.glob("#{my_path}/#{name}/*").each { |subpath|
314
+ base = File.basename(subpath)
315
+ next if "static" == base
316
+ next unless File.directory?(subpath) || base.match(/\.(css|js)$/)
317
+ FileUtils.copy_entry(subpath, "#{out}/#{base}")
318
+ }
319
+ end
320
+ end
321
+
322
+
323
+
324
+ get %r{(?:image|file)/(.*)} do
325
+ path = params[:captures].first
326
+ full_path = File.join(options.pres_dir, path)
327
+ send_file full_path
328
+ end
329
+
330
+ get %r{/(.*)} do
331
+ @title = ShowOffUtils.showoff_title
332
+ what = params[:captures].first
333
+ what = 'index' if "" == what
334
+ if (what != "favicon.ico")
335
+ data = send(what)
336
+ if data.is_a?(File)
337
+ send_file data.path
338
+ else
339
+ data
340
+ end
341
+ end
342
+ end
343
+
344
+
345
+
346
+ end
@@ -0,0 +1,259 @@
1
+ class ShowOffUtils
2
+ SHOWOFF_JSON_FILE = 'showoff.json'
3
+
4
+ def self.create(dirname,create_samples,dir='one')
5
+ Dir.mkdir(dirname) if !File.exists?(dirname)
6
+ Dir.chdir(dirname) do
7
+ if create_samples
8
+ # create section
9
+ Dir.mkdir(dir)
10
+
11
+ # create markdown file
12
+ File.open("#{dir}/01_slide.md", 'w+') do |f|
13
+ f.puts make_slide("My Presentation")
14
+ f.puts make_slide("Bullet Points","bullets incremental",["first point","second point","third point"])
15
+ end
16
+ end
17
+
18
+ # create showoff.json
19
+ File.open(SHOWOFF_JSON_FILE, 'w+') do |f|
20
+ f.puts "{ \"name\": \"My Preso\", \"sections\": [ {\"section\":\"#{dir}\"} ]}"
21
+ end
22
+
23
+ if create_samples
24
+ puts "done. run 'showoff serve' in #{dirname}/ dir to see slideshow"
25
+ else
26
+ puts "done. add slides, modify #{SHOWOFF_JSON_FILE} and then run 'showoff serve' in #{dirname}/ dir to see slideshow"
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.heroku(name)
32
+ if !File.exists?(SHOWOFF_JSON_FILE)
33
+ puts "fail. not a showoff directory"
34
+ return false
35
+ end
36
+ # create .gems file
37
+ File.open('.gems', 'w+') do |f|
38
+ f.puts "bluecloth"
39
+ f.puts "nokogiri"
40
+ f.puts "showoff"
41
+ f.puts "gli"
42
+ end if !File.exists?('.gems')
43
+
44
+ # create config.ru file
45
+ File.open('config.ru', 'w+') do |f|
46
+ f.puts 'require "showoff"'
47
+ f.puts 'run ShowOff.new'
48
+ end if !File.exists?('config.ru')
49
+
50
+ puts "herokuized. run something like this to launch your heroku presentation:
51
+
52
+ heroku create #{name}
53
+ git add .gems config.ru
54
+ git commit -m 'herokuized'
55
+ git push heroku master
56
+ "
57
+ end
58
+
59
+ # Makes a slide as a string.
60
+ # [title] title of the slide
61
+ # [classes] any "classes" to include, such as 'smaller', 'transition', etc.
62
+ # [content] slide content. Currently, if this is an array, it will make a bullet list. Otherwise
63
+ # the string value of this will be put in the slide as-is
64
+ def self.make_slide(title,classes="",content=nil)
65
+ slide = "!SLIDE #{classes}\n"
66
+ slide << "# #{title} #\n"
67
+ slide << "\n"
68
+ if content
69
+ if content.kind_of? Array
70
+ content.each { |x| slide << "* #{x.to_s}\n" }
71
+ else
72
+ slide << content.to_s
73
+ end
74
+ end
75
+ slide
76
+ end
77
+
78
+ TYPES = {
79
+ :default => lambda { |t,size,source,type| make_slide(t,"#{size} #{type}",source) },
80
+ 'title' => lambda { |t,size,dontcare| make_slide(t,size) },
81
+ 'bullets' => lambda { |t,size,dontcare| make_slide(t,"#{size} bullets incremental",["bullets","go","here"])},
82
+ 'smbullets' => lambda { |t,size,dontcare| make_slide(t,"#{size} smbullets incremental",["bullets","go","here","and","here"])},
83
+ 'code' => lambda { |t,size,src| make_slide(t,size,blank?(src) ? " @@@ Ruby\n code_here()" : src) },
84
+ 'commandline' => lambda { |t,size,dontcare| make_slide(t,"#{size} commandline"," $ command here\n output here")},
85
+ 'full-page' => lambda { |t,size,dontcare| make_slide(t,"#{size} full-page","![Image Description](image/ref.png)")},
86
+ }
87
+
88
+
89
+ # Adds a new slide to a given dir, giving it a number such that it falls after all slides
90
+ # in that dir.
91
+ # Options are:
92
+ # [:dir] - dir where we put the slide (if omitted, slide is output to $stdout)
93
+ # [:name] - name of the file, without the number prefix. (if omitted, a default is used)
94
+ # [:title] - title in the slide. If not specified the source file name is
95
+ # used. If THAT is not specified, uses the value of +:name+. If THAT is not
96
+ # specified, a suitable default is used
97
+ # [:code] - path to a source file to use as content (force :type to be 'code')
98
+ # [:number] - true if numbering should be done, false if not
99
+ # [:type] - the type of slide to create
100
+ def self.add_slide(options)
101
+
102
+ add_new_dir(options[:dir]) if options[:dir] && !File.exists?(options[:dir])
103
+
104
+ options[:type] = 'code' if options[:code]
105
+
106
+ title = determine_title(options[:title],options[:name],options[:code])
107
+
108
+ options[:name] = 'new_slide' if !options[:name]
109
+
110
+ size,source = determine_size_and_source(options[:code])
111
+ type = options[:type] || :default
112
+ slide = TYPES[type].call(title,size,source)
113
+
114
+ if options[:dir]
115
+ filename = determine_filename(options[:dir],options[:name],options[:number])
116
+ write_file(filename,slide)
117
+ else
118
+ puts slide
119
+ puts
120
+ end
121
+
122
+ end
123
+
124
+ # Adds the given directory to this presentation, appending it to
125
+ # the end of showoff.json as well
126
+ def self.add_new_dir(dir)
127
+ puts "Creating #{dir}..."
128
+ Dir.mkdir dir
129
+
130
+ showoff_json = JSON.parse(File.read(SHOWOFF_JSON_FILE))
131
+ showoff_json << { "section" => dir }
132
+ File.open(SHOWOFF_JSON_FILE,'w') do |file|
133
+ file.puts JSON.generate(showoff_json)
134
+ end
135
+ puts "#{SHOWOFF_JSON_FILE} updated"
136
+ end
137
+
138
+ def self.blank?(string)
139
+ string.nil? || string.strip.length == 0
140
+ end
141
+
142
+ def self.determine_size_and_source(code)
143
+ size = ""
144
+ source = ""
145
+ if code
146
+ source,lines,width = read_code(code)
147
+ size = adjust_size(lines,width)
148
+ end
149
+ [size,source]
150
+ end
151
+
152
+ def self.write_file(filename,slide)
153
+ File.open(filename,'w') do |file|
154
+ file.puts slide
155
+ end
156
+ puts "Wrote #{filename}"
157
+ end
158
+
159
+ def self.determine_filename(slide_dir,slide_name,number)
160
+ filename = "#{slide_dir}/#{slide_name}.md"
161
+ if number
162
+ max = find_next_number(slide_dir)
163
+ filename = "#{slide_dir}/#{max}_#{slide_name}.md"
164
+ end
165
+ filename
166
+ end
167
+
168
+ # Finds the next number in the given dir to
169
+ # name a slide as the last slide in the dir.
170
+ def self.find_next_number(slide_dir)
171
+ max = 0
172
+ Dir.open(slide_dir).each do |file|
173
+ if file =~ /(\d+).*\.md/
174
+ num = $1.to_i
175
+ max = num if num > max
176
+ end
177
+ end
178
+ max += 1
179
+ max = "0#{max}" if max < 10
180
+ max
181
+ end
182
+
183
+ def self.determine_title(title,slide_name,code)
184
+ if blank?(title)
185
+ title = slide_name
186
+ title = File.basename(code) if code
187
+ end
188
+ title = "Title here" if blank?(title)
189
+ title
190
+ end
191
+
192
+ # Determines a more optimal value for the size (e.g. small vs. smaller)
193
+ # based upon the size of the code being formatted.
194
+ def self.adjust_size(lines,width)
195
+ size = ""
196
+ # These values determined empircally
197
+ size = "small" if width > 50
198
+ size = "small" if lines > 15
199
+ size = "smaller" if width > 57
200
+ size = "smaller" if lines > 19
201
+ puts "warning, some lines are too long and the code may be cut off" if width > 65
202
+ puts "warning, your code is too long and the code may be cut off" if lines > 23
203
+ size
204
+ end
205
+
206
+ # Reads the code from the source file, returning
207
+ # the code, indented for markdown, as well as the number of lines
208
+ # and the width of the largest line
209
+ def self.read_code(source_file)
210
+ code = " @@@ #{lang(source_file)}\n"
211
+ lines = 0
212
+ width = 0
213
+ File.open(source_file) do |code_file|
214
+ code_file.readlines.each do |line|
215
+ code += " #{line}"
216
+ lines += 1
217
+ width = line.length if line.length > width
218
+ end
219
+ end
220
+ [code,lines,width]
221
+ end
222
+
223
+ def self.showoff_sections(dir = '.')
224
+ index = File.join(dir, ShowOffUtils::SHOWOFF_JSON_FILE )
225
+ order = nil
226
+ if File.exists?(index)
227
+ data = JSON.parse(File.read(index))
228
+ pp data
229
+ if data.is_a?(Hash)
230
+ order = data['sections']
231
+ else
232
+ order = data
233
+ end
234
+ order = order.map { |s| s['section'] }
235
+ end
236
+ order
237
+ end
238
+
239
+ def self.showoff_title(dir = '.')
240
+ index = File.join(dir, ShowOffUtils::SHOWOFF_JSON_FILE )
241
+ order = nil
242
+ if File.exists?(index)
243
+ data = JSON.parse(File.read(index))
244
+ data.is_a?(Hash) && data['name'] || "Presentation"
245
+ end
246
+ end
247
+
248
+ EXTENSIONS = {
249
+ 'pl' => 'perl',
250
+ 'rb' => 'ruby',
251
+ 'erl' => 'erlang',
252
+ # so not exhaustive, but probably good enough for now
253
+ }
254
+
255
+ def self.lang(source_file)
256
+ ext = File.extname(source_file).gsub(/^\./,'')
257
+ EXTENSIONS[ext] || ext
258
+ end
259
+ end