# ~*~ encoding: utf-8 ~*~ require 'spirit/render/errors' require 'spirit/render/sanitize' require 'spirit/render/templates' module Spirit module Render # HTML Renderer for Genie Markup Language, which is just GitHub Flavored # Markdown with Embedded YAML for describing problems. Designed for use # with Redcarpet. # @see Spirit::Tilt::Template # @see http://github.github.com/github-flavored-markdown/ class HTML < ::Redcarpet::Render::HTML @sanitize = Sanitize.new class << self; attr_reader :sanitize end # Paragraphs that start and end with '---' are treated as embedded YAML # and are parsed for questions/answers. PROBLEM_REGEX = /^"""$(.*?)^"""$/m # Paragraphs that only contain images are rendered with {Spirit::Render::Image}. IMAGE_REGEX = /\A\s*]+>\s*\z/m # Renderer configuration options. CONFIGURATION = { hard_wrap: true, no_styles: true, } # Creates a new HTML renderer. # @param [Hash] options described in the RedCarpet documentation. def initialize(options={}) super CONFIGURATION.merge options @nav, @headers = Navigation.new, Headers.new @prob, @img = 0, 0 # indices for Problem #, Figure # @name = options.delete(:name) || 'untitled' end # Pygmentizes code blocks. # @param [String] code code block contents # @param [String] marker name of language, for syntax highlighting # @return [String] highlighted code def block_code(code, marker) #language, type, id = (marker || 'text').split ':' #highlighted = Albino.colorize code, language language, _, _ = (marker || 'text').split ':' Albino.colorize code, language # TODO #case type #when 'demo', 'test' # executable id: id, raw: code, colored: highlighted #else highlighted end end # Detects block images and renders them as such. # @return [String] rendered html def paragraph(text) case text when IMAGE_REGEX then block_image(text) else p(text) end rescue RenderError => e # fall back to paragraph Spirit.logger.warn e.message p(text) end # Increases all header levels by one and keeps a navigation bar. # @return [String] rendered html def header(text, level) html, name = h(text, level += 1) @nav.append(text, name) if level == 2 html end # Runs a first pass through the document to look for problem blocks. # @param [String] document markdown document def preprocess(document) document.gsub(PROBLEM_REGEX) { |yaml| problem $1 } end # Sanitizes the final document. # @param [String] document html document # @return [String] sanitized document def postprocess(document) HTML.sanitize.clean(@nav.render + document.force_encoding('utf-8')) end private # Prepares an executable code block. # @option opts [String] id author-supplied ID # @option opts [String] raw code to execute # @option opts [String] colored syntax highlighted code # @return [String] #def executable(opts) # opts[:colored] + @exe.render(Object.new, id: opts[:id], raw: opts[:raw]) #end # Prepares a problem form. Returns +yaml+ if the given text does not # contain valid yaml markup for a problem. # @param [String] yaml YAML markup # @return [String] rendered HTML def problem(yaml) problem = Problem.parse(yaml) Spirit.logger.record :problem, "ID: #{problem.id}" problem.save!(@name) and problem.render(index: @prob += 1) rescue RenderError yaml end # Prepares a block image. Raises {RenderError} if the given text does not # contain a valid image block. # @param [String] text markdown text # @return [String] rendered HTML def block_image(text) Image.parse(text).render(index: @img += 1) end # Wraps the given text with header tags. # @return [String] rendered HTML # @return [String] anchor name def h(text, level) header = @headers.add(text, level) return header.render, header.name end # Wraps the given text with paragraph tags. # @param [String] text paragraph text # @return [String] rendered html def p(text) '

' + text + '

' end end end end