This repository has been archived by the owner on Sep 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
/
ls.cpp
199 lines (174 loc) · 5.97 KB
/
ls.cpp
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
This file is part of duckOS.
duckOS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
duckOS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with duckOS. If not, see <https://www.gnu.org/licenses/>.
Copyright (c) Byteduck 2016-2020. All rights reserved.
*/
// A program that lists the contents of a directory.
#include <libduck/Args.h>
#include <libduck/Path.h>
#include <libduck/FormatStream.h>
#include <unistd.h>
#include <algorithm>
#include <termios.h>
#include <sys/ioctl.h>
bool g_colorize = false;
bool g_no_color = false;
bool g_human = false;
bool g_show_all = false;
bool g_long_format = false;
bool g_columns = false;
constexpr auto RESET_FORMAT = "\033[39m";
constexpr char ENTRY_TYPE_CHARS[] = {
'?', // DT_UNKNOWN
'-', // DT_REG
'd', // DT_DIR
'c', // DT_CHR
'b', // DT_BLK
'f', // DT_FIFO
's', // DT_SOCK
'l', // DT_LNK
};
constexpr const char* ENTRY_TYPE_COLORS[] = {
"", // DT_UNKNOWN
"\033[39m", // DT_REG
"\033[36m", // DT_DIR
"\033[32m", // DT_CHR
"\033[33m", // DT_BLK
"\033[35m", // DT_FIFO
"\033[38m", // DT_SOCK
"\033[34m", // DT_LNK
};
std::string entry_permissions_string(const Duck::DirectoryEntry& entry) {
constexpr char bit_names[] = {'r', 'w', 'x'};
constexpr const char* bit_colors[] {"\033[36m", "\033[31m", "\033[32m"};
Duck::StringOutputStream stream;
for(mode_t bit = 0; bit < 9; bit++) {
if(entry.mode() & (0x1u << (8 - bit))) {
if(g_colorize)
stream << bit_colors[bit % 3] << bit_names[bit % 3] << RESET_FORMAT;
else
stream << bit_names[bit % 3];
} else {
stream << '-';
}
}
return stream.string();
}
std::string entry_name(const Duck::DirectoryEntry& entry) {
Duck::StringOutputStream out;
if(!g_no_color)
out << ENTRY_TYPE_COLORS[entry.type()] << entry.name() << RESET_FORMAT;
else
out << entry.name();
return out.string();
}
std::string entry_size_str(const Duck::DirectoryEntry& entry, size_t widest_size) {
std::string size_str = g_human ? entry.size().readable() : std::to_string(entry.size().bytes);
if(widest_size)
return std::string(widest_size - size_str.size(), ' ') + size_str;
return size_str;
};
int main(int argc, char** argv) {
std::string dir_name = ".";
Duck::Args args;
args.add_positional(dir_name, false, "DIR", "The directory to list.");
args.add_named(g_no_color, "n", "no-color", "Do not colorize the output.");
args.add_named(g_colorize, "c", "color", "Colorize the output.");
args.add_named(g_show_all, "a", "all", "Show entries starting with \".\".");
args.add_named(g_long_format, "l", "long", "Show more details for entries.");
args.add_named(g_human, "h", "human-readable", "Show human-readable sizes.");
args.add_named(g_columns, "c", "columns", "Force multi-column output.");
args.parse(argc, argv);
if(!isatty(STDOUT_FILENO))
g_no_color = true;
else
g_columns = true;
g_columns = g_columns && !g_long_format;
// Read entries
auto dirs_res = Duck::Path(dir_name).get_directory_entries();
if(dirs_res.is_error()) {
Duck::printerrln("ls: cannot access {}: {}", dir_name, dirs_res.strerror());
return dirs_res.code();
}
// Sort by name / type
auto entries = dirs_res.value();
std::sort(entries.begin(), entries.end(), [](auto const& lhs, auto const& rhs) {
return lhs.name() < rhs.name();
});
std::sort(entries.begin(), entries.end(), [](auto const& lhs, auto const& rhs) {
return lhs.type() > rhs.type();
});
if(g_long_format) {
// If we're in long format, first we need to calculate how much to pad the size values by.
size_t widest_size = 0;
for(auto& entry : entries) {
if(entry.name()[0] == '.' && !g_show_all)
continue;
widest_size = std::max(widest_size, entry_size_str(entry, 0).size());
}
// Then, print each entry.
for(auto& entry : entries) {
if(entry.name()[0] == '.' && !g_show_all)
continue;
Duck::Stream::std_out
<< ENTRY_TYPE_CHARS[entry.type()] << entry_permissions_string(entry) << ' '
<< entry_size_str(entry, widest_size) << ' '
<< entry_name(entry) << '\n';
}
} else if(g_columns) {
// If we're printing columns, figure out how wide they should be
winsize winsz;
winsz.ws_col = 80;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsz);
size_t widest_name = 0;
for(auto& entry : entries) {
if(entry.name()[0] == '.' && !g_show_all)
continue;
widest_name = std::max(widest_name, entry.name().size());
}
/* Yeah, this calculation doesn't take into account that we
* don't have padding on the right. I'm too lazy to do that
* right now */
constexpr size_t padding = 2;
auto n_cols = std::max((int) winsz.ws_col / (int) (widest_name + padding), 1);
auto n_rows = (entries.size() + n_cols - 1) / n_cols;
std::vector<std::vector<Duck::DirectoryEntry>> cols;
cols.emplace_back();
for (auto& entry : entries) {
if (cols[cols.size() - 1].size() == n_rows)
cols.emplace_back();
cols[cols.size() - 1].push_back(entry);
}
for (auto row = 0; row < n_rows; row++) {
for(auto col = 0; col < n_cols; col++) {
auto& col_entries = cols[col];
if (col_entries.size() <= row)
continue;
Duck::Stream::std_out << entry_name(col_entries[row]);
if (col != n_cols - 1) {
auto pad = padding + widest_name - col_entries[row].name().size();
for (auto i = 0; i < pad; i++)
Duck::Stream::std_out << ' ';
}
}
Duck::Stream::std_out << '\n';
}
} else {
// If we're in short format, we can simply print the entries.
for(auto& entry : entries) {
if(entry.name()[0] == '.' && !g_show_all)
continue;
Duck::Stream::std_out << entry_name(entry) << '\n';
}
}
return 0;
}