ruby_rich 0.5.0 → 0.5.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.
- checksums.yaml +4 -4
- data/lib/ruby_rich/markdown.rb +294 -30
- data/lib/ruby_rich/table.rb +2 -2
- data/lib/ruby_rich/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35642b63217134791fd7201a1b26488473164d74646bfa3b2492294611e92340
|
|
4
|
+
data.tar.gz: 3c9633c93770785e7b003b81fb8ff484c21114184ac2b0615fee1bd16380fbca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21e958777b0107f4a92b7de61aa725cca02951b5aff8874c3ab9149b1c8cb45c1998d39f93529e04d92e2e45c4ba67ad4f053568ea1dfe503bd6d9568c7f511d
|
|
7
|
+
data.tar.gz: b5abb3cf83b0f53100f13286dcb388a54b9238503ff276f2a591a71d873eaaef116a4dc599175b75023388f95f30d6934da36071fddb88cecff367b1e07c2910
|
data/lib/ruby_rich/markdown.rb
CHANGED
|
@@ -286,8 +286,9 @@ module RubyRich
|
|
|
286
286
|
UNORDERED_MARKERS = { 1 => '•', 2 => '◦', 3 => '▸' }.freeze
|
|
287
287
|
|
|
288
288
|
def convert_li(el)
|
|
289
|
-
depth = [@
|
|
289
|
+
depth = [@list_types.length, 1].max
|
|
290
290
|
list_type = @list_types.last
|
|
291
|
+
indent = " " * (depth - 1)
|
|
291
292
|
marker = if list_type == :ol
|
|
292
293
|
@list_counters[-1] += 1
|
|
293
294
|
"#{@list_counters[-1]}."
|
|
@@ -295,11 +296,11 @@ module RubyRich
|
|
|
295
296
|
UNORDERED_MARKERS[depth.clamp(1, 3)] || '▸'
|
|
296
297
|
end
|
|
297
298
|
task = detect_task_marker(el)
|
|
298
|
-
text = inline_content(el)
|
|
299
|
+
text = inline_content(el).gsub(/\n{2,}/, "\n")
|
|
299
300
|
if task
|
|
300
|
-
"#{tc(task ? :task_checked : :task_unchecked)}#{task}#{AnsiCode.reset} #{text.strip}\n"
|
|
301
|
+
"#{indent}#{tc(task ? :task_checked : :task_unchecked)}#{task}#{AnsiCode.reset} #{text.strip}\n"
|
|
301
302
|
else
|
|
302
|
-
"#{tc(list_type == :ol ? :ordered_list : :"list_level_#{depth.clamp(1, 3)}")}#{marker}#{AnsiCode.reset} #{text.strip}\n"
|
|
303
|
+
"#{indent}#{tc(list_type == :ol ? :ordered_list : :"list_level_#{depth.clamp(1, 3)}")}#{marker}#{AnsiCode.reset} #{text.strip}\n"
|
|
303
304
|
end
|
|
304
305
|
end
|
|
305
306
|
|
|
@@ -737,34 +738,119 @@ module RubyRich
|
|
|
737
738
|
return formula if formula.nil? || formula.strip.empty?
|
|
738
739
|
|
|
739
740
|
result = formula.dup
|
|
740
|
-
result = process_frac(result)
|
|
741
|
-
result = process_sqrt(result)
|
|
742
741
|
result = process_cases(result)
|
|
743
|
-
result = process_scripts(result)
|
|
744
742
|
result = replace_symbols(result)
|
|
743
|
+
result = process_scripts(result)
|
|
744
|
+
result = process_frac(result)
|
|
745
|
+
result = process_sqrt(result)
|
|
745
746
|
result = strip_delim_spacing(result)
|
|
746
747
|
result
|
|
747
748
|
end
|
|
748
749
|
|
|
749
|
-
#
|
|
750
|
+
# Find the index of the } that matches the { at `open_pos`.
|
|
751
|
+
# Returns nil when braces are unbalanced.
|
|
752
|
+
def self.find_matching_brace(text, open_pos)
|
|
753
|
+
return nil unless text[open_pos] == '{'
|
|
754
|
+
depth = 1
|
|
755
|
+
i = open_pos + 1
|
|
756
|
+
while i < text.length && depth > 0
|
|
757
|
+
case text[i]
|
|
758
|
+
when '{' then depth += 1
|
|
759
|
+
when '}' then depth -= 1
|
|
760
|
+
when '\\' then i += 1
|
|
761
|
+
end
|
|
762
|
+
i += 1
|
|
763
|
+
end
|
|
764
|
+
depth == 0 ? i - 1 : nil
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# \frac{num}{den} / \dfrac{num}{den} / \tfrac{num}{den}
|
|
768
|
+
# → (num)/(den) when num/den include operators, otherwise num/den
|
|
750
769
|
def self.process_frac(text)
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
770
|
+
result = +""
|
|
771
|
+
i = 0
|
|
772
|
+
while i < text.length
|
|
773
|
+
cmd_len = nil
|
|
774
|
+
if text[i..].start_with?('\\dfrac') || text[i..].start_with?('\\tfrac')
|
|
775
|
+
cmd_len = 6
|
|
776
|
+
elsif text[i..].start_with?('\\frac')
|
|
777
|
+
cmd_len = 5
|
|
778
|
+
end
|
|
779
|
+
if cmd_len
|
|
780
|
+
j = i + cmd_len
|
|
781
|
+
while j < text.length && text[j] =~ /\s/
|
|
782
|
+
j += 1
|
|
783
|
+
end
|
|
784
|
+
if j < text.length && text[j] == '{'
|
|
785
|
+
num_start = j
|
|
786
|
+
num_end = find_matching_brace(text, num_start)
|
|
787
|
+
if num_end
|
|
788
|
+
k = num_end + 1
|
|
789
|
+
while k < text.length && text[k] =~ /\s/
|
|
790
|
+
k += 1
|
|
791
|
+
end
|
|
792
|
+
if k < text.length && text[k] == '{'
|
|
793
|
+
den_start = k
|
|
794
|
+
den_end = find_matching_brace(text, den_start)
|
|
795
|
+
if den_end
|
|
796
|
+
num = text[num_start + 1...num_end]
|
|
797
|
+
den = text[den_start + 1...den_end]
|
|
798
|
+
# Only wrap in parens when the expression includes
|
|
799
|
+
# operators that would change precedence without them.
|
|
800
|
+
op_rx = /[+\-±∓×÷=<>]/
|
|
801
|
+
num_wrap = num =~ op_rx ? "(#{num})" : num
|
|
802
|
+
den_wrap = den =~ op_rx ? "(#{den})" : den
|
|
803
|
+
result << "#{num_wrap}/#{den_wrap}"
|
|
804
|
+
i = den_end + 1
|
|
805
|
+
next
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
result << text[i]
|
|
812
|
+
i += 1
|
|
757
813
|
end
|
|
814
|
+
result
|
|
758
815
|
end
|
|
759
816
|
|
|
760
817
|
# \sqrt{x} → √(x) \sqrt[n]{x} → ⁿ√(x)
|
|
761
818
|
def self.process_sqrt(text)
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
819
|
+
result = +""
|
|
820
|
+
i = 0
|
|
821
|
+
while i < text.length
|
|
822
|
+
if text[i..].start_with?('\\sqrt')
|
|
823
|
+
j = i + 5
|
|
824
|
+
deg_text = nil
|
|
825
|
+
while j < text.length && text[j] =~ /\s/
|
|
826
|
+
j += 1
|
|
827
|
+
end
|
|
828
|
+
if j < text.length && text[j] == '['
|
|
829
|
+
close_br = text.index(']', j)
|
|
830
|
+
if close_br
|
|
831
|
+
deg_text = text[j + 1...close_br]
|
|
832
|
+
j = close_br + 1
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
while j < text.length && text[j] =~ /\s/
|
|
836
|
+
j += 1
|
|
837
|
+
end
|
|
838
|
+
if j < text.length && text[j] == '{'
|
|
839
|
+
rad_start = j
|
|
840
|
+
rad_end = find_matching_brace(text, rad_start)
|
|
841
|
+
if rad_end
|
|
842
|
+
rad = text[rad_start + 1...rad_end]
|
|
843
|
+
prefix = deg_text ? script_chars(deg_text, SUPERSCRIPTS) : ''
|
|
844
|
+
result << "#{prefix}√(#{rad})"
|
|
845
|
+
i = rad_end + 1
|
|
846
|
+
next
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
result << text[i]
|
|
851
|
+
i += 1
|
|
767
852
|
end
|
|
853
|
+
result
|
|
768
854
|
end
|
|
769
855
|
|
|
770
856
|
# \begin{cases} ... \end{cases} → ⎧ … ⎨ … ⎩ …
|
|
@@ -811,8 +897,15 @@ module RubyRich
|
|
|
811
897
|
|
|
812
898
|
# Replace \command tokens with Unicode equivalents.
|
|
813
899
|
def self.replace_symbols(text)
|
|
814
|
-
# Handle \text{
|
|
815
|
-
|
|
900
|
+
# Handle brace-wrapped font/formatting commands: \text{ab}, \mathbf{ab}, \mathbb{R}, etc.
|
|
901
|
+
# Strip the wrapper, keep the content.
|
|
902
|
+
text = text.gsub(/\\(?:text\w*|math[bif]|mathbf|mathrm|mathit|mathsf|mathtt|mathcal|mathfrak|mathbb|mathscr|boldsymbol|bm|emph)\s*\{(.*?)\}/) {
|
|
903
|
+
Regexp.last_match(1)
|
|
904
|
+
}
|
|
905
|
+
# Handle font commands with single-char arg (space-separated): \mathbf E
|
|
906
|
+
text = text.gsub(/\\(?:mathbf|mathrm|mathit|mathsf|mathtt|mathcal|mathfrak|mathbb|mathscr|boldsymbol|bm)\s+([a-zA-Z0-9])/) {
|
|
907
|
+
Regexp.last_match(1)
|
|
908
|
+
}
|
|
816
909
|
# Replace all other \commands
|
|
817
910
|
text.gsub(/\\([a-zA-Z]+)/) { |m|
|
|
818
911
|
SYMBOLS[Regexp.last_match(1)] || m
|
|
@@ -825,6 +918,8 @@ module RubyRich
|
|
|
825
918
|
.gsub(/\[\s+/, '[').gsub(/\s+\]/, ']')
|
|
826
919
|
.gsub(/\{\s+/, '{').gsub(/\s+\}/, '}')
|
|
827
920
|
.gsub(/\\s+/, ' ')
|
|
921
|
+
.gsub(/([·×÷]) +/, '\1')
|
|
922
|
+
.gsub(/ +([·×÷])/, '\1')
|
|
828
923
|
end
|
|
829
924
|
end
|
|
830
925
|
|
|
@@ -834,6 +929,23 @@ module RubyRich
|
|
|
834
929
|
module MermaidRenderer
|
|
835
930
|
BAR_MAX = 32
|
|
836
931
|
|
|
932
|
+
LEAF_BIN = "leaf"
|
|
933
|
+
|
|
934
|
+
def self.leaf_available?
|
|
935
|
+
@leaf_available ||= system("which #{LEAF_BIN} > /dev/null 2>&1")
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
def self.render_via_leaf(source, width)
|
|
939
|
+
return nil unless leaf_available?
|
|
940
|
+
IO.popen([LEAF_BIN, "--inline", "plain:#{width}"], "r+", err: "/dev/null") do |io|
|
|
941
|
+
io.write(source)
|
|
942
|
+
io.close_write
|
|
943
|
+
io.read.strip
|
|
944
|
+
end
|
|
945
|
+
rescue
|
|
946
|
+
nil
|
|
947
|
+
end
|
|
948
|
+
|
|
837
949
|
def self.render(source, width = 80)
|
|
838
950
|
trimmed = source.strip
|
|
839
951
|
return "" if trimmed.empty?
|
|
@@ -842,6 +954,10 @@ module RubyRich
|
|
|
842
954
|
case type
|
|
843
955
|
when :pie
|
|
844
956
|
render_pie(trimmed, width)
|
|
957
|
+
when :flowchart, :sequence, :class, :gantt, :state, :generic
|
|
958
|
+
# Prefer leaf for high-quality ASCII-art rendering
|
|
959
|
+
result = render_via_leaf("```mermaid\n#{trimmed}\n```\n", width)
|
|
960
|
+
result && !result.empty? ? result : render_fallback(trimmed, type, width)
|
|
845
961
|
else
|
|
846
962
|
render_fallback(trimmed, type, width)
|
|
847
963
|
end
|
|
@@ -917,6 +1033,148 @@ module RubyRich
|
|
|
917
1033
|
].join("\n")
|
|
918
1034
|
end
|
|
919
1035
|
|
|
1036
|
+
# Flowchart / graph → edge-list rendering with node labels.
|
|
1037
|
+
def self.render_flowchart(source, width)
|
|
1038
|
+
lines = source.lines.map(&:chomp)
|
|
1039
|
+
# Build node registry: id => label
|
|
1040
|
+
nodes = {}
|
|
1041
|
+
edges = []
|
|
1042
|
+
|
|
1043
|
+
lines.each do |line|
|
|
1044
|
+
stripped = line.strip
|
|
1045
|
+
next if stripped.empty?
|
|
1046
|
+
next if stripped.downcase.start_with?("flowchart", "graph")
|
|
1047
|
+
|
|
1048
|
+
# Parse edge: src ---|label|---> tgt
|
|
1049
|
+
m = stripped.match(
|
|
1050
|
+
/\A(.+?)\s*(-+>|==+>|-\.+>|=+>)\s*(\|(.*?)\|)?\s*(.+)\z/
|
|
1051
|
+
)
|
|
1052
|
+
if m
|
|
1053
|
+
src_raw = m[1].strip
|
|
1054
|
+
tgt_raw = m[5].strip
|
|
1055
|
+
arrow = m[2]
|
|
1056
|
+
label = m[4]&.strip
|
|
1057
|
+
|
|
1058
|
+
src_id, src_lbl = parse_node(src_raw)
|
|
1059
|
+
tgt_id, tgt_lbl = parse_node(tgt_raw)
|
|
1060
|
+
|
|
1061
|
+
# Only store shaped labels — don't let bare IDs overwrite them
|
|
1062
|
+
nodes[src_id] = src_lbl if src_lbl && src_raw =~ /[\[\(\{]/
|
|
1063
|
+
nodes[tgt_id] = tgt_lbl if tgt_lbl && tgt_raw =~ /[\[\(\{]/
|
|
1064
|
+
|
|
1065
|
+
edges << {
|
|
1066
|
+
src: src_id, src_label: src_lbl || src_id,
|
|
1067
|
+
tgt: tgt_id, tgt_label: tgt_lbl || tgt_id,
|
|
1068
|
+
edge_label: label
|
|
1069
|
+
}
|
|
1070
|
+
next
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# Standalone node definition: id[text] / id{text} / id(text)
|
|
1074
|
+
nm = stripped.match(/\A([A-Za-z0-9_]+)\s*[\[\(\{].+[\]\)\}]\z/)
|
|
1075
|
+
if nm
|
|
1076
|
+
nid, nlbl = parse_node(stripped)
|
|
1077
|
+
nodes[nid] = nlbl if nlbl
|
|
1078
|
+
end
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
return "[Mermaid flowchart: no edges found]" if edges.empty?
|
|
1082
|
+
|
|
1083
|
+
out = +""
|
|
1084
|
+
edges.each do |e|
|
|
1085
|
+
src = nodes[e[:src]] || e[:src_label]
|
|
1086
|
+
tgt = nodes[e[:tgt]] || e[:tgt_label]
|
|
1087
|
+
lbl = e[:edge_label] ? " ─#{e[:edge_label]}─▶ " : " ──▶ "
|
|
1088
|
+
out << "#{src}#{lbl}#{tgt}\n"
|
|
1089
|
+
end
|
|
1090
|
+
out.strip
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
# Extract [id, label] from a node token like "A[开始]" or "B{是否通过?}"
|
|
1094
|
+
def self.parse_node(raw)
|
|
1095
|
+
raw = raw.strip
|
|
1096
|
+
# Square brackets
|
|
1097
|
+
if raw =~ /\A([A-Za-z0-9_]+)\s*\[(.+)\]\z/
|
|
1098
|
+
[$1, $2]
|
|
1099
|
+
# Curly braces (diamond)
|
|
1100
|
+
elsif raw =~ /\A([A-Za-z0-9_]+)\s*\{(.+)\}\z/
|
|
1101
|
+
[$1, $2]
|
|
1102
|
+
# Round parens
|
|
1103
|
+
elsif raw =~ /\A([A-Za-z0-9_]+)\s*\((.+)\)\z/
|
|
1104
|
+
[$1, $2]
|
|
1105
|
+
# Just an id
|
|
1106
|
+
elsif raw =~ /\A([A-Za-z0-9_]+)\z/
|
|
1107
|
+
[$1, $1]
|
|
1108
|
+
else
|
|
1109
|
+
[raw, raw]
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
# Sequence diagram → participant-message listing.
|
|
1114
|
+
def self.render_sequence(source, width)
|
|
1115
|
+
lines = source.lines.map(&:chomp)
|
|
1116
|
+
participants = []
|
|
1117
|
+
messages = []
|
|
1118
|
+
|
|
1119
|
+
lines.each do |line|
|
|
1120
|
+
stripped = line.strip
|
|
1121
|
+
next if stripped.empty? || stripped.downcase.start_with?("sequencediagram")
|
|
1122
|
+
|
|
1123
|
+
# participant / actor definition
|
|
1124
|
+
if stripped =~ /\A(?:participant|actor)\s+(.+)\z/i
|
|
1125
|
+
participants << $1.strip
|
|
1126
|
+
next
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
# Note
|
|
1130
|
+
if stripped =~ /\ANote\s+(?:over\s+)?(.+?):\s*(.+)\z/i
|
|
1131
|
+
messages << { type: :note, target: $1.strip, text: $2.strip }
|
|
1132
|
+
next
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
# Message: A->>B: text / A-->>B: text / A-)B: text
|
|
1136
|
+
if stripped =~ /\A(.+?)\s*(-+>>?|-->>|-\)|-[xX])\s*(.+?)\s*:\s*(.+)\z/
|
|
1137
|
+
src = $1.strip
|
|
1138
|
+
arrow_type = $2.strip
|
|
1139
|
+
tgt = $3.strip
|
|
1140
|
+
text = $4.strip
|
|
1141
|
+
participants |= [src, tgt] unless participants.include?(src) && participants.include?(tgt)
|
|
1142
|
+
dashed = arrow_type.start_with?("--")
|
|
1143
|
+
messages << { type: :msg, src: src, tgt: tgt, text: text, dashed: dashed }
|
|
1144
|
+
end
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
return "[Mermaid sequence: no messages found]" if messages.empty?
|
|
1148
|
+
|
|
1149
|
+
max_participant = participants.map(&:length).max
|
|
1150
|
+
max_participant = 8 if max_participant < 8
|
|
1151
|
+
|
|
1152
|
+
out = +""
|
|
1153
|
+
participants.each do |p|
|
|
1154
|
+
out << sprintf("%-#{max_participant + 4}s", "[#{p}]")
|
|
1155
|
+
end
|
|
1156
|
+
out << "\n#{'─' * ((max_participant + 4) * participants.size)}\n"
|
|
1157
|
+
|
|
1158
|
+
messages.each do |m|
|
|
1159
|
+
case m[:type]
|
|
1160
|
+
when :note
|
|
1161
|
+
out << " 📝 #{m[:target]}: #{m[:text]}\n"
|
|
1162
|
+
when :msg
|
|
1163
|
+
src_idx = participants.index(m[:src]) || 0
|
|
1164
|
+
tgt_idx = participants.index(m[:tgt]) || participants.size - 1
|
|
1165
|
+
rightward = src_idx <= tgt_idx
|
|
1166
|
+
if m[:dashed]
|
|
1167
|
+
arrow = rightward ? "╌╌▶" : "◀╌╌"
|
|
1168
|
+
else
|
|
1169
|
+
arrow = rightward ? "──▶" : "◀──"
|
|
1170
|
+
end
|
|
1171
|
+
out << sprintf(" %-#{max_participant}s #{arrow} %s: %s\n",
|
|
1172
|
+
m[:src], m[:tgt], m[:text])
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
out.strip
|
|
1176
|
+
end
|
|
1177
|
+
|
|
920
1178
|
# Proxy theme colour access (same instance as TerminalConverter).
|
|
921
1179
|
def self.tc(key)
|
|
922
1180
|
color, bright = MarkdownTheme[key]
|
|
@@ -947,14 +1205,18 @@ module RubyRich
|
|
|
947
1205
|
VERTICAL_THRESHOLD = 5
|
|
948
1206
|
|
|
949
1207
|
def self.extract(markdown_text)
|
|
950
|
-
|
|
1208
|
+
# Strip leading blank lines so that a heredoc like <<~'MD'\n\n---\n
|
|
1209
|
+
# is still recognised as having frontmatter.
|
|
1210
|
+
stripped = markdown_text.lstrip
|
|
1211
|
+
return [markdown_text, nil, false] unless stripped.start_with?("---")
|
|
951
1212
|
|
|
952
|
-
rest =
|
|
953
|
-
offset =
|
|
1213
|
+
rest = stripped[3..]
|
|
1214
|
+
offset = 3
|
|
954
1215
|
rest.each_line do |line|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1216
|
+
trimmed = line.strip
|
|
1217
|
+
if trimmed == "---" || trimmed == "..."
|
|
1218
|
+
fm_block = stripped[3...offset]
|
|
1219
|
+
content = stripped[(offset + line.length)..] || ""
|
|
958
1220
|
pairs = parse_pairs(fm_block)
|
|
959
1221
|
return [markdown_text, nil, false] if pairs.empty?
|
|
960
1222
|
vertical = pairs.length >= VERTICAL_THRESHOLD
|
|
@@ -980,8 +1242,9 @@ module RubyRich
|
|
|
980
1242
|
raw_value = trimmed[(colon_pos + 1)..].strip
|
|
981
1243
|
i += 1 and next if key.empty?
|
|
982
1244
|
|
|
983
|
-
if ["
|
|
984
|
-
# Multiline value
|
|
1245
|
+
if [">-", ">", "|", "|-"].include?(raw_value)
|
|
1246
|
+
# Multiline value with explicit indicator
|
|
1247
|
+
i += 1
|
|
985
1248
|
parts = []
|
|
986
1249
|
while i < lines.length && lines[i].start_with?(' ', "\t")
|
|
987
1250
|
part = lines[i].strip
|
|
@@ -990,7 +1253,8 @@ module RubyRich
|
|
|
990
1253
|
end
|
|
991
1254
|
pairs << [key, parts.join(" ")]
|
|
992
1255
|
elsif raw_value.empty?
|
|
993
|
-
#
|
|
1256
|
+
# Empty value: could be a list or an implicit multiline string.
|
|
1257
|
+
i += 1
|
|
994
1258
|
items = []
|
|
995
1259
|
while i < lines.length && lines[i].start_with?(' ', "\t")
|
|
996
1260
|
item = lines[i].strip
|
data/lib/ruby_rich/table.rb
CHANGED
|
@@ -127,7 +127,7 @@ module RubyRich
|
|
|
127
127
|
|
|
128
128
|
row_content = row.map.with_index do |cell, i|
|
|
129
129
|
rendered = cell.render.sub(/\e\[0m\z/, '')
|
|
130
|
-
content = bold ? rendered :
|
|
130
|
+
content = bold ? "\e[1m#{rendered}" : rendered
|
|
131
131
|
aligned_content = align_cell(content, column_widths[i]).sub(/\e\[0m\z/, '')
|
|
132
132
|
" #{aligned_content} "
|
|
133
133
|
end.join(border_chars[:vertical])
|
|
@@ -214,7 +214,7 @@ module RubyRich
|
|
|
214
214
|
def render_row(row, column_widths, bold: false)
|
|
215
215
|
row.map.with_index do |cell, i|
|
|
216
216
|
rendered = cell.render.sub(/\e\[0m\z/, '')
|
|
217
|
-
content = bold ? rendered :
|
|
217
|
+
content = bold ? "\e[1m#{rendered}" : rendered
|
|
218
218
|
align_cell(content, column_widths[i]).sub(/\e\[0m\z/, '')
|
|
219
219
|
end.join(" | ").prepend("| ").concat(" |\e[0m")
|
|
220
220
|
end
|
data/lib/ruby_rich/version.rb
CHANGED