脑图km文件转mkdown

package main

import (
    "encoding/json"
    "log"
    "os"
    "path"
    "strings"
)

var w *writer = &writer{builder: new(strings.Builder)}

func init() {
    log.SetFlags(log.Lshortfile | log.Ltime)
}

func main() {
    if err := scanDir(os.Args[1]); err != nil {
        log.Fatalln(err)
    }
}

func scanDir(d string) error {
    dir, err := os.ReadDir(d)
    if err != nil {
        return err
    }

    for _, entry := range dir {
        p := path.Join(d, entry.Name())
        if entry.IsDir() {
            if err := scanDir(p); err != nil {
                return err
            }
        } else {
            if p[len(p)-3:] != ".km" {
                continue
            }
            content, err := os.ReadFile(p)
            if err != nil {
                return err
            }

            if err := convertDoc(p, content); err != nil {
                return err
            }
        }
    }
    return nil
}

func convertDoc(p string, content []byte) error {
    tree := struct {
        Root node `json:"root"`
    }{}
    if err := json.Unmarshal(content, &tree); err != nil {
        return err
    }

    root := tree.Root
    w.writeContent("[TOC]")
    if err := walkWrite(1, &root); err != nil {
        return err
    }

    f, err := os.OpenFile(path.Join(path.Dir(p), strings.Replace(path.Base(p), ".km", ".md", 1)), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()

    _, err = f.WriteString(w.all())
    w.builder.Reset()
    return err
}

func walkWrite(deep int, n *node) error {
    w.writeHeadline(deep, n.Data.Title)
    if n.Data.Link != "" {
        if n.Data.LinkTitle != "" {
            w.writeLink(n.Data.LinkTitle, n.Data.Link)
        } else {
            w.writeLink("详见此文", n.Data.Link)
        }
    }
    if len(n.Data.Note) == 0 {
        w.writeBlankLine()
    } else {
        w.writeContent(n.Data.Note)
    }
    for _, c := range n.Children {
        if err := walkWrite(deep+1, c); err != nil {
            return err
        }
    }
    return nil
}

type writer struct {
    builder *strings.Builder
}

func (w *writer) writeHeadline(n int, headline string) error {
    if n > 7 {
        n = 7
    }
    w.builder.Grow(n + 1 + len(headline) + 2)
    for n > 0 {
        w.builder.WriteByte('#')
        n--
    }
    w.builder.WriteByte(' ')
    _, err := w.builder.WriteString(headline)
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
    return err
}

func (w *writer) writeBlankLine() {
    w.builder.Grow(2)
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
}

func (w *writer) writeLink(title, link string) {
    w.builder.Grow(8 + len(title) + len(link))
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
    w.builder.WriteByte('[')
    w.builder.WriteString(title)
    w.builder.WriteByte(']')
    w.builder.WriteByte('(')
    w.builder.WriteString(link)
    w.builder.WriteByte(')')
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
}

func (w *writer) writeContent(content string) error {
    w.builder.Grow(len(content) + 4)
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
    _, err := w.builder.WriteString(content)
    w.builder.WriteByte('\r')
    w.builder.WriteByte('\n')
    return err
}

func (w *writer) all() string {
    return w.builder.String()
}

type data struct {
    Id        string `json:"id"`
    Ts        int64  `json:"created"`
    Title     string `json:"text"`
    Note      string `json:"note"`
    LinkTitle string `json:"hyperlinkTitle"`
    Link      string `json:"hyperlink"`
}

type node struct {
    Data     data    `json:"data"`
    Children []*node `json:"children"`
}