require 'optparse'
require 'erb'
require 'redcloth'
require 'maruku'
require 'logger'
require 'fileutils'
require 'ftools'
require 'hpricot'
require 'uv'
module Slideshow
class Params
def initialize( name, headers )
@svgname = "#{name}.svg"
@cssname = "#{name}.css"
@headers = headers
end
def params_binding
binding
end
end
def Slideshow.cache_dir
PLATFORM =~ /win32/ ? win32_cache_dir : File.join(File.expand_path("~"), ".slideshow")
end
def Slideshow.win32_cache_dir
unless File.exists?(home = ENV['HOMEDRIVE'] + ENV['HOMEPATH'])
puts "No HOMEDRIVE or HOMEPATH environment variable. Set one to save a" +
"local cache of stylesheets for syntax highlighting and more."
return false
else
return File.join(home, 'slideshow')
end
end
def Slideshow.load_template( name )
templatesdir = "#{File.dirname(__FILE__)}/templates"
logger.debug "templatesdir=#{templatesdir}"
File.read( "#{templatesdir}/#{name}" )
end
def Slideshow.render_template( content, b=TOPLEVEL_BINDING )
ERB.new( content ).result( b )
end
def Slideshow.create_slideshow( fn )
if get_boolean_option( 's5', false )
headerdoc = load_template( 's5/header.html.erb' )
footerdoc = load_template( 's5/footer.html.erb' )
styledoc = load_template( 's5/style.css.erb' )
elsif get_boolean_option( 'fuller', false ) || get_boolean_option( 'fullerscreen', false ) # use fullerscreen templates
headerdoc = load_template( 'header.html.erb' )
footerdoc = load_template( 'footer.html.erb' )
styledoc = load_template( 'style.css.erb' )
else # use default s6 templates
headerdoc = load_template( 's6/header.html.erb' )
footerdoc = load_template( 's6/footer.html.erb' )
styledoc = load_template( 's6/style.css.erb' )
end
# background theming shared between s5/s6/fullerscreen
gradientdoc = load_template( 'gradient.svg.erb' )
basename = File.basename( fn, '.*' )
extname = File.extname( fn )
known_textile_extnames = [ '.textile', '.t' ]
known_markdown_extnames = [ '.markdown', '.mark', '.m', '.txt', '.text' ]
known_extnames = known_textile_extnames + known_markdown_extnames
if extname.eql?("") then
extname = ".textile" # default to .textile
known_extnames.each { |e|
logger.debug "File.exists? #{basename}#{e}"
if File.exists?( "#{basename}#{e}" ) then
extname = e
logger.debug "extname=#{extname}"
break
end
}
end
inname = "#{basename}#{extname}"
outname = "#{basename}.html"
svgname = "#{basename}.svg"
cssname = "#{basename}.css"
logger.debug "inname=#{inname}"
content = File.read( inname )
# read source document
# strip leading optional headers (key/value pairs) including optional empty lines
read_headers = true
content = ""
File.open( inname ).readlines.each do |line|
if read_headers && line =~ /^\s*(\w[\w-]*)[ \t]*:[ \t]*(.*)/
key = $1.downcase
value = $2.strip
logger.debug " adding option: key=>#{key}< value=>#{value}<"
store_option( key, value )
elsif line =~ /^\s*$/
content << line unless read_headers
else
read_headers =false
content << line
end
end
# run pre-filters (built-in macros)
# o replace {{{ w/
# o replace }}} w/
content.gsub!( "{{{{{{", "_S9BEGIN_" )
content.gsub!( "}}}}}}", "_S9END_
" )
content.gsub!( "{{{", "" )
content.gsub!( "}}}", "
" )
# restore escaped {{{}}} I'm sure there's a better way! Rubyize this! Anyone?
content.gsub!( "_S9BEGIN_", "{{{" )
content.gsub!( "_S9END_", "}}}" )
set_default_options()
params = Params.new( basename, $options )
puts "Preparing slideshow theme '#{svgname}'..."
out = File.new( svgname, "w+" )
out << render_template( gradientdoc, params.params_binding )
out.flush
out.close
puts "Preparing slideshow '#{outname}'..."
# convert light-weight markup to hypertext
if known_markdown_extnames.include?( extname )
content = Maruku.new( content, {:on_error => :raise} ).to_html
# old code: content = BlueCloth.new( content ).to_html
else
content = RedCloth.new( content ).to_html
end
# post-processing
slide_counter = 0
content2 = ''
# wrap h1's in slide divs; note use just
content.each_line { |line|
if line.include?( '" if slide_counter > 0
content2 << "
\n\n"
slide_counter += 1
end
content2 << line
}
content2 << "\n\n
" if slide_counter > 0
## todo: run syntax highlighting before markup/textilize? lets us add textile to highlighted code?
## avoid undoing escaped entities?
include_code_stylesheet = false
# syntax highlight code
# todo: can the code handle escaped entities? e.g. >
doc = Hpricot(content2)
doc.search("pre.code, pre > code").each do |e|
if e.inner_html =~ /^\s*#!(\w+)/
lang = $1.downcase
if e.inner_html =~ /^\{\{\{/ # {{{ assumes escape/literal #!lang
# do nothing; next
logger.debug " skipping syntax highlighting using lang=#{lang}; assumimg escaped literal"
else
logger.debug " syntax highlighting using lang=#{lang}"
if Uv.syntaxes.include?(lang)
code = e.inner_html.sub(/^\s*#!\w+/, '').strip
code.gsub!( "<", "<" )
code.gsub!( ">", ">" )
code.gsub!( "&", "&" )
# todo: missing any other entities? use CGI::unescapeHTML?
logger.debug "code=>#{code}<"
# get options using headers
code_line_numbers = get_boolean_option( 'code-line-numbers', true )
code_theme = get_option( 'code-theme', 'amy' )
code_highlighted = Uv.parse( code, "xhtml", lang, code_line_numbers, code_theme )
# old code: e.inner_html = code_highlighted
# todo: is it ok to replace the pre.code enclosing element to avoid duplicates?
e.swap( code_highlighted )
include_code_stylesheet = true
end
end
end
end
content2 = doc.to_s
out = File.new( outname, "w+" )
out << render_template( headerdoc, params.params_binding )
out << content2
out << render_template( footerdoc, params.params_binding )
out.flush
out.close
puts "Preparing slideshow stylesheet '#{cssname}'..."
out = File.new( cssname, "w+" )
out << render_template( styledoc, params.params_binding )
if include_code_stylesheet
logger.debug "cache_dir=#{cache_dir}"
FileUtils.mkdir(cache_dir) unless File.exists?(cache_dir) if cache_dir
Uv.copy_files "xhtml", cache_dir
theme = get_option( 'code-theme', 'amy' )
theme_content = File.read( "#{cache_dir}/css/#{theme}.css" )
out << "/* styles for code syntax highlighting theme '#{theme}' */\n"
out << "\n"
out << theme_content
end
out.flush
out.close
if get_boolean_option( 's5', false )
# copy s5 machinery to s5 folder
# todo/fix: is there a better way to check if the dir exists?
Dir.mkdir( 's5' ) unless File.directory?( 's5' )
[ 'opera.css', 'outline.css', 'print.css', 's5-core.css', 'slides.js' ].each do |name|
source = "#{File.dirname(__FILE__)}/templates/s5/#{name}"
dest = "s5/#{name}"
logger.debug "copying '#{source} ' to '#{dest}'"
File.copy( source, dest )
end
elsif get_boolean_option( 'fuller', false ) || get_boolean_option( 'fullerscreen', false )
# do nothing; slideshow machinery built into plugin (requires install!)
else
# copy s6 machinery to s6 folder
Dir.mkdir( 's6' ) unless File.directory?( 's6' )
[ 'outline.css', 'print.css', 'slides.css', 'slides.js', 'jquery.js' ].each do |name|
source = "#{File.dirname(__FILE__)}/templates/s6/#{name}"
dest = "s6/#{name}"
logger.debug "copying '#{source} ' to '#{dest}'"
File.copy( source, dest )
end
end
puts "Done."
end
def Slideshow.logger
if @@logger.nil?
@@logger = Logger.new(STDOUT)
end
@@logger
end
def Slideshow.set_default_options()
defaults =
[
[ 'title', 'Untitled Slide Show' ],
[ 'gradient-theme', 'dark' ],
[ 'gradient-color1', 'red' ],
[ 'gradient-color2', 'black' ],
[ 'code-theme', 'amy' ],
[ 'code-line-numbers', 'true' ]
]
defaults.each do | item |
key = item[0]
value = item[1]
$options[ key ] = value if $options[ key ].nil?
end
end
def Slideshow.store_option( key, value )
key = key.downcase
if key.eql? 'code-theme' then
$options[ 'code-theme' ] = value.tr( '-', '_' )
elsif key.eql? 'gradient' then
values = value.split( ' ' )
$options[ 'gradient-theme' ] = values[0].tr( '-', '_' )
$options[ 'gradient-color1' ] = values[1] if values[1]
$options[ 'gradient-color2' ] = values[2] if values[2]
elsif key.eql? 'gradient-colors' then
values = value.split( ' ' )
$options[ 'gradient-color1' ] = values[0]
$options[ 'gradient-color2' ] = values[1] if values[1]
elsif key.eql? 'gradient-color' then
$options[ 'gradient-color1' ] = value
elsif key.eql? 'gradient-theme' then
$options[ 'gradient-theme' ] = value.tr( '-', '_' )
else
$options[ key ] = value
end
end
def Slideshow.get_option( key, default )
value = $options[ key ]
value.nil? ? default : value
end
def Slideshow.get_boolean_option( key, default )
value = $options[ key ]
if value.nil?
default
else
if( value == true || value.downcase == 'true' || value.downcase == 'yes' || value.downcase == 'on' )
true
else
false
end
end
end
def Slideshow.main
@@logger = nil
$options = {}
logger.level = Logger::INFO
opt=OptionParser.new do |opts|
opts.banner = "Usage: slideshow [options] name"
#todo/fix: use -s5 option without optional hack? possible with OptionParser package/lib?
# use -5 switch instead?
opts.on( '-s[OPTIONAL]', '--s5', 'S5 Compatible Slide Show' ) { $options[ 's5' ] = true; }
opts.on( '-f[OPTIONAL]', '--fullerscreen', 'FullerScreen Compatible Slide Show' ) { $options[ 'fuller' ] = true; }
# opts.on( "-s", "--style STYLE", "Select Stylesheet" ) { |s| $options[:style]=s }
# opts.on( "-v", "--version", "Show version" ) {}
opts.on( "-t", "--trace", "Show debug trace" ) {
logger.datetime_format = "%H:%H:%S"
logger.level = Logger::DEBUG
}
opts.on_tail( "-h", "--help", "Show this message" ) {
puts
puts "Slide Show (S9) is a free web alternative to PowerPoint or KeyNote in Ruby"
puts
puts opts.help
puts
puts "Examples:"
puts " slideshow microformats"
puts " slideshow microformats.textile"
puts " slideshow -s5 microformats # S5 compatible"
puts
puts "Further information:"
puts " http://slideshow.rubyforge.org"
exit
}
end
opt.parse!
puts "Slide Show (S9) Version: 0.5.1 on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
ARGV.each { |fn| Slideshow.create_slideshow( fn ) }
end
end
Slideshow.main if __FILE__ == $0