Skip to content

Commit 427e34a

Browse files
committed
more compatibility
1 parent a72f07b commit 427e34a

2 files changed

Lines changed: 202 additions & 60 deletions

File tree

lib/yard/templates/helpers/markup/hybrid_markdown.rb

Lines changed: 202 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class HybridMarkdown
7676
PLACEHOLDER_RE = /\0(\d+)\0/.freeze
7777
ESCAPABLE_CHARS_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])/.freeze
7878
AUTOLINK_RE = /<([A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\s]*|[A-Za-z0-9.!#$%&'*+\/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+)>/.freeze
79+
TAB_WIDTH = 4
7980

8081
def initialize(text)
8182
@references = {}
@@ -231,7 +232,7 @@ def parse_yard_indented_code(lines, index)
231232

232233
while index < lines.length
233234
line = lines[index]
234-
break unless blank_line?(line) || line =~ /^(?: {2,}|\t)/
235+
break unless blank_line?(line) || indented_code_start?(line)
235236
body << line
236237
index += 1
237238
end
@@ -280,7 +281,7 @@ def parse_list(lines, index)
280281
list_indent = marker[:indent]
281282

282283
while index < lines.length
283-
break if items.any? && thematic_break?(lines[index]) && leading_spaces(lines[index]) <= list_indent + 3
284+
break if items.any? && thematic_break?(lines[index]) && leading_columns(lines[index]) <= list_indent + 3
284285

285286
item_marker = parse_list_marker(lines[index])
286287
break unless item_marker && same_list_type?(marker, item_marker)
@@ -318,13 +319,14 @@ def parse_list(lines, index)
318319
if blank_line?(line)
319320
item_lines << "\n"
320321
blank_seen = true
321-
elsif blank_seen && (line.start_with?("\t") || leading_spaces(line) >= content_indent)
322-
break if first_line.empty? && item_lines.all? { |item_line| item_line == "\n" } && !line.start_with?("\t") && leading_spaces(line) == content_indent
322+
elsif blank_seen && indented_to?(line, content_indent)
323+
break if first_line.empty? && item_lines.all? { |item_line| item_line == "\n" } &&
324+
leading_columns(line) == content_indent
323325
item_loose = true if loose_list_item_continuation?(item_lines)
324326
stripped = strip_list_item_indent(line, content_indent)
325327
item_lines << stripped
326328
blank_seen = false
327-
elsif !blank_seen && (line.start_with?("\t") || leading_spaces(line) >= content_indent)
329+
elsif !blank_seen && indented_to?(line, content_indent)
328330
stripped = strip_list_item_indent(line, content_indent)
329331
item_lines << stripped
330332
blank_seen = false
@@ -432,9 +434,8 @@ def parse_blockquote(lines, index)
432434
if blank_line?(line)
433435
quoted_lines << "\n"
434436
previous_blank = true
435-
elsif blockquote_start?(line)
436-
line =~ BLOCKQUOTE_RE
437-
quoted_lines << "#{$1}\n"
437+
elsif (stripped = strip_blockquote_marker(line))
438+
quoted_lines << stripped
438439
saw_quote = true
439440
previous_blank = false
440441
else
@@ -809,25 +810,25 @@ def fenced_code_start?(line)
809810
end
810811

811812
def indented_code_start?(line)
812-
line =~ /^(?: {2,}|\t)/
813+
leading_columns(line) >= 2
813814
end
814815

815816
def indented_code_block_start?(lines, index)
816817
return false unless indented_code_start?(lines[index])
817-
return true if lines[index] =~ /^(?: {4,}|\t)/
818+
return true if leading_columns(lines[index]) >= 4
818819

819820
!index.zero? && blank_line?(lines[index - 1])
820821
end
821822

822823
def yard_indented_code_start?(lines, index)
823-
return false unless lines[index] =~ /^(?: {2,}|\t)!!!([\w.+-]+)[ \t]*$/
824+
return false unless leading_columns(lines[index]) >= 2
825+
return false unless consume_columns(lines[index], 2) =~ /^!!!([\w.+-]+)[ \t]*$/
824826
return false if index + 1 >= lines.length
825827

826-
indented_code_block_start?(lines, index) && lines[index + 1] =~ /^(?: {2,}|\t)/
828+
indented_code_block_start?(lines, index) && indented_code_start?(lines[index + 1])
827829
end
828830

829831
def list_start?(line, interrupt_paragraph = false)
830-
return true if line =~ UNORDERED_LIST_RE || line =~ RDOC_ORDERED_LIST_RE
831832
return false unless (marker = parse_list_marker(line))
832833
return true unless interrupt_paragraph
833834

@@ -843,7 +844,7 @@ def labeled_list_start?(lines, index)
843844
end
844845

845846
def blockquote_start?(line)
846-
line =~ BLOCKQUOTE_RE
847+
!strip_blockquote_marker(line).nil?
847848
end
848849

849850
def html_block_start?(line, interrupt_paragraph = false)
@@ -875,26 +876,14 @@ def table_alignment(cell)
875876

876877
def unindent(lines)
877878
indent = lines.reject { |line| blank_line?(line) }.map do |line|
878-
line.start_with?("\t") ? 4 : line[/\A +/, 0].to_s.length
879+
leading_columns(line)
879880
end.min || 4
880881

881-
lines.map do |line|
882-
if line.start_with?("\t")
883-
line.sub(/^\t/, '')
884-
else
885-
line.sub(/\A {0,#{indent}}/, '')
886-
end
887-
end.join
882+
lines.map { |line| consume_columns(line, indent) }.join
888883
end
889884

890885
def unindent_indented_code(lines)
891-
lines.map do |line|
892-
if line.start_with?("\t")
893-
line.sub(/^\t/, '')
894-
else
895-
line.sub(/^ {0,4}/, '')
896-
end
897-
end.join
886+
lines.map { |line| consume_columns(line, 4) }.join
898887
end
899888

900889
def code_block(text, lang = nil)
@@ -961,29 +950,58 @@ def strip_fenced_indent(line, indent)
961950
end
962951

963952
def parse_list_marker(line)
964-
if line =~ /^(\s{0,3})([*+-])([ \t]*)(.*)$/
965-
match = Regexp.last_match
966-
return nil if match[3].empty? && !match[4].empty?
967-
968-
return {:ordered => false, :bullet => match[2], :indent => match[1].length,
969-
:marker_length => 1, :padding => match[3].length, :content => match[4]}
953+
source = line.to_s.sub(/\n\z/, '')
954+
indent, index = scan_leading_columns(source)
955+
return nil if indent > 3
956+
return nil if index >= source.length
957+
958+
char = source[index, 1]
959+
current_column = indent
960+
961+
if '*+-'.include?(char)
962+
marker_length = 1
963+
marker_end = index + 1
964+
current_column += 1
965+
padding, marker_end = scan_padding_columns(source, marker_end, current_column)
966+
content = source[marker_end..-1].to_s
967+
return nil if padding.zero? && !content.empty?
968+
969+
return {:ordered => false, :bullet => char, :indent => indent,
970+
:marker_length => marker_length, :padding => padding, :content => content}
970971
end
971972

972-
if line =~ /^(\s{0,3})(\d{1,9})([.)])([ \t]*)(.*)$/
973-
match = Regexp.last_match
974-
return nil if match[4].empty? && !match[5].empty?
975-
976-
return {:ordered => true, :delimiter => match[3], :start => match[2].to_i,
977-
:indent => match[1].length, :marker_length => match[2].length + 1,
978-
:padding => match[4].length, :content => match[5]}
973+
number = source[index..-1][/^\d{1,9}/]
974+
if number
975+
marker_end = index + number.length
976+
delimiter = source[marker_end, 1]
977+
if delimiter == '.' || delimiter == ')'
978+
marker_length = number.length + 1
979+
current_column += marker_length
980+
marker_end += 1
981+
padding, marker_end = scan_padding_columns(source, marker_end, current_column)
982+
content = source[marker_end..-1].to_s
983+
return nil if padding.zero? && !content.empty?
984+
985+
return {:ordered => true, :delimiter => delimiter, :start => number.to_i,
986+
:indent => indent, :marker_length => marker_length,
987+
:padding => padding, :content => content}
988+
end
979989
end
980990

981-
return nil unless line =~ /^\s{0,3}([A-Za-z])\.(?:[ \t]+(.*))$/
991+
if source[index, 2] =~ /\A[A-Za-z]\.\z/
992+
marker_length = 2
993+
marker_end = index + marker_length
994+
current_column += marker_length
995+
padding, marker_end = scan_padding_columns(source, marker_end, current_column)
996+
content = source[marker_end..-1].to_s
997+
return nil if padding.zero? && !content.empty?
998+
999+
return {:ordered => true, :delimiter => '.', :start => 1,
1000+
:indent => indent, :marker_length => marker_length,
1001+
:padding => padding, :content => content}
1002+
end
9821003

983-
match = Regexp.last_match
984-
{:ordered => true, :delimiter => '.', :start => 1,
985-
:indent => line[/\A */].to_s.length, :marker_length => 2, :padding => 1,
986-
:content => (match[2] || '')}
1004+
nil
9871005
end
9881006

9891007
def list_item_padding(marker)
@@ -1403,7 +1421,7 @@ def whitespace_char?(char)
14031421
def punctuation_char?(char)
14041422
return false if char.nil?
14051423

1406-
char =~ /[[:punct:]]/ || unicode_symbol_char?(char)
1424+
ascii_punctuation_char?(char) || unicode_symbol_char?(char)
14071425
end
14081426

14091427
def unicode_symbol_char?(char)
@@ -1420,20 +1438,28 @@ def unicode_symbol_char?(char)
14201438
(0x20A0..0x20CF).include?(codepoint)
14211439
end
14221440

1423-
def leading_spaces(line)
1424-
line[/\A */, 0].to_s.length
1441+
def ascii_punctuation_char?(char)
1442+
return false unless ascii_only_compat?(char)
1443+
1444+
byte = char.to_s.unpack('C').first
1445+
return false unless byte
1446+
1447+
(0x21..0x2F).include?(byte) ||
1448+
(0x3A..0x40).include?(byte) ||
1449+
(0x5B..0x60).include?(byte) ||
1450+
(0x7B..0x7E).include?(byte)
1451+
end
1452+
1453+
def leading_columns(line)
1454+
scan_leading_columns(line.to_s).first
14251455
end
14261456

14271457
def indented_to?(line, indent)
1428-
line.start_with?("\t") || leading_spaces(line) >= indent
1458+
leading_columns(line) >= indent
14291459
end
14301460

14311461
def strip_list_item_indent(line, content_indent)
1432-
if line.start_with?("\t")
1433-
line.sub(/^\t/, '')
1434-
else
1435-
line.sub(/\A {0,#{content_indent}}/, '')
1436-
end
1462+
consume_columns(line, content_indent, 0, true)
14371463
end
14381464

14391465
def escape_url(url)
@@ -1525,9 +1551,9 @@ def split_reference_container_prefix(line)
15251551
prefix = ''
15261552
content = line.chomp
15271553

1528-
while content =~ /\A(\s{0,3}> ?)(.*)\z/
1529-
prefix << $1
1530-
content = $2
1554+
while (split = split_blockquote_prefix(content))
1555+
prefix << split[0]
1556+
content = split[1].chomp
15311557
end
15321558

15331559
[prefix, content]
@@ -1681,6 +1707,102 @@ def split_lines(text)
16811707
text.to_s.split(/^/, -1)
16821708
end
16831709

1710+
def scan_leading_columns(text)
1711+
index = 0
1712+
column = 0
1713+
source = text.to_s
1714+
1715+
while index < source.length
1716+
char = source[index, 1]
1717+
if char == ' '
1718+
column += 1
1719+
elsif char == "\t"
1720+
column += TAB_WIDTH - (column % TAB_WIDTH)
1721+
else
1722+
break
1723+
end
1724+
index += 1
1725+
end
1726+
1727+
[column, index]
1728+
end
1729+
1730+
def scan_padding_columns(text, index, start_column)
1731+
column = start_column
1732+
padding = 0
1733+
source = text.to_s
1734+
1735+
while index < source.length
1736+
char = source[index, 1]
1737+
if char == ' '
1738+
column += 1
1739+
padding += 1
1740+
elsif char == "\t"
1741+
advance = TAB_WIDTH - (column % TAB_WIDTH)
1742+
column += advance
1743+
padding += advance
1744+
else
1745+
break
1746+
end
1747+
index += 1
1748+
end
1749+
1750+
[padding, index]
1751+
end
1752+
1753+
def consume_columns(text, columns, start_column = 0, normalize_remaining = false)
1754+
index = 0
1755+
column = start_column
1756+
remaining = columns
1757+
prefix_width = 0
1758+
source = text.to_s
1759+
1760+
while index < source.length && remaining > 0
1761+
char = source[index, 1]
1762+
if char == ' '
1763+
column += 1
1764+
remaining -= 1
1765+
index += 1
1766+
elsif char == "\t"
1767+
advance = TAB_WIDTH - (column % TAB_WIDTH)
1768+
if advance <= remaining
1769+
column += advance
1770+
remaining -= advance
1771+
index += 1
1772+
else
1773+
prefix_width += advance - remaining if normalize_remaining
1774+
column += advance
1775+
remaining = 0
1776+
index += 1
1777+
end
1778+
else
1779+
break
1780+
end
1781+
end
1782+
1783+
if normalize_remaining
1784+
while index < source.length
1785+
char = source[index, 1]
1786+
if char == ' '
1787+
prefix_width += 1
1788+
column += 1
1789+
index += 1
1790+
elsif char == "\t"
1791+
advance = TAB_WIDTH - (column % TAB_WIDTH)
1792+
prefix_width += advance
1793+
column += advance
1794+
index += 1
1795+
else
1796+
break
1797+
end
1798+
end
1799+
1800+
(' ' * prefix_width) + source[index..-1].to_s
1801+
else
1802+
source[index..-1].to_s
1803+
end
1804+
end
1805+
16841806
def lazy_blockquote_continuation?(quoted_lines, line)
16851807
return false if block_boundary?(line)
16861808
return false if indented_code_start?(line) && !blockquote_paragraph_context?(quoted_lines)
@@ -1726,6 +1848,27 @@ def normalize_heading_line(line)
17261848
normalize_paragraph_line(line).rstrip
17271849
end
17281850

1851+
def split_blockquote_prefix(line)
1852+
source = line.to_s
1853+
indent, index = scan_leading_columns(source)
1854+
return nil if indent > 3
1855+
return nil unless source[index, 1] == '>'
1856+
1857+
prefix = source[0..index]
1858+
rest = source[(index + 1)..-1].to_s
1859+
if rest.start_with?(' ') || rest.start_with?("\t")
1860+
prefix << rest[0, 1]
1861+
rest = consume_columns(rest, 1, indent + 1, true)
1862+
end
1863+
1864+
[prefix, rest.end_with?("\n") ? rest : "#{rest}\n"]
1865+
end
1866+
1867+
def strip_blockquote_marker(line)
1868+
split = split_blockquote_prefix(line)
1869+
split && split[1]
1870+
end
1871+
17291872
def loose_list_item_continuation?(item_lines)
17301873
return false if open_fence_in_lines?(item_lines)
17311874

0 commit comments

Comments
 (0)