lib/slideshow.rb in slideshow-0.7.7 vs lib/slideshow.rb in slideshow-0.7.8

- old
+ new

@@ -1,645 +1,55 @@ $KCODE = 'utf' +LIB_PATH = File.expand_path( File.dirname(__FILE__) ) +$:.unshift(LIB_PATH) + +# core and stlibs require 'optparse' require 'erb' -require 'redcloth' require 'logger' require 'fileutils' require 'ftools' require 'pp' +# required gems +require 'redcloth' +# own code +require 'slideshow/opts' +require 'slideshow/gen' + module Slideshow - VERSION = '0.7.7' + VERSION = '0.7.8' -# 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 Slideshow.main - def put( key, value ) - key = normalize_key( key ) - setter = "#{key}=".to_sym - - if respond_to? setter - send setter, value - else - @hash[ key ] = value - end - end - - def gradient=( value ) - put_gradient( value, :theme, :color1, :color2 ) - end - - def gradient_colors=( value ) - put_gradient( value, :color1, :color2 ) - end - - def gradient_color=( value ) - put_gradient( value, :color1 ) - end - - def gradient_theme=( value ) - put_gradient( value, :theme ) - end - - def []( key ) - value = @hash[ normalize_key( key ) ] - if value.nil? - puts "** Warning: header '#{key}' undefined" - "- #{key} not found -" - else - value - 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 manifest - get( 'manifest', 's6.txt' ) - end - - def output_path - get( 'output', '.' ) - end - - def code_engine - get( 'code-engine', DEFAULTS[ :code_engine ] ) - end - - def code_txmt - get( 'code-txmt', DEFAULTS[ :code_txmt ]) - end - - - DEFAULTS = - { - :title => 'Untitled Slide Show', - :footer => '', - :subfooter => '', - :gradient_theme => 'dark', - :gradient_color1 => 'red', - :gradient_color2 => 'black', - - :code_engine => 'uv', # ultraviolet (uv) | coderay (cr) - :code_txmt => 'false', # Text Mate Hyperlink for Source? - } - - def set_defaults - DEFAULTS.each_pair do | key, value | - @hash[ key ] = value if @hash[ key ].nil? - end - end - - def get( key, default ) - @hash.fetch( normalize_key(key), default ) - end - -private - - def normalize_key( key ) - key.to_s.downcase.tr('-', '_').to_sym - end - - # Assigns the given gradient-* keys to the values in the given string. - def put_gradient( string, *keys ) - values = string.split( ' ' ) - - values.zip(keys).each do |v, k| - @hash[ normalize_key( "gradient-#{k}" ) ] = v.tr( '-', '_' ) - end - end - - def get_boolean( key, default ) - value = @hash[ normalize_key( key ) ] - if value.nil? - default - else - (value == true || value =~ /true|yes|on/i) ? true : false - end - end - -end # class Opts - - -class Gen - - KNOWN_TEXTILE_EXTNAMES = [ '.textile', '.t' ] - KNOWN_MARKDOWN_EXTNAMES = [ '.markdown', '.m', '.mark', '.mkdn', '.md', '.txt', '.text' ] - KNOWN_EXTNAMES = KNOWN_TEXTILE_EXTNAMES + KNOWN_MARKDOWN_EXTNAMES - - # note: only bluecloth is listed as a dependency in gem specs (because it's Ruby only and, thus, easy to install) - # if you want to use other markdown libs install the required/desired lib e.g. - # use gem install rdiscount for rdiscount and so on - # - # also note for now the first present markdown library gets used - # the search order is first come, first serve, that is: rdiscount, rpeg-markdown, maruku, bluecloth (fallback, always present) - KNOWN_MARKDOWN_LIBS = [ - [ 'rdiscount', lambda { |content| RDiscount.new( content ).to_html } ], - [ 'rpeg-markdown', lambda { |content| PEGMarkdown.new( content ).to_html } ], - [ 'maruku', lambda { |content| Maruku.new( content, {:on_error => :raise} ).to_html } ], - [ 'bluecloth', lambda { |content| BlueCloth.new( content ).to_html } ] - ] - - BUILTIN_MANIFESTS = [ 'fullerscreen.txt', 'fullerscreen.txt.gen', - 's5.txt', 's5.txt.gen', - 's6.txt', 's6.txt.gen', - 's5blank.txt.gen' ] - - def initialize - @logger = Logger.new(STDOUT) - @logger.level = Logger::INFO - @opts = Opts.new - end - - # replace w/ attr_reader :logger, :opts ?? - - def logger - @logger - end - - def opts - @opts - end - - def headers - # give access to helpers to opts with a different name - @opts - end - - def session - # give helpers/plugins a session-like hash - @session - end - - def markup_type - @markup_type # :textile, :markdown - end - - def load_markdown_libs - # check for available markdown libs/gems - # try to require each lib and remove any not installed - @markdown_libs = [] - - KNOWN_MARKDOWN_LIBS.each do |lib| - begin - require lib[0] - @markdown_libs << lib - rescue LoadError => ex - logger.debug "Markdown library #{lib[0]} not found. Use gem install #{lib[0]} to install." - end - end - - logger.debug "Installed Markdown libraries: #{@markdown_libs.map{ |lib| lib[0] }.join(', ')}" - logger.debug "Using Markdown library #{@markdown_libs.first[0]}." - end - - # todo: move to filter (for easier reuse) - def markdown_to_html( content ) - @markdown_libs.first[1].call( content ) - end - - # todo: move to filter (for easier reuse) - def textile_to_html( content ) - # 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 - - def wrap_markup( text ) - if markup_type == :textile - # saveguard with notextile wrapper etc./no further processing needed - "<notextile>\n#{text}\n</notextile>" - else - text - end - end + # allow env variable to set RUBYOPT-style default command line options + # e.g. -o slides -t <your_template_manifest_here> + slideshowopt = ENV[ 'SLIDESHOWOPT' ] - 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_manifest( path ) + args = [] + args += slideshowopt.split if slideshowopt + args += ARGV.dup - # check if file exists (if yes use custom template package!) - allows you to override builtin package with same name - if BUILTIN_MANIFESTS.include?( path ) && !File.exists?( path ) - templatesdir = "#{File.dirname(__FILE__)}/templates" - logger.debug "use builtin template package" - logger.debug "templatesdir=#{templatesdir}" - filename = "#{templatesdir}/#{path}" - else - templatesdir = File.dirname( path ) - logger.debug "use custom template package" - logger.debug "templatesdir=#{templatesdir}" - filename = path - end - - manifest = [] - puts " Loading template manifest #{filename}..." - - File.open( filename ).readlines.each_with_index do |line,i| - case line - when /^\s*$/ - # skip empty lines - when /^\s*#.*$/ - # skip comment lines - else - logger.debug "line #{i+1}: #{line.strip}" - values = line.strip.split( /[ <,+]+/ ) - - # add source for shortcuts (assumes relative path; if not issue warning/error) - values << values[0] if values.size == 1 - - # normalize all source paths (1..-1) /make full path/add template dir - (1..values.size-1).each do |i| - values[i] = "#{templatesdir}/#{values[i]}" - logger.debug " path[#{i}]=>#{values[i]}<" - end - - manifest << values - end - end - - manifest + Gen.new.run(args) end - def load_template( path ) - puts " Loading template #{path}..." - return File.read( path ) - end - - def render_template( content, the_binding ) - ERB.new( content ).result( the_binding ) - end - - def load_template_old_delete( 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 with_output_path( dest, output_path ) - dest_full = File.expand_path( dest, output_path ) - logger.debug "dest_full=#{dest_full}" - - # make sure dest path exists - dest_path = File.dirname( dest_full ) - logger.debug "dest_path=#{dest_path}" - File.makedirs( dest_path ) unless File.directory? dest_path - dest_full - end - - def create_slideshow_templates - logger.debug "manifest=#{opts.manifest}.gen" - manifest = load_manifest( opts.manifest+".gen" ) - - # expand output path in current dir and make sure output path exists - outpath = File.expand_path( opts.output_path ) - logger.debug "outpath=#{outpath}" - File.makedirs( outpath ) unless File.directory? outpath - - manifest.each do |entry| - dest = entry[0] - source = entry[1] - - puts "Copying to #{dest} from #{source}..." - File.copy( source, with_output_path( dest, outpath ) ) - end - - puts "Done." - end - - def create_slideshow( fn ) - - logger.debug "manifest=#{opts.manifest}" - manifest = load_manifest( opts.manifest ) - # pp manifest - - # expand output path in current dir and make sure output path exists - outpath = File.expand_path( opts.output_path ) - logger.debug "outpath=#{outpath}" - File.makedirs( outpath ) unless File.directory? outpath - - dirname = File.dirname( fn ) - basename = File.basename( fn, '.*' ) - extname = File.extname( fn ) - logger.debug "dirname=#{dirname}, basename=#{basename}, extname=#{extname}" - - # change working dir to sourcefile dir - # todo: add a -c option to commandline? to let you set cwd? - - newcwd = File.expand_path( dirname ) - oldcwd = File.expand_path( Dir.pwd ) - - unless newcwd == oldcwd then - logger.debug "oldcwd=#{oldcwd}" - logger.debug "newcwd=#{newcwd}" - Dir.chdir newcwd - end - - puts "Preparing slideshow '#{basename}'..." - - if extname.empty? then - extname = ".textile" # default to .textile - - KNOWN_EXTNAMES.each do |e| - logger.debug "File.exists? #{dirname}/#{basename}#{e}" - if File.exists?( "#{dirname}/#{basename}#{e}" ) then - extname = e - logger.debug "extname=#{extname}" - break - end - end - end - - if KNOWN_MARKDOWN_EXTNAMES.include?( extname ) - @markup_type = :markdown - else - @markup_type = :textile - end - - # shared variables for templates (binding) - @content_for = {} # reset content_for hash - @name = basename - @headers = @opts # deprecate/remove: use headers method in template - - @session = {} # reset session hash for plugins/helpers - - inname = "#{dirname}/#{basename}#{extname}" - - logger.debug "inname=#{inname}" - - content_with_headers = File.read( inname ) - - # todo: read headers before command line options (lets you override options using commandline switch)? - - # read source document; split off optional header from source - # strip leading optional headers (key/value pairs) including optional empty lines - - read_headers = true - content = "" - - # fix: allow comments in header too (#) - - content_with_headers.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 - - opts.set_defaults - - # ruby note: .*? is non-greedy (shortest-possible) regex match - content.gsub!(/__SKIP__.*?__END__/m, '') - content.sub!(/__END__.*/m, '') - - # allow plugins/helpers; process source (including header) using erb - - # note: include is a ruby keyword; rename to __include__ so we can use it - content.gsub!( /<%=[ \t]*include/, '<%= __include__' ) - - content = ERB.new( content ).result( binding ) - - # run pre-filters (built-in macros) - # o replace {{{ w/ <pre class='code'> - # o replace }}} w/ </pre> - content.gsub!( "{{{{{{", "<pre class='code'>_S9BEGIN_" ) - content.gsub!( "}}}}}}", "_S9END_</pre>" ) - content.gsub!( "{{{", "<pre class='code'>" ) - content.gsub!( "}}}", "</pre>" ) - # restore escaped {{{}}} I'm sure there's a better way! Rubyize this! Anyone? - content.gsub!( "_S9BEGIN_", "{{{" ) - content.gsub!( "_S9END_", "}}}" ) - - # convert light-weight markup to hypertext - - content = case @markup_type - when :markdown - markdown_to_html( content ) - when :textile - textile_to_html( content ) - end - - # post-processing - - slide_counter = 0 - content2 = '' - - ## todo: move this to a filter (for easier reuse) - - # wrap h1's in slide divs; note use just <h1 since some processors add ids e.g. <h1 id='x'> - content.each_line do |line| - if line.include?( '<h1' ) then - content2 << "\n\n</div>" if slide_counter > 0 - content2 << "<div class='slide'>\n\n" - slide_counter += 1 - end - content2 << line - end - content2 << "\n\n</div>" if slide_counter > 0 - - manifest.each do |entry| - outname = entry[0] - if outname.include? '__file__' # process - outname = outname.gsub( '__file__', basename ) - puts "Preparing #{outname}..." - - out = File.new( with_output_path( outname, outpath ), "w+" ) - - out << render_template( load_template( entry[1] ), binding ) - - if entry.size > 2 # more than one source file? assume header and footer with content added inbetween - out << content2 - out << render_template( load_template( entry[2] ), binding ) - end - - out.flush - out.close - - else # just copy verbatim if target/dest has no __file__ in name - dest = entry[0] - source = entry[1] - - puts "Copying to #{dest} from #{source}..." - File.copy( source, with_output_path( dest, outpath ) ) - end - end - - puts "Done." -end - -def load_plugins - - # use lib folder unless we're in our very own folder - # (that use lib for its core functionality), thus, use plugins instead - if( File.expand_path( File.dirname(__FILE__) ) == File.expand_path( 'lib' ) ) - pattern = 'plugins/**/*.rb' - else - pattern = 'lib/**/*.rb' - end - - logger.debug "pattern=#{pattern}" - - Dir.glob( pattern ) do |plugin| - begin - puts "Loading plugins in '#{plugin}'..." - require( plugin ) - rescue Exception => e - puts "** error: failed loading plugins in '#{plugin}': #{e}" - end - end -end - -def run( args ) - - opt=OptionParser.new do |cmd| - - cmd.banner = "Usage: slideshow [options] name" - - #todo/fix: use -s5 option without optional hack? possible with OptionParser package/lib? - # use -5 switch instead? - cmd.on( '-s[OPTIONAL]', '--s5', 'S5 Compatible Slide Show' ) { opts.put( 's5', true ); opts.put( 'manifest', 's5.txt' ) } - cmd.on( '-f[OPTIONAL]', '--fullerscreen', 'FullerScreen Compatible Slide Show' ) { opts.put( 'fuller', true ); opts.put( 'manifest', 'fullerscreen.txt' ) } - # opts.on( "-s", "--style STYLE", "Select Stylesheet" ) { |s| $options[:style]=s } - # opts.on( "-v", "--version", "Show version" ) {} - - cmd.on( '-g', '--generate', 'Generate Slide Show Templates' ) { opts.put( 'generate', true ) } - - cmd.on( '-o', '--output PATH', 'outputs to Path' ) { |s| opts.put( 'output', s ) } - - # use -d or -o to select output directory for slideshow or slideshow templates? - # cmd.on( '-d', '--directory DIRECTORY', 'Output Directory' ) { |s| opts.put( 'directory', s ) } - # cmd.on( '-i', '--include PATH', 'Load Path' ) { |s| opts.put( 'include', s ) } - - # todo: find different letter for debug trace switch (use v for version?) - cmd.on( "-v", "--verbose", "Show debug trace" ) do - logger.datetime_format = "%H:%H:%S" - logger.level = Logger::DEBUG - end - - cmd.on( "-t", "--template TEMPLATE", "Template Manifest" ) do |t| - # todo: do some checks on passed in template argument - opts.put( 'manifest', t ) - end - - cmd.on_tail( "-h", "--help", "Show this message" ) do - puts - puts "Slide Show (S9) is a free web alternative to PowerPoint or KeyNote in Ruby" - puts - puts cmd.help - puts - puts "Examples:" - puts " slideshow microformats" - puts " slideshow microformats.textile" - puts " slideshow -s5 microformats # S5 compatible" - puts " slideshow -f microformats # FullerScreen compatible" - puts " slideshow -o slides microformats # Output slideshow to slides folder" - puts - puts "More examles:" - puts " slideshow -g # Generate slide show templates" - puts " slideshow -g -s5 # Generate S5 compatible slide show templates" - puts " slideshow -g -f # Generate FullerScreen compatible slide show templates" - puts - puts " slideshow -t s3.txt microformats # Use custom slide show templates" - puts - puts "Further information:" - puts " http://slideshow.rubyforge.org" - exit - end - end - - opt.parse!( args ) - - puts "Slide Show (S9) Version: #{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" - - if opts.generate? - create_slideshow_templates - else - load_markdown_libs - load_plugins # check for optional plugins/extension in ./lib folder - - args.each { |fn| create_slideshow( fn ) } - end -end - -end # class Gen - -def Slideshow.main - Gen.new.run(ARGV) -end - end # module Slideshow # load built-in (required) helpers/plugins -require "#{File.dirname(__FILE__)}/helpers/text_helper.rb" -require "#{File.dirname(__FILE__)}/helpers/capture_helper.rb" +require 'slideshow/helpers/text_helper.rb' +require 'slideshow/helpers/capture_helper.rb' # load built-in (optional) helpers/plugins # If a helper fails to load, simply ingnore it # If you want to use it install missing required gems e.g.: # gem install coderay # gem install ultraviolet etc. BUILTIN_OPT_HELPERS = [ - "#{File.dirname(__FILE__)}/helpers/uv_helper.rb", - "#{File.dirname(__FILE__)}/helpers/coderay_helper.rb", + 'slideshow/helpers/uv_helper.rb', + 'slideshow/helpers/coderay_helper.rb', ] BUILTIN_OPT_HELPERS.each do |helper| begin require(helper) \ No newline at end of file