module Slideshow class Gen def initialize @logger = Logger.new(STDOUT) @logger.level = Logger::INFO @opts = Opts.new @config = Config.new end attr_reader :logger, :opts, :config attr_reader :session # give helpers/plugins a session-like hash def headers # give access to helpers to opts with a different name @opts end attr_reader :markup_type # :textile, :markdown def load_markdown_libs # check for available markdown libs/gems # try to require each lib and remove any not installed @markdown_libs = [] config.known_markdown_libs.each do |lib| begin require lib @markdown_libs << lib rescue LoadError => ex logger.debug "Markdown library #{lib} not found. Use gem install #{lib} to install." end end puts " Found #{@markdown_libs.length} Markdown libraries: #{@markdown_libs.join(', ')}" end def markdown_to_html( content ) # call markdown filter; turn markdown lib name into method_name (mn) # eg. rpeg-markdown => rpeg_markdown_to_html puts " Converting Markdown-text (#{content.length} bytes) to HTML using library '#{@markdown_libs.first}'..." mn = @markdown_libs.first.downcase.tr( '-', '_' ) mn = "#{mn}_to_html".to_sym send mn, content # call 1st configured markdown engine e.g. kramdown_to_html( content ) end # uses configured markup processor (textile,markdown) to generate html def text_to_html( content ) content = case @markup_type when :markdown markdown_to_html( content ) when :textile textile_to_html( content ) end content end def guard_text( text ) # todo/fix 2: note for Textile we need to differentiate between blocks and inline # thus, to avoid runs - use guard_block (add a leading newline to avoid getting include in block that goes before) # todo/fix: remove wrap_markup; replace w/ guard_text # why: text might be css, js, not just html wrap_markup( text ) end def guard_block( text ) if markup_type == :textile # saveguard with notextile wrapper etc./no further processing needed # note: add leading newlines to avoid block-runons "\n\n\n#{text}\n\n" else text end end def guard_inline( text ) wrap_markup( text ) end def wrap_markup( text ) if markup_type == :textile # saveguard with notextile wrapper etc./no further processing needed "\n#{text}\n" else text end end def cache_dir RUBY_PLATFORM =~ /win32/ ? win32_cache_dir : File.join(File.expand_path("~"), ".slideshow") end def win32_cache_dir unless ENV['HOMEDRIVE'] && ENV['HOMEPATH'] && 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 config_dir unless @config_dir # first time? calculate config_dir value to "cache" if opts.config_path @config_dir = opts.config_path else @config_dir = cache_dir end # make sure path exists FileUtils.makedirs( @config_dir ) unless File.directory? @config_dir end @config_dir end def load_manifest_core( path ) manifest = [] File.open( path ).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 manifest << values end end manifest end def load_manifest( path ) filename = path puts " Loading template manifest #{filename}..." manifest = load_manifest_core( filename ) # post-processing # normalize all source paths (1..-1) /make full path/add template dir templatesdir = File.dirname( path ) logger.debug "templatesdir=#{templatesdir}" manifest.each do |values| (1..values.size-1).each do |i| values[i] = "#{templatesdir}/#{values[i]}" logger.debug " path[#{i}]=>#{values[i]}<" end end manifest end def find_manifests( patterns ) manifests = [] patterns.each do |pattern| pattern.gsub!( '\\', '/') # normalize path; make sure all path use / only logger.debug "Checking #{pattern}" Dir.glob( pattern ) do |file| logger.debug " Found manifest: #{file}" manifests << [ File.basename( file ), file ] end end manifests end def installed_generator_manifests # 1) search gem/templates builtin_patterns = [ "#{File.dirname( LIB_PATH )}/templates/*.txt.gen" ] find_manifests( builtin_patterns ) end def installed_template_manifests # 1) search ./templates # 2) search config_dir/templates # 3) search gem/templates builtin_patterns = [ "#{File.dirname( LIB_PATH )}/templates/*.txt" ] config_patterns = [ "#{config_dir}/templates/*.txt", "#{config_dir}/templates/*/*.txt" ] current_patterns = [ "templates/*.txt", "templates/*/*.txt" ] patterns = [] patterns += current_patterns unless LIB_PATH == File.expand_path( 'lib' ) # don't include working dir if we test code from repo (don't include slideshow/templates) patterns += config_patterns patterns += builtin_patterns find_manifests( patterns ) 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}" FileUtils.makedirs( dest_path ) unless File.directory? dest_path dest_full end def fetch_file( dest, src ) logger.debug "fetch( dest: #{dest}, src: #{src})" uri = URI.parse( src ) # new code: honor proxy env variable HTTP_PROXY proxy = ENV['HTTP_PROXY'] proxy = ENV['http_proxy'] if proxy.nil? # try possible lower/case env variable (for *nix systems) is this necessary?? if proxy proxy = URI.parse( proxy ) logger.debug "using net http proxy: proxy.host=#{proxy.host}, proxy.port=#{proxy.port}" if proxy.user && proxy.password logger.debug " using credentials: proxy.user=#{proxy.user}, proxy.password=****" else logger.debug " using no credentials" end else logger.debug "using direct net http access; no proxy configured" proxy = OpenStruct.new # all fields return nil (e.g. proxy.host, etc.) end # same as short-cut: http_proxy.get_respone( uri ) # use full code for easier changes http_proxy = Net::HTTP::Proxy( proxy.host, proxy.port, proxy.user, proxy.password ) http = http_proxy.new( uri.host, uri.port ) request = Net::HTTP::Get.new( uri.request_uri ) response = http.request( request ) unless response.code == '200' # note: responsoe.code is a string msg = "#{response.code} #{response.message}" puts "*** error: #{msg}" return # todo: throw StandardException? end logger.debug " content_type: #{response.content_type}, content_length: #{response.content_length}" # check for content type; use 'wb' for images if response.content_type =~ /image/ logger.debug ' switching to binary' flags = 'wb' else flags = 'w' end File.open( dest, flags ) do |f| f.write( response.body ) end end def fetch_slideshow_templates logger.debug "fetch_uri=#{opts.fetch_uri}" src = opts.fetch_uri ## check for builtin shortcut (assume no / or \) if src.index( '/' ).nil? && src.index( '\\' ).nil? shortcut = src.clone src = config.map_fetch_shortcut( src ) if src.nil? puts "** Error: No mapping found for fetch shortcut '#{shortcut}'." return end puts " Mapping fetch shortcut '#{shortcut}' to: #{src}" end # src = 'http://github.com/geraldb/slideshow/raw/d98e5b02b87ee66485431b1bee8fb6378297bfe4/code/templates/fullerscreen.txt' # src = 'http://github.com/geraldb/sandbox/raw/13d4fec0908fbfcc456b74dfe2f88621614b5244/s5blank/s5blank.txt' uri = URI.parse( src ) logger.debug "host: #{uri.host}, port: #{uri.port}, path: #{uri.path}" dirname = File.dirname( uri.path ) basename = File.basename( uri.path, '.*' ) # e.g. fullerscreen (without extension) filename = File.basename( uri.path ) # e.g. fullerscreen.txt (with extension) logger.debug "dirname: #{dirname}" logger.debug "basename: #{basename}, filename: #{filename}" dlbase = "http://#{uri.host}:#{uri.port}#{dirname}" pkgpath = File.expand_path( "#{config_dir}/templates/#{basename}" ) logger.debug "dlpath: #{dlbase}" logger.debug "pkgpath: #{pkgpath}" FileUtils.makedirs( pkgpath ) unless File.directory? pkgpath puts "Fetching template package '#{basename}'" puts " : from '#{dlbase}'" puts " : saving to '#{pkgpath}'" # download manifest dest = "#{pkgpath}/#{filename}" puts " Downloading manifest '#{filename}'..." fetch_file( dest, src ) manifest = load_manifest_core( dest ) # download templates listed in manifest manifest.each do |values| values[1..-1].each do |file| dest = "#{pkgpath}/#{file}" # make sure path exists destpath = File.dirname( dest ) FileUtils.makedirs( destpath ) unless File.directory? destpath src = "#{dlbase}/#{file}" puts " Downloading template '#{file}'..." fetch_file( dest, src ) end end puts "Done." end def create_slideshow_templates manifest_name = opts.manifest logger.debug "manifest=#{manifest_name}" manifests = installed_generator_manifests # check for builtin generator manifests matches = manifests.select { |m| m[0] == manifest_name+".gen" } if matches.empty? puts "*** error: unknown template manifest '#{manifest_name}'" # todo: list installed manifests exit 2 end manifest = load_manifest( matches[0][1] ) # expand output path in current dir and make sure output path exists outpath = File.expand_path( opts.output_path ) logger.debug "outpath=#{outpath}" FileUtils.makedirs( outpath ) unless File.directory? outpath manifest.each do |entry| dest = entry[0] source = entry[1] puts "Copying to #{dest} from #{source}..." FileUtils.copy( source, with_output_path( dest, outpath ) ) end puts "Done." end def list_slideshow_templates manifests = installed_template_manifests puts '' puts 'Installed templates include:' manifests.each do |manifest| puts " #{manifest[0]} (#{manifest[1]})" end end def create_slideshow( fn ) manifest_path_or_name = opts.manifest # add .txt file extension if missing (for convenience) manifest_path_or_name << ".txt" if File.extname( manifest_path_or_name ).empty? logger.debug "manifest=#{manifest_path_or_name}" # check if file exists (if yes use custom template package!) - allows you to override builtin package with same name if File.exists?( manifest_path_or_name ) manifest = load_manifest( manifest_path_or_name ) else # check for builtin manifests manifests = installed_template_manifests matches = manifests.select { |m| m[0] == manifest_path_or_name } if matches.empty? puts "*** error: unknown template manifest '#{manifest_path_or_name}'" # todo: list installed manifests exit 2 end manifest = load_manifest( matches[0][1] ) end # expand output path in current dir and make sure output path exists outpath = File.expand_path( opts.output_path ) logger.debug "outpath=#{outpath}" FileUtils.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 config.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 config.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 @extname = extname @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 = File.read( inname ) # run text filters config.text_filters.each do |filter| mn = filter.tr( '-', '_' ).to_sym # construct method name (mn) content = send( mn, content ) # call filter e.g. include_helper_hack( content ) end # convert light-weight markup to hypertext content = text_to_html( content ) # post-processing # 1) add slide break if @markup_type == :markdown && @markdown_libs.first == 'pandoc-ruby' content = add_slide_directive_before_div_h1( content ) else content = add_slide_directive_before_h1( content ) end dump_content_to_file_debug_html( content ) # 2) use generic slide break processing instruction to # split content into slides slide_counter = 0 slides = [] slide_source = "" content.each_line do |line| if line.include?( '/m, from )) logger.debug " adding css classes from pi #{$1.downcase}: #{$2.strip}" if slide.classes.nil? slide.classes = $2.strip else slide.classes << " #{$2.strip}" end from = Regexp.last_match.end(0) end # try to cut off header using non-greedy .+? pattern; tip test regex online at rubular.com # note/fix: needs to get improved to also handle case for h1 wrapped into div # (e.g. extract h1 - do not assume it starts slide source) if slide_source =~ /^\s*(.*?<\/h\d>)\s*(.*)/m slide.header = $1 slide.content = ($2 ? $2 : "") logger.debug " adding slide with header:\n#{slide.header}" else slide.content = slide_source logger.debug " adding slide with *no* header:\n#{slide.content}" end slides2 << slide end # for convenience create a string w/ all in-one-html # no need to wrap slides in divs etc. content2 = "" slides2.each do |slide| content2 << slide.to_classic_html end # make content2 and slide2 available to erb template # -- todo: cleanup variable names and use attr_readers for content and slide @slides = slides2 # strutured content @content = content2 # content all-in-one 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}..." FileUtils.copy( source, with_output_path( dest, outpath ) ) end end puts "Done." end def load_plugins patterns = [] patterns << "#{config_dir}/lib/**/*.rb" patterns << 'lib/**/*.rb' unless LIB_PATH == File.expand_path( 'lib' ) # don't include lib if we are in repo (don't include slideshow/lib) patterns.each do |pattern| pattern.gsub!( '\\', '/') # normalize path; make sure all path use / only 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 end def run( args ) opt=OptionParser.new do |cmd| cmd.banner = "Usage: slideshow [options] name" cmd.on( '-o', '--output PATH', 'Output Path' ) { |s| opts.put( 'output', s ) } cmd.on( '-g', '--generate', 'Generate Slide Show Templates (Using Built-In S6 Pack)' ) { opts.put( 'generate', true ) } cmd.on( "-t", "--template MANIFEST", "Template Manifest" ) do |t| # todo: do some checks on passed in template argument opts.put( 'manifest', t ) end # ?? opts.on( "-s", "--style STYLE", "Select Stylesheet" ) { |s| $options[:style]=s } # ?? opts.on( "--version", "Show version" ) {} # ?? cmd.on( '-i', '--include PATH', 'Load Path' ) { |s| opts.put( 'include', s ) } cmd.on( '-f', '--fetch URI', 'Fetch Templates' ) do |u| opts.put( 'fetch_uri', u ) end cmd.on( '-c', '--config PATH', 'Configuration Path (default is ~/.slideshow)' ) do |p| opts.put( 'config_path', p ) end cmd.on( '-l', '--list', 'List Installed Templates' ) { opts.put( 'list', true ) } # 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_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 # Process slides using Textile" puts " slideshow microformats.text # Process slides using Markdown" puts " slideshow -o slides microformats # Output slideshow to slides folder" puts puts "More examles:" puts " slideshow -g # Generate slide show templates using built-in S6 pack" puts puts " slideshow -l # List installed slide show templates" puts " slideshow -f s5blank # Fetch (install) S5 blank starter template from internet" puts " slideshow -t s5blank microformats # Use your own slide show templates (e.g. s5blank)" puts puts "Further information:" puts " http://slideshow.rubyforge.org" exit end end opt.parse!( args ) config.load puts "Slide Show (S9) Version: #{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" if opts.list? list_slideshow_templates elsif opts.generate? create_slideshow_templates elsif opts.fetch? fetch_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 end # module Slideshow