Skip to content

Commit a7ee26e

Browse files
authored
Merge pull request #23 from c42f/cjf/markdown
Markdown formatting for AbstractString log messages
2 parents b87cf48 + e9bb625 commit a7ee26e

File tree

3 files changed

+135
-55
lines changed

3 files changed

+135
-55
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.1.0"
66
[deps]
77
LeftChildRightSiblingTrees = "1d6d02ad-be62-4b6b-8a6d-2f90e265016e"
88
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
9+
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
910
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1011
ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c"
1112
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

src/TerminalLogger.jl

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Markdown
2+
13
"""
24
TerminalLogger(stream=stderr, min_level=$ProgressLevel; meta_formatter=default_metafmt,
35
show_limited=true, right_justify=0)
@@ -104,6 +106,46 @@ function termlength(str)
104106
return N
105107
end
106108

109+
function format_message(message, prefix_width, io_context)
110+
formatted = sprint(show, MIME"text/plain"(), message, context=io_context)
111+
msglines = split(chomp(formatted), '\n')
112+
if length(msglines) > 1
113+
# For multi-line messages it's possible that the message was carefully
114+
# formatted with vertical alignemnt. Therefore we play it safe by
115+
# prepending a blank line.
116+
pushfirst!(msglines, SubString(""))
117+
end
118+
msglines
119+
end
120+
121+
function format_message(message::AbstractString, prefix_width, io_context)
122+
# For strings, use Markdown to do the formatting. The markdown renderer
123+
# isn't very composable with other text formatting so this is quite hacky.
124+
message = Markdown.parse(message)
125+
prepend_prefix = !isempty(message.content) &&
126+
message.content[1] isa Markdown.Paragraph
127+
if prepend_prefix
128+
# Hack: We prepend the prefix here to allow the markdown renderer to be
129+
# aware of the indenting which will result from prepending the prefix.
130+
# Without this we will get many issues of broken vertical alignment.
131+
# Avoid collisions: using placeholder from unicode private use area
132+
placeholder = '\uF8FF'^prefix_width
133+
pushfirst!(message.content[1].content, placeholder)
134+
end
135+
formatted = sprint(show, MIME"text/plain"(), message, context=io_context)
136+
msglines = split(chomp(formatted), '\n')
137+
# Hack': strip left margin which can't be configured in Markdown
138+
# terminal rendering.
139+
msglines = [startswith(s, " ") ? s[3:end] : s for s in msglines]
140+
if prepend_prefix
141+
# Hack'': Now remove the prefix from the rendered markdown.
142+
msglines[1] = replace(msglines[1], placeholder=>""; count=1)
143+
elseif !isempty(msglines[1])
144+
pushfirst!(msglines, SubString(""))
145+
end
146+
msglines
147+
end
148+
107149
function findbar(bartree, id)
108150
if !(bartree isa AbstractArray)
109151
bartree.data.id === id && return bartree
@@ -208,10 +250,15 @@ function handle_message(logger::TerminalLogger, level, message, _module, group,
208250

209251
substr(s) = SubString(s, 1, length(s)) # julia 0.6 compat
210252

211-
# Generate a text representation of the message and all key value pairs,
212-
# split into lines.
213-
msglines = [(0,l) for l in split(chomp(string(message)), '\n')]
253+
color,prefix,suffix = logger.meta_formatter(level, _module, group, id, filepath, line)
254+
255+
# Generate a text representation of the message
214256
dsize = displaysize(logger.stream)
257+
msglines = format_message(message, textwidth(prefix),
258+
IOContext(logger.stream, :displaysize=>(dsize[1],dsize[2]-2)))
259+
# Add indentation level
260+
msglines = [(0,l) for l in msglines]
261+
# Generate a text representation of all key value pairs, split into lines.
215262
if !isempty(kwargs)
216263
valbuf = IOBuffer()
217264
rows_per_value = max(1, dsize[1]÷(length(kwargs)+1))
@@ -234,7 +281,6 @@ function handle_message(logger::TerminalLogger, level, message, _module, group,
234281

235282
# Format lines as text with appropriate indentation and with a box
236283
# decoration on the left.
237-
color,prefix,suffix = logger.meta_formatter(level, _module, group, id, filepath, line)
238284
minsuffixpad = 2
239285
buf = IOBuffer()
240286
iob = IOContext(buf, logger.stream)

test/TerminalLogger.jl

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,55 @@
1-
import TerminalLoggers.default_metafmt
1+
using TerminalLoggers: default_metafmt, format_message
22

33
@noinline func1() = backtrace()
44

5+
function dummy_metafmt(level, _module, group, id, file, line)
6+
:cyan,"PREFIX","SUFFIX"
7+
end
8+
9+
# Log formatting
10+
function genmsgs(events; level=Info, _module=Main,
11+
file="some/path.jl", line=101, color=false, width=75,
12+
meta_formatter=dummy_metafmt, show_limited=true,
13+
right_justify=0)
14+
buf = IOBuffer()
15+
io = IOContext(buf, :displaysize=>(30,width), :color=>color)
16+
logger = TerminalLogger(io, Debug,
17+
meta_formatter=meta_formatter,
18+
show_limited=show_limited,
19+
right_justify=right_justify)
20+
prev_have_color = Base.have_color
21+
return map(events) do (message, kws)
22+
kws = Dict(pairs(kws))
23+
id = pop!(kws, :_id, :an_id)
24+
# Avoid markdown formatting while testing layouting. Don't wrap
25+
# progress messages though; ProgressLogging.asprogress() doesn't
26+
# like that.
27+
is_progress = message isa Progress || haskey(kws, :progress)
28+
handle_message(logger, level, message, _module, :a_group, id,
29+
file, line; kws...)
30+
String(take!(buf))
31+
end
32+
end
33+
34+
function genmsg(message; kwargs...)
35+
kws = Dict(kwargs)
36+
logconfig = Dict(
37+
k => pop!(kws, k)
38+
for k in [
39+
:level,
40+
:_module,
41+
:file,
42+
:line,
43+
:color,
44+
:width,
45+
:meta_formatter,
46+
:show_limited,
47+
:right_justify,
48+
] if haskey(kws, k)
49+
)
50+
return genmsgs([(message, kws)]; logconfig...)[1]
51+
end
52+
553
@testset "TerminalLogger" begin
654
# First pass log limiting
755
@test min_enabled_level(TerminalLogger(devnull, Debug)) == Debug
@@ -35,59 +83,15 @@ import TerminalLoggers.default_metafmt
3583
(:yellow, "Warning:", "@ Main b.jl:2-5")
3684
end
3785

38-
function dummy_metafmt(level, _module, group, id, file, line)
39-
:cyan,"PREFIX","SUFFIX"
40-
end
41-
42-
# Log formatting
43-
function genmsgs(events; level=Info, _module=Main,
44-
file="some/path.jl", line=101, color=false, width=75,
45-
meta_formatter=dummy_metafmt, show_limited=true,
46-
right_justify=0)
47-
buf = IOBuffer()
48-
io = IOContext(buf, :displaysize=>(30,width), :color=>color)
49-
logger = TerminalLogger(io, Debug,
50-
meta_formatter=meta_formatter,
51-
show_limited=show_limited,
52-
right_justify=right_justify)
53-
prev_have_color = Base.have_color
54-
return map(events) do (message, kws)
55-
kws = Dict(pairs(kws))
56-
id = pop!(kws, :_id, :an_id)
57-
handle_message(logger, level, message, _module, :a_group, id,
58-
file, line; kws...)
59-
String(take!(buf))
60-
end
61-
end
62-
function genmsg(message; kwargs...)
63-
kws = Dict(kwargs)
64-
logconfig = Dict(
65-
k => pop!(kws, k)
66-
for k in [
67-
:level,
68-
:_module,
69-
:file,
70-
:line,
71-
:color,
72-
:width,
73-
:meta_formatter,
74-
:show_limited,
75-
:right_justify,
76-
] if haskey(kws, k)
77-
)
78-
return genmsgs([(message, kws)]; logconfig...)[1]
79-
end
80-
8186
# Basic tests for the default setup
8287
@test genmsg("msg", level=Info, meta_formatter=default_metafmt) ==
8388
"""
8489
[ Info: msg
8590
"""
86-
@test genmsg("line1\nline2", level=Warn, _module=Base,
91+
@test genmsg("msg", level=Warn, _module=Base,
8792
file="other.jl", line=42, meta_formatter=default_metafmt) ==
8893
"""
89-
┌ Warning: line1
90-
│ line2
94+
┌ Warning: msg
9195
└ @ Base other.jl:42
9296
"""
9397
# Full metadata formatting
@@ -127,10 +131,10 @@ import TerminalLoggers.default_metafmt
127131
"""
128132
[ PREFIX xxx SUFFIX
129133
"""
130-
@test genmsg("xxx\nxxx", width=20, right_justify=200) ==
134+
@test genmsg("xxxxxxxx xxxxxxxx", width=20, right_justify=200) ==
131135
"""
132-
┌ PREFIX xxx
133-
xxx SUFFIX
136+
┌ PREFIX xxxxxxxx
137+
xxxxxxxx SUFFIX
134138
"""
135139
# When adding the suffix would overflow the display width, add it on
136140
# the next line:
@@ -239,9 +243,10 @@ import TerminalLoggers.default_metafmt
239243
end
240244

241245
# Basic colorization test.
242-
@test genmsg("line1\nline2", color=true) ==
246+
@test genmsg("line1\n\nline2", color=true) ==
243247
"""
244248
\e[36m\e[1m┌ \e[22m\e[39m\e[36m\e[1mPREFIX \e[22m\e[39mline1
249+
\e[36m\e[1m│ \e[22m\e[39m
245250
\e[36m\e[1m│ \e[22m\e[39mline2
246251
\e[36m\e[1m└ \e[22m\e[39m\e[90mSUFFIX\e[39m
247252
"""
@@ -260,6 +265,34 @@ import TerminalLoggers.default_metafmt
260265
@test genmsgs([("", (progress = 0.1,)), ("", (progress = "done",))], width = 60)[end]
261266
r"Progress: 100%\|█+\| Time: .*"
262267

268+
@testset "Message formatting" begin
269+
io_ctx = IOContext(IOBuffer(), :displaysize=>(20,20))
270+
271+
# Short paragraph on a single line
272+
@test format_message("Hi `code`", 6, io_ctx) ==
273+
["Hi code"]
274+
275+
# Longer paragraphs wrap around the prefix
276+
@test format_message("x x x x x x x x x x x x x x x x x x x x x", 6, io_ctx) ==
277+
["x x x x x"
278+
"x x x x x x x x"
279+
"x x x x x x x x"]
280+
281+
# Markdown block elements get their own lines
282+
@test format_message("# Hi", 6, io_ctx) ==
283+
["",
284+
"Hi",
285+
"≡≡≡≡"]
286+
287+
# For non-strings a blank line is added so that any formatting for
288+
# vertical alignment isn't broken
289+
@test format_message([1 2; 3 4], 6, io_ctx) ==
290+
["",
291+
"2×2 Array{Int64,2}:",
292+
" 1 2",
293+
" 3 4"]
294+
end
295+
263296
@testset "Independent progress bars" begin
264297
msgs = genmsgs([
265298
("Bar1", (progress = 0.0, _id = 1111)), # 1

0 commit comments

Comments
 (0)