2010-12-20 06:22:49 +03:00
|
|
|
|
# coding: utf-8
|
2015-12-16 08:07:31 +03:00
|
|
|
|
# frozen_string_literal: false
|
2010-12-20 06:22:49 +03:00
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# For RDoc::Text#to_html
|
|
|
|
|
|
|
|
|
|
require 'strscan'
|
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
##
|
|
|
|
|
# For RDoc::Text#snippet
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
gem 'json'
|
2015-12-22 15:08:13 +03:00
|
|
|
|
rescue NameError => e # --disable-gems
|
|
|
|
|
raise unless e.name == :gem
|
2012-11-27 08:28:14 +04:00
|
|
|
|
rescue Gem::LoadError
|
|
|
|
|
end
|
|
|
|
|
|
2010-04-01 11:45:16 +04:00
|
|
|
|
##
|
|
|
|
|
# Methods for manipulating comment text
|
|
|
|
|
|
|
|
|
|
module RDoc::Text
|
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
##
|
|
|
|
|
# Maps markup formats to classes that can parse them. If the format is
|
|
|
|
|
# unknown, "rdoc" format is used.
|
|
|
|
|
|
|
|
|
|
MARKUP_FORMAT = {
|
|
|
|
|
'markdown' => RDoc::Markdown,
|
|
|
|
|
'rdoc' => RDoc::Markup,
|
|
|
|
|
'rd' => RDoc::RD,
|
|
|
|
|
'tomdoc' => RDoc::TomDoc,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MARKUP_FORMAT.default = RDoc::Markup
|
|
|
|
|
|
2010-12-20 06:22:49 +03:00
|
|
|
|
##
|
|
|
|
|
# Maps an encoding to a Hash of characters properly transcoded for that
|
|
|
|
|
# encoding.
|
|
|
|
|
#
|
|
|
|
|
# See also encode_fallback.
|
|
|
|
|
|
|
|
|
|
TO_HTML_CHARACTERS = Hash.new do |h, encoding|
|
|
|
|
|
h[encoding] = {
|
|
|
|
|
:close_dquote => encode_fallback('”', encoding, '"'),
|
|
|
|
|
:close_squote => encode_fallback('’', encoding, '\''),
|
|
|
|
|
:copyright => encode_fallback('©', encoding, '(c)'),
|
|
|
|
|
:ellipsis => encode_fallback('…', encoding, '...'),
|
|
|
|
|
:em_dash => encode_fallback('—', encoding, '---'),
|
|
|
|
|
:en_dash => encode_fallback('–', encoding, '--'),
|
|
|
|
|
:open_dquote => encode_fallback('“', encoding, '"'),
|
|
|
|
|
:open_squote => encode_fallback('‘', encoding, '\''),
|
|
|
|
|
:trademark => encode_fallback('®', encoding, '(r)'),
|
|
|
|
|
}
|
|
|
|
|
end if Object.const_defined? :Encoding
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Transcodes +character+ to +encoding+ with a +fallback+ character.
|
|
|
|
|
|
|
|
|
|
def self.encode_fallback character, encoding, fallback
|
|
|
|
|
character.encode(encoding, :fallback => { character => fallback },
|
|
|
|
|
:undef => :replace, :replace => fallback)
|
|
|
|
|
end
|
|
|
|
|
|
2010-04-01 11:45:16 +04:00
|
|
|
|
##
|
|
|
|
|
# Expands tab characters in +text+ to eight spaces
|
|
|
|
|
|
|
|
|
|
def expand_tabs text
|
|
|
|
|
expanded = []
|
|
|
|
|
|
|
|
|
|
text.each_line do |line|
|
2015-01-12 12:01:28 +03:00
|
|
|
|
nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do
|
2011-02-07 10:07:12 +03:00
|
|
|
|
r = "#{$1}#{$2}#{' ' * (8 - $2.size)}"
|
|
|
|
|
r.force_encoding text.encoding if Object.const_defined? :Encoding
|
|
|
|
|
r
|
2015-01-12 12:01:28 +03:00
|
|
|
|
end
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
|
|
|
|
expanded << line
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
expanded.join
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Flush +text+ left based on the shortest line
|
|
|
|
|
|
|
|
|
|
def flush_left text
|
2011-05-14 04:39:16 +04:00
|
|
|
|
indent = 9999
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
|
|
|
|
text.each_line do |line|
|
2011-05-14 04:39:16 +04:00
|
|
|
|
line_indent = line =~ /\S/ || 9999
|
|
|
|
|
indent = line_indent if indent > line_indent
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
2011-02-07 10:07:12 +03:00
|
|
|
|
empty = ''
|
|
|
|
|
empty.force_encoding text.encoding if Object.const_defined? :Encoding
|
|
|
|
|
|
2011-05-14 04:39:16 +04:00
|
|
|
|
text.gsub(/^ {0,#{indent}}/, empty)
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
2010-12-20 06:22:49 +03:00
|
|
|
|
# Convert a string in markup format into HTML.
|
2010-04-01 11:45:16 +04:00
|
|
|
|
#
|
|
|
|
|
# Requires the including class to implement #formatter
|
|
|
|
|
|
|
|
|
|
def markup text
|
2014-09-05 05:41:25 +04:00
|
|
|
|
if @store.rdoc.options
|
|
|
|
|
locale = @store.rdoc.options.locale
|
|
|
|
|
else
|
|
|
|
|
locale = nil
|
|
|
|
|
end
|
|
|
|
|
if locale
|
|
|
|
|
i18n_text = RDoc::I18n::Text.new(text)
|
|
|
|
|
text = i18n_text.translate(locale)
|
|
|
|
|
end
|
2012-11-27 08:28:14 +04:00
|
|
|
|
parse(text).accept formatter
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Strips hashes, expands tabs then flushes +text+ to the left
|
|
|
|
|
|
|
|
|
|
def normalize_comment text
|
|
|
|
|
return text if text.empty?
|
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
text = strip_stars text
|
|
|
|
|
text = strip_hashes text
|
|
|
|
|
text = expand_tabs text
|
|
|
|
|
text = flush_left text
|
2011-02-02 03:32:30 +03:00
|
|
|
|
text = strip_newlines text
|
|
|
|
|
text
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Normalizes +text+ then builds a RDoc::Markup::Document from it
|
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
def parse text, format = 'rdoc'
|
2010-04-01 11:45:16 +04:00
|
|
|
|
return text if RDoc::Markup::Document === text
|
2012-11-27 08:28:14 +04:00
|
|
|
|
return text.parse if RDoc::Comment === text
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
text = normalize_comment text # TODO remove, should not be necessary
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
|
|
|
|
return RDoc::Markup::Document.new if text =~ /\A\n*\z/
|
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
MARKUP_FORMAT[format].parse text
|
|
|
|
|
end
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
##
|
|
|
|
|
# The first +limit+ characters of +text+ as HTML
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
2012-11-27 08:28:14 +04:00
|
|
|
|
def snippet text, limit = 100
|
|
|
|
|
document = parse text
|
2010-04-01 11:45:16 +04:00
|
|
|
|
|
2013-09-19 03:33:36 +04:00
|
|
|
|
RDoc::Markup::ToHtmlSnippet.new(options, limit).convert document
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Strips leading # characters from +text+
|
|
|
|
|
|
|
|
|
|
def strip_hashes text
|
|
|
|
|
return text if text =~ /^(?>\s*)[^\#]/
|
2011-02-02 03:32:30 +03:00
|
|
|
|
|
|
|
|
|
empty = ''
|
|
|
|
|
empty.force_encoding text.encoding if Object.const_defined? :Encoding
|
|
|
|
|
|
|
|
|
|
text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty)
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Strips leading and trailing \n characters from +text+
|
|
|
|
|
|
|
|
|
|
def strip_newlines text
|
2011-02-02 03:32:30 +03:00
|
|
|
|
text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Strips /* */ style comments
|
|
|
|
|
|
|
|
|
|
def strip_stars text
|
2012-11-27 08:28:14 +04:00
|
|
|
|
return text unless text =~ %r%/\*.*\*/%m
|
|
|
|
|
|
2011-02-07 10:07:12 +03:00
|
|
|
|
encoding = text.encoding if Object.const_defined? :Encoding
|
|
|
|
|
|
2011-06-16 08:59:24 +04:00
|
|
|
|
text = text.gsub %r%Document-method:\s+[\w:.#=!?]+%, ''
|
2011-02-07 10:07:12 +03:00
|
|
|
|
|
|
|
|
|
space = ' '
|
|
|
|
|
space.force_encoding encoding if encoding
|
|
|
|
|
|
|
|
|
|
text.sub! %r%/\*+% do space * $&.length end
|
|
|
|
|
text.sub! %r%\*+/% do space * $&.length end
|
|
|
|
|
text.gsub! %r%^[ \t]*\*%m do space * $&.length end
|
|
|
|
|
|
|
|
|
|
empty = ''
|
|
|
|
|
empty.force_encoding encoding if encoding
|
|
|
|
|
text.gsub(/^\s+$/, empty)
|
2010-12-20 06:22:49 +03:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Converts ampersand, dashes, ellipsis, quotes, copyright and registered
|
|
|
|
|
# trademark symbols in +text+ to properly encoded characters.
|
|
|
|
|
|
|
|
|
|
def to_html text
|
|
|
|
|
if Object.const_defined? :Encoding then
|
|
|
|
|
html = ''.encode text.encoding
|
|
|
|
|
|
|
|
|
|
encoded = RDoc::Text::TO_HTML_CHARACTERS[text.encoding]
|
|
|
|
|
else
|
|
|
|
|
html = ''
|
|
|
|
|
encoded = {
|
|
|
|
|
:close_dquote => '”',
|
|
|
|
|
:close_squote => '’',
|
|
|
|
|
:copyright => '©',
|
|
|
|
|
:ellipsis => '…',
|
|
|
|
|
:em_dash => '—',
|
|
|
|
|
:en_dash => '–',
|
|
|
|
|
:open_dquote => '“',
|
|
|
|
|
:open_squote => '‘',
|
|
|
|
|
:trademark => '®',
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
s = StringScanner.new text
|
|
|
|
|
insquotes = false
|
|
|
|
|
indquotes = false
|
|
|
|
|
after_word = nil
|
|
|
|
|
|
|
|
|
|
until s.eos? do
|
|
|
|
|
case
|
2012-11-27 08:28:14 +04:00
|
|
|
|
when s.scan(/<(tt|code)>.*?<\/\1>/) then # skip contents of tt
|
2010-12-20 06:22:49 +03:00
|
|
|
|
html << s.matched.gsub('\\\\', '\\')
|
2012-11-27 08:28:14 +04:00
|
|
|
|
when s.scan(/<(tt|code)>.*?/) then
|
|
|
|
|
warn "mismatched <#{s[1]}> tag" # TODO signal file/line
|
2010-12-20 06:22:49 +03:00
|
|
|
|
html << s.matched
|
|
|
|
|
when s.scan(/<[^>]+\/?s*>/) then # skip HTML tags
|
|
|
|
|
html << s.matched
|
|
|
|
|
when s.scan(/\\(\S)/) then # unhandled suppressed crossref
|
|
|
|
|
html << s[1]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/\.\.\.(\.?)/) then
|
|
|
|
|
html << s[1] << encoded[:ellipsis]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/\(c\)/) then
|
|
|
|
|
html << encoded[:copyright]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/\(r\)/) then
|
|
|
|
|
html << encoded[:trademark]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/---/) then
|
|
|
|
|
html << encoded[:em_dash]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/--/) then
|
|
|
|
|
html << encoded[:en_dash]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/"|"/) then
|
|
|
|
|
html << encoded[indquotes ? :close_dquote : :open_dquote]
|
|
|
|
|
indquotes = !indquotes
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/``/) then # backtick double quote
|
|
|
|
|
html << encoded[:open_dquote]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/''/) then # tick double quote
|
|
|
|
|
html << encoded[:close_dquote]
|
|
|
|
|
after_word = nil
|
|
|
|
|
when s.scan(/'/) then # single quote
|
|
|
|
|
if insquotes
|
|
|
|
|
html << encoded[:close_squote]
|
|
|
|
|
insquotes = false
|
|
|
|
|
elsif after_word
|
|
|
|
|
# Mary's dog, my parents' house: do not start paired quotes
|
|
|
|
|
html << encoded[:close_squote]
|
|
|
|
|
else
|
|
|
|
|
html << encoded[:open_squote]
|
|
|
|
|
insquotes = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after_word = nil
|
|
|
|
|
else # advance to the next potentially significant character
|
|
|
|
|
match = s.scan(/.+?(?=[<\\.("'`&-])/) #"
|
|
|
|
|
|
|
|
|
|
if match then
|
|
|
|
|
html << match
|
|
|
|
|
after_word = match =~ /\w$/
|
|
|
|
|
else
|
|
|
|
|
html << s.rest
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
html
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|
|
|
|
|
|
2011-02-05 09:20:57 +03:00
|
|
|
|
##
|
|
|
|
|
# Wraps +txt+ to +line_len+
|
|
|
|
|
|
|
|
|
|
def wrap(txt, line_len = 76)
|
|
|
|
|
res = []
|
|
|
|
|
sp = 0
|
|
|
|
|
ep = txt.length
|
|
|
|
|
|
|
|
|
|
while sp < ep
|
|
|
|
|
# scan back for a space
|
|
|
|
|
p = sp + line_len - 1
|
|
|
|
|
if p >= ep
|
|
|
|
|
p = ep
|
|
|
|
|
else
|
|
|
|
|
while p > sp and txt[p] != ?\s
|
|
|
|
|
p -= 1
|
|
|
|
|
end
|
|
|
|
|
if p <= sp
|
|
|
|
|
p = sp + line_len
|
|
|
|
|
while p < ep and txt[p] != ?\s
|
|
|
|
|
p += 1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
res << txt[sp...p] << "\n"
|
|
|
|
|
sp = p
|
|
|
|
|
sp += 1 while sp < ep and txt[sp] == ?\s
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
res.join.strip
|
|
|
|
|
end
|
|
|
|
|
|
2010-04-01 11:45:16 +04:00
|
|
|
|
end
|