jer*_*ich 5 string markdown string-interpolation julia
假设我有一个变量filename = "/home/jimmy/logger.log"
。我想要一些像这样的降价文档:
md"""
The output is logged at `$(filename)`
"""
Run Code Online (Sandbox Code Playgroud)
我知道有两种解决方法:
第一的:
Markdown.parse("The output is logged at `" * filename * "`")
Run Code Online (Sandbox Code Playgroud)
第二:
str = "The output is logged at `$(filename)`"
md"$str"
Run Code Online (Sandbox Code Playgroud)
有没有办法直接告诉markdown宏在反引号解析之前进行字符串插值?
This was puzzling to figure out, but I think I've got it. No, there is no way to get the @md_str
macro to do string interpolation first, but I have a workaround below.
It took me a while to realize that when an @f_str
or @f_cmd
macro is called using the f"arg"
or f`arg`
syntax, it receives the raw string raw"arg"
as the argument rather than the expression:("arg")
that a macro would otherwise get:
julia> macro showarg_str(x) show(x) end;
julia> showarg"text$var"
"text\$var"
julia> @showarg_str("text$var")
:("text$(var)")
Run Code Online (Sandbox Code Playgroud)
This behavior is defined in the julia-paser.scm
parser.
But code like md"$expr"
shows the evaluated expr
and not the raw string raw"$expr"
! What's going on? It turns out $expr
gets parsed into an expression during the parsing into Markdown via custom code in the Markdown module; this is largely implemented in interp.jl
. According to the documentation this is intentional and done so that the Markdown trees store expressions instead of the strings from evaluating these expressions. In theory, this facilitates advanced custom features. In practice, this gave me a metaphorical headache.
Now, using Markdown.parse
would appear to work as you desire. Because as a regular function and not a macro, Markdown.parse
receives the interpolated string, not the raw string or the expression:
julia> Markdown.parse("The output is logged at `$(filename)`") |> show
The output is logged at `/home/jimmy/logger.log`
Run Code Online (Sandbox Code Playgroud)
However, because $
processing occurs again in Markdown.parse
, there are undesirable/unintuitive behaviors like this:
julia> Markdown.parse(raw"$filename") |> show
:filename
Run Code Online (Sandbox Code Playgroud)
The following is not documented, but the best way I see to change this behavior is to define and customize a new Markdown flavor in Markdown.flavors
, then call Markdown.parse
with this flavor as an argument. In the flavor's config, we just need to stop blockinterp
and interp
from getting called during parsing. This can be done like so:
let
config = deepcopy(Markdown.flavors[:julia])
filter!(!=(Markdown.blockinterp), config.regular)
filter!(!=(Markdown.interp), get(config.inner, '$', []))
Markdown.flavors[:julia_nointerp] = config
end
Run Code Online (Sandbox Code Playgroud)
To allow the Markdown module to be loaded after rather than before this configuring, we can put this into a convenience function instead:
function md(str, flavor=:julia_nointerp)
@isdefined(Markdown) || error("the Markdown module must be loaded. Try `using Markdown`.")
if flavor == :julia_nointerp && !haskey(Markdown.flavors, :julia_nointerp)
config = deepcopy(Markdown.flavors[:julia])
filter!(!=(Markdown.blockinterp), config.regular)
filter!(!=(Markdown.interp), get(config.inner, '$', []))
Markdown.flavors[:julia_nointerp] = config
end
return Markdown.parse(str; flavor=flavor)
end
Run Code Online (Sandbox Code Playgroud)
现在您可以调用md("markdown code")
使用标准字符串插值来获取 Markdown 解析,而不是执行特殊$
解析md"markdown code"
:
julia> filename = "/home/jimmy/logger.log";
julia> md("The output is logged at `$(filename)`") |> show
The output is logged at `/home/jimmy/logger.log`
julia> md("The output is logged at `$(filename)`") |> typeof
Markdown.MD
julia> md("The output is logged at `$(filename)`").content
1-element Array{Any,1}:
Markdown.Paragraph(Any["The output is logged at ", Markdown.Code("", "/home/jimmy/logger.log")])
Run Code Online (Sandbox Code Playgroud)
PS:我不建议变成md
宏或尝试修改宏,@md_str
因为:(1)一个函数就足够了,(2)使用函数意味着用户可以确定在传递参数之前将发生标准字符串插值到md
。