From 00ff84d3c02e551cb7c4be3f71e1836e9f5abce2 Mon Sep 17 00:00:00 2001 From: Stefan Tatschner Date: Sun, 28 Jun 2015 20:42:41 +0200 Subject: Add HTMLGitlab formatter This custom formatter for rouge is needed to generate HTML output specifically for gitlab. Since its usecase is mostly suitable for gitlab it had been rejected upstream: https://github.com/jneen/rouge/pull/268 Thanks a lot to @stanhu, @jneen and @tsigo for review! --- app/helpers/blob_helper.rb | 2 +- lib/rouge/formatters/html_gitlab.rb | 168 ++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 lib/rouge/formatters/html_gitlab.rb diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index f6263818055..77d99140c43 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -8,7 +8,7 @@ module BlobHelper ) begin - @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content) + @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe rescue @lexer = Rouge::Lexers::PlainText diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb new file mode 100644 index 00000000000..485af6832d7 --- /dev/null +++ b/lib/rouge/formatters/html_gitlab.rb @@ -0,0 +1,168 @@ +require 'cgi' + +module Rouge + module Formatters + class HTMLGitlab < Rouge::Formatter + tag 'html_gitlab' + + # Creates a new Rouge::Formatter::HTMLGitlab instance. + # + # [+nowrap+] If set to True, don't wrap the output at all, not + # even inside a
 tag (default: false).
+      # [+cssclass+]        CSS class for the wrapping 
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
 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 
+      #                     tags. Used in combination with linenos and lineanchors
+      #                     (default: false).
+      # [+inline_theme+]    Inline CSS styles for the 
 tag (default: false).
+      def initialize(
+          nowrap: false,
+          cssclass: 'highlight',
+          linenos: nil,
+          linenostart: 1,
+          lineanchors: false,
+          lineanchorsid: 'L',
+          anchorlinenos: false,
+          inline_theme: nil
+        )
+        @nowrap = nowrap
+        @cssclass = cssclass
+        @linenos = linenos
+        @linenostart = linenostart
+        @lineanchors = lineanchors
+        @lineanchorsid = lineanchorsid
+        @anchorlinenos = anchorlinenos
+        @inline_theme = Theme.find(@inline_theme).new if @inline_theme.is_a?(String)
+      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 << "
" unless @nowrap
+        html << wrap_lines(data[:code])
+        html << "
\n" unless @nowrap + html + end + + def render_tableized(tokens) + data = process_tokens(tokens) + + html = '' + html << "
" unless @nowrap + html << '' + html << "' + html << "' + html << '
"
+        html << wrap_linenos(data[:numbers])
+        html << '
"
+        html << wrap_lines(data[:code])
+        html << '
' + html << '
' unless @nowrap + html + end + + def process_tokens(tokens) + num_lines = 0 + last_val = '' + rendered = '' + + tokens.each do |tok, val| + last_val = val + num_lines += val.scan(/\n/).size + rendered << span(tok, val) + end + + numbers = (@linenostart..num_lines + @linenostart - 1).to_a + + { numbers: numbers, code: rendered } + end + + def wrap_linenos(numbers) + if @anchorlinenos + numbers.map! do |number| + "
#{number}" + end + end + numbers.join("\n") + end + + def wrap_lines(rendered) + if @lineanchors + lines = rendered.split("\n") + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + + if @linenos == 'inline' + "" \ + "#{number}" \ + "#{line}" \ + '' + else + "#{line}" \ + '' + end + end + lines.join("\n") + else + if @linenos == 'inline' + lines = rendered.split("\n") + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + "#{number}#{line}" + end + lines.join("\n") + else + rendered + 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 + "#{val}" + else + "#{val}" + end + end + end + end + end +end -- cgit v1.2.1