Back to home page

OSCL-LXR

 
 

    


0001 #
0002 # Licensed to the Apache Software Foundation (ASF) under one or more
0003 # contributor license agreements.  See the NOTICE file distributed with
0004 # this work for additional information regarding copyright ownership.
0005 # The ASF licenses this file to You under the Apache License, Version 2.0
0006 # (the "License"); you may not use this file except in compliance with
0007 # the License.  You may obtain a copy of the License at
0008 #
0009 #    http://www.apache.org/licenses/LICENSE-2.0
0010 #
0011 # Unless required by applicable law or agreed to in writing, software
0012 # distributed under the License is distributed on an "AS IS" BASIS,
0013 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 # See the License for the specific language governing permissions and
0015 # limitations under the License.
0016 #
0017 
0018 require 'liquid'
0019 require 'rouge'
0020 
0021 module Jekyll
0022   class IncludeExampleTag < Liquid::Tag
0023 
0024     def initialize(tag_name, markup, tokens)
0025       @markup = markup
0026       super
0027     end
0028 
0029     def render(context)
0030       site = context.registers[:site]
0031       config_dir = '../examples/src/main'
0032       @code_dir = File.join(site.source, config_dir)
0033 
0034       clean_markup = @markup.strip
0035 
0036       parts = clean_markup.strip.split(' ')
0037       if parts.length > 1 then
0038         @snippet_label = ':' + parts[0]
0039         snippet_file = parts[1]
0040       else
0041         @snippet_label = ''
0042         snippet_file = parts[0]
0043       end
0044 
0045       @file = File.join(@code_dir, snippet_file)
0046       @lang = snippet_file.split('.').last
0047 
0048       begin
0049         code = File.open(@file).read.encode("UTF-8")
0050       rescue => e
0051         # We need to explicitly exit on exceptions here because Jekyll will silently swallow
0052         # them, leading to silent build failures (see https://github.com/jekyll/jekyll/issues/5104)
0053         puts(e)
0054         puts(e.backtrace)
0055         exit 1
0056       end
0057       code = select_lines(code).strip
0058 
0059       formatter = Rouge::Formatters::HTMLPygments.new(Rouge::Formatters::HTML.new)
0060       lexer = Rouge::Lexer.find(@lang)
0061       rendered_code = formatter.format(lexer.lex(code))
0062 
0063       hint = "<div><small>Find full example code at " \
0064         "\"examples/src/main/#{snippet_file}\" in the Spark repo.</small></div>"
0065 
0066       rendered_code + hint
0067     end
0068 
0069     # Trim the code block so as to have the same indention, regardless of their positions in the
0070     # code file.
0071     def trim_codeblock(lines)
0072       # Select the minimum indention of the current code block.
0073       min_start_spaces = lines
0074         .select { |l| l.strip.size !=0 }
0075         .map { |l| l[/\A */].size }
0076         .min
0077 
0078       lines.map { |l| l.strip.size == 0 ? l : l[min_start_spaces .. -1] }
0079     end
0080 
0081     # Select lines according to labels in code. Currently we use "$example on$" and "$example off$"
0082     # as labels. Note that code blocks identified by the labels should not overlap.
0083     def select_lines(code)
0084       lines = code.each_line.to_a
0085 
0086       # Select the array of start labels from code.
0087       startIndices = lines
0088         .each_with_index
0089         .select { |l, i| l.include? "$example on#{@snippet_label}$" }
0090         .map { |l, i| i }
0091 
0092       # Select the array of end labels from code.
0093       endIndices = lines
0094         .each_with_index
0095         .select { |l, i| l.include? "$example off#{@snippet_label}$" }
0096         .map { |l, i| i }
0097 
0098       raise "Start indices amount is not equal to end indices amount, see #{@file}." \
0099         unless startIndices.size == endIndices.size
0100 
0101       raise "No code is selected by include_example, see #{@file}." \
0102         if startIndices.size == 0
0103 
0104       # Select and join code blocks together, with a space line between each of two continuous
0105       # blocks.
0106       lastIndex = -1
0107       result = ""
0108       startIndices.zip(endIndices).each do |start, endline|
0109         raise "Overlapping between two example code blocks are not allowed, see #{@file}." \
0110             if start <= lastIndex
0111         raise "$example on$ should not be in the same line with $example off$, see #{@file}." \
0112             if start == endline
0113         lastIndex = endline
0114         range = Range.new(start + 1, endline - 1)
0115         trimmed = trim_codeblock(lines[range])
0116         # Filter out possible example tags of overlapped labels.
0117         taggs_filtered = trimmed.select { |l| !l.include? '$example ' }
0118         result += taggs_filtered.join
0119         result += "\n"
0120       end
0121       result
0122     end
0123   end
0124 end
0125 
0126 Liquid::Template.register_tag('include_example', Jekyll::IncludeExampleTag)