class ShowoffUtils
Constants
- EXTENSIONS
- HEROKU_CONFIG_FILE
- HEROKU_GEMS_FILE
- HEROKU_PROCFILE
- REQUIRED_GEMS
- TYPES
Public Class Methods
Adds the given directory to this presentation, appending it to the end of showoff.json as well
# File lib/showoff_utils.rb, line 325 def self.add_new_dir(dir) puts "Creating #{dir}..." Dir.mkdir dir showoff_json = JSON.parse(File.read(ShowoffUtils.presentation_config_file)) showoff_json["section"] = dir File.open(ShowoffUtils.presentation_config_file,'w') do |file| file.puts JSON.generate(showoff_json) end puts "#{ShowoffUtils.presentation_config_file} updated" end
Adds a new slide to a given dir, giving it a number such that it falls after all slides in that dir. Options are:
- :dir
-
dir where we put the slide (if omitted, slide is output to $stdout)
-
- :name
-
name of the file, without the number prefix. (if omitted, a default is used)
-
- :title
-
title in the slide. If not specified the source file name is used. If THAT is not specified, uses the value of
:name
. If THAT is not specified, a suitable default is used
-
- :code
-
path to a source file to use as content (force :type to be ‘code’)
-
- :number
-
true if numbering should be done, false if not
-
- :type
-
the type of slide to create
-
# File lib/showoff_utils.rb, line 299 def self.add_slide(options) add_new_dir(options[:dir]) if options[:dir] && !File.exist?(options[:dir]) options[:type] = 'code' if options[:code] title = determine_title(options[:title],options[:name],options[:code]) options[:name] = 'new_slide' if !options[:name] size,source = determine_size_and_source(options[:code]) type = options[:type] || :default slide = TYPES[type].call(title,size,source) if options[:name] filename = determine_filename(options[:dir],options[:name],options[:number]) write_file(filename,slide) else puts slide puts end end
Determines a more optimal value for the size (e.g. small vs. smaller) based upon the size of the code being formatted.
# File lib/showoff_utils.rb, line 398 def self.adjust_size(lines,width) size = "" # These values determined empircally size = "small" if width > 50 size = "small" if lines > 15 size = "smaller" if width > 57 size = "smaller" if lines > 19 puts "WARNING: some lines are too long and might be truncated" if width > 65 puts "WARNING: your code is too long and may not fit on a slide" if lines > 23 size end
# File lib/showoff_utils.rb, line 337 def self.blank?(string) string.nil? || string.strip.length == 0 end
clone a repo url, then run a provided block
# File lib/showoff_utils.rb, line 235 def self.clone(url, branch=nil, path=nil) require 'tmpdir' Dir.mktmpdir do |dir| if branch system('git', 'clone', '-b', branch, '--single-branch', '--depth', '1', url, dir) else system('git', 'clone', '--depth', '1', url, dir) end dir = File.join(dir, path) if path Dir.chdir dir do yield if block_given? end end end
# File lib/showoff_utils.rb, line 662 def self.command(command, error='command failed') puts "Running '#{command}'..." system(command) or raise error end
# File lib/showoff_utils.rb, line 34 def self.create(dirname,create_samples,dirs='one') FileUtils.mkdir_p(dirname) Dir.chdir(dirname) do dirs = dirs.split(',') if create_samples dirs.each do |dir| # create section FileUtils.mkdir_p(dir) # create markdown file File.open("#{dir}/00_section.md", 'w+') do |f| f.puts make_slide("Section Header", "center subsection") end File.open("#{dir}/01_slide.md", 'w+') do |f| f.puts make_slide("My Presentation") end File.open("#{dir}/02_slide.md", 'w+') do |f| f.puts make_slide("Bullet Points","bullets incremental",["first point","second point","third point"]) end end end # Create asset directories FileUtils.mkdir_p('_files/share') FileUtils.mkdir_p('_images') # create showoff.json File.open(ShowoffUtils.presentation_config_file, 'w+') do |f| sections = dirs.collect {|dir| {"section" => dir} } f.puts JSON.pretty_generate({ "name" => "My Preso", "sections" => sections }) end end end
Creates the given filename if it doesn’t exist or if force is true
filename - String name of the file to create force - if true, the file will always be created, if false, only create
if it's not there
block - takes a block that will be given the file handle to write
data into the file IF it's being created
Examples
create_file_if_needed("config.ru",false) do |file| file.puts "require 'showoff'" file.puts "run Showoff.new" end
Returns true if the file was created
# File lib/showoff_utils.rb, line 650 def self.create_file_if_needed(filename,force) if !File.exist?(filename) || force File.open(filename, 'w+') do |f| yield f end true else puts "#{filename} exists; not overwriting (see showoff help heroku)" false end end
Creates the file that lists the gems for heroku
filename - String name of the file password - Boolean to indicate if we are setting a password force - Boolean to indicate if we should overwrite the existing file formatter - Proc/lambda that takes 1 argument, the gem name, and formats it for the file
This is so we can support both the old .gems and the new bundler Gemfile
header - Proc/lambda that creates any header information in the file
Returns a boolean indicating that we had to create the file or not.
# File lib/showoff_utils.rb, line 626 def self.create_gems_file(filename,password,force,formatter,header=nil) create_file_if_needed(filename,force) do |file| file.puts header.call unless header.nil? REQUIRED_GEMS.each { |gem| file.puts formatter.call(gem) } file.puts formatter.call("rack") if password end end
# File lib/showoff_utils.rb, line 563 def self.default_style(dir = '.') get_config_option(dir, 'style', '') end
# File lib/showoff_utils.rb, line 567 def self.default_style?(style, dir = '.') default = default_style(dir) style.split('/').last.sub(/\.css$/, '') == default end
# File lib/showoff_utils.rb, line 358 def self.determine_filename(slide_dir,slide_name,number) raise "Slide name is required" unless slide_name if number next_num = find_next_number(slide_dir) slide_name = "#{next_num}_#{slide_name}" end if slide_dir filename = "#{slide_dir}/#{slide_name}.md" else filename = "#{slide_name}.md" end filename end
# File lib/showoff_utils.rb, line 341 def self.determine_size_and_source(code) size = "" source = "" if code source,lines,width = read_code(code) size = adjust_size(lines,width) end [size,source] end
# File lib/showoff_utils.rb, line 387 def self.determine_title(title,slide_name,code) if blank?(title) title = slide_name title = File.basename(code) if code end title = "Title here" if blank?(title) title end
Finds the next number in the given dir to name a slide as the last slide in the dir.
# File lib/showoff_utils.rb, line 377 def self.find_next_number(slide_dir) slide_dir ||= '.' max = Dir.glob("#{slide_dir}/*.md").collect do |f| next unless f =~ /^#{slide_dir}\/(\d+)/ $1.to_i end.compact.max || 0 sprintf("%02d", max+1) end
# File lib/showoff_utils.rb, line 586 def self.get_config_option(dir, option, default = nil) index = File.join(dir, ShowoffUtils.presentation_config_file) if File.exist?(index) data = JSON.parse(File.read(index)) if data.is_a?(Hash) if default.is_a?(Hash) default.merge(data[option] || {}) else data[option] || default end end else default end end
generate a static version of the site into the gh-pages branch
# File lib/showoff_utils.rb, line 221 def self.github Showoff.do_static(nil) FileUtils.touch 'static/.nojekyll' `git add -f static` sha = `git write-tree`.chomp tree_sha = `git rev-parse #{sha}:static`.chomp `git read-tree HEAD` # reset staging to last-commit ghp_sha = `git rev-parse gh-pages 2>/dev/null`.chomp extra = ghp_sha != 'gh-pages' ? "-p #{ghp_sha}" : '' commit_sha = `echo 'static presentation' | git commit-tree #{tree_sha} #{extra}`.chomp `git update-ref refs/heads/gh-pages #{commit_sha}` end
Setup presentation to run on Heroku
name - String containing heroku name force - boolean if .gems/Gemfile and config.ru should be overwritten if they don’t exist password - String containing password to protect your heroku site; nil means no password protection
# File lib/showoff_utils.rb, line 189 def self.heroku(name, force = false, password = nil) modified_something = create_gems_file(HEROKU_GEMS_FILE, !password.nil?, force, lambda{ |gem| "gem '#{gem}'" }, lambda{ "source :rubygems" }) create_file_if_needed(HEROKU_PROCFILE,force) do |file| modified_something = true file.puts 'bundle exec thin start -R config.ru -e production -p $PORT' end create_file_if_needed(HEROKU_CONFIG_FILE,force) do |file| modified_something = true file.puts 'require "showoff"' file.puts 'require "showoff/version"' if password.nil? file.puts 'run Showoff.new' else file.puts 'require "rack"' file.puts 'showoff_app = Showoff.new' file.puts 'protected_showoff = Rack::Auth::Basic.new(showoff_app) do |username, password|' file.puts "\tpassword == '#{password}'" file.puts 'end' file.puts 'run protected_showoff' end end modified_something end
# File lib/showoff_utils.rb, line 102 def self.info(config, json = false) ShowoffUtils.presentation_config_file = config showoff = Showoff.new! content = showoff.slides(false, true) dom = Nokogiri::HTML(content) data = {} data['files'] = self.showoff_slide_files('.') data['images'] = dom.css('img').map {|img| img[:src].sub(/.\/image\/+/,'') } data['styles'] = Dir.glob('*.css') data['scripts'] = Dir.glob('*.js') # now grep through the styles and identify referenced images data['styles'].each do |style| File.readlines(style).each do |line| next unless line =~ /url\(\'(\S+)\'\)/ data['images'] << $1 end end if json puts JSON.pretty_generate(data) else data.each do |key, list| puts "#{key.capitalize}:" list.each {|file| puts " * #{file}" } puts end end end
# File lib/showoff_utils.rb, line 609 def self.lang(source_file) ext = File.extname(source_file).gsub(/^\./,'') EXTENSIONS[ext] || ext end
Makes a slide as a string.
- title
-
title of the slide
- classes
-
any “classes” to include, such as ‘smaller’, ‘transition’, etc.
- content
-
slide content. Currently, if this is an array, it will make a bullet list. Otherwise the string value of this will be put in the slide as-is
# File lib/showoff_utils.rb, line 263 def self.make_slide(title,classes="",content=nil) slide = "<!SLIDE #{classes}>\n" slide << "# #{title}\n" slide << "\n" if content if content.kind_of? Array content.each { |x| slide << "* #{x.to_s}\n" } else slide << content.to_s end end slide end
Helper method to parse a comma separated options string and stores the result in a dictionrary
Example:
"tpl=hpi,title=Over the rainbow" will be stored as { "tpl" => "hpi", "title" => "Over the rainbow" }
# File lib/showoff_utils.rb, line 13 def self.parse_options(option_string="") result = {} if option_string option_string.split(",").each do |element| pair = element.split("=") result[pair[0]] = pair.size > 1 ? pair[1] : nil end end result end
# File lib/showoff_utils.rb, line 559 def self.pause_msg(dir = '.') get_config_option(dir, 'pause_msg', 'PAUSED') end
# File lib/showoff_utils.rb, line 26 def self.presentation_config_file @presentation_config_file ||= 'showoff.json' end
# File lib/showoff_utils.rb, line 30 def self.presentation_config_file=(filename) @presentation_config_file = filename end
Reads the code from the source file, returning the code, indented for markdown, as well as the number of lines and the width of the largest line
# File lib/showoff_utils.rb, line 413 def self.read_code(source_file) code = " @@@ #{lang(source_file)}\n" lines = 0 width = 0 File.open(source_file) do |code_file| code_file.readlines.each do |line| code += " #{line}" lines += 1 width = line.length if line.length > width end end [code,lines,width] end
# File lib/showoff_utils.rb, line 477 def self.showoff_legacy_sections(dir, data) # each entry in sections can be: # - "filename.md" # - { "section": "filename.md" } # - { "section": "directory" } # - { "section": [ "array.md, "of.md, "files.md"] } # - { "include": "sections.json" } sections = {} counters = {} lastpath = nil data.map do |entry| next entry if entry.is_a? String next nil unless entry.is_a? Hash next entry['section'] if entry.include? 'section' section = nil if entry.include? 'include' file = entry['include'] path = File.dirname(file) data = JSON.parse(File.read(file)) if data.is_a? Array if path == '.' section = data else section = data.map do |source| "#{path}/#{source}" end end end end section end.flatten.compact.each do |entry| # We do this in two passes simply because most of it was already done # and I don't want to waste time on legacy functionality. # Normalize to a proper path from presentation root filename = File.expand_path(entry).sub(/^#{dir}\//, '') # and then strip out the locale directory, if there is one filename.sub!(/^(locales\/[\w-]+\/)/, '') locale = $1 if File.directory? filename path = entry sections[path] ||= [] Dir.glob("#{filename}/**/*.md").sort.each do |slidefile| fullpath = locale.nil? ? slidefile : "#{locale}/#{slidefile}" sections[path] << fullpath end else path = File.dirname(entry) # this lastpath business allows us to reference files in a directory that aren't # necessarily contiguous. if path != lastpath counters[path] ||= 0 counters[path] += 1 end # now record the last path we've seen lastpath = path # and if there are more than one disparate occurences of path, add a counter to this string path = "#{path} (#{counters[path]})" unless counters[path] == 1 sections[path] ||= [] sections[path] << filename end end sections end
# File lib/showoff_utils.rb, line 577 def self.showoff_markdown(dir = ".") get_config_option(dir, "markdown", "redcarpet") end
# File lib/showoff_utils.rb, line 572 def self.showoff_pdf_options(dir = '.') opts = get_config_option(dir, 'pdf_options', {:page_size => 'Letter', :orientation => 'Landscape', :print_media_type => true}) Hash[opts.map {|k, v| [k.to_sym, v]}] # keys must be symbols end
# File lib/showoff_utils.rb, line 581 def self.showoff_renderer_options(dir = '.', default_options = MarkdownConfig::defaults(dir)) opts = get_config_option(dir, showoff_markdown(dir), default_options) Hash[opts.map {|k, v| [k.to_sym, v]}] if opts # keys must be symbols end
# File lib/showoff_utils.rb, line 427 def self.showoff_sections(dir, data = nil, logger = nil) unless logger logger = Logger.new(STDOUT) logger.level = Logger::WARN end begin unless data index = File.join(dir, ShowoffUtils.presentation_config_file) data = JSON.parse(File.read(index)) rescue ["."] # default boring showoff.json end logger.debug data if data.is_a?(Hash) # dup so we don't overwrite the original data structure and make it impossible to re-localize sections = data['sections'].dup else sections = data.dup end if sections.is_a? Array sections = showoff_legacy_sections(dir, sections) elsif sections.is_a? Hash raise "Named sections are unsupported on Ruby versions less than 1.9." if RUBY_VERSION.start_with? '1.8' sections.each do |key, value| next if value.is_a? Array cwd = File.expand_path(dir) path = File.dirname(value) data = JSON.parse(File.read(value)) raise "The section file #{value} must contain an array of filenames." unless data.is_a? Array # get relative paths to each slide in the array sections[key] = data.map do |filename| File.expand_path("#{path}/#{filename}").sub(/^#{cwd}\//, '') end end else raise "The `sections` key must be an Array or Hash, not a #{sections.class}." end rescue => e logger.error "There was a problem with the presentation file #{index}" logger.error e.message logger.debug e.backtrace sections = {} end sections end
# File lib/showoff_utils.rb, line 550 def self.showoff_slide_files(dir, data = nil, logger = nil) data = showoff_sections(dir, data, logger) data.map { |key, value| value }.flatten end
# File lib/showoff_utils.rb, line 555 def self.showoff_title(dir = '.') get_config_option(dir, 'name', "Presentation") end
# File lib/showoff_utils.rb, line 69 def self.skeleton(config) if config unless File.expand_path(config) == File.expand_path(File.basename(config),'.') FileUtils.cp(config, '.') end ShowoffUtils.presentation_config_file = File.basename(config) end # Create asset directories FileUtils.mkdir_p('_files/share') FileUtils.mkdir_p('_images') self.showoff_slide_files('.').each do |filename| next if File.exist? filename puts "Creating: #{filename}" if filename.downcase.end_with? '.md' FileUtils.mkdir_p File.dirname(filename) File.open(filename, 'w+') do |f| if filename =~ /section/i # kind of looks like a section slide f.puts make_slide("#{filename.sub(/\.md$/, '')}", "center subsection") else f.puts make_slide("#{filename.sub(/\.md$/, '')}") end end else FileUtils.mkdir_p filename end end end
just update the repo in cwd
# File lib/showoff_utils.rb, line 252 def self.update(verbose=false) puts "Updating presentation repository..." if verbose system('git', 'pull') end
# File lib/showoff_utils.rb, line 133 def self.validate(config) showoff = Showoff.new!(:pres_file => config) validators = showoff.settings.showoff_config['validators'] || {} files = [] errors = [] # get a list of actual filenames files = self.showoff_slide_files('.') files.each do |filename| unless File.exist? filename errors << "Missing path: #{filename}" next end if filename.downcase.end_with? '.md' print '.' showoff.get_code_from_slide(filename.sub('.md',''), 'all', false).each_with_index do |block, index| lang, code, classes = block validator = validators[lang] if classes.include? 'no-validate' print '-' next elsif validator # write out a tempfile because many validators require files to work with Tempfile.open('showoff-validation') do |f| File.write(f.path, code) unless system("#{validator} #{f.path}", :out => File::NULL, :err => File::NULL) print 'F' errors << "Invalid #{lang} code on #{filename} [#{index}]" end end end end end end puts puts "Found #{errors.size} errors." unless errors.empty? errors.each { |err| puts " * #{err}" } exit 1 end end
# File lib/showoff_utils.rb, line 351 def self.write_file(filename,slide) File.open(filename,'w') do |file| file.puts slide end puts "Wrote #{filename}" end