Markdown.jl 使用總結

Table of Contents

Julia 的標準庫 Markdown.jl 可靈活高效地解析 markdown, 從 Julia 0.4 版本開始, Markdown.jl 便成爲 Julia 的基礎庫.

當我們從 REPL 進入幫助模式(按 ? 鍵), 或者在 Jupyter Notebook 中使用 ? 查看函數的幫助信息時, 豐富多彩的幫助文字背後即是 Markdown.jl 在發揮作用.


從 docstring 開始

查看 + 的說明文檔時, 你會發現字體、顏色、樣式並不是唯一的,這是如何實現的呢

在這裏插入圖片描述

在這裏插入圖片描述

要弄清楚這一點, 我們需要引入幾個 macro 和 MD 這個函數

using Markdown
using Markdown: @doc, @doc_str, MD

首先將 + 的 docstr 保存下來, 使用 @doc 保存到 doc 變量, 可以發現, doc 的類型是 MD, 這種類型就是 markdown 在 Julia 中的被解析的對象, 且 MD 有兩部分, 一部分是 meta, 反映函數的元信息, 另一部分是 content 是一個類型爲 MD 的一維 array, 用來具體說明函數的用法. 也就是說, docstring 可以由各種 MD 實例組成.

doc = @doc +; # type: MD
doc.meta
Dict{Any,Any} with 3 entries:
  :typesig => Union{}
  :binding => Base.:+
  :results => Base.Docs.DocStr[DocStr(svec("    +(x, y...)\n\nAddition operator…
doc.content
2-element Array{Any,1}:
 ```
+(x, y...)
```

Addition operator. `x+y+z+...` calls this function with all arguments, i.e. `+(x, y, z, ...)`.

# Examples

```jldoctest
julia> 1 + 20 + 4
25

julia> +(1, 20, 4)
25
```

那麼你會想到, 如果我們有了 MD 各種實例, 如何重新組合成完整的 docstring 呢?

MD 函數可以幫助你實現. 它接受兩種類型方法

  • 傳入類型爲 MD 的一維 array 時, 可組合起來形成一個新的 MD

  • 傳入類型爲 Dict 類型時, 可添加到 MD 實例的 meta 部分, 作爲輔助說明

根據這個思路, 你可以寫出 + docstr

docPseudo = MD(Vector{MD}(
        [doc.content[1], doc.content[2]]
        )
);
docPseudo.meta = Dict(:typesig => Union{}, :binding => Base.:+, :results => docPseudo);
docPseudo # 結果和 + 的文檔是一樣的, 限於篇幅就不放上來了

利用 @doc 進行註釋

從上面的討論中, 我們知道 文檔字符串在 Julia 中就是 MD 對象, 那麼如何將字符串"變成"函數的文檔呢?

答案: 使用 @doc

利用命令 @doc @doc 或者 ?@doc, 你會發現 @doc 有兩個功能:

  1. 獲取某個對象(函數、宏、變量)的文檔
  2. 給某個對象(函數、宏、變量)添加文檔

你或許已經知道,在 Julia 中我們在函數體前用 """ 給函數做註釋:

"""
print `param` (take **Int** type as the argument)

# First Examples

```julia
julia> str = "Hello World\n"
julia> Print(str*str)
$(let str = "Hello World"
    str*str
end)
```
"""
function Print(param::String)
    println(param)
    end

但在 REPL 中, 以上方式會無法將字符串解析成文檔字符串(運行結果只是單純的一段字符串和一個函數體), 使用 md doc 字符串宏給函數做註釋時會報 ERROR: syntax 語法錯誤

這時應使用 @doc 宏和 -> 符號給函數(或者任何你想要註釋的對象)做註釋

@doc """
print `param` (take **Float64** type as the argument)

# Second Examples

```julia
julia> r = rand()
julia> Print(r)
$(let r=rand()
    r
end)
```
""" ->
function Print(param::Float64)
    println(param)
    end

或者

docstring = """
print `param` (take **String** type as the argument)

# Third Examples

```julia
julia> Print(1)
1
```
""";

@doc docstring Print(param::Int) = print(param)

查看 Print 的文檔:

?Print

print param (take Int type as the argument)

First Examples

julia> str = "Hello World
"
julia> Print(str*str)
Hello WorldHello World

print param (take Float64 type as the argument)

Second Examples

julia> r = rand()
julia> Print(r)
0.40218381068451414

print param (take String type as the argument)

Third Examples

julia> Print(1)
1

你會驚喜地發現, 隨着 method 的增加, docstring 也能夠依次增加, 並且如果需要覆蓋某種 method, 只需要重新使用 @doc 進行註釋

@doc "# Same Type as String" Print(param::String) = print(param);
?Print # 或者 @doc Print

Same Type as String


print param (take Float64 type as the argument)

Second Examples

…(後面的內容省略)

除了函數, Julia 還可以對常量、變量、數組或者其他類型的對象進行註釋

# 對 MD 對象進行註釋
mdObject = doc"mdObject"
@doc "this belongs to `md` object" mdObject
@doc mdObject
# 結果爲: this belongs to `md` object
# 對常量註釋
@doc "# Constant
**$(typeof(imag))** type

the square is $(imag*imag)" -> 
const imag = 1im

@doc imag

Constant

Complex{Int64} type

the square is -1 + 0im

從上面這個例子可以看到, Julia 的插值功能非常強大, Julia 能夠先將變量的值"計算"出來, 然後"鏈接"到 docstring. 然而 docstring 並非能夠動態地把值"鏈接"過來, 請看以下例子:

@doc "# Vector of Random Variables
param `n`: length of the vector
defalut `n` = $(length(num))
"  num = rand(3);
@doc num

Vector of Random Variables

param n: length of the vector

defalut n = 3

num= rand(1);
@doc num

Vector of Random Variables

param n: length of the vector

defalut n = 3

更改了 num 的值, 但是 docstring 依然是第一次賦值的結果, 在很多場景下這非常實用, 尤其是對常量註釋

但是我們可以遵循之前討論的思路, 用新的 MD 對象進行覆蓋

@doc "# Vector of Random Variables
param `n`: length of the vector

defalut `n` = $(length(num))
"  num = rand(100)

@doc num

Vector of Random Variables

param n: length of the vector

defalut n = 100


實現 MD 實例: 從字符串到 MD 對象

接下來, 你會想知道, 如何寫出 MD 實例, 有兩種方式:

  1. 對符合 markdown 語法的字符串,使用 mddoc 字符串宏 (string macro)、@doc_str 宏(macro) 、Markdown.parse()函數直接轉化成 MD 對象
  2. 對普通字符串,使用 Markdown.jl的相關函數: Header、 LaTeX、 Image、 Link、 Bold、 Italic、 Table、 Code、 Footnote、 Paragraph 等進行相應地組建 markdown 結構

用宏和函數解析 markdown 字符串

md_logo = md"""
# Julia Icon

![Julia Icon](https://raw.githubusercontent.com/JuliaLang/julia-logo-graphics/master/images/old-style/julia-logo-488-by-338.png)
""";


doc_str_logo = @doc_str """
# Julia Dragon Icon
![Julia Dragon Icon](https://discourse.juliacn.com/uploads/default/original/1X/6422ed319b31937c1e13c9dc7ab6b5166db5ba79.jpeg)
""";

str = """
# Julia Three-Balls Icon
![Julia Three-Balls Icon](https://raw.githubusercontent.com/JuliaLang/julia-logo-graphics/master/images/old-style/three-balls.png)
"""
parse_logo = Markdown.parse(str);

mdChunk = MD(Vector{MD}(
        [doc_str_logo, md_logo, parse_logo]
        )
)

Julia Dragon Icon

Julia Dragon Icon

Julia Icon

[外鏈圖片轉存失敗(img-17KGGDkr-1567694955361)(https://raw.githubusercontent.com/JuliaLang/julia-logo-graphics/master/images/old-style/julia-logo-488-by-338.png)]

Julia Three-Balls Icon

Julia Three-Balls Icon

在解析含有插值字符串時, 這三種方式略有不同, md 字符串宏會把不會對插值代碼求值, parse 函數會對插值求值, 而 @doc_str 宏無法解析含有插值的字符串

md"""
print `str` (only take **String** type as the argument)

# Examples

```julia
julia> Print("Hello World")
$(let str="Hello World"
   str
  end
)
```
"""

print str (only take String type as the argument)

Examples

julia> Print("Hello World")
$(let str="Hello World"
   str
  end
)

Markdown.parse("""
print `str` (only take **String** type as the argument)

# Examples

```julia
julia> Print("Hello World")
$(let str="Hello World"
   str
  end
)
```
""")

print str (only take String type as the argument)

Examples

julia> Print("Hello World")
Hello World

@doc_str """
print `str` (only take **String** type as the argument)

# Examples

```julia
julia> Print("Hello World")
$(let str="Hello World"
   str
  end
)
```
"""

MethodError: no method matching @doc_str(::LineNumberNode, ::Module, ::Expr)
Closest candidates are:
  @doc_str(::LineNumberNode, ::Module, !Matched::AbstractString, !Matched::Any...) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Markdown/src/Markdown.jl:56

函數式編程組建 markdown 結構

using Markdown
using Markdown: MD,  Header, Italic, Bold,  plain, Code, LaTeX, Paragraph, html

根據 markdown 語法和 HTML 結構, 我們知道 md 中的 # title 寫法其實是對應 html 的 <h1>title<\h1>
在 Julia 中, 用函數的方式把 h1 生成相應 MD 對象的過程是: 先使用 Header() 生成 Header 對象, 再用 MD() 函數接收一維數組, 把數組所有內容轉化成一個整體的 MD 對象:

h1 = Header("title") # type => Header{1}
md_header = MD(h1) # type => MD
md_header == md"# title" # => 結果: true

再看一個簡單的 md 語句 md1

 md1 = md"foo *italic foo* **bold foo** `code foo`"

foo italic foo bold foo code foo

如果我們需要實現 md1 的 markdown 結構, 用函數的形式, 需要寫成

md2 = MD(Paragraph(["foo ", Italic("italic foo"), " ", Bold("bold foo"), " ",  Code("code foo")]))
md1 == md2 # => 結果: true

同樣地, 我們可以"合併"不同類型的數據作爲一個整體的 MD 對象:

md3 = md"""inline $\LaTeX$ formula 

$\frac{1}{2}$

interline formula $$\frac{1}{2}$$
""";

LATEX = LaTeX("\\text{end with}\\, \\LaTeX \\cdots");
v = [Header("MD 實例"), md3, 
    Header("MD 實例轉化的普通字符串"), plain(md3),
    Header("LaTeX 公式"), LATEX]
md_v = MD(v);

map(x->typeof(x), vcat(v, md_v))
7-element Array{DataType,1}:
 Header{1}
 MD       
 Header{1}
 String   
 Header{1}
 LaTeX    
 MD       

@doc的第一個參數不僅支持字符串, 還支持 MD 對象:

o5 = .5;
@doc md_v o5;
@doc o5

MD 實例

inline LaTeX\LaTeX formula

12 \frac{1}{2}

interline formula 12\frac{1}{2}

MD 實例轉化的普通字符串

“inline $\LaTeX$ formula \n\n$$\n\frac{1}{2}\n$$\n\ninterline formula $\frac{1}{2}$\n”

LaTeX 公式

end with&ThinSpace;LaTeX \text{end with}\, \LaTeX \cdots


MD 對象轉化成 String

MD 對象可以轉化成以下三種字符串:

  • md 格式的字符串: plain() 函數
  • html 格式的字符串: html() 函數
  • tex 格式的字符串: latex() 函數
 md1 = md"foo *italic foo* **bold foo** `code foo`"

foo italic foo bold foo code foo

plain(md1)
"foo *italic foo* **bold foo** `code foo`\n"
html(md1)
"<p>foo <em>italic foo</em> <strong>bold foo</strong> <code>code foo</code></p>\n"
latex(md1)
"foo \\emph{italic foo} \\textbf{bold foo} \\texttt{code foo}\n\n"

實現文檔類型轉化: md 文件 轉化成 html 文件和 tex 文件

MD 對象不僅可以用於添加文檔說明, 還可以實現 markdown 與其他文檔類型相互轉化
比如我們有一個 markdown 文件 (爲方便說明, 我們從 github 上下載 Markdown.jl 的 md 文檔)

using HTTP

url = "https://raw.githubusercontent.com/JuliaStdlibs/Markdown.jl/master/docs/src/index.md"
r = HTTP.request("GET", url; verbose=1); 
# verbose 參數用於打印請求的客戶端和服務器之間信息流, 0 表示不顯示信息, 1 表示打印最簡單的交互信息, 2 表示打印 request 請求和 response 響應內容信息, 3 表示打印詳細的 Debug 信息

mdString = String(r.body); # type: str
open(f -> write(f, mdString), "index.md", "w"); # 保存成名爲 index.md 的本地文件
# 將本地 md 文件解析成 MD 對象
mdInstance = Markdown.parse_file(joinpath(pwd(), "index.md")); # => type: MD

你可以先把 MD 對象轉化成 String, 然後把 String 保存成文本文件:

mdHtml = html(mdInstance); # => type: str 
open(f -> write(f, mdHtml), "index.html", "w");
mdLatex = latex(mdInstance); # => type: str 
open(f -> write(f, mdLatex), "index.tex", "w");

而另外一種簡便的轉化方法是使用 html(io::IO, md::MD) latex(io::IO, md::MD) 方法, 直接進行 IO 操作 (這得益於 multiple dispatch)

open(f->Markdown.html(f, mdInstance), "readme.html", "w")
# 等價於
# io = open("readme.html", "w");
# Markdown.html(io, mdInstance);
# close(io)
# 或者使用 tohtml 函數
# open(f->Markdown.tohtml(f, mdInstance), "readme.html", "w")

在這裏插入圖片描述

open(f->Markdown.latex(f, mdInstance), "readme.tex", "w")

在這裏插入圖片描述

參考資料

  1. Markdoan.jl github地址: https://github.com/JuliaStdlibs/Markdown.jl
  2. Julia 官方文檔介紹: https://docs.julialang.org/en/latest/stdlib/Markdown/
  3. 國外博客 MonthOfJulia Day 36: Markdown: https://www.juliabloggers.com/monthofjulia-day-36-markdown/
  4. markdown 語法中文介紹: http://www.markdown.cn/
  5. markdown 語法 cheatsheet: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章