如何在 Markdown Julia 中使用固定宽度的内插字符串格式?

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宏在反引号解析之前进行字符串插值?

Vin*_* Yu 1

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