On Sun, 01 May 2022 12:51:16 +0000 Roger Knecht <rkne...@pm.me> wrote:
> Add new applet which resembles the MS-DOS tree program to list directories > and files in a tree structure. > > function old new delta > tree_print - 388 +388 > .rodata 95678 95767 +89 > tree_main - 73 +73 > tree_print_prefix - 28 +28 > packed_usage 34417 34429 +12 > globals - 8 +8 > applet_main 3192 3200 +8 > applet_names 2747 2752 +5 > ------------------------------------------------------------------------------ > (add/remove: 5/0 grow/shrink: 4/0 up/down: 611/0) Total: 611 bytes > > Signed-off-by: Roger Knecht <rkne...@pm.me> > --- > Changelog: > > V4: > - Rephrase commit message > - Updated bloatcheck to latest master > > 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.zh...@analog.com> > > Maxime Coste <ma...@kakoune.org> > paste implementation > + > +Roger Knecht <rkne...@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 <rkne...@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 Hi, if (dirent->d_name[0] != '.') { > + if (strncmp(dirent->d_name, ".", 1) != 0) { twisted logic? > + is_file = !is_directory(dirent->d_name, 1); is_not_last is used only once > + is_not_last = (index + 1) < size; > + symlink_path = xmalloc_readlink(dirent->d_name); > + > + // print tree line prefix > + tree_print_prefix(directory); > + if ((index + 1) < size) { > + 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); > + } > + Less lines maybe less size (untested) if (is_directory(dirent->d_name, 1)) { globals.directories++; tree_print(dirent->d_name, &child_directory); } else { globals.files++; printf((symlink) ? "%s -> %s\n" : "%s\n", dirent->d_name, symlink_path); } free(symlink_path); Ciao, Tito > + // 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 <rkne...@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 > > _______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox