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
有兩個功能:
- 獲取某個對象(函數、宏、變量)的文檔
- 給某個對象(函數、宏、變量)添加文檔
你或許已經知道,在 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 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 實例, 有兩種方式:
- 對符合 markdown 語法的字符串,使用
md
和doc
字符串宏 (string macro)、@doc_str
宏(macro) 、Markdown.parse()
函數直接轉化成 MD 對象- 對普通字符串,使用
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 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
在解析含有插值字符串時, 這三種方式略有不同,
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 formula
interline formula
MD 實例轉化的普通字符串
“inline $\LaTeX$ formula \n\n$$\n\frac{1}{2}\n$$\n\ninterline formula $\frac{1}{2}$\n”
LaTeX 公式
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")
參考資料
- Markdoan.jl github地址: https://github.com/JuliaStdlibs/Markdown.jl
- Julia 官方文檔介紹: https://docs.julialang.org/en/latest/stdlib/Markdown/
- 國外博客 MonthOfJulia Day 36: Markdown: https://www.juliabloggers.com/monthofjulia-day-36-markdown/
- markdown 語法中文介紹: http://www.markdown.cn/
- markdown 語法 cheatsheet: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet