better plaintext renderer
This commit is contained in:
parent
e418796cfe
commit
65471b5a1b
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rbmark'
|
||||
|
||||
module RBMark
|
||||
module MMMD
|
||||
module Renderers
|
||||
# HTML Renderer
|
||||
class HTML
|
|
@ -0,0 +1,449 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Attempt to source a provider for the wide char width calculator
|
||||
# (TODO)
|
||||
|
||||
module MMMD
|
||||
# Module for managing terminal output
|
||||
module TextManager
|
||||
# ANSI SGR escape code for bg color
|
||||
# @param text [String]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def bg(text, options)
|
||||
color = options['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 options [Hash]
|
||||
# @return [String]
|
||||
def fg(text, options)
|
||||
color = options['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]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def bold(text, _options)
|
||||
"\e[1m#{text}\e[22m"
|
||||
end
|
||||
|
||||
# ANSI SGR escape code for italics text
|
||||
# @param text [String]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def italics(text, _options)
|
||||
"\e[3m#{text}\e[23m"
|
||||
end
|
||||
|
||||
# ANSI SGR escape code for underline text
|
||||
# @param text [String]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def underline(text, _options)
|
||||
"\e[4m#{text}\e[24m"
|
||||
end
|
||||
|
||||
# ANSI SGR escape code for strikethrough text
|
||||
# @param text [String]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def strikethrough(text, _options)
|
||||
"\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 = ""
|
||||
length = 0
|
||||
until words.empty?
|
||||
word = words.shift
|
||||
wordlength = smort_length(word)
|
||||
if wordlength > width
|
||||
words.prepend(word[width..])
|
||||
word = word[..width - 1]
|
||||
end
|
||||
if length + wordlength + 1 > width
|
||||
output.append(line.lstrip)
|
||||
line = word
|
||||
length = wordlength
|
||||
next
|
||||
end
|
||||
length += wordlength
|
||||
line += word
|
||||
end
|
||||
output.append(line.lstrip)
|
||||
output.join("\n")
|
||||
end
|
||||
|
||||
# (TODO: smorter stronger better faster)
|
||||
# SmЯt™ word length
|
||||
# @param text [String]
|
||||
# @return [Integer]
|
||||
def smort_length(text)
|
||||
text.gsub(/\e\[[^m]+m/, '').length
|
||||
end
|
||||
|
||||
# Left-justify a line while ignoring terminal control codes
|
||||
# @param text [String]
|
||||
# @param size [Integer]
|
||||
# @return [String]
|
||||
def ljust_cc(text, size)
|
||||
text.lines.map do |line|
|
||||
textlength = smort_length(line)
|
||||
textlength < size ? line + " " * (size - textlength) : line
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Right-justify a line while ignoring terminal control codes
|
||||
# @param text [String]
|
||||
# @param size [Integer]
|
||||
# @return [String]
|
||||
def rjust_cc(text, size)
|
||||
text.lines.map do |line|
|
||||
textlength = smort_length(line)
|
||||
textlength < size ? " " * (size - textlength) + line : line
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Center-justify a line while ignoring terminal control codes
|
||||
# @param text [String]
|
||||
# @param size [Integer]
|
||||
# @return [String]
|
||||
def center_cc(text, size)
|
||||
text.lines.map do |line|
|
||||
textlength = smort_length(line)
|
||||
if textlength < size
|
||||
freelength = size - textlength
|
||||
rightlength = freelength / 2
|
||||
leftlength = freelength - rightlength
|
||||
" " * leftlength + line + " " * rightlength
|
||||
else
|
||||
line
|
||||
end
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Draw a screen-width box around text
|
||||
# @param text [String]
|
||||
# @param options [Hash]
|
||||
# @return [String]
|
||||
def box(text, options)
|
||||
size = options[:hsize] - 2
|
||||
text = wordwrap(text, (size * 0.8).floor).lines.filter_map do |line|
|
||||
"│#{ljust_cc(line, size)}│" unless line.empty?
|
||||
end.join("\n")
|
||||
<<~TEXT
|
||||
╭#{'─' * size}╮
|
||||
#{text}
|
||||
╰#{'─' * size}╯
|
||||
TEXT
|
||||
end
|
||||
|
||||
# Draw text right-justified
|
||||
def rjust(text, options)
|
||||
size = options[:hsize]
|
||||
wordwrap(text, (size * 0.8).floor).lines.filter_map do |line|
|
||||
rjust_cc(line, size) unless line.empty?
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Draw text centered
|
||||
def center(text, options)
|
||||
size = options[:hsize]
|
||||
wordwrap(text, (size * 0.8).floor).lines.filter_map do |line|
|
||||
center_cc(line, size) unless line.empty?
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Underline the last line of the text piece
|
||||
def underline_block(text, options)
|
||||
textlines = text.lines
|
||||
last = "".match(/()()()/)
|
||||
textlines.each do |x|
|
||||
current = x.match(/\A(\s*)(.+?)(\s*)\Z/)
|
||||
last = current if smort_length(current[2]) > smort_length(last[2])
|
||||
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, options), rtxt].join('')
|
||||
textlines.join("")
|
||||
end
|
||||
|
||||
# Add extra newlines around the text
|
||||
def extra_newlines(text, options)
|
||||
size = options[:hsize]
|
||||
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, options)
|
||||
textlines = text.lines
|
||||
last_line = textlines.last.match(/^.*$/)[0]
|
||||
textlines[-1] = "#{underline(last_line, options)}\n"
|
||||
textlines.join("")
|
||||
end
|
||||
|
||||
# Indent all lines
|
||||
def indent(text, _options)
|
||||
_indent(text)
|
||||
end
|
||||
|
||||
# Indent all lines (inner)
|
||||
def _indent(text)
|
||||
text.lines.map do |line|
|
||||
" #{line}"
|
||||
end.join("")
|
||||
end
|
||||
|
||||
# Left overline all lines
|
||||
def leftline(text, _options)
|
||||
text.lines.map do |line|
|
||||
" │ #{line}"
|
||||
end.join("")
|
||||
end
|
||||
|
||||
# Bulletpoints
|
||||
def bullet(text, _options)
|
||||
"-#{_indent(text)[1..]}"
|
||||
end
|
||||
|
||||
# Numbers
|
||||
def numbered(text, options)
|
||||
number = options[:number]
|
||||
length = number.to_s.length + 1
|
||||
(length / 4 + 1).times { text = _indent(text) }
|
||||
"#{number}.#{text[length..]}"
|
||||
end
|
||||
end
|
||||
|
||||
module Renderers
|
||||
module PlaintermConstants
|
||||
DEFAULT_STYLE = {
|
||||
"PointBlank::DOM::Paragraph" => {
|
||||
indent: true,
|
||||
increase_level: true
|
||||
},
|
||||
"PointBlank::DOM::Text" => {},
|
||||
"PointBlank::DOM::SetextHeading1" => {
|
||||
center: true,
|
||||
bold: true,
|
||||
extra_newlines: true,
|
||||
underline_full_block: true
|
||||
},
|
||||
"PointBlank::DOM::SetextHeading2" => {
|
||||
center: true,
|
||||
underline_block: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading1" => {
|
||||
center: true,
|
||||
bold: true,
|
||||
extra_newlines: true,
|
||||
underline_full_block: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading2" => {
|
||||
center: true,
|
||||
underline_block: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading3" => {
|
||||
underline: true,
|
||||
bold: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading4" => {
|
||||
bold: true,
|
||||
underline: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading5" => {
|
||||
underline: true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading6" => {
|
||||
underline: true
|
||||
},
|
||||
"PointBlank::DOM::InlineImage" => {
|
||||
underline: true
|
||||
},
|
||||
"PointBlank::DOM::InlineLink" => {
|
||||
underline: true
|
||||
},
|
||||
"PointBlank::DOM::InlinePre" => {},
|
||||
"PointBlank::DOM::InlineEmphasis" => {
|
||||
italics: true
|
||||
},
|
||||
"PointBlank::DOM::InlineStrong" => {
|
||||
bold: true
|
||||
},
|
||||
"PointBlank::DOM::ULListElement" => {
|
||||
bullet: true,
|
||||
increase_level: true
|
||||
},
|
||||
"PointBlank::DOM::OLListElement" => {
|
||||
numbered: true,
|
||||
increase_level: true
|
||||
},
|
||||
"PointBlank::DOM::QuoteBlock" => {
|
||||
leftline: true,
|
||||
increase_level: true
|
||||
}
|
||||
}.freeze
|
||||
|
||||
DEFAULT_EFFECT_PRIORITY = {
|
||||
numbered: 10_000,
|
||||
leftline: 9500,
|
||||
bullet: 9000,
|
||||
indent: 8500,
|
||||
underline_full_block: 8000,
|
||||
underline_block: 7500,
|
||||
extra_newlines: 7000,
|
||||
center: 6000,
|
||||
rjust: 5500,
|
||||
box: 5000,
|
||||
underline: 4000,
|
||||
italics: 3500,
|
||||
bold: 3000,
|
||||
fg: 2500,
|
||||
bg: 2000,
|
||||
strikethrough: 1500
|
||||
}.freeze
|
||||
|
||||
# Class for managing styles and style overrides
|
||||
class StyleManager
|
||||
class << self
|
||||
# Define a default style for specified class
|
||||
# @param key [String] class name
|
||||
# @param style [Hash] style
|
||||
# @return [void]
|
||||
def define_style(key, style)
|
||||
@style ||= DEFAULT_STYLE.dup
|
||||
@style[key] = style
|
||||
end
|
||||
|
||||
# Define an effect priority value
|
||||
# @param key [String] effect name
|
||||
# @param priority [Integer] value of the priority
|
||||
# @return [void]
|
||||
def define_effect_priority(key, priority)
|
||||
@effect_priority ||= DEFAULT_EFFECT_PRIORITY.dup
|
||||
@effect_priority[key] = priority
|
||||
end
|
||||
|
||||
# Get computed style
|
||||
# @return [Hash]
|
||||
def style
|
||||
@style ||= DEFAULT_STYLE.dup
|
||||
end
|
||||
|
||||
# Get computed effect priority
|
||||
# @return [Hash]
|
||||
def effect_priority
|
||||
@effect_priority ||= DEFAULT_EFFECT_PRIORITY.dup
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(overrides)
|
||||
@style = self.class.style
|
||||
@effect_priority = self.class.effect_priority
|
||||
@style = @style.merge(overrides["style"]) if overrides["style"]
|
||||
end
|
||||
|
||||
attr_reader :style, :effect_priority
|
||||
end
|
||||
end
|
||||
|
||||
# Primary document renderer
|
||||
class Plainterm
|
||||
include ::MMMD::TextManager
|
||||
|
||||
# @param input [String]
|
||||
# @param options [Hash]
|
||||
def initialize(input, options)
|
||||
@doc = input
|
||||
@color_mode = options.fetch("color", true)
|
||||
@ansi_mode = options.fetch("ansi", true)
|
||||
style_manager = PlaintermConstants::StyleManager.new(options)
|
||||
@style = style_manager.style
|
||||
@effect_priority = style_manager.effect_priority
|
||||
@effects = @effect_priority.to_a.sort_by(&:last).map(&:first)
|
||||
@options = options
|
||||
@options[:hsize] ||= 80
|
||||
end
|
||||
|
||||
# Return rendered text
|
||||
# @return [String]
|
||||
def render
|
||||
_render(@doc, @options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _render(element, options, inline: false, level: 0, index: 0)
|
||||
modeswitch = element.is_a?(::PointBlank::DOM::LeafBlock) ||
|
||||
element.is_a?(::PointBlank::DOM::Paragraph)
|
||||
inline ||= modeswitch
|
||||
level += calculate_level_increase(element)
|
||||
text = if element.children.empty?
|
||||
element.content
|
||||
else
|
||||
element.children.map.with_index do |child, index|
|
||||
_render(child, options, inline: inline,
|
||||
level: level,
|
||||
index: index)
|
||||
end.join(inline ? '' : "\n\n")
|
||||
end
|
||||
run_filters(text, element, level: level,
|
||||
modeswitch: modeswitch,
|
||||
index: index)
|
||||
end
|
||||
|
||||
def run_filters(text, element, level:, modeswitch:, index:)
|
||||
element_style = @style[element.class.name]
|
||||
return text unless element_style
|
||||
|
||||
hsize = 80 - (4 * level)
|
||||
text = wordwrap(text, hsize) if modeswitch
|
||||
params = element_style.dup
|
||||
params[:hsize] = hsize
|
||||
params[:number] = index + 1
|
||||
@effects.each do |effect|
|
||||
text = method(effect).call(text, params) if element_style[effect]
|
||||
end
|
||||
text
|
||||
end
|
||||
|
||||
def calculate_level_increase(element)
|
||||
level = 0
|
||||
element_style = @style[element.class.name]
|
||||
level += 1 if element_style && element_style[:increase_level]
|
||||
level
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
mmmd
|
383
mmmdpp.rb
383
mmmdpp.rb
|
@ -1,386 +1,3 @@
|
|||
#!/usr/bin/ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/blankshell'
|
||||
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)
|
||||
newtext = output.join("\n")
|
||||
newtext
|
||||
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
|
||||
end
|
||||
|
||||
DEFAULT_STYLE = {
|
||||
"PointBlank::DOM::Paragraph" => {
|
||||
"inline" => true,
|
||||
"indent" => true
|
||||
},
|
||||
"PointBlank::DOM::Text" => {
|
||||
"inline" => true
|
||||
},
|
||||
"PointBlank::DOM::SetextHeading1" => {
|
||||
"inline" => true,
|
||||
"center" => true,
|
||||
"bold" => true,
|
||||
"extra_newlines" => true,
|
||||
"underline_full_block" => true
|
||||
},
|
||||
"PointBlank::DOM::SetextHeading2" => {
|
||||
"inline" => true,
|
||||
"center" => true,
|
||||
"underline_block" => true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading1" => {
|
||||
"inline" => true,
|
||||
"center" => true,
|
||||
"bold" => true,
|
||||
"extra_newlines" => true,
|
||||
"underline_full_block" => true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading2" => {
|
||||
"inline" => true,
|
||||
"center" => true,
|
||||
"underline_block" => true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading3" => {
|
||||
"inline" => true,
|
||||
"underline" => true,
|
||||
"bold" => true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading4" => {
|
||||
"inline" => true,
|
||||
"bold" => true,
|
||||
"underline" => true
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading5" => {
|
||||
"inline" => true,
|
||||
"underline" => true,
|
||||
},
|
||||
"PointBlank::DOM::ATXHeading6" => {
|
||||
"inline" => true,
|
||||
"underline" => true
|
||||
},
|
||||
"PointBlank::DOM::InlineImage" => {
|
||||
"inline" => true
|
||||
},
|
||||
"PointBlank::DOM::InlineLink" => {
|
||||
"inline" => true
|
||||
},
|
||||
"PointBlank::DOM::InlinePre" => {
|
||||
"inline" => true
|
||||
},
|
||||
"PointBlank::DOM::InlineEmphasis" => {
|
||||
"inline" => true,
|
||||
"italics" => true
|
||||
},
|
||||
"PointBlank::DOM::InlineStrong" => {
|
||||
"inline" => true,
|
||||
"bold" => true
|
||||
},
|
||||
"PointBlank::DOM::ULBlock" => {
|
||||
"bullet" => true
|
||||
},
|
||||
"PointBlank::DOM::OLBlock" => {
|
||||
"numbered" => 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],
|
||||
["extra_newlines", false],
|
||||
["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 = PointBlank::DOM::Document.parse(input)
|
||||
@color_mode = options.fetch("color", true)
|
||||
@ansi_mode = options.fetch("ansi", true)
|
||||
@style = ::MDPP::DEFAULT_STYLE.dup
|
||||
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
|
||||
_render(@doc.children, @doc.properties)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _render(children, props)
|
||||
blocks = children.map do |child|
|
||||
if child.is_a? ::PointBlank::DOM::Text or
|
||||
child.is_a? ::PointBlank::DOM::CodeBlock
|
||||
child.content
|
||||
elsif child.is_a? ::PointBlank::DOM::InlineBreak
|
||||
"\n"
|
||||
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
|
||||
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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/blankshell.rb'
|
||||
require_relative 'lib/mmmd/blankshell.rb'
|
||||
|
||||
structure = PointBlank::DOM::Document.parse(File.read(ARGV[0]))
|
||||
def red(string)
|
||||
|
|
Loading…
Reference in New Issue