aboutsummaryrefslogtreecommitdiff
path: root/docs/_plugins/include_example.rb
blob: 0f4184c7462be190a45ba29cd8e86c092f3b6ff9 (plain) (blame)
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
42
43
44
45
46
47
48
49
50
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
96
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'liquid'
require 'pygments'

module Jekyll
  class IncludeExampleTag < Liquid::Tag
    
    def initialize(tag_name, markup, tokens)
      @markup = markup
      super
    end
 
    def render(context)
      site = context.registers[:site]
      config_dir = (site.config['code_dir'] || '../examples/src/main').sub(/^\//,'')
      @code_dir = File.join(site.source, config_dir)

      clean_markup = @markup.strip
      @file = File.join(@code_dir, clean_markup)
      @lang = clean_markup.split('.').last

      code = File.open(@file).read.encode("UTF-8")
      code = select_lines(code)
 
      Pygments.highlight(code, :lexer => @lang)
    end
 
    # Trim the code block so as to have the same indention, regardless of their positions in the
    # code file.
    def trim_codeblock(lines)
      # Select the minimum indention of the current code block.
      min_start_spaces = lines
        .select { |l| l.strip.size !=0 }
        .map { |l| l[/\A */].size }
        .min

      lines.map { |l| l[min_start_spaces .. -1] }
    end

    # Select lines according to labels in code. Currently we use "$example on$" and "$example off$"
    # as labels. Note that code blocks identified by the labels should not overlap.
    def select_lines(code)
      lines = code.each_line.to_a

      # Select the array of start labels from code.
      startIndices = lines
        .each_with_index
        .select { |l, i| l.include? "$example on$" }
        .map { |l, i| i }

      # Select the array of end labels from code.
      endIndices = lines
        .each_with_index
        .select { |l, i| l.include? "$example off$" }
        .map { |l, i| i }

      raise "Start indices amount is not equal to end indices amount, please check the code." \
        unless startIndices.size == endIndices.size

      raise "No code is selected by include_example, please check the code." \
        if startIndices.size == 0

      # Select and join code blocks together, with a space line between each of two continuous
      # blocks.
      lastIndex = -1
      result = ""
      startIndices.zip(endIndices).each do |start, endline|
        raise "Overlapping between two example code blocks are not allowed." if start <= lastIndex
        raise "$example on$ should not be in the same line with $example off$." if start == endline
        lastIndex = endline
        range = Range.new(start + 1, endline - 1)
        result += trim_codeblock(lines[range]).join
        result += "\n"
      end
      result
    end
  end
end

Liquid::Template.register_tag('include_example', Jekyll::IncludeExampleTag)