package tree import ( "fmt" "io/fs" "path/filepath" "slices" "strings" "git.flo.fo/FLO/dirmd/internal/config" "git.flo.fo/FLO/dirmd/internal/filter" ) func BuildFullTree(absRoot string, cfg *config.Config) (*Node, error) { root := &Node{IsDir: true, Children: make(map[string]*Node)} err := filepath.WalkDir(absRoot, func(path string, d fs.DirEntry, err error) error { if err != nil { return nil } rel, rerr := filepath.Rel(absRoot, path) if rerr != nil || rel == "." { return nil } if filter.IsSymlink(path) { return nil } depth := strings.Count(rel, string(filepath.Separator)) + 1 if depth > cfg.MaxDepth { node := addPath(root, rel, d.IsDir()) node.Annotation = "exceeded --max-depth" if d.IsDir() { return filepath.SkipDir } return nil } name := filepath.Base(path) if d.IsDir() { if strings.HasPrefix(name, ".") { node := addPath(root, rel, true) node.Annotation = "ignored (default)" return filepath.SkipDir } if annotation, ok := ignoreReason(name, cfg.Ignores); ok { node := addPath(root, rel, true) node.Annotation = annotation return filepath.SkipDir } if cfg.OmitTest && filter.IsTestDir(rel) { node := addPath(root, rel, true) node.Annotation = "tests omitted (--omit-test)" return filepath.SkipDir } addPath(root, rel, true) return nil } if annotation, ok := fileAnnotation(rel, name, cfg); ok { node := addPath(root, rel, false) node.Annotation = annotation return nil } addPath(root, rel, false) return nil }) if err != nil { return nil, fmt.Errorf("cannot walk %s: %w", absRoot, err) } return root, nil } func ignoreReason(name string, ignores []string) (string, bool) { for _, pattern := range ignores { if matched, _ := filepath.Match(pattern, name); matched { if slices.Contains(config.DefaultIgnores, pattern) { return "ignored (default)", true } return "ignored (--ignore)", true } } return "", false } func fileAnnotation(rel, name string, cfg *config.Config) (string, bool) { if strings.HasPrefix(name, ".") { return "", false } if annotation, ok := ignoreReason(name, cfg.Ignores); ok { return annotation, true } if cfg.OmitTest && filter.IsTestFile(rel, name) { return "tests omitted (--omit-test)", true } if cfg.OmitMd && filter.IsNonReadmeMarkdown(rel, name) { return "omitted (--omit-md)", true } if cfg.FrontendOnly && !filter.IsFrontendOrReadme(rel, name) { return "omitted (--frontend-only)", true } if cfg.SkipFrontend && filter.IsFrontendExt(rel) { return "omitted (--skip-frontend)", true } return "", false } func RenderBreadcrumb(absRoot, relPath string) string { var sb strings.Builder rootName := filepath.Base(absRoot) dir := filepath.Dir(relPath) if dir == "." { return rootName + "/" + filepath.Base(relPath) } parts := strings.Split(filepath.ToSlash(dir), "/") sb.WriteString(rootName) for _, part := range parts { sb.WriteString(" / ") sb.WriteString(part) } sb.WriteString(" / ") sb.WriteString(filepath.Base(relPath)) return sb.String() }