tree: new applet
No binary (build failed)
—
(log yok)
| @@ -181,3 +181,6 @@ Jie Zhang <[email protected]> | |
| Maxime Coste <[email protected]> | |
| paste implementation | |
| + | |
| + | Roger Knecht <[email protected]> |
| + | tree |
| @@ -0,0 +1,118 @@ | |
| + | /* vi: set sw=4 ts=4: */ |
| + | /* |
| + | * Copyright (C) 2022 Roger Knecht <[email protected]> |
| + | * |
| + | * Licensed under GPLv2, see file LICENSE in this source tree. |
| + | */ |
| + | //config:config TREE |
| + | //config: bool "tree (0.6 kb)" |
| + | //config: default y |
| + | //config: help |
| + | //config: List files and directories in a tree structure. |
| + | |
| + | //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" |
| + | #include "common_bufsiz.h" |
| + | |
| + | #define prefix_buf bb_common_bufsiz1 |
| + | |
| + | static void tree_print(unsigned count[2], const char* directory_name, char* prefix_pos) |
| + | { |
| + | struct dirent **entries; |
| + | int index, size; |
| + | |
| + | // 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); |
| + | |
| + | // print all directory entries |
| + | for (index = 0; index < size;) { |
| + | struct dirent *dirent = entries[index++]; |
| + | |
| + | // filter hidden files and directories |
| + | if (dirent->d_name[0] != '.') { |
| + | int status; |
| + | struct stat statBuf; |
| + | |
| + | //TODO: when -l is implemented, use stat, not lstat, if -l |
| + | status = lstat(dirent->d_name, &statBuf); |
| + | |
| + | if (index == size) { |
| + | strcpy(prefix_pos, "└── "); |
| + | } else { |
| + | strcpy(prefix_pos, "├── "); |
| + | } |
| + | fputs_stdout(prefix_buf); |
| + | |
| + | if (status == 0 && S_ISLNK(statBuf.st_mode)) { |
| + | // handle symlink |
| + | char* symlink_path = xmalloc_readlink(dirent->d_name); |
| + | printf("%s -> %s\n", dirent->d_name, symlink_path); |
| + | free(symlink_path); |
| + | count[1]++; |
| + | } else if (status == 0 && S_ISDIR(statBuf.st_mode) |
| + | && (prefix_pos - prefix_buf) < (COMMON_BUFSIZE - 16) |
| + | ) { |
| + | // handle directory |
| + | char* pos; |
| + | if (index == size) { |
| + | pos = stpcpy(prefix_pos, " "); |
| + | } else { |
| + | pos = stpcpy(prefix_pos, "│ "); |
| + | } |
| + | tree_print(count, dirent->d_name, pos); |
| + | count[0]++; |
| + | } else { |
| + | // handle file |
| + | puts(dirent->d_name); |
| + | count[1]++; |
| + | } |
| + | } |
| + | |
| + | // 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 UNUSED_PARAM, char **argv) |
| + | { |
| + | unsigned count[2] = { 0, 0 }; |
| + | |
| + | setup_common_bufsiz(); |
| + | |
| + | if (!argv[1]) |
| + | *argv-- = (char*)"."; |
| + | |
| + | // list directories given as command line arguments |
| + | while (*(++argv)) |
| + | tree_print(count, *argv, prefix_buf); |
| + | |
| + | // print statistic |
| + | printf("\n%u directories, %u files\n", count[0], count[1]); |
| + | |
| + | return EXIT_SUCCESS; |
| + | } |
| @@ -0,0 +1,100 @@ | |
| + | #!/bin/sh |
| + | |
| + | # Copyright 2022 by Roger Knecht <[email protected]> |
| + | # 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\ |
| + | 7 directories, 8 files\n" \ |
| + | "" "" |
| + | #note: tree v2.0.1 says "8 directories, 7 files": |
| + | #it counts "test3 -> ../../test3" as a directory, even though it does not follow this symlink |
| + | |
| + | 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\ |
| + | 7 directories, 9 files\n" \ |
| + | "" "" |
| + | #note: tree v2.0.1 says "8 directories, 7 files" (not "8 files", probably a/testfile4 -> ../b/testfile4 and b/testfile4 are counted as one file, not 2?) |
| + | |
| + | rm -rf tree.tempdir tree2.tempdir tree3.tempdir |
| + | |
| + | exit $FAILCOUNT |