90s

Minimalist, customizable shell written in C99 with syntax highlighting
git clone https://codeberg.org/night0721/90s
Log | Files | Refs | README | LICENSE

commit 4c47f02034318ea9f134a0d02e852026422ae1d8
parent 28891a6cebb26b794fe6e5ce079f1c15982d2f42
Author: night0721 <[email protected]>
Date:   Thu,  1 Feb 2024 19:02:30 +0000

handle signals, rename functions, history command, qol features, fix highlight highlighting full command

Diffstat:
MMakefile | 4++--
MREADME.md | 6+++---
Mcolor.c | 2+-
Acommands.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommands.h | 8++++++++
Mconstants.h | 4++--
Mhistory.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Mhistory.h | 1+
Mrush.c | 165+++++++++++++++++++++++++------------------------------------------------------
9 files changed, 240 insertions(+), 122 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,4 +1,4 @@ -rush: rush.c color.c constants.h history.c - gcc -o rush rush.c color.c history.c +rush: rush.c color.c constants.h history.c commands.c + gcc -o rush rush.c color.c history.c commands.c all: rush diff --git a/README.md b/README.md @@ -17,15 +17,15 @@ $ ./rush # Features - Showing current time and directory with custom color -- syntax highlighting on valid commands using ANSI colors -- history navigation using up and down keys +- Syntax highlighting on valid commands using ANSI colors +- History navigation using up and down keys with history command +- Built in commands # Todo Features - Pipe - stdin, stdout, stderr redirect - background jobs - editing using left and right arrow keys -- history command - export command to setenv - tab completion diff --git a/color.c b/color.c @@ -11,7 +11,7 @@ void color_text(char str[], const char *color) { } char *buf = malloc(size); if (buf == NULL) { - fprintf(stderr, "rush: Memory allocation failed\n"); + fprintf(stderr, "rush: Error allocating memory\n"); exit(EXIT_FAILURE); } diff --git a/commands.c b/commands.c @@ -0,0 +1,126 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> +#include <errno.h> +#include <sys/wait.h> + +#include "commands.h" +#include "history.h" + +// Function declarations for builtin commands +int cd(char **args); +int help(char **args); +int quit(char **args); +int history(char **args); + +// List of builtin commands' names +char *builtin_cmds[] = { + "cd", + "help", + "exit", + "history" +}; + +int (*builtin_func[]) (char **) = { + &cd, + &help, + &quit, // cant name it exit as it is taken + &history +}; + +// number of built in commands +int num_builtins() { + return sizeof(builtin_cmds) / sizeof(char *); +} + +// change directory +int cd(char **args) { + if (args[1] == NULL) { + char *home = getenv("HOME"); + if (chdir(home) != 0) { + perror("rush"); + } + } else { + if (chdir(args[1]) != 0) { + perror("rush"); + } + } + return 1; +} + +// show help menu +int help(char **args) { + printf("rush v0.01-alpha\n"); + printf("Built in commands:\n"); + + for (int i = 0; i < num_builtins(); i++) { + printf(" %s\n", builtin_cmds[i]); + } + + printf("Use 'man' to read manual of programs\n"); + printf("Licensed under GPL v3\n"); + return 1; +} + +int quit(char **args) { + return 0; // exit prompting loop, which also the shell +} + +int history(char **args) { + char **history = get_all_history(); + + for (int i = 0; history[i] != NULL; ++i) { + printf("%s\n", history[i]); + free(history[i]); + } + + free(history); + return 1; +} + +bool is_builtin(char *command) { + for (int i = 0; i < num_builtins(); i++) { + if (strcmp(command, builtin_cmds[i]) == 0) { + return true; + } + } + return false; +} + +// execute built in commands or launch commands and wait it to terminate, return 1 to keep shell running +int execute(char **args) { + if (args[0] == NULL) { // An empty command was entered. + return 1; + } + + for (int i = 0; i < num_builtins(); i++) { + if (strcmp(args[0], builtin_cmds[i]) == 0) { + return (*builtin_func[i])(args); + } + } + + pid_t pid, wpid; + int status; + + pid = fork(); + if (pid == 0) { + // Child process + if (execvp(args[0], args) == -1) { + if (errno == ENOENT) { + fprintf(stderr, "rush: command not found: %s\n", args[0]); + } + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + perror("Cannot fork"); + } else { + // Parent process + do { + wpid = waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + return 1; +} diff --git a/commands.h b/commands.h @@ -0,0 +1,8 @@ +#ifndef COMMANDS_H_ +#define COMMANDS_H_ + +int num_builtins(); +bool is_builtin(char *command); +int execute(char **args); + +#endif diff --git a/constants.h b/constants.h @@ -3,7 +3,7 @@ #define HISTFILE ".rush_history" // history file name #define TOK_BUFSIZE 64 // buffer size of each token -#define RL_BUFSIZE 1024 +#define RL_BUFSIZE 1024 // size of each command #define TOK_DELIM " \t\r\n\a" // delimiter for token - +#define MAX_HISTORY 8192 // maximum lines of reading history #endif diff --git a/history.c b/history.c @@ -2,6 +2,7 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <limits.h> #include "constants.h" @@ -89,3 +90,48 @@ char *read_command(int direction) { fclose(history_file); return last_nlf + 1; // return the string from the new line feed } + +int is_duplicate(char **history, int line_count, char *line) { + for (int i = 0; i < line_count; ++i) { + if (strcmp(history[i], line) == 0) { + return 1; + } + } + return 0; +} + +char **get_all_history() { + history_file = fopen(histfile_path, "r"); + if (history_file == NULL) { + fprintf(stderr, "rush: Error opening history file\n"); + exit(EXIT_FAILURE); + } + + char **history = malloc(MAX_HISTORY * sizeof(char*)); + if (history == NULL) { + fprintf(stderr, "rush: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + char buffer[RL_BUFSIZE]; // Adjust the buffer size as needed + int line_count = 0; + + while (fgets(buffer, sizeof(buffer), history_file) != NULL) { + buffer[strcspn(buffer, "\n")] = '\0'; + if (!is_duplicate(history, line_count, buffer)) { + history[line_count] = strdup(buffer); + if (history[line_count] == NULL) { + fprintf(stderr, "Error allocating memory\n"); + exit(EXIT_FAILURE); + } + line_count++; + if (line_count >= MAX_HISTORY) { + fprintf(stderr, "Maximum number of lines reached.\n"); + exit(EXIT_FAILURE); + } + } + } + + fclose(history_file); + history[line_count] = NULL; + return history; +} diff --git a/history.h b/history.h @@ -4,5 +4,6 @@ void save_command_history(char *command); void check_history_file(); char *read_command(int direction); +char **get_all_history(); #endif diff --git a/rush.c b/rush.c @@ -1,4 +1,3 @@ -#include <sys/wait.h> #include <termios.h> #include <unistd.h> #include <stdlib.h> @@ -7,112 +6,14 @@ #include <limits.h> #include <time.h> #include <stdbool.h> -#include <errno.h> #include "color.h" #include "constants.h" #include "history.h" +#include "commands.h" -/* - * Function Declarations for builtin shell commands: - */ -int rush_cd(char **args); -int help(char **args); -int rush_exit(char **args); - - -/* - * List of builtin commands, followed by their corresponding functions. - */ -char *builtin_str[] = { - "cd", - "help", - "exit" -}; - -int (*builtin_func[]) (char **) = { - &rush_cd, - &help, - &rush_exit -}; - -int rush_num_builtins() { - return sizeof(builtin_str) / sizeof(char *); -} - -// change directory -int rush_cd(char **args) { - if (args[1] == NULL) { - char *home = getenv("HOME"); - if (chdir(home) != 0) { - perror("rush"); - } - } else { - if (chdir(args[1]) != 0) { - perror("rush"); - } - } - return 1; -} - -// show help menu -int help(char **args) { - printf("rush v0.01-alpha\n"); - printf("Built in commands:\n"); - - for (int i = 0; i < rush_num_builtins(); i++) { - printf(" %s\n", builtin_str[i]); - } - - printf("Use 'man' to read manual of programs\n"); - printf("Licensed under GPL v3\n"); - return 1; -} - -int rush_exit(char **args) { - return 0; // exit prompting loop, which also the shell -} - -// launch program and wait it to terminate, return 1 to continue running -int rush_launch(char **args) { - pid_t pid, wpid; - int status; - - pid = fork(); - if (pid == 0) { - // Child process - if (execvp(args[0], args) == -1) { - if (errno == ENOENT) { - fprintf(stderr, "rush: command not found: %s\n", args[0]); - } - } - exit(EXIT_FAILURE); - } else if (pid < 0) { - perror("Cannot fork"); - } else { - // Parent process - do { - wpid = waitpid(pid, &status, WUNTRACED); - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - } - - return 1; -} - -// execute built in commands or launch commands, return 1 to keep shell running -int rush_execute(char **args) { - if (args[0] == NULL) { - // An empty command was entered. - return 1; - } - - for (int i = 0; i < rush_num_builtins(); i++) { - if (strcmp(args[0], builtin_str[i]) == 0) { - return (*builtin_func[i])(args); - } - } - - return rush_launch(args); +void quit_sig(int sig) { + return 0; } void change_terminal_attribute(int option) { @@ -165,20 +66,24 @@ bool find_command(char **paths, char *command) { if (access(current_path, X_OK) == 0) { // command is executable return true; + } else { + if (is_builtin(command)) { + return true; + } } paths++; } return false; } -char *rush_read_line(char **paths) { +char *readline(char **paths) { int bufsize = RL_BUFSIZE; int position = 0; char *buffer = malloc(sizeof(char) * bufsize); int c; if (!buffer) { - fprintf(stderr, "rush: allocation error\n"); + fprintf(stderr, "rush: Error allocating memory\n"); exit(EXIT_FAILURE); } @@ -186,6 +91,7 @@ char *rush_read_line(char **paths) { while (1) { c = getchar(); // read a character int buf_len = strlen(buffer); + //printf("buflen %i\n", buf_len); if (buf_len > 0) { printf("\033[%dD", strlen(buffer)); // move cursor to the beginning printf("\033[K"); // clear line to the right of cursor @@ -240,21 +146,45 @@ char *rush_read_line(char **paths) { } } char *cmd_part = strchr(buffer, ' '); + char *command_without_arg = NULL; + int cmd_len = 0; bool valid; + if (cmd_part != NULL) { - char *cmd = malloc(sizeof(char) * (cmd_part - buffer + 1)); + cmd_len = cmd_part - buffer; + char *cmd = malloc(sizeof(char) * cmd_len + 1); + command_without_arg = malloc(sizeof(char) * cmd_len + 1); + if (cmd == NULL || command_without_arg == NULL) { + fprintf(stderr, "rush: Error allocating memory\n"); + exit(EXIT_FAILURE); + } for (int i = 0; i < (cmd_part - buffer); i++) { cmd[i] = buffer[i]; } - cmd[cmd_part - buffer] = '\0'; + strcpy(command_without_arg, cmd); + cmd[cmd_len] = '\0'; + command_without_arg[cmd_len] = '\0'; valid = find_command(paths, cmd); } else { valid = find_command(paths, buffer); } + if (valid) { - printf("\x1b[38;2;000;255;000m%s\x1b[0m", buffer); // print green as valid command + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command + } } else { - printf("\x1b[38;2;255;000;000m%s\x1b[0m", buffer); // print red as sinvalid command + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as sinvalid command + } } fflush(stdout); @@ -263,7 +193,7 @@ char *rush_read_line(char **paths) { bufsize += RL_BUFSIZE; buffer = realloc(buffer, bufsize); if (!buffer) { - fprintf(stderr, "rush: allocation error\n"); + fprintf(stderr, "rush: Error allocating memory\n"); exit(EXIT_FAILURE); } } @@ -271,7 +201,7 @@ char *rush_read_line(char **paths) { } // split line into arguments -char **rush_split_line(char *line) { +char **argsplit(char *line) { int bufsize = TOK_BUFSIZE, position = 0; char **tokens = malloc(bufsize * sizeof(char*)); char *token; @@ -297,6 +227,11 @@ char **rush_split_line(char *line) { token = strtok(NULL, TOK_DELIM); } + // makes ls and diff have color without user typing it + if (strcmp(tokens[0], "ls") == 0 || strcmp(tokens[0], "diff") == 0) { + tokens[position] = "--color=auto"; + } + position++; tokens[position] = NULL; return tokens; } @@ -329,9 +264,9 @@ void command_loop(char **paths) { printf("%s %s %s ", time, cwd, arrow); - line = rush_read_line(paths); - args = rush_split_line(line); - status = rush_execute(args); + line = readline(paths); + args = argsplit(line); + status = execute(args); free(line); free(args); @@ -339,9 +274,11 @@ void command_loop(char **paths) { }; } - int main(int argc, char **argv) { // setup + signal(SIGINT, quit_sig); + signal(SIGTERM, quit_sig); + signal(SIGQUIT, quit_sig); check_history_file(); char **paths = setup_path_variable(); change_terminal_attribute(1); // turn off echoing and disabling getchar requires pressing enter key to return