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