summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/excerpt.rb
blob: 63185203a1338eb9cebe1ba2bfeca481ed8ad52c (plain)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# frozen_string_literal: true

module Jekyll
  class Excerpt
    extend Forwardable

    attr_accessor :content, :doc, :ext
    attr_writer   :output

    def_delegators :@doc,
                   :site, :name, :ext, :extname,
                   :collection, :related_posts, :type,
                   :coffeescript_file?, :yaml_file?,
                   :url, :next_doc, :previous_doc

    private :coffeescript_file?, :yaml_file?

    # Initialize this Excerpt instance.
    #
    # doc - The Document.
    #
    # Returns the new Excerpt.
    def initialize(doc)
      self.doc = doc
      self.content = extract_excerpt(doc.content)
    end

    # Fetch YAML front-matter data from related doc, without layout key
    #
    # Returns Hash of doc data
    def data
      @data ||= doc.data.dup
      @data.delete("layout")
      @data.delete("excerpt")
      @data
    end

    def trigger_hooks(*); end

    # 'Path' of the excerpt.
    #
    # Returns the path for the doc this excerpt belongs to with #excerpt appended
    def path
      File.join(doc.path, "#excerpt")
    end

    # 'Relative Path' of the excerpt.
    #
    # Returns the relative_path for the doc this excerpt belongs to with #excerpt appended
    def relative_path
      @relative_path ||= File.join(doc.relative_path, "#excerpt")
    end

    # Check if excerpt includes a string
    #
    # Returns true if the string passed in
    def include?(something)
      output&.include?(something) || content.include?(something)
    end

    # The UID for this doc (useful in feeds).
    # e.g. /2008/11/05/my-awesome-doc
    #
    # Returns the String UID.
    def id
      "#{doc.id}#excerpt"
    end

    def to_s
      output || content
    end

    def to_liquid
      Jekyll::Drops::ExcerptDrop.new(self)
    end

    # Returns the shorthand String identifier of this doc.
    def inspect
      "<#{self.class} id=#{id}>"
    end

    def output
      @output ||= Renderer.new(doc.site, self, site.site_payload).run
    end

    def place_in_layout?
      false
    end

    def render_with_liquid?
      return false if data["render_with_liquid"] == false

      !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
    end

    protected

    # Internal: Extract excerpt from the content
    #
    # By default excerpt is your first paragraph of a doc: everything before
    # the first two new lines:
    #
    #     ---
    #     title: Example
    #     ---
    #
    #     First paragraph with [link][1].
    #
    #     Second paragraph.
    #
    #     [1]: http://example.com/
    #
    # This is fairly good option for Markdown and Textile files. But might cause
    # problems for HTML docs (which is quite unusual for Jekyll). If default
    # excerpt delimiter is not good for you, you might want to set your own via
    # configuration option `excerpt_separator`. For example, following is a good
    # alternative for HTML docs:
    #
    #     # file: _config.yml
    #     excerpt_separator: "<!-- more -->"
    #
    # Notice that all markdown-style link references will be appended to the
    # excerpt. So the example doc above will have this excerpt source:
    #
    #     First paragraph with [link][1].
    #
    #     [1]: http://example.com/
    #
    # Excerpts are rendered same time as content is rendered.
    #
    # Returns excerpt String

    LIQUID_TAG_REGEX = %r!{%-?\s*(\w+)\s*.*?-?%}!m.freeze
    MKDWN_LINK_REF_REGEX = %r!^ {0,3}(?:(\[[^\]]+\])(:.+))$!.freeze

    def extract_excerpt(doc_content)
      head, _, tail = doc_content.to_s.partition(doc.excerpt_separator)
      return head if tail.empty?

      head = sanctify_liquid_tags(head) if head.include?("{%")
      definitions = extract_markdown_link_reference_definitions(head, tail)
      return head if definitions.empty?

      head << "\n\n" << definitions.join("\n")
    end

    private

    # append appropriate closing tag(s) (for each Liquid block), to the `head` if the
    # partitioning resulted in leaving the closing tag somewhere in the `tail` partition.
    def sanctify_liquid_tags(head)
      modified  = false
      tag_names = head.scan(LIQUID_TAG_REGEX)
      tag_names.flatten!
      tag_names.reverse_each do |tag_name|
        next unless liquid_block?(tag_name)
        next if endtag_regex_stash(tag_name).match?(head)

        modified = true
        head << "\n{% end#{tag_name} %}"
      end

      print_build_warning if modified
      head
    end

    def extract_markdown_link_reference_definitions(head, tail)
      [].tap do |definitions|
        tail.scan(MKDWN_LINK_REF_REGEX).each do |segments|
          definitions << segments.join if head.include?(segments[0])
        end
      end
    end

    def endtag_regex_stash(tag_name)
      @endtag_regex_stash ||= {}
      @endtag_regex_stash[tag_name] ||= %r!{%-?\s*end#{tag_name}.*?\s*-?%}!m
    end

    def liquid_block?(tag_name)
      return false unless tag_name.is_a?(String)
      return false unless Liquid::Template.tags[tag_name]

      Liquid::Template.tags[tag_name].ancestors.include?(Liquid::Block)
    rescue NoMethodError
      Jekyll.logger.error "Error:",
                          "A Liquid tag in the excerpt of #{doc.relative_path} couldn't be parsed."
      raise
    end

    def print_build_warning
      Jekyll.logger.warn "Warning:", "Excerpt modified in #{doc.relative_path}!"
      Jekyll.logger.warn "", "Found a Liquid block containing the excerpt separator " \
                             "#{doc.excerpt_separator.inspect}."
      Jekyll.logger.warn "", "The block has been modified with the appropriate closing tag."
      Jekyll.logger.warn "", "Feel free to define a custom excerpt or excerpt_separator in the"
      Jekyll.logger.warn "", "document's Front Matter if the generated excerpt is unsatisfactory."
    end
  end
end