中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
專為 Gopher 準備的 Markdown 教程

  Markdown大家應(yīng)該不會隨便找到,即使是現(xiàn)在的一篇關(guān)于常用的任何網(wǎng)絡(luò)社區(qū)都可以參考。Markdown現(xiàn)在不是支持Markdown的。語法教程,而是希望通過 Go 語言 Markdown 解析庫的來更深入地了解、掌握 Markdown。

  如果你對 Markdown 語法很少了解,這里有一份極簡的教程:

  studygolang/markdown。

  我們會在一些 Markdown 解析庫的中看到類似這樣的描述:通用標記,支持 GFM 擴展等。

  這是 Markdown 標準或規(guī)范。CommonMark 官網(wǎng)提到:

  由于沒有明顯的文檔規(guī)范,Markdown 解析渲染存在很大差異。因此用戶會經(jīng)常發(fā)現(xiàn)一個系統(tǒng)(例如 GitHub)上渲染正常的在另一個系統(tǒng)上渲染不正常。更正是由于 Markdown 中不存在“語法錯誤”,所以無法立即發(fā)現(xiàn)問題。

  在 Markdown 處理上“模糊的”是不可取的。所以 CommonMark 規(guī)范的目的是去掉二義性,統(tǒng)一明確處理 Markdown 的解析規(guī)則。

  該規(guī)范的主要參與者包括:

  David Greenspan, 來自 MeteorVicent Marti, 來自 GitHub Neil Williams, 來自 RedditBenjamin Dumke-von der Ehe, Stack ExchangeJeff Atwood, 前 Stack Exchange 聯(lián)合創(chuàng)始人,Discourse 創(chuàng)始人

  因為之前的作者,我們可以歸為這個大都該從眾所創(chuàng)作的規(guī)范,需要一個相同的期待的Markdown。

  規(guī)范的具體內(nèi)容,有興趣的可以看官網(wǎng)上的規(guī)范定義文檔,其中的一些點,在后面的文中介紹 Go 語言的 Markdown 解析器時會介紹。

  CommonMark(標準降價),為什么還會有GFM?

  GFM 因為的名字的縮寫,也就是一般 GitHub 的 Markdown,和標準 GitHub 的 Markdown,和標準的 Markdown 有一些區(qū)別,一些功能,所以 Markdown 主要是增加了一些功能,所以 Markdown 是典型的增加了一些擴展。GFM 是類似的,它基于 GitHub 的標準 Markdown 存在類似的分支有很多,但流行,GFM幾乎成為了最有影響力的一個,而且各種解析器、編輯器都支持GFM。

  對于人類而言,關(guān)于CommonMark和GFM,知道這么多就了。

  在 GitHub 上,發(fā)現(xiàn) Go GitHub 上的解析器多了一個,如何選擇? Markdown 庫,看看 CommonMark 有無推薦。

  在常用語言的常用標記列表中,其中一個常用的語言分類了 Go 語言雖然是一個同樣的實現(xiàn)標記。

  因為 Hugo 從 0.60.0 開始,使用 Markdown 解析器默認 yuin/goldmark,使用前是黑色星期五。因此我們本文主要通過學(xué)習(xí) goldmark 解析器來深入學(xué)習(xí) Markdown。

  goldmark 是一個用符合標準的 Go 編寫的 Markdown 良好的標記。易于擴展,擴展標記。它解析最新的規(guī)范。0.029 通用標記。

  該庫希望滿足以下需求:

  擴展擴展與量級評分表的語言(restr)比較,Markdown 表達方面的表達方式,我們可以對 Markdown 的表達方式進行標記。 CommonMark 且復(fù)雜多處完全實現(xiàn)。Markdown 有方言。GitHub Flavored Markdown 被廣泛使用基于 CommonMark,有效地并提出了 CommonMark 是否是規(guī)范的問題。結(jié)構(gòu)良好基于 AST;保留節(jié)點的源位置。純 Go 語言實現(xiàn)

  基于此,該庫具有以下一些特性:

  符合標準。goldmark 完全符合最新的 CommonMark 規(guī)范??蓴U展。你是否要在 Markdown 中添加 @username 提到誰的語法?你可以輕松地在 goldmark 中實現(xiàn)。你可以添加 AST 節(jié)點,用于節(jié)點級元素的解析器,內(nèi)聯(lián)級的器,用于段落的通道,整個 AST 結(jié)構(gòu)的以及實現(xiàn)渲染器等。定義。健。goldmark 擴展通過模糊測試工具 go-fu 已進行了測試。編集。goldmark 擴展了相當常見的,刪除,任務(wù)列表和列表。只依賴標準庫。

  本文使用的 Go 版本是 1.14.x,依賴管理使用 Go Module。

  首先安裝金標:

  $ 去獲取 github.com/yuin/goldmark

  為了方便演示,我們使用

  https://studygolang.com/markdown 上的教程作為原始降價內(nèi)容(部分內(nèi)容是 studygolang 獨有的)。

  演示1

  代碼如下:

  函數(shù)演示1(){

  source, err := ioutil.ReadFile("guide.md")

  if err != nil {

  panic(err)

  }

  f, err := os.Create("guide1.html")

  if err != nil {

  panic(err)

  }

  err = goldmark.Convert(source, f)

  if err != nil {

  panic(err)

  }

  }guide.md 存放 https://studygolang.com/markdown 上的原始 markdown 內(nèi)容,略有增減;通過 goldmark 的 Convert 函數(shù)將 markdown 轉(zhuǎn)為 html,沒有使用任何選項;

  在項目目錄生成 guide1.html,在瀏覽器中打開,發(fā)現(xiàn)存在以下問題:

  不支持自動鏈接,例如 https://studygolang.com 不會被識別為鏈接;不支持刪除線,即 ~~;不支持表格;不支持任務(wù)列表;不支持語法高亮;不支持 @;不支持表情;Demo2

  demo1 中問題 1-4 是 GFM 支持的語法,因此我們可以通過 goldmark 內(nèi)置的 GFM 擴展實現(xiàn)。

  func demo2() {

  source, err := ioutil.ReadFile("guide.md")

  if err != nil {

  panic(err)

  }

  f, err := os.Create("guide2.html")

  if err != nil {

  panic(err)

  }

  // 自定義解析器

  markdown := goldmark.New(

  // 支持 GFM

  goldmark.WithExtensions(extension.GFM),

  )

  err = markdown.Convert(source, f)

  if err != nil {

  panic(err)

  }

  }驗證上面問題 1-4 發(fā)現(xiàn)都解決了;這里關(guān)于自動鏈接,有一個小問題:中文標點符號問題。自動鏈接:https://studygolang.com 這里的冒號是中文的,因此識別不出來。所以,如果使用自動鏈接,注意鏈接前后加上英文空格;或單獨鏈接總是使用 <> 包裹,這是 CommonMark 支持的語法;Dome3

  語法高亮問題,雖然 goldmark 庫沒有內(nèi)置該擴展,但該庫作者在另外一個包實現(xiàn)了,這就是 goldmark-highlighting。

  func demo3() {

  source, err := ioutil.ReadFile("guide.md")

  if err != nil {

  panic(err)

  }

  f, err := os.Create("guide3.html")

  if err != nil {

  panic(err)

  }

  // 自定義解析器

  markdown := goldmark.New(

  // 支持 GFM

  goldmark.WithExtensions(extension.GFM),

  // 語法高亮

  goldmark.WithExtensions(

  highlighting.NewHighlighting(

  highlighting.WithStyle("monokai"),

  highlighting.WithFormatOptions(

  html.WithLineNumbers(true),

  ),

  ),

  ),

  )

  err = markdown.Convert(source, f)

  if err != nil {

  panic(err)

  }

  }

  這是語法高亮后的效果:

  

  語法高亮使用的是一個 Go 第三方庫:alecthomas/chroma

  上節(jié)遺留的問題先不處理,因為涉及到擴展 goldmark,我們先學(xué)習(xí)下 goldmark 的設(shè)計。

  該庫的主包(github.com/yuin/goldmark)公開的內(nèi)容不多,一共 3 個類型和 3 個函數(shù)。

  

  Markdown 是一個接口

  type Markdown interface {

  // Convert interprets a UTF-8 bytes source in Markdown and write rendered

  // contents to a writer w.

  Convert(source []byte, writer io.Writer, opts ...parser.ParseOption) error

  // Parser returns a Parser that will be used for conversion.

  Parser() parser.Parser

  // SetParser sets a Parser to this object.

  SetParser(parser.Parser)

  // Parser returns a Renderer that will be used for conversion.

  Renderer() renderer.Renderer

  // SetRenderer sets a Renderer to this object.

  SetRenderer(renderer.Renderer)

  }解析器:Parser渲染器:Renderer解析 markdown 文本并將渲染的結(jié)果寫入 io.Writer 中

  從這個接口的定義可以看到,我們可以定義自己的 Parser 或 Renderer 來改變 goldmark 的工作方式。一般我們不需要這么做,只需要使用默認的實現(xiàn)即可,這也就是 DefaultParser() 和 DefaultRenderer() 兩個函數(shù)的作用。另外,在轉(zhuǎn)換時(Convert)支持指定解析選項。

  然而在該包中,我們并沒有看到 Markdown 接口的實現(xiàn)類型。很顯然,實現(xiàn)類型沒有導(dǎo)出。這體現(xiàn)了“依賴抽象而不依賴具體”的設(shè)計原則。

  看看獲取 Markdown 接口實例的 New 函數(shù):

  func New(options ...Option) Markdown

  該函數(shù)接收一個不定參數(shù):Option,用于控制 Markdown 的行為。這里引出了 Go 中常見的一個設(shè)計模式。

  Go 不是完全的面向?qū)ο笳Z言。當類型中有較多成員,且可以通過外部控制時,根據(jù)封裝的原則,一般不建議將這些字段導(dǎo)出(公開),但這樣一來構(gòu)造函數(shù)就需要能接收很多參數(shù);亦或是希望通過其他方式擴展。在 Go 中有兩種較常見的設(shè)計方法。

  1)通過另外一個結(jié)構(gòu)體來控制

  這里以 BigCache 這個包為例,該包中的 Config 結(jié)構(gòu)體就是這種設(shè)計。這么做有什么好處?

  func NewBigCache(config Config) (*BigCache, error)

  一方面控制了 BigCache 類型的行為,避免實例化后可以隨意更改,起到了封裝的作用。另一方面,讓構(gòu)造函數(shù)更簡潔,只需要接收一個 Config 即可(注意最好使用 Config 值類型,而不是指針)。而且可以通過提供一些 Config 的默認值來做到更易用,比如 bigcache.DefaultConfig() 函數(shù)就是這樣的例子。

  2)通過一個函數(shù)類型來控制

  前面提到,goldmark 只導(dǎo)出了 Markdown 接口,并沒有導(dǎo)出該接口的具體實現(xiàn)類型。那想要控制具體實現(xiàn)類型的行為怎么辦呢?這就是 Option 這個函數(shù)類型的作用:接收一個 markdown 類型指針(注意這里不是 Markdown 接口,而是實現(xiàn)了該接口的具體類型)

  type Option func(*markdown)

  然后構(gòu)造函數(shù)中接收一個 Option 類型的不定參數(shù),來控制 Markdown 的行為。

  func New(options ...Option) Markdown

  為了方便使用 ,一般包會提供若干獲得 Option 實例的方法。goldmark 包提供了 5 個返回 Option 的函數(shù):

  // 增加擴展

  func WithExtensions(ext ...Extender) Option

  // 允許你覆蓋默認的 Parser

  func WithParser(p parser.Parser) Option

  // 為 Parser 修改配置選項

  func WithParserOptions(opts ...parser.Option) Option

  // 允許你覆蓋默認的 Render

  func WithRenderer(r renderer.Renderer) Option

  // 為 Renderer 修改配置選項

  func WithRendererOptions(opts ...renderer.Option) Option

  在 Demo3 中使用了 WithExtensions() 為解析器增加擴展,該函數(shù)接收一個 Extender 類型的不定參數(shù)。

  Extender 也是一個接口

  該接口用于擴展 Markdown,因此方法接收一個 Markdown 參數(shù):

  type Extender interface {

  // Extend extends the Markdown.

  Extend(Markdown)

  }

  由此可見,所謂的 goldmark 擴展,就是一個實現(xiàn)了 Extender 接口的類型。

  Convert 函數(shù)

  這是一個為了方便使用的函數(shù),在 Go 語言標準庫中有大量這樣的設(shè)計。

  針對包的主要類型提供一個默認實例,包級便利函數(shù)直接調(diào)用該默認實例的相應(yīng)方法

  比如 log 包中的 std 是一個默認的 Logger 實例、net/http 包中的 DefaultClient 是一個默認的 Client 實例。

  而 Convert 函數(shù)的實現(xiàn)如下:

  func Convert(source []byte, w io.Writer, opts ...parser.ParseOption) error {

  return defaultMarkdown.Convert(source, w, opts...)

  }

  其中 defaultMarkdown 就是一個 Markdown 的實例:

  var defaultMarkdown = New()

  因此最終調(diào)用的還是 Markdown 接口的 Convert 方法。

  New 函數(shù)

  最后看看該包 New 函數(shù)實現(xiàn):

  // New returns a new Markdown with given options.

  func New(options ...Option) Markdown {

  md := &markdown{

  parser: DefaultParser(),

  renderer: DefaultRenderer(),

  extensions: []Extender{},

  }

  for _, opt := range options {

  opt(md)

  }

  for _, e := range md.extensions {

  e.Extend(md)

  }

  return md

  }markdown 實現(xiàn)了 Markdown 接口;parser 和 renderer 使用 goldmark 包默認的;遍歷執(zhí)行 Options;如果 Option 是 Extender,則 md.extensions 會賦上值:func WithExtensions(ext ...Extender) Option {

  return func(m *markdown) {

  m.extensions=append(m.extensions, ext...)

  }

  }最后遍歷執(zhí)行 Extender 的 Extend 方法;

  從 goldmark 的設(shè)計和源碼看出,它大量使用接口,包括 Parser 和 Renderer 都是接口,這使得它具有極強的可擴展性。接下來我們會嘗試自己實現(xiàn)一個 goldmark 擴展。

  上面我們遺留了兩個文憑問題沒有處理,即支持 @ 和 :+1: 這種形式的表情?,F(xiàn)在我們通過實現(xiàn)自己的擴展來解決這兩個問題。

  goldmark 的文檔有一些擴展開發(fā)的內(nèi)容,提供了一個 goldmark 處理 markdown 的概要圖:

  |

  V

  +-------- parser.Parser ---------------------------

  | 1. Parse block elements into AST

  | 1. If a parsed block is a paragraph, apply

  | ast.ParagraphTransformer

  | 2. Traverse AST and parse blocks.

  | 1. Process delimiters(emphasis) at the end of

  | block parsing

  | 3. Apply parser.ASTTransformers to AST

  |

  V

  |

  V

  +------- renderer.Renderer ------------------------

  | 1. Traverse AST and apply renderer.NodeRenderer

  | corespond to the node type

  |

  V

  你可能看著有點暈。整體上擴展開發(fā)有 4 個工作:

  定義一個 AST(抽象語法樹)節(jié)點(結(jié)構(gòu)體),該節(jié)點需要嵌入一個 ast.BaseBlock 或 ast.BaseInline;定義一個解析器(Parser),實現(xiàn) parser.BlockParser 或parser.InlineParser;定義一個渲染器(Renderer),實現(xiàn) renderer.NodeRenderer;定義一個 goldmark 擴展,實現(xiàn) goldmark.Extender;

  其中 Block 和 Inline 是什么意思?學(xué)習(xí)過 Web 前端的應(yīng)該了解。Markdown 和 HTML 類似,將內(nèi)容元素分為塊級元素(Block)和行級元素(Inline):(塊級元素優(yōu)先級高于行級元素)

  塊級元素:塊引用(>)、列表項和列表(列表只能包含列表項)、分隔線、標題、代碼塊、某些 HTML 塊、段落等行級元素:內(nèi)聯(lián)代碼(code)、強調(diào)、加粗、鏈接、圖片、某些 HTML 標簽、文本等

  根據(jù)以上的內(nèi)容,要自己實現(xiàn)一個擴展還是有難度的。好在 goldmark 中內(nèi)置了一些擴展,可以作為參考。

  CommonMark 是不支持刪除線(strikethrough)的,而 GFM 支持。因此 goldmark 通過 strikethrough 這個擴展來實現(xiàn)對 GFM 刪除線的支持。根據(jù)擴展實現(xiàn)的步驟來學(xué)習(xí)下 strikethrough 擴展的源碼。

  1)AST 節(jié)點結(jié)構(gòu)體:Strikethrough

  // A Strikethrough struct represents a strikethrough of GFM text.

  type Strikethrough struct {

  gast.BaseInline

  }

  // Dump implements Node.Dump.

  func (n *Strikethrough) Dump(source []byte, level int) {

  gast.DumpHelper(n, source, level, nil, nil)

  }

  // KindStrikethrough is a NodeKind of the Strikethrough node.

  var KindStrikethrough = gast.NewNodeKind("Strikethrough")

  // Kind implements Node.Kind.

  func (n *Strikethrough) Kind() gast.NodeKind {

  return KindStrikethrough

  }

  // NewStrikethrough returns a new Strikethrough node.

  func NewStrikethrough() *Strikethrough {

  return &Strikethrough{}

  }內(nèi)嵌了一個 gast.BaseInline(gast 是 ast 導(dǎo)入時重命名的),表明是一個行級元素;根據(jù) goldmark 的設(shè)計,AST 節(jié)點結(jié)構(gòu)體需要實現(xiàn) ast.Node 接口,然而該接口擁有很多方法,為了方便實現(xiàn),該庫使用了內(nèi)嵌的方式來處理;ast.NewNodeKind() 構(gòu)造函數(shù)得到一個 NodeKind;

  看一下類圖:

  

  因為 Go 不是完全面向?qū)ο笳Z言,因此這里的類圖是不嚴謹?shù)模?/p>

  BaseNode 并沒有實現(xiàn) Node 接口,因為它沒有實現(xiàn) Kind 和 Dump 方法,這也是因為 Go 沒有抽象類的概念;BaseBlock 和 BaseInline 的區(qū)別在于實現(xiàn)的幾個方法,BaseLine 里面好幾個方法直接 panic,不需要實現(xiàn);因為 BaseNode、BaseBlock 和 BaseInline 實際沒有實現(xiàn) Node 接口,因此它們不能直接當做 Node 使用,一定程度上模擬了抽象類;Strikethrough 擴展通過內(nèi)嵌 BaseInline “繼承”了相應(yīng)的實現(xiàn)方法,同時提供 Kind 和 Dump 的實現(xiàn),達到完整實現(xiàn)了 Node 接口的目的,因此是一個 Node;

  2)Strikethrough 解析器

  var defaultStrikethroughDelimiterProcessor = &strikethroughDelimiterProcessor{}

  type strikethroughParser struct {}

  var defaultStrikethroughParser = &strikethroughParser{}

  // NewStrikethroughParser return a new InlineParser that parses

  // strikethrough expressions.

  func NewStrikethroughParser() parser.InlineParser {

  return defaultStrikethroughParser

  }

  func (s *strikethroughParser) Trigger() []byte {

  return []byte{'~'}

  }

  func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {

  before := block.PrecendingCharacter()

  line, segment := block.PeekLine()

  node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor)

  if node == nil {

  return nil

  }

  node.Segment = segment.WithStop(segment.Start + node.OriginalLength)

  block.Advance(node.OriginalLength)

  pc.PushDelimiter(node)

  return node

  }InlineParser 接口只有兩個方法:Trigger 和 Parse;Trigger 表示遇到什么字符觸發(fā)該節(jié)點解析;Parse 是擴展的一個關(guān)鍵點,不同的擴展實現(xiàn)方式不同。這里有兩點提一下:block.PeekLine() 獲取當前行,這個很有用,解析基本都能用到;block.Advance() 表示移動內(nèi)部指針,可以理解為文件讀取過程中的 Seek;

  3)Strikethrough 渲染器

  // StrikethroughHTMLRenderer is a renderer.NodeRenderer implementation that

  // renders Strikethrough nodes.

  type StrikethroughHTMLRenderer struct {

  html.Config

  }

  // NewStrikethroughHTMLRenderer returns a new StrikethroughHTMLRenderer.

  func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {

  r := &StrikethroughHTMLRenderer{

  Config: html.NewConfig(),

  }

  for _, opt := range opts {

  opt.SetHTMLOption(&r.Config)

  }

  return r

  }

  // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.

  func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {

  reg.Register(ast.KindStrikethrough, r.renderStrikethrough)

  }

  // StrikethroughAttributeFilter defines attribute names which dd elements can have.

  var StrikethroughAttributeFilter = html.GlobalAttributeFilter

  func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {

  if entering {

  if n.Attributes() != nil {

  _, _ = w.WriteString("<del")< p="">

  html.RenderAttributes(w, n, StrikethroughAttributeFilter)

  _ = w.WriteByte('>')

  } else {

  _, _ = w.WriteString("")

  }

  } else {

  _, _ = w.WriteString("")

  }

  return gast.WalkContinue, nil

  }renderer.NodeRenderer 接口只有一個方法:RegisterFuncs,用于注冊節(jié)點類型對應(yīng)的渲染函數(shù);渲染函數(shù)是一個回調(diào)函數(shù),簽名為:func(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error),用于渲染某一個節(jié)點(Node);渲染為 HTML,刪除線通過加 標簽來實現(xiàn);

  4)定義一個擴展類型

  擴展需要實現(xiàn) goldmark.Extender 接口。

  type strikethrough struct {}

  // Strikethrough is an extension that allow you to use strikethrough expression like '~~text~~' .

  var Strikethrough = &strikethrough{}

  func (e *strikethrough) Extend(m goldmark.Markdown) {

  m.Parser().AddOptions(parser.WithInlineParsers(

  util.Prioritized(NewStrikethroughParser(), 500),

  ))

  m.Renderer().AddOptions(renderer.WithNodeRenderers(

  util.Prioritized(NewStrikethroughHTMLRenderer(), 500),

  ))

  }這是一個固定的形式:結(jié)構(gòu)體不導(dǎo)出,導(dǎo)出一個實例。外部使用時直接用導(dǎo)出的實例;Extend 方法的實現(xiàn),設(shè)置解析選項和渲染選項,固定的寫法;

  至此內(nèi)置擴展 Strikethrough 的源碼分析完成了,建議先別繼續(xù)往下看,自己動手實現(xiàn)一個 @ 的擴展!

  現(xiàn)在我們實現(xiàn)一個 @ 的擴展,命名為:Mention。

  1)AST 節(jié)點結(jié)構(gòu)體:MentionNode

  // KindMention is a NodeKind of the Mention node.

  var KindMention = gast.NewNodeKind("Mention")

  type MentionNode struct {

  gast.BaseInline

  Who string

  }

  // NewStrikethrough returns a new Mention node.

  func NewMentionNode(username string) *MentionNode {

  return &MentionNode{

  BaseInline: gast.BaseInline{},

  Who: username,

  }

  }

  // Dump implements Node.Dump.

  func (n *MentionNode) Dump(source []byte, level int) {

  gast.DumpHelper(n, source, level, nil, nil)

  }

  // Kind implements Node.Kind.

  func (n *MentionNode) Kind() gast.NodeKind {

  return KindMention

  }這里的關(guān)鍵是結(jié)構(gòu)體字段 Who,用于保存 @ 誰;其他和 Strikethrough 沒啥區(qū)別;

  2)Mention 解析器

  var usernameRegexp = regexp.MustCompile(`@([^\s@]{4,20})`)

  type mentionParser struct{}

  func NewMentionParser() parser.InlineParser {

  return mentionParser{}

  }

  func (m mentionParser) Trigger() []byte {

  return []byte{'@'}

  }

  func (m mentionParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {

  before := block.PrecendingCharacter()

  if !unicode.IsSpace(before) {

  return nil

  }

  line, _ := block.PeekLine()

  matched := usernameRegexp.FindSubmatch(line)

  if len(matched) < 2 {

  return nil

  }

  block.Advance(len(matched[0]))

  node := NewMentionNode(string(matched[1]))

  return node

  }這里假定 @ 用戶名長度在 4-20 字符;Trigger 在遇到 @ 時觸發(fā);要求 @ 之前必須是空格;通過正則找到當前行(line)中的目標字符串(用戶名),matched 中第一個元素包含 @,第二個元素不包含 @;因為解析出了用戶名,用戶名這個字符串就不需要再解析了,因此通過 block.Advance 移動指針;構(gòu)建一個 MentionNode 并返回;

  3)Mention 渲染器

  type mentionHTMLRenderer struct{}

  func NewMentionHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {

  return mentionHTMLRenderer{}

  }

  func (m mentionHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {

  reg.Register(KindMention, m.renderMention)

  }

  func (m mentionHTMLRenderer) renderMention(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {

  if entering {

  mn := n.(*MentionNode)

  w.WriteString(`@`)

  w.WriteString(mn.Who)

  } else {

  w.WriteString("")

  }

  return gast.WalkContinue, nil

  }entering 為 true,表示進入該節(jié)點,因此構(gòu)造鏈接寫入 w 中;由于在 Parse 時移動了指針,因此這里將 Who 寫入 w;else 中閉合 a 標簽;

  4)定義一個擴展類型

  type mention struct{}

  var Mention = mention{}

  func (m mention) Extend(markdown goldmark.Markdown) {

  markdown.Parser().AddOptions(parser.WithInlineParsers(

  util.Prioritized(NewMentionParser(), 500),

  ))

  markdown.Renderer().AddOptions(renderer.WithNodeRenderers(

  util.Prioritized(NewMentionHTMLRenderer(), 500),

  ))

  }

  跟上面 Strikethrough 沒有太多區(qū)別。

  使用 Mention

  使用和其他擴展沒有區(qū)別:

  markdown := goldmark.New(

  // 支持 GFM

  goldmark.WithExtensions(extension.GFM),

  // 語法高亮

  goldmark.WithExtensions(

  highlighting.NewHighlighting(

  highlighting.WithStyle("monokai"),

  highlighting.WithFormatOptions(

  html.WithLineNumbers(true),

  ),

  ),

  ),

  // 支持 @

  goldmark.WithExtensions(mention.Mention),

  )

  這樣 @ 就能正常解析了。

  這個就不講解了,有興趣的可以動手實現(xiàn)下,有問題可以交流。

  另外介紹兩個 goldmark “擴展”。這里擴展用引號,是因為它們并非按照上面要求的方式實現(xiàn)的,因此不算是真正意義上 goldmark 說的擴展,只能說是增加了 goldmark 的功能。(這也證明了 goldmark 的可擴展性很強)

  這是一個很實用的功能,特別對于長文來說。有一個擴展實現(xiàn)了該功能,即

  https://github.com/mdigger/goldmark-toc。

  它的實現(xiàn)方式是擴展 goldmark.Markdown 接口,也可以說是類似 goldmark.Extender 的方式。

  // Markdown extends initialied goldmark.Markdown and return converter function.

  func Markdown(m goldmark.Markdown) ConverterFunc {

  m.Parser().AddOptions(

  parser.WithAttribute(),

  parser.WithAutoHeadingID(),

  )

  return func(source []byte, writer io.Writer) (toc []Header, err error) {

  doc := m.Parser().Parse(text.NewReader(source), WithIDs())

  toc = Headers(doc, source)

  if writer != nil {

  err = m.Renderer().Render(writer, source, doc)

  }

  return toc, err

  }

  }

  接著 Demo4,為其增加 TOC 輸出,相關(guān)代碼改為:

  convertFunc := toc.Markdown(markdown)

  headers, err := convertFunc(source, f)

  for _, header := range headers {

  fmt.Printf("%+v

  ", header)

  }

  運行輸出如下信息:

  {Level:2 Text:語法指導(dǎo) ID:yu-fa-zhi-dao}

  {Level:3 Text:普通內(nèi)容 ID:pu-tong-nei-rong}

  {Level:3 Text:提及用戶 ID:ti-ji-yong-hu}

  {Level:3 Text:表情符號 Emoji ID:biao-qing-fu-hao-emoji}

  {Level:4 Text:一些表情例子 ID:xie-biao-qing-li-zi}

  {Level:3 Text:大標題 - Heading 3 ID:da-biao-ti-heading-3}

  {Level:4 Text:Heading 4 ID:heading-4}

  {Level:5 Text:Heading 5 ID:heading-5}

  {Level:6 Text:Heading 6 ID:heading-6}

  {Level:3 Text:圖片 ID:tu-pian}

  {Level:3 Text:代碼塊 ID:dai-ma-kuai}

  {Level:4 Text:普通 ID:pu-tong}

  {Level:4 Text:語法高亮支持 ID:yu-fa-gao-liang-zhi-chi}

  {Level:5 Text:演示 Go 代碼高亮 ID:yan-shi-go-dai-ma-gao-liang}

  {Level:5 Text:演示 JSON 代碼高亮 ID:yan-shi-json-dai-ma-gao-liang}

  {Level:3 Text:有序、無序列表 ID:you-xu-wu-xu-lie-biao}

  {Level:4 Text:無序列表 ID:wu-xu-lie-biao}

  {Level:4 Text:有序列表 ID:you-xu-lie-biao}

  {Level:3 Text:表格 ID:biao-ge}

  {Level:3 Text:段落 ID:duan-luo}

  {Level:3 Text:任務(wù)列表 ID:ren-wu-lie-biao}

  通過這些數(shù)據(jù)可以很方便的實現(xiàn) TOC。

  這個擴展來自同一個作者:

  https://github.com/mdigger/goldmark-stats,用于進行文本統(tǒng)計,這個擴展統(tǒng)計的是渲染后的,因此比直接統(tǒng)計原始 markdown 文本要更準確。為我們 Dem4 增加統(tǒng)計功能,在最后加上如下代碼:

  doc := goldmark.DefaultParser().Parse(text.NewReader(source))

  info := stats.New(doc, source)

  fmt.Printf("words: %d, unique: %d, chars: %d, reading time: %v

  ",

  info.Words, info.Unique(), info.Chars, info.Duration(400))

  輸出:

  words: 194, unique: 141, chars: 1263, reading time: 3m9s作者是俄國人,這個 words 是針對西方國家的,用空格分隔的詞,并非中文的字,因此中文忽略該字段;中文主要看 chars 字段,表示多少個字;閱讀時間,我們按照一分鐘 400 個字算,需要 3m9s;

  Markdown 已經(jīng)成為程序員必須掌握的技能,如果你還不會,抓緊學(xué)習(xí)下。而這篇文章帶領(lǐng)你從 Go 的角度對 Markdown 有了更深的了解。

  希望通過 goldmark 這個庫,除了學(xué)習(xí)到 Markdown 的知識,更能學(xué)習(xí)到一些 Go 語言優(yōu)秀庫的設(shè)計思想。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
eKuiper 源碼解讀:從一條 SQL 到流處理任務(wù)的旅程
自制 Python小工具 將markdown文件轉(zhuǎn)換成Html文件
Go:如何優(yōu)雅地實現(xiàn)并發(fā)編排任務(wù)
OpenGL 與 Go 教程(一)Hello, OpenGL
Apache Thrift  Go Tutorial
ZendFrame中render, _forward , _redirect 的區(qū)別
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服