diff --git a/classes b/classes new file mode 100644 index 0000000..9153047 --- /dev/null +++ b/classes @@ -0,0 +1,13 @@ +Bold [x} +Italics [x] +Underline [x] +Strikethrough [x] +CodeInline [x] +Link [x] +Image [x] +Headings [x] +CodeBlock [x] +QuoteBlock [x] +ULBlock [x] +OLBLock [x] +TableBlock [] diff --git a/document.rb b/document.rb new file mode 100644 index 0000000..747e0ae --- /dev/null +++ b/document.rb @@ -0,0 +1,756 @@ +# frozen_string_literal: true + +module RBMark + # Parser units + # Parsers are divided into three categories: + # - Slicers - these parsers read the whole text of an element and slice it into chunks digestible by other parsers + # - ChunkParsers - these parsers transform chunks of text into a single DOM unit + # - InlineParsers - these parsers are called directly by the slicer to check whether a certain element matches needed criteria + module Parsers + # Abstract slicer class + class Slicer + # @param parent [::RBMark::DOM::DOMObject] + def initialize + @chunk_parsers = [] + end + + attr_accessor :chunk_parsers + + private + + def parse_chunk(text) + @chunk_parsers.each do |parser| + unless parser.is_a? ChunkParser + raise StandardError, 'not a ChunkParser' + end + + next unless parser.match?(text) + + return parser.match(text) + end + nil + end + end + + # Abstract inline parser class + class InlineParser + # Test if piece matches bold syntax + # @param text [String] + # @return [Boolean] + def match?(text) + text.match?(@match_exp) + end + + # Construct a new object from text + # @param text [String] + # @return [Object] + def match(text) + @class.parse(text) + end + + attr_reader :class, :match_exp + end + + # Abstract chunk parser class + class ChunkParser + # Stub for match method + def match(text) + element = ::RBMark::DOM::Text.new + element.content = text + element + end + + # Stub for match? method + def match?(_text) + true + end + end + + # Slices text into paragraphs and feeds slices to chunk parsers + class RootSlicer < Slicer + # Parse text into chunks and feed each to the chain + # @param text [String] + def parse(text) + output = text.split(/(?:\r\r|\n\n|\r\n\r\n|\Z)/) + .reject { |x| x.match(/\A\s*\Z/) } + .map do |block| + parse_chunk(block) + end + merge_list_indents(output) + end + + private + + def merge_list_indents(chunks) + last_list = nil + delete_deferred = [] + chunks.each_with_index do |chunk, index| + if !last_list and [::RBMark::DOM::ULBlock, + ::RBMark::DOM::OLBlock].include? chunk.class + last_list = chunk + elsif last_list and mergeable?(last_list, chunk) + merge(last_list, chunk) + delete_deferred.prepend(index) + else + last_list = nil + end + end + delete_deferred.each { |i| chunks.delete_at(i) } + chunks + end + + def mergeable?(last_list, chunk) + if chunk.is_a? ::RBMark::DOM::IndentBlock or + (chunk.is_a? ::RBMark::DOM::ULBlock and + last_list.is_a? ::RBMark::DOM::ULBlock) or + (chunk.is_a? ::RBMark::DOM::OLBlock and + last_list.is_a? ::RBMark::DOM::OLBlock and + last_list.properties["num"] > chunk.properties["num"]) + true + else + false + end + end + + def merge(last_list, chunk) + if chunk.is_a? ::RBMark::DOM::IndentBlock + last_list.children.last.children.append(*chunk.children) + else + last_list.children.append(*chunk.children) + end + end + end + + # Inline text slicer (slices based on the start and end symbols) + class InlineSlicer < Slicer + # Parse slices + # @param text [String] + def parse(text) + parts = [] + index = prepare_markers + until text.empty? + before, part, text = slice(text) + parts.append(::RBMark::DOM::Text.parse(before)) unless before.empty? + next unless part + + element = index.fetch(part.regexp, + ::RBMark::Parsers::TextInlineParser.new) + .match(part[0]) + parts.append(element) + end + parts + end + + private + + # Prepare markers from chunk_parsers + # @return [Hash] + def prepare_markers + index = {} + @markers = @chunk_parsers.map do |parser| + index[parser.match_exp] = parser + parser.match_exp + end + index + end + + # Get the next slice of a text based on markers + # @param text [String] + # @return [Array<(String,MatchData,String)>] + def slice(text) + first_tag = @markers.map { |x| text.match(x) } + .reject(&:nil?) + .min_by { |x| x.offset(0)[0] } + return text, nil, "" unless first_tag + + [first_tag.pre_match, first_tag, first_tag.post_match] + end + end + + # Slicer for unordered lists + class UnorderedSlicer < Slicer + # Parse list elements + def parse(text) + output = [] + buffer = "" + text.lines.each do |line| + if line.start_with? "- " and !buffer.empty? + output.append(make_element(buffer)) + buffer = "" + end + buffer += line[2..] + end + output.append(make_element(buffer)) unless buffer.empty? + output + end + + private + + def make_element(text) + ::RBMark::DOM::ListElement.parse(text) + end + end + + # Slicer for unordered lists + class OrderedSlicer < Slicer + # rubocop:disable Metrics/AbcSize + + # Parse list elements + def parse(text) + output = [] + buffer = "" + indent = text.match(/\A\d+\. /)[0].length + num = text.match(/\A(\d+)\. /)[1] + text.lines.each do |line| + if line.start_with?(/\d+\. /) and !buffer.empty? + output.append(make_element(buffer, num)) + buffer = "" + indent = line.match(/\A\d+\. /)[0].length + num = line.match(/\A(\d+)\. /)[1] + end + buffer += line[indent..] + end + output.append(make_element(buffer, num)) unless buffer.empty? + output + end + + # rubocop:enable Metrics/AbcSize + private + + def make_element(text, num) + element = ::RBMark::DOM::ListElement.parse(text) + element.property num: num.to_i + element + end + end + + # Quote block parser + class QuoteChunkParser < ChunkParser + # Tests for chunk being a block quote + # @param text [String] + # @return [Boolean] + def match?(text) + text.lines.map do |x| + x.match?(/\A\s*>(?:\s[^\n\r]+|)\Z/m) + end.all?(true) + end + + # Transforms text chunk into a block quote + # @param text + # @return [::RBMark::DOM::QuoteBlock] + def match(text) + text = text.lines.map do |x| + x.match(/\A\s*>(\s[^\n\r]+|)\Z/m)[1].to_s[1..] + end.join("\n") + ::RBMark::DOM::QuoteBlock.parse(text) + end + end + + # Paragraph block + class ParagraphChunkParser < ChunkParser + # Acts as a fallback for the basic paragraph chunk + # @param text [String] + # @return [Boolean] + def match?(_text) + true + end + + # Creates a new paragraph with the given text + def match(text) + ::RBMark::DOM::Paragraph.parse(text) + end + end + + # Code block + class CodeChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + text.match?(/\A```\w+[\r\n]{1,2}.*[\r\n]{1,2}```\Z/m) + end + + # Create a new element + def match(text) + lang, code = text.match( + /\A```(\w+)[\r\n]{1,2}(.*)[\r\n]{1,2}```\Z/m + )[1, 2] + element = ::RBMark::DOM::CodeBlock.new + element.property language: lang + text = ::RBMark::DOM::Text.new + text.content = code + element.append(text) + element + end + end + + # Heading chunk parser + class HeadingChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + text.match?(/\A\#{1,4}\s/) + end + + # Create a new element + def match(text) + case text.match(/\A\#{1,4}\s/)[0] + when "# " then ::RBMark::DOM::Heading1.parse(text[2..]) + when "## " then ::RBMark::DOM::Heading2.parse(text[3..]) + when "### " then ::RBMark::DOM::Heading3.parse(text[4..]) + when "#### " then ::RBMark::DOM::Heading4.parse(text[5..]) + end + end + end + + # Unordered list parser (chunk) + class UnorderedChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + return false unless text.start_with? "- " + + text.lines.map do |line| + line.match?(/\A(?:- .*| .*| )\Z/) + end.all?(true) + end + + # Create a new element + def match(text) + ::RBMark::DOM::ULBlock.parse(text) + end + end + + # Ordered list parser (chunk) + class OrderedChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + return false unless text.start_with?(/\d+\. /) + + indent = 0 + text.lines.each do |line| + if line.start_with?(/\d+\. /) + indent = line.match(/\A\d+\. /)[0].length + elsif line.start_with?(/\s+/) + return false if line.match(/\A\s+/)[0].length < indent + else + return false + end + end + true + end + + # Create a new element + def match(text) + ::RBMark::DOM::OLBlock.parse(text) + end + end + + # Indented block parser + class IndentChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + text.lines.map do |x| + x.start_with? " " or x.start_with? "\t" + end.all?(true) + end + + # Create a new element + def match(text) + text = text.lines.map { |x| x.match(/\A(?: {4}|\t)(.*)\Z/)[1] } + .join("\n") + ::RBMark::DOM::IndentBlock.parse(text) + end + end + + # Horizontal Rule block parser + class HRChunkParser < ChunkParser + # Check if a block matches the given parser rule + # @param text [String] + # @return [Boolean] + def match?(text) + text.match?(/\A-{3,}\Z/) + end + + # Create a new element + def match(text) + element = ::RBMark::DOM::HorizontalRule.new() + element.content = "" + element + end + end + + # Stub text parser + class TextInlineParser < InlineParser + # Stub method for creating new Text object + def match(text) + instance = ::RBMark::DOM::Text.new + instance.content = text + instance + end + end + + # Bold text + class BoldInlineParser < InlineParser + def initialize + super + @match_exp = /(?") - # Inline code (discord style) - .gsub(/(?#{code.gsub /[*`~_!\[]/,"\\\\\\0"}" - } - # Inline code (Markdown style) - .gsub(/(?#{code.gsub /[*`~_!\[]/,"\\\\\\0"}" - } - # Bold-italics - .gsub(/(?\\1") - # Bold - .gsub(/(?\\1") - # Italics - .gsub(/(?\\1") - # Strikethrough - .gsub(/(?\\1") - # Underline - .gsub(/(?\\1") - # Image - .gsub(/(?") - # Link - .gsub(/(?\\1") - super - end - end - ## Translator for linear leftmost tags. - # Leftmost linear tags open on the leftmost end of the string, and close once the line ends. These tags do not need to be explicitly closed. - class LeftmostTagTranslator < AbstractTranslator - def initialize(text) - @input = text - @output = text - super() - end - def to_html - # Headers - @output = @input.split("\n").map do |x| - x.gsub(/^(?"+content+"" - }.gsub(/^\-{3,}/,"
") - end.join("\n") - super - end - end - ## Translator for code blocks in markdown - # Code blocks can have syntax highlighting. This class implements an attribute for providing a syntax highlighter, one handler per requested output. - class CodeBlockTranslator < AbstractTranslator - def initialize(text) - @input = text - @output = text - super() - end - def to_html - @output = @input.gsub(/(?:\n|^)```([\w_-]*)([\s\S]+?)```/) { - language,code = Regexp.last_match[1..2] - code = Markdown::html_highlighter.call(language,code) if Markdown::html_highlighter - "
#{code.gsub /[|#*`~_!\[]/,"\\\\\\0"}
" - } - super() - end - end - ## Translator for quotes in Markdown. - # These deserve their own place in hell. As if the "yaml with triangle brackets instead of spaces" syntax wasn't horrible enough, each quote is its own markdown context. - class QuoteTranslator < AbstractTranslator - def initialize(text) - if text.is_a? Array then - @lines = text - elsif text.is_a? String then - @lines = text.split("\n") - end - @output = text - super() - end - def input= (v) - @lines = v.split("\n") - @output = v - end - def input - @lines.join("\n") - end - def to_html - stack = [] - range = [] - @lines.each_with_index { |x,index| - if x.match /^\s*> ?/ then - range[0] = index if not range[0] - range[1] = index - else - stack.append(range[0]..range[1]) if range[0] and range[1] - range = [] - end - } - stack.append(range[0]..range[1]) if range[0] and range[1] - stack.reverse.each { |r| - @lines[r.begin] = "
\n"+@lines[r.begin] - @lines[r.end] = @lines[r.end]+"\n
" - @lines[r] = @lines[r].map { |line| - line.sub /^(\s*)> ?/,"\\1 " - } - @lines[r] = QuoteTranslator.new(@lines[r]).to_html - } - @output = @lines.join("\n") - super - end - end - - ## Table parser - # translates tables from a format in markdown to an html table - class TableTranslator < AbstractTranslator - def initialize(text) - @input = text - @output = text - super() - end - def to_html - lines = @output.split("\n") - table_testline = -1 - table_start = -1 - table_column_count = 0 - tables = [] - cur_table = [] - lines.each_with_index { |line,index| - if (table_start != -1) and (line.match /^\s*\|([^\|]*\|){#{table_column_count-1}}$/) then - if (table_testline == -1) then - if (line.match /^\s*\|(\-*\|){#{table_column_count-1}}$/) then - table_testline = 1 - else - table_start = -1 - cur_table = [] - end - else - cur_table.push (line.split("|").filter_map { |x| x.strip if x.match /\S+/ }) - end - elsif (table_start != -1) then - obj = {table: cur_table, start: table_start, end: index} - tables.push(obj) - table_start = -1 - cur_table = [] - table_testline = -1 - table_column_count = 0 - end - if (table_start == -1) and (line.start_with? /\s*\|/ ) and (line.match /^\s*\|.*\|/) then - table_start = index - table_column_count = line.count "|" - cur_table.push (line.split("|").filter_map { |x| x.strip if x.match /\S+/ }) - end - } - if cur_table != [] then - obj = {table: cur_table, start:table_start, end: lines.count-1} - tables.push(obj) - end - tables.reverse.each { |x| - lines[x[:start]..x[:end]] = (x[:table].map do |a2d| - (a2d.map { |x| (x.start_with? "#") ? " "+x.sub(/^#\s+/,"")+"" : " "+x+""}).prepend(" ").append(" ") - end).flatten.prepend("").append("
") - } - @output = lines.join("\n") - super() - end - end - - # Backslash cleaner - # Cleans excessive backslashes after the translation - class BackslashTranslator < AbstractTranslator - def initialize(text) - @input = text - @output = text - end - def to_html - @output = @input.gsub(/\\(.)/,"\\1") - end - end -end - - diff --git a/mdpp.rb b/mdpp.rb new file mode 100644 index 0000000..6b789b2 --- /dev/null +++ b/mdpp.rb @@ -0,0 +1,430 @@ +#!/usr/bin/ruby +# frozen_string_literal: true + +require_relative 'document' +require 'io/console' +require 'io/console/size' + +module MDPP + # Module for managing terminal output + module TextManager + # ANSI SGR escape code for bg color + # @param text [String] + # @param properties [Hash] + # @return [String] + def bg(text, properties) + color = properties['bg'] + if color.is_a? Integer + "\e[48;5;#{color}m#{text}\e[49m" + elsif color.is_a? String and color.match?(/\A#[A-Fa-f0-9]{6}\Z/) + vector = color.scan(/[A-Fa-f0-9]{2}/).map { |x| x.to_i(16) } + "\e[48;2;#{vector[0]};#{vector[1]};#{vector[2]}\e[49m" + else + Kernel.warn "WARNING: Invalid color - #{color}" + text + end + end + + # ANSI SGR escape code for fg color + # @param text [String] + # @param properties [Hash] + # @return [String] + def fg(text, properties) + color = properties['fg'] + if color.is_a? Integer + "\e[38;5;#{color}m#{text}\e[39m" + elsif color.is_a? String and color.match?(/\A#[A-Fa-f0-9]{6}\Z/) + vector = color.scan(/[A-Fa-f0-9]{2}/).map { |x| x.to_i(16) } + "\e[38;2;#{vector[0]};#{vector[1]};#{vector[2]}\e[39m" + else + Kernel.warn "WARNING: Invalid color - #{color}" + text + end + end + + # ANSI SGR escape code for bold text + # @param text [String] + # @return [String] + def bold(text) + "\e[1m#{text}\e[22m" + end + + # ANSI SGR escape code for italics text + # @param text [String] + # @return [String] + def italics(text) + "\e[3m#{text}\e[23m" + end + + # ANSI SGR escape code for underline text + # @param text [String] + # @return [String] + def underline(text) + "\e[4m#{text}\e[24m" + end + + # ANSI SGR escape code for strikethrough text + # @param text [String] + # @return [String] + def strikethrough(text) + "\e[9m#{text}\e[29m" + end + + # Word wrapping algorithm + # @param text [String] + # @param width [Integer] + # @return [String] + def wordwrap(text, width) + words = text.split(/ +/) + output = [] + line = "" + until words.empty? + word = words.shift + if word.length > width + words.prepend(word[width..]) + word = word[..width - 1] + end + if line.length + word.length + 1 > width + output.append(line.lstrip) + line = word + next + end + line = [line, word].join(' ') + end + output.append(line.lstrip) + output.join("\n") + end + + # Draw a screen-width box around text + # @param text [String] + # @param center_margins [Integer] + # @return [String] + def box(text) + size = IO.console.winsize[1] - 2 + text = wordwrap(text, (size * 0.8).floor).lines.filter_map do |line| + "│#{line.strip.ljust(size)}│" unless line.empty? + end.join("\n") + <<~TEXT + ╭#{'─' * size}╮ + #{text} + ╰#{'─' * size}╯ + TEXT + end + + # Draw text right-justified + def rjust(text) + size = IO.console.winsize[1] + wordwrap(text, (size * 0.8).floor).lines.filter_map do |line| + line.strip.rjust(size) unless line.empty? + end.join("\n") + end + + # Draw text centered + def center(text) + size = IO.console.winsize[1] + wordwrap(text, (size * 0.8).floor).lines.filter_map do |line| + line.strip.center(size) unless line.empty? + end.join("\n") + end + + # Underline the last line of the text piece + def underline_block(text) + textlines = text.lines + last = "".match(/()()()/) + textlines.each do |x| + current = x.match(/\A(\s*)(.+?)(\s*)\Z/) + last = current if current[2].length > last[2].length + end + ltxt = last[1] + ctxt = textlines.last.slice(last.offset(2)[0]..last.offset(2)[1] - 1) + rtxt = last[3] + textlines[-1] = [ltxt, underline(ctxt), rtxt].join('') + textlines.join("") + end + + # Add extra newlines around the text + def extra_newlines(text) + size = IO.console.winsize[1] + textlines = text.lines + textlines.prepend("#{' ' * size}\n") + textlines.append("\n#{' ' * size}\n") + textlines.join("") + end + + # Underline last line edge to edge + def underline_full_block(text) + textlines = text.lines + textlines[-1] = underline(textlines.last) + textlines.join("") + end + + # Indent all lines + def indent(text, properties) + _indent(text, level: properties['level']) + end + + # Indent all lines (inner) + def _indent(text, **_useless) + text.lines.map do |line| + " #{line}" + end.join("") + end + + # Bulletpoints + def bullet(text, _number, properties) + level = properties['level'] + "-#{_indent(text, level: level)[1..]}" + end + + # Numbers + def numbered(text, number, properties) + level = properties['level'] + "#{number}.#{_indent(text, level: level)[number.to_s.length + 1..]}" + end + + # Sideline for quotes + def sideline(text) + text.lines.map do |line| + "│ #{line}" + end.join("") + end + + # Long bracket for code blocks + def longbracket(text, properties) + puts properties.inspect + textlines = text.lines + textlines = textlines.map do |line| + "│ #{line}" + end + textlines.prepend("┌ (#{properties['element'][:language]})\n") + textlines.append("\n└\n") + textlines.join("") + end + + # Add text to bibliography + def bibliography(text, properties) + @bibliography.append([text, properties['element'][:link]]) + "#{text}[#{@bibliography.length + 1}]" + end + end + + DEFAULT_STYLE = { + "RBMark::DOM::Paragraph" => { + "inline" => true, + "indent" => true + }, + "RBMark::DOM::Text" => { + "inline" => true + }, + "RBMark::DOM::Heading1" => { + "inline" => true, + "center" => true, + "bold" => true, + "extra_newlines" => true, + "underline_full_block" => true + }, + "RBMark::DOM::Heading2" => { + "inline" => true, + "center" => true, + "underline_block" => true + }, + "RBMark::DOM::Heading3" => { + "inline" => true, + "underline" => true, + "bold" => true, + "indent" => true + }, + "RBMark::DOM::Heading4" => { + "inline" => true, + "underline" => true, + "indent" => true + }, + "RBMark::DOM::InlineImage" => { + "bibliography" => true, + "inline" => true + }, + "RBMark::DOM::InlineLink" => { + "bibliography" => true, + "inline" => true + }, + "RBMark::DOM::InlinePre" => { + "inline" => true + }, + "RBMark::DOM::InlineStrike" => { + "inline" => true, + "strikethrough" => true + }, + "RBMark::DOM::InlineUnder" => { + "inline" => true, + "underline" => true + }, + "RBMark::DOM::InlineItalics" => { + "inline" => true, + "italics" => true + }, + "RBMark::DOM::InlineBold" => { + "inline" => true, + "bold" => true + }, + "RBMark::DOM::QuoteBlock" => { + "sideline" => true + }, + "RBMark::DOM::CodeBlock" => { + "longbracket" => true + }, + "RBMark::DOM::ULBlock" => { + "bullet" => true + }, + "RBMark::DOM::OLBlock" => { + "numbered" => true + }, + "RBMark::DOM::HorizontalRule" => { + "extra_newlines" => true + } + }.freeze + + STYLE_PRIO0 = [ + ["numbered", true], + ["bullet", true] + ].freeze + + STYLE_PRIO1 = [ + ["center", false], + ["rjust", false], + ["box", false], + ["indent", true], + ["underline", false], + ["bold", false], + ["italics", false], + ["strikethrough", false], + ["bg", true], + ["fg", true], + ["bibliography", true], + ["extra_newlines", false], + ["sideline", false], + ["longbracket", true], + ["underline_block", false], + ["underline_full_block", false] + ].freeze + + # Primary document renderer + class Renderer + include ::MDPP::TextManager + + # @param input [String] + # @param options [Hash] + def initialize(input, options) + @doc = RBMark::DOM::Document.parse(input) + @color_mode = options.fetch("color", true) + @ansi_mode = options.fetch("ansi", true) + @style = ::MDPP::DEFAULT_STYLE.dup + @bibliography = [] + return unless options['style'] + + @style = @style.map do |k, v| + v = v.merge(**options['style'][k]) if options['style'][k] + [k, v] + end.to_h + end + + # Return rendered text + # @return [String] + def render + text = _render(@doc.children, @doc.properties) + text += _render_bibliography unless @bibliography.empty? + text + end + + private + + def _render_bibliography + size = IO.console.winsize[1] + text = "\n#{('─' * size)}\n" + text += @bibliography.map.with_index do |element, index| + "- [#{index + 1}] #{wordwrap(element.join(': '), size - 15)}" + end.join("\n") + text + end + + def _render(children, props) + blocks = children.map do |child| + case child + when ::RBMark::DOM::Text then child.content + when ::RBMark::DOM::InlineBreak then "\n" + when ::RBMark::DOM::HorizontalRule + size = IO.console.winsize[1] + "─" * size + else + child_props = get_props(child, props) + calc_wordwrap( + _render(child.children, + child_props), + props, child_props + ) + end + end + apply_props(blocks, props) + end + + def calc_wordwrap(obj, props, obj_props) + size = IO.console.winsize[1] + return obj if obj_props['center'] or + obj_props['rjust'] + + if !props['inline'] and obj_props['inline'] + wordwrap(obj, size - 2 * (props['level'].to_i + 1)) + else + obj + end + end + + def get_props(obj, props) + new_props = @style[obj.class.to_s].dup || {} + if props["level"] + new_props["level"] = props["level"] + new_props["level"] += 1 unless new_props["inline"] + else + new_props["level"] = 2 + end + new_props["element"] = obj.properties + new_props + end + + def apply_props(blockarray, properties) + blockarray = prio0(blockarray, properties) + text = blockarray.join(properties['inline'] ? "" : "\n\n") + .gsub(/\n{2,}/, "\n\n") + prio1(text, properties) + end + + def prio0(blocks, props) + ::MDPP::STYLE_PRIO0.filter { |x| props.include? x[0] }.each do |style| + blocks = blocks.map.with_index do |block, index| + if style[1] + method(style[0].to_s).call(block, index + 1, props) + else + method(style[0].to_s).call(block, index + 1) + end + end + end + blocks + end + + def prio1(block, props) + ::MDPP::STYLE_PRIO1.filter { |x| props.include? x[0] }.each do |style| + block = if style[1] + method(style[0].to_s).call(block, props) + else + method(style[0].to_s).call(block) + end + end + block + end + end +end + +if __FILE__ == $0 + text = $stdin.read + renderer = MDPP::Renderer.new(text, {}) + puts renderer.render +end diff --git a/test.md b/test.md new file mode 100644 index 0000000..5a53f4f --- /dev/null +++ b/test.md @@ -0,0 +1,105 @@ +# Header level sadga kjshdkj hasdkjs hakjdhakjshd kashd kjashd kjashdk asjhdkj ashdkj ahskj hdaskd haskj hdkjash dkjashd ksajdh askjd hak askjhdkasjhdaksjhd sakjd 1 + +> Block quote text +> +> Second block quote paragraph +> Block quote **bold** and *italics* test +> Block quote **bold *italics* mix** test + +## Header level 2 + +[link](http://example.com) +![image alt text](http://example.com) + +```plaintext +code *block* +eat my shit +``` + +paragraph with ``inline code block`` + +- Unordered list element 1 +- Unordered list element 2 + +1. Ordered list element 1 +2. Ordered list element 2 + +This is not a list +- because it continues the paragraph +- this is how it should be, like it or not + +- This is also not a list +because there is text on the next line + +- But this here is a list + because the spacing is made correctly + + more so than that, there are multiple paragraphs here! + + - AND even more lists in a list! + - how extra +- And this is just the next element in the list + +1. same thing but with ordered lists + ordered lists have a little extra special property to them + + the indentations are always symmetrical to the last space of the bullet's number +10. i.e., if you look at this here example + this will work + + obviously + + +1. But this +10. Won't + because the indentation doesn't match the start of the line. + +generally speaking this kind of insane syntax trickery won't be necessary, +but it's just better to have standards than to have none of them. + +an unfortunate side effect of this flexibility should also be noted, and +it's that markdown linters don't like this sort of stuff. +Yet another reason not to use a markdown linter. + +- And this is just the lame stupid old way to do this, as described by mardkownguide + + > just indent your stuff and it works + > really it's as simple as that. + > bruh + + there can be as many as infinite number of elements appended to the list that way. + + you can even start a sublist here if you want to + + - here's a new nested list + - could you imagine the potential + + and here's an image of nothing + + ![image](https://example.com/nothing.png) + +- I may also need to merge lists for this to work properly + +### Third level header + +text + +#### 4th level text + +- list +- > list with blockquote + > quote + > more of the same quote + > + > - inner list + > - list2 + > + > ```plaintext + > code block inside a quote + > could you imagine that shit + > eh + > ``` + +--- + +gnomo diff --git a/test.rb b/test.rb deleted file mode 100644 index 48cbeb5..0000000 --- a/test.rb +++ /dev/null @@ -1,118 +0,0 @@ -require_relative "markdown" -puts Markdown::LinearTagTranslator.new(< Quote begins -> -> yea -> # header btw -> > nextlevel quote -> > more quote -> > those are quotes -> > yes -> > > third level quote -> > > yes -> > second level again -> > > third level again -> > second level oioioi -> > -> > > third -> > > -> > > -> > > -> -> -> -> fin -CODE - ).to_html - -puts Markdown::CodeBlockTranslator.new(< Here's a bunch of shit i guess lmao idk -```markdown -test -test -test -|1|2|3| -|-|-|-| -|a|b|c| - -| uneven rows | test | yes | -|-|-|-| -| sosiska | dinozavri | suda pihaem | -| sosiska 2 | vitalya 2 | brat 2 | -*** test *** -piss -cock -__cock__ -# hi -``` -> ok -> here i go pissing -> ***time to take a piss*** -> > pissing -> > "what the hell are you doing" -> > i'm taking a pieeees -> > "why areyou not jomping at me thats what yourshupposed to do -> > I might do it focking later -> > ok -> # bug -> __cum__ -__mashup__ - -| # sosiska | sosiska | suda pihaem | -|-|-|-| -| # 2 | chuvak ya ukral tvayu sardelku ))0)))0))))))) | __blya ((9((9((9)__ | -| # azazaz lalka sasI | test | test | -TEXT - )+Markdown::QuoteTranslator+Markdown::LeftmostTagTranslator+Markdown::LinearTagTranslator+Markdown::TableTranslator+Markdown::BackslashTranslator) - .to_html -write = File.new("/tmp/test.html","w") -write.write(test) -write.close