From 49f7c2be366f0b918b68a4cc8e4335ebab0a15ba Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Sun, 30 Apr 2023 02:12:02 -0400 Subject: [PATCH] fix ls and add start of ed --- Makefile | 1 + src/command.h | 3 +- src/commands/cat.c | 51 ++-- src/commands/ed.c | 528 ++++++++++++++++++++++++++++++++++++++++ src/commands/ls.c | 184 ++++++++++---- src/main.c | 6 +- src/util/regex.c | 528 ++++++++++++++++++++++++++++++++++++++++ src/util/regex.h | 65 +++++ src/{ => util}/shared.c | 17 +- src/{ => util}/shared.h | 27 ++ 10 files changed, 1322 insertions(+), 88 deletions(-) create mode 100644 src/commands/ed.c create mode 100644 src/util/regex.c create mode 100644 src/util/regex.h rename src/{ => util}/shared.c (90%) rename src/{ => util}/shared.h (51%) diff --git a/Makefile b/Makefile index 28e8e07..a2be56b 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ EOF: clean build dirs: mkdir -p ./$(BIN) mkdir -p ./$(BIN)/src + mkdir -p ./$(BIN)/src/util mkdir -p ./$(BIN)/src/commands run: build diff --git a/src/command.h b/src/command.h index 0b7b8ad..fb1acb3 100644 --- a/src/command.h +++ b/src/command.h @@ -1,4 +1,4 @@ -#include "shared.h" +#include "util/shared.h" #include #include @@ -19,3 +19,4 @@ COMMAND_EMPTY(user_id); COMMAND(ls); COMMAND(tail); COMMAND(head); +COMMAND(ed); diff --git a/src/commands/cat.c b/src/commands/cat.c index 8e22706..dd1a732 100644 --- a/src/commands/cat.c +++ b/src/commands/cat.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -90,18 +91,26 @@ COMMAND(cat) { flags.change_tabs = false; flags.end_lines_dollar = false; + int start = 0; + for (int i = 0; i < argc; i++) { if (!prefix("-", argv[i])) { continue; } + if (streql("-", argv[i])) { + break; + } + if (streql("--help", argv[i])) { help(); } - size_t len = strlen(argv[i] + 1); - for (size_t j = 0; j < len; j++) { - char c = argv[i][j + 1]; + start++; + + size_t len = strlen(argv[i]); + for (size_t j = 1; j < len; j++) { + char c = argv[i][j]; switch (c) { case 'n': flags.number_lines = true; @@ -131,33 +140,19 @@ COMMAND(cat) { } } - bool files = false; - for (int i = 0; i < argc; i++) { - if (streql("-", argv[i])) { - files = true; - cat_file(stdin, flags); - } else if (prefix("-", argv[i])) { - continue; - } else { - files = true; - struct stat s; - if (stat(argv[i], &s) < 0) { - printf("error: failed to read %s: %s\n", argv[i], strerror(errno)); - continue; - } - if (!S_ISREG(s.st_mode)) { - printf("error: %s is not a file\n", argv[i]); - continue; - } - FILE* in = get_file(argv[i], "r"); - cat_file(in, flags); - fclose(in); - } + int arg_len = argc - start; + + if (arg_len < 1) { + cat_file(stdin, flags); + return EXIT_SUCCESS; } - if (!files) { - cat_file(stdin, flags); + for (int i = start; i < argc; i++) { + FILE* in = get_file(argv[i], "r"); + cat_file(in, flags); + if (in != stdin) + fclose(in); } - + return EXIT_SUCCESS; } diff --git a/src/commands/ed.c b/src/commands/ed.c new file mode 100644 index 0000000..2082ef6 --- /dev/null +++ b/src/commands/ed.c @@ -0,0 +1,528 @@ +#include "../command.h" +#include "../util//regex.h" + +#include +#include +#include + +#define INPUT_LEN 1024 + +static char** lines = NULL; +static unsigned long line_capacity; +static unsigned long line_count; +static unsigned long line_current; +static bool pending_writes; + +enum LineAddressType { + INDEX, + RANGE, + SET, +}; + +struct LineAddress { + enum LineAddressType type; + union { + struct { + long int i; + } index; + struct { + long int a; + long int b; + } range; + struct { + long int* b; + unsigned long c; + unsigned long s; + } set; + } data; + bool empty; +}; + +enum RegexDirection { + BEFORE, + AFTER, + ALL +}; + +static bool parse_regex(char** end, struct LineAddress* address, enum RegexDirection dir) { + char c; + char* index = *end; + char* regex_str = index; + while(true) { + c = *(index++); + if (c == '\0') { + fprintf(stderr, "error: missing regex after %c\n", dir == BEFORE ? '?' : '/'); + return false; + } + if (c == (dir == BEFORE ? '?' : '/')) { + *(index - 1) = '\0'; + break; + } + } + unsigned long cap = 8; + unsigned long siz = 0; + long int* buf = malloc(cap * sizeof(unsigned long)); + + re_t regex = re_compile(regex_str); + unsigned long i = (dir == ALL ? 0 : line_current); + unsigned long until = (dir == BEFORE ? 0 : line_count - 1); + for (; (dir == BEFORE ? i >= until : i < until); dir == BEFORE ? i-- : i++) { + int len; + if (re_matchp(regex, lines[i], &len) == -1) { + if (dir == BEFORE && i == 0) break; + continue; + } + if (cap == siz) { + cap *= 2; + buf = realloc(buf, cap * sizeof(unsigned long)); + } + buf[siz] = i; + siz++; + if (dir == BEFORE && i == 0) break; + } + + address->type = SET; + address->data.set.s = siz; + address->data.set.c = cap; + address->data.set.b = buf; + *end = index; + return true; +} + +static bool read_address(char** command, bool whitespace, struct LineAddress* a) { + char* index = *command; + struct LineAddress address; + memset(&address, 0, sizeof(struct LineAddress)); + + address.empty = false; + if (strlen(*command) < 1) { + address.type = INDEX; + address.data.index.i = line_current + 1; + if (line_current >= line_count) line_current = line_count - 1; + *a = address; + return true; + } + + char* end_pre; + long int n_pre = strtol(index, &end_pre, 10) - 1; + if (end_pre == index) { + n_pre = -1; + } else { + if (n_pre < 0) { + fprintf(stderr, "error: input cannot be negative\n"); + return false; + } + index = end_pre; + } + + char pre = *(index++); + switch (pre) { + case '.': + address.type = INDEX; + address.data.index.i = line_current; + break; + case '$': + address.type = INDEX; + address.data.index.i = line_count - 1; + break; + case '-': + case '^': { + address.type = INDEX; + char* end; + long int n = strtol(index, &end, 10) - 1; + if (n < 0) { + fprintf(stderr, "error: input cannot be negative\n"); + return false; + } + if (index == end) { + address.data.index.i = line_current - 1; + } else { + address.data.index.i = line_current - n; + } + if (address.data.index.i < 0) { + fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + break; + } + case '+': { + address.type = INDEX; + char* end; + long int n = strtol(index, &end, 10) - 1; + if (n < 0) { + fprintf(stderr, "error: input cannot be negative\n"); + return false; + } + if (index == end) { + address.data.index.i = line_current + 1; + } else { + address.data.index.i = line_current + n; + } + if (address.data.index.i >= (long int) line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + break; + } + case '%': + address.type = RANGE; + address.data.range.a = 0; + address.data.range.b = line_count - 1; + break; + case ';': + address.type = RANGE; + address.data.range.a = line_current; + address.data.range.b = line_count - 1; + break; + case '/': + if (!parse_regex(&index, &address, AFTER)) return false; + break; + case '?': + if (!parse_regex(&index, &address, BEFORE)) return false; + break; + default: { + index--; + if (n_pre == -1) { + address.type = INDEX; + address.data.index.i = line_current; + address.empty = true; + break; + } else if (whitespace) { + address.type = INDEX; + address.data.index.i = line_current + n_pre; + } else { + address.type = INDEX; + address.data.index.i = n_pre; + } + if (address.data.index.i < 0 || address.data.index.i >= (long int) line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + } + } + *command = index; + *a = address; + return true; +} + +static void free_address (struct LineAddress address) { + if (address.type != SET) return; + free(address.data.set.b); +} + +static void free_data() { + if (lines == NULL) return; + for (unsigned long i = 0; i < line_count; i++) + free(lines[i]); + free(lines); + lines = NULL; +} + +static void load_empty() { + free_data(); + + line_capacity = 8; + lines = malloc(sizeof(char*) * line_capacity); + + line_count = 0; + line_current = 0; + pending_writes = false; +} + +static void get_input(FILE* file, char*** buffer, unsigned long* capacity, unsigned long* size) { + unsigned long cap = 8; + unsigned long siz = 0; + char** buf = malloc(sizeof(char*) * cap); + + char* line = NULL; + size_t offset = 0; + + clearerr(stdin); + while (getline(&line, &offset, file) != -1) { + if (cap == siz) { + cap *= 2; + buf = realloc(buf, sizeof(char*) * cap); + } + buf[siz] = line; + siz++; + line = NULL; + } + + free(line); + + *buffer = buf; + *capacity = cap; + *size = siz; +} + +int ed_getline(char *buf, size_t size) { + size_t i = 0; + int ch; + clearerr(stdin); + while ((ch = getchar()) != EOF) { // Read until EOF ... + if (i + 1 < size) { + buf[i++] = ch; + } + if (ch == '\n') { // ... or end of line + break; + } + } + buf[i] = '\0'; + if (i == 0) { + return EOF; + } + return i; +} + +static void load_file(FILE* file) { + free_data(); + line_current = 0; + get_input(file, &lines, &line_capacity, &line_count); + if (file != stdin) + fclose(file); +} + +static bool check_if_sure() { + if (!pending_writes) { + return true; + } + + printf("Do you really want to quit? "); + fflush(stdout); + + char buf[INPUT_LEN]; + if (ed_getline(buf, INPUT_LEN) == EOF) { + putchar('\n'); + return false; + } + + return prefix("y", buf); +} + +static bool skip_whitespace(char** index) { + char c; + bool w = false; + while (c = **index, c == ' ' || c == '\t') { (*index)++; w = true; } + return w; +} + +static void expand(unsigned long count) { + if (count < line_capacity) return; + line_capacity *= 2; + if (count > line_capacity) line_capacity = count; + lines = realloc(lines, line_capacity * sizeof(char*)); +} + +static void append_lines(unsigned long index, char** new, unsigned long new_len) { + if (new_len < 1) return; + pending_writes = true; + expand(line_count + new_len); + if (index + 1 <= line_count) + memmove(&lines[index+new_len], &lines[index], sizeof(char*) * (line_count - index)); + memcpy(&lines[index], new, sizeof(char*) * new_len); + line_count += new_len; +} + +static void delete_lines(unsigned long a, unsigned long b) { + if (b < a) return; + pending_writes = true; + for (unsigned long i = a; i <= b; i++) { + free(lines[i]); + } + if (b == line_count - 1) { + line_count = a; + return; + } + memmove(&lines[a], &lines[b+1], sizeof(char*) * (line_count - (b + 1))); + line_count -= (b - a) + 1; + line_current = a; + if (line_current >= line_count) line_current = line_count - 1; +} + +static bool handle_append(struct LineAddress* address) { + if (address->type != INDEX) { + fprintf(stderr, "error: append command requires index addressing\n"); + return false; + } + if (line_count == 0) { + address->data.index.i = -1; + } + char** buf; + unsigned long cap, size; + get_input(stdin, &buf, &cap, &size); + if (size > 0) { + append_lines(address->data.index.i + 1, buf, size); + } + line_current += size; + free(buf); + return true; +} + +static bool handle_delete(struct LineAddress* address) { + if (address->empty && address->data.index.i >= (long int) line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", address->data.index.i + 1); + return false; + } + if (address->type == INDEX) { + delete_lines(address->data.index.i, address->data.index.i); + } else if (address->type == RANGE) { + delete_lines(address->data.range.a, address->data.range.b); + } else if (address->type == SET) { + for (unsigned long i = 0; i < address->data.set.s; i++) { + delete_lines(address->data.set.b[i], address->data.set.b[i]); + } + } + return true; +} + +static void prompt() { + printf("%ld: ", line_current + 1); + fflush(stdout); + + char buf[INPUT_LEN]; + if (ed_getline(buf, INPUT_LEN) == EOF) { putchar('\n'); return; } + if (buf[0] == '\0') { putchar('\n'); return; } + + char* index = &buf[0]; + bool whitespace = skip_whitespace(&index); + + struct LineAddress address; + if (!read_address(&index, whitespace, &address)) return; + + char cmd = *(index++); + + if (cmd == ',') { + if (address.type != INDEX) { + fprintf(stderr, "error: comma range addressing requires two index addresses\n"); + free_address(address); + return; + } + struct LineAddress address2; + whitespace = skip_whitespace(&index); + if (!read_address(&index, whitespace, &address2)) { + free_address(address); + return; + } + if (address2.type != INDEX) { + fprintf(stderr, "error: comma range addressing requires two index addresses\n"); + free_address(address); + free_address(address2); + return; + } + address.type = RANGE; + address.data.range.a = address.data.index.i; // cursed + address.data.range.b = address2.data.index.i; + + cmd = *(index++); + } + + if (address.type == RANGE && address.data.range.a > address.data.range.b) { + fprintf(stderr, "error: range addressing must be in ascending order\n"); + free_address(address); + return; + } + + switch (cmd) { + case '\n': + case '\0': + if (address.empty) { + if (line_current == line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", line_current + 1); + break; + } else if (line_current + 1 == line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", line_current + 2); + break; + } else { + line_current++; + } + printf("%s", lines[line_current]); + break; + } + if (address.type == INDEX) { + line_current = address.data.index.i; + } else if (address.type == RANGE) { + line_current = address.data.range.b; + } else if (address.type == SET) { + fprintf(stderr, "error: unexpected range addressing\n"); + break; + } + printf("%s", lines[line_current]); + break; + case 'a': + handle_append(&address); + break; + case 'd': + handle_delete(&address); + break; + case 'c': + if (!handle_delete(&address)) { break; } + address.type = INDEX; + address.data.index.i = line_current - 1; + handle_append(&address); + break; + case 'p': + if (address.empty && address.data.index.i >= (long int) line_count) { + fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + break; + } + if (address.type == INDEX) { + printf("%s", lines[address.data.index.i]); + } else if (address.type == RANGE) { + for (long int i = address.data.range.a; i <= address.data.range.b; i++) { + printf("%s", lines[i]); + } + } else if (address.type == SET) { + for (unsigned long i = 0; i < address.data.set.s; i++) { + printf("%s", lines[address.data.set.b[i]]); + } + } + break; + case 'q': + if(check_if_sure()) { + free_address(address); + free_data(); + exit(EXIT_SUCCESS); + }; + break; + case 'Q': + free_address(address); + free_data(); + exit(EXIT_SUCCESS); + case 'g': + skip_whitespace(&index); + free_address(address); + if (*(index++) != '/') { + fprintf(stderr, "error: unexpected character at start or regex\n"); + break; + } + if (!parse_regex(&index, &address, ALL)) { return; } + for (unsigned long i = 0; i < address.data.set.s; i++) { + printf("%s", lines[address.data.set.b[i]]); + } + break; + default: + fprintf(stderr, "error: unimplemented command\n"); + break; + } + + free_address(address); + +} + +static void prompt_loop() { + while (true) { + prompt(); + } +} + +COMMAND(ed) { + if (argc < 1) { + load_empty(); + prompt_loop(); + } else { + FILE* file = get_file(argv[0], "r"); + load_file(file); + prompt_loop(); + } + return EXIT_SUCCESS; +} diff --git a/src/commands/ls.c b/src/commands/ls.c index b0b00a5..8d64f74 100644 --- a/src/commands/ls.c +++ b/src/commands/ls.c @@ -12,12 +12,14 @@ #include #include -#define FILE_COLOR "\x1b[0m" -#define DIR_COLOR "\x1b[1;34m" -#define SET_UID_COLOR "\x1b[41m" -#define SET_GID_COLOR "\x1b[43m" -#define EXEC_COLOR "\x1b[1;92m" -#define LINK_COLOR "\x1b[1;96m" +#define FILE_COLOR ANSCII BLACK COLOR +#define DIR_COLOR ANSCII BOLD NEXT NORMAL BLUE COLOR +#define DIR_COLOR_EXEC ANSCII BACKGROUND GREEN NEXT NORMAL BLACK COLOR +#define LINK_COLOR ANSCII BOLD NEXT HIGHLIGHT TURQUOISE COLOR +#define SET_UID_COLOR ANSCII BACKGROUND RED NEXT NORMAL WHITE COLOR +#define SET_GID_COLOR ANSCII BACKGROUND YELLOW NEXT NORMAL BLACK COLOR +#define EXEC_COLOR ANSCII BOLD NEXT NORMAL GREEN COLOR +#define BLK_COLOR ANSCII BOLD NEXT NORMAL YELLOW COLOR struct Flags { bool hidden; @@ -25,17 +27,17 @@ struct Flags { bool more_info; bool one_column; bool recurse; - bool colored; + enum When colored; }; struct FileInfo { - char mode[11]; - int links; struct passwd* usr; struct group* grp; - char size[5]; - char date[13]; char name[PATH_MAX]; + char date[13]; + char mode[11]; + char size[5]; + int links; bool set_uid; bool set_gid; bool exec; @@ -76,34 +78,59 @@ static void append_path(char buf[PATH_MAX], const char* dir_path, const char* fi buf[dir_len + file_len] = '\0'; } -static bool get_file_info(struct dirent* file, const char* dir_path, struct FileInfo* info) { +static bool get_file_info(const char* file_path, const char* dir_path, struct FileInfo* info) { uid_t uid = getuid(); gid_t gid = getgid(); char buf[PATH_MAX]; - append_path(buf, dir_path, file->d_name); + append_path(buf, dir_path, file_path); struct stat s; memset(&s, 0, sizeof(struct stat)); - if (stat(buf, &s) < 0) { - // printf("\x1b[0merror: failed to read file '%s': %s\n", buf, strerror(errno)); + if (lstat(buf, &s) < 0) { + printf("\x1b[0merror: failed to read file '%s': %s\n", buf, strerror(errno)); return false; } - + + int ty = (s.st_mode & S_IFMT) >> 12; + info->set_uid = false; info->set_gid = false; info->exec = false; info->name[0] = '\0'; - if (file->d_type == DT_DIR) - info->mode[0] = 'd'; - else if (file->d_type == DT_LNK) - info->mode[0] = 'l'; - else - info->mode[0] = '-'; - + switch (ty) { + case DT_BLK: + info->mode[0] = 'b'; + break; + case DT_CHR: + info->mode[0] = 'c'; + break; + case DT_DIR: + info->mode[0] = 'd'; + break; + case DT_FIFO: + info->mode[0] = 'f'; + break; + case DT_LNK: + info->mode[0] = 'l'; + break; + case DT_SOCK: + info->mode[0] = 's'; + break; + case DT_UNKNOWN: + info->mode[0] = 'u'; + break; + case DT_WHT: + info->mode[0] = 'w'; + break; + default: + info->mode[0] = '-'; + break; + } + info->mode[1] = (s.st_mode & S_IRUSR) ? 'r' : '-'; info->mode[2] = (s.st_mode & S_IWUSR) ? 'w' : '-'; if (s.st_mode & S_IXUSR) { @@ -146,21 +173,31 @@ static bool get_file_info(struct dirent* file, const char* dir_path, struct File info->usr = getpwuid(s.st_uid); info->grp = getgrgid(s.st_gid); info->links = s.st_nlink; - info->type = file->d_type; + info->type = ty; - strcpy(info->name, file->d_name); + strcpy(info->name, file_path); print_file_size(s.st_size, info->size); - print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000, info->date); + print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000000, info->date); return true; } static char* get_file_color(struct FileInfo* info) { char* color; if (info->type == DT_DIR) { - color = DIR_COLOR; + if (info->mode[8] == 'w') { + color = DIR_COLOR_EXEC; + } else { + color = DIR_COLOR; + } } else if (info->type == DT_LNK) { color = LINK_COLOR; + } else if ( + info->type == DT_CHR || + info->type == DT_BLK || + info->type == DT_SOCK + ) { + color = BLK_COLOR; } else { if (info->set_uid) { color = SET_UID_COLOR; @@ -181,7 +218,8 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo if (!isatty(1)) { flags->one_column = true; - flags->colored = false; + if (flags->colored == AUTO) + flags->colored = NO; } char* color; @@ -192,7 +230,7 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo struct FileInfo finfo = files[i]; color = get_file_color(&finfo); if (flags->more_info) { - printf("%s %*d %*s %*s %*s %s %s%s\x1b[0m", + printf("%s %*d %*s %*s %*s %s %s%s%s", finfo.mode, info.max_link, finfo.links, @@ -203,37 +241,40 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo info.max_size, finfo.size, finfo.date, - flags->colored ? color : FILE_COLOR, - finfo.name + flags->colored != NO ? color : "", + finfo.name, + flags->colored != NO ? "\x1b[0m" : "" ); if (finfo.type == DT_LNK) { char path[PATH_MAX]; append_path(path, dir_path, finfo.name); char lnk[PATH_MAX]; - if (readlink(path, lnk, PATH_MAX) < 0) { - putchar('\n'); + ssize_t n; + if ((n = readlink(path, lnk, PATH_MAX)) != -1) { + printf(" -> %.*s\n", (int)n, lnk); } else { - printf(" -> %s\n", lnk); + putchar('\n'); } } else { putchar('\n'); } } else if (flags->one_column) { - printf("%s%s\x1b[0m\n", color, finfo.name); + printf("%s%s%s\n", color, finfo.name, flags->colored != NO ? "\x1b[0m" : ""); } else { if (info.total_len > w.ws_col) { if (i != 0 && i % row_count == 0) putchar('\n'); - printf("%s%*s\x1b[0m", flags->colored ? color : FILE_COLOR, -column_width, finfo.name); + printf("%s%*s%s", flags->colored != NO ? color : "", -column_width, + finfo.name, flags->colored != NO ? "\x1b[0m" : ""); } else { - printf("%s%s\x1b[0m ", flags->colored ? color : FILE_COLOR, finfo.name); + printf("%s%s%s ", flags->colored != NO ? color : "", finfo.name, + flags->colored != NO ? "\x1b[0m" : ""); } } } - if (info.total_len <= w.ws_col) printf("\n"); - + if (!flags->more_info) printf("\n"); } static bool is_dot_dir(const char* path) { @@ -254,25 +295,24 @@ static void push_file( struct FileInfo** files, struct FileListInfo* info, int* size, int* capacity, - struct dirent* file, + const char* file_path, const char* dir_path ) { struct FileInfo finfo; - if (!get_file_info(file, dir_path, &finfo)) return; + if (!get_file_info(file_path, dir_path, &finfo)) return; if (*size == *capacity) { *capacity *= 2; *files = realloc(*files, sizeof(struct FileInfo) * *capacity); } - int user_len = strlen(finfo.usr->pw_name); if (user_len > info->max_usr) info->max_usr = user_len; int group_len = strlen(finfo.grp->gr_name); if (group_len > info->max_grp) info->max_grp = group_len; - int name_len = strlen(file->d_name); + int name_len = strlen(file_path); if (name_len > info->max_name) info->max_name = name_len; int size_len = strlen(finfo.size); @@ -307,10 +347,13 @@ static bool recurse_directory(char* path, struct Flags* flags) { if (!flags->hidden && prefix(".", file->d_name)) continue; if (is_dot_dir(file->d_name)) continue; if (first) { - printf("\n%s%s:%s\n", DIR_COLOR, path, FILE_COLOR); + if (flags->colored == NO) + printf("\n%s:\n", path); + else + printf("\n%s%s:%s\n", DIR_COLOR, path, FILE_COLOR); first = false; } - push_file(&files, &info, &size, &capacity, file, path); + push_file(&files, &info, &size, &capacity, file->d_name, path); } list_files(files, size, info, flags, path); @@ -355,16 +398,39 @@ static bool list_directory(char* path, struct Flags* flags) { while ((file = readdir(d)) != NULL) { if (!flags->hidden && prefix(".", file->d_name)) continue; if (flags->hide_dot && is_dot_dir(file->d_name)) continue; - push_file(&files, &info, &size, &capacity, file, path); + push_file(&files, &info, &size, &capacity, file->d_name, path); } - list_files(files, size, info, flags, path); + if (size > 0) list_files(files, size, info, flags, path); free(files); closedir(d); return true; } +static bool is_dir(const char* path) { + struct stat s; + if (stat(path, &s) < 0) return false; + return S_ISDIR(s.st_mode); +} + +static void list_file_args(int start, int argc, char** argv, struct Flags* flags) { + int capacity = 8; + int size = 0; + struct FileInfo* files = malloc(sizeof(struct FileInfo) * capacity); + struct FileListInfo info; + memset(&info, 0, sizeof(struct FileListInfo)); + + for (int i = start; i < argc; i++) { + if (is_dir(argv[i])) continue; + push_file((struct FileInfo**) &files, &info, &size, &capacity, argv[i], "."); + } + + if (size > 0) list_files(files, size, info, flags, "."); + + free(files); +} + static void help() { printf("Usage: ls [FILE]...\n\n"); printf("List directory contents\n\n"); @@ -384,15 +450,24 @@ COMMAND(ls) { flags.hide_dot = false; flags.one_column = false; flags.recurse = false; - flags.colored = false; + flags.colored = AUTO; int start = 0; for (int i = 0; i < argc; i++) { if (!prefix("-", argv[i])) break; if (streql("--help", argv[i])) help(); start++; - if (streql("--color=auto", argv[i]) || streql("--color=yes", argv[i])) { - flags.colored = true; + if (prefix("--color=", argv[i])) { + char* arg = argv[i] + 8; + if (streql("yes", arg) || streql("always", arg)) { + flags.colored = YES; + } else if (streql("auto", arg)) { + flags.colored = AUTO; + } else if (streql("no", arg) || streql("never", arg)) { + flags.colored = NO; + } else { + error("error: invalid color options: %s", arg); + } continue; } for (size_t j = 1; j < strlen(argv[i]); j++) { @@ -422,14 +497,19 @@ COMMAND(ls) { if (argc - start == 0) { list_directory(".", &flags); - if (!flags.more_info) printf("\n"); return EXIT_SUCCESS; } + list_file_args(start, argc, argv, &flags); + bool titled = argc - start > 1; for (int i = start; i < argc; i++) { + if (!is_dir(argv[i])) continue; if (titled && !flags.recurse) { - printf("\n%s%s:%s\n", DIR_COLOR, argv[i], FILE_COLOR); + if (flags.colored != NO) + printf("\n%s%s:%s\n", DIR_COLOR, argv[i], FILE_COLOR); + else + printf("\n%s:\n", argv[i]); } if (list_directory(argv[i], &flags) && i + 1 != argc) if (titled && !flags.recurse) printf("\n"); diff --git a/src/main.c b/src/main.c index 72e1531..cd9a81f 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,4 @@ -#include "shared.h" +#include "util/shared.h" #include "command.h" #include @@ -20,7 +20,7 @@ int main (ARGUMENTS) { if (argc < 2) { printf("usage: lazysphere [function [arguments]...]\n\n"); printf("currently defined functions:\n"); - printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head\n"); + printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head, ed\n"); return EXIT_SUCCESS; } argc--; @@ -54,6 +54,8 @@ int main (ARGUMENTS) { return tail(NEXT_ARGS); } else if (streql(cmd, "head")) { return head(NEXT_ARGS); + } else if (streql(cmd, "ed")) { + return ed(NEXT_ARGS); } else { error("error: invalid command %s", cmd); } diff --git a/src/util/regex.c b/src/util/regex.c new file mode 100644 index 0000000..58b1f54 --- /dev/null +++ b/src/util/regex.c @@ -0,0 +1,528 @@ +/* + * + * Mini regex-module inspired by Rob Pike's regex code described in: + * + * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html + * + * + * + * Supports: + * --------- + * '.' Dot, matches any character + * '^' Start anchor, matches beginning of string + * '$' End anchor, matches end of string + * '*' Asterisk, match zero or more (greedy) + * '+' Plus, match one or more (greedy) + * '?' Question, match zero or one (non-greedy) + * '[abc]' Character class, match if one of {'a', 'b', 'c'} + * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken! + * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } + * '\s' Whitespace, \t \f \r \n \v and spaces + * '\S' Non-whitespace + * '\w' Alphanumeric, [a-zA-Z0-9_] + * '\W' Non-alphanumeric + * '\d' Digits, [0-9] + * '\D' Non-digits + * + * + */ + + + +#include "regex.h" +#include +#include + +/* Definitions: */ + +#define MAX_REGEXP_OBJECTS 30 /* Max number of regex symbols in expression. */ +#define MAX_CHAR_CLASS_LEN 40 /* Max length of character-class buffer in. */ + + +enum { UNUSED, DOT, BEGIN, END, QUESTIONMARK, STAR, PLUS, CHAR, CHAR_CLASS, INV_CHAR_CLASS, DIGIT, NOT_DIGIT, ALPHA, NOT_ALPHA, WHITESPACE, NOT_WHITESPACE, /* BRANCH */ }; + +typedef struct regex_t +{ + unsigned char type; /* CHAR, STAR, etc. */ + union + { + unsigned char ch; /* the character itself */ + unsigned char* ccl; /* OR a pointer to characters in class */ + } u; +} regex_t; + + + +/* Private function declarations: */ +static int matchpattern(regex_t* pattern, const char* text, int* matchlength); +static int matchcharclass(char c, const char* str); +static int matchstar(regex_t p, regex_t* pattern, const char* text, int* matchlength); +static int matchplus(regex_t p, regex_t* pattern, const char* text, int* matchlength); +static int matchone(regex_t p, char c); +static int matchdigit(char c); +static int matchalpha(char c); +static int matchwhitespace(char c); +static int matchmetachar(char c, const char* str); +static int matchrange(char c, const char* str); +static int matchdot(char c); +static int ismetachar(char c); + + + +/* Public functions: */ +int re_match(const char* pattern, const char* text, int* matchlength) +{ + return re_matchp(re_compile(pattern), text, matchlength); +} + +int re_matchp(re_t pattern, const char* text, int* matchlength) +{ + *matchlength = 0; + if (pattern != 0) + { + if (pattern[0].type == BEGIN) + { + return ((matchpattern(&pattern[1], text, matchlength)) ? 0 : -1); + } + else + { + int idx = -1; + + do + { + idx += 1; + + if (matchpattern(pattern, text, matchlength)) + { + if (text[0] == '\0') + return -1; + + return idx; + } + } + while (*text++ != '\0'); + } + } + return -1; +} + +re_t re_compile(const char* pattern) +{ + /* The sizes of the two static arrays below substantiates the static RAM usage of this module. + MAX_REGEXP_OBJECTS is the max number of symbols in the expression. + MAX_CHAR_CLASS_LEN determines the size of buffer for chars in all char-classes in the expression. */ + static regex_t re_compiled[MAX_REGEXP_OBJECTS]; + static unsigned char ccl_buf[MAX_CHAR_CLASS_LEN]; + int ccl_bufidx = 1; + + char c; /* current char in pattern */ + int i = 0; /* index into pattern */ + int j = 0; /* index into re_compiled */ + + while (pattern[i] != '\0' && (j+1 < MAX_REGEXP_OBJECTS)) + { + c = pattern[i]; + + switch (c) + { + /* Meta-characters: */ + case '^': { re_compiled[j].type = BEGIN; } break; + case '$': { re_compiled[j].type = END; } break; + case '.': { re_compiled[j].type = DOT; } break; + case '*': { re_compiled[j].type = STAR; } break; + case '+': { re_compiled[j].type = PLUS; } break; + case '?': { re_compiled[j].type = QUESTIONMARK; } break; +/* case '|': { re_compiled[j].type = BRANCH; } break; <-- not working properly */ + + /* Escaped character-classes (\s \w ...): */ + case '\\': + { + if (pattern[i+1] != '\0') + { + /* Skip the escape-char '\\' */ + i += 1; + /* ... and check the next */ + switch (pattern[i]) + { + /* Meta-character: */ + case 'd': { re_compiled[j].type = DIGIT; } break; + case 'D': { re_compiled[j].type = NOT_DIGIT; } break; + case 'w': { re_compiled[j].type = ALPHA; } break; + case 'W': { re_compiled[j].type = NOT_ALPHA; } break; + case 's': { re_compiled[j].type = WHITESPACE; } break; + case 'S': { re_compiled[j].type = NOT_WHITESPACE; } break; + + /* Escaped character, e.g. '.' or '$' */ + default: + { + re_compiled[j].type = CHAR; + re_compiled[j].u.ch = pattern[i]; + } break; + } + } + /* '\\' as last char in pattern -> invalid regular expression. */ +/* + else + { + re_compiled[j].type = CHAR; + re_compiled[j].ch = pattern[i]; + } +*/ + } break; + + /* Character class: */ + case '[': + { + /* Remember where the char-buffer starts. */ + int buf_begin = ccl_bufidx; + + /* Look-ahead to determine if negated */ + if (pattern[i+1] == '^') + { + re_compiled[j].type = INV_CHAR_CLASS; + i += 1; /* Increment i to avoid including '^' in the char-buffer */ + if (pattern[i+1] == 0) /* incomplete pattern, missing non-zero char after '^' */ + { + return 0; + } + } + else + { + re_compiled[j].type = CHAR_CLASS; + } + + /* Copy characters inside [..] to buffer */ + while ( (pattern[++i] != ']') + && (pattern[i] != '\0')) /* Missing ] */ + { + if (pattern[i] == '\\') + { + if (ccl_bufidx >= MAX_CHAR_CLASS_LEN - 1) + { + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + if (pattern[i+1] == 0) /* incomplete pattern, missing non-zero char after '\\' */ + { + return 0; + } + ccl_buf[ccl_bufidx++] = pattern[i++]; + } + else if (ccl_bufidx >= MAX_CHAR_CLASS_LEN) + { + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + ccl_buf[ccl_bufidx++] = pattern[i]; + } + if (ccl_bufidx >= MAX_CHAR_CLASS_LEN) + { + /* Catches cases such as [00000000000000000000000000000000000000][ */ + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + /* Null-terminate string end */ + ccl_buf[ccl_bufidx++] = 0; + re_compiled[j].u.ccl = &ccl_buf[buf_begin]; + } break; + + /* Other characters: */ + default: + { + re_compiled[j].type = CHAR; + re_compiled[j].u.ch = c; + } break; + } + /* no buffer-out-of-bounds access on invalid patterns - see https://github.com/kokke/tiny-regex-c/commit/1a279e04014b70b0695fba559a7c05d55e6ee90b */ + if (pattern[i] == 0) + { + return 0; + } + + i += 1; + j += 1; + } + /* 'UNUSED' is a sentinel used to indicate end-of-pattern */ + re_compiled[j].type = UNUSED; + + return (re_t) re_compiled; +} + +void re_print(regex_t* pattern) +{ + const char* types[] = { "UNUSED", "DOT", "BEGIN", "END", "QUESTIONMARK", "STAR", "PLUS", "CHAR", "CHAR_CLASS", "INV_CHAR_CLASS", "DIGIT", "NOT_DIGIT", "ALPHA", "NOT_ALPHA", "WHITESPACE", "NOT_WHITESPACE", "BRANCH" }; + + int i; + int j; + char c; + for (i = 0; i < MAX_REGEXP_OBJECTS; ++i) + { + if (pattern[i].type == UNUSED) + { + break; + } + + printf("type: %s", types[pattern[i].type]); + if (pattern[i].type == CHAR_CLASS || pattern[i].type == INV_CHAR_CLASS) + { + printf(" ["); + for (j = 0; j < MAX_CHAR_CLASS_LEN; ++j) + { + c = pattern[i].u.ccl[j]; + if ((c == '\0') || (c == ']')) + { + break; + } + printf("%c", c); + } + printf("]"); + } + else if (pattern[i].type == CHAR) + { + printf(" '%c'", pattern[i].u.ch); + } + printf("\n"); + } +} + + + +/* Private functions: */ +static int matchdigit(char c) +{ + return isdigit(c); +} +static int matchalpha(char c) +{ + return isalpha(c); +} +static int matchwhitespace(char c) +{ + return isspace(c); +} +static int matchalphanum(char c) +{ + return ((c == '_') || matchalpha(c) || matchdigit(c)); +} +static int matchrange(char c, const char* str) +{ + return ( (c != '-') + && (str[0] != '\0') + && (str[0] != '-') + && (str[1] == '-') + && (str[2] != '\0') + && ( (c >= str[0]) + && (c <= str[2]))); +} +static int matchdot(char c) +{ +#if defined(RE_DOT_MATCHES_NEWLINE) && (RE_DOT_MATCHES_NEWLINE == 1) + (void)c; + return 1; +#else + return c != '\n' && c != '\r'; +#endif +} +static int ismetachar(char c) +{ + return ((c == 's') || (c == 'S') || (c == 'w') || (c == 'W') || (c == 'd') || (c == 'D')); +} + +static int matchmetachar(char c, const char* str) +{ + switch (str[0]) + { + case 'd': return matchdigit(c); + case 'D': return !matchdigit(c); + case 'w': return matchalphanum(c); + case 'W': return !matchalphanum(c); + case 's': return matchwhitespace(c); + case 'S': return !matchwhitespace(c); + default: return (c == str[0]); + } +} + +static int matchcharclass(char c, const char* str) +{ + do + { + if (matchrange(c, str)) + { + return 1; + } + else if (str[0] == '\\') + { + /* Escape-char: increment str-ptr and match on next char */ + str += 1; + if (matchmetachar(c, str)) + { + return 1; + } + else if ((c == str[0]) && !ismetachar(c)) + { + return 1; + } + } + else if (c == str[0]) + { + if (c == '-') + { + return ((str[-1] == '\0') || (str[1] == '\0')); + } + else + { + return 1; + } + } + } + while (*str++ != '\0'); + + return 0; +} + +static int matchone(regex_t p, char c) +{ + switch (p.type) + { + case DOT: return matchdot(c); + case CHAR_CLASS: return matchcharclass(c, (const char*)p.u.ccl); + case INV_CHAR_CLASS: return !matchcharclass(c, (const char*)p.u.ccl); + case DIGIT: return matchdigit(c); + case NOT_DIGIT: return !matchdigit(c); + case ALPHA: return matchalphanum(c); + case NOT_ALPHA: return !matchalphanum(c); + case WHITESPACE: return matchwhitespace(c); + case NOT_WHITESPACE: return !matchwhitespace(c); + default: return (p.u.ch == c); + } +} + +static int matchstar(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + int prelen = *matchlength; + const char* prepoint = text; + while ((text[0] != '\0') && matchone(p, *text)) + { + text++; + (*matchlength)++; + } + while (text >= prepoint) + { + if (matchpattern(pattern, text--, matchlength)) + return 1; + (*matchlength)--; + } + + *matchlength = prelen; + return 0; +} + +static int matchplus(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + const char* prepoint = text; + while ((text[0] != '\0') && matchone(p, *text)) + { + text++; + (*matchlength)++; + } + while (text > prepoint) + { + if (matchpattern(pattern, text--, matchlength)) + return 1; + (*matchlength)--; + } + + return 0; +} + +static int matchquestion(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + if (p.type == UNUSED) + return 1; + if (matchpattern(pattern, text, matchlength)) + return 1; + if (*text && matchone(p, *text++)) + { + if (matchpattern(pattern, text, matchlength)) + { + (*matchlength)++; + return 1; + } + } + return 0; +} + + +#if 0 + +/* Recursive matching */ +static int matchpattern(regex_t* pattern, const char* text, int *matchlength) +{ + int pre = *matchlength; + if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK)) + { + return matchquestion(pattern[1], &pattern[2], text, matchlength); + } + else if (pattern[1].type == STAR) + { + return matchstar(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == PLUS) + { + return matchplus(pattern[0], &pattern[2], text, matchlength); + } + else if ((pattern[0].type == END) && pattern[1].type == UNUSED) + { + return text[0] == '\0'; + } + else if ((text[0] != '\0') && matchone(pattern[0], text[0])) + { + (*matchlength)++; + return matchpattern(&pattern[1], text+1); + } + else + { + *matchlength = pre; + return 0; + } +} + +#else + +/* Iterative matching */ +static int matchpattern(regex_t* pattern, const char* text, int* matchlength) +{ + int pre = *matchlength; + do + { + if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK)) + { + return matchquestion(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == STAR) + { + return matchstar(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == PLUS) + { + return matchplus(pattern[0], &pattern[2], text, matchlength); + } + else if ((pattern[0].type == END) && pattern[1].type == UNUSED) + { + return (text[0] == '\0'); + } +/* Branching is not working properly + else if (pattern[1].type == BRANCH) + { + return (matchpattern(pattern, text) || matchpattern(&pattern[2], text)); + } +*/ + (*matchlength)++; + } + while ((text[0] != '\0') && matchone(*pattern++, *text++)); + + *matchlength = pre; + return 0; +} + +#endif diff --git a/src/util/regex.h b/src/util/regex.h new file mode 100644 index 0000000..69facc6 --- /dev/null +++ b/src/util/regex.h @@ -0,0 +1,65 @@ +/* + * + * Mini regex-module inspired by Rob Pike's regex code described in: + * + * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html + * + * + * + * Supports: + * --------- + * '.' Dot, matches any character + * '^' Start anchor, matches beginning of string + * '$' End anchor, matches end of string + * '*' Asterisk, match zero or more (greedy) + * '+' Plus, match one or more (greedy) + * '?' Question, match zero or one (non-greedy) + * '[abc]' Character class, match if one of {'a', 'b', 'c'} + * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken! + * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } + * '\s' Whitespace, \t \f \r \n \v and spaces + * '\S' Non-whitespace + * '\w' Alphanumeric, [a-zA-Z0-9_] + * '\W' Non-alphanumeric + * '\d' Digits, [0-9] + * '\D' Non-digits + * + * + */ + +#ifndef _TINY_REGEX_C +#define _TINY_REGEX_C + + +#ifndef RE_DOT_MATCHES_NEWLINE +/* Define to 0 if you DON'T want '.' to match '\r' + '\n' */ +#define RE_DOT_MATCHES_NEWLINE 1 +#endif + +#ifdef __cplusplus +extern "C"{ +#endif + + + +/* Typedef'd pointer to get abstract datatype. */ +typedef struct regex_t* re_t; + + +/* Compile regex string pattern to a regex_t-array. */ +re_t re_compile(const char* pattern); + + +/* Find matches of the compiled pattern inside text. */ +int re_matchp(re_t pattern, const char* text, int* matchlength); + + +/* Find matches of the txt pattern inside text (will compile automatically first). */ +int re_match(const char* pattern, const char* text, int* matchlength); + + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef _TINY_REGEX_C */ diff --git a/src/shared.c b/src/util/shared.c similarity index 90% rename from src/shared.c rename to src/util/shared.c index 240e364..c5c22e9 100644 --- a/src/shared.c +++ b/src/util/shared.c @@ -18,15 +18,19 @@ void error(const char* format, ...) { FILE* get_file_s(const char* path, const char* type) { struct stat s; - if (stat(path, &s) < 0) { + if (lstat(path, &s) < 0) { + if (type[0] != 'r') goto read; fprintf(stderr, "error: failed to read %s: %s\n", path, strerror(errno)); return NULL; } - if (!S_ISREG(s.st_mode)) { - fprintf(stderr, "error: %s is not a file\n", path); + if (S_ISDIR(s.st_mode)) { + fprintf(stderr, "error: %s is a directory\n", path); return NULL; } - FILE* file = fopen(path, type); + + FILE* file; +read: + file = fopen(path, type); if (file == NULL) { fprintf(stderr, "error: failed to open file %s: %s\n", path, strerror(errno)); } @@ -34,7 +38,10 @@ FILE* get_file_s(const char* path, const char* type) { } FILE* get_file(const char* path, const char* type) { - if (streql("-", path)) return stdin; + if (streql("-", path) && type[0] == 'r') { + clearerr(stdin); + return stdin; + } FILE* file = get_file_s(path, type); if (file == NULL) exit(EXIT_FAILURE); return file; diff --git a/src/shared.h b/src/util/shared.h similarity index 51% rename from src/shared.h rename to src/util/shared.h index a542ba6..5e5f4a9 100644 --- a/src/shared.h +++ b/src/util/shared.h @@ -4,6 +4,33 @@ #include #include +#define ANSCII "\x1b[" +#define NEXT ";" + +#define RESET "0" +#define BOLD "1" + +#define NORMAL "3" +#define BACKGROUND "4" +#define HIGHLIGHT "9" + +#define BLACK "0" +#define RED "1" +#define GREEN "2" +#define YELLOW "3" +#define BLUE "4" +#define MAGENTA "5" +#define TURQUOISE "6" +#define WHITE "7" + +#define COLOR "m" + +enum When { + YES, + NO, + AUTO +}; + __attribute__ ((__format__(printf, 1, 2))) void error(const char* format, ...);