Skip to content

Commit a62ee02

Browse files
committed
feat: replace table with tree view in drives get
Show drive contents as an indented tree with metadata (type, ID) on each document line. Nested folders now display their full hierarchy with proper tree connectors instead of a flat table with a single Folder column.
1 parent ca5ee17 commit a62ee02

2 files changed

Lines changed: 43 additions & 41 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/drives.rs

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -137,55 +137,21 @@ async fn get(id: &str, format: OutputFormat, profile_name: Option<&str>) -> Resu
137137
drive["documentType"].as_str().unwrap_or("-")
138138
);
139139

140-
// Show documents table
140+
// Show contents as a tree with metadata
141141
if let Some(nodes) = drive.pointer("/state/nodes").and_then(|v| v.as_array()) {
142-
let files: Vec<&Value> = nodes
142+
let files = nodes
143143
.iter()
144144
.filter(|n| n["kind"].as_str() == Some("file"))
145-
.collect();
145+
.count();
146146
let folders = nodes
147147
.iter()
148148
.filter(|n| n["kind"].as_str() == Some("folder"))
149149
.count();
150-
println!("\nContents: {} files, {folders} folders", files.len());
151-
152-
if !files.is_empty() {
153-
// Build folder id -> name lookup for the Folder column
154-
let folder_map: std::collections::HashMap<&str, &str> = nodes
155-
.iter()
156-
.filter(|n| n["kind"].as_str() == Some("folder"))
157-
.filter_map(|n| Some((n["id"].as_str()?, n["name"].as_str()?)))
158-
.collect();
150+
println!("\nContents: {files} files, {folders} folders");
159151

152+
if files > 0 || folders > 0 {
160153
println!();
161-
let rows: Vec<Vec<String>> = files
162-
.iter()
163-
.map(|f| {
164-
let folder = f["parentFolder"]
165-
.as_str()
166-
.and_then(|pid| folder_map.get(pid).copied())
167-
.unwrap_or("");
168-
vec![
169-
f["id"].as_str().unwrap_or("-").to_string(),
170-
f["name"].as_str().unwrap_or("-").to_string(),
171-
f["documentType"].as_str().unwrap_or("-").to_string(),
172-
folder.to_string(),
173-
]
174-
})
175-
.collect();
176-
if folders > 0 {
177-
print_table(&["ID", "Name", "Type", "Folder"], &rows);
178-
} else {
179-
// No folders — skip the Folder column for cleaner output
180-
let rows: Vec<Vec<String>> = rows
181-
.into_iter()
182-
.map(|mut r| {
183-
r.pop();
184-
r
185-
})
186-
.collect();
187-
print_table(&["ID", "Name", "Type"], &rows);
188-
}
154+
print_drive_tree(nodes, None, "");
189155
}
190156
}
191157
}
@@ -348,3 +314,39 @@ async fn delete(ids: &[String], skip_confirm: bool, profile_name: Option<&str>)
348314
}
349315
Ok(())
350316
}
317+
318+
/// Print drive contents as an indented tree with metadata (Type, ID) on each file line.
319+
fn print_drive_tree(nodes: &[Value], parent: Option<&str>, indent: &str) {
320+
let children: Vec<&Value> = nodes
321+
.iter()
322+
.filter(|n| {
323+
let pf = n["parentFolder"].as_str();
324+
match parent {
325+
None => pf.is_none() || pf == Some(""),
326+
Some(p) => pf == Some(p),
327+
}
328+
})
329+
.collect();
330+
331+
for (i, child) in children.iter().enumerate() {
332+
let is_last = i == children.len() - 1;
333+
let connector = if is_last {
334+
"\u{2514}\u{2500}\u{2500} "
335+
} else {
336+
"\u{251c}\u{2500}\u{2500} "
337+
};
338+
let child_indent = if is_last { " " } else { "\u{2502} " };
339+
340+
let name = child["name"].as_str().unwrap_or("-");
341+
let kind = child["kind"].as_str().unwrap_or("file");
342+
let id = child["id"].as_str().unwrap_or("-");
343+
344+
if kind == "folder" {
345+
println!("{indent}{connector}\u{1f4c1} {name}/");
346+
print_drive_tree(nodes, Some(id), &format!("{indent}{child_indent}"));
347+
} else {
348+
let doc_type = child["documentType"].as_str().unwrap_or("-");
349+
println!("{indent}{connector}{name} ({doc_type}) [{id}]");
350+
}
351+
}
352+
}

0 commit comments

Comments
 (0)