From b41479c91e6685511c1f8ba8586106b322073b62 Mon Sep 17 00:00:00 2001 From: Ben Sanders Date: Sun, 2 Aug 2026 09:49:25 -0400 Subject: added statscounter code --- .../kramdown-2.5.2/lib/kramdown/parser/base.rb | 137 +++++ .../kramdown-2.5.2/lib/kramdown/parser/html.rb | 621 +++++++++++++++++++++ .../kramdown-2.5.2/lib/kramdown/parser/kramdown.rb | 377 +++++++++++++ .../lib/kramdown/parser/kramdown/abbreviation.rb | 80 +++ .../lib/kramdown/parser/kramdown/autolink.rb | 31 + .../lib/kramdown/parser/kramdown/blank_line.rb | 30 + .../lib/kramdown/parser/kramdown/block_boundary.rb | 34 ++ .../lib/kramdown/parser/kramdown/blockquote.rb | 38 ++ .../lib/kramdown/parser/kramdown/codeblock.rb | 57 ++ .../lib/kramdown/parser/kramdown/codespan.rb | 58 ++ .../lib/kramdown/parser/kramdown/emphasis.rb | 66 +++ .../lib/kramdown/parser/kramdown/eob.rb | 26 + .../lib/kramdown/parser/kramdown/escaped_chars.rb | 25 + .../lib/kramdown/parser/kramdown/extensions.rb | 214 +++++++ .../lib/kramdown/parser/kramdown/footnote.rb | 64 +++ .../lib/kramdown/parser/kramdown/header.rb | 70 +++ .../kramdown/parser/kramdown/horizontal_rule.rb | 27 + .../lib/kramdown/parser/kramdown/html.rb | 165 ++++++ .../lib/kramdown/parser/kramdown/html_entity.rb | 34 ++ .../lib/kramdown/parser/kramdown/line_break.rb | 25 + .../lib/kramdown/parser/kramdown/link.rb | 149 +++++ .../lib/kramdown/parser/kramdown/list.rb | 286 ++++++++++ .../lib/kramdown/parser/kramdown/math.rb | 53 ++ .../lib/kramdown/parser/kramdown/paragraph.rb | 62 ++ .../lib/kramdown/parser/kramdown/smart_quotes.rb | 174 ++++++ .../lib/kramdown/parser/kramdown/table.rb | 171 ++++++ .../kramdown/parser/kramdown/typographic_symbol.rb | 44 ++ .../kramdown-2.5.2/lib/kramdown/parser/markdown.rb | 57 ++ 28 files changed, 3175 insertions(+) create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/base.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/html.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/abbreviation.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/autolink.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blank_line.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/block_boundary.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/blockquote.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codeblock.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/codespan.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/emphasis.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/eob.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/escaped_chars.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/extensions.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/footnote.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/header.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/horizontal_rule.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/html_entity.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/line_break.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/link.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/list.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/math.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/paragraph.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/smart_quotes.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/table.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/kramdown/typographic_symbol.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser/markdown.rb (limited to 'vendor/bundle/ruby/3.4.0/gems/kramdown-2.5.2/lib/kramdown/parser') 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 +# +# 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 +# +# 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 = //im + HTML_COMMENT_RE = //m + HTML_INSTRUCTION_RE = /<\?(.*?)\?>/m + HTML_CDATA_RE = //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*)?\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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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}(?[^ \t].*)\n(?[-=])[-=]*[ \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 = /^(?\#{1,6})[\t ]*(?[^ \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!(/(?#{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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# 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.: + #

He said, "'Quoted' words in a larger quote."

+ [/(\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. "Custer'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 +# +# 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>)/).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}#{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 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 +# +# This file is part of kramdown which is licensed under the MIT. +#++ +# + +module Kramdown + module Parser + class Kramdown + + TYPOGRAPHIC_SYMS = [['---', :mdash], ['--', :ndash], ['...', :hellip], + ['\\<<', '<<'], ['\\>>', '>>'], + ['<< ', :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 +# +# 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 -- cgit v1.2.3