refactor: 项目结构重组,src/ 扁平化为根目录,提取 pkg/ 子包
- 模块名重命名 yunshu -> hub.gaomia.site/titor/YunShu - Go 版本升级 1.21 -> 1.25 - src/ 目录删除,所有文件移至根目录 - 新增 pkg/mdprint/: Markdown AST 解析+ANSI 渲染 - 新增 pkg/style/: 终端颜色样式(8色 ANSI + 24位真彩色) - 新增 pkg/termui/: 终端输入组件(交互式输入/密码/确认) - 更新文档:AGENTS.md、architecture.md、changelog.md、taolun.md - gitignore 通配符修复 yunshu.exe -> yunshu.exe*
This commit is contained in:
204
pkg/mdprint/render.go
Normal file
204
pkg/mdprint/render.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package mdprint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"hub.gaomia.site/titor/YunShu/pkg/style"
|
||||
)
|
||||
|
||||
func renderNode(node Node) string {
|
||||
switch n := node.(type) {
|
||||
case Heading:
|
||||
return renderHeading(n)
|
||||
case Paragraph:
|
||||
return renderParagraph(n)
|
||||
case CodeBlock:
|
||||
return renderCodeBlock(n)
|
||||
case Blockquote:
|
||||
return renderBlockquote(n)
|
||||
case List:
|
||||
return renderList(n)
|
||||
case Table:
|
||||
return renderTable(n)
|
||||
case ThematicBreak:
|
||||
return "\n" + style.Dim.Render("────────────────────────────") + "\n"
|
||||
case Text:
|
||||
return style.New().Render(n.Text)
|
||||
case Bold:
|
||||
return renderBold(n)
|
||||
case Italic:
|
||||
return renderItalic(n)
|
||||
case Code:
|
||||
return renderInlineCode(n)
|
||||
case Link:
|
||||
return renderLink(n)
|
||||
default:
|
||||
return fmt.Sprintf("%v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func renderInline(nodes []Node) string {
|
||||
var b strings.Builder
|
||||
for _, child := range nodes {
|
||||
b.WriteString(renderNode(child))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
var headingConfig = []struct {
|
||||
symbol string
|
||||
style *style.Style
|
||||
}{
|
||||
{"▪ ", style.New().Bold().FgHex("#6B8E9B")},
|
||||
{"▪ ", style.New().Bold().FgHex("#89A894")},
|
||||
{"▪ ", style.New().Bold().FgHex("#A6C0B5")},
|
||||
{"▫ ", style.New().Bold().FgHex("#C3B1BD")},
|
||||
{"▫ ", style.New().Bold().FgHex("#7B8E8A")},
|
||||
{"", style.New().Bold().Dim()},
|
||||
}
|
||||
|
||||
func renderHeading(h Heading) string {
|
||||
cfg := headingConfig[0]
|
||||
if h.Level-1 < len(headingConfig) {
|
||||
cfg = headingConfig[h.Level-1]
|
||||
}
|
||||
suffix := ""
|
||||
if h.Level == 1 {
|
||||
suffix = "\n"
|
||||
}
|
||||
return "\n" + cfg.style.Render(cfg.symbol+renderInline(h.Content)) + suffix
|
||||
}
|
||||
|
||||
func renderParagraph(p Paragraph) string {
|
||||
return renderInline(p.Content)
|
||||
}
|
||||
|
||||
func renderCodeBlock(c CodeBlock) string {
|
||||
var b strings.Builder
|
||||
if c.Lang != "" {
|
||||
b.WriteString(style.Dim.Render(c.Lang + " ") + "\n")
|
||||
}
|
||||
for _, line := range strings.Split(c.Body, "\n") {
|
||||
b.WriteString(" " + style.New().Fg(style.ColorYellow).Render(line) + "\n")
|
||||
}
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
func renderBlockquote(q Blockquote) string {
|
||||
var b strings.Builder
|
||||
for i, child := range q.Children {
|
||||
if i > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
rendered := renderNode(child)
|
||||
for _, line := range strings.Split(rendered, "\n") {
|
||||
b.WriteString(style.Dim.Render("│ ") + line + "\n")
|
||||
}
|
||||
}
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
func renderList(l List) string {
|
||||
var b strings.Builder
|
||||
for i, item := range l.Items {
|
||||
if i > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
prefix := "- "
|
||||
if l.Ordered {
|
||||
prefix = fmt.Sprintf("%d. ", i+1)
|
||||
}
|
||||
if item.Checked != nil {
|
||||
check := " "
|
||||
if *item.Checked {
|
||||
check = "x"
|
||||
}
|
||||
prefix = fmt.Sprintf("- [%s] ", check)
|
||||
}
|
||||
b.WriteString(style.Dim.Render(prefix) + renderInline(item.Content))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func renderTable(t Table) string {
|
||||
if len(t.Headers) == 0 {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
|
||||
colWidths := make([]int, len(t.Headers))
|
||||
for i, h := range t.Headers {
|
||||
colWidths[i] = len(h)
|
||||
}
|
||||
for _, row := range t.Rows {
|
||||
for i, cell := range row {
|
||||
if i < len(colWidths) && len(cell) > colWidths[i] {
|
||||
colWidths[i] = len(cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderRow := func(cells []string, isHeader bool) {
|
||||
b.WriteString("| ")
|
||||
for i, cell := range cells {
|
||||
if i > 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
if i < len(colWidths) {
|
||||
cell = fmt.Sprintf("%-*s", colWidths[i], cell)
|
||||
}
|
||||
if isHeader {
|
||||
b.WriteString(style.Bold.Render(cell))
|
||||
} else {
|
||||
b.WriteString(cell)
|
||||
}
|
||||
}
|
||||
b.WriteString(" |")
|
||||
}
|
||||
|
||||
renderRow(t.Headers, true)
|
||||
|
||||
sep := "| "
|
||||
for i, w := range colWidths {
|
||||
if i > 0 {
|
||||
sep += " | "
|
||||
}
|
||||
sep += strings.Repeat("-", w)
|
||||
}
|
||||
sep += " |"
|
||||
b.WriteString("\n" + style.Dim.Render(sep))
|
||||
|
||||
for _, row := range t.Rows {
|
||||
b.WriteString("\n")
|
||||
renderRow(row, false)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func renderBold(b Bold) string {
|
||||
return style.New().Bold().Render(renderInline(b.Content))
|
||||
}
|
||||
|
||||
func renderItalic(i Italic) string {
|
||||
return style.New().Italic().Render(renderInline(i.Content))
|
||||
}
|
||||
|
||||
func renderInlineCode(c Code) string {
|
||||
return style.New().Fg(style.ColorYellow).Render("`" + c.Text + "`")
|
||||
}
|
||||
|
||||
func renderLink(l Link) string {
|
||||
text := renderInline(l.Content)
|
||||
url := l.URL
|
||||
if text == "" {
|
||||
text = url
|
||||
url = ""
|
||||
}
|
||||
styled := style.New().Underline().Fg(style.ColorBlue).Render(text)
|
||||
if url != "" {
|
||||
styled += style.Dim.Render(" (" + url + ")")
|
||||
}
|
||||
return styled
|
||||
}
|
||||
Reference in New Issue
Block a user