renderTree function
Renders entries (rooted at the start point root) as a tree-style
listing: each line carries the aggregated size, directories are listed
first, and subtrees deeper than maxDepth are collapsed (their line still
reports the full aggregate). maxDepth == 0 means no depth limit.
Implementation
List<String> renderTree(
String root,
List<StatEntry> entries, {
required int maxDepth,
}) {
final rootNode = _TreeNode(root, isDir: true);
for (final e in entries) {
final rel = _treeRel(root, e.path);
if (rel.isEmpty) {
rootNode.isDir = e.isDir;
rootNode.isLink = e.isLink;
rootNode.size = e.size;
continue;
}
final parts = rel.split('/');
var node = rootNode;
for (var i = 0; i < parts.length; i++) {
final seg = parts[i];
if (seg.isEmpty) continue;
final leaf = i == parts.length - 1;
final child = node.children.putIfAbsent(seg, () => _TreeNode(seg));
if (leaf) {
child.isDir = e.isDir;
child.isLink = e.isLink;
child.size = e.size;
} else {
child.isDir = true;
}
node = child;
}
}
_treeTotals(rootNode);
final lines = <String>['$root [${_fmtBytes(rootNode.total)}]'];
var dirs = 0;
var files = 0;
void walk(_TreeNode node, String prefix, int depth) {
if (maxDepth != 0 && depth > maxDepth) return;
final kids = node.children.values.toList()
..sort((a, b) {
if (a.isDir != b.isDir) return a.isDir ? -1 : 1;
return a.name.compareTo(b.name);
});
for (var i = 0; i < kids.length; i++) {
final k = kids[i];
final last = i == kids.length - 1;
final label = k.isLink ? '${k.name} ->' : k.name;
lines.add(
'$prefix${last ? '└── ' : '├── '}$label '
'[${_fmtBytes(k.total)}]',
);
if (k.isDir) {
dirs++;
} else {
files++;
}
if (k.isDir && k.children.isNotEmpty) {
walk(k, '$prefix${last ? ' ' : '│ '}', depth + 1);
}
}
}
walk(rootNode, '', 1);
lines
..add('')
..add(
'$dirs director${dirs == 1 ? 'y' : 'ies'}, '
'$files file${files == 1 ? '' : 's'}',
);
return lines;
}