summaryrefslogtreecommitdiff
path: root/Userland/Utilities/tree.cpp
blob: 12f4ea874479c50451e5d0e6095c6f32f7924055 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
 * Copyright (c) 2020, Stijn De Ridder <stijn.deridder@hotmail.com>
 * Copyright (c) 2022, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/LexicalPath.h>
#include <AK/QuickSort.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Vector.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/DirIterator.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

static bool flag_show_hidden_files = false;
static bool flag_show_only_directories = false;
static int max_depth = INT_MAX;

static int g_directories_seen = 0;
static int g_files_seen = 0;

static void print_directory_tree(const String& root_path, int depth, const String& indent_string)
{
    if (depth > 0) {
        String root_indent_string;
        if (depth > 1) {
            root_indent_string = indent_string.substring(0, (depth - 1) * 4);
        } else {
            root_indent_string = "";
        }
        out("{}|-- ", root_indent_string);
    }

    String root_dir_name = LexicalPath::basename(root_path);
    out("\033[34;1m{}\033[0m\n", root_dir_name);

    if (depth >= max_depth) {
        return;
    }

    Core::DirIterator di(root_path, flag_show_hidden_files ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots);
    if (di.has_error()) {
        warnln("{}: {}", root_path, di.error_string());
        return;
    }

    Vector<String> names;
    while (di.has_next()) {
        String name = di.next_path();
        if (di.has_error()) {
            warnln("{}: {}", root_path, di.error_string());
            continue;
        }
        names.append(name);
    }

    quick_sort(names);

    for (size_t i = 0; i < names.size(); i++) {
        String name = names[i];

        StringBuilder builder;
        builder.append(root_path);
        if (!root_path.ends_with('/')) {
            builder.append('/');
        }
        builder.append(name);
        String full_path = builder.to_string();

        struct stat st;
        int rc = lstat(full_path.characters(), &st);
        if (rc == -1) {
            warnln("lstat({}) failed: {}", full_path, strerror(errno));
            continue;
        }

        if (S_ISDIR(st.st_mode)) {
            g_directories_seen++;

            bool at_last_entry = i == names.size() - 1;
            String new_indent_string;
            if (at_last_entry) {
                new_indent_string = String::formatted("{}    ", indent_string);
            } else {
                new_indent_string = String::formatted("{}|   ", indent_string);
            }

            print_directory_tree(full_path.characters(), depth + 1, new_indent_string);
        } else if (!flag_show_only_directories) {
            g_files_seen++;

            outln("{}|-- {}", indent_string, name);
        }
    }
}

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    TRY(Core::System::pledge("stdio rpath tty"));

    Vector<const char*> directories;

    Core::ArgsParser args_parser;
    args_parser.add_option(flag_show_hidden_files, "Show hidden files", "all", 'a');
    args_parser.add_option(flag_show_only_directories, "Show only directories", "only-directories", 'd');
    args_parser.add_option(max_depth, "Maximum depth of the tree", "maximum-depth", 'L', "level");
    args_parser.add_positional_argument(directories, "Directories to print", "directories", Core::ArgsParser::Required::No);
    args_parser.parse(arguments);

    if (max_depth < 1) {
        warnln("{}: Invalid level, must be greater than 0.", arguments.argv[0]);
        return 1;
    }

    if (directories.is_empty()) {
        print_directory_tree(".", 0, "");
        puts("");
    } else {
        for (const char* directory : directories) {
            print_directory_tree(directory, 0, "");
            puts("");
        }
    }

    outln("{} directories, {} files", g_directories_seen, g_files_seen);

    return 0;
}