require 'optparse' require 'erb' require 'redcloth' require 'maruku' require 'logger' require 'fileutils' require 'ftools' require 'hpricot' require 'uv' module Slideshow VERSION = '0.6' class Params def initialize( name, headers ) @svgname = "#{name}.svg" @cssname = "#{name}.css" @headers = headers end def params_binding binding end end # todo: split (command line) options and headers? # e.g. share (command line) options between slide shows (but not headers?) class Opts def initialize @hash = {} end def put( key, value ) key = key.downcase if key.eql? 'code-theme' then code_theme = value elsif key.eql? 'gradient' then gradient = value elsif key.eql? 'gradient-colors' then gradient_colors = value elsif key.eql? 'gradient-color' then gradient_color = value elsif key.eql? 'gradient-theme' then gradient-theme = value else @hash[ key ] = value end end def code_theme=( value ) @hash[ 'code-theme' ] = value.tr( '-', '_' ) end def gradient=( value ) values = value.split( ' ' ) @hash[ 'gradient-theme' ] = values[0].tr( '-', '_' ) @hash[ 'gradient-color1' ] = values[1] if values[1] @hash[ 'gradient-color2' ] = values[2] if values[2] end def gradient_colors=( value ) values = value.split( ' ' ) @hash[ 'gradient-color1' ] = values[0] @hash[ 'gradient-color2' ] = values[1] if values[1] end def gradient_color=( value ) @hash[ 'gradient-color1' ] = value end def gradient_theme=( value ) @hash[ 'gradient-theme' ] = value.tr( '-', '_' ) end def get( key, default ) value = @hash[ key ] value.nil? ? default : value end def []( key ) value = @hash[ key ] value.nil? ? "- #{key} not found -" : value end def get_boolean( key, default ) value = @hash[ 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 generate? get_boolean( 'generate', false ) end def has_includes? @hash[ 'include' ] end def includes # fix: use os-agnostic delimiter (use : for Mac/Unix?) has_includes? ? @hash['include'].split( ';' ) : [] end def s5? get_boolean( 's5', false ) end def fullerscreen? get_boolean( 'fuller', false ) || get_boolean( 'fullerscreen', false ) end def code_theme get( 'code-theme', 'amy' ) end def code_line_numbers? get_boolean( 'code-line-numbers', true ) end DEFAULTS = [ [ 'title', 'Untitled Slide Show' ], [ 'gradient-theme', 'dark' ], [ 'gradient-color1', 'red' ], [ 'gradient-color2', 'black' ], [ 'code-theme', 'amy' ], [ 'code-line-numbers', 'true' ] ] def set_defaults DEFAULTS.each do | item | key = item[0] value = item[1] @hash[ key ] = value if @hash[ key ].nil? end end end # class Opts class Gen KNOWN_TEXTILE_EXTNAMES = [ '.textile', '.t' ] KNOWN_MARKDOWN_EXTNAMES = [ '.markdown', '.mark', '.m', '.txt', '.text' ] def initialize @logger = Logger.new(STDOUT) @logger.level = Logger::INFO @opts = Opts.new end def logger @logger end def opts @opts end def cache_dir PLATFORM =~ /win32/ ? win32_cache_dir : File.join(File.expand_path("~"), ".slideshow") end def 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 load_template( name, builtin ) if opts.has_includes? opts.includes.each do |path| logger.debug "File.exists? #{path}/#{name}" if File.exists?( "#{path}/#{name}" ) then puts "Loading custom template #{path}/#{name}..." return File.read( "#{path}/#{name}" ) end end end # fallback load builtin template packaged with gem load_builtin_template( builtin ) end def load_builtin_template( name ) templatesdir = "#{File.dirname(__FILE__)}/templates" logger.debug "templatesdir=#{templatesdir}" File.read( "#{templatesdir}/#{name}" ) end def render_template( content, b=TOPLEVEL_BINDING ) ERB.new( content ).result( b ) end def create_slideshow_templates if opts.s5? files = [[ 's5/header.html.erb', 'header.html.erb' ], [ 's5/footer.html.erb', 'footer.html.erb' ], [ 's5/style.css.erb', 'style.css.erb' ]] elsif opts.fullerscreen? # use fullerscreen templates files = [[ 'header.html.erb', 'header.html.erb' ], [ 'footer.html.erb', 'footer.html.erb' ], [ 'style.css.erb', 'style.css.erb' ]] else # use default s6 templates files = [[ 's6/header.html.erb', 'header.html.erb' ], [ 's6/footer.html.erb', 'footer.html.erb' ], [ 's6/style.css.erb', 'style.css.erb' ]] end # background theming shared between s5/s6/fullerscreen files << [ 'gradient.svg.erb', 'gradient.svg.erb' ] files.each do |file| source = "#{File.dirname(__FILE__)}/templates/#{file[0]}" dest = "#{file[1]}" puts "Copying '#{source}' to '#{dest}'" File.copy( source, dest ) end puts "Done." end def create_slideshow( fn ) if opts.s5? headerdoc = load_template( 'header.html.erb', 's5/header.html.erb' ) footerdoc = load_template( 'footer.html.erb', 's5/footer.html.erb' ) styledoc = load_template( 'style.css.erb', 's5/style.css.erb' ) elsif opts.fullerscreen? # use fullerscreen templates headerdoc = load_template( 'header.html.erb', 'header.html.erb' ) footerdoc = load_template( 'footer.html.erb', 'footer.html.erb' ) styledoc = load_template( 'style.css.erb', 'style.css.erb' ) else # use default s6 templates headerdoc = load_template( 'header.html.erb', 's6/header.html.erb' ) footerdoc = load_template( 'footer.html.erb', 's6/footer.html.erb' ) styledoc = load_template( 'style.css.erb', 's6/style.css.erb' ) end # background theming shared between s5/s6/fullerscreen gradientdoc = load_template( 'gradient.svg.erb', 'gradient.svg.erb' ) basename = File.basename( fn, '.*' ) extname = File.extname( fn ) 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}<" opts.put( 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_", "}}}" ) opts.set_defaults params = Params.new( basename, opts ) 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 # turn off hard line breaks # turn off span caps (see http://rubybook.ca/2008/08/16/redcloth) red = RedCloth.new( content, [:no_span_caps] ) red.hard_breaks = false content = red.to_html end # post-processing slide_counter = 0 content2 = '' # wrap h1's in slide divs; note use just