134 lines
2.7 KiB
Go
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 ""
|
|
}
|