spirit 0.2 → 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/lib/spirit.rb +8 -10
 - data/lib/spirit/constants.rb +17 -7
 - data/lib/spirit/document.rb +4 -9
 - data/lib/spirit/errors.rb +0 -1
 - data/lib/spirit/logger.rb +5 -6
 - data/lib/spirit/manifest.rb +4 -5
 - data/lib/spirit/render.rb +0 -2
 - data/lib/spirit/render/errors.rb +0 -4
 - data/lib/spirit/render/html.rb +17 -117
 - data/lib/spirit/render/processable.rb +78 -0
 - data/lib/spirit/render/processors.rb +15 -0
 - data/lib/spirit/render/processors/base.rb +40 -0
 - data/lib/spirit/render/processors/block_image_processor.rb +49 -0
 - data/lib/spirit/render/processors/headers_processor.rb +41 -0
 - data/lib/spirit/render/processors/layout_processor.rb +28 -0
 - data/lib/spirit/render/processors/math_processor.rb +102 -0
 - data/lib/spirit/render/processors/problems_processor.rb +76 -0
 - data/lib/spirit/render/processors/pygments_processor.rb +22 -0
 - data/lib/spirit/render/processors/sanitize_processor.rb +86 -0
 - data/lib/spirit/render/templates.rb +1 -3
 - data/lib/spirit/render/templates/header.rb +2 -3
 - data/lib/spirit/render/templates/image.rb +6 -13
 - data/lib/spirit/render/templates/multi.rb +9 -10
 - data/lib/spirit/render/templates/navigation.rb +4 -5
 - data/lib/spirit/render/templates/problem.rb +24 -28
 - data/lib/spirit/render/templates/short.rb +2 -3
 - data/lib/spirit/render/templates/table.rb +2 -2
 - data/lib/spirit/render/templates/template.rb +12 -8
 - data/lib/spirit/version.rb +1 -2
 - data/views/header.haml +1 -1
 - data/views/img.haml +2 -2
 - data/views/layout.haml +27 -0
 - data/views/multi.haml +10 -14
 - data/views/nav.haml +2 -2
 - data/views/short.haml +6 -11
 - data/views/table.haml +20 -26
 - metadata +36 -57
 - data/lib/spirit/render/sanitize.rb +0 -90
 - data/views/exe.haml +0 -5
 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  class BlockImageProcessor < Base
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    process :paragraph, :filter
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    # Paragraphs that only contain images are rendered with
         
     | 
| 
      
 10 
     | 
    
         
            +
                    # {Spirit::Render::Image}.
         
     | 
| 
      
 11 
     | 
    
         
            +
                    IMAGE_REGEX = /\A\s*<img[^<>]+>\s*\z/m
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def initialize(*args)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @image = 0
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    # Detects block images and renders them as such.
         
     | 
| 
      
 18 
     | 
    
         
            +
                    # @return [String] rendered html
         
     | 
| 
      
 19 
     | 
    
         
            +
                    def filter(text)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      case text
         
     | 
| 
      
 21 
     | 
    
         
            +
                      when IMAGE_REGEX then block_image(text)
         
     | 
| 
      
 22 
     | 
    
         
            +
                      else p(text) end
         
     | 
| 
      
 23 
     | 
    
         
            +
                    rescue RenderError => e # fall back to paragraph
         
     | 
| 
      
 24 
     | 
    
         
            +
                      Spirit.logger.warn e.message
         
     | 
| 
      
 25 
     | 
    
         
            +
                      p(text)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    private
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    # Prepares a block image. Raises {RenderError} if the given text does
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # not contain a valid image block.
         
     | 
| 
      
 32 
     | 
    
         
            +
                    # @param  [String] text           markdown text
         
     | 
| 
      
 33 
     | 
    
         
            +
                    # @return [String] rendered HTML
         
     | 
| 
      
 34 
     | 
    
         
            +
                    def block_image(text)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      Image.parse(text).render(index: @image += 1)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    # Wraps the given text with paragraph tags.
         
     | 
| 
      
 39 
     | 
    
         
            +
                    # @param [String] text            paragraph text
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # @return [String] rendered html
         
     | 
| 
      
 41 
     | 
    
         
            +
                    def p(text)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      '<p>' + text + '</p>'
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  # In-charge of headers, navigation bar, and nesting.
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Depends on renderer#navigation and renderer#nesting
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class HeadersProcessor < Base
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    process :header, :header
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    def initialize(renderer, *args)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      renderer.nesting = @nesting = []
         
     | 
| 
      
 13 
     | 
    
         
            +
                      renderer.navigation = @navigation = Navigation.new
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @headers = Headers.new
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    # Increases all header levels by one and keeps a navigation bar.
         
     | 
| 
      
 18 
     | 
    
         
            +
                    # @return [String] rendered html
         
     | 
| 
      
 19 
     | 
    
         
            +
                    def header(text, level)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      h = headers.add(text, level += 1)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      navigation.append(text, h.name) if level == 2
         
     | 
| 
      
 22 
     | 
    
         
            +
                      nest h
         
     | 
| 
      
 23 
     | 
    
         
            +
                      h.render
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    attr_accessor :headers, :navigation, :nesting
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    # Maintains the +nesting+ array.
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # @param [Header] h
         
     | 
| 
      
 32 
     | 
    
         
            +
                    def nest(h)
         
     | 
| 
      
 33 
     | 
    
         
            +
                      nesting.pop until nesting.empty? or h.level > nesting.last.level
         
     | 
| 
      
 34 
     | 
    
         
            +
                      nesting << h
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  # Post-processes a layout in HAML.
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class LayoutProcessor < Base
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                    TEMPLATE = File.join VIEWS, 'layout.haml'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    attr_accessor :engine, :renderer
         
     | 
| 
      
 11 
     | 
    
         
            +
                    process :postprocess, :render
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def initialize(renderer, *args)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      template  = File.read TEMPLATE
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @engine   = Haml::Engine.new template, HAML_CONFIG
         
     | 
| 
      
 16 
     | 
    
         
            +
                      @renderer = renderer
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    def render(document)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      engine.render renderer,
         
     | 
| 
      
 21 
     | 
    
         
            +
                        content: document.force_encoding('utf-8')
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,102 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  # Pre-processes math markup in latex.
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Adapted from
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # http://www.math.union.edu/~dpvc/transfer/mathjax/mathjax-editing.js
         
     | 
| 
      
 8 
     | 
    
         
            +
                  class MathProcessor < Base
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    process :preprocess,  :filter
         
     | 
| 
      
 11 
     | 
    
         
            +
                    process :postprocess, :replace
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    # Pattern for delimiters and special symbols; used to search for math in
         
     | 
| 
      
 14 
     | 
    
         
            +
                    # the document.
         
     | 
| 
      
 15 
     | 
    
         
            +
                    SPLIT = /(\$\$?|                        (?# 1 or 2 dollars)
         
     | 
| 
      
 16 
     | 
    
         
            +
                              \\(?:begin|end)\{[a-z]*\*?\}| (?# latex envs)
         
     | 
| 
      
 17 
     | 
    
         
            +
                              \\[\\{}$]|                    (?# \\ \{ \})
         
     | 
| 
      
 18 
     | 
    
         
            +
                              [{}]|                         (?# braces )
         
     | 
| 
      
 19 
     | 
    
         
            +
                              (?:\n\s*)+|                   (?# newlines w. optional spaces)
         
     | 
| 
      
 20 
     | 
    
         
            +
                              @@\d+@@)                      (?# @@digits@@)
         
     | 
| 
      
 21 
     | 
    
         
            +
                           /ix
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    def initialize(renderer, document)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      reset_counters
         
     | 
| 
      
 25 
     | 
    
         
            +
                      @math   = []
         
     | 
| 
      
 26 
     | 
    
         
            +
                      @blocks = document.gsub(/\r\n?/, "\n").split SPLIT
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    # Replace math in document with +@@index@@+.
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # @return [String] document
         
     | 
| 
      
 31 
     | 
    
         
            +
                    def filter(document)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      blocks.each_with_index do |block, i|
         
     | 
| 
      
 33 
     | 
    
         
            +
                        case
         
     | 
| 
      
 34 
     | 
    
         
            +
                        when '@' == block[0]
         
     | 
| 
      
 35 
     | 
    
         
            +
                          process_pseudo_marker block, i
         
     | 
| 
      
 36 
     | 
    
         
            +
                        when @start
         
     | 
| 
      
 37 
     | 
    
         
            +
                          process_potential_close block, i
         
     | 
| 
      
 38 
     | 
    
         
            +
                        else
         
     | 
| 
      
 39 
     | 
    
         
            +
                          process_potential_start block, i
         
     | 
| 
      
 40 
     | 
    
         
            +
                        end
         
     | 
| 
      
 41 
     | 
    
         
            +
                      end
         
     | 
| 
      
 42 
     | 
    
         
            +
                      process_math if @last
         
     | 
| 
      
 43 
     | 
    
         
            +
                      blocks.join
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    def replace(document)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      document.gsub(/@@(\d+)@@/) { math[$1.to_i] }
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    private
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    attr_reader :blocks, :math
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    def process_pseudo_marker(block, i)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      blocks[i] = "@@#{math.length}@@"
         
     | 
| 
      
 56 
     | 
    
         
            +
                      math << block
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    def process_potential_start(block, i)
         
     | 
| 
      
 60 
     | 
    
         
            +
                      case block
         
     | 
| 
      
 61 
     | 
    
         
            +
                      when '$', '$$'
         
     | 
| 
      
 62 
     | 
    
         
            +
                        @start, @close, @braces = i, block, 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                      when /\A\\begin\{([a-z]*\*?)\}/
         
     | 
| 
      
 64 
     | 
    
         
            +
                        @start, @close, @braces = i, "\\end{#{$1}}", 0
         
     | 
| 
      
 65 
     | 
    
         
            +
                      end
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    def process_potential_close(block, i)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      case block
         
     | 
| 
      
 70 
     | 
    
         
            +
                      when @close     # process if braces match
         
     | 
| 
      
 71 
     | 
    
         
            +
                        @braces.zero? ? process_math(i) : @last = i
         
     | 
| 
      
 72 
     | 
    
         
            +
                      when /\n.*\n/   # don't go over double line breaks
         
     | 
| 
      
 73 
     | 
    
         
            +
                        process_math if @last
         
     | 
| 
      
 74 
     | 
    
         
            +
                        reset_counters
         
     | 
| 
      
 75 
     | 
    
         
            +
                      when '{' then @braces += 1 # balance braces
         
     | 
| 
      
 76 
     | 
    
         
            +
                      when '}' then @braces -= 1 if @braces > 0
         
     | 
| 
      
 77 
     | 
    
         
            +
                      end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    # Collects the math from blocks +i+ through +j+, replaces &, <, and > by
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # named entities, and resets that math positions.
         
     | 
| 
      
 82 
     | 
    
         
            +
                    def process_math(last = @last)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      block = blocks[@start..last].join
         
     | 
| 
      
 84 
     | 
    
         
            +
                        .gsub(/&/, '&')
         
     | 
| 
      
 85 
     | 
    
         
            +
                        .gsub(/</, '<')
         
     | 
| 
      
 86 
     | 
    
         
            +
                        .gsub(/>/, '>')
         
     | 
| 
      
 87 
     | 
    
         
            +
                      last.downto(@start+1) { |k| blocks[k] = '' }
         
     | 
| 
      
 88 
     | 
    
         
            +
                      blocks[@start] = "@@#{math.length}@@"
         
     | 
| 
      
 89 
     | 
    
         
            +
                      math << block
         
     | 
| 
      
 90 
     | 
    
         
            +
                      reset_counters
         
     | 
| 
      
 91 
     | 
    
         
            +
                    end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                    def reset_counters
         
     | 
| 
      
 94 
     | 
    
         
            +
                      @start = @close = @last = nil
         
     | 
| 
      
 95 
     | 
    
         
            +
                      @braces = 0
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  # Pre-processes problem markup in YAML.
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Dependent on renderer#problems and renderer#nesting.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class ProblemsProcessor < Base
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    # Paragraphs that start and end with +"""+ are treated as embedded YAML
         
     | 
| 
      
 10 
     | 
    
         
            +
                    # and are parsed for questions/answers.
         
     | 
| 
      
 11 
     | 
    
         
            +
                    REGEX = /^"""$(.*?)^"""$/m
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    MARKER = /\A<!-- %%(\d+)%% -->\z/
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    attr_reader :problems, :solutions
         
     | 
| 
      
 16 
     | 
    
         
            +
                    delegate :size, :count, :each, :each_with_index, to: :problems
         
     | 
| 
      
 17 
     | 
    
         
            +
                    process  :preprocess, :filter
         
     | 
| 
      
 18 
     | 
    
         
            +
                    process  :block_html, :replace
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    def initialize(renderer, *args)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @renderer  = renderer
         
     | 
| 
      
 22 
     | 
    
         
            +
                      @problems  = []
         
     | 
| 
      
 23 
     | 
    
         
            +
                      @solutions = []
         
     | 
| 
      
 24 
     | 
    
         
            +
                      renderer.problems = self
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    # Replaces YAML markup in document with <!-- %%index%% -->
         
     | 
| 
      
 28 
     | 
    
         
            +
                    # @return [String] document
         
     | 
| 
      
 29 
     | 
    
         
            +
                    def filter(document)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      document.gsub(REGEX) { problem $1 }
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    def replace(html)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      return html unless is_marker? html
         
     | 
| 
      
 35 
     | 
    
         
            +
                      replace_nesting html, renderer.nesting
         
     | 
| 
      
 36 
     | 
    
         
            +
                      ''
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    private
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    attr_reader :renderer
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    # Update associated problem with nesting information.
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # @return [void]
         
     | 
| 
      
 45 
     | 
    
         
            +
                    def replace_nesting(html, nesting)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      match = html.strip.match MARKER
         
     | 
| 
      
 47 
     | 
    
         
            +
                      prob  = problems[match[1].to_i]
         
     | 
| 
      
 48 
     | 
    
         
            +
                      prob.nesting = nesting.dup
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    # @return [Boolean] true iff the given html corresponds to a problem
         
     | 
| 
      
 52 
     | 
    
         
            +
                    #   marker
         
     | 
| 
      
 53 
     | 
    
         
            +
                    def is_marker?(html)
         
     | 
| 
      
 54 
     | 
    
         
            +
                      html.strip =~ MARKER and problems[$1.to_i]
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    # If the given text contains valid YAML, returns a marker. Otherwise,
         
     | 
| 
      
 58 
     | 
    
         
            +
                    # returns the original text.
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # @param  [String] text            candidate YAML markup
         
     | 
| 
      
 60 
     | 
    
         
            +
                    # @return [String] text or marker
         
     | 
| 
      
 61 
     | 
    
         
            +
                    def problem(text)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      p = Problem.parse(text)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      p.id = problems.size
         
     | 
| 
      
 64 
     | 
    
         
            +
                      self.problems  << p
         
     | 
| 
      
 65 
     | 
    
         
            +
                      self.solutions << {digest: p.digest, solution: Marshal.dump(p.answer)}
         
     | 
| 
      
 66 
     | 
    
         
            +
                      Spirit.logger.record :problem, "ID: #{p.id}"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    rescue RenderError
         
     | 
| 
      
 68 
     | 
    
         
            +
                      text
         
     | 
| 
      
 69 
     | 
    
         
            +
                    else "<!-- %%#{p.id}%% -->"
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'pygments'
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 4 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  class PygmentsProcessor < Base
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                    process :block_code, :highlight_code
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    # Pygmentizes code blocks.
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # @param [String] code        code block contents
         
     | 
| 
      
 12 
     | 
    
         
            +
                    # @param [String] marker      name of language
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # @return [String] highlighted code
         
     | 
| 
      
 14 
     | 
    
         
            +
                    def highlight_code(code, marker)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      Pygments.highlight(code, lexer: marker || 'text')
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,86 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sanitize'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Spirit
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Render
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Processors
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  # Encapsulates sanitization options.
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @see https://github.com/github/gollum/blob/master/lib/gollum/sanitization.rb
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class SanitizeProcessor < Base
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    process :postprocess, :clean
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    # white-listed elements
         
     | 
| 
      
 14 
     | 
    
         
            +
                    ELEMENTS = [
         
     | 
| 
      
 15 
     | 
    
         
            +
                      'a', 'abbr', 'acronym', 'address', 'area', 'b', 'big',
         
     | 
| 
      
 16 
     | 
    
         
            +
                      'blockquote', 'br', 'button', 'caption', 'center', 'cite',
         
     | 
| 
      
 17 
     | 
    
         
            +
                      'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir',
         
     | 
| 
      
 18 
     | 
    
         
            +
                      'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1',
         
     | 
| 
      
 19 
     | 
    
         
            +
                      'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input',
         
     | 
| 
      
 20 
     | 
    
         
            +
                      'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu',
         
     | 
| 
      
 21 
     | 
    
         
            +
                      'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
         
     | 
| 
      
 22 
     | 
    
         
            +
                      'select', 'small', 'span', 'strike', 'strong', 'sub',
         
     | 
| 
      
 23 
     | 
    
         
            +
                      'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th',
         
     | 
| 
      
 24 
     | 
    
         
            +
                      'thead', 'tr', 'tt', 'u', 'ul', 'var'
         
     | 
| 
      
 25 
     | 
    
         
            +
                    ].freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    # white-listed attributes
         
     | 
| 
      
 28 
     | 
    
         
            +
                    ATTRIBUTES = {
         
     | 
| 
      
 29 
     | 
    
         
            +
                      'a'   => ['href', 'name', 'data-magellan-destination', 'data-action'],
         
     | 
| 
      
 30 
     | 
    
         
            +
                      'input' => ['data-max-page'],
         
     | 
| 
      
 31 
     | 
    
         
            +
                      'dd'  => ['data-magellan-arrival'],
         
     | 
| 
      
 32 
     | 
    
         
            +
                      'dl'  => ['data-magellan-expedition'],
         
     | 
| 
      
 33 
     | 
    
         
            +
                      'img' => ['src'],
         
     | 
| 
      
 34 
     | 
    
         
            +
                      :all  => ['abbr', 'accept', 'accept-charset',
         
     | 
| 
      
 35 
     | 
    
         
            +
                                'accesskey', 'action', 'align', 'alt', 'axis',
         
     | 
| 
      
 36 
     | 
    
         
            +
                                'border', 'cellpadding', 'cellspacing', 'char',
         
     | 
| 
      
 37 
     | 
    
         
            +
                                'charoff', 'class', 'charset', 'checked', 'cite',
         
     | 
| 
      
 38 
     | 
    
         
            +
                                'clear', 'cols', 'colspan', 'color',
         
     | 
| 
      
 39 
     | 
    
         
            +
                                'compact', 'coords', 'datetime', 'dir',
         
     | 
| 
      
 40 
     | 
    
         
            +
                                'disabled', 'enctype', 'for', 'frame',
         
     | 
| 
      
 41 
     | 
    
         
            +
                                'headers', 'height', 'hreflang',
         
     | 
| 
      
 42 
     | 
    
         
            +
                                'hspace', 'id', 'ismap', 'label', 'lang',
         
     | 
| 
      
 43 
     | 
    
         
            +
                                'longdesc', 'maxlength', 'media', 'method',
         
     | 
| 
      
 44 
     | 
    
         
            +
                                'multiple', 'name', 'nohref', 'noshade',
         
     | 
| 
      
 45 
     | 
    
         
            +
                                'nowrap', 'prompt', 'readonly', 'rel', 'rev',
         
     | 
| 
      
 46 
     | 
    
         
            +
                                'rows', 'rowspan', 'rules', 'scope',
         
     | 
| 
      
 47 
     | 
    
         
            +
                                'selected', 'shape', 'size', 'span',
         
     | 
| 
      
 48 
     | 
    
         
            +
                                'start', 'summary', 'tabindex', 'target',
         
     | 
| 
      
 49 
     | 
    
         
            +
                                'title', 'type', 'usemap', 'valign', 'value',
         
     | 
| 
      
 50 
     | 
    
         
            +
                                'vspace', 'width']
         
     | 
| 
      
 51 
     | 
    
         
            +
                    }.freeze
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    # white-listed protocols
         
     | 
| 
      
 54 
     | 
    
         
            +
                    PROTOCOLS = {
         
     | 
| 
      
 55 
     | 
    
         
            +
                      'a'   => {'href' => ['http', 'https', 'mailto', 'ftp', 'irc', 'apt', :relative]},
         
     | 
| 
      
 56 
     | 
    
         
            +
                      'img' => {'src'  => ['http', 'https', :relative]}
         
     | 
| 
      
 57 
     | 
    
         
            +
                    }.freeze
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    # elements to remove (incl. contents)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    REMOVE_CONTENTS = %w[script style].freeze
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    # attributes to add to elements
         
     | 
| 
      
 63 
     | 
    
         
            +
                    ADD_ATTRIBUTES = { 'a' => {'rel' => 'nofollow'} }.freeze
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    def self.config
         
     | 
| 
      
 66 
     | 
    
         
            +
                      { elements:         ELEMENTS.dup,
         
     | 
| 
      
 67 
     | 
    
         
            +
                        attributes:       ATTRIBUTES.dup,
         
     | 
| 
      
 68 
     | 
    
         
            +
                        protocols:        PROTOCOLS.dup,
         
     | 
| 
      
 69 
     | 
    
         
            +
                        add_attributes:   ADD_ATTRIBUTES.dup,
         
     | 
| 
      
 70 
     | 
    
         
            +
                        remove_contents:  REMOVE_CONTENTS.dup,
         
     | 
| 
      
 71 
     | 
    
         
            +
                        allow_comments:   false
         
     | 
| 
      
 72 
     | 
    
         
            +
                      }
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                    class_attribute :sanitizer
         
     | 
| 
      
 76 
     | 
    
         
            +
                    self.sanitizer = ::Sanitize.new config
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    def clean(document)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      sanitizer.clean document
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,6 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # ~*~ encoding: utf-8 ~*~
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
1 
     | 
    
         
             
            # require all template types
         
     | 
| 
       4 
     | 
    
         
            -
            %w 
     | 
| 
      
 2 
     | 
    
         
            +
            %w[template header image problem multi short table navigation].each do |type|
         
     | 
| 
       5 
3 
     | 
    
         
             
              require File.join 'spirit', 'render', 'templates', type
         
     | 
| 
       6 
4 
     | 
    
         
             
            end
         
     | 
| 
         @@ -29,9 +29,8 @@ module Spirit 
     | 
|
| 
       29 
29 
     | 
    
         
             
                class Header < Template
         
     | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                  # Name of template file for rendering headers.
         
     | 
| 
       32 
     | 
    
         
            -
                   
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                  attr_reader :name
         
     | 
| 
      
 32 
     | 
    
         
            +
                  self.template = 'header.haml'
         
     | 
| 
      
 33 
     | 
    
         
            +
                  attr_reader :name, :text, :level
         
     | 
| 
       35 
34 
     | 
    
         | 
| 
       36 
35 
     | 
    
         
             
                  # Creates a new header.
         
     | 
| 
       37 
36 
     | 
    
         
             
                  # @param [String] text          header text
         
     | 
| 
         @@ -1,26 +1,20 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # ~*~ encoding: utf-8 ~*~
         
     | 
| 
       2 
1 
     | 
    
         
             
            require 'nokogiri'
         
     | 
| 
       3 
2 
     | 
    
         | 
| 
       4 
3 
     | 
    
         
             
            module Spirit
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
4 
     | 
    
         
             
              module Render
         
     | 
| 
       7 
5 
     | 
    
         | 
| 
       8 
6 
     | 
    
         
             
                # Renders a block image with a figure number.
         
     | 
| 
       9 
7 
     | 
    
         
             
                class Image < Template
         
     | 
| 
       10 
8 
     | 
    
         | 
| 
       11 
9 
     | 
    
         
             
                  # <img ...>
         
     | 
| 
       12 
     | 
    
         
            -
                  IMAGE_TAG = 'img'
         
     | 
| 
      
 10 
     | 
    
         
            +
                  IMAGE_TAG = 'img'.freeze
         
     | 
| 
       13 
11 
     | 
    
         | 
| 
       14 
12 
     | 
    
         
             
                  # Name of template file for rendering block images
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                  class << self
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                    # Parses the given text for a block image.
         
     | 
| 
       20 
     | 
    
         
            -
                    def parse(text)
         
     | 
| 
       21 
     | 
    
         
            -
                      Image.new text
         
     | 
| 
       22 
     | 
    
         
            -
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self.template = 'img.haml'
         
     | 
| 
       23 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
                  # Parses the given text for a block image.
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def self.parse(text)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    Image.new text
         
     | 
| 
       24 
18 
     | 
    
         
             
                  end
         
     | 
| 
       25 
19 
     | 
    
         | 
| 
       26 
20 
     | 
    
         
             
                  # Creates a new image.
         
     | 
| 
         @@ -37,7 +31,7 @@ module Spirit 
     | 
|
| 
       37 
31 
     | 
    
         | 
| 
       38 
32 
     | 
    
         
             
                  # Parses the given HTML, or raise {RenderError} if it is invalid.
         
     | 
| 
       39 
33 
     | 
    
         
             
                  def parse_or_raise
         
     | 
| 
       40 
     | 
    
         
            -
                    frag = Nokogiri::HTML::DocumentFragment.parse(@html)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    frag = Nokogiri::HTML::DocumentFragment.parse(@html.strip)
         
     | 
| 
       41 
35 
     | 
    
         
             
                    if 1 == frag.children.count and
         
     | 
| 
       42 
36 
     | 
    
         
             
                      node = frag.children.first and
         
     | 
| 
       43 
37 
     | 
    
         
             
                      node.is_a? Nokogiri::XML::Element and
         
     | 
| 
         @@ -50,5 +44,4 @@ module Spirit 
     | 
|
| 
       50 
44 
     | 
    
         
             
                end
         
     | 
| 
       51 
45 
     | 
    
         | 
| 
       52 
46 
     | 
    
         
             
              end
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
47 
     | 
    
         
             
            end
         
     |