When using logging macros with Julia, I noticed that the output is framed, i.e., it uses certain unicode characters as prefix to create a one-sided frame, like so:
julia> warning = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Donec in erat ut felis molestie tincidunt vel quis sem.
Aenean id justo cursus, luctus justo at, dignissim ligula.
Praesent porttitor ante in egestas commodo.
Vestibulum ac nibh convallis, gravida leo sed, blandit turpis.
Morbi ut nisi ut ex pretium viverra at at lorem.
Nunc porta justo vel bibendum feugiat.
Curabitur semper tellus ut nunc porttitor rutrum.
"""
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nDonec in erat ut felis molestie tincidunt vel quis sem.\nAenean id justo cursus, luctus justo at, dignissim ligula.\nPraesent porttitor ante in egestas commodo.\nVestibulum ac nibh convallis, gravida leo sed, blandit turpis.\nMorbi ut nisi ut ex pretium viverra at at lorem.\nNunc porta justo vel bibendum feugiat.\nCurabitur semper tellus ut nunc porttitor rutrum.\n"
julia> @warn warning
┌ Warning: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
│ Donec in erat ut felis molestie tincidunt vel quis sem.
│ Aenean id justo cursus, luctus justo at, dignissim ligula.
│ Praesent porttitor ante in egestas commodo.
│ Vestibulum ac nibh convallis, gravida leo sed, blandit turpis.
│ Morbi ut nisi ut ex pretium viverra at at lorem.
│ Nunc porta justo vel bibendum feugiat.
│ Curabitur semper tellus ut nunc porttitor rutrum.
└ @ Main REPL[18]:1
julia>
Notice that the @warn output is all within a frame to mark its context. I would like to use this syntax elsewhere in my code. Is it possible to get just the framed output without calling one of the logging macros?
In other words, it is possible to add a prefix to every println() function output without creating a custom function?
The logging output code is here and is somewhat specialized for logging, but it would be pretty easy to write something similar.
For example:
function printframed(io::IO, args...; label=nothing)
lines = split(string(args...), '\n')
iob = IOBuffer()
if isempty(lines)
println(iob, '┌')
else
println(iob, "┌ ", lines[1])
for i = 2:length(lines)
println(iob, "│ ", lines[i])
end
end
if !isnothing(label)
println(iob, "└ ", label)
end
print(io, String(take!(iob)))
end
printframed(args...; label=nothing) = printframed(stdout, args...; label=label)
gives
julia> printframed("The quick\nbrown fox\njumped over the lazy dog.", label="@ example")
┌ The quick
│ brown fox
│ jumped over the lazy dog.
└ @ example
Sorry, I believe I wasn’t clear with my question.
What I really meant was to achieve that framed output (or the middle lines of the framed output) but with mutiple println calls.
Basically something like set_printprefix("│ "), so that all consequent printed lines have a prefix.
I do not know how to achieve this in a language like Julia without OOP (i could use classes and methods in Python).
So, to be clear, I want a way to make it so that the default println (or a custom printprefixed) function prints with a prefix without explicitly providing it. Not a just single function which I call once.
julia> function prefixprinter(prefix)
function wrappedprint(s)
print(prefix)
println(s)
end
end
prefixprinter (generic function with 1 method)
julia> const greet = prefixprinter("hello: ")
(::var"#wrappedprint#4"{String}) (generic function with 1 method)
julia> greet("world")
hello: world
julia> greet("everyone")
hello: everyone
And, I have found a solution, which is to use closures:
Here is the code:
function get_printinfo(prefix::String="", prefix_arr::Vector=[""])::Function
if prefix != ""
push!(prefix_arr, prefix)
end
function printinfo(s::String=""; prefix::String="", pop::Bool=false)::Function
if pop
pop!(prefix_arr)
end
if s != ""
fullprefix = join(prefix_arr, "")
print(fullprefix)
println(s)
end
return get_printinfo(prefix, prefix_arr)
end
return printinfo
end
This function returns a closure function that, when called, prints the string with the given prefix (implicitly passed).
The new function can also be used to recursively add more prefixes on top or pop the last one.
Note that when the prefix and pop arguments are used, they actually modify the existing function itself, and also return itself. prefix argument takes effect in next call, while pop argument takes effect in current one.
An example on how to print repl-like output with this (repl> lines are actually printed output not actual an repl prompt):
julia> replprint=get_printinfo("repl> ")
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> replprint("@time somefunc(arg1, arg2)")
repl> @time somefunc(arg1, arg2)
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> timereplprint=replprint(prefix="@time ") # `replprint` itself is also modified
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> timereplprint("somefunc(arg1, arg2)")
repl> @time somefunc(arg1, arg2)
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> # new prefix takes effect in next call, but pop takes effect in current one
julia> whichreplprint=timereplprint("somefunc(arg1, arg2)", prefix="@which ", pop=true)
repl> somefunc(arg1, arg2)
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> timereplprint("somefunc(arg1, arg2)") # `timereplprint` itself was also modified
repl> @which somefunc(arg1, arg2)
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
julia> whichreplprint("somefunc(arg1, arg2)")
repl> @which somefunc(arg1, arg2)
(::var"#printinfo#13"{var"#printinfo#10#14"{Vector{String}}}) (generic function with 2 methods)
Thanks for your help! I wrote a very similar function but with the ability to recursively add more prefixes on top, and pop the last one.
Actually, I intend to use this while writing tests, so I want to use a single convenient printinfo function at every point in the tests, so that I can dynamically change its prefix. Also the context of @testset and the pop functionality helps to revert back to a previous prefix.
All I have to do is manually add “┌” and "└ " characters to the beginning of the first and last print statements and define printinfo, the middle lines are prefixed appropriately.
Example usage:
using Test
# `get_printinfo()` definition here...
# define `printinfo` once
printinfo = get_printinfo()
@testset "Set 1" begin
printinfo("┌ Testing `Set 1`...") # not updated
@test true
printinfo("└ Done.")
end
@testset "Set 2" begin
printinfo("┌ Testing `Set 2`..."; prefix="│ ") # updated `prefix`
@testset "Set 2 A" begin
printinfo("┌ Testing subset A..."; prefix="│ ") # updated `prefix` again
printinfo("check type")
@test isa(true, Bool)
printinfo("└ Done."; pop=true)
end
@testset "Set 2 B" begin
printinfo("┌ Testing subset B..."; prefix="│ ") # updated `prefix` again
@test true
printinfo("└ Done."; pop=true)
end
printinfo("└ Done.", pop=true)
end
(You could also define a callable object, which is useful if you want to do more than just call it. Or an alternative IO stream type or similar to pass to println so that you can call println(prefixobject, ...) and it will call your own println method.)
I thought about using a callable object but then realized that my function provides pretty much the same functionality.
However, I think defining an alternative IO stream type could be useful, but I do not know how to do that or where to learn more about it. I did not find anything in the IO and Networking section of the Julia docs.