301 lines
8.9 KiB
Ruby
301 lines
8.9 KiB
Ruby
|
# -*- coding: utf-8; frozen_string_literal: true -*-
|
||
|
#
|
||
|
#--
|
||
|
# Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
|
||
|
#
|
||
|
# This file is part of kramdown which is licensed under the MIT.
|
||
|
#++
|
||
|
#
|
||
|
|
||
|
require 'kramdown/converter'
|
||
|
|
||
|
module Kramdown
|
||
|
|
||
|
module Converter
|
||
|
|
||
|
# Converts a Kramdown::Document to a manpage in groff format. See man(7), groff_man(7) and
|
||
|
# man-pages(7) for information regarding the output.
|
||
|
class Man < Base
|
||
|
|
||
|
def convert(el, opts = {indent: 0, result: +''}) #:nodoc:
|
||
|
send("convert_#{el.type}", el, opts)
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def inner(el, opts, use = :all)
|
||
|
arr = el.children.reject {|e| e.type == :blank }
|
||
|
arr.each_with_index do |inner_el, index|
|
||
|
next if use == :rest && index == 0
|
||
|
break if use == :first && index > 0
|
||
|
options = opts.dup
|
||
|
options[:parent] = el
|
||
|
options[:index] = index
|
||
|
options[:prev] = (index == 0 ? nil : arr[index - 1])
|
||
|
options[:next] = (index == arr.length - 1 ? nil : arr[index + 1])
|
||
|
convert(inner_el, options)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_root(el, opts)
|
||
|
@title_done = false
|
||
|
opts[:result] = +".\\\" generated by kramdown\n"
|
||
|
inner(el, opts)
|
||
|
opts[:result]
|
||
|
end
|
||
|
|
||
|
def convert_blank(*)
|
||
|
end
|
||
|
alias convert_hr convert_blank
|
||
|
alias convert_xml_pi convert_blank
|
||
|
|
||
|
def convert_p(el, opts)
|
||
|
if (opts[:index] != 0 && opts[:prev].type != :header) ||
|
||
|
(opts[:parent].type == :blockquote && opts[:index] == 0)
|
||
|
opts[:result] << macro("P")
|
||
|
end
|
||
|
inner(el, opts)
|
||
|
newline(opts[:result])
|
||
|
end
|
||
|
|
||
|
def convert_header(el, opts)
|
||
|
return unless opts[:parent].type == :root
|
||
|
case el.options[:level]
|
||
|
when 1
|
||
|
unless @title_done
|
||
|
@title_done = true
|
||
|
data = el.options[:raw_text].scan(/([^(]+)\s*\((\d\w*)\)(?:\s*-+\s*(.*))?/).first ||
|
||
|
el.options[:raw_text].scan(/([^\s]+)\s*(?:-*\s+)?()(.*)/).first
|
||
|
return unless data && data[0]
|
||
|
name = data[0]
|
||
|
section = (data[1].to_s.empty? ? el.attr['data-section'] || '7' : data[1])
|
||
|
description = (data[2].to_s.empty? ? nil : " - #{data[2]}")
|
||
|
date = el.attr['data-date'] ? quote(el.attr['data-date']) : nil
|
||
|
extra = (el.attr['data-extra'] ? quote(escape(el.attr['data-extra'].to_s)) : nil)
|
||
|
opts[:result] << macro("TH", quote(escape(name.upcase)), quote(section), date, extra)
|
||
|
if description
|
||
|
opts[:result] << macro("SH", "NAME") << escape("#{name}#{description}") << "\n"
|
||
|
end
|
||
|
end
|
||
|
when 2
|
||
|
opts[:result] << macro("SH", quote(escape(el.options[:raw_text])))
|
||
|
when 3
|
||
|
opts[:result] << macro("SS", quote(escape(el.options[:raw_text])))
|
||
|
else
|
||
|
warning("Header levels greater than three are not supported")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_codeblock(el, opts)
|
||
|
opts[:result] << macro("sp") << macro("RS", 4) << macro("EX")
|
||
|
opts[:result] << newline(escape(el.value, true))
|
||
|
opts[:result] << macro("EE") << macro("RE")
|
||
|
end
|
||
|
|
||
|
def convert_blockquote(el, opts)
|
||
|
opts[:result] << macro("RS")
|
||
|
inner(el, opts)
|
||
|
opts[:result] << macro("RE")
|
||
|
end
|
||
|
|
||
|
def convert_ul(el, opts)
|
||
|
compact = (el.attr['class'] =~ /\bcompact\b/)
|
||
|
opts[:result] << macro("sp") << macro("PD", 0) if compact
|
||
|
inner(el, opts)
|
||
|
opts[:result] << macro("PD") if compact
|
||
|
end
|
||
|
alias convert_dl convert_ul
|
||
|
alias convert_ol convert_ul
|
||
|
|
||
|
def convert_li(el, opts)
|
||
|
sym = (opts[:parent].type == :ul ? '\(bu' : "#{opts[:index] + 1}.")
|
||
|
opts[:result] << macro("IP", sym, 4)
|
||
|
inner(el, opts, :first)
|
||
|
if el.children.size > 1
|
||
|
opts[:result] << macro("RS")
|
||
|
inner(el, opts, :rest)
|
||
|
opts[:result] << macro("RE")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_dt(el, opts)
|
||
|
opts[:result] << macro(opts[:prev] && opts[:prev].type == :dt ? "TQ" : "TP")
|
||
|
inner(el, opts)
|
||
|
opts[:result] << "\n"
|
||
|
end
|
||
|
|
||
|
def convert_dd(el, opts)
|
||
|
inner(el, opts, :first)
|
||
|
if el.children.size > 1
|
||
|
opts[:result] << macro("RS")
|
||
|
inner(el, opts, :rest)
|
||
|
opts[:result] << macro("RE")
|
||
|
end
|
||
|
opts[:result] << macro("sp") if opts[:next] && opts[:next].type == :dd
|
||
|
end
|
||
|
|
||
|
TABLE_CELL_ALIGNMENT = {left: 'l', center: 'c', right: 'r', default: 'l'}
|
||
|
|
||
|
def convert_table(el, opts)
|
||
|
opts[:alignment] = el.options[:alignment].map {|a| TABLE_CELL_ALIGNMENT[a] }
|
||
|
table_options = ["box"]
|
||
|
table_options << "center" if el.attr['class'] =~ /\bcenter\b/
|
||
|
opts[:result] << macro("TS") << "#{table_options.join(' ')} ;\n"
|
||
|
inner(el, opts)
|
||
|
opts[:result] << macro("TE") << macro("sp")
|
||
|
end
|
||
|
|
||
|
def convert_thead(el, opts)
|
||
|
opts[:result] << opts[:alignment].map {|a| "#{a}b" }.join(' ') << " .\n"
|
||
|
inner(el, opts)
|
||
|
opts[:result] << "=\n"
|
||
|
end
|
||
|
|
||
|
def convert_tbody(el, opts)
|
||
|
opts[:result] << ".T&\n" if opts[:index] != 0
|
||
|
opts[:result] << opts[:alignment].join(' ') << " .\n"
|
||
|
inner(el, opts)
|
||
|
opts[:result] << (opts[:next].type == :tfoot ? "=\n" : "_\n") if opts[:next]
|
||
|
end
|
||
|
|
||
|
def convert_tfoot(el, opts)
|
||
|
inner(el, opts)
|
||
|
end
|
||
|
|
||
|
def convert_tr(el, opts)
|
||
|
inner(el, opts)
|
||
|
opts[:result] << "\n"
|
||
|
end
|
||
|
|
||
|
def convert_td(el, opts)
|
||
|
result = opts[:result]
|
||
|
opts[:result] = +''
|
||
|
inner(el, opts)
|
||
|
if opts[:result] =~ /\n/
|
||
|
warning("Table cells using links are not supported")
|
||
|
result << "\t"
|
||
|
else
|
||
|
result << opts[:result] << "\t"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_html_element(*)
|
||
|
warning("HTML elements are not supported")
|
||
|
end
|
||
|
|
||
|
def convert_xml_comment(el, opts)
|
||
|
newline(opts[:result]) << ".\"#{escape(el.value, true).rstrip.gsub(/\n/, "\n.\"")}\n"
|
||
|
end
|
||
|
alias convert_comment convert_xml_comment
|
||
|
|
||
|
def convert_a(el, opts)
|
||
|
if el.children.size == 1 && el.children[0].type == :text &&
|
||
|
el.attr['href'] == el.children[0].value
|
||
|
newline(opts[:result]) << macro("UR", escape(el.attr['href'])) << macro("UE")
|
||
|
elsif el.attr['href'].start_with?('mailto:')
|
||
|
newline(opts[:result]) << macro("MT", escape(el.attr['href'].sub(/^mailto:/, ''))) <<
|
||
|
macro("UE")
|
||
|
else
|
||
|
newline(opts[:result]) << macro("UR", escape(el.attr['href']))
|
||
|
inner(el, opts)
|
||
|
newline(opts[:result]) << macro("UE")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_img(_el, _opts)
|
||
|
warning("Images are not supported")
|
||
|
end
|
||
|
|
||
|
def convert_em(el, opts)
|
||
|
opts[:result] << '\fI'
|
||
|
inner(el, opts)
|
||
|
opts[:result] << '\fP'
|
||
|
end
|
||
|
|
||
|
def convert_strong(el, opts)
|
||
|
opts[:result] << '\fB'
|
||
|
inner(el, opts)
|
||
|
opts[:result] << '\fP'
|
||
|
end
|
||
|
|
||
|
def convert_codespan(el, opts)
|
||
|
opts[:result] << "\\fB#{escape(el.value)}\\fP"
|
||
|
end
|
||
|
|
||
|
def convert_br(_el, opts)
|
||
|
newline(opts[:result]) << macro("br")
|
||
|
end
|
||
|
|
||
|
def convert_abbreviation(el, opts)
|
||
|
opts[:result] << escape(el.value)
|
||
|
end
|
||
|
|
||
|
def convert_math(el, opts)
|
||
|
if el.options[:category] == :block
|
||
|
convert_codeblock(el, opts)
|
||
|
else
|
||
|
convert_codespan(el, opts)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def convert_footnote(*)
|
||
|
warning("Footnotes are not supported")
|
||
|
end
|
||
|
|
||
|
def convert_raw(*)
|
||
|
warning("Raw content is not supported")
|
||
|
end
|
||
|
|
||
|
def convert_text(el, opts)
|
||
|
text = escape(el.value)
|
||
|
text.lstrip! if opts[:result][-1] == "\n"
|
||
|
opts[:result] << text
|
||
|
end
|
||
|
|
||
|
def convert_entity(el, opts)
|
||
|
opts[:result] << unicode_char(el.value.code_point)
|
||
|
end
|
||
|
|
||
|
def convert_smart_quote(el, opts)
|
||
|
opts[:result] << unicode_char(::Kramdown::Utils::Entities.entity(el.value.to_s).code_point)
|
||
|
end
|
||
|
|
||
|
TYPOGRAPHIC_SYMS_MAP = {
|
||
|
mdash: '\(em', ndash: '\(em', hellip: '\.\.\.',
|
||
|
laquo_space: '\[Fo]', raquo_space: '\[Fc]', laquo: '\[Fo]', raquo: '\[Fc]'
|
||
|
}
|
||
|
|
||
|
def convert_typographic_sym(el, opts)
|
||
|
opts[:result] << TYPOGRAPHIC_SYMS_MAP[el.value]
|
||
|
end
|
||
|
|
||
|
def macro(name, *args)
|
||
|
".#{[name, *args].compact.join(' ')}\n"
|
||
|
end
|
||
|
|
||
|
def newline(text)
|
||
|
text << "\n" unless text[-1] == "\n"
|
||
|
text
|
||
|
end
|
||
|
|
||
|
def quote(text)
|
||
|
"\"#{text.gsub(/"/, '\\"')}\""
|
||
|
end
|
||
|
|
||
|
def escape(text, preserve_whitespace = false)
|
||
|
text = (preserve_whitespace ? text.dup : text.gsub(/\s+/, ' '))
|
||
|
text.gsub!('\\', "\\e")
|
||
|
text.gsub!(/^\./, '\\\\&.')
|
||
|
text.gsub!(/[.'-]/) {|m| "\\#{m}" }
|
||
|
text
|
||
|
end
|
||
|
|
||
|
def unicode_char(codepoint)
|
||
|
"\\[u#{codepoint.to_s(16).rjust(4, '0')}]"
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|