summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/base.rb137
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/html.rb621
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown.rb377
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/abbreviation.rb80
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/autolink.rb31
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blank_line.rb30
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/block_boundary.rb34
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blockquote.rb38
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codeblock.rb57
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codespan.rb58
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/emphasis.rb66
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/eob.rb26
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/escaped_chars.rb25
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/extensions.rb214
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/footnote.rb64
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/header.rb70
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/horizontal_rule.rb27
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html.rb165
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html_entity.rb34
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/line_break.rb25
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/link.rb149
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/list.rb286
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/math.rb53
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/paragraph.rb62
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/smart_quotes.rb174
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/table.rb171
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/typographic_symbol.rb44
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/markdown.rb57
28 files changed, 3175 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/base.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/base.rb
new file mode 100644
index 0000000..075385e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/base.rb
@@ -0,0 +1,137 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/utils'
+require 'kramdown/parser'
+
+module Kramdown
+
+ module Parser
+
+ # == \Base class for parsers
+ #
+ # This class serves as base class for parsers. It provides common methods that can/should be
+ # used by all parsers, especially by those using StringScanner(Kramdown) for parsing.
+ #
+ # A parser object is used as a throw-away object, i.e. it is only used for storing the needed
+ # state information during parsing. Therefore one can't instantiate a parser object directly but
+ # only use the Base::parse method.
+ #
+ # == Implementing a parser
+ #
+ # Implementing a new parser is rather easy: just derive a new class from this class and put it
+ # in the Kramdown::Parser module -- the latter is needed so that the auto-detection of the new
+ # parser works correctly. Then you need to implement the +#parse+ method which has to contain
+ # the parsing code.
+ #
+ # Have a look at the Base::parse, Base::new and Base#parse methods for additional information!
+ class Base
+
+ # The hash with the parsing options.
+ attr_reader :options
+
+ # The array with the parser warnings.
+ attr_reader :warnings
+
+ # The original source string.
+ attr_reader :source
+
+ # The root element of element tree that is created from the source string.
+ attr_reader :root
+
+ # Initialize the parser object with the +source+ string and the parsing +options+.
+ #
+ # The @root element, the @warnings array and @text_type (specifies the default type for newly
+ # created text nodes) are automatically initialized.
+ def initialize(source, options)
+ @source = source
+ @options = Kramdown::Options.merge(options)
+ @root = Element.new(:root, nil, nil, encoding: (source.encoding rescue nil), location: 1,
+ options: {}, abbrev_defs: {}, abbrev_attr: {})
+
+ @root.options[:abbrev_defs].default_proc = @root.options[:abbrev_attr].default_proc =
+ lambda do |h, k|
+ k_mod = k.gsub(/[\s\p{Z}]+/, " ")
+ k != k_mod ? h[k_mod] : nil
+ end
+ @warnings = []
+ @text_type = :text
+ end
+ private_class_method(:new, :allocate)
+
+ # Parse the +source+ string into an element tree, possibly using the parsing +options+, and
+ # return the root element of the element tree and an array with warning messages.
+ #
+ # Initializes a new instance of the calling class and then calls the +#parse+ method that must
+ # be implemented by each subclass.
+ def self.parse(source, options = {})
+ parser = new(source, options)
+ parser.parse
+ [parser.root, parser.warnings]
+ end
+
+ # Parse the source string into an element tree.
+ #
+ # The parsing code should parse the source provided in @source and build an element tree the
+ # root of which should be @root.
+ #
+ # This is the only method that has to be implemented by sub-classes!
+ def parse
+ raise NotImplementedError
+ end
+
+ # Add the given warning +text+ to the warning array.
+ def warning(text)
+ @warnings << text
+ # TODO: add position information
+ end
+
+ # Modify the string +source+ to be usable by the parser (unifies line ending characters to
+ # +\n+ and makes sure +source+ ends with a new line character).
+ def adapt_source(source)
+ unless source.valid_encoding?
+ raise "The source text contains invalid characters for the used encoding #{source.encoding}"
+ end
+ source = source.encode('UTF-8')
+ source.gsub!(/\r\n?/, "\n")
+ source.chomp!
+ source << "\n"
+ end
+
+ # This helper method adds the given +text+ either to the last element in the +tree+ if it is a
+ # +type+ element or creates a new text element with the given +type+.
+ def add_text(text, tree = @tree, type = @text_type)
+ last = tree.children.last
+ if last && last.type == type
+ last.value << text
+ elsif !text.empty?
+ location = (last && last.options[:location] || tree.options[:location])
+ tree.children << Element.new(type, text, nil, location: location)
+ end
+ end
+
+ # Extract the part of the StringScanner +strscan+ backed string specified by the +range+. This
+ # method works correctly under Ruby 1.8 and Ruby 1.9.
+ def extract_string(range, strscan)
+ result = nil
+ begin
+ enc = strscan.string.encoding
+ strscan.string.force_encoding('ASCII-8BIT')
+ result = strscan.string[range].force_encoding(enc)
+ ensure
+ strscan.string.force_encoding(enc)
+ end
+ result
+ end
+
+ end
+
+ end
+
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/html.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/html.rb
new file mode 100644
index 0000000..47093de
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/html.rb
@@ -0,0 +1,621 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'rexml/parsers/baseparser'
+require 'strscan'
+require 'kramdown/utils'
+require 'kramdown/parser'
+
+module Kramdown
+
+ module Parser
+
+ # Used for parsing an HTML document.
+ #
+ # The parsing code is in the Parser module that can also be used by other parsers.
+ class Html < Base
+
+ # Contains all constants that are used when parsing.
+ module Constants
+
+ # :stopdoc:
+ # The following regexps are based on the ones used by REXML, with some slight modifications.
+ HTML_DOCTYPE_RE = /<!DOCTYPE.*?>/im
+ HTML_COMMENT_RE = /<!--(.*?)-->/m
+ HTML_INSTRUCTION_RE = /<\?(.*?)\?>/m
+ HTML_CDATA_RE = /<!\[CDATA\[(.*?)\]\]>/m
+ HTML_ATTRIBUTE_RE = /\s*(#{REXML::Parsers::BaseParser::UNAME_STR})(?:\s*=\s*(?:(\p{Word}+)|("|')(.*?)\3))?/m
+ HTML_TAG_RE = /<((?>#{REXML::Parsers::BaseParser::UNAME_STR}))\s*((?>\s+#{REXML::Parsers::BaseParser::UNAME_STR}(?:\s*=\s*(?:\p{Word}+|("|').*?\3))?)*)\s*(\/)?>/m
+ HTML_TAG_CLOSE_RE = /<\/(#{REXML::Parsers::BaseParser::UNAME_STR})\s*>/m
+ HTML_ENTITY_RE = /&([\w:][\w.:-]*);|&#(\d+);|&\#x([0-9a-fA-F]+);/
+
+ HTML_CONTENT_MODEL_BLOCK = %w[address applet article aside blockquote body
+ dd details div dl fieldset figure figcaption
+ footer form header hgroup iframe li main
+ map menu nav noscript object section summary td]
+ HTML_CONTENT_MODEL_SPAN = %w[a abbr acronym b bdo big button cite caption del dfn dt em
+ h1 h2 h3 h4 h5 h6 i ins label legend optgroup p q rb rbc
+ rp rt rtc ruby select small span strong sub sup th tt]
+ HTML_CONTENT_MODEL_RAW = %w[script style math option textarea pre code kbd samp var]
+ # The following elements are also parsed as raw since they need child elements that cannot
+ # be expressed using kramdown syntax: colgroup table tbody thead tfoot tr ul ol
+
+ HTML_CONTENT_MODEL = Hash.new {|h, k| h[k] = :raw }
+ HTML_CONTENT_MODEL_BLOCK.each {|i| HTML_CONTENT_MODEL[i] = :block }
+ HTML_CONTENT_MODEL_SPAN.each {|i| HTML_CONTENT_MODEL[i] = :span }
+ HTML_CONTENT_MODEL_RAW.each {|i| HTML_CONTENT_MODEL[i] = :raw }
+
+ # Some HTML elements like script belong to both categories (i.e. are valid in block and
+ # span HTML) and don't appear therefore!
+ # script, textarea
+ HTML_SPAN_ELEMENTS = %w[a abbr acronym b big bdo br button cite code del dfn em i img input
+ ins kbd label mark option q rb rbc rp rt rtc ruby samp select small
+ span strong sub sup time tt u var]
+ HTML_BLOCK_ELEMENTS = %w[address article aside applet body blockquote caption col colgroup
+ dd div dl dt fieldset figcaption footer form h1 h2 h3 h4 h5 h6
+ header hgroup hr html head iframe legend menu li main map nav ol
+ optgroup p pre section summary table tbody td th thead tfoot tr ul]
+ HTML_ELEMENTS_WITHOUT_BODY = %w[area base br col command embed hr img input keygen link
+ meta param source track wbr]
+
+ HTML_ELEMENT = Hash.new(false)
+ (HTML_SPAN_ELEMENTS + HTML_BLOCK_ELEMENTS + HTML_ELEMENTS_WITHOUT_BODY +
+ HTML_CONTENT_MODEL.keys).each do |a|
+ HTML_ELEMENT[a] = true
+ end
+ end
+
+ # Contains the parsing methods. This module can be mixed into any parser to get HTML parsing
+ # functionality. The only thing that must be provided by the class are instance variable
+ # @stack for storing the needed state and @src (instance of StringScanner) for the actual
+ # parsing.
+ module Parser
+
+ include Constants
+
+ # Process the HTML start tag that has already be scanned/checked via @src.
+ #
+ # Does the common processing steps and then yields to the caller for further processing
+ # (first parameter is the created element; the second parameter is +true+ if the HTML
+ # element is already closed, ie. contains no body; the third parameter specifies whether the
+ # body - and the end tag - need to be handled in case closed=false).
+ def handle_html_start_tag(line = nil) # :yields: el, closed, handle_body
+ name = @src[1]
+ name.downcase! if HTML_ELEMENT[name.downcase]
+ closed = !@src[4].nil?
+ attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[name])
+
+ el = Element.new(:html_element, name, attrs, category: :block)
+ el.options[:location] = line if line
+ @tree.children << el
+
+ if !closed && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
+ closed = true
+ end
+ if name == 'script' || name == 'style'
+ handle_raw_html_tag(name)
+ yield(el, false, false)
+ else
+ yield(el, closed, true)
+ end
+ end
+
+ # Parses the given string for HTML attributes and returns the resulting hash.
+ #
+ # If the optional +line+ parameter is supplied, it is used in warning messages.
+ #
+ # If the optional +in_html_tag+ parameter is set to +false+, attributes are not modified to
+ # contain only lowercase letters.
+ def parse_html_attributes(str, line = nil, in_html_tag = true)
+ attrs = {}
+ str.scan(HTML_ATTRIBUTE_RE).each do |attr, val, _sep, quoted_val|
+ attr.downcase! if in_html_tag
+ if attrs.key?(attr)
+ warning("Duplicate HTML attribute '#{attr}' on line #{line || '?'} - overwriting previous one")
+ end
+ attrs[attr] = val || quoted_val || ""
+ end
+ attrs
+ end
+
+ # Handle the raw HTML tag at the current position.
+ def handle_raw_html_tag(name)
+ curpos = @src.pos
+ if @src.scan_until(/(?=<\/#{name}\s*>)/mi)
+ add_text(extract_string(curpos...@src.pos, @src), @tree.children.last, :raw)
+ @src.scan(HTML_TAG_CLOSE_RE)
+ else
+ add_text(@src.rest, @tree.children.last, :raw)
+ @src.terminate
+ warning("Found no end tag for '#{name}' - auto-closing it")
+ end
+ end
+
+ HTML_RAW_START = /(?=<(#{REXML::Parsers::BaseParser::UNAME_STR}|\/|!--|\?|!\[CDATA\[))/ # :nodoc:
+
+ # Parse raw HTML from the current source position, storing the found elements in +el+.
+ # Parsing continues until one of the following criteria are fulfilled:
+ #
+ # - The end of the document is reached.
+ # - The matching end tag for the element +el+ is found (only used if +el+ is an HTML
+ # element).
+ #
+ # When an HTML start tag is found, processing is deferred to #handle_html_start_tag,
+ # providing the block given to this method.
+ def parse_raw_html(el, &block)
+ @stack.push(@tree)
+ @tree = el
+
+ done = false
+ while !@src.eos? && !done
+ if (result = @src.scan_until(HTML_RAW_START))
+ add_text(result, @tree, :text)
+ line = @src.current_line_number
+ if (result = @src.scan(HTML_COMMENT_RE))
+ @tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
+ elsif (result = @src.scan(HTML_INSTRUCTION_RE))
+ @tree.children << Element.new(:xml_pi, result, nil, category: :block, location: line)
+ elsif @src.scan(HTML_CDATA_RE)
+ @tree.children << Element.new(:text, @src[1], nil, cdata: true, location: line)
+ elsif @src.scan(HTML_TAG_RE)
+ if method(:handle_html_start_tag).arity.abs >= 1
+ handle_html_start_tag(line, &block)
+ else
+ handle_html_start_tag(&block) # DEPRECATED: method needs to accept line number in 2.0
+ end
+ elsif @src.scan(HTML_TAG_CLOSE_RE)
+ if @tree.value == (HTML_ELEMENT[@tree.value] ? @src[1].downcase : @src[1])
+ done = true
+ else
+ add_text(@src.matched, @tree, :text)
+ warning("Found invalidly used HTML closing tag for '#{@src[1]}' on " \
+ "line #{line} - ignoring it")
+ end
+ else
+ add_text(@src.getch, @tree, :text)
+ end
+ else
+ add_text(@src.rest, @tree, :text)
+ @src.terminate
+ if @tree.type == :html_element
+ warning("Found no end tag for '#{@tree.value}' on line " \
+ "#{@tree.options[:location]} - auto-closing it")
+ end
+ done = true
+ end
+ end
+
+ @tree = @stack.pop
+ end
+
+ end
+
+ # Converts HTML elements to native elements if possible.
+ class ElementConverter
+
+ # :stopdoc:
+
+ include Constants
+ include ::Kramdown::Utils::Entities
+
+ REMOVE_TEXT_CHILDREN = %w[html head hgroup ol ul dl table colgroup tbody thead tfoot tr
+ select optgroup]
+ WRAP_TEXT_CHILDREN = %w[body section nav article aside header footer address div li dd
+ blockquote figure figcaption fieldset form]
+ REMOVE_WHITESPACE_CHILDREN = %w[body section nav article aside header footer address
+ div li dd blockquote figure figcaption td th fieldset form]
+ STRIP_WHITESPACE = %w[address article aside blockquote body caption dd div dl dt fieldset
+ figcaption form footer header h1 h2 h3 h4 h5 h6 legend li nav p
+ section td th]
+ SIMPLE_ELEMENTS = %w[em strong blockquote hr br img p thead tbody tfoot tr td th ul ol dl
+ li dl dt dd]
+
+ def initialize(root)
+ @root = root
+ end
+
+ def self.convert(root, el = root)
+ new(root).process(el)
+ end
+
+ # Convert the element +el+ and its children.
+ def process(el, do_conversion = true, preserve_text = false, parent = nil)
+ case el.type
+ when :xml_comment, :xml_pi
+ ptype = if parent.nil?
+ 'div'
+ else
+ case parent.type
+ when :html_element then parent.value
+ when :code_span then 'code'
+ when :code_block then 'pre'
+ when :header then 'h1'
+ else parent.type.to_s
+ end
+ end
+ el.options.replace(category: (HTML_CONTENT_MODEL[ptype] == :span ? :span : :block))
+ return
+ when :html_element
+ # do nothing
+ when :root
+ el.children.map! do |c|
+ if c.type == :text
+ process_text(c.value, !do_conversion)
+ else
+ process(c)
+ c
+ end
+ end.flatten!
+ remove_whitespace_children(el)
+ return
+ else return
+ end
+
+ mname = "convert_#{el.value}"
+ if do_conversion && self.class.method_defined?(mname)
+ send(mname, el)
+ else
+ type = el.value
+ remove_text_children(el) if do_conversion && REMOVE_TEXT_CHILDREN.include?(type)
+
+ if do_conversion && SIMPLE_ELEMENTS.include?(type)
+ set_basics(el, type.intern)
+ process_children(el, do_conversion, preserve_text)
+ else
+ process_html_element(el, do_conversion, preserve_text)
+ end
+
+ if do_conversion
+ strip_whitespace(el) if STRIP_WHITESPACE.include?(type)
+ remove_whitespace_children(el) if REMOVE_WHITESPACE_CHILDREN.include?(type)
+ wrap_text_children(el) if WRAP_TEXT_CHILDREN.include?(type)
+ end
+ end
+ end
+
+ def process_children(el, do_conversion = true, preserve_text = false)
+ el.children.map! do |c|
+ if c.type == :text
+ process_text(c.value, preserve_text || !do_conversion)
+ else
+ process(c, do_conversion, preserve_text, el)
+ c
+ end
+ end.flatten!
+ end
+
+ # Process the HTML text +raw+: compress whitespace (if +preserve+ is +false+) and convert
+ # entities in entity elements.
+ def process_text(raw, preserve = false)
+ raw.gsub!(/\s+/, ' ') unless preserve
+ src = Kramdown::Utils::StringScanner.new(raw)
+ result = []
+ until src.eos?
+ if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
+ result << Element.new(:text, tmp)
+ src.scan(HTML_ENTITY_RE)
+ val = src[1] || src[2]&.to_i || src[3].hex
+ result << if %w[lsquo rsquo ldquo rdquo].include?(val)
+ Element.new(:smart_quote, val.intern)
+ elsif %w[mdash ndash hellip laquo raquo].include?(val)
+ Element.new(:typographic_sym, val.intern)
+ else
+ begin
+ Element.new(:entity, entity(val), nil, original: src.matched)
+ rescue ::Kramdown::Error
+ src.pos -= src.matched_size - 1
+ Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'))
+ end
+ end
+ else
+ result << Element.new(:text, src.rest)
+ src.terminate
+ end
+ end
+ result
+ end
+
+ def process_html_element(el, do_conversion = true, preserve_text = false)
+ el.options.replace(category: HTML_SPAN_ELEMENTS.include?(el.value) ? :span : :block,
+ content_model: (do_conversion ? HTML_CONTENT_MODEL[el.value] : :raw))
+ process_children(el, do_conversion, preserve_text)
+ end
+
+ def remove_text_children(el)
+ el.children.delete_if {|c| c.type == :text }
+ end
+
+ def wrap_text_children(el)
+ tmp = []
+ last_is_p = false
+ el.children.each do |c|
+ if !c.block? || c.type == :text
+ unless last_is_p
+ tmp << Element.new(:p, nil, nil, transparent: true)
+ last_is_p = true
+ end
+ tmp.last.children << c
+ tmp
+ else
+ tmp << c
+ last_is_p = false
+ end
+ end
+ el.children = tmp
+ end
+
+ def strip_whitespace(el)
+ return if el.children.empty?
+ if el.children.first.type == :text
+ el.children.first.value.lstrip!
+ end
+ if el.children.last.type == :text
+ el.children.last.value.rstrip!
+ end
+ end
+
+ def remove_whitespace_children(el)
+ i = -1
+ el.children = el.children.reject do |c|
+ i += 1
+ c.type == :text && c.value.strip.empty? &&
+ (i == 0 || i == el.children.length - 1 || (el.children[i - 1].block? &&
+ el.children[i + 1].block?))
+ end
+ end
+
+ def set_basics(el, type, opts = {})
+ el.type = type
+ el.options.replace(opts)
+ el.value = nil
+ end
+
+ def extract_text(el, raw)
+ raw << el.value.to_s if el.type == :text
+ el.children.each {|c| extract_text(c, raw) }
+ end
+
+ def convert_textarea(el)
+ process_html_element(el, true, true)
+ end
+
+ def convert_a(el)
+ if el.attr['href']
+ set_basics(el, :a)
+ process_children(el)
+ else
+ process_html_element(el, false)
+ end
+ end
+
+ EMPHASIS_TYPE_MAP = {'em' => :em, 'i' => :em, 'strong' => :strong, 'b' => :strong}
+ def convert_em(el)
+ text = +''
+ extract_text(el, text)
+ if text =~ /\A\s/ || text =~ /\s\z/
+ process_html_element(el, false)
+ else
+ set_basics(el, EMPHASIS_TYPE_MAP[el.value])
+ process_children(el)
+ end
+ end
+ %w[b strong i].each do |i|
+ alias_method("convert_#{i}".to_sym, :convert_em)
+ end
+
+ def convert_h1(el)
+ set_basics(el, :header, level: el.value[1..1].to_i)
+ extract_text(el, el.options[:raw_text] = +'')
+ process_children(el)
+ end
+ %w[h2 h3 h4 h5 h6].each do |i|
+ alias_method("convert_#{i}".to_sym, :convert_h1)
+ end
+
+ def convert_code(el)
+ raw = +''
+ extract_text(el, raw)
+ result = process_text(raw, true)
+ begin
+ str = result.inject(+'') do |mem, c|
+ case c.type
+ when :text
+ mem << c.value
+ when :entity
+ mem << if [60, 62, 34, 38].include?(c.value.code_point)
+ c.value.code_point.chr
+ else
+ c.value.char
+ end
+ when :smart_quote, :typographic_sym
+ mem << entity(c.value.to_s).char
+ else
+ raise "Bug - please report"
+ end
+ end
+ result.clear
+ result << Element.new(:text, str)
+ rescue StandardError
+ end
+ if result.length > 1 || result.first.type != :text
+ process_html_element(el, false, true)
+ else
+ if el.value == 'code'
+ set_basics(el, :codespan)
+ el.attr['class']&.gsub!(/\s+\bhighlighter-\w+\b|\bhighlighter-\w+\b\s*/, '')
+ else
+ set_basics(el, :codeblock)
+ if el.children.size == 1 && el.children.first.value == 'code'
+ value = (el.children.first.attr['class'] || '').scan(/\blanguage-\S+/).first
+ el.attr['class'] = "#{value} #{el.attr['class']}".rstrip if value
+ end
+ end
+ el.value = result.first.value
+ el.children.clear
+ end
+ end
+ alias convert_pre convert_code
+
+ def convert_table(el)
+ unless is_simple_table?(el)
+ process_html_element(el, false)
+ return
+ end
+ remove_text_children(el)
+ process_children(el)
+ set_basics(el, :table)
+
+ calc_alignment = lambda do |c|
+ if c.type == :tr
+ el.options[:alignment] = c.children.map do |td|
+ if td.attr['style']
+ td.attr['style'].slice!(/(?:;\s*)?text-align:\s+(center|left|right)/)
+ td.attr.delete('style') if td.attr['style'].strip.empty?
+ $1 ? $1.to_sym : :default
+ else
+ :default
+ end
+ end
+ else
+ c.children.each {|cc| calc_alignment.call(cc) }
+ end
+ end
+ calc_alignment.call(el)
+ el.children.delete_if {|c| c.type == :html_element }
+
+ change_th_type = lambda do |c|
+ if c.type == :th
+ c.type = :td
+ else
+ c.children.each {|cc| change_th_type.call(cc) }
+ end
+ end
+ change_th_type.call(el)
+
+ if el.children.first.type == :tr
+ tbody = Element.new(:tbody)
+ tbody.children = el.children
+ el.children = [tbody]
+ end
+ end
+
+ def is_simple_table?(el)
+ only_phrasing_content = lambda do |c|
+ c.children.all? do |cc|
+ (cc.type == :text || !HTML_BLOCK_ELEMENTS.include?(cc.value)) && only_phrasing_content.call(cc)
+ end
+ end
+ check_cells = proc do |c|
+ if c.value == 'th' || c.value == 'td'
+ return false unless only_phrasing_content.call(c)
+ else
+ c.children.each {|cc| check_cells.call(cc) }
+ end
+ end
+ check_cells.call(el)
+
+ nr_cells = 0
+ check_nr_cells = lambda do |t|
+ if t.value == 'tr'
+ count = t.children.count {|cc| cc.value == 'th' || cc.value == 'td' }
+ if count != nr_cells
+ if nr_cells == 0
+ nr_cells = count
+ else
+ nr_cells = -1
+ break
+ end
+ end
+ else
+ t.children.each {|cc| check_nr_cells.call(cc) }
+ end
+ end
+ check_nr_cells.call(el)
+ return false if nr_cells == -1 || nr_cells == 0
+
+ alignment = nil
+ check_alignment = proc do |t|
+ if t.value == 'tr'
+ cur_alignment = t.children.select {|cc| cc.value == 'th' || cc.value == 'td' }.map do |cell|
+ md = /text-align:\s+(center|left|right|justify|inherit)/.match(cell.attr['style'].to_s)
+ return false if md && (md[1] == 'justify' || md[1] == 'inherit')
+ md.nil? ? :default : md[1]
+ end
+ alignment = cur_alignment if alignment.nil?
+ return false if alignment != cur_alignment
+ else
+ t.children.each {|cc| check_alignment.call(cc) }
+ end
+ end
+ check_alignment.call(el)
+
+ check_rows = lambda do |t, type|
+ t.children.all? do |r|
+ (r.value == 'tr' || r.type == :text) && r.children.all? {|c| c.value == type || c.type == :text }
+ end
+ end
+ check_rows.call(el, 'td') ||
+ (el.children.all? do |t|
+ t.type == :text || (t.value == 'thead' && check_rows.call(t, 'th')) ||
+ ((t.value == 'tfoot' || t.value == 'tbody') && check_rows.call(t, 'td'))
+ end && el.children.any? {|t| t.value == 'tbody' })
+ end
+
+ def convert_script(el)
+ if is_math_tag?(el)
+ handle_math_tag(el)
+ else
+ process_html_element(el)
+ end
+ end
+
+ def is_math_tag?(el)
+ el.attr['type'].to_s =~ /\bmath\/tex\b/
+ end
+
+ def handle_math_tag(el)
+ set_basics(el, :math, category: (el.attr['type'].include?("mode=display") ? :block : :span))
+ el.value = el.children.shift.value.sub(/\A(?:%\s*)?<!\[CDATA\[\n?(.*?)(?:\s%)?\]\]>\z/m, '\1')
+ el.attr.delete('type')
+ end
+
+ end
+
+ include Parser
+
+ # Parse the source string provided on initialization as HTML document.
+ def parse
+ @stack, @tree = [], @root
+ @src = Kramdown::Utils::StringScanner.new(adapt_source(source))
+
+ while true
+ if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/o))
+ @tree.children << Element.new(:xml_pi, result.strip, nil, category: :block)
+ elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/o))
+ # ignore the doctype
+ elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/o))
+ @tree.children << Element.new(:xml_comment, result.strip, nil, category: :block)
+ else
+ break
+ end
+ end
+
+ tag_handler = lambda do |c, closed, handle_body|
+ parse_raw_html(c, &tag_handler) if !closed && handle_body
+ end
+ parse_raw_html(@tree, &tag_handler)
+
+ ElementConverter.convert(@tree)
+ end
+
+ end
+
+ end
+
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown.rb
new file mode 100644
index 0000000..7e17f3f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown.rb
@@ -0,0 +1,377 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'strscan'
+require 'stringio'
+require 'kramdown/parser'
+
+# TODO: use [[:alpha:]] in all regexp to allow parsing of international values in 1.9.1
+# NOTE: use @src.pre_match only before other check/match?/... operations, otherwise the content is changed
+
+module Kramdown
+
+ module Parser
+
+ # Used for parsing a document in kramdown format.
+ #
+ # If you want to extend the functionality of the parser, you need to do the following:
+ #
+ # * Create a new subclass
+ # * add the needed parser methods
+ # * modify the @block_parsers and @span_parsers variables and add the names of your parser
+ # methods
+ #
+ # Here is a small example for an extended parser class that parses ERB style tags as raw text if
+ # they are used as span-level elements (an equivalent block-level parser should probably also be
+ # made to handle the block case):
+ #
+ # require 'kramdown/parser/kramdown'
+ #
+ # class Kramdown::Parser::ERBKramdown < Kramdown::Parser::Kramdown
+ #
+ # def initialize(source, options)
+ # super
+ # @span_parsers.unshift(:erb_tags)
+ # end
+ #
+ # ERB_TAGS_START = /<%.*?%>/
+ #
+ # def parse_erb_tags
+ # @src.pos += @src.matched_size
+ # @tree.children << Element.new(:raw, @src.matched)
+ # end
+ # define_parser(:erb_tags, ERB_TAGS_START, '<%')
+ #
+ # end
+ #
+ # The new parser can be used like this:
+ #
+ # require 'kramdown/document'
+ # # require the file with the above parser class
+ #
+ # Kramdown::Document.new(input_text, :input => 'ERBKramdown').to_html
+ #
+ class Kramdown < Base
+
+ include ::Kramdown
+
+ # Create a new Kramdown parser object with the given +options+.
+ def initialize(source, options)
+ super
+
+ reset_env
+
+ @alds = {}
+ @footnotes = {}
+ @link_defs = {}
+ update_link_definitions(@options[:link_defs])
+
+ @block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header,
+ :horizontal_rule, :list, :definition_list, :block_html, :setext_header,
+ :block_math, :table, :footnote_definition, :link_definition,
+ :abbrev_definition, :block_extensions, :eob_marker, :paragraph]
+ @span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
+ :smart_quotes, :inline_math, :span_extensions, :html_entity,
+ :typographic_syms, :line_break, :escaped_chars]
+
+ @span_pattern_cache ||= Hash.new {|h, k| h[k] = {} }
+ end
+ private_class_method(:new, :allocate)
+
+ # The source string provided on initialization is parsed into the @root element.
+ def parse
+ configure_parser
+ parse_blocks(@root, adapt_source(source))
+ update_tree(@root)
+ correct_abbreviations_attributes
+ replace_abbreviations(@root)
+ @footnotes.each do |_name, data|
+ update_tree(data[:content])
+ replace_abbreviations(data[:content])
+ end
+ footnote_count = 0
+ @footnotes.each do |name, data|
+ (footnote_count += 1; next) if data.key?(:marker)
+ line = data[:content].options[:location]
+ warning("Footnote definition for '#{name}' on line #{line} is unreferenced - ignoring")
+ end
+ @root.options[:footnote_count] = footnote_count
+ end
+
+ protected
+
+ # :doc:
+ #
+ # Update the parser specific link definitions with the data from +link_defs+ (the value of the
+ # :link_defs option).
+ #
+ # The parameter +link_defs+ is a hash where the keys are possibly unnormalized link IDs and
+ # the values are two element arrays consisting of the link target and a title (can be +nil+).
+ def update_link_definitions(link_defs)
+ link_defs.each {|k, v| @link_defs[normalize_link_id(k)] = v }
+ end
+
+ # Adapt the object to allow parsing like specified in the options.
+ def configure_parser
+ @parsers = {}
+ (@block_parsers + @span_parsers).each do |name|
+ if self.class.has_parser?(name)
+ @parsers[name] = self.class.parser(name)
+ else
+ raise Kramdown::Error, "Unknown parser: #{name}"
+ end
+ end
+ @span_start, @span_start_re = span_parser_regexps
+ end
+
+ # Create the needed span parser regexps.
+ def span_parser_regexps(parsers = @span_parsers)
+ span_start = /#{parsers.map {|name| @parsers[name].span_start }.join('|')}/
+ [span_start, /(?=#{span_start})/]
+ end
+
+ # Parse all block-level elements in +text+ into the element +el+.
+ def parse_blocks(el, text = nil)
+ @stack.push([@tree, @src, @block_ial])
+ @tree, @block_ial = el, nil
+ @src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
+
+ status = catch(:stop_block_parsing) do
+ until @src.eos?
+ @block_parsers.any? do |name|
+ if @src.check(@parsers[name].start_re)
+ send(@parsers[name].method)
+ else
+ false
+ end
+ end || begin
+ warning('Warning: this should not occur - no block parser handled the line')
+ add_text(@src.scan(/.*\n/))
+ end
+ end
+ end
+
+ @tree, @src, @block_ial = *@stack.pop
+ status
+ end
+
+ # Update the tree by parsing all :+raw_text+ elements with the span-level parser (resets the
+ # environment) and by updating the attributes from the IALs.
+ def update_tree(element)
+ last_blank = nil
+ element.children.map! do |child|
+ case child.type
+ when :raw_text
+ last_blank = nil
+ reset_env(src: ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
+ text_type: :text)
+ parse_spans(child)
+ child.children
+ when :eob
+ update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
+ []
+ when :blank
+ if last_blank
+ last_blank.value << child.value
+ []
+ else
+ last_blank = child
+ child
+ end
+ else
+ last_blank = nil
+ update_tree(child)
+ update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
+ # DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be
+ # the default behaviour
+ if child.type == :dt || (child.type == :header && @options[:auto_id_stripping])
+ update_raw_text(child)
+ end
+ child
+ end
+ end.flatten!
+ end
+
+ def span_pattern_cache(stop_re, span_start)
+ @span_pattern_cache[stop_re][span_start] ||= /(?=#{Regexp.union(stop_re, span_start)})/
+ end
+ private :span_pattern_cache
+
+ # Parse all span-level elements in the source string of @src into +el+.
+ #
+ # If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
+ # matches and if no block is given or if a block is given and it returns +true+.
+ #
+ # The parameter +parsers+ can be used to specify the (span-level) parsing methods that should
+ # be used for parsing.
+ #
+ # The parameter +text_type+ specifies the type which should be used for created text nodes.
+ def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
+ @stack.push([@tree, @text_type]) unless @tree.nil?
+ @tree, @text_type = el, text_type
+
+ span_start = @span_start
+ span_start_re = @span_start_re
+ span_start, span_start_re = span_parser_regexps(parsers) if parsers
+ parsers ||= @span_parsers
+
+ used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
+ stop_re_found = false
+ while !@src.eos? && !stop_re_found
+ if (result = @src.scan_until(used_re))
+ add_text(result)
+ if stop_re && @src.check(stop_re)
+ stop_re_found = (block_given? ? yield : true)
+ end
+ processed = parsers.any? do |name|
+ if @src.check(@parsers[name].start_re)
+ send(@parsers[name].method)
+ true
+ else
+ false
+ end
+ end unless stop_re_found
+ add_text(@src.getch) if !processed && !stop_re_found
+ else
+ (add_text(@src.rest); @src.terminate) unless stop_re
+ break
+ end
+ end
+
+ @tree, @text_type = @stack.pop
+
+ stop_re_found
+ end
+
+ # Reset the current parsing environment. The parameter +env+ can be used to set initial
+ # values for one or more environment variables.
+ def reset_env(opts = {})
+ opts = {text_type: :raw_text, stack: []}.merge(opts)
+ @src = opts[:src]
+ @tree = opts[:tree]
+ @block_ial = opts[:block_ial]
+ @stack = opts[:stack]
+ @text_type = opts[:text_type]
+ end
+
+ # Return the current parsing environment.
+ def save_env
+ [@src, @tree, @block_ial, @stack, @text_type]
+ end
+
+ # Restore the current parsing environment.
+ def restore_env(env)
+ @src, @tree, @block_ial, @stack, @text_type = *env
+ end
+
+ # Update the given attributes hash +attr+ with the information from the inline attribute list
+ # +ial+ and all referenced ALDs.
+ def update_attr_with_ial(attr, ial)
+ ial[:refs]&.each do |ref|
+ update_attr_with_ial(attr, ref) if (ref = @alds[ref])
+ end
+ ial.each do |k, v|
+ if k == IAL_CLASS_ATTR
+ attr[k] = "#{attr[k]} #{v}".lstrip
+ elsif k.kind_of?(String)
+ attr[k] = v
+ end
+ end
+ end
+
+ # Update the raw text for automatic ID generation.
+ def update_raw_text(item)
+ raw_text = +''
+
+ append_text = lambda do |child|
+ if child.type == :text
+ raw_text << child.value
+ else
+ child.children.each {|c| append_text.call(c) }
+ end
+ end
+
+ append_text.call(item)
+ item.options[:raw_text] = raw_text
+ end
+
+ # Create a new block-level element, taking care of applying a preceding block IAL if it
+ # exists. This method should always be used for creating a block-level element!
+ def new_block_el(*args)
+ el = Element.new(*args)
+ if @block_ial
+ el.options[:ial] = @block_ial
+ @block_ial = nil
+ end
+ el
+ end
+
+ @@parsers = {}
+
+ # Struct class holding all the needed data for one block/span-level parser method.
+ Data = Struct.new(:name, :start_re, :span_start, :method)
+
+ # Add a parser method
+ #
+ # * with the given +name+,
+ # * using +start_re+ as start regexp
+ # * and, for span parsers, +span_start+ as a String that can be used in a regexp and
+ # which identifies the starting character(s)
+ #
+ # to the registry. The method name is automatically derived from the +name+ or can explicitly
+ # be set by using the +meth_name+ parameter.
+ def self.define_parser(name, start_re, span_start = nil, meth_name = "parse_#{name}")
+ raise "A parser with the name #{name} already exists!" if @@parsers.key?(name)
+ @@parsers[name] = Data.new(name, start_re, span_start, meth_name)
+ end
+
+ # Return the Data structure for the parser +name+.
+ def self.parser(name = nil)
+ @@parsers[name]
+ end
+
+ # Return +true+ if there is a parser called +name+.
+ def self.has_parser?(name)
+ @@parsers.key?(name)
+ end
+
+ # Regexp for matching indentation (one tab or four spaces)
+ INDENT = /^(?:\t| {4})/
+ # Regexp for matching the optional space (zero or up to three spaces)
+ OPT_SPACE = / {0,3}/
+
+ require 'kramdown/parser/kramdown/blank_line'
+ require 'kramdown/parser/kramdown/eob'
+ require 'kramdown/parser/kramdown/paragraph'
+ require 'kramdown/parser/kramdown/header'
+ require 'kramdown/parser/kramdown/blockquote'
+ require 'kramdown/parser/kramdown/table'
+ require 'kramdown/parser/kramdown/codeblock'
+ require 'kramdown/parser/kramdown/horizontal_rule'
+ require 'kramdown/parser/kramdown/list'
+ require 'kramdown/parser/kramdown/link'
+ require 'kramdown/parser/kramdown/extensions'
+ require 'kramdown/parser/kramdown/footnote'
+ require 'kramdown/parser/kramdown/html'
+ require 'kramdown/parser/kramdown/escaped_chars'
+ require 'kramdown/parser/kramdown/html_entity'
+ require 'kramdown/parser/kramdown/line_break'
+ require 'kramdown/parser/kramdown/typographic_symbol'
+ require 'kramdown/parser/kramdown/autolink'
+ require 'kramdown/parser/kramdown/codespan'
+ require 'kramdown/parser/kramdown/emphasis'
+ require 'kramdown/parser/kramdown/smart_quotes'
+ require 'kramdown/parser/kramdown/math'
+ require 'kramdown/parser/kramdown/abbreviation'
+
+ end
+
+ end
+
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/abbreviation.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/abbreviation.rb
new file mode 100644
index 0000000..e9d7764
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/abbreviation.rb
@@ -0,0 +1,80 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ ABBREV_DEFINITION_START = /^#{OPT_SPACE}\*\[(.+?)\]:(.*?)\n/
+
+ # Parse the link definition at the current location.
+ def parse_abbrev_definition
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ abbrev_id, abbrev_text = @src[1], @src[2]
+ abbrev_text.strip!
+ if @root.options[:abbrev_defs][abbrev_id]
+ warning("Duplicate abbreviation ID '#{abbrev_id}' on line #{start_line_number} " \
+ "- overwriting")
+ end
+ @tree.children << new_block_el(:eob, :abbrev_def)
+ @root.options[:abbrev_defs][abbrev_id] = abbrev_text
+ @root.options[:abbrev_attr][abbrev_id] = @tree.children.last
+ true
+ end
+ define_parser(:abbrev_definition, ABBREV_DEFINITION_START)
+
+ # Correct abbreviation attributes.
+ def correct_abbreviations_attributes
+ @root.options[:abbrev_attr].keys.each do |k|
+ @root.options[:abbrev_attr][k] = @root.options[:abbrev_attr][k].attr
+ end
+ end
+
+ # Replace the abbreviation text with elements.
+ def replace_abbreviations(el, regexps = nil)
+ return if @root.options[:abbrev_defs].empty?
+ unless regexps
+ sorted_abbrevs = @root.options[:abbrev_defs].keys.sort {|a, b| b.length <=> a.length }
+ regexps = [Regexp.union(*sorted_abbrevs.map do |k|
+ /#{Regexp.escape(k).gsub(/\\\s/, "[\\s\\p{Z}]+").force_encoding(Encoding::UTF_8)}/
+ end)]
+ regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
+ end
+ el.children.map! do |child|
+ if child.type == :text && el.options[:content_model] != :raw
+ if child.value.match?(regexps.first)
+ result = []
+ strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
+ text_lineno = strscan.current_line_number
+ while (temp = strscan.scan_until(regexps.last))
+ abbr_lineno = strscan.current_line_number
+ abbr = strscan.scan(regexps.first) # begin of line case of abbr with \W char as first one
+ if abbr.nil?
+ temp << strscan.scan(/\W|^/)
+ abbr = strscan.scan(regexps.first)
+ end
+ result << Element.new(:text, temp, nil, location: text_lineno)
+ result << Element.new(:abbreviation, abbr, nil, location: abbr_lineno)
+ text_lineno = strscan.current_line_number
+ end
+ result << Element.new(:text, strscan.rest, nil, location: text_lineno)
+ else
+ child
+ end
+ else
+ replace_abbreviations(child, regexps)
+ child
+ end
+ end.flatten!
+ end
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/autolink.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/autolink.rb
new file mode 100644
index 0000000..5f0ccfd
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/autolink.rb
@@ -0,0 +1,31 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ ACHARS = '[[:alnum:]]-_.'
+ AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[#{ACHARS}]+?@[#{ACHARS}]+?)>"
+ AUTOLINK_START = /#{AUTOLINK_START_STR}/u
+
+ # Parse the autolink at the current location.
+ def parse_autolink
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1])
+ el = Element.new(:a, nil, {'href' => href}, location: start_line_number)
+ add_text(@src[1].sub(/^mailto:/, ''), el)
+ @tree.children << el
+ end
+ define_parser(:autolink, AUTOLINK_START, '<')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blank_line.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blank_line.rb
new file mode 100644
index 0000000..0d62c32
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blank_line.rb
@@ -0,0 +1,30 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ BLANK_LINE = /(?>^\s*\n)+/
+
+ # Parse the blank line at the current postition.
+ def parse_blank_line
+ @src.pos += @src.matched_size
+ if (last_child = @tree.children.last) && last_child.type == :blank
+ last_child.value << @src.matched
+ else
+ @tree.children << new_block_el(:blank, @src.matched)
+ end
+ true
+ end
+ define_parser(:blank_line, BLANK_LINE)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/block_boundary.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/block_boundary.rb
new file mode 100644
index 0000000..bb64299
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/block_boundary.rb
@@ -0,0 +1,34 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/extensions'
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/eob'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ BLOCK_BOUNDARY = /#{BLANK_LINE}|#{EOB_MARKER}|#{IAL_BLOCK_START}|\Z/
+
+ # Return +true+ if we are after a block boundary.
+ def after_block_boundary?
+ last_child = @tree.children.last
+ !last_child || last_child.type == :blank ||
+ (last_child.type == :eob && last_child.value.nil?) || @block_ial
+ end
+
+ # Return +true+ if we are before a block boundary.
+ def before_block_boundary?
+ @src.check(self.class::BLOCK_BOUNDARY)
+ end
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blockquote.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blockquote.rb
new file mode 100644
index 0000000..3732b83
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blockquote.rb
@@ -0,0 +1,38 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/extensions'
+require 'kramdown/parser/kramdown/eob'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ BLOCKQUOTE_START = /^#{OPT_SPACE}> ?/
+
+ # Parse the blockquote at the current location.
+ def parse_blockquote
+ start_line_number = @src.current_line_number
+ result = @src.scan(PARAGRAPH_MATCH)
+ until @src.match?(self.class::LAZY_END)
+ result << @src.scan(PARAGRAPH_MATCH)
+ end
+ result.gsub!(BLOCKQUOTE_START, '')
+
+ el = new_block_el(:blockquote, nil, nil, location: start_line_number)
+ @tree.children << el
+ parse_blocks(el, result)
+ true
+ end
+ define_parser(:blockquote, BLOCKQUOTE_START)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codeblock.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codeblock.rb
new file mode 100644
index 0000000..89d2053
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codeblock.rb
@@ -0,0 +1,57 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/extensions'
+require 'kramdown/parser/kramdown/eob'
+require 'kramdown/parser/kramdown/paragraph'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ CODEBLOCK_START = INDENT
+ CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?(?:#{INDENT}[ \t]*\S.*\n)+(?:(?!#{IAL_BLOCK_START}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|^#{OPT_SPACE}#{LAZY_END_HTML_START})^[ \t]*\S.*\n)*)*/
+
+ # Parse the indented codeblock at the current location.
+ def parse_codeblock
+ start_line_number = @src.current_line_number
+ data = @src.scan(self.class::CODEBLOCK_MATCH)
+ data.gsub!(/\n( {0,3}\S)/, ' \\1')
+ data.gsub!(INDENT, '')
+ @tree.children << new_block_el(:codeblock, data, nil, location: start_line_number)
+ true
+ end
+ define_parser(:codeblock, CODEBLOCK_START)
+
+ FENCED_CODEBLOCK_START = /^~{3,}/
+ FENCED_CODEBLOCK_MATCH = /^((~){3,})\s*?((\S+?)(?:\?\S*)?)?\s*?\n(.*?)^\1\2*\s*?\n/m
+
+ # Parse the fenced codeblock at the current location.
+ def parse_codeblock_fenced
+ if @src.check(self.class::FENCED_CODEBLOCK_MATCH)
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ el = new_block_el(:codeblock, @src[5], nil, location: start_line_number, fenced: true)
+ lang = @src[3].to_s.strip
+ unless lang.empty?
+ el.options[:lang] = lang
+ el.attr['class'] = "language-#{@src[4]}"
+ end
+ @tree.children << el
+ true
+ else
+ false
+ end
+ end
+ define_parser(:codeblock_fenced, FENCED_CODEBLOCK_START)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codespan.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codespan.rb
new file mode 100644
index 0000000..9032161
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codespan.rb
@@ -0,0 +1,58 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ CODESPAN_DELIMITER = /`+/
+
+ # Parse the codespan at the current scanner location.
+ def parse_codespan
+ start_line_number = @src.current_line_number
+ result = @src.scan(CODESPAN_DELIMITER)
+ simple = (result.length == 1)
+ saved_pos = @src.save_pos
+
+ if simple && @src.pre_match =~ /\s\Z|\A\Z/ && @src.match?(/\s/)
+ add_text(result)
+ return
+ end
+
+ # assign static regex to avoid allocating the same on every instance
+ # where +result+ equals a single-backtick. Interpolate otherwise.
+ if result == '`'
+ scan_pattern = /`/
+ str_sub_pattern = /`\Z/
+ else
+ scan_pattern = /#{result}/
+ str_sub_pattern = /#{result}\Z/
+ end
+
+ if (text = @src.scan_until(scan_pattern))
+ text.sub!(str_sub_pattern, '')
+ unless simple
+ text = text[1..-1] if text[0..0] == ' '
+ text = text[0..-2] if text[-1..-1] == ' '
+ end
+ @tree.children << Element.new(:codespan, text, nil, {
+ codespan_delimiter: result,
+ location: start_line_number,
+ })
+
+ else
+ @src.revert_pos(saved_pos)
+ add_text(result)
+ end
+ end
+ define_parser(:codespan, CODESPAN_DELIMITER, '`')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/emphasis.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/emphasis.rb
new file mode 100644
index 0000000..a69ab4a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/emphasis.rb
@@ -0,0 +1,66 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ EMPHASIS_START = /(?:\*\*?|__?)/
+
+ # Parse the emphasis at the current location.
+ def parse_emphasis
+ start_line_number = @src.current_line_number
+ saved_pos = @src.save_pos
+
+ result = @src.scan(EMPHASIS_START)
+ element = (result.length == 2 ? :strong : :em)
+ type = result[0..0]
+
+ if (type == '_' && @src.pre_match =~ /[[:alpha:]]-?[[:alpha:]]*_*\z/) || @src.check(/\s/) ||
+ @tree.type == element || @stack.any? {|el, _| el.type == element }
+ add_text(result)
+ return
+ end
+
+ warnings_pos = @warnings.size
+ sub_parse = lambda do |delim, elem|
+ el = Element.new(elem, nil, nil, location: start_line_number)
+ stop_re = /#{Regexp.escape(delim)}/
+ found = parse_spans(el, stop_re) do
+ (@src.pre_match[-1, 1] !~ /\s/) &&
+ (elem != :em || !@src.match?(/#{Regexp.escape(delim * 2)}(?!#{Regexp.escape(delim)})/)) &&
+ (type != '_' || !@src.match?(/#{Regexp.escape(delim)}[[:alnum:]]/)) && !el.children.empty?
+ end
+ [found, el, stop_re]
+ end
+
+ found, el, stop_re = sub_parse.call(result, element)
+ if !found && element == :strong && @tree.type != :em
+ @src.revert_pos(saved_pos)
+ @src.pos += 1
+ found, el, stop_re = sub_parse.call(type, :em)
+ end
+ if found
+ # Useful for implementing underlines.
+ el.options[:char] = type
+
+ @src.scan(stop_re)
+ @tree.children << el
+ else
+ @warnings.slice!(0...warnings_pos)
+ @src.revert_pos(saved_pos)
+ @src.pos += result.length
+ add_text(result)
+ end
+ end
+ define_parser(:emphasis, EMPHASIS_START, '\*|_')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/eob.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/eob.rb
new file mode 100644
index 0000000..82a037a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/eob.rb
@@ -0,0 +1,26 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ EOB_MARKER = /^\^\s*?\n/
+
+ # Parse the EOB marker at the current location.
+ def parse_eob_marker
+ @src.pos += @src.matched_size
+ @tree.children << new_block_el(:eob)
+ true
+ end
+ define_parser(:eob_marker, EOB_MARKER)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/escaped_chars.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/escaped_chars.rb
new file mode 100644
index 0000000..9c2066f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/escaped_chars.rb
@@ -0,0 +1,25 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ ESCAPED_CHARS = /\\([\\.*_+`<>()\[\]{}#!:|"'$=-])/
+
+ # Parse the backslash-escaped character at the current location.
+ def parse_escaped_chars
+ @src.pos += @src.matched_size
+ add_text(@src[1])
+ end
+ define_parser(:escaped_chars, ESCAPED_CHARS, '\\\\')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/extensions.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/extensions.rb
new file mode 100644
index 0000000..92ac620
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/extensions.rb
@@ -0,0 +1,214 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ IAL_CLASS_ATTR = 'class'
+
+ # Parse the string +str+ and extract all attributes and add all found attributes to the hash
+ # +opts+.
+ def parse_attribute_list(str, opts)
+ return if str.strip.empty? || str.strip == ':'
+ attrs = str.scan(ALD_TYPE_ANY)
+ attrs.each do |key, sep, val, ref, id_and_or_class, _, _|
+ if ref
+ (opts[:refs] ||= []) << ref
+ elsif id_and_or_class
+ id_and_or_class.scan(ALD_TYPE_ID_OR_CLASS).each do |id_attr, class_attr|
+ if class_attr
+ opts[IAL_CLASS_ATTR] = "#{opts[IAL_CLASS_ATTR]} #{class_attr}".lstrip
+ else
+ opts['id'] = id_attr
+ end
+ end
+ else
+ val.gsub!(/\\(\}|#{sep})/, "\\1")
+ opts[key] = val
+ end
+ end
+ warning("No or invalid attributes found in IAL/ALD content: #{str}") if attrs.empty?
+ end
+
+ # Update the +ial+ with the information from the inline attribute list +opts+.
+ def update_ial_with_ial(ial, opts)
+ (ial[:refs] ||= []).concat(opts[:refs]) if opts.key?(:refs)
+ opts.each do |k, v|
+ if k == IAL_CLASS_ATTR
+ ial[k] = "#{ial[k]} #{v}".lstrip
+ elsif k.kind_of?(String)
+ ial[k] = v
+ end
+ end
+ end
+
+ # Parse the generic extension at the current point. The parameter +type+ can either be :block
+ # or :span depending whether we parse a block or span extension tag.
+ def parse_extension_start_tag(type)
+ saved_pos = @src.save_pos
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+
+ error_block = lambda do |msg|
+ warning(msg)
+ @src.revert_pos(saved_pos)
+ add_text(@src.getch) if type == :span
+ false
+ end
+
+ if @src[4] || @src.matched == '{:/}'
+ name = (@src[4] ? "for '#{@src[4]}' " : '')
+ return error_block.call("Invalid extension stop tag #{name} found on line " \
+ "#{start_line_number} - ignoring it")
+ end
+
+ ext = @src[1]
+ opts = {}
+ body = nil
+ parse_attribute_list(@src[2] || '', opts)
+
+ unless @src[3]
+ stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/)
+ if (result = @src.scan_until(stop_re))
+ body = result.sub!(stop_re, '')
+ body.chomp! if type == :block
+ else
+ return error_block.call("No stop tag for extension '#{ext}' found on line " \
+ "#{start_line_number} - ignoring it")
+ end
+ end
+
+ if handle_extension(ext, opts, body, type, start_line_number)
+ true
+ else
+ error_block.call("Invalid extension with name '#{ext}' specified on line " \
+ "#{start_line_number} - ignoring it")
+ end
+ end
+
+ def handle_extension(name, opts, body, type, line_no = nil)
+ case name
+ when 'comment'
+ if body.kind_of?(String)
+ @tree.children << Element.new(:comment, body, nil, category: type, location: line_no)
+ end
+ true
+ when 'nomarkdown'
+ if body.kind_of?(String)
+ @tree.children << Element.new(:raw, body, nil, category: type,
+ location: line_no, type: opts['type'].to_s.split(/\s+/))
+ end
+ true
+ when 'options'
+ opts.select do |k, v|
+ k = k.to_sym
+ if Kramdown::Options.defined?(k)
+ if @options[:forbidden_inline_options].include?(k) ||
+ k == :forbidden_inline_options
+ warning("Option #{k} may not be set inline")
+ next false
+ end
+
+ begin
+ val = Kramdown::Options.parse(k, v)
+ @options[k] = val
+ (@root.options[:options] ||= {})[k] = val
+ rescue StandardError
+ end
+ false
+ else
+ true
+ end
+ end.each do |k, _v|
+ warning("Unknown kramdown option '#{k}'")
+ end
+ @tree.children << new_block_el(:eob, :extension) if type == :block
+ true
+ else
+ false
+ end
+ end
+
+ ALD_ID_CHARS = /[\w-]/
+ ALD_ANY_CHARS = /\\\}|[^}]/
+ ALD_ID_NAME = /\w#{ALD_ID_CHARS}*/
+ ALD_CLASS_NAME = /[^\s.#]+/
+ ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^}\2])*?)\2/
+ ALD_TYPE_CLASS_NAME = /\.(#{ALD_CLASS_NAME})/
+ ALD_TYPE_ID_NAME = /#([A-Za-z][\w:-]*)/
+ ALD_TYPE_ID_OR_CLASS = /#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}/
+ ALD_TYPE_ID_OR_CLASS_MULTI = /((?:#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME})+)/
+ ALD_TYPE_REF = /(#{ALD_ID_NAME})/
+ ALD_TYPE_ANY = /(?:\A|\s)(?:#{ALD_TYPE_KEY_VALUE_PAIR}|#{ALD_TYPE_REF}|#{ALD_TYPE_ID_OR_CLASS_MULTI})(?=\s|\Z)/
+ ALD_START = /^#{OPT_SPACE}\{:(#{ALD_ID_NAME}):(#{ALD_ANY_CHARS}+)\}\s*?\n/
+
+ EXT_STOP_STR = "\\{:/(%s)?\\}"
+ EXT_START_STR = "\\{::(\\w+)(?:\\s(#{ALD_ANY_CHARS}*?)|)(\\/)?\\}"
+ EXT_BLOCK_START = /^#{OPT_SPACE}(?:#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME})\s*?\n/
+ EXT_BLOCK_STOP_STR = "^#{OPT_SPACE}#{EXT_STOP_STR}\s*?\n"
+
+ IAL_BLOCK = /\{:(?!:|\/)(#{ALD_ANY_CHARS}+)\}\s*?\n/
+ IAL_BLOCK_START = /^#{OPT_SPACE}#{IAL_BLOCK}/
+
+ BLOCK_EXTENSIONS_START = /^#{OPT_SPACE}\{:/
+
+ # Parse one of the block extensions (ALD, block IAL or generic extension) at the current
+ # location.
+ def parse_block_extensions
+ if @src.scan(ALD_START)
+ parse_attribute_list(@src[2], @alds[@src[1]] ||= {})
+ @tree.children << new_block_el(:eob, :ald)
+ true
+ elsif @src.check(EXT_BLOCK_START)
+ parse_extension_start_tag(:block)
+ elsif @src.scan(IAL_BLOCK_START)
+ if (last_child = @tree.children.last) && last_child.type != :blank &&
+ (last_child.type != :eob ||
+ [:link_def, :abbrev_def, :footnote_def].include?(last_child.value))
+ parse_attribute_list(@src[1], last_child.options[:ial] ||= {})
+ @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
+ else
+ parse_attribute_list(@src[1], @block_ial ||= {})
+ end
+ true
+ else
+ false
+ end
+ end
+ define_parser(:block_extensions, BLOCK_EXTENSIONS_START)
+
+ EXT_SPAN_START = /#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME}/
+ IAL_SPAN_START = /\{:(#{ALD_ANY_CHARS}+)\}/
+ SPAN_EXTENSIONS_START = /\{:/
+
+ # Parse the extension span at the current location.
+ def parse_span_extensions
+ if @src.check(EXT_SPAN_START)
+ parse_extension_start_tag(:span)
+ elsif @src.check(IAL_SPAN_START)
+ if (last_child = @tree.children.last) && last_child.type != :text
+ @src.pos += @src.matched_size
+ attr = {}
+ parse_attribute_list(@src[1], attr)
+ update_ial_with_ial(last_child.options[:ial] ||= {}, attr)
+ update_attr_with_ial(last_child.attr, attr)
+ else
+ warning("Found span IAL after text - ignoring it")
+ add_text(@src.getch)
+ end
+ else
+ add_text(@src.getch)
+ end
+ end
+ define_parser(:span_extensions, SPAN_EXTENSIONS_START, '\{:')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/footnote.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/footnote.rb
new file mode 100644
index 0000000..cf1dc92
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/footnote.rb
@@ -0,0 +1,64 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/extensions'
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/codeblock'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ FOOTNOTE_DEFINITION_START = /^#{OPT_SPACE}\[\^(#{ALD_ID_NAME})\]:\s*?(.*?\n#{CODEBLOCK_MATCH})/
+
+ # Parse the foot note definition at the current location.
+ def parse_footnote_definition
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+
+ el = Element.new(:footnote_def, nil, nil, location: start_line_number)
+ parse_blocks(el, @src[2].gsub(INDENT, ''))
+ if @footnotes[@src[1]]
+ warning("Duplicate footnote name '#{@src[1]}' on line #{start_line_number} - overwriting")
+ end
+ @tree.children << new_block_el(:eob, :footnote_def)
+ (@footnotes[@src[1]] = {})[:content] = el
+ @footnotes[@src[1]][:eob] = @tree.children.last
+ true
+ end
+ define_parser(:footnote_definition, FOOTNOTE_DEFINITION_START)
+
+ FOOTNOTE_MARKER_START = /\[\^(#{ALD_ID_NAME})\]/
+
+ # Parse the footnote marker at the current location.
+ def parse_footnote_marker
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ fn_def = @footnotes[@src[1]]
+ if fn_def
+ if fn_def[:eob]
+ update_attr_with_ial(fn_def[:eob].attr, fn_def[:eob].options[:ial] || {})
+ fn_def[:attr] = fn_def[:eob].attr
+ fn_def[:options] = fn_def[:eob].options
+ fn_def.delete(:eob)
+ end
+ fn_def[:marker] ||= []
+ fn_def[:marker].push(Element.new(:footnote, fn_def[:content], fn_def[:attr],
+ fn_def[:options].merge(name: @src[1], location: start_line_number)))
+ @tree.children << fn_def[:marker].last
+ else
+ warning("Footnote definition for '#{@src[1]}' not found on line #{start_line_number}")
+ add_text(@src.matched)
+ end
+ end
+ define_parser(:footnote_marker, FOOTNOTE_MARKER_START, '\[')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/header.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/header.rb
new file mode 100644
index 0000000..8424af3
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/header.rb
@@ -0,0 +1,70 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/block_boundary'
+require 'rexml/xmltokens'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ SETEXT_HEADER_START = /^#{OPT_SPACE}(?<contents>[^ \t].*)\n(?<level>[-=])[-=]*[ \t\r\f\v]*\n/
+
+ # Parse the Setext header at the current location.
+ def parse_setext_header
+ return false unless after_block_boundary?
+ text, id = parse_header_contents
+ return false if text.empty?
+ add_header(@src["level"] == '-' ? 2 : 1, text, id)
+ true
+ end
+ define_parser(:setext_header, SETEXT_HEADER_START)
+
+ ATX_HEADER_START = /^(?<level>\#{1,6})[\t ]*(?<contents>[^ \t].*)\n/
+
+ # Parse the Atx header at the current location.
+ def parse_atx_header
+ return false unless after_block_boundary?
+ text, id = parse_header_contents
+ text.sub!(/(?<!\\)#+\z/, '') && text.rstrip!
+ return false if text.empty?
+ add_header(@src["level"].length, text, id)
+ true
+ end
+ define_parser(:atx_header, ATX_HEADER_START)
+
+ protected
+
+ HEADER_ID = /[\t ]{#(?<id>#{REXML::XMLTokens::NAME_START_CHAR}#{REXML::XMLTokens::NAME_CHAR}*)}\z/
+
+ # Returns header text and optional ID.
+ def parse_header_contents
+ text = @src["contents"]
+ text.rstrip!
+ id_match = HEADER_ID.match(text)
+ if id_match
+ id = id_match["id"]
+ text = text[0...-id_match[0].length]
+ text.rstrip!
+ end
+ [text, id]
+ end
+
+ def add_header(level, text, id)
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ el = new_block_el(:header, nil, nil, level: level, raw_text: text, location: start_line_number)
+ add_text(text, el)
+ el.attr['id'] = id if id
+ @tree.children << el
+ end
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/horizontal_rule.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/horizontal_rule.rb
new file mode 100644
index 0000000..f2aa6a4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/horizontal_rule.rb
@@ -0,0 +1,27 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ HR_START = /^#{OPT_SPACE}(\*|-|_)[ \t]*\1[ \t]*\1(\1|[ \t])*\n/
+
+ # Parse the horizontal rule at the current location.
+ def parse_horizontal_rule
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ @tree.children << new_block_el(:hr, nil, nil, location: start_line_number)
+ true
+ end
+ define_parser(:horizontal_rule, HR_START)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html.rb
new file mode 100644
index 0000000..37e6b4c
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html.rb
@@ -0,0 +1,165 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/html'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ include Kramdown::Parser::Html::Parser
+ include Kramdown::Utils::Html
+
+ # Mapping of markdown attribute value to content model. I.e. :raw when "0", :default when "1"
+ # (use default content model for the HTML element), :span when "span", :block when block and
+ # for everything else +nil+ is returned.
+ HTML_MARKDOWN_ATTR_MAP = {"0" => :raw, "1" => :default, "span" => :span, "block" => :block}
+
+ TRAILING_WHITESPACE = /[ \t]*\n/
+
+ def handle_kramdown_html_tag(el, closed, handle_body)
+ if @block_ial
+ el.options[:ial] = @block_ial
+ @block_ial = nil
+ end
+
+ content_model = if @tree.type != :html_element || @tree.options[:content_model] != :raw
+ (@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw)
+ else
+ :raw
+ end
+ if (val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')])
+ content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val)
+ end
+
+ @src.scan(TRAILING_WHITESPACE) if content_model == :block
+ el.options[:content_model] = content_model
+ el.options[:is_closed] = closed
+
+ if !closed && handle_body
+ case content_model
+ when :block
+ unless parse_blocks(el)
+ warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
+ end
+ when :span
+ curpos = @src.pos
+ if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
+ add_text(extract_string(curpos...@src.pos, @src), el)
+ @src.scan(HTML_TAG_CLOSE_RE)
+ else
+ add_text(@src.rest, el)
+ @src.terminate
+ warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
+ end
+ else
+ parse_raw_html(el) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
+ end
+ unless @tree.type == :html_element && @tree.options[:content_model] == :raw
+ @src.scan(TRAILING_WHITESPACE)
+ end
+ end
+ end
+
+ HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/
+
+ # Parse the HTML at the current position as block-level HTML.
+ def parse_block_html
+ line = @src.current_line_number
+ if (result = @src.scan(HTML_COMMENT_RE))
+ @tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
+ @src.scan(TRAILING_WHITESPACE)
+ true
+ elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
+ @src.pos += @src.matched_size
+ handle_html_start_tag(line) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
+ Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
+ true
+ elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
+ name = @src[1].downcase
+
+ if @tree.type == :html_element && @tree.value == name
+ @src.pos += @src.matched_size
+ throw :stop_block_parsing, :found
+ else
+ false
+ end
+ else
+ false
+ end
+ end
+ define_parser(:block_html, HTML_BLOCK_START)
+
+ HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/|!\[CDATA\[)/
+
+ # Parse the HTML at the current position as span-level HTML.
+ def parse_span_html
+ line = @src.current_line_number
+ if (result = @src.scan(HTML_COMMENT_RE))
+ @tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
+ elsif @src.scan(HTML_CDATA_RE)
+ add_text(escape_html(@src[1]))
+ elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
+ warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
+ add_text(result)
+ elsif (result = @src.scan(HTML_TAG_RE))
+ tag_name = @src[1]
+ tag_name.downcase! if HTML_ELEMENT[tag_name.downcase]
+ if HTML_BLOCK_ELEMENTS.include?(tag_name)
+ warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
+ add_text(result)
+ return
+ end
+
+ attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[tag_name])
+ attrs.each_value {|value| value.gsub!(/\n+/, ' ') unless value.empty? }
+
+ do_parsing = if HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw
+ false
+ else
+ @options[:parse_span_html]
+ end
+ if (val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')])
+ case val
+ when :block
+ warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) " \
+ "- using default mode")
+ when :span
+ do_parsing = true
+ when :default
+ do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
+ when :raw
+ do_parsing = false
+ end
+ end
+
+ el = Element.new(:html_element, tag_name, attrs, category: :span, location: line,
+ content_model: (do_parsing ? :span : :raw), is_closed: !@src[4].nil?)
+ @tree.children << el
+ stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
+ stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
+ if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
+ if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
+ @src.scan(stop_re)
+ else
+ warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
+ add_text(@src.rest, el)
+ @src.terminate
+ end
+ end
+ Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
+ else
+ add_text(@src.getch)
+ end
+ end
+ define_parser(:span_html, HTML_SPAN_START, '<')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html_entity.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html_entity.rb
new file mode 100644
index 0000000..dc5e7ad
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html_entity.rb
@@ -0,0 +1,34 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/html'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ # Parse the HTML entity at the current location.
+ def parse_html_entity
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ begin
+ value = ::Kramdown::Utils::Entities.entity(@src[1] || @src[2]&.to_i || @src[3].hex)
+ @tree.children << Element.new(:entity, value,
+ nil, original: @src.matched, location: start_line_number)
+ rescue ::Kramdown::Error
+ @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'),
+ nil, location: start_line_number)
+ add_text(@src.matched[1..-1])
+ end
+ end
+ define_parser(:html_entity, Kramdown::Parser::Html::Constants::HTML_ENTITY_RE, '&')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/line_break.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/line_break.rb
new file mode 100644
index 0000000..021bb0a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/line_break.rb
@@ -0,0 +1,25 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ LINE_BREAK = /( |\\\\)(?=\n)/
+
+ # Parse the line break at the current location.
+ def parse_line_break
+ @tree.children << Element.new(:br, nil, nil, location: @src.current_line_number)
+ @src.pos += @src.matched_size
+ end
+ define_parser(:line_break, LINE_BREAK, '( |\\\\)(?=\n)')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/link.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/link.rb
new file mode 100644
index 0000000..f4664f3
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/link.rb
@@ -0,0 +1,149 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/escaped_chars'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ # Normalize the link identifier.
+ def normalize_link_id(id)
+ id.gsub(/\s+/, ' ').downcase
+ end
+
+ LINK_DEFINITION_START = /^#{OPT_SPACE}\[([^\n\]]+)\]:[ \t]*(?:<(.*?)>|([^\n]*?\S[^\n]*?))(?:(?:[ \t]*?\n|[ \t]+?)[ \t]*?(["'])(.+?)\4)?[ \t]*?\n/
+
+ # Parse the link definition at the current location.
+ def parse_link_definition
+ return false if @src[3].to_s.match?(/[ \t]+["']/)
+ @src.pos += @src.matched_size
+ link_id, link_url, link_title = normalize_link_id(@src[1]), @src[2] || @src[3], @src[5]
+ if @link_defs[link_id]
+ warning("Duplicate link ID '#{link_id}' on line #{@src.current_line_number} - overwriting")
+ end
+ @tree.children << new_block_el(:eob, :link_def)
+ @link_defs[link_id] = [link_url, link_title, @tree.children.last]
+ true
+ end
+ define_parser(:link_definition, LINK_DEFINITION_START)
+
+ # This helper methods adds the approriate attributes to the element +el+ of type +a+ or +img+
+ # and the element itself to the @tree.
+ def add_link(el, href, title, alt_text = nil, ial = nil)
+ el.options[:ial] = ial
+ update_attr_with_ial(el.attr, ial) if ial
+ if el.type == :a
+ el.attr['href'] = href
+ else
+ el.attr['src'] = href
+ el.attr['alt'] = alt_text
+ el.children.clear
+ end
+ el.attr['title'] = title if title
+ @tree.children << el
+ end
+
+ LINK_BRACKET_STOP_RE = /(\])|!?\[/
+ LINK_PAREN_STOP_RE = /(\()|(\))|\s(?=['"])/
+ LINK_INLINE_ID_RE = /\s*?\[([^\]]+)?\]/
+ LINK_INLINE_TITLE_RE = /\s*?(["'])(.+?)\1\s*?\)/m
+ LINK_START = /!?\[(?=[^^])/
+
+ # Parse the link at the current scanner position. This method is used to parse normal links as
+ # well as image links.
+ def parse_link
+ start_line_number = @src.current_line_number
+ result = @src.scan(LINK_START)
+ cur_pos = @src.pos
+ saved_pos = @src.save_pos
+
+ link_type = (result.match?(/^!/) ? :img : :a)
+
+ # no nested links allowed
+ if link_type == :a && (@tree.type == :img || @tree.type == :a ||
+ @stack.any? {|t, _| t && (t.type == :img || t.type == :a) })
+ add_text(result)
+ return
+ end
+ el = Element.new(link_type, nil, nil, location: start_line_number)
+
+ count = 1
+ found = parse_spans(el, LINK_BRACKET_STOP_RE) do
+ count += (@src[1] ? -1 : 1)
+ count - el.children.count {|c| c.type == :img } == 0
+ end
+ unless found
+ @src.revert_pos(saved_pos)
+ add_text(result)
+ return
+ end
+ alt_text = extract_string(cur_pos...@src.pos, @src).gsub(ESCAPED_CHARS, '\1')
+ @src.scan(LINK_BRACKET_STOP_RE)
+
+ # reference style link or no link url
+ if @src.scan(LINK_INLINE_ID_RE) || !@src.check(/\(/)
+ emit_warning = !@src[1]
+ link_id = normalize_link_id(@src[1] || alt_text)
+ if @link_defs.key?(link_id)
+ link_def = @link_defs[link_id]
+ add_link(el, link_def[0], link_def[1], alt_text,
+ link_def[2] && link_def[2].options[:ial])
+ else
+ if emit_warning
+ warning("No link definition for link ID '#{link_id}' found on line #{start_line_number}")
+ end
+ @src.revert_pos(saved_pos)
+ add_text(result)
+ end
+ return
+ end
+
+ # link url in parentheses
+ if @src.scan(/\(<(.*?)>/)
+ link_url = @src[1]
+ if @src.scan(/\)/)
+ add_link(el, link_url, nil, alt_text)
+ return
+ end
+ else
+ link_url = +''
+ nr_of_brackets = 0
+ while (temp = @src.scan_until(LINK_PAREN_STOP_RE))
+ link_url << temp
+ if @src[2]
+ nr_of_brackets -= 1
+ break if nr_of_brackets == 0
+ elsif @src[1]
+ nr_of_brackets += 1
+ else
+ break
+ end
+ end
+ link_url = link_url[1..-2]
+ link_url.strip!
+
+ if nr_of_brackets == 0
+ add_link(el, link_url, nil, alt_text)
+ return
+ end
+ end
+
+ if @src.scan(LINK_INLINE_TITLE_RE)
+ add_link(el, link_url, @src[2], alt_text)
+ else
+ @src.revert_pos(saved_pos)
+ add_text(result)
+ end
+ end
+ define_parser(:link, LINK_START, '!?\[')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/list.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/list.rb
new file mode 100644
index 0000000..3db5409
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/list.rb
@@ -0,0 +1,286 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/eob'
+require 'kramdown/parser/kramdown/horizontal_rule'
+require 'kramdown/parser/kramdown/extensions'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ LIST_ITEM_IAL = /^\s*(?:\{:(?!(?:#{ALD_ID_NAME})?:|\/)(#{ALD_ANY_CHARS}+)\})\s*/
+ LIST_ITEM_IAL_CHECK = /^#{LIST_ITEM_IAL}?\s*\n/
+
+ PARSE_FIRST_LIST_LINE_REGEXP_CACHE = Hash.new do |h, indentation|
+ indent_re = /^ {#{indentation}}/
+ content_re = /^(?:(?:\t| {4}){#{indentation / 4}} {#{indentation % 4}}|(?:\t| {4}){#{indentation / 4 + 1}}).*\S.*\n/
+ lazy_re = /(?!^ {0,#{[indentation, 3].min}}(?:#{IAL_BLOCK}|#{LAZY_END_HTML_STOP}|#{LAZY_END_HTML_START})).*\S.*\n/
+
+ h[indentation] = [content_re, lazy_re, indent_re]
+ end
+
+ # Used for parsing the first line of a list item or a definition, i.e. the line with list item
+ # marker or the definition marker.
+ def parse_first_list_line(indentation, content)
+ if content.match?(self.class::LIST_ITEM_IAL_CHECK)
+ indentation = 4
+ else
+ while content.match?(/^ *\t/)
+ temp = content.scan(/^ */).first.length + indentation
+ content.sub!(/^( *)(\t+)/) { $1 << " " * (4 - (temp % 4) + ($2.length - 1) * 4) }
+ end
+ indentation += content[/^ */].length
+ end
+ content.sub!(/^\s*/, '')
+
+ [content, indentation, *PARSE_FIRST_LIST_LINE_REGEXP_CACHE[indentation]]
+ end
+
+ PATTERN_TAIL = /[\t| ].*?\n/
+
+ LIST_START_UL = /^(#{OPT_SPACE}[+*-])(#{PATTERN_TAIL})/
+ LIST_START_OL = /^(#{OPT_SPACE}\d+\.)(#{PATTERN_TAIL})/
+ LIST_START = /#{LIST_START_UL}|#{LIST_START_OL}/
+
+ # Parse the ordered or unordered list at the current location.
+ def parse_list
+ start_line_number = @src.current_line_number
+ type, list_start_re = (@src.check(LIST_START_UL) ? [:ul, LIST_START_UL] : [:ol, LIST_START_OL])
+ list = new_block_el(type, nil, nil, location: start_line_number)
+
+ item = nil
+ content_re, lazy_re, indent_re = nil
+ eob_found = false
+ nested_list_found = false
+ last_is_blank = false
+ until @src.eos?
+ start_line_number = @src.current_line_number
+ if last_is_blank && @src.check(HR_START)
+ break
+ elsif @src.scan(EOB_MARKER)
+ eob_found = true
+ break
+ elsif @src.scan(list_start_re)
+ list.options[:first_list_marker] ||= @src[1].strip
+ item = Element.new(:li, nil, nil, location: start_line_number)
+ item.value, indentation, content_re, lazy_re, indent_re =
+ parse_first_list_line(@src[1].length, @src[2])
+ list.children << item
+
+ item.value.sub!(self.class::LIST_ITEM_IAL) do
+ parse_attribute_list($1, item.options[:ial] ||= {})
+ ''
+ end
+
+ list_start_re = fetch_pattern(type, indentation)
+ nested_list_found = (item.value =~ LIST_START)
+ last_is_blank = false
+ item.value = [item.value]
+ elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
+ result.sub!(/^(\t+)/) { " " * 4 * $1.length }
+ indentation_found = result.sub!(indent_re, '')
+ if !nested_list_found && indentation_found && result =~ LIST_START
+ item.value << +''
+ nested_list_found = true
+ elsif nested_list_found && !indentation_found && result =~ LIST_START
+ result = " " * (indentation + 4) << result
+ end
+ item.value.last << result
+ last_is_blank = false
+ elsif (result = @src.scan(BLANK_LINE))
+ nested_list_found = true
+ last_is_blank = true
+ item.value.last << result
+ else
+ break
+ end
+ end
+
+ @tree.children << list
+
+ last = nil
+ list.children.each do |it|
+ temp = Element.new(:temp, nil, nil, location: it.options[:location])
+
+ env = save_env
+ location = it.options[:location]
+ it.value.each do |val|
+ @src = ::Kramdown::Utils::StringScanner.new(val, location)
+ parse_blocks(temp)
+ location = @src.current_line_number
+ end
+ restore_env(env)
+
+ it.children = temp.children
+ it.value = nil
+
+ it_children = it.children
+ next if it_children.empty?
+
+ # Handle the case where an EOB marker is inserted by a block IAL for the first paragraph
+ it_children.delete_at(1) if it_children.first.type == :p &&
+ it_children.length >= 2 && it_children[1].type == :eob && it_children.first.options[:ial]
+
+ if it_children.first.type == :p &&
+ (it_children.length < 2 || it_children[1].type != :blank ||
+ (it == list.children.last && it_children.length == 2 && !eob_found)) &&
+ (list.children.last != it || list.children.size == 1 ||
+ list.children[0..-2].any? {|cit| !cit.children.first || cit.children.first.type != :p || cit.children.first.options[:transparent] })
+ it_children.first.children.first.value << "\n" if it_children.size > 1 && it_children[1].type != :blank
+ it_children.first.options[:transparent] = true
+ end
+
+ last = (it_children.last.type == :blank ? it_children.pop : nil)
+ end
+
+ @tree.children << last if !last.nil? && !eob_found
+
+ true
+ end
+ define_parser(:list, LIST_START)
+
+ DEFINITION_LIST_START = /^(#{OPT_SPACE}:)(#{PATTERN_TAIL})/
+
+ # Parse the ordered or unordered list at the current location.
+ def parse_definition_list
+ children = @tree.children
+ if !children.last || (children.length == 1 && children.last.type != :p) ||
+ (children.length >= 2 && children[-1].type != :p &&
+ (children[-1].type != :blank || children[-1].value != "\n" || children[-2].type != :p))
+ return false
+ end
+
+ first_as_para = false
+ deflist = new_block_el(:dl)
+ para = @tree.children.pop
+ if para.type == :blank
+ para = @tree.children.pop
+ first_as_para = true
+ end
+ # take location from preceding para which is the first definition term
+ deflist.options[:location] = para.options[:location]
+ para.children.first.value.split("\n").each do |term|
+ el = Element.new(:dt, nil, nil, location: @src.current_line_number)
+ term.sub!(self.class::LIST_ITEM_IAL) do
+ parse_attribute_list($1, el.options[:ial] ||= {})
+ ''
+ end
+ el.options[:raw_text] = term
+ el.children << Element.new(:raw_text, term)
+ deflist.children << el
+ end
+ deflist.options[:ial] = para.options[:ial]
+
+ item = nil
+ content_re, lazy_re, indent_re = nil
+ def_start_re = DEFINITION_LIST_START
+ last_is_blank = false
+ until @src.eos?
+ start_line_number = @src.current_line_number
+ if @src.scan(def_start_re)
+ item = Element.new(:dd, nil, nil, location: start_line_number)
+ item.options[:first_as_para] = first_as_para
+ item.value, indentation, content_re, lazy_re, indent_re =
+ parse_first_list_line(@src[1].length, @src[2])
+ deflist.children << item
+
+ item.value.sub!(self.class::LIST_ITEM_IAL) do |_match|
+ parse_attribute_list($1, item.options[:ial] ||= {})
+ ''
+ end
+
+ def_start_re = fetch_pattern(:dl, indentation)
+ first_as_para = false
+ last_is_blank = false
+ elsif @src.check(EOB_MARKER)
+ break
+ elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
+ result.sub!(/^(\t+)/) { " " * ($1 ? 4 * $1.length : 0) }
+ result.sub!(indent_re, '')
+ item.value << result
+ first_as_para = false
+ last_is_blank = false
+ elsif (result = @src.scan(BLANK_LINE))
+ first_as_para = true
+ item.value << result
+ last_is_blank = true
+ else
+ break
+ end
+ end
+
+ last = nil
+ deflist.children.each do |it|
+ next if it.type == :dt
+
+ parse_blocks(it, it.value)
+ it.value = nil
+ it_children = it.children
+ next if it_children.empty?
+
+ last = (it_children.last.type == :blank ? it_children.pop : nil)
+
+ if it_children.first && it_children.first.type == :p && !it.options.delete(:first_as_para)
+ it_children.first.children.first.value << "\n" if it_children.size > 1
+ it_children.first.options[:transparent] = true
+ end
+ end
+
+ children = @tree.children
+ if children.length >= 1 && children.last.type == :dl
+ children[-1].children.concat(deflist.children)
+ elsif children.length >= 2 && children[-1].type == :blank &&
+ children[-2].type == :dl
+ children.pop
+ children[-1].children.concat(deflist.children)
+ else
+ children << deflist
+ end
+
+ children << last if last
+
+ true
+ end
+ define_parser(:definition_list, DEFINITION_LIST_START)
+
+ private
+
+ # precomputed patterns for indentations 1..4 and fallback expression
+ # to compute pattern when indentation is outside the 1..4 range.
+ def fetch_pattern(type, indentation)
+ case type
+ when :ul
+ case indentation
+ when 1 then /^( {0}[+*-])(#{PATTERN_TAIL})/o
+ when 2 then /^( {0,1}[+*-])(#{PATTERN_TAIL})/o
+ when 3 then /^( {0,2}[+*-])(#{PATTERN_TAIL})/o
+ else /^( {0,3}[+*-])(#{PATTERN_TAIL})/o
+ end
+ when :ol
+ case indentation
+ when 1 then /^( {0}\d+\.)(#{PATTERN_TAIL})/o
+ when 2 then /^( {0,1}\d+\.)(#{PATTERN_TAIL})/o
+ when 3 then /^( {0,2}\d+\.)(#{PATTERN_TAIL})/o
+ else /^( {0,3}\d+\.)(#{PATTERN_TAIL})/o
+ end
+ when :dl
+ case indentation
+ when 1 then /^( {0}:)(#{PATTERN_TAIL})/o
+ when 2 then /^( {0,1}:)(#{PATTERN_TAIL})/o
+ when 3 then /^( {0,2}:)(#{PATTERN_TAIL})/o
+ else /^( {0,3}:)(#{PATTERN_TAIL})/o
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/math.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/math.rb
new file mode 100644
index 0000000..7812292
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/math.rb
@@ -0,0 +1,53 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/block_boundary'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ BLOCK_MATH_START = /^#{OPT_SPACE}(\\)?\$\$(.*?)\$\$(\s*?\n)?/m
+
+ # Parse the math block at the current location.
+ def parse_block_math
+ start_line_number = @src.current_line_number
+ if !after_block_boundary?
+ return false
+ elsif @src[1]
+ @src.scan(/^#{OPT_SPACE}\\/o) if @src[3]
+ return false
+ end
+
+ saved_pos = @src.save_pos
+ @src.pos += @src.matched_size
+ data = @src[2].strip
+ if before_block_boundary?
+ @tree.children << new_block_el(:math, data, nil, category: :block, location: start_line_number)
+ true
+ else
+ @src.revert_pos(saved_pos)
+ false
+ end
+ end
+ define_parser(:block_math, BLOCK_MATH_START)
+
+ INLINE_MATH_START = /\$\$(.*?)\$\$/m
+
+ # Parse the inline math at the current location.
+ def parse_inline_math
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ @tree.children << Element.new(:math, @src[1].strip, nil, category: :span, location: start_line_number)
+ end
+ define_parser(:inline_math, INLINE_MATH_START, '\$')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/paragraph.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/paragraph.rb
new file mode 100644
index 0000000..f6af5ce
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/paragraph.rb
@@ -0,0 +1,62 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/blank_line'
+require 'kramdown/parser/kramdown/extensions'
+require 'kramdown/parser/kramdown/eob'
+require 'kramdown/parser/kramdown/list'
+require 'kramdown/parser/kramdown/html'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ LAZY_END_HTML_SPAN_ELEMENTS = HTML_SPAN_ELEMENTS + %w[script]
+ LAZY_END_HTML_START = /<(?>(?!(?:#{LAZY_END_HTML_SPAN_ELEMENTS.join('|')})\b)#{REXML::Parsers::BaseParser::UNAME_STR})/
+ LAZY_END_HTML_STOP = /<\/(?!(?:#{LAZY_END_HTML_SPAN_ELEMENTS.join('|')})\b)#{REXML::Parsers::BaseParser::UNAME_STR}\s*>/m
+
+ LAZY_END = /#{BLANK_LINE}|#{IAL_BLOCK_START}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|^#{OPT_SPACE}#{LAZY_END_HTML_START}|\Z/
+
+ PARAGRAPH_START = /^#{OPT_SPACE}[^ \t].*?\n/
+ PARAGRAPH_MATCH = /^.*?\n/
+ PARAGRAPH_END = /#{LAZY_END}|#{DEFINITION_LIST_START}/
+
+ # Parse the paragraph at the current location.
+ def parse_paragraph
+ pos = @src.pos
+ start_line_number = @src.current_line_number
+ result = @src.scan(PARAGRAPH_MATCH)
+ until @src.match?(paragraph_end)
+ result << @src.scan(PARAGRAPH_MATCH)
+ end
+ result.rstrip!
+ if (last_child = @tree.children.last) && last_child.type == :p
+ last_item_in_para = last_child.children.last
+ if last_item_in_para && last_item_in_para.type == @text_type
+ joiner = (extract_string((pos - 3)...pos, @src) == " \n" ? " \n" : "\n")
+ last_item_in_para.value << joiner << result
+ else
+ add_text(result, last_child)
+ end
+ else
+ @tree.children << new_block_el(:p, nil, nil, location: start_line_number)
+ result.lstrip!
+ add_text(result, @tree.children.last)
+ end
+ true
+ end
+ define_parser(:paragraph, PARAGRAPH_START)
+
+ def paragraph_end
+ self.class::PARAGRAPH_END
+ end
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/smart_quotes.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/smart_quotes.rb
new file mode 100644
index 0000000..3a9026a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/smart_quotes.rb
@@ -0,0 +1,174 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+#--
+# Parts of this file are based on code from RubyPants:
+#
+# = RubyPants -- SmartyPants ported to Ruby
+#
+# Ported by Christian Neukirchen <mailto:chneukirchen@gmail.com>
+# Copyright (C) 2004 Christian Neukirchen
+#
+# Incooporates ideas, comments and documentation by Chad Miller
+# Copyright (C) 2004 Chad Miller
+#
+# Original SmartyPants by John Gruber
+# Copyright (C) 2003 John Gruber
+#
+#
+# = RubyPants -- SmartyPants ported to Ruby
+#
+#
+# [snip]
+#
+# == Authors
+#
+# John Gruber did all of the hard work of writing this software in
+# Perl for Movable Type and almost all of this useful documentation.
+# Chad Miller ported it to Python to use with Pyblosxom.
+#
+# Christian Neukirchen provided the Ruby port, as a general-purpose
+# library that follows the *Cloth API.
+#
+#
+# == Copyright and License
+#
+# === SmartyPants license:
+#
+# Copyright (c) 2003 John Gruber
+# (http://daringfireball.net)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# * Neither the name "SmartyPants" nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# This software is provided by the copyright holders and contributors
+# "as is" and any express or implied warranties, including, but not
+# limited to, the implied warranties of merchantability and fitness
+# for a particular purpose are disclaimed. In no event shall the
+# copyright owner or contributors be liable for any direct, indirect,
+# incidental, special, exemplary, or consequential damages (including,
+# but not limited to, procurement of substitute goods or services;
+# loss of use, data, or profits; or business interruption) however
+# caused and on any theory of liability, whether in contract, strict
+# liability, or tort (including negligence or otherwise) arising in
+# any way out of the use of this software, even if advised of the
+# possibility of such damage.
+#
+# === RubyPants license
+#
+# RubyPants is a derivative work of SmartyPants and smartypants.py.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# This software is provided by the copyright holders and contributors
+# "as is" and any express or implied warranties, including, but not
+# limited to, the implied warranties of merchantability and fitness
+# for a particular purpose are disclaimed. In no event shall the
+# copyright owner or contributors be liable for any direct, indirect,
+# incidental, special, exemplary, or consequential damages (including,
+# but not limited to, procurement of substitute goods or services;
+# loss of use, data, or profits; or business interruption) however
+# caused and on any theory of liability, whether in contract, strict
+# liability, or tort (including negligence or otherwise) arising in
+# any way out of the use of this software, even if advised of the
+# possibility of such damage.
+#
+# == Links
+#
+# John Gruber:: http://daringfireball.net
+# SmartyPants:: http://daringfireball.net/projects/smartypants
+#
+# Chad Miller:: http://web.chad.org
+#
+# Christian Neukirchen:: http://kronavita.de/chris
+#
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ SQ_PUNCT = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
+ SQ_CLOSE = %![^ \\\\\t\r\n\\[{(-]!
+
+ SQ_RULES = [
+ [/("|')(?=[_*]{1,2}\S)/, [:lquote1]],
+ [/("|')(?=#{SQ_PUNCT}(?!\.\.)\B)/, [:rquote1]],
+ # Special case for double sets of quotes, e.g.:
+ # <p>He said, "'Quoted' words in a larger quote."</p>
+ [/(\s?)"'(?=\w)/, [1, :ldquo, :lsquo]],
+ [/(\s?)'"(?=\w)/, [1, :lsquo, :ldquo]],
+ # Special case for decade abbreviations (the '80s):
+ [/(\s?)'(?=\d\ds)/, [1, :rsquo]],
+
+ # Get most opening single/double quotes:
+ [/(\s)('|")(?=\w)/, [1, :lquote2]],
+ # Single/double closing quotes:
+ [/(#{SQ_CLOSE})('|")/, [1, :rquote2]],
+ # Special case for e.g. "<i>Custer</i>'s Last Stand."
+ [/("|')(?=\s|s\b|$)/, [:rquote1]],
+ # Any remaining single quotes should be opening ones:
+ [/(.?)'/m, [1, :lsquo]],
+ [/(.?)"/m, [1, :ldquo]],
+ ] # '"
+
+ SQ_SUBSTS = {
+ [:rquote1, '"'] => :rdquo,
+ [:rquote1, "'"] => :rsquo,
+ [:rquote2, '"'] => :rdquo,
+ [:rquote2, "'"] => :rsquo,
+ [:lquote1, '"'] => :ldquo,
+ [:lquote1, "'"] => :lsquo,
+ [:lquote2, '"'] => :ldquo,
+ [:lquote2, "'"] => :lsquo,
+ }
+ SMART_QUOTES_RE = /[^\\]?["']/
+
+ # Parse the smart quotes at current location.
+ def parse_smart_quotes
+ start_line_number = @src.current_line_number
+ substs = SQ_RULES.find {|reg, _subst| @src.scan(reg) }[1]
+ substs.each do |subst|
+ if subst.kind_of?(Integer)
+ add_text(@src[subst])
+ else
+ val = SQ_SUBSTS[[subst, @src[subst.to_s[-1, 1].to_i]]] || subst
+ @tree.children << Element.new(:smart_quote, val, nil, location: start_line_number)
+ end
+ end
+ end
+ define_parser(:smart_quotes, SMART_QUOTES_RE, '[^\\\\]?["\']')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/table.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/table.rb
new file mode 100644
index 0000000..53103ce
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/table.rb
@@ -0,0 +1,171 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser/kramdown/block_boundary'
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ TABLE_SEP_LINE = /^([+|: \t-]*?-[+|: \t-]*?)[ \t]*\n/
+ TABLE_HSEP_ALIGN = /[ \t]?(:?)-+(:?)[ \t]?/
+ TABLE_FSEP_LINE = /^[+|: \t=]*?=[+|: \t=]*?[ \t]*\n/
+ TABLE_ROW_LINE = /^(.*?)[ \t]*\n/
+ TABLE_PIPE_CHECK = /(?:\||.*?[^\\\n]\|)/
+ TABLE_LINE = /#{TABLE_PIPE_CHECK}.*?\n/
+ TABLE_START = /^#{OPT_SPACE}(?=\S)#{TABLE_LINE}/
+
+ # Parse the table at the current location.
+ def parse_table
+ return false unless after_block_boundary?
+
+ saved_pos = @src.save_pos
+ orig_pos = @src.pos
+ table = new_block_el(:table, nil, nil, alignment: [], location: @src.current_line_number)
+ leading_pipe = (@src.check(TABLE_LINE) =~ /^\s*\|/)
+ @src.scan(TABLE_SEP_LINE)
+
+ rows = []
+ has_footer = false
+ columns = 0
+
+ add_container = lambda do |type, force|
+ if !has_footer || type != :tbody || force
+ cont = Element.new(type)
+ cont.children, rows = rows, []
+ table.children << cont
+ end
+ end
+
+ until @src.eos?
+ break unless @src.check(TABLE_LINE)
+ if @src.scan(TABLE_SEP_LINE)
+ if rows.empty?
+ # nothing to do, ignoring multiple consecutive separator lines
+ elsif table.options[:alignment].empty? && !has_footer
+ add_container.call(:thead, false)
+ table.options[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right|
+ (left.empty? && right.empty? && :default) || (right.empty? && :left) ||
+ (left.empty? && :right) || :center
+ end
+ else # treat as normal separator line
+ add_container.call(:tbody, false)
+ end
+ elsif @src.scan(TABLE_FSEP_LINE)
+ add_container.call(:tbody, true) unless rows.empty?
+ has_footer = true
+ elsif @src.scan(TABLE_ROW_LINE)
+ trow = Element.new(:tr)
+
+ # parse possible code spans on the line and correctly split the line into cells
+ env = save_env
+ cells = []
+ @src[1].split(/(<code.*?>.*?<\/code>)/).each_with_index do |str, i|
+ if i.odd?
+ (cells.empty? ? cells : cells.last) << str
+ else
+ reset_env(src: Kramdown::Utils::StringScanner.new(str, @src.current_line_number))
+ root = Element.new(:root)
+ parse_spans(root, nil, [:codespan])
+
+ root.children.each do |c|
+ if c.type == :raw_text
+ f, *l = c.value.split(/(?<!\\)\|/, -1).map {|t| t.gsub(/\\\|/, '|') }
+ (cells.empty? ? cells : cells.last) << f
+ cells.concat(l)
+ else
+ delim = (c.value.scan(/`+/).max || '') + '`'
+ tmp = +"#{delim}#{' ' if delim.size > 1}#{c.value}#{' ' if delim.size > 1}#{delim}"
+ (cells.empty? ? cells : cells.last) << tmp
+ end
+ end
+ end
+ end
+ restore_env(env)
+
+ cells.shift if leading_pipe && cells.first.strip.empty?
+ cells.pop if cells.last.strip.empty?
+ cells.each do |cell_text|
+ tcell = Element.new(:td)
+ tcell.children << Element.new(:raw_text, cell_text.strip)
+ trow.children << tcell
+ end
+ columns = [columns, cells.length].max
+ rows << trow
+ else
+ break
+ end
+ end
+
+ unless before_block_boundary?
+ @src.revert_pos(saved_pos)
+ return false
+ end
+
+ # Parse all lines of the table with the code span parser
+ env = save_env
+ l_src = ::Kramdown::Utils::StringScanner.new(extract_string(orig_pos...(@src.pos - 1), @src),
+ @src.current_line_number)
+ reset_env(src: l_src)
+ root = Element.new(:root)
+ parse_spans(root, nil, [:codespan, :span_html])
+ restore_env(env)
+
+ # Check if each line has at least one unescaped pipe that is not inside a code span/code
+ # HTML element
+ # Note: It doesn't matter that we parse *all* span HTML elements because the row splitting
+ # algorithm above only takes <code> elements into account!
+ pipe_on_line = false
+ while (c = root.children.shift)
+ next unless (lines = c.value)
+ lines = lines.split("\n")
+ if c.type == :codespan
+ if lines.size > 2 || (lines.size == 2 && !pipe_on_line)
+ break
+ elsif lines.size == 2 && pipe_on_line
+ pipe_on_line = false
+ end
+ else
+ break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/o
+ pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/o)
+ end
+ end
+ @src.revert_pos(saved_pos) and return false unless pipe_on_line
+
+ add_container.call(has_footer ? :tfoot : :tbody, false) unless rows.empty?
+
+ if table.children.none? {|el| el.type == :tbody }
+ warning("Found table without body on line #{table.options[:location]} - ignoring it")
+ @src.revert_pos(saved_pos)
+ return false
+ end
+
+ # adjust all table rows to have equal number of columns, same for alignment defs
+ table.children.each do |kind|
+ kind.children.each do |row|
+ (columns - row.children.length).times do
+ row.children << Element.new(:td)
+ end
+ end
+ end
+ if table.options[:alignment].length > columns
+ table.options[:alignment] = table.options[:alignment][0...columns]
+ else
+ table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
+ end
+
+ @tree.children << table
+
+ true
+ end
+ define_parser(:table, TABLE_START)
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/typographic_symbol.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/typographic_symbol.rb
new file mode 100644
index 0000000..d8bbc63
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/typographic_symbol.rb
@@ -0,0 +1,44 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+module Kramdown
+ module Parser
+ class Kramdown
+
+ TYPOGRAPHIC_SYMS = [['---', :mdash], ['--', :ndash], ['...', :hellip],
+ ['\\<<', '&lt;&lt;'], ['\\>>', '&gt;&gt;'],
+ ['<< ', :laquo_space], [' >>', :raquo_space],
+ ['<<', :laquo], ['>>', :raquo]]
+ TYPOGRAPHIC_SYMS_SUBST = Hash[*TYPOGRAPHIC_SYMS.flatten]
+ TYPOGRAPHIC_SYMS_RE = /#{TYPOGRAPHIC_SYMS.map {|k, _v| Regexp.escape(k) }.join('|')}/
+
+ # Parse the typographic symbols at the current location.
+ def parse_typographic_syms
+ start_line_number = @src.current_line_number
+ @src.pos += @src.matched_size
+ val = TYPOGRAPHIC_SYMS_SUBST[@src.matched]
+ if val.kind_of?(Symbol)
+ @tree.children << Element.new(:typographic_sym, val, nil, location: start_line_number)
+ elsif @src.matched == '\\<<'
+ @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'),
+ nil, location: start_line_number)
+ @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'),
+ nil, location: start_line_number)
+ else
+ @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'),
+ nil, location: start_line_number)
+ @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'),
+ nil, location: start_line_number)
+ end
+ end
+ define_parser(:typographic_syms, TYPOGRAPHIC_SYMS_RE, '--|\\.\\.\\.|(?:\\\\| )?(?:<<|>>)')
+
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/markdown.rb b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/markdown.rb
new file mode 100644
index 0000000..8902029
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/markdown.rb
@@ -0,0 +1,57 @@
+# -*- coding: utf-8; frozen_string_literal: true -*-
+#
+#--
+# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
+#
+# This file is part of kramdown which is licensed under the MIT.
+#++
+#
+
+require 'kramdown/parser'
+
+module Kramdown
+
+ module Parser
+
+ # Used for parsing a document in Markdown format.
+ #
+ # This parser is based on the kramdown parser and removes the parser methods for the additional
+ # non-Markdown features. However, since some things are handled differently by the kramdown
+ # parser methods (like deciding when a list item contains just text), this parser differs from
+ # real Markdown parsers in some respects.
+ #
+ # Note, though, that the parser basically fails just one of the Markdown test cases (some others
+ # also fail but those failures are negligible).
+ class Markdown < Kramdown
+
+ # Array with all the parsing methods that should be removed from the standard kramdown parser.
+ EXTENDED = [:codeblock_fenced, :table, :definition_list, :footnote_definition,
+ :abbrev_definition, :block_math, :block_extensions,
+ :footnote_marker, :smart_quotes, :inline_math, :span_extensions, :typographic_syms]
+
+ def initialize(source, options) # :nodoc:
+ super
+ @block_parsers.delete_if {|i| EXTENDED.include?(i) }
+ @span_parsers.delete_if {|i| EXTENDED.include?(i) }
+ end
+
+ # :stopdoc:
+
+ BLOCK_BOUNDARY = /#{BLANK_LINE}|#{EOB_MARKER}|\Z/
+ LAZY_END = /#{BLANK_LINE}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|
+ ^#{OPT_SPACE}#{LAZY_END_HTML_START}|\Z/x
+ CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?(?:#{INDENT}[ \t]*\S.*\n)+)*/
+ PARAGRAPH_END = LAZY_END
+
+ IAL_RAND_CHARS = (('a'..'z').to_a + ('0'..'9').to_a)
+ IAL_RAND_STRING = (1..20).collect { IAL_RAND_CHARS[rand(IAL_RAND_CHARS.size)] }.join
+ LIST_ITEM_IAL = /^\s*(#{IAL_RAND_STRING})?\s*\n/
+ IAL_SPAN_START = LIST_ITEM_IAL
+
+ # :startdoc:
+
+ end
+
+ end
+
+end