diff --git a/bin/mmmdpp b/bin/mmmdpp new file mode 100755 index 0000000..b2c2c9b --- /dev/null +++ b/bin/mmmdpp @@ -0,0 +1,170 @@ +#!/bin/ruby +# frozen_string_literal: true + +require 'io/console/size' +require 'optionparser' +require 'json' +require 'mmmd' + +class ParserError < StandardError +end + +class OptionNavigator + def initialize + @options = {} + end + + # Read a definition + # @param define [String] + def read_definition(define) + define.split(";").each do |part| + locstring, _, value = part.partition(":") + locstring = deconstruct(locstring.strip) + assign(locstring, JSON.parse(value)) + end + end + + attr_reader :options + + private + + def check_unescaped(str, index) + return true if index.zero? + + reverse_index = index - 1 + count = 0 + while str[reverse_index] == "\\" + break if reverse_index.zero? + + count += 1 + reverse_index -= 1 + end + count.even? + end + + def find_unescaped(str, pattern, index) + found = str.index(pattern, index) + return nil unless found + + until check_unescaped(str, found) + index = found + 1 + found = str.index(pattern, index) + return nil unless found + end + found + end + + def deconstruct(locstring) + parts = [] + buffer = "" + part = nil + until locstring.empty? + case locstring[0] + when '"' + raise ParserError, 'separator missing' unless buffer.empty? + + closepart = find_unescaped(locstring, '"', 1) + raise ParserError, 'unclosed string' unless closepart + + buffer = locstring[0..closepart] + part = buffer[1..-2] + locstring = locstring[closepart + 1..] + when '.' + parts.append(part) + buffer = "" + part = nil + locstring = locstring[1..] + when '[' + raise ParserError, 'separator missing' unless buffer.empty? + + closepart = find_unescaped(locstring, ']', 1) + raise ParserError, 'unclosed index' unless closepart + + buffer = locstring[0..closepart] + part = locstring[1..-2].to_i + locstring = locstring.delete_prefix(buffer) + else + raise ParserError, 'separator missing' unless buffer.empty? + + buffer = locstring.match(/^[\w_]+/)[0] + part = buffer.to_sym + locstring = locstring.delete_prefix(buffer) + end + end + parts.append(part) if part + parts + end + + def assign(keys, value) + current = @options + while keys.length > 1 + current_key = keys.shift + unless current[current_key] + next_key = keys.first + case next_key + when Integer + current[current_key] = [] + when String + current[current_key] = {} + when Symbol + current[current_key] = {} + end + end + current = current[current_key] + end + current[keys.shift] = value + end +end + +return unless $PROGRAM_NAME == __FILE__ + +options = { + include: [], + nav: OptionNavigator.new +} +parser = OptionParser.new do |opts| + opts.banner = "Usage: mmmdpp [OPTIONS] (input|-) (output|-)" + + opts.on("-r", "--renderer [STRING]", String, + "Specify renderer to use for this document") do |renderer| + options[:renderer] = renderer + end + + opts.on("-i", "--include [STRING]", String, + "Script to execute before rendering.\ + May be specified multiple times.") do |inc| + options[:include].append(inc) + end + + opts.on("-o", "--option [STRING]", String, + "Add option string. Can be repeated. Format: : \n"\ + ": (<\"string\">||<[integer]>)"\ + "[.(<\"string\"||<[integer]>[...]]\n"\ + "Example: \"style\".\"CodeBlock\".literal.[0]: 50") do |value| + options[:nav].read_definition(value) if value + end +end +parser.parse! + +unless ARGV[1] + warn parser.help + exit 1 +end + +Renderers = { + "HTML" => -> { ::MMMD::Renderers::HTML }, + "Plainterm" => -> { ::MMMD::Renderers::Plainterm } +}.freeze + +options[:include].each { |name| Kernel.load(name) } +renderer_opts = options[:nav].options +renderer_opts["hsize"] ||= IO.console_size[1] +input = ARGV[0] == "-" ? $stdin.read : File.read(ARGV[0]) +output = ARGV[1] == "-" ? $stdout : File.open(ARGV[1], "w") +doc = MMMD.parse(input) +rclass = Renderers[options[:renderer] || "PlainTerm"] +raise StandardError, "unknown renderer: #{options[:renderer]}" unless rclass + +renderer = rclass.call.new(doc, renderer_opts) +output.puts(renderer.render) +output.close diff --git a/lib/mmmd/renderers.rb b/lib/mmmd/renderers.rb index b6a8b9d..2904103 100644 --- a/lib/mmmd/renderers.rb +++ b/lib/mmmd/renderers.rb @@ -6,6 +6,6 @@ module MMMD # Renderers from Markdown to expected output format module Renderers autoload :HTML, 'renderers/html' - autoload :PlainTerm, 'renderers/plainterm' + autoload :Plainterm, 'renderers/plainterm' end end diff --git a/lib/mmmd/renderers/html.rb b/lib/mmmd/renderers/html.rb index 67a094f..d857643 100644 --- a/lib/mmmd/renderers/html.rb +++ b/lib/mmmd/renderers/html.rb @@ -229,7 +229,9 @@ module MMMD text = if element.children.empty? element.content else - literal = @mapping[element.class.name][:inline] || literaltext + literal = @mapping[element.class.name] + &.fetch(:inline, false) || + literaltext element.children.map do |child| _render(child, options, inline: inline, level: level, diff --git a/lib/mmmd/renderers/plainterm.rb b/lib/mmmd/renderers/plainterm.rb index 8ad1f48..db022dd 100644 --- a/lib/mmmd/renderers/plainterm.rb +++ b/lib/mmmd/renderers/plainterm.rb @@ -396,7 +396,7 @@ module MMMD @effect_priority = style_manager.effect_priority @effects = @effect_priority.to_a.sort_by(&:last).map(&:first) @options = options - @options[:hsize] ||= 80 + @options["hsize"] ||= 80 end # Return rendered text @@ -430,7 +430,7 @@ module MMMD element_style = @style[element.class.name] return text unless element_style - hsize = 80 - (4 * level) + hsize = @options["hsize"] - (4 * level) text = wordwrap(text, hsize) if modeswitch params = element_style.dup params[:hsize] = hsize diff --git a/mmmdpp.rb b/mmmdpp.rb deleted file mode 100644 index ddff607..0000000 --- a/mmmdpp.rb +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/ruby -# frozen_string_literal: true - -require 'optionparser' -