Skip to content
This repository
Browse code

New Improved BackTicks - now with more classes

Refactored (if you consider the correct generation of all our contents
as tests) the entire BackTick logic to allow for more modular parsing of
code blocks.
  • Loading branch information...
commit 1035986711e2a214bc36cf88ce20e91f44591a87 1 parent dd037d6
Franklin Webber authored November 27, 2012

Showing 1 changed file with 219 additions and 36 deletions. Show diff stats Hide diff stats

  1. 255  plugins/backtick_code_block.rb
255  plugins/backtick_code_block.rb
@@ -2,43 +2,226 @@
2 2
 require './plugins/pygments_code'
3 3
 
4 4
 module BacktickCodeBlock
5  
-  include HighlightCode
6  
-  AllOptions = /([^\s]+)\s+(.+?)(https?:\/\/\S+)\s*(.+)?/i
7  
-  LangCaption = /([^\s]+)\s*(.+)?/i
8 5
   def render_code_block(input)
9  
-    @options = nil
10  
-    @caption = nil
11  
-    @lang = nil
12  
-    @url = nil
13  
-    @title = nil
14  
-    input.force_encoding("ASCII-8BIT").gsub(/^`{3} *([^\n]+)?\n(.+?)\n`{3}/m) do
15  
-      @options = $1 || ''
16  
-      str = $2
17  
-
18  
-      if @options =~ AllOptions
19  
-        @lang = $1
20  
-        @caption = "<figcaption><span>#{$2}</span><a href='#{$3}'>#{$4 || 'link'}</a></figcaption>"
21  
-      elsif @options =~ LangCaption
22  
-        @lang = $1
23  
-        @caption = "<figcaption><span>#{$2}</span></figcaption>"
24  
-      end
25  
-
26  
-      if str.match(/\A( {4}|\t)/)
27  
-        str = str.gsub(/^( {4}|\t)/, '')
28  
-      end
29  
-      if @lang.nil? || @lang == 'plain'
30  
-        code = tableize_code(str.gsub('<','&lt;').gsub('>','&gt;'))
31  
-        "<figure class='code'>#{@caption}#{code}</figure>"
32  
-      else
33  
-        if @lang.include? "-raw"
34  
-          raw = "``` #{@options.sub('-raw', '')}\n"
35  
-          raw += str
36  
-          raw += "\n```\n"
37  
-        else
38  
-          code = highlight(str, @lang)
39  
-          "<figure class='code'>#{@caption}#{code}</figure>"
40  
-        end
41  
-      end
  6
+    input.force_encoding("ASCII-8BIT").gsub(back_ticks_format_regex) do |back_tick_string|
  7
+      code_block_from(back_tick_string).to_html
42 8
     end
43 9
   end
  10
+
  11
+  def back_ticks_format_regex
  12
+    @back_ticks_format_regex ||= /^`{3} *(?<options>[^\n]+)?\n(?<code>.+?)\n`{3}/m
  13
+  end
  14
+
  15
+  def code_block_formats
  16
+    @code_block_formats ||= [ PlainCodeBlock, RawCodeBlock, HighlightedCodeBlock ]
  17
+  end
  18
+
  19
+  def code_block_from(back_tick_string)
  20
+    match = back_ticks_format_regex.match back_tick_string
  21
+    options = CodeBlockOptions.from match['options']
  22
+    code = strip_leading_spaces match['code']
  23
+
  24
+    format = code_block_formats.find {|format| format.match? options }
  25
+    format.new(code,options)
  26
+  end
  27
+
  28
+  def strip_leading_spaces(string)
  29
+    string.match(/\A( {4}|\t)/) ? string.gsub(/^( {4}|\t)/, '') : string
  30
+  end
  31
+
  32
+  class CodeBlockOptions
  33
+
  34
+    #
  35
+    # Parses the string into an instance of CodeBlockOptions which contains
  36
+    # metadata about the code block.
  37
+    #
  38
+    # @param [String] string a string from the top of a code block that defines
  39
+    #   the language about the code and caption information.
  40
+    #
  41
+    # @return [CodeBlockString]
  42
+    #
  43
+    def self.from(string)
  44
+      caption = CodeBlockCaption.new *parse(string)
  45
+      new caption.language, caption
  46
+    end
  47
+
  48
+    # The language of the code block
  49
+    attr_reader :language
  50
+
  51
+    # The caption for the code block
  52
+    attr_reader :caption
  53
+
  54
+
  55
+    def initialize(language,caption)
  56
+      @language = language
  57
+      @caption = caption
  58
+    end
  59
+
  60
+    private
  61
+
  62
+    #
  63
+    # Parses a code block string and returns an array components found within
  64
+    # the specified string.
  65
+    #
  66
+    # @note this implementation simply returns the result of the matching
  67
+    #   format in the specified order that they are matched. If a new format
  68
+    #   is created which changes that format, this method will need to be
  69
+    #   updated.
  70
+    #
  71
+    def self.parse(string)
  72
+      formats.find { |format| format.match(string.to_s) }.match(string.to_s).to_a
  73
+    end
  74
+
  75
+    #
  76
+    # The different acceptable formats of the code block string. All new formats
  77
+    # should be appended before the last one None.
  78
+    #
  79
+    #
  80
+    def self.formats
  81
+      [ LanguageCaptionAndLink, LanguageAndCaption, None ]
  82
+    end
  83
+
  84
+    LanguageCaptionAndLink = /(?<language>[^\s]+)\s+(?<caption>.+?)(?<link>https?:\/\/\S+)\s*(?<link_title>.+)?/i
  85
+    LanguageAndCaption = /(?<language>[^\s]+)\s*(?<caption>.+)?/
  86
+    None = //
  87
+
  88
+  end
  89
+
  90
+  #
  91
+  # Each code block has a caption whether they specify one or not. That's how
  92
+  # things work around here.
  93
+  #
  94
+  class CodeBlockCaption
  95
+
  96
+    attr_reader :raw_caption, :language, :caption, :link_url, :link_title
  97
+
  98
+    #
  99
+    # This initializer is built to receive the input from the CodeBlockOptions
  100
+    # parse method. The simplicity there makes for an initializer designed in
  101
+    # a way only a mother of a MatchData, converted to an array, could love.
  102
+    #
  103
+    def initialize(raw_options = nil, language = nil,caption = nil,link_url = nil,link_title = nil)
  104
+      @raw_caption = raw_caption
  105
+      @language = language.to_s
  106
+      @caption = caption
  107
+      @link_url = link_url
  108
+      @link_title = link_title || 'link'
  109
+    end
  110
+
  111
+    def to_s
  112
+      raw_caption
  113
+    end
  114
+
  115
+    def to_html
  116
+      return "" unless caption
  117
+
  118
+      html = "<figcaption><span>#{caption}</span>"
  119
+      html += "<a href='#{link_url}'>#{link_title}</a>" if link_url
  120
+      html += "</figcaption>"
  121
+    end
  122
+  end
  123
+
  124
+
  125
+  #
  126
+  # When the code block language has not been specified or is plain then you
  127
+  # have a plain old code block.
  128
+  #
  129
+  # @example Plain Old Code Block
  130
+  #
  131
+  #       ```
  132
+  #       This is PlainsVille? I thought it would be more exciting.
  133
+  #       ```
  134
+  #
  135
+  class PlainCodeBlock
  136
+    include HighlightCode
  137
+
  138
+    def self.match?(options)
  139
+      options.language.to_s =~ /(^$|plain)/
  140
+    end
  141
+
  142
+    attr_reader :codeblock, :caption
  143
+
  144
+    def initialize(codeblock,options)
  145
+      @codeblock = codeblock
  146
+      @caption = options.caption
  147
+    end
  148
+
  149
+    def to_html
  150
+      clean_code = codeblock.gsub('<','&lt;').gsub('>','&gt;')
  151
+      final_code = tableize_code clean_code
  152
+      "<figure class='code'>#{caption.to_html}#{final_code}</figure>"
  153
+    end
  154
+
  155
+  end
  156
+
  157
+
  158
+  #
  159
+  # When the code block language has the suffix of -raw, then the block
  160
+  # will be displayed in raw format without the normal HTML formatting.
  161
+  #
  162
+  # @example Raw Ruby Code Block
  163
+  #
  164
+  #       ```ruby-raw
  165
+  #       class WelcomeToMondayNightRaw
  166
+  #         def self.raw ; puts "rawness" ; end
  167
+  #       end
  168
+  #       ```
  169
+  #
  170
+  class RawCodeBlock
  171
+
  172
+    def self.match?(options)
  173
+      options.language.to_s =~ /-raw$/
  174
+    end
  175
+
  176
+    attr_reader :codeblock, :caption
  177
+
  178
+    def initialize(codeblock,options)
  179
+      @codeblock = codeblock
  180
+      @caption = options.caption
  181
+    end
  182
+
  183
+    def to_html
  184
+      raw = "``` #{caption.to_s.sub('-raw', '')}\n"
  185
+      raw += raw_code
  186
+      raw += "\n```\n"
  187
+    end
  188
+
  189
+  end
  190
+
  191
+
  192
+  #
  193
+  # This is your standard highlighted code block that uses the language to
  194
+  # generate a code block so spectacular in appeal and design it is bound to
  195
+  # impress all those that gaze upon it.
  196
+  #
  197
+  # @example Highlighted Code Block
  198
+  #
  199
+  #     ```ruby Gemfile
  200
+  #       gem 'this'
  201
+  #       gem 'that'
  202
+  #       gem 'clever-name-for-the-other-thing'
  203
+  #     ```
  204
+  #
  205
+  class HighlightedCodeBlock
  206
+    include HighlightCode
  207
+
  208
+    def self.match?(options)
  209
+      options.language.to_s !~ /(^$|plain)/
  210
+    end
  211
+
  212
+    attr_reader :codeblock, :caption, :language
  213
+
  214
+    def initialize(codeblock,options)
  215
+      @codeblock = codeblock
  216
+      @caption = options.caption
  217
+      @language = options.language
  218
+    end
  219
+
  220
+    def to_html
  221
+      syntax_highlighted_code = highlight(codeblock,language)
  222
+      "<figure class='code'>#{caption.to_html}#{syntax_highlighted_code}</figure>"
  223
+    end
  224
+
  225
+  end
  226
+
44 227
 end

0 notes on commit 1035986

Please sign in to comment.
Something went wrong with that request. Please try again.