|
18 | 18 | [["path" fs/exists? "$path is not a valid path"] |
19 | 19 | ["path" (partial allowed-path? db) (str "Access denied - path $path outside allowed directories: " (tools.util/workspace-roots-strs db))]]) |
20 | 20 |
|
| 21 | +(defn ^:private tree-walk |
| 22 | + "Recursively walks a directory tree, building a visual tree representation with counts." |
| 23 | + ([directory stats] |
| 24 | + (tree-walk directory "" stats 1 0)) |
| 25 | + ([directory stats max-depth] |
| 26 | + (tree-walk directory "" stats max-depth 0)) |
| 27 | + ([directory prefix stats max-depth current-depth] |
| 28 | + (let [filepaths (->> (fs/list-dir directory) |
| 29 | + (map fs/file-name) |
| 30 | + (remove #(string/starts-with? % ".")) |
| 31 | + sort |
| 32 | + vec)] |
| 33 | + (loop [index 0 |
| 34 | + output ""] |
| 35 | + (if (>= index (count filepaths)) |
| 36 | + output |
| 37 | + (let [filename (nth filepaths index) |
| 38 | + absolute (fs/path directory filename) |
| 39 | + is-last? (= index (dec (count filepaths))) |
| 40 | + current-prefix (if is-last? "└── " "├── ") |
| 41 | + next-prefix (if is-last? " " "│ ")] |
| 42 | + |
| 43 | + ;; Register file/directory in stats |
| 44 | + (if (fs/directory? absolute) |
| 45 | + (swap! (:dir-count stats) inc) |
| 46 | + (swap! (:file-count stats) inc)) |
| 47 | + |
| 48 | + (let [current-line (str prefix current-prefix filename "\n") |
| 49 | + ;; Only recurse if we haven't reached max depth |
| 50 | + subtree-output (if (and (fs/directory? absolute) |
| 51 | + (< current-depth max-depth)) |
| 52 | + (tree-walk absolute |
| 53 | + (str prefix next-prefix) |
| 54 | + stats |
| 55 | + max-depth |
| 56 | + (inc current-depth)) |
| 57 | + "")] |
| 58 | + (recur (inc index) |
| 59 | + (str output current-line subtree-output))))))))) |
| 60 | + |
21 | 61 | (defn ^:private directory-tree [arguments {:keys [db]}] |
22 | 62 | (let [path (delay (fs/canonicalize (get arguments "path")))] |
23 | 63 | (or (tools.util/invalid-arguments arguments (path-validations db)) |
24 | | - (tools.util/single-text-content |
25 | | - (reduce |
26 | | - (fn [out path] |
27 | | - (str out |
28 | | - (format "[%s] %s\n" |
29 | | - (if (fs/directory? path) "DIR" "FILE") |
30 | | - path))) |
31 | | - "" |
32 | | - (sort (fs/list-dir @path))))))) |
| 64 | + (let [max-depth (or (get arguments "max_depth") Integer/MAX_VALUE) |
| 65 | + stats {:dir-count (atom 0) |
| 66 | + :file-count (atom 0)} |
| 67 | + tree-output (tree-walk @path stats max-depth) |
| 68 | + summary (format "%d directories, %d files" |
| 69 | + @(:dir-count stats) |
| 70 | + @(:file-count stats))] |
| 71 | + (tools.util/single-text-content |
| 72 | + (str tree-output "\n" summary)))))) |
33 | 73 |
|
34 | 74 | (def ^:private read-file-max-lines 2000) |
35 | 75 |
|
|
0 commit comments