BigW Consortium Gitlab

html_gitlab.rb 5.78 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
require 'cgi'

module Rouge
  module Formatters
    class HTMLGitlab < Rouge::Formatter
      tag 'html_gitlab'

      # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
      #
      # [+nowrap+]          If set to True, don't wrap the output at all, not
      #                     even inside a <tt><pre></tt> tag (default: false).
      # [+cssclass+]        CSS class for the wrapping <tt><div></tt> tag
      #                     (default: 'highlight').
      # [+linenos+]         If set to 'table', output line numbers as a table
      #                     with two cells, one containing the line numbers,
      #                     the other the whole code. This is copy paste friendly,
      #                     but may cause alignment problems with some browsers
      #                     or fonts. If set to 'inline', the line numbers will
      #                     be integrated in the <tt><pre></tt> tag that contains
      #                     the code (default: nil).
      # [+linenostart+]     The line number for the first line (default: 1).
      # [+lineanchors+]     If set to true the formatter will wrap each output
      #                     line in an anchor tag with a name of L-linenumber.
      #                     This allows easy linking to certain lines
      #                     (default: false).
      # [+lineanchorsid+]   If lineanchors is true the name of the anchors can
      #                     be changed with lineanchorsid to e.g. foo-linenumber
      #                     (default: 'L').
      # [+anchorlinenos+]   If set to true, will wrap line numbers in <tt><a></tt>
      #                     tags. Used in combination with linenos and lineanchors
      #                     (default: false).
      # [+inline_theme+]    Inline CSS styles for the <pre> tag (default: false).
      def initialize(
          nowrap: false,
          cssclass: 'highlight',
          linenos: nil,
          linenostart: 1,
          lineanchors: false,
          lineanchorsid: 'L',
          anchorlinenos: false,
          inline_theme: nil
42
      )
43 44 45 46 47 48 49
        @nowrap = nowrap
        @cssclass = cssclass
        @linenos = linenos
        @linenostart = linenostart
        @lineanchors = lineanchors
        @lineanchorsid = lineanchorsid
        @anchorlinenos = anchorlinenos
50
        @inline_theme = Theme.find(inline_theme).new if inline_theme.is_a?(String)
51 52 53 54 55 56 57 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
      end

      def render(tokens)
        case @linenos
        when 'table'
          render_tableized(tokens)
        when 'inline'
          render_untableized(tokens)
        else
          render_untableized(tokens)
        end
      end

      alias_method :format, :render

      private

      def render_untableized(tokens)
        data = process_tokens(tokens)

        html = ''
        html << "<pre class=\"#{@cssclass}\"><code>" unless @nowrap
        html << wrap_lines(data[:code])
        html << "</code></pre>\n" unless @nowrap
        html
      end

      def render_tableized(tokens)
        data = process_tokens(tokens)

        html = ''
        html << "<div class=\"#{@cssclass}\">" unless @nowrap
        html << '<table><tbody>'
        html << "<td class=\"linenos\"><pre>"
        html << wrap_linenos(data[:numbers])
        html << '</pre></td>'
        html << "<td class=\"lines\"><pre><code>"
        html << wrap_lines(data[:code])
        html << '</code></pre></td>'
        html << '</tbody></table>'
        html << '</div>' unless @nowrap
        html
      end

      def process_tokens(tokens)
Stan Hu committed
96 97
        rendered = []
        current_line = ''
98 99

        tokens.each do |tok, val|
Stan Hu committed
100 101 102 103 104 105 106 107 108 109 110
          # In the case of multi-line values (e.g. comments), we need to apply
          # styling to each line since span elements are inline.
          val.lines.each do |line|
            stripped = line.chomp
            current_line << span(tok, stripped)

            if line.end_with?("\n")
              rendered << current_line
              current_line = ''
            end
          end
111 112
        end

Stan Hu committed
113 114 115 116
        # Add leftover text
        rendered << current_line if current_line.present?

        num_lines = rendered.size
117 118 119 120 121 122 123 124 125 126 127 128 129 130
        numbers = (@linenostart..num_lines + @linenostart - 1).to_a

        { numbers: numbers, code: rendered }
      end

      def wrap_linenos(numbers)
        if @anchorlinenos
          numbers.map! do |number|
            "<a href=\"##{@lineanchorsid}#{number}\">#{number}</a>"
          end
        end
        numbers.join("\n")
      end

Stan Hu committed
131
      def wrap_lines(lines)
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
        if @lineanchors
          lines = lines.each_with_index.map do |line, index|
            number = index + @linenostart

            if @linenos == 'inline'
              "<a name=\"L#{number}\"></a>" \
              "<span class=\"linenos\">#{number}</span>" \
              "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
              '</span>'
            else
              "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
              '</span>'
            end
          end
          lines.join("\n")
        else
          if @linenos == 'inline'
            lines = lines.each_with_index.map do |line, index|
              number = index + @linenostart
              "<span class=\"linenos\">#{number}</span>#{line}"
            end
            lines.join("\n")
          else
Stan Hu committed
155
            lines.join("\n")
156 157 158 159 160 161 162 163 164 165 166 167 168
          end
        end
      end

      def span(tok, val)
        # http://stackoverflow.com/a/1600584/2587286
        val = CGI.escapeHTML(val)

        if tok.shortname.empty?
          val
        else
          if @inline_theme
            rules = @inline_theme.style_for(tok).rendered_rules
Stan Hu committed
169
            "<span style=\"#{rules.to_a.join(';')}\"#{val}</span>"
170
          else
Stan Hu committed
171
            "<span class=\"#{tok.shortname}\">#{val}</span>"
172 173 174 175 176 177
          end
        end
      end
    end
  end
end