// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . package docutils import ( "bytes" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting/v2" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer/html" "os" "path/filepath" "regexp" "sync" ) type cacheItem struct { ModifiedAt int64 Data []byte } var cacheMap = map[string]*cacheItem{} // path => *cacheItem var cacheLocker = &sync.RWMutex{} func ReadMarkdownFile(path string, productName string, rootURL string) ([]byte, error) { stat, err := os.Stat(path) if err != nil { return nil, err } if stat.IsDir() { return nil, os.ErrNotExist } var modifiedAt = stat.ModTime().Unix() cacheLocker.RLock() item, ok := cacheMap[path] if ok && item.ModifiedAt == modifiedAt { cacheLocker.RUnlock() return item.Data, nil } cacheLocker.RUnlock() data, err := os.ReadFile(path) if err != nil { return nil, err } // keywords data = bytes.ReplaceAll(data, []byte("GoEdge"), []byte(productName)) data = bytes.ReplaceAll(data, []byte("GOEDGE"), []byte(productName)) data = bytes.ReplaceAll(data, []byte("http://goedge.cn"), []byte("http://example.com")) data = bytes.ReplaceAll(data, []byte("https://goedge.cn"), []byte("https://example.com")) var markdown = goldmark.New( goldmark.WithParserOptions( parser.WithAutoHeadingID(), // read note ), goldmark.WithExtensions(extension.Table, extension.Strikethrough, highlighting.NewHighlighting(highlighting.WithStyle("github"), highlighting.WithFormatOptions(chromahtml.TabWidth(4))), ), goldmark.WithRendererOptions(html.WithHardWraps()), ) var buf = &bytes.Buffer{} err = markdown.Convert(data, buf) if err != nil { return nil, err } data = buf.Bytes() // convert links { var reg = regexp.MustCompile(`(?U)`) data = reg.ReplaceAllFunc(data, func(link []byte) []byte { var pieces = reg.FindSubmatch(link) if len(pieces) > 1 { var newLink = append([]byte(rootURL), bytes.TrimSuffix(pieces[1], []byte(".md"))...) newLink = append(newLink, []byte(".html")...) newLink = []byte(filepath.Clean(string(newLink))) return bytes.ReplaceAll(link, pieces[1], newLink) } return link }) } // convert images { var reg = regexp.MustCompile(`(?U)]*>`) data = reg.ReplaceAllFunc(data, func(link []byte) []byte { var pieces = reg.FindSubmatch(link) if len(pieces) > 1 { var newLink = append([]byte(rootURL), pieces[1]...) newLink = []byte(filepath.Clean(string(newLink))) return []byte("" + string(bytes.ReplaceAll(link, pieces[1], newLink)) + "") } return link }) } // put into cache item = &cacheItem{ ModifiedAt: modifiedAt, Data: data, } cacheLocker.Lock() cacheMap[path] = item cacheLocker.Unlock() return data, nil }