red_quilt 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +109 -0
  4. data/.rubocop_todo.yml +7 -0
  5. data/CHANGELOG.md +57 -0
  6. data/README.md +284 -0
  7. data/Rakefile +8 -0
  8. data/ast-spec.md +1227 -0
  9. data/docs/architecture.md +81 -0
  10. data/docs/arena-usage.md +363 -0
  11. data/docs/commonmark-conformance.md +241 -0
  12. data/exe/redquilt +7 -0
  13. data/lib/red_quilt/arena.rb +366 -0
  14. data/lib/red_quilt/block_parser.rb +724 -0
  15. data/lib/red_quilt/blockquote.rb +151 -0
  16. data/lib/red_quilt/cli.rb +182 -0
  17. data/lib/red_quilt/diagnostic.rb +47 -0
  18. data/lib/red_quilt/document.rb +126 -0
  19. data/lib/red_quilt/extended_autolink_pass.rb +185 -0
  20. data/lib/red_quilt/footnote_definition.rb +147 -0
  21. data/lib/red_quilt/footnote_pass.rb +39 -0
  22. data/lib/red_quilt/footnote_registry.rb +68 -0
  23. data/lib/red_quilt/indentation.rb +73 -0
  24. data/lib/red_quilt/inline/builder.rb +674 -0
  25. data/lib/red_quilt/inline/flanking.rb +120 -0
  26. data/lib/red_quilt/inline/html_entities.rb +2180 -0
  27. data/lib/red_quilt/inline/lexer.rb +280 -0
  28. data/lib/red_quilt/inline/link_scanner.rb +315 -0
  29. data/lib/red_quilt/inline/token_kind.rb +39 -0
  30. data/lib/red_quilt/inline/tokens.rb +73 -0
  31. data/lib/red_quilt/inline.rb +34 -0
  32. data/lib/red_quilt/inline_pass.rb +53 -0
  33. data/lib/red_quilt/line.rb +14 -0
  34. data/lib/red_quilt/lint_pass.rb +71 -0
  35. data/lib/red_quilt/list.rb +317 -0
  36. data/lib/red_quilt/node_ref.rb +114 -0
  37. data/lib/red_quilt/node_type.rb +66 -0
  38. data/lib/red_quilt/plain_text.rb +46 -0
  39. data/lib/red_quilt/reference_definition.rb +309 -0
  40. data/lib/red_quilt/renderer/html.rb +279 -0
  41. data/lib/red_quilt/renderer/mdast.rb +152 -0
  42. data/lib/red_quilt/source_map.rb +29 -0
  43. data/lib/red_quilt/source_span.rb +26 -0
  44. data/lib/red_quilt/theme.rb +28 -0
  45. data/lib/red_quilt/themes/default.css +87 -0
  46. data/lib/red_quilt/version.rb +5 -0
  47. data/lib/red_quilt.rb +86 -0
  48. data/mise.toml +2 -0
  49. data/sig/red_quilt.rbs +45 -0
  50. metadata +91 -0
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedQuilt
4
+ module Inline
5
+ # CommonMark spec 6.2 flanking delimiter run helpers.
6
+ #
7
+ # Determines whether a delimiter run can open and/or close an emphasis.
8
+ # All input positions are byte offsets into the document source.
9
+ module Flanking
10
+ UNICODE_WHITESPACE_RE = /\A[\s   -    ]\z/
11
+ # CommonMark 0.31.2 expanded the definition of "punctuation" for
12
+ # flanking purposes to also include Unicode S (symbol) category, so
13
+ # currency / math / other symbols form delimiter-run boundaries.
14
+ UNICODE_PUNCT_RE = /\A[\p{P}\p{S}]\z/
15
+
16
+ # Fast-path lookup table for ASCII whitespace. Flanking inputs are
17
+ # mostly single-byte ASCII; the table lets us skip regex matches
18
+ # entirely on the hot path. (ASCII punctuation uses the shared
19
+ # Inline::ASCII_PUNCT table.)
20
+ ASCII_WHITESPACE = Array.new(128, false)
21
+ [0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20].each { |b| ASCII_WHITESPACE[b] = true }
22
+ ASCII_WHITESPACE.freeze
23
+
24
+ module_function
25
+
26
+ # Returns the character immediately before the byte position, or nil
27
+ # if at the start of source / outside the inline range.
28
+ def char_before(source, byte_pos, range_start)
29
+ return nil if byte_pos <= range_start
30
+
31
+ prev = byte_pos - 1
32
+ b = source.getbyte(prev)
33
+ # ASCII fast path: shared 1-byte string (avoids byteslice + alloc).
34
+ return BYTE_CHR[b] if b < 0x80
35
+
36
+ # Walk back at most 4 bytes to find the UTF-8 code point start.
37
+ i = prev
38
+ while i >= range_start && i > byte_pos - 4
39
+ b = source.getbyte(i)
40
+ if b < 0x80 || b >= 0xC0
41
+ return source.byteslice(i, byte_pos - i)
42
+ end
43
+
44
+ i -= 1
45
+ end
46
+ nil
47
+ end
48
+
49
+ def char_at(source, byte_pos, range_end)
50
+ return nil if byte_pos >= range_end
51
+
52
+ b = source.getbyte(byte_pos)
53
+ return BYTE_CHR[b] if b < 0x80
54
+
55
+ len = if b < 0xC0
56
+ 1
57
+ elsif b < 0xE0
58
+ 2
59
+ elsif b < 0xF0
60
+ 3
61
+ else
62
+ 4
63
+ end
64
+ source.byteslice(byte_pos, [len, range_end - byte_pos].min)
65
+ end
66
+
67
+ def whitespace?(char)
68
+ return true if char.nil?
69
+ if char.bytesize == 1
70
+ return ASCII_WHITESPACE[char.getbyte(0)]
71
+ end
72
+
73
+ UNICODE_WHITESPACE_RE.match?(char)
74
+ end
75
+
76
+ def punctuation?(char)
77
+ return false if char.nil?
78
+ if char.bytesize == 1
79
+ return ASCII_PUNCT[char.getbyte(0)]
80
+ end
81
+
82
+ UNICODE_PUNCT_RE.match?(char)
83
+ end
84
+
85
+ # CommonMark spec: left-flanking delimiter run.
86
+ def left_flanking?(prev_char, next_char)
87
+ return false if whitespace?(next_char)
88
+ return true unless punctuation?(next_char)
89
+
90
+ whitespace?(prev_char) || punctuation?(prev_char)
91
+ end
92
+
93
+ # CommonMark spec: right-flanking delimiter run.
94
+ def right_flanking?(prev_char, next_char)
95
+ return false if whitespace?(prev_char)
96
+ return true unless punctuation?(prev_char)
97
+
98
+ whitespace?(next_char) || punctuation?(next_char)
99
+ end
100
+
101
+ # Returns [can_open, can_close] for a delimiter run.
102
+ #
103
+ # char must be "*", "_", or "~". For "_", word-character adjacency
104
+ # rules apply on top of the flanking rules; "*" and "~" use plain
105
+ # flanking only.
106
+ def can_open_close(char, prev_char, next_char)
107
+ left = left_flanking?(prev_char, next_char)
108
+ right = right_flanking?(prev_char, next_char)
109
+ if char == "_"
110
+ can_open = left && (!right || punctuation?(prev_char))
111
+ can_close = right && (!left || punctuation?(next_char))
112
+ else
113
+ can_open = left
114
+ can_close = right
115
+ end
116
+ [can_open, can_close]
117
+ end
118
+ end
119
+ end
120
+ end