Files
dirmd/internal/renderer/renderer.go
T

134 lines
2.7 KiB
Go

package renderer
import (
"fmt"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"dirmd/internal/tree"
"dirmd/internal/walker"
)
const fenceChars = "~~~"
func Write(result *walker.Result, outputPath string, appendMode bool, singleFile bool) error {
flags := os.O_CREATE | os.O_WRONLY
if appendMode {
flags |= os.O_APPEND
} else {
flags |= os.O_TRUNC
}
f, err := os.OpenFile(outputPath, flags, 0644)
if err != nil {
return fmt.Errorf("cannot open output file: %w", err)
}
defer f.Close()
var sb strings.Builder
// Determine the correct H1 path
h1Path := result.AbsRoot
if singleFile && len(result.Entries) > 0 {
h1Path = filepath.Join(result.AbsRoot, result.Entries[0].RelPath)
}
fmt.Fprintf(&sb, "# %s\n\n", h1Path)
if !singleFile {
root := tree.Build(result.Entries)
rootName := filepath.Base(result.AbsRoot)
sb.WriteString("## Directory Tree\n\n")
sb.WriteString(fenceChars + "\n")
sb.WriteString(tree.Render(root, rootName))
sb.WriteString(fenceChars + "\n\n")
}
if len(result.Entries) > 0 {
if !singleFile {
sb.WriteString("## Contents\n\n")
}
for _, entry := range result.Entries {
fullPath := filepath.Join(result.AbsRoot, entry.RelPath)
content, err := readFile(fullPath)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
continue
}
language := extToLang(filepath.Ext(entry.RelPath))
fmt.Fprintf(&sb, "### ./%s\n\n", entry.RelPath)
fmt.Fprintf(&sb, "%s", fenceChars)
if language != "" {
fmt.Fprintf(&sb, "%s", language)
}
fmt.Fprintf(&sb, "\n")
fmt.Fprintf(&sb, "%s", content)
if !strings.HasSuffix(content, "\n") {
fmt.Fprintf(&sb, "\n")
}
fmt.Fprintf(&sb, "%s\n\n", fenceChars)
}
}
_, err = f.WriteString(sb.String())
return err
}
func readFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
for i, b := range data {
if b == 0 {
return "", fmt.Errorf("contains null byte at position %d", i)
}
}
if !utf8.Valid(data) {
return "", fmt.Errorf("invalid UTF-8 encoding")
}
content := string(data)
content = strings.ReplaceAll(content, "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")
return content, nil
}
func extToLang(ext string) string {
mapping := map[string]string{
".go": "go",
".md": "markdown",
".yml": "yaml",
".yaml": "yaml",
".js": "javascript",
".ts": "typescript",
".css": "css",
".html": "html",
".json": "json",
".xml": "xml",
".sql": "sql",
".sh": "bash",
".py": "python",
".rs": "rust",
".c": "c",
".h": "c",
".cpp": "cpp",
".hpp": "cpp",
".txt": "text",
}
if lang, ok := mapping[strings.ToLower(ext)]; ok {
return lang
}
return ""
}