Class: ShowOff

Inherits:
Sinatra::Application
  • Object
show all
Defined in:
lib/showoff.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app = nil) ⇒ ShowOff



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/showoff.rb', line 58

def initialize(app=nil)
  super(app)
  @logger = Logger.new(STDOUT)
  @logger.formatter = proc { |severity,datetime,progname,msg| "#{progname} #{msg}\n" }
  @logger.level = settings.verbose ? Logger::DEBUG : Logger::WARN

  @review  = settings.review
  @execute = settings.execute

  dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
  @logger.debug(dir)

  showoff_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
  settings.pres_dir ||= Dir.pwd
  @root_path = "."

  # Load up the default keymap, then merge in any customizations
  keymapfile   = File.expand_path(File.join('~', '.showoff', 'keymap.json'))
  @keymap      = Keymap.default
  @keymap.merge! JSON.parse(File.read(keymapfile)) rescue {}

  settings.pres_dir = File.expand_path(settings.pres_dir)
  if (settings.pres_file)
    ShowOffUtils.presentation_config_file = settings.pres_file
  end

  # Load configuration for page size and template from the
  # configuration JSON file
  if File.exists?(ShowOffUtils.presentation_config_file)
    showoff_json = JSON.parse(File.read(ShowOffUtils.presentation_config_file))
    settings.showoff_config = showoff_json

    # Set options for encoding, template and page size
    settings.encoding = showoff_json["encoding"]
    settings.page_size = showoff_json["page-size"] || "Letter"
    settings.pres_template = showoff_json["templates"]
  end

  # code execution timeout
  settings.showoff_config['timeout'] ||= 15

  # highlightjs syntax style
  @highlightStyle = settings.showoff_config['highlight'] || 'default'

  # variables used for building section numbering and title
  @slide_count   = 0
  @section_major = 0
  @section_minor = 0
  @section_title = settings.showoff_config['name'] rescue 'Showoff Presentation'

  @logger.debug settings.pres_template

  @cached_image_size = {}
  @logger.debug settings.pres_dir
  @pres_name = settings.pres_dir.split('/').pop
  require_ruby_files

  # Default asset path
  @asset_path = "./"

  # Create stats directory
  FileUtils.mkdir settings.statsdir unless File.directory? settings.statsdir

  # Page view time accumulator. Tracks how often slides are viewed by the audience
  begin
    @@counter = JSON.parse(File.read("#{settings.statsdir}/#{settings.viewstats}"))

    # port old format stats
    unless @@counter.has_key? 'user_agents'
      @@counter = { 'user_agents' => {}, 'pageviews' => @@counter }
    end
  rescue
    @@counter = { 'user_agents' => {}, 'pageviews' => {} }
  end

  # keeps track of form responses. In memory to avoid concurrence issues.
  begin
    @@forms = JSON.parse(File.read("#{settings.statsdir}/#{settings.forms}"))
  rescue
    @@forms = Hash.new
  end

  @@downloads = Hash.new # Track downloadable files
  @@cookie    = nil      # presenter cookie. Identifies the presenter for control messages
  @@current   = Hash.new # The current slide that the presenter is viewing
  @@cache     = nil      # Cache slide content for subsequent hits

  # flush stats to disk periodically
  Thread.new do
    loop do
      sleep 30
      ShowOff.flush
    end
  end

  # Initialize Markdown Configuration
  MarkdownConfig::setup(settings.pres_dir)
end

Instance Attribute Details

#cached_image_sizeObject (readonly)

Returns the value of attribute cached_image_size.



31
32
33
# File 'lib/showoff.rb', line 31

def cached_image_size
  @cached_image_size
end

Class Method Details

.do_static(args) ⇒ Object



1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
# File 'lib/showoff.rb', line 1048

def self.do_static(args)
  args ||= [] # handle nil arguments
  what   = args[0] || "index"
  opt    = args[1]

  # Sinatra now aliases new to new!
  # https://github.com/sinatra/sinatra/blob/v1.3.3/lib/sinatra/base.rb#L1369
  showoff = ShowOff.new!

  name = showoff.instance_variable_get(:@pres_name)
  path = showoff.instance_variable_get(:@root_path)
  logger = showoff.instance_variable_get(:@logger)

  if what == 'supplemental'
    data = showoff.send(what, opt, true)
  else
    data = showoff.send(what, true)
  end

  if data.is_a?(File)
    FileUtils.cp(data.path, "#{name}.pdf")
  else
    out = File.expand_path("#{path}/static")
    # First make a directory
    FileUtils.makedirs(out)
    # Then write the html
    file = File.new("#{out}/index.html", "w")
    file.puts(data)
    file.close
    # Now copy all the js and css
    my_path = File.join( File.dirname(__FILE__), '..', 'public')
    ["js", "css"].each { |dir|
      FileUtils.copy_entry("#{my_path}/#{dir}", "#{out}/#{dir}", false, false, true)
    }
    # And copy the directory
    Dir.glob("#{my_path}/#{name}/*").each { |subpath|
      base = File.basename(subpath)
      next if "static" == base
      next unless File.directory?(subpath) || base.match(/\.(css|js)$/)
      FileUtils.copy_entry(subpath, "#{out}/#{base}")
    }

    # Set up file dir
    file_dir = File.join(out, 'file')
    FileUtils.makedirs(file_dir)
    pres_dir = showoff.settings.pres_dir

    # ..., copy all user-defined styles and javascript files
    Dir.glob("#{pres_dir}/*.{css,js}").each { |path|
      FileUtils.copy(path, File.join(file_dir, File.basename(path)))
    }

    # ... and copy all needed image files
    [/img src=[\"\'].\/file\/(.*?)[\"\']/, /style=[\"\']background: url\(\'file\/(.*?)'/].each do |regex|
      data.scan(regex).flatten.each do |path|
        dir = File.dirname(path)
        FileUtils.makedirs(File.join(file_dir, dir))
        begin
          FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
        rescue Errno::ENOENT => e
          puts "Missing source file: #{path}"
        end
      end
    end
    # copy images from css too
    Dir.glob("#{pres_dir}/*.css").each do |css_path|
      File.open(css_path) do |file|
        data = file.read
        data.scan(/url\([\"\']?(?!https?:\/\/)(.*?)[\"\']?\)/).flatten.each do |path|
          path.gsub!(/(\#.*)$/, '') # get rid of the anchor
          path.gsub!(/(\?.*)$/, '') # get rid of the query
          logger.debug path
          dir = File.dirname(path)
          FileUtils.makedirs(File.join(file_dir, dir))
          begin
            FileUtils.copy(File.join(pres_dir, path), File.join(file_dir, path))
          rescue Errno::ENOENT => e
            puts "Missing source file: #{path}"
          end
        end
      end
    end
  end
end

.flushObject

save stats to disk



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/showoff.rb', line 158

def self.flush
  if defined?(@@counter) and not @@counter.empty?
    File.open("#{settings.statsdir}/#{settings.viewstats}", 'w') do |f|
      if settings.verbose then
        f.write(JSON.pretty_generate(@@counter))
      else
        f.write(@@counter.to_json)
      end
    end
  end

  if defined?(@@forms) and not @@forms.empty?
    File.open("#{settings.statsdir}/#{settings.forms}", 'w') do |f|
      if settings.verbose then
        f.write(JSON.pretty_generate(@@forms))
      else
        f.write(@@forms.to_json)
      end
    end
  end
end

.pres_dir_currentObject



180
181
182
183
# File 'lib/showoff.rb', line 180

def self.pres_dir_current
  opt = {:pres_dir => Dir.pwd}
  ShowOff.set opt
end

Instance Method Details

#authorized?Boolean



1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
# File 'lib/showoff.rb', line 1152

def authorized?
  if not settings.showoff_config.has_key? 'password'
    # if no password is set, then default to allowing access to localhost
    request.env['REMOTE_HOST'] == 'localhost' or request.ip == '127.0.0.1'
  else
    auth   ||= Rack::Auth::Basic::Request.new(request.env)
    user     = settings.showoff_config['user'] || ''
    password = settings.showoff_config['password']
    auth.provided? && auth.basic? && auth.credentials && auth.credentials == [user, password]
  end
end

#get_code_from_slide(path, index) ⇒ Object

Load a slide file from disk, parse it and return the text of a code block by index



1134
1135
1136
1137
1138
1139
1140
1141
1142
# File 'lib/showoff.rb', line 1134

def get_code_from_slide(path, index)
  slide = "#{path}.md"
  return unless File.exists? slide

  html = process_markdown(slide, File.read(slide), {})
  doc  = Nokogiri::HTML::DocumentFragment.parse(html)

  return doc.css('code.execute')[index.to_i].text rescue 'Invalid code block index'
end

#guidObject



1164
1165
1166
1167
# File 'lib/showoff.rb', line 1164

def guid
  # this is a terrifyingly simple GUID generator
  (0..15).to_a.map{|a| rand(16).to_s(16)}.join
end

#protected!Object

Basic auth boilerplate



1145
1146
1147
1148
1149
1150
# File 'lib/showoff.rb', line 1145

def protected!
  unless authorized?
    response['WWW-Authenticate'] = %(Basic realm="#{@title}: Protected Area")
    throw(:halt, [401, "Not authorized\n"])
  end
end

#require_ruby_filesObject



185
186
187
# File 'lib/showoff.rb', line 185

def require_ruby_files
  Dir.glob("#{settings.pres_dir}/*.rb").map { |path| require path }
end


1169
1170
1171
# File 'lib/showoff.rb', line 1169

def valid_cookie
  (request.cookies['presenter'] == @@cookie)
end