ls: Add support for -A and -h
This commit is contained in:
@@ -5,17 +5,24 @@
|
|||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <getopt.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
|
||||||
struct config_t
|
struct Config
|
||||||
{
|
{
|
||||||
bool list = false;
|
enum class Visibility {
|
||||||
bool all = false;
|
Normal,
|
||||||
bool directory = false;
|
AlmostAll,
|
||||||
|
All
|
||||||
|
};
|
||||||
|
Visibility visibility { Visibility::Normal };
|
||||||
|
bool show_as_list { false };
|
||||||
|
bool human_readable { false };
|
||||||
|
bool directory { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct simple_entry_t
|
struct simple_entry_t
|
||||||
@@ -58,7 +65,7 @@ const char* entry_color(mode_t mode)
|
|||||||
return "\e[0m";
|
return "\e[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_access_string(mode_t mode)
|
BAN::String build_access_string(mode_t mode, const Config&)
|
||||||
{
|
{
|
||||||
BAN::String access;
|
BAN::String access;
|
||||||
MUST(access.resize(10));
|
MUST(access.resize(10));
|
||||||
@@ -75,12 +82,12 @@ BAN::String build_access_string(mode_t mode)
|
|||||||
return access;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_hard_links_string(nlink_t links)
|
BAN::String build_hard_links_string(nlink_t links, const Config&)
|
||||||
{
|
{
|
||||||
return MUST(BAN::String::formatted("{}", links));
|
return MUST(BAN::String::formatted("{}", links));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_owner_name_string(uid_t uid)
|
BAN::String build_owner_name_string(uid_t uid, const Config&)
|
||||||
{
|
{
|
||||||
struct passwd* passwd = getpwuid(uid);
|
struct passwd* passwd = getpwuid(uid);
|
||||||
if (passwd == nullptr)
|
if (passwd == nullptr)
|
||||||
@@ -88,7 +95,7 @@ BAN::String build_owner_name_string(uid_t uid)
|
|||||||
return BAN::String(BAN::StringView(passwd->pw_name));
|
return BAN::String(BAN::StringView(passwd->pw_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_owner_group_string(gid_t gid)
|
BAN::String build_owner_group_string(gid_t gid, const Config&)
|
||||||
{
|
{
|
||||||
struct group* grp = getgrgid(gid);
|
struct group* grp = getgrgid(gid);
|
||||||
if (grp == nullptr)
|
if (grp == nullptr)
|
||||||
@@ -96,23 +103,35 @@ BAN::String build_owner_group_string(gid_t gid)
|
|||||||
return BAN::String(BAN::StringView(grp->gr_name));
|
return BAN::String(BAN::StringView(grp->gr_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_size_string(off_t size)
|
BAN::String build_size_string(off_t size, const Config& config)
|
||||||
{
|
{
|
||||||
|
if (!config.human_readable || size < 1024)
|
||||||
return MUST(BAN::String::formatted("{}", size));
|
return MUST(BAN::String::formatted("{}", size));
|
||||||
|
|
||||||
|
size = size / 1024 * 10;
|
||||||
|
|
||||||
|
size_t suffix_idx = 0;
|
||||||
|
for (; size >= 10240; size /= 1024)
|
||||||
|
suffix_idx++;
|
||||||
|
|
||||||
|
constexpr char suffix[] { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' };
|
||||||
|
if (size >= 100)
|
||||||
|
return MUST(BAN::String::formatted("{}{}", size / 10, suffix[suffix_idx]));
|
||||||
|
return MUST(BAN::String::formatted("{}.{}{}", size / 10, size % 10, suffix[suffix_idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_month_string(BAN::Time time)
|
BAN::String build_month_string(BAN::Time time, const Config&)
|
||||||
{
|
{
|
||||||
static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||||
return BAN::String(BAN::StringView(months[(time.month - 1) % 12]));
|
return BAN::String(BAN::StringView(months[(time.month - 1) % 12]));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_day_string(BAN::Time time)
|
BAN::String build_day_string(BAN::Time time, const Config&)
|
||||||
{
|
{
|
||||||
return MUST(BAN::String::formatted("{}", time.day));
|
return MUST(BAN::String::formatted("{}", time.day));
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::String build_time_string(BAN::Time time)
|
BAN::String build_time_string(BAN::Time time, const Config&)
|
||||||
{
|
{
|
||||||
static uint32_t current_year = ({ timespec real_time; clock_gettime(CLOCK_REALTIME, &real_time); BAN::from_unix_time(real_time.tv_sec).year; });
|
static uint32_t current_year = ({ timespec real_time; clock_gettime(CLOCK_REALTIME, &real_time); BAN::from_unix_time(real_time.tv_sec).year; });
|
||||||
if (time.year != current_year)
|
if (time.year != current_year)
|
||||||
@@ -151,7 +170,7 @@ BAN::Vector<size_t> resolve_layout(const BAN::Vector<simple_entry_t>& entries)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int list_directory(const BAN::String& path, config_t config)
|
int list_directory(const BAN::String& path, Config config)
|
||||||
{
|
{
|
||||||
static char link_buffer[PATH_MAX];
|
static char link_buffer[PATH_MAX];
|
||||||
|
|
||||||
@@ -191,8 +210,19 @@ int list_directory(const BAN::String& path, config_t config)
|
|||||||
struct dirent* dirent;
|
struct dirent* dirent;
|
||||||
while ((dirent = readdir(dirp)))
|
while ((dirent = readdir(dirp)))
|
||||||
{
|
{
|
||||||
if (!config.all && dirent->d_name[0] == '.')
|
switch (config.visibility)
|
||||||
|
{
|
||||||
|
case Config::Visibility::Normal:
|
||||||
|
if (dirent->d_name[0] == '.')
|
||||||
continue;
|
continue;
|
||||||
|
break;
|
||||||
|
case Config::Visibility::AlmostAll:
|
||||||
|
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case Config::Visibility::All:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1)
|
if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1)
|
||||||
{
|
{
|
||||||
@@ -218,8 +248,8 @@ int list_directory(const BAN::String& path, config_t config)
|
|||||||
[](const simple_entry_t& lhs, const simple_entry_t& rhs)
|
[](const simple_entry_t& lhs, const simple_entry_t& rhs)
|
||||||
{
|
{
|
||||||
// sort directories first
|
// sort directories first
|
||||||
bool lhs_isdir = S_ISDIR(lhs.st.st_mode);
|
const bool lhs_isdir = S_ISDIR(lhs.st.st_mode);
|
||||||
bool rhs_isdir = S_ISDIR(rhs.st.st_mode);
|
const bool rhs_isdir = S_ISDIR(rhs.st.st_mode);
|
||||||
if (lhs_isdir != rhs_isdir)
|
if (lhs_isdir != rhs_isdir)
|
||||||
return lhs_isdir;
|
return lhs_isdir;
|
||||||
|
|
||||||
@@ -231,7 +261,7 @@ int list_directory(const BAN::String& path, config_t config)
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!config.list)
|
if (!config.show_as_list)
|
||||||
{
|
{
|
||||||
if (!g_stdout_terminal)
|
if (!g_stdout_terminal)
|
||||||
{
|
{
|
||||||
@@ -298,7 +328,7 @@ int list_directory(const BAN::String& path, config_t config)
|
|||||||
full_entry_t full_entry;
|
full_entry_t full_entry;
|
||||||
|
|
||||||
#define GET_ENTRY_STRING(property, input) \
|
#define GET_ENTRY_STRING(property, input) \
|
||||||
full_entry.property = build_ ## property ## _string(input); \
|
full_entry.property = build_ ## property ## _string(input, config); \
|
||||||
if (full_entry.property.size() > max_entry.property.size()) \
|
if (full_entry.property.size() > max_entry.property.size()) \
|
||||||
max_entry.property = full_entry.property;
|
max_entry.property = full_entry.property;
|
||||||
|
|
||||||
@@ -350,60 +380,65 @@ int usage(const char* argv0, int ret)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
config_t config;
|
Config config;
|
||||||
|
|
||||||
int i = 1;
|
for (;;)
|
||||||
for (; i < argc; i++)
|
|
||||||
{
|
{
|
||||||
if (argv[i][0] != '-')
|
static option long_options[] {
|
||||||
break;
|
{ "all", no_argument, nullptr, 'a' },
|
||||||
if (argv[i][1] == '\0')
|
{ "almost-all", no_argument, nullptr, 'A' },
|
||||||
|
{ "directory" , no_argument, nullptr, 'd' },
|
||||||
|
{ "human-readable", no_argument, nullptr, 'h' },
|
||||||
|
{ "list", no_argument, nullptr, 'l' },
|
||||||
|
{ "help", no_argument, nullptr, 'x' },
|
||||||
|
};
|
||||||
|
|
||||||
|
int ch = getopt_long(argc, argv, "aAlh", long_options, nullptr);
|
||||||
|
if (ch == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (argv[i][1] == '-')
|
switch (ch)
|
||||||
{
|
{
|
||||||
if (strcmp(argv[i], "--help") == 0)
|
case 'a':
|
||||||
return usage(argv[0], 0);
|
config.visibility = Config::Visibility::All;
|
||||||
else if (strcmp(argv[i], "--all") == 0)
|
break;
|
||||||
config.all = true;
|
case 'A':
|
||||||
else if (strcmp(argv[i], "--list") == 0)
|
config.visibility = Config::Visibility::AlmostAll;
|
||||||
config.list = true;
|
break;
|
||||||
else if (strcmp(argv[i], "--directory") == 0)
|
case 'd':
|
||||||
config.directory = true;
|
config.directory = true;
|
||||||
else
|
break;
|
||||||
{
|
case 'h':
|
||||||
fprintf(stderr, "unrecognized option '%s'\n", argv[i]);
|
config.human_readable = true;
|
||||||
return usage(argv[0], 2);
|
break;
|
||||||
}
|
case 'l':
|
||||||
}
|
config.show_as_list = true;
|
||||||
else
|
break;
|
||||||
{
|
case 'x':
|
||||||
for (size_t j = 1; argv[i][j]; j++)
|
fprintf(stderr, "usage: %s [OPTION]... [FILE]...\n", argv[0]);
|
||||||
{
|
fprintf(stderr, " list information about FILEs\n");
|
||||||
if (argv[i][j] == 'h')
|
fprintf(stderr, "OPTIONS:\n");
|
||||||
return usage(argv[0], 0);
|
fprintf(stderr, " -a, --all do not ignore entries starting with .\n");
|
||||||
else if (argv[i][j] == 'a')
|
fprintf(stderr, " -A, --almost-all do not list . and ..\n");
|
||||||
config.all = true;
|
fprintf(stderr, " -d, --directory list directories and not their contents\n");
|
||||||
else if (argv[i][j] == 'l')
|
fprintf(stderr, " -h, --human-readable print sizes in human readable form\n");
|
||||||
config.list = true;
|
fprintf(stderr, " -l, --list use long listing format\n");
|
||||||
else if (argv[i][j] == 'd')
|
fprintf(stderr, " --help show this message and exit\n");
|
||||||
config.directory = true;
|
return 0;
|
||||||
else
|
case '?':
|
||||||
{
|
fprintf(stderr, "invalid option %c\n", optopt);
|
||||||
fprintf(stderr, "unrecognized option '%c'\n", argv[i][j]);
|
fprintf(stderr, "see '%s --help' for usage\n", argv[0]);
|
||||||
return usage(argv[0], 2);
|
return 1;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::Vector<BAN::String> files;
|
BAN::Vector<BAN::String> files;
|
||||||
|
|
||||||
if (i == argc)
|
if (optind == argc)
|
||||||
MUST(files.emplace_back("."_sv));
|
MUST(files.emplace_back("."_sv));
|
||||||
else for (; i < argc; i++)
|
else for (int i = optind; i < argc; i++)
|
||||||
MUST(files.emplace_back(BAN::StringView(argv[i])));
|
MUST(files.emplace_back(BAN::StringView(argv[i])));
|
||||||
|
|
||||||
g_stdout_terminal = isatty(STDOUT_FILENO) && tcgetwinsize(STDOUT_FILENO, &g_terminal_size) == 0;
|
g_stdout_terminal = isatty(STDOUT_FILENO) && tcgetwinsize(STDOUT_FILENO, &g_terminal_size) == 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user