[PATCH v3] Added miscutil/tree.c
Roger Knecht
rknecht at pm.me
Mon Apr 18 12:54:20 UTC 2022
Adds the tree program to list directories and files in a tree structure.
function old new delta
tree_print - 388 +388
.rodata 95677 95766 +89
tree_main - 73 +73
tree_print_prefix - 28 +28
globals - 8 +8
applet_main 3192 3200 +8
applet_names 2747 2752 +5
packed_usage 34414 34396 -18
------------------------------------------------------------------------------
(add/remove: 5/0 grow/shrink: 3/1 up/down: 599/-18) Total: 581 bytes
Signed-off-by: Roger Knecht <rknecht at pm.me>
---
V4 is addressing Tito's remark regarding the symlink and multiple
directory handling.
Changelog:
V3:
- Fixed symlink handling
- Handle multiple directories in command line arguments
- Extended tests for symlink and multiple directories
- Reduced size by using libbb functions
V2:
- Fixed tree help text
- Reduced size by 644 bytes
AUTHORS | 3 +
miscutils/tree.c | 135 +++++++++++++++++++++++++++++++++++++++++++
testsuite/tree.tests | 97 +++++++++++++++++++++++++++++++
3 files changed, 235 insertions(+)
create mode 100644 miscutils/tree.c
create mode 100755 testsuite/tree.tests
diff --git a/AUTHORS b/AUTHORS
index 5c9a634c9..9ec0e2ee4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -181,3 +181,6 @@ Jie Zhang <jie.zhang at analog.com>
Maxime Coste <mawww at kakoune.org>
paste implementation
+
+Roger Knecht <rknecht at pm.me>
+ tree
diff --git a/miscutils/tree.c b/miscutils/tree.c
new file mode 100644
index 000000000..e053e8483
--- /dev/null
+++ b/miscutils/tree.c
@@ -0,0 +1,135 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2022 Roger Knecht <rknecht at pm.me>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//config:config TREE
+//config: bool "tree (0.6 kb)"
+//config: default n
+//config: help
+//config: List files and directories in a tree structure.
+//config:
+
+//applet:IF_TREE(APPLET(tree, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_TREE) += tree.o
+
+//usage:#define tree_trivial_usage NOUSAGE_STR
+//usage:#define tree_full_usage ""
+
+#include "libbb.h"
+
+#define PREFIX_CHILD "├── "
+#define PREFIX_LAST_CHILD "└── "
+#define PREFIX_GRAND_CHILD "│ "
+#define PREFIX_LAST_GRAND_CHILD " "
+#define DEFAULT_PATH "."
+
+struct directory {
+ struct directory* parent;
+ const char* prefix;
+};
+
+static struct globals {
+ int directories;
+ int files;
+} globals;
+
+static void tree_print_prefix(struct directory* directory) {
+ if (directory) {
+ tree_print_prefix(directory->parent);
+ fputs_stdout(directory->prefix);
+ }
+}
+
+static void tree_print(const char* directory_name, struct directory* directory) {
+ struct dirent **entries, *dirent;
+ struct directory child_directory;
+ char* symlink_path;
+ int index, size;
+ bool is_not_last, is_file;
+
+ // read directory entries
+ size = scandir(directory_name, &entries, NULL, alphasort);
+
+ if (size < 0) {
+ fputs_stdout(directory_name);
+ puts(" [error opening dir]");
+ return;
+ }
+
+ // print directory name
+ puts(directory_name);
+
+ // switch to sub directory
+ xchdir(directory_name);
+
+ child_directory.parent = directory;
+
+ // print all directory entries
+ for (index = 0; index < size; index++) {
+ dirent = entries[index];
+
+ // filter hidden files and directories
+ if (strncmp(dirent->d_name, ".", 1) != 0) {
+ is_file = !is_directory(dirent->d_name, 1);
+ is_not_last = (index + 1) < size;
+ symlink_path = xmalloc_readlink(dirent->d_name);
+
+ // print tree line prefix
+ tree_print_prefix(directory);
+
+ if (is_not_last) {
+ child_directory.prefix = PREFIX_GRAND_CHILD;
+ fputs_stdout(PREFIX_CHILD);
+ } else {
+ child_directory.prefix = PREFIX_LAST_GRAND_CHILD;
+ fputs_stdout(PREFIX_LAST_CHILD);
+ }
+
+ // count directories and files
+ if (is_file)
+ globals.files++;
+ else
+ globals.directories++;
+
+ if (symlink_path) {
+ // handle symlink
+ printf("%s -> %s\n", dirent->d_name, symlink_path);
+ free(symlink_path);
+ } else if (is_file)
+ // handle file
+ puts(dirent->d_name);
+ else
+ // handle directory
+ tree_print(dirent->d_name, &child_directory);
+ }
+
+ // release directory entry
+ free(dirent);
+ }
+
+ // release directory array
+ free(entries);
+
+ // switch to parent directory
+ xchdir("..");
+}
+
+int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tree_main(int argc, char **argv)
+{
+ if (argc == 1)
+ // list current working directory
+ tree_print(DEFAULT_PATH, NULL);
+
+ // list directories given as command line arguments
+ while (*(++argv))
+ tree_print(*argv, NULL);
+
+ // print statistic
+ printf("\n%d directories, %d files\n", globals.directories, globals.files);
+
+ return EXIT_SUCCESS;
+}
diff --git a/testsuite/tree.tests b/testsuite/tree.tests
new file mode 100755
index 000000000..bad28d46c
--- /dev/null
+++ b/testsuite/tree.tests
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+# Copyright 2022 by Roger Knecht <rknecht at pm.me>
+# Licensed under GPLv2, see file LICENSE in this source tree.
+
+. ./testing.sh -v
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "tree error opening dir" \
+ "tree tree.tempdir" \
+ "\
+tree.tempdir [error opening dir]\n\
+\n\
+0 directories, 0 files\n" \
+ "" ""
+
+mkdir -p tree2.tempdir
+touch tree2.tempdir/testfile
+
+testing "tree single file" \
+ "cd tree2.tempdir && tree" \
+ "\
+.\n\
+└── testfile\n\
+\n\
+0 directories, 1 files\n" \
+ "" ""
+
+mkdir -p tree3.tempdir/test1 \
+ tree3.tempdir/test2/a \
+ tree3.tempdir/test2/b \
+ tree3.tempdir/test3/c \
+ tree3.tempdir/test3/d
+
+touch tree3.tempdir/test2/a/testfile1 \
+ tree3.tempdir/test2/a/testfile2 \
+ tree3.tempdir/test2/a/testfile3 \
+ tree3.tempdir/test2/b/testfile4 \
+ tree3.tempdir/test3/c/testfile5 \
+ tree3.tempdir/test3/d/testfile6 \
+ tree3.tempdir/test3/d/.testfile7
+
+(cd tree3.tempdir/test2/a && ln -s ../b/testfile4 .)
+(cd tree3.tempdir/test2/b && ln -s ../../test3 .)
+
+testing "tree nested directories and files" \
+ "cd tree3.tempdir && tree" \
+ "\
+.\n\
+├── test1\n\
+├── test2\n\
+│ ├── a\n\
+│ │ ├── testfile1\n\
+│ │ ├── testfile2\n\
+│ │ ├── testfile3\n\
+│ │ └── testfile4 -> ../b/testfile4\n\
+│ └── b\n\
+│ ├── test3 -> ../../test3\n\
+│ └── testfile4\n\
+└── test3\n\
+ ├── c\n\
+ │ └── testfile5\n\
+ └── d\n\
+ └── testfile6\n\
+\n\
+8 directories, 7 files\n" \
+ "" ""
+
+testing "tree multiple directories" \
+ "tree tree2.tempdir tree3.tempdir" \
+ "\
+tree2.tempdir\n\
+└── testfile\n\
+tree3.tempdir\n\
+├── test1\n\
+├── test2\n\
+│ ├── a\n\
+│ │ ├── testfile1\n\
+│ │ ├── testfile2\n\
+│ │ ├── testfile3\n\
+│ │ └── testfile4 -> ../b/testfile4\n\
+│ └── b\n\
+│ ├── test3 -> ../../test3\n\
+│ └── testfile4\n\
+└── test3\n\
+ ├── c\n\
+ │ └── testfile5\n\
+ └── d\n\
+ └── testfile6\n\
+\n\
+8 directories, 8 files\n" \
+ "" ""
+
+rm -rf tree.tempdir tree2.tempdir tree3.tempdir
+
+exit $FAILCOUNT
--
2.17.1
More information about the busybox
mailing list