From af129a72f2798d8c472502f88661548bb4511102 Mon Sep 17 00:00:00 2001 From: "Andrew D. France" Date: Mon, 7 Jul 2025 09:49:01 -0500 Subject: [PATCH] feat: Add Lua 5.3 compatibility and fix script argument handling --- .gitignore | 20 +- .gitmodules | 7 + install | 77 ++ lib/compat53 | 1 + premake5.lua | 33 +- src/lush.c | 2849 +++++++++++++++++++++--------------------- test/compat_test.lua | 60 + 7 files changed, 1606 insertions(+), 1441 deletions(-) create mode 100644 .gitmodules create mode 100755 install create mode 160000 lib/compat53 create mode 100644 test/compat_test.lua diff --git a/.gitignore b/.gitignore index a861fad..20ed6ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,19 @@ +/bin/ +/obj/ Makefile *.make -bin -obj -.cache + + +*.log + + +.cache/ + + +.vscode/ + + +.DS_Store +Thumbs.db + +build-tracer.lisp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8e625f2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "lib/compat53"] + path = lib/compat53 + url = https://github.com/lunarmodules/lua-compat-5.3.git +[submodule "lib/hashmap"] + path = lib/hashmap + url = https://github.com/tidwall/hashmap.c.git + diff --git a/install b/install new file mode 100755 index 0000000..ebdbaeb --- /dev/null +++ b/install @@ -0,0 +1,77 @@ +#!/bin/sh + +# Function to install packages using apt (Debian/Ubuntu) +install_with_apt() { + sudo apt-get update + sudo apt-get install -y lua5.4 liblua5.4-dev +} + +# Function to install packages using yum (Red Hat/CentOS) +install_with_yum() { + sudo yum install -y epel-release + sudo yum install -y lua lua-devel +} + +# New package manager used in Fedora +install_with_dnf() { + sudo dnf install -y lua lua-devel +} + +# Function to install packages using pacman (Arch Linux) +install_with_pacman() { + sudo pacman -Sy --noconfirm lua +} + +if [ -f /etc/arch-release ]; then + install_with_pacman +elif [ -f /etc/debian_version ]; then + install_with_apt +elif [ -f /etc/redhat-release ] || [ -f /etc/centos-release ]; then + if command -v dnf >/dev/null 2>&1; then + install_with_dnf + else + install_with_yum + fi +else + echo "Your linux distro is not supported currently." + echo "You need to manually install these packages: lua and your distro's lua dev package" +fi + +PREMAKE_VERSION="5.0.0-beta2" +OS="linux" + +echo "downloading premake $PREMAKE_VERSION" +wget -q https://github.com/premake/premake-core/releases/download/v${PREMAKE_VERSION}/premake-${PREMAKE_VERSION}-${OS}.tar.gz -O premake.tar.gz +echo "extracting premake" +tar -xzf premake.tar.gz +echo "installing premake" +sudo mv premake5 example.so libluasocket.so /usr/bin +sudo chmod +x /usr/bin/premake5 +rm premake.tar.gz + +premake5 gmake +make +if [ ! -d ~/.lush ]; then + cp -rf ./.lush ~/ +fi + +# always update example +cp -f ./.lush/scripts/example.lua ~/.lush/scripts/example.lua + +# Install the new shell binary to a temporary location +sudo cp ./bin/Debug/lush/lush /usr/bin/lush.new + +# Atomically replace the old binary +sudo mv /usr/bin/lush.new /usr/bin/lush + +# Ensure the shell is registered in /etc/shells +if ! grep -Fxq "/usr/bin/lush" /etc/shells; then + echo "/usr/bin/lush" | sudo tee -a /etc/shells >/dev/null +fi + +# Optionally change the shell +chsh -s /usr/bin/lush + +echo "=====================" +echo "INSTALLATION FINISHED" +echo "=====================" diff --git a/lib/compat53 b/lib/compat53 new file mode 160000 index 0000000..dfd83b4 --- /dev/null +++ b/lib/compat53 @@ -0,0 +1 @@ +Subproject commit dfd83b4930c8b85fd39d208523f7293cf469b205 diff --git a/premake5.lua b/premake5.lua index 037cfca..a112127 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,33 +1,34 @@ workspace("lush") configurations({ "Debug", "Release" }) --- lush project project("lush") kind("ConsoleApp") language("C") targetdir("bin/%{cfg.buildcfg}/lush") local lua_inc_path = "/usr/include" -local lua_lib_path = "/usr/lib" +links({ "lua5.4" }) -if os.findlib("lua5.4") then - lua_inc_path = "/usr/include/lua5.4" - lua_lib_path = "/usr/lib/5.4" - links({ "lua5.4" }) -else - links({ "lua" }) -end +-- Add all necessary include directories +includedirs({ + lua_inc_path, + "lib/hashmap", + "lib/compat53/c-api" +}) -includedirs({ lua_inc_path, "lib/hashmap" }) -libdirs({ lua_lib_path }) +libdirs({ "/usr/lib" }) +-- Compile all necessary source files, including the compat library files({ - "src/**.h", - "src/**.c", - "lib/hashmap/**.h", - "lib/hashmap/**.c", + "src/**.h", + "src/**.c", + "lib/hashmap/hashmap.h", + "lib/hashmap/hashmap.c", + "lib/compat53/c-api/compat-5.3.h", + "lib/compat53/c-api/compat-5.3.c" }) -defines({ 'LUSH_VERSION="0.3.2"' }) + +defines({ 'LUSH_VERSION="0.3.2"', 'COMPAT53_PREFIX=""' }) filter("configurations:Debug") defines({ "DEBUG" }) diff --git a/src/lush.c b/src/lush.c index 1c42139..a262144 100644 --- a/src/lush.c +++ b/src/lush.c @@ -14,15 +14,22 @@ THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ - #include "lush.h" #include "hashmap.h" -#include "lauxlib.h" -#include "lua.h" #include "lua_api.h" -#include "lualib.h" -#include -#include +#include +#include +#include + + +/* + * Added a "forward declaration" for the function. This tells the compiler + * that the function exists and what it looks like, satisfying the + * implicit declaration rule when it's called later in main(). +*/ +int luaopen_compat53 (lua_State *L); + +/* Now, include all other necessary system headers */ #include #include #include @@ -46,18 +53,18 @@ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. #define MAX_GLOB 512 typedef enum { - OP_PIPE = 1, // | - OP_AND, // && - OP_OR, // || - OP_SEMICOLON, // ; - OP_BACKGROUND, // & - OP_REDIRECT_STDOUT, // 1> or > - OP_REDIRECT_STDERR, // 2> - OP_REDIRECT_BOTH, // &> - OP_APPEND_STDOUT, // 1>> or >> - OP_APPEND_STDERR, // 2>> - OP_APPEND_BOTH, // &>> - OP_OTHER // All other operators like parentheses, braces, etc. + OP_PIPE = 1, // | + OP_AND, // && + OP_OR, // || + OP_SEMICOLON, // ; + OP_BACKGROUND, // & + OP_REDIRECT_STDOUT, // 1> or > + OP_REDIRECT_STDERR, // 2> + OP_REDIRECT_BOTH, // &> + OP_APPEND_STDOUT, // 1>> or >> + OP_APPEND_STDERR, // 2>> + OP_APPEND_BOTH, // &>> + OP_OTHER // All other operators like parentheses, braces, etc. } OperatorType; // initialize prompt format @@ -67,12 +74,12 @@ char *prompt_format = NULL; hashmap_t *aliases = NULL; void lush_add_alias(const char *alias, const char *command) { - // make a new map if one doesnt exist - if (aliases == NULL) { - aliases = hm_new_hashmap(); - } + // make a new map if one doesnt exist + if (aliases == NULL) { + aliases = hm_new_hashmap(); + } - hm_set(aliases, (char *)alias, (char *)command); + hm_set(aliases, (char *)alias, (char *)command); } char *lush_get_alias(char *alias) { return hm_get(aliases, alias); } @@ -80,1541 +87,1539 @@ char *lush_get_alias(char *alias) { return hm_get(aliases, alias); } // -- shell utility -- static void set_raw_mode(struct termios *orig_termios) { - struct termios raw; - tcgetattr(STDIN_FILENO, orig_termios); - raw = *orig_termios; - raw.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &raw); + struct termios raw; + tcgetattr(STDIN_FILENO, orig_termios); + raw = *orig_termios; + raw.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &raw); } static void reset_terminal_mode(struct termios *orig_termios) { - tcsetattr(STDIN_FILENO, TCSANOW, orig_termios); + tcsetattr(STDIN_FILENO, TCSANOW, orig_termios); } static int get_terminal_width() { - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) { - perror("ioctl"); - return -1; - } - return w.ws_col; + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) { + perror("ioctl"); + return -1; + } + return w.ws_col; } // -- prompt helper functions -- static size_t get_prompt_size(const char *format, const char *username, - const char *hostname, const char *cwd) { - size_t prompt_len = 0; - - while (*format) { - if (strncmp(format, "%u", 2) == 0) { - prompt_len += strlen(username); - format += 2; - } else if (strncmp(format, "%h", 2) == 0) { - prompt_len += strlen(hostname); - format += 2; - } else if (strncmp(format, "%w", 2) == 0) { - prompt_len += strlen(cwd); - format += 2; - } else if (strncmp(format, "%t", 2) == 0) { - prompt_len += 8; // size of time format - format += 2; - } else if (strncmp(format, "%d", 2) == 0) { - prompt_len += 10; - format += 2; - } else { - prompt_len++; - format++; - } - } - return prompt_len; + const char *hostname, const char *cwd) { + size_t prompt_len = 0; + + while (*format) { + if (strncmp(format, "%u", 2) == 0) { + prompt_len += strlen(username); + format += 2; + } else if (strncmp(format, "%h", 2) == 0) { + prompt_len += strlen(hostname); + format += 2; + } else if (strncmp(format, "%w", 2) == 0) { + prompt_len += strlen(cwd); + format += 2; + } else if (strncmp(format, "%t", 2) == 0) { + prompt_len += 8; // size of time format + format += 2; + } else if (strncmp(format, "%d", 2) == 0) { + prompt_len += 10; + format += 2; + } else { + prompt_len++; + format++; + } + } + return prompt_len; } static char *format_prompt_string(const char *input, const char *username, - const char *hostname, const char *cwd) { - // Calculate the size of the new string - size_t new_size = get_prompt_size(input, username, hostname, cwd) + 1; - - // Allocate memory for the new string - char *result = (char *)malloc(new_size); - if (!result) { - perror("malloc failed"); - return NULL; - } - - // Get the current time - time_t current_time; - time(¤t_time); - struct tm *local_time = localtime(¤t_time); - - // Replace placeholders in the input string and build the result string - char *dest = result; - while (*input) { - if (strncmp(input, "%u", 2) == 0) { - strcpy(dest, username); - dest += strlen(username); - input += 2; - } else if (strncmp(input, "%h", 2) == 0) { - strcpy(dest, hostname); - dest += strlen(hostname); - input += 2; - } else if (strncmp(input, "%w", 2) == 0) { - strcpy(dest, cwd); - dest += strlen(cwd); - input += 2; - } else if (strncmp(input, "%t", 2) == 0) { - // Format the time as HH:MM:SS - char time_string[9]; // HH:MM:SS is 8 characters + null terminator - strftime(time_string, sizeof(time_string), "%H:%M:%S", local_time); - strcpy(dest, time_string); - dest += strlen(time_string); - input += 2; - } else if (strncmp(input, "%d", 2) == 0) { - // Format the date as mm/dd/yyyy - char date_string[36]; - snprintf(date_string, 36, "%02d/%02d/%04d", local_time->tm_mon + 1, - local_time->tm_mday, local_time->tm_year + 1900); - strcpy(dest, date_string); - dest += strlen(date_string); - input += 2; - } else { - *dest++ = *input++; - } - } - - *dest = '\0'; // Null-terminate the result string - return result; + const char *hostname, const char *cwd) { + // Calculate the size of the new string + size_t new_size = get_prompt_size(input, username, hostname, cwd) + 1; + + // Allocate memory for the new string + char *result = (char *)malloc(new_size); + if (!result) { + perror("malloc failed"); + return NULL; + } + + // Get the current time + time_t current_time; + time(¤t_time); + struct tm *local_time = localtime(¤t_time); + + // Replace placeholders in the input string and build the result string + char *dest = result; + while (*input) { + if (strncmp(input, "%u", 2) == 0) { + strcpy(dest, username); + dest += strlen(username); + input += 2; + } else if (strncmp(input, "%h", 2) == 0) { + strcpy(dest, hostname); + dest += strlen(hostname); + input += 2; + } else if (strncmp(input, "%w", 2) == 0) { + strcpy(dest, cwd); + dest += strlen(cwd); + input += 2; + } else if (strncmp(input, "%t", 2) == 0) { + // Format the time as HH:MM:SS + char time_string[9]; // HH:MM:SS is 8 characters + null terminator + strftime(time_string, sizeof(time_string), "%H:%M:%S", local_time); + strcpy(dest, time_string); + dest += strlen(time_string); + input += 2; + } else if (strncmp(input, "%d", 2) == 0) { + // Format the date as mm/dd/yyyy + char date_string[36]; + snprintf(date_string, 36, "%02d/%02d/%04d", local_time->tm_mon + 1, + local_time->tm_mday, local_time->tm_year + 1900); + strcpy(dest, date_string); + dest += strlen(date_string); + input += 2; + } else { + *dest++ = *input++; + } + } + + *dest = '\0'; // Null-terminate the result string + return result; } static char *get_prompt() { - char *username = getenv("USER"); - char hostname[256]; - gethostname(hostname, sizeof(hostname)); - char *cwd = getcwd(NULL, 0); - - // Replace /home/ with ~ - char *home_prefix = "/home/"; - size_t home_len = strlen(home_prefix) + strlen(username); - char *prompt_cwd; - if (strncmp(cwd, home_prefix, strlen(home_prefix)) == 0 && - strncmp(cwd + strlen(home_prefix), username, strlen(username)) == 0) { - prompt_cwd = malloc(strlen(cwd) - home_len + - 2); // 1 for ~ and 1 for null terminator - snprintf(prompt_cwd, strlen(cwd) - home_len + 2, "~%s", cwd + home_len); - } else { - prompt_cwd = strdup(cwd); - } - - // get the prompt if no format in init.lua - if (prompt_format == NULL) { - size_t prompt_len = - strlen(prompt_cwd) + strlen(username) + strlen(hostname) + 6; - char *prompt = (char *)malloc(prompt_len); - snprintf(prompt, prompt_len, "[%s@%s:%s]", username, hostname, - prompt_cwd); - free(cwd); - return prompt; - } - - // get formatted prompt - char *prompt = - format_prompt_string(prompt_format, username, hostname, prompt_cwd); - - free(cwd); - return prompt; + char *username = getenv("USER"); + char hostname[256]; + gethostname(hostname, sizeof(hostname)); + char *cwd = getcwd(NULL, 0); + + // Replace /home/ with ~ + char *home_prefix = "/home/"; + size_t home_len = strlen(home_prefix) + strlen(username); + char *prompt_cwd; + if (strncmp(cwd, home_prefix, strlen(home_prefix)) == 0 && + strncmp(cwd + strlen(home_prefix), username, strlen(username)) == 0) { + prompt_cwd = malloc(strlen(cwd) - home_len + + 2); // 1 for ~ and 1 for null terminator + snprintf(prompt_cwd, strlen(cwd) - home_len + 2, "~%s", cwd + home_len); + } else { + prompt_cwd = strdup(cwd); + } + + // get the prompt if no format in init.lua + if (prompt_format == NULL) { + size_t prompt_len = + strlen(prompt_cwd) + strlen(username) + strlen(hostname) + 6; + char *prompt = (char *)malloc(prompt_len); + snprintf(prompt, prompt_len, "[%s@%s:%s]", username, hostname, + prompt_cwd); + free(cwd); + free(prompt_cwd); + return prompt; + } + + // get formatted prompt + char *prompt = + format_prompt_string(prompt_format, username, hostname, prompt_cwd); + + free(cwd); + free(prompt_cwd); + return prompt; } static size_t get_stripped_length(const char *str) { - size_t len = 0; - size_t i = 0; - - // Set the locale to properly handle UTF-8 multi-byte characters - setlocale(LC_CTYPE, ""); - - while (str[i] != '\0') { - if (str[i] == '\033' && str[i + 1] == '[') { - // Skip over the escape sequence - while (str[i] != 'm' && str[i] != '\0') { - i++; - } - // Include 'm' character to end the escape sequence - if (str[i] == 'm') { - i++; - } - } else if (str[i] == '\n') { - len = 0; - i++; - } else { - // Calculate the length of the current multi-byte character - int char_len = mblen(&str[i], MB_CUR_MAX); - if (char_len < 1) { - char_len = 1; // Fallback in case of errors - } - len++; - i += char_len; - } - } - - return len; + size_t len = 0; + size_t i = 0; + + // Set the locale to properly handle UTF-8 multi-byte characters + setlocale(LC_CTYPE, ""); + + while (str[i] != '\0') { + if (str[i] == '\033' && str[i + 1] == '[') { + // Skip over the escape sequence + while (str[i] != 'm' && str[i] != '\0') { + i++; + } + // Include 'm' character to end the escape sequence + if (str[i] == 'm') { + i++; + } + } else if (str[i] == '\n') { + len = 0; + i++; + } else { + // Calculate the length of the current multi-byte character + int char_len = mblen(&str[i], MB_CUR_MAX); + if (char_len < 1) { + char_len = 1; // Fallback in case of errors + } + len++; + i += char_len; + } + } + + return len; } // physical refers to the number of character spaces it takes up static size_t get_physical_length(const char *str) { - size_t len = 0; - size_t i = 0; - - // Set the locale to properly handle UTF-8 multi-byte characters - setlocale(LC_CTYPE, ""); - - while (str[i] != '\0') { - if (str[i] == '\033' && str[i + 1] == '[') { - // Skip over the escape sequence - while (str[i] != 'm' && str[i] != '\0') { - i++; - } - // Include 'm' character to end the escape sequence - if (str[i] == 'm') { - i++; - } - } else { - // Calculate the length of the current multi-byte character - int char_len = mblen(&str[i], MB_CUR_MAX); - if (char_len < 1) { - char_len = 1; // Fallback in case of errors - } - len++; - i += char_len; - } - } - - return len; + size_t len = 0; + size_t i = 0; + + // Set the locale to properly handle UTF-8 multi-byte characters + setlocale(LC_CTYPE, ""); + + while (str[i] != '\0') { + if (str[i] == '\033' && str[i + 1] == '[') { + // Skip over the escape sequence + while (str[i] != 'm' && str[i] != '\0') { + i++; + } + // Include 'm' character to end the escape sequence + if (str[i] == 'm') { + i++; + } + } else { + // Calculate the length of the current multi-byte character + int char_len = mblen(&str[i], MB_CUR_MAX); + if (char_len < 1) { + char_len = 1; // Fallback in case of errors + } + len++; + i += char_len; + } + } + + return len; } static char *get_stripped_prompt(const char *str) { - size_t i = 0, j = 0; - char *clean_str = (char *)malloc(strlen(str) + 1); - - if (clean_str == NULL) { - perror("malloc failed"); - exit(EXIT_FAILURE); - } - - // Set the locale to properly handle UTF-8 multi-byte characters - setlocale(LC_CTYPE, ""); - - while (str[i] != '\0') { - if (str[i] == '\033' && str[i + 1] == '[') { - // Skip over the escape sequence - while (str[i] != 'm' && str[i] != '\0') { - i++; - } - // Skip the 'm' character to end the escape sequence - if (str[i] == 'm') { - i++; - } - } else { - // Calculate the length of the current multi-byte character - int char_len = mblen(&str[i], MB_CUR_MAX); - if (char_len < 1) { - char_len = 1; // Fallback in case of errors - } - - // Copy the non-escape sequence character(s) to the clean string - for (int k = 0; k < char_len; k++) { - clean_str[j++] = str[i++]; - } - } - } - - // Null-terminate the clean string - clean_str[j] = '\0'; - - return clean_str; + size_t i = 0, j = 0; + char *clean_str = (char *)malloc(strlen(str) + 1); + + if (clean_str == NULL) { + perror("malloc failed"); + exit(EXIT_FAILURE); + } + + // Set the locale to properly handle UTF-8 multi-byte characters + setlocale(LC_CTYPE, ""); + + while (str[i] != '\0') { + if (str[i] == '\033' && str[i + 1] == '[') { + // Skip over the escape sequence + while (str[i] != 'm' && str[i] != '\0') { + i++; + } + // Skip the 'm' character to end the escape sequence + if (str[i] == 'm') { + i++; + } + } else { + // Calculate the length of the current multi-byte character + int char_len = mblen(&str[i], MB_CUR_MAX); + if (char_len < 1) { + char_len = 1; // Fallback in case of errors + } + + // Copy the non-escape sequence character(s) to the clean string + for (int k = 0; k < char_len; k++) { + clean_str[j++] = str[i++]; + } + } + } + + // Null-terminate the clean string + clean_str[j] = '\0'; + + return clean_str; } static int get_prompt_newlines(const char *prompt) { - int newlines = 0; - int width = get_terminal_width(); - size_t lines = 0; - size_t capacity = 2; - char *stripped_prompt = get_stripped_prompt(prompt); - int *line_widths = (int *)malloc(capacity * sizeof(int)); - - if (line_widths == NULL) { - perror("malloc failed"); - exit(EXIT_FAILURE); - } - - setlocale(LC_CTYPE, ""); - - size_t i = 0, j = 0; - while (i < strlen(stripped_prompt)) { - int char_len = mblen(&stripped_prompt[i], MB_CUR_MAX); - if (char_len < 1) - char_len = 1; - - if (stripped_prompt[i] == '\n') { - if (i % width != width - 1) - newlines++; - - if (lines >= capacity) { - capacity *= 2; - line_widths = realloc(line_widths, capacity * sizeof(int)); - if (line_widths == NULL) { - perror("realloc failed"); - exit(EXIT_FAILURE); - } - } - line_widths[lines++] = j; - j = 0; - } - j++; - i += char_len; - } - - // also account for if the terminal width causes wrapping - for (size_t k = 0; k < lines; k++) { - newlines += (line_widths[k]) / width; - if ((line_widths[k] % width) == 0) - newlines--; - } - - // Total number of lines is newlines plus the number of wrapped lines - free(line_widths); - free(stripped_prompt); - return newlines; + int newlines = 0; + int width = get_terminal_width(); + size_t lines = 0; + size_t capacity = 2; + char *stripped_prompt = get_stripped_prompt(prompt); + int *line_widths = (int *)malloc(capacity * sizeof(int)); + + if (line_widths == NULL) { + perror("malloc failed"); + exit(EXIT_FAILURE); + } + + setlocale(LC_CTYPE, ""); + + size_t i = 0, j = 0; + while (i < strlen(stripped_prompt)) { + int char_len = mblen(&stripped_prompt[i], MB_CUR_MAX); + if (char_len < 1) + char_len = 1; + + if (stripped_prompt[i] == '\n') { + if (i % width != width - 1) + newlines++; + + if (lines >= capacity) { + capacity *= 2; + line_widths = realloc(line_widths, capacity * sizeof(int)); + if (line_widths == NULL) { + perror("realloc failed"); + exit(EXIT_FAILURE); + } + } + line_widths[lines++] = j; + j = 0; + } + j++; + i += char_len; + } + + // also account for if the terminal width causes wrapping + for (size_t k = 0; k < lines; k++) { + newlines += (line_widths[k]) / width; + if ((line_widths[k] % width) == 0) + newlines--; + } + + // Total number of lines is newlines plus the number of wrapped lines + free(line_widths); + free(stripped_prompt); + return newlines; } // -- autocomplete -- static int alphebetize_strings(const void *a, const void *b) { - return strcmp(*(const char **)a, *(const char **)b); + return strcmp(*(const char **)a, *(const char **)b); } static char **get_suggestions(size_t *count, const char *path) { - DIR *dir; - struct dirent *entry; - size_t capacity = 10; - size_t size = 0; - - // Allocate initial space for the array of strings - char **items = malloc(capacity * sizeof(char *)); - if (items == NULL) { - perror("Unable to allocate memory"); - exit(EXIT_FAILURE); - } - - // Open the current directory - dir = opendir(path); - if (dir == NULL) { - dir = opendir("."); - if (dir == NULL) { - perror("Unable to open current directory"); - free(items); - exit(EXIT_FAILURE); - } - } - - // Read each directory entry - while ((entry = readdir(dir)) != NULL) { - // Allocate space for each filename - if (size >= capacity) { - capacity *= 2; - items = realloc(items, capacity * sizeof(char *)); - if (items == NULL) { - perror("Unable to reallocate memory"); - closedir(dir); - exit(EXIT_FAILURE); - } - } - - // Copy the entry name to the array - items[size] = strdup(entry->d_name); - if (items[size] == NULL) { - perror("Unable to allocate memory for entry"); - closedir(dir); - exit(EXIT_FAILURE); - } - size++; - } - - // Close the directory - closedir(dir); - - // Set the count to the number of items found - *count = size; - - // Reallocate the array to the exact size needed - items = realloc(items, size * sizeof(char *)); - - qsort(items, size, sizeof(char *), alphebetize_strings); - - return items; + DIR *dir; + struct dirent *entry; + size_t capacity = 10; + size_t size = 0; + + // Allocate initial space for the array of strings + char **items = malloc(capacity * sizeof(char *)); + if (items == NULL) { + perror("Unable to allocate memory"); + exit(EXIT_FAILURE); + } + + // Open the current directory + dir = opendir(path); + if (dir == NULL) { + dir = opendir("."); + if (dir == NULL) { + perror("Unable to open current directory"); + free(items); + exit(EXIT_FAILURE); + } + } + + // Read each directory entry + while ((entry = readdir(dir)) != NULL) { + // Allocate space for each filename + if (size >= capacity) { + capacity *= 2; + items = realloc(items, capacity * sizeof(char *)); + if (items == NULL) { + perror("Unable to reallocate memory"); + closedir(dir); + exit(EXIT_FAILURE); + } + } + + // Copy the entry name to the array + items[size] = strdup(entry->d_name); + if (items[size] == NULL) { + perror("Unable to allocate memory for entry"); + closedir(dir); + exit(EXIT_FAILURE); + } + size++; + } + + // Close the directory + closedir(dir); + + // Set the count to the number of items found + *count = size; + + // Reallocate the array to the exact size needed + items = realloc(items, size * sizeof(char *)); + + qsort(items, size, sizeof(char *), alphebetize_strings); + + return items; } static const char *suggestion_difference(const char *input, - const char *suggestion) { - size_t input_len = strlen(input); - size_t suggestion_len = strlen(suggestion); + const char *suggestion) { + size_t input_len = strlen(input); + size_t suggestion_len = strlen(suggestion); - if (input_len <= suggestion_len && - strncmp(input, suggestion, input_len) == 0) { - return &suggestion[input_len]; - } + if (input_len <= suggestion_len && + strncmp(input, suggestion, input_len) == 0) { + return &suggestion[input_len]; + } - return NULL; + return NULL; } static void free_suggestions(char **suggestions, int count) { - for (size_t i = 0; i < count; i++) { - free(suggestions[i]); - } + for (size_t i = 0; i < count; i++) { + free(suggestions[i]); + } - free(suggestions); + free(suggestions); } static const char *find_suggestion(const char *input, char **suggestions, - size_t count) { - if (strlen(input) == 0) - return NULL; - - for (size_t i = 0; i < count; i++) { - if (strncmp(input, suggestions[i], strlen(input)) == 0) { - const char *suggestion = - suggestion_difference(input, suggestions[i]); - return suggestion; - } - } - - return NULL; + size_t count) { + if (strlen(input) == 0) + return NULL; + + for (size_t i = 0; i < count; i++) { + if (strncmp(input, suggestions[i], strlen(input)) == 0) { + const char *suggestion = + suggestion_difference(input, suggestions[i]); + return suggestion; + } + } + + return NULL; } static const char *get_current_token(const char *input) { - const char *last_space = strrchr(input, ' '); + const char *last_space = strrchr(input, ' '); - if (last_space == NULL) - return input; + if (last_space == NULL) + return input; - // return the substring after the space - return last_space + 1; + // return the substring after the space + return last_space + 1; } static const char *get_current_word(const char *input) { - const char *current_token = get_current_token(input); - const char *last_slash = strrchr(current_token, '/'); + const char *current_token = get_current_token(input); + const char *last_slash = strrchr(current_token, '/'); - if (last_slash == NULL) - return current_token; + if (last_slash == NULL) + return current_token; - return last_slash + 1; + return last_slash + 1; } char *get_suggestions_path(const char *str) { - if (str == NULL) { - return strdup("./"); - } + if (str == NULL) { + return strdup("./"); + } - const char *last_slash = strrchr(str, '/'); + const char *last_slash = strrchr(str, '/'); - if (last_slash == NULL) { - return strdup("./"); - } + if (last_slash == NULL) { + return strdup("./"); + } - size_t length = last_slash - str; + size_t length = last_slash - str; - // Allocate memory for the substring and copy it - char *result = (char *)malloc(length + 3); - if (result == NULL) { - perror("malloc failed"); - exit(EXIT_FAILURE); - } + // Allocate memory for the substring and copy it + char *result = (char *)malloc(length + 3); + if (result == NULL) { + perror("malloc failed"); + exit(EXIT_FAILURE); + } - strncpy(result, "./", 2); - result += 2; - strncpy(result, str, length); - result -= 2; - result[length + 2] = '\0'; + strncpy(result, "./", 2); + result += 2; + strncpy(result, str, length); + result -= 2; + result[length + 2] = '\0'; - return result; + return result; } // -- shell buffer handling -- static void reprint_buffer(char *buffer, int *last_lines, int *pos, - int history_pos) { - static size_t old_buffer_len = 0; - char suggestion[PATH_MAX] = {0}; - char *prompt = get_prompt(); - int width = get_terminal_width(); - int prompt_newlines = get_prompt_newlines(prompt); - size_t prompt_length = get_stripped_length(prompt); - // handle history before doing calculations - if (history_pos >= 0) { - char *history_line = lush_get_past_command(history_pos); - if (history_line != NULL) { - strncpy(buffer, history_line, BUFFER_SIZE); - free(history_line); - // remove newline from buffer - buffer[strlen(buffer) - 1] = '\0'; - *pos = strlen(buffer); - } - } - - // handle autocomplete before doing calculations as well - size_t suggestions_count = 0; - const char *current_token = get_current_token(buffer); - char *suggestions_path = get_suggestions_path(current_token); - const char *current_word = get_current_word(buffer); - char **suggestions = get_suggestions(&suggestions_count, suggestions_path); - const char *autocomplete_suggestion = - find_suggestion(current_word, suggestions, suggestions_count); - - if (autocomplete_suggestion != NULL) { - strncpy(suggestion, autocomplete_suggestion, PATH_MAX); - } - - // free(suggestions_path); - - int num_lines = ((strlen(buffer) + prompt_length + 1) / width) + 1; - int cursor_pos = (prompt_length + *pos + 1) % width; - int cursor_line = (prompt_length + *pos + 1) / width + 1; - // move cursor down if it is up a number of lines first - if (num_lines - cursor_line > 0) { - printf("\033[%dB", num_lines - cursor_line); - // compensate for if we have just filled a line - if ((strlen(buffer) + prompt_length + 1) % width == 0 && - old_buffer_len < strlen(buffer)) { - printf("\033[A"); - } - // compense for if we have just emptied a line - if ((strlen(buffer) + prompt_length + 1) % width == width - 1 && - old_buffer_len > strlen(buffer)) { - printf("\033[B"); - } - } - - if (old_buffer_len < strlen(buffer) || history_pos >= 0) { - for (int i = 0; i < *last_lines; i++) { - printf("\r\033[K"); - if (i > 0) - printf("\033[A"); - } - } else { - for (int i = 0; i < num_lines; i++) { - printf("\r\033[K"); - if (i > 0) - printf("\033[A"); - } - } - *last_lines = num_lines; - - // if the prompt has new lines move up - if (prompt_newlines > 0) { - printf("\033[%dA", prompt_newlines); - } - - // ensure line is cleared before printing - printf("\r\033[K"); - printf("%s ", prompt); - printf("%s", buffer); - if (suggestion_enable) - printf("\033[0;33m%s\033[0m ", suggestion); - - // move cursor up and to the right to be in correct position - if (cursor_pos > 0) - printf("\r\033[%dC", cursor_pos); - else - printf("\r"); - - if (num_lines > 1 && (num_lines - cursor_line) > 0) - printf("\033[%dA", num_lines - cursor_line); - - // if autocomplete goes to new line move up - int suggested_lines = - ((strlen(buffer) + strlen(suggestion) + prompt_length + 1) / width) + 1; - if (suggested_lines > num_lines) - printf("\033[A"); - - // cleanup - free(prompt); - old_buffer_len = strlen(buffer); - suggestion[0] = '\0'; - free_suggestions(suggestions, suggestions_count); + int history_pos) { + static size_t old_buffer_len = 0; + char suggestion[PATH_MAX] = {0}; + char *prompt = get_prompt(); + int width = get_terminal_width(); + int prompt_newlines = get_prompt_newlines(prompt); + size_t prompt_length = get_stripped_length(prompt); + // handle history before doing calculations + if (history_pos >= 0) { + char *history_line = lush_get_past_command(history_pos); + if (history_line != NULL) { + strncpy(buffer, history_line, BUFFER_SIZE); + free(history_line); + // remove newline from buffer + buffer[strlen(buffer) - 1] = '\0'; + *pos = strlen(buffer); + } + } + + // handle autocomplete before doing calculations as well + size_t suggestions_count = 0; + const char *current_token = get_current_token(buffer); + char *suggestions_path = get_suggestions_path(current_token); + const char *current_word = get_current_word(buffer); + char **suggestions = get_suggestions(&suggestions_count, suggestions_path); + const char *autocomplete_suggestion = + find_suggestion(current_word, suggestions, suggestions_count); + + if (autocomplete_suggestion != NULL) { + strncpy(suggestion, autocomplete_suggestion, PATH_MAX); + } + + free(suggestions_path); + + int num_lines = ((strlen(buffer) + prompt_length + 1) / width) + 1; + int cursor_pos = (prompt_length + *pos + 1) % width; + int cursor_line = (prompt_length + *pos + 1) / width + 1; + // move cursor down if it is up a number of lines first + if (num_lines - cursor_line > 0) { + printf("\033[%dB", num_lines - cursor_line); + // compensate for if we have just filled a line + if ((strlen(buffer) + prompt_length + 1) % width == 0 && + old_buffer_len < strlen(buffer)) { + printf("\033[A"); + } + // compense for if we have just emptied a line + if ((strlen(buffer) + prompt_length + 1) % width == width - 1 && + old_buffer_len > strlen(buffer)) { + printf("\033[B"); + } + } + + if (old_buffer_len < strlen(buffer) || history_pos >= 0) { + for (int i = 0; i < *last_lines; i++) { + printf("\r\033[K"); + if (i > 0) + printf("\033[A"); + } + } else { + for (int i = 0; i < num_lines; i++) { + printf("\r\033[K"); + if (i > 0) + printf("\033[A"); + } + } + *last_lines = num_lines; + + // if the prompt has new lines move up + if (prompt_newlines > 0) { + printf("\033[%dA", prompt_newlines); + } + + // ensure line is cleared before printing + printf("\r\033[K"); + printf("%s ", prompt); + printf("%s", buffer); + if (suggestion_enable) + printf("\033[0;33m%s\033[0m ", suggestion); + + // move cursor up and to the right to be in correct position + if (cursor_pos > 0) + printf("\r\033[%dC", cursor_pos); + else + printf("\r"); + + if (num_lines > 1 && (num_lines - cursor_line) > 0) + printf("\033[%dA", num_lines - cursor_line); + + // if autocomplete goes to new line move up + int suggested_lines = + ((strlen(buffer) + strlen(suggestion) + prompt_length + 1) / width) + 1; + if (suggested_lines > num_lines) + printf("\033[A"); + + // cleanup + free(prompt); + old_buffer_len = strlen(buffer); + suggestion[0] = '\0'; + free_suggestions(suggestions, suggestions_count); } char *lush_read_line() { - struct termios orig_termios; - char *buffer = (char *)calloc(BUFFER_SIZE, sizeof(char)); - int pos = 0; - int history_pos = -1; - int last_lines = 1; - char last_command; - int c; - - // init buffer and make raw mode - set_raw_mode(&orig_termios); - - while (true) { - c = getchar(); - - if (c == '\033') { // escape sequence - getchar(); // skip [ - switch (getchar()) { - case 'A': // up arrow - reprint_buffer(buffer, &last_lines, &pos, ++history_pos); - break; - case 'B': // down arrow - reprint_buffer(buffer, &last_lines, &pos, --history_pos); - if (history_pos < 0) - history_pos = 0; - break; - case 'C': // right arrow - { - int width = get_terminal_width(); - char *prompt = get_prompt(); - size_t prompt_length = get_stripped_length(prompt); - if ((prompt_length + pos) % width == width - 2) { - printf("\033[B"); - } - if (pos < strlen(buffer)) { - pos++; - // if modifying text reset history - history_pos = -1; - reprint_buffer(buffer, &last_lines, &pos, history_pos); - } - free(prompt); - } break; - case 'D': // left arrow - { - int width = get_terminal_width(); - char *prompt = get_prompt(); - size_t prompt_length = get_stripped_length(prompt); - if ((prompt_length + pos) % width == width - 1) { - printf("\033[A"); - } - - if (pos > 0) { - pos--; - // if modifying text reset history - history_pos = -1; - reprint_buffer(buffer, &last_lines, &pos, history_pos); - } - free(prompt); - } break; - case '3': // delete - if (getchar() == '~') { - if (pos < strlen(buffer)) { - memmove(&buffer[pos], &buffer[pos + 1], - strlen(&buffer[pos + 1]) + 1); - // if modifying text reset history - history_pos = -1; - reprint_buffer(buffer, &last_lines, &pos, history_pos); - } - } - break; - default: - break; - } - } else if (c == '\177') { // backspace - if (pos > 0) { - memmove(&buffer[pos - 1], &buffer[pos], - strlen(&buffer[pos]) + 1); - pos--; - // handle edge case where cursor should be moved up - int width = get_terminal_width(); - char *prompt = get_prompt(); - size_t prompt_length = get_stripped_length(prompt); - if ((prompt_length + pos + 1) % width == width - 1 && - pos <= strlen(buffer)) { - printf("\033[A"); - } - // if modifying text reset history - history_pos = -1; - reprint_buffer(buffer, &last_lines, &pos, history_pos); - } - } else if (c == '\t') { - char suggestion[PATH_MAX]; - size_t suggestions_count = 0; - const char *current_token = get_current_token(buffer); - char *suggestions_path = get_suggestions_path(current_token); - const char *current_word = get_current_word(buffer); - char **suggestions = - get_suggestions(&suggestions_count, suggestions_path); - const char *autocomplete_suggestion = - find_suggestion(current_word, suggestions, suggestions_count); - - if (autocomplete_suggestion != NULL) { - size_t suggestion_len = strlen(autocomplete_suggestion); - size_t current_word_len = strlen(current_word); - - // Insert the suggestion in place of the current word - memmove(&buffer[pos + suggestion_len], &buffer[pos], - strlen(&buffer[pos]) + 1); - strncpy(&buffer[pos], autocomplete_suggestion, suggestion_len); - - // Move the cursor forward - pos += suggestion_len; - } - - reprint_buffer(buffer, &last_lines, &pos, history_pos); - free(suggestions_path); - free_suggestions(suggestions, suggestions_count); - - } else if (c == '\n') { - // if modifying text reset history - history_pos = -1; - pos = strlen(buffer); - reprint_buffer(buffer, &last_lines, &pos, history_pos); - break; // submit the command - } else { - if (pos < BUFFER_SIZE - 1) { - // insert text into buffer - memmove(&buffer[pos + 1], &buffer[pos], - strlen(&buffer[pos]) + 1); - buffer[pos] = c; - pos++; - // handle edge case where cursor should be moved down - int width = get_terminal_width(); - char *prompt = get_prompt(); - size_t prompt_length = get_stripped_length(prompt); - if ((prompt_length + pos) % width == width - 1 && - pos < strlen(buffer)) { - printf("\033[B"); - } - // if modifying text reset history - history_pos = -1; - reprint_buffer(buffer, &last_lines, &pos, history_pos); - } - } - } - - reset_terminal_mode(&orig_termios); - return buffer; + struct termios orig_termios; + char *buffer = (char *)calloc(BUFFER_SIZE, sizeof(char)); + int pos = 0; + int history_pos = -1; + int last_lines = 1; + int c; + + // init buffer and make raw mode + set_raw_mode(&orig_termios); + + while (true) { + c = getchar(); + + if (c == '\033') { // escape sequence + getchar(); // skip [ + switch (getchar()) { + case 'A': // up arrow + reprint_buffer(buffer, &last_lines, &pos, ++history_pos); + break; + case 'B': // down arrow + reprint_buffer(buffer, &last_lines, &pos, --history_pos); + if (history_pos < 0) + history_pos = 0; + break; + case 'C': // right arrow + { + int width = get_terminal_width(); + char *prompt = get_prompt(); + size_t prompt_length = get_stripped_length(prompt); + if ((prompt_length + pos) % width == width - 2) { + printf("\033[B"); + } + if (pos < strlen(buffer)) { + pos++; + // if modifying text reset history + history_pos = -1; + reprint_buffer(buffer, &last_lines, &pos, history_pos); + } + free(prompt); + } break; + case 'D': // left arrow + { + int width = get_terminal_width(); + char *prompt = get_prompt(); + size_t prompt_length = get_stripped_length(prompt); + if ((prompt_length + pos) % width == width - 1) { + printf("\033[A"); + } + + if (pos > 0) { + pos--; + // if modifying text reset history + history_pos = -1; + reprint_buffer(buffer, &last_lines, &pos, history_pos); + } + free(prompt); + } break; + case '3': // delete + if (getchar() == '~') { + if (pos < strlen(buffer)) { + memmove(&buffer[pos], &buffer[pos + 1], + strlen(&buffer[pos + 1]) + 1); + // if modifying text reset history + history_pos = -1; + reprint_buffer(buffer, &last_lines, &pos, history_pos); + } + } + break; + default: + break; + } + } else if (c == '\177') { // backspace + if (pos > 0) { + memmove(&buffer[pos - 1], &buffer[pos], + strlen(&buffer[pos]) + 1); + pos--; + // handle edge case where cursor should be moved up + int width = get_terminal_width(); + char *prompt = get_prompt(); + size_t prompt_length = get_stripped_length(prompt); + if ((prompt_length + pos + 1) % width == width - 1 && + pos <= strlen(buffer)) { + printf("\033[A"); + } + // if modifying text reset history + history_pos = -1; + reprint_buffer(buffer, &last_lines, &pos, history_pos); + free(prompt); + } + } else if (c == '\t') { + char suggestion[PATH_MAX]; + size_t suggestions_count = 0; + const char *current_token = get_current_token(buffer); + char *suggestions_path = get_suggestions_path(current_token); + const char *current_word = get_current_word(buffer); + char **suggestions = + get_suggestions(&suggestions_count, suggestions_path); + const char *autocomplete_suggestion = + find_suggestion(current_word, suggestions, suggestions_count); + + if (autocomplete_suggestion != NULL) { + size_t suggestion_len = strlen(autocomplete_suggestion); + size_t current_word_len = strlen(current_word); + + // Insert the suggestion in place of the current word + memmove(&buffer[pos + suggestion_len], &buffer[pos], + strlen(&buffer[pos]) + 1); + strncpy(&buffer[pos], autocomplete_suggestion, suggestion_len); + + // Move the cursor forward + pos += suggestion_len; + } + + reprint_buffer(buffer, &last_lines, &pos, history_pos); + free(suggestions_path); + free_suggestions(suggestions, suggestions_count); + + } else if (c == '\n') { + // if modifying text reset history + history_pos = -1; + pos = strlen(buffer); + reprint_buffer(buffer, &last_lines, &pos, history_pos); + break; // submit the command + } else { + if (pos < BUFFER_SIZE - 1) { + // insert text into buffer + memmove(&buffer[pos + 1], &buffer[pos], + strlen(&buffer[pos]) + 1); + buffer[pos] = c; + pos++; + // handle edge case where cursor should be moved down + int width = get_terminal_width(); + char *prompt = get_prompt(); + size_t prompt_length = get_stripped_length(prompt); + if ((prompt_length + pos) % width == width - 1 && + pos < strlen(buffer)) { + printf("\033[B"); + } + // if modifying text reset history + history_pos = -1; + reprint_buffer(buffer, &last_lines, &pos, history_pos); + free(prompt); + } + } + } + + reset_terminal_mode(&orig_termios); + return buffer; } char *lush_resolve_aliases(char *line) { - // Allocate memory for the new string - char *result = (char *)malloc(BUFFER_SIZE); - if (!result) { - perror("malloc failed"); - return NULL; - } - - // Create a copy of the input line for tokenization - char *line_copy = strdup(line); - if (!line_copy) { - perror("strdup failed"); - free(result); - return NULL; - } - - // Start building the result string - char *dest = result; - char *arg = strtok(line_copy, " "); - while (arg != NULL) { - // Check shell aliases - if (aliases != NULL) { - char *alias = hm_get(aliases, arg); - if (alias != NULL) { - // Replace alias - strcpy(dest, alias); - dest += strlen(alias); - } else { - // No alias found, use the original token - strcpy(dest, arg); - dest += strlen(arg); - } - } else { - // No aliases set, just use the original token - strcpy(dest, arg); - dest += strlen(arg); - } - - // Add a space after each token (if it's not the last one) - arg = strtok(NULL, " "); - if (arg != NULL) { - *dest++ = ' '; - } - } - - *dest = '\0'; // Null-terminate the result string - - // Clean up - free(line_copy); - - return result; + // Allocate memory for the new string + char *result = (char *)malloc(BUFFER_SIZE); + if (!result) { + perror("malloc failed"); + return NULL; + } + + // Create a copy of the input line for tokenization + char *line_copy = strdup(line); + if (!line_copy) { + perror("strdup failed"); + free(result); + return NULL; + } + + // Start building the result string + char *dest = result; + char *arg = strtok(line_copy, " "); + while (arg != NULL) { + // Check shell aliases + if (aliases != NULL) { + char *alias = hm_get(aliases, arg); + if (alias != NULL) { + // Replace alias + strcpy(dest, alias); + dest += strlen(alias); + } else { + // No alias found, use the original token + strcpy(dest, arg); + dest += strlen(arg); + } + } else { + // No aliases set, just use the original token + strcpy(dest, arg); + dest += strlen(arg); + } + + // Add a space after each token (if it's not the last one) + arg = strtok(NULL, " "); + if (arg != NULL) { + *dest++ = ' '; + } + } + + *dest = '\0'; // Null-terminate the result string + + // Clean up + free(line_copy); + + return result; } static void lush_expand_globs(char ***args) { - if (args == NULL || *args == NULL) { - return; - } - - // iterate over each arg - for (int i = 0; args[i]; i++) { - char **arg_list = args[i]; - for (int j = 0; arg_list[j]; j++) { - char *arg = arg_list[j]; - - // check if the arg string has a glob pattern - if (strpbrk(arg, "*?[") != NULL) { - glob_t glob_result; - memset(&glob_result, 0, sizeof(glob_result)); - - // use glob to expand the arg - int ret = - glob(arg, GLOB_TILDE | GLOB_BRACE, NULL, &glob_result); - if (ret == 0) { - // if there is a nonzero number of matches malloc new arg - // memory - if (glob_result.gl_pathc > 0) { - char **new_args = (char **)malloc( - (glob_result.gl_pathc + 1) * sizeof(char *)); - if (new_args == NULL) { - perror("malloc failed"); - globfree(&glob_result); - return; - } - - for (size_t k = 0; k < glob_result.gl_pathc; k++) { - new_args[k] = strdup(glob_result.gl_pathv[k]); - if (new_args[k] == NULL) { - perror("strdup failed"); - for (size_t l = 0; l < k; l++) { - free(new_args[l]); - } - free(new_args); - globfree(&glob_result); - return; - } - } - new_args[glob_result.gl_pathc] = NULL; - - // replace the original arg with new list - arg_list[j] = new_args[0]; - - for (size_t k = 1; k < glob_result.gl_pathc; k++) { - // insert the new argument - arg_list[j + k] = new_args[k]; - } - - free(new_args); - } - } else if (ret == GLOB_NOMATCH) { - - } else { - fprintf(stderr, "glob failed with error code %d\n", ret); - } - - globfree(&glob_result); - } - } - } + if (args == NULL || *args == NULL) { + return; + } + + // iterate over each arg + for (int i = 0; args[i]; i++) { + char **arg_list = args[i]; + for (int j = 0; arg_list[j]; j++) { + char *arg = arg_list[j]; + + // check if the arg string has a glob pattern + if (strpbrk(arg, "*?[") != NULL) { + glob_t glob_result; + memset(&glob_result, 0, sizeof(glob_result)); + + // use glob to expand the arg + int ret = + glob(arg, GLOB_TILDE | GLOB_BRACE, NULL, &glob_result); + if (ret == 0) { + // if there is a nonzero number of matches malloc new arg + // memory + if (glob_result.gl_pathc > 0) { + char **new_args = (char **)malloc( + (glob_result.gl_pathc + 1) * sizeof(char *)); + if (new_args == NULL) { + perror("malloc failed"); + globfree(&glob_result); + return; + } + + for (size_t k = 0; k < glob_result.gl_pathc; k++) { + new_args[k] = strdup(glob_result.gl_pathv[k]); + if (new_args[k] == NULL) { + perror("strdup failed"); + for (size_t l = 0; l < k; l++) { + free(new_args[l]); + } + free(new_args); + globfree(&glob_result); + return; + } + } + new_args[glob_result.gl_pathc] = NULL; + + // replace the original arg with new list + arg_list[j] = new_args[0]; + + for (size_t k = 1; k < glob_result.gl_pathc; k++) { + // insert the new argument + arg_list[j + k] = new_args[k]; + } + + free(new_args); + } + } else if (ret == GLOB_NOMATCH) { + + } else { + fprintf(stderr, "glob failed with error code %d\n", ret); + } + + globfree(&glob_result); + } + } + } } static int is_operator(const char *str) { - const char *operators[] = {"1>>", "2>>", "&>>", ">>", "1>", "2>", "&>", - "||", "&&", ">", "&", ";", "|"}; - int num_operators = sizeof(operators) / sizeof(operators[0]); - for (int i = 0; i < num_operators; i++) { - if (strncmp(str, operators[i], strlen(operators[i])) == 0) { - switch (i) { - case 0: - return OP_APPEND_STDOUT; - case 1: - return OP_APPEND_STDERR; - case 2: - return OP_APPEND_BOTH; - case 3: - return OP_APPEND_STDOUT; - case 4: - return OP_REDIRECT_STDOUT; - case 5: - return OP_REDIRECT_STDERR; - case 6: - return OP_REDIRECT_BOTH; - case 7: - return OP_OR; - case 8: - return OP_AND; - case 9: - return OP_REDIRECT_STDOUT; - case 10: - return OP_BACKGROUND; - case 11: - return OP_SEMICOLON; - case 12: - return OP_PIPE; - default: - return 0; - } - } - } - return 0; // Not an operator + const char *operators[] = {"1>>", "2>>", "&>>", ">>", "1>", "2>", "&>", + "||", "&&", ">", "&", ";", "|"}; + int num_operators = sizeof(operators) / sizeof(operators[0]); + for (int i = 0; i < num_operators; i++) { + if (strncmp(str, operators[i], strlen(operators[i])) == 0) { + switch (i) { + case 0: + return OP_APPEND_STDOUT; + case 1: + return OP_APPEND_STDERR; + case 2: + return OP_APPEND_BOTH; + case 3: + return OP_APPEND_STDOUT; + case 4: + return OP_REDIRECT_STDOUT; + case 5: + return OP_REDIRECT_STDERR; + case 6: + return OP_REDIRECT_BOTH; + case 7: + return OP_OR; + case 8: + return OP_AND; + case 9: + return OP_REDIRECT_STDOUT; + case 10: + return OP_BACKGROUND; + case 11: + return OP_SEMICOLON; + case 12: + return OP_PIPE; + default: + return 0; + } + } + } + return 0; // Not an operator } static int operator_length(const char *str) { - const char *operators[] = {"1>>", "2>>", "&>>", ">>", "1>", "2>", "&>", - "||", "&&", ">", "&", ";", "|"}; - int num_operators = sizeof(operators) / sizeof(operators[0]); - for (int i = 0; i < num_operators; i++) { - if (strncmp(str, operators[i], strlen(operators[i])) == 0) { - switch (i) { - case 0: - case 1: - case 2: - return 3; - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - return 2; - case 9: - case 10: - case 11: - case 12: - return 1; - default: - return 0; - } - } - } - return 0; + const char *operators[] = {"1>>", "2>>", "&>>", ">>", "1>", "2>", "&>", + "||", "&&", ">", "&", ";", "|"}; + int num_operators = sizeof(operators) / sizeof(operators[0]); + for (int i = 0; i < num_operators; i++) { + if (strncmp(str, operators[i], strlen(operators[i])) == 0) { + switch (i) { + case 0: + case 1: + case 2: + return 3; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return 2; + case 9: + case 10: + case 11: + case 12: + return 1; + default: + return 0; + } + } + } + return 0; } static char *trim_whitespace(char *str) { - char *end; + char *end; - // Trim leading space - while (isspace((unsigned char)*str)) - str++; + // Trim leading space + while (isspace((unsigned char)*str)) + str++; - if (*str == 0) - return str; // If all spaces, return empty string + if (*str == 0) + return str; // If all spaces, return empty string - // Trim trailing space - end = str + strlen(str) - 1; - while (end > str && isspace((unsigned char)*end)) - end--; + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) + end--; - *(end + 1) = '\0'; + *(end + 1) = '\0'; - return str; + return str; } // Split the command based on various chaining operations char **lush_split_commands(char *line) { - char **commands = calloc(16, sizeof(char *)); - if (!commands) { - perror("calloc failed"); - exit(1); - } - - int pos = 0; - char *start = line; - bool in_quote = false; - while (*start) { - // Skip leading spaces - while (isspace((unsigned char)*start)) - start++; - - // Check for operators - int op_len = operator_length(start); - if (op_len > 0) { - // Allocate memory for operator command - char *operator_cmd = calloc(op_len + 1, sizeof(char)); - strncpy(operator_cmd, start, op_len); - commands[pos++] = operator_cmd; - start += op_len; - } else { - // Collect regular commands until the next operator or end of string - char *next = start; - while (*next) { - if (*next == '"') { - in_quote = !in_quote; - } - next++; - - if (!in_quote && is_operator(next)) - break; - } - - // Copy the command between start and next - char *command = strndup(start, next - start); - commands[pos++] = trim_whitespace(command); - start = next; - } - - if (pos >= 16) { - // Resize if necessary - commands = realloc(commands, (pos + 16) * sizeof(char *)); - if (!commands) { - perror("realloc failed"); - exit(1); - } - } - } - - commands[pos] = NULL; - return commands; + char **commands = calloc(16, sizeof(char *)); + if (!commands) { + perror("calloc failed"); + exit(1); + } + + int pos = 0; + char *start = line; + bool in_quote = false; + while (*start) { + // Skip leading spaces + while (isspace((unsigned char)*start)) + start++; + + // Check for operators + int op_len = operator_length(start); + if (op_len > 0) { + // Allocate memory for operator command + char *operator_cmd = calloc(op_len + 1, sizeof(char)); + strncpy(operator_cmd, start, op_len); + commands[pos++] = operator_cmd; + start += op_len; + } else { + // Collect regular commands until the next operator or end of string + char *next = start; + while (*next) { + if (*next == '"') { + in_quote = !in_quote; + } + next++; + + if (!in_quote && is_operator(next)) + break; + } + + // Copy the command between start and next + char *command = strndup(start, next - start); + commands[pos++] = trim_whitespace(command); + start = next; + } + + if (pos >= 16) { + // Resize if necessary + commands = realloc(commands, (pos + 16) * sizeof(char *)); + if (!commands) { + perror("realloc failed"); + exit(1); + } + } + } + + commands[pos] = NULL; + return commands; } char ***lush_split_args(char **commands, int *status) { - int outer_pos = 0; - char ***command_args = calloc(128, sizeof(char **)); - if (!command_args) { - perror("calloc failed"); - exit(1); - } - - for (int i = 0; commands[i]; i++) { - int pos = 0; - char **args = calloc(128, sizeof(char *)); - if (!args) { - perror("calloc failed"); - exit(1); - } - - bool inside_string = false; - char *current_token = &commands[i][0]; - for (int j = 0; commands[i][j]; j++) { - if (commands[i][j] == '"' && !inside_string) { - // beginning of a string - commands[i][j++] = '\0'; - if (commands[i][j] != '"') { - inside_string = true; - current_token = &commands[i][j]; - } else { - commands[i][j] = '\0'; - current_token = &commands[i][++j]; - } - } else if (inside_string) { - if (commands[i][j] == '"') { - // ending of a string - inside_string = false; - commands[i][j] = '\0'; - args[pos++] = current_token; - current_token = NULL; - } else { - // character in string - continue; - } - } else if (commands[i][j] == ' ') { - // space delimeter - if (current_token && *current_token != ' ') { - args[pos++] = current_token; - } - current_token = &commands[i][j + 1]; // go past the space - commands[i][j] = '\0'; // null the space - } else if (commands[i][j] == '$' && commands[i][j + 1] && - commands[i][j + 1] != ' ') { - // environment variable - args[pos++] = getenv(&commands[i][++j]); - while (commands[i][j]) { - ++j; - } - current_token = &commands[i][j + 1]; - } else { - // regular character - continue; - } - } - - // verify that string literals are finished - if (inside_string) { - *status = -1; - return command_args; - } else if (current_token && *current_token != ' ') { - // tack on last arg - args[pos++] = current_token; - } - - // add this commands args array to the outer array - command_args[outer_pos++] = args; - } - - *status = outer_pos; - return command_args; + int outer_pos = 0; + char ***command_args = calloc(128, sizeof(char **)); + if (!command_args) { + perror("calloc failed"); + exit(1); + } + + for (int i = 0; commands[i]; i++) { + int pos = 0; + char **args = calloc(128, sizeof(char *)); + if (!args) { + perror("calloc failed"); + exit(1); + } + + bool inside_string = false; + char *current_token = &commands[i][0]; + for (int j = 0; commands[i][j]; j++) { + if (commands[i][j] == '"' && !inside_string) { + // beginning of a string + commands[i][j++] = '\0'; + if (commands[i][j] != '"') { + inside_string = true; + current_token = &commands[i][j]; + } else { + commands[i][j] = '\0'; + current_token = &commands[i][++j]; + } + } else if (inside_string) { + if (commands[i][j] == '"') { + // ending of a string + inside_string = false; + commands[i][j] = '\0'; + args[pos++] = current_token; + current_token = NULL; + } else { + // character in string + continue; + } + } else if (commands[i][j] == ' ') { + // space delimeter + if (current_token && *current_token != ' ') { + args[pos++] = current_token; + } + current_token = &commands[i][j + 1]; // go past the space + commands[i][j] = '\0'; // null the space + } else if (commands[i][j] == '$' && commands[i][j + 1] && + commands[i][j + 1] != ' ') { + // environment variable + args[pos++] = getenv(&commands[i][++j]); + while (commands[i][j]) { + ++j; + } + current_token = &commands[i][j + 1]; + } else { + // regular character + continue; + } + } + + // verify that string literals are finished + if (inside_string) { + *status = -1; + return command_args; + } else if (current_token && *current_token != ' ') { + // tack on last arg + args[pos++] = current_token; + } + + // add this commands args array to the outer array + command_args[outer_pos++] = args; + } + + *status = outer_pos; + return command_args; } static int run_command(lua_State *L, char ***commands) { - // check if the command is a lua script - char *ext = strrchr(commands[0][0], '.'); - if (ext) { - ext++; - if (strcmp(ext, "lua") == 0) { - return ((*builtin_func[LUSH_LUA])(L, commands)); - } - } - - // check shell builtins - for (int j = 0; j < lush_num_builtins(); j++) { - if (strcmp(commands[0][0], builtin_strs[j]) == 0) { - return ((*builtin_func[j])(L, commands)); - } - } - - return lush_execute_command(commands[0], STDIN_FILENO, STDOUT_FILENO); + // check if the command is a lua script + char *ext = strrchr(commands[0][0], '.'); + if (ext) { + ext++; + if (strcmp(ext, "lua") == 0) { + return ((*builtin_func[LUSH_LUA])(L, commands)); + } + } + + // check shell builtins + for (int j = 0; j < lush_num_builtins(); j++) { + if (strcmp(commands[0][0], builtin_strs[j]) == 0) { + return ((*builtin_func[j])(L, commands)); + } + } + + return lush_execute_command(commands[0], STDIN_FILENO, STDOUT_FILENO); } static int run_command_redirect(lua_State *L, char ***commands, int operator) { - if (commands[2] == NULL) - return -1; - - int mode = (operator>= OP_REDIRECT_STDOUT && operator<= OP_REDIRECT_BOTH) - ? O_TRUNC - : O_APPEND; - - int redirect_flags = 0; - if (operator== OP_REDIRECT_STDOUT || operator== OP_APPEND_STDOUT) { - redirect_flags = 1; - } else if (operator== OP_REDIRECT_STDERR || operator== OP_APPEND_STDERR) { - redirect_flags = 2; - } else { - redirect_flags = 3; - } - - int saved_stdout = -1, saved_stderr = -1; - int fd = open(commands[2][0], O_WRONLY | O_CREAT | mode, 0644); - if (fd == -1) { - perror("invalid fd"); - return -1; - } - - // Redirect stdout - if (redirect_flags & 1) { - saved_stdout = dup(STDOUT_FILENO); - if (saved_stdout == -1) { - perror("dup stdout"); - close(fd); - return -1; - } - - if (dup2(fd, STDOUT_FILENO) == -1) { - perror("dup2 stdout"); - close(fd); - close(saved_stdout); - return -1; - } - } - - // Redirect stderr - if (redirect_flags & 2) { - saved_stderr = dup(STDERR_FILENO); - if (saved_stderr == -1) { - perror("dup stderr"); - if (saved_stdout != -1) { - dup2(saved_stdout, STDOUT_FILENO); - close(saved_stdout); - } - close(fd); - return -1; - } - - if (dup2(fd, STDERR_FILENO) == -1) { - perror("dup2 stderr"); - if (saved_stdout != -1) { - dup2(saved_stdout, STDOUT_FILENO); - close(saved_stdout); - } - close(saved_stderr); - close(fd); - return -1; - } - } - - close(fd); - - // Run the command - run_command(L, commands); - - // Restore stdout - if (saved_stdout != -1) { - if (dup2(saved_stdout, STDOUT_FILENO) == -1) { - perror("dup2 restore stdout"); - close(saved_stdout); - return -1; - } - close(saved_stdout); - } - - // Restore stderr - if (saved_stderr != -1) { - if (dup2(saved_stderr, STDERR_FILENO) == -1) { - perror("dup2 restore stderr"); - close(saved_stderr); - return -1; - } - close(saved_stderr); - } - - return 0; + if (commands[2] == NULL) + return -1; + + int mode = (operator>= OP_REDIRECT_STDOUT && operator<= OP_REDIRECT_BOTH) + ? O_TRUNC + : O_APPEND; + + int redirect_flags = 0; + if (operator== OP_REDIRECT_STDOUT || operator== OP_APPEND_STDOUT) { + redirect_flags = 1; + } else if (operator== OP_REDIRECT_STDERR || operator== OP_APPEND_STDERR) { + redirect_flags = 2; + } else { + redirect_flags = 3; + } + + int saved_stdout = -1, saved_stderr = -1; + int fd = open(commands[2][0], O_WRONLY | O_CREAT | mode, 0644); + if (fd == -1) { + perror("invalid fd"); + return -1; + } + + // Redirect stdout + if (redirect_flags & 1) { + saved_stdout = dup(STDOUT_FILENO); + if (saved_stdout == -1) { + perror("dup stdout"); + close(fd); + return -1; + } + + if (dup2(fd, STDOUT_FILENO) == -1) { + perror("dup2 stdout"); + close(fd); + close(saved_stdout); + return -1; + } + } + + // Redirect stderr + if (redirect_flags & 2) { + saved_stderr = dup(STDERR_FILENO); + if (saved_stderr == -1) { + perror("dup stderr"); + if (saved_stdout != -1) { + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + } + close(fd); + return -1; + } + + if (dup2(fd, STDERR_FILENO) == -1) { + perror("dup2 stderr"); + if (saved_stdout != -1) { + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + } + close(saved_stderr); + close(fd); + return -1; + } + } + + close(fd); + + // Run the command + run_command(L, commands); + + // Restore stdout + if (saved_stdout != -1) { + if (dup2(saved_stdout, STDOUT_FILENO) == -1) { + perror("dup2 restore stdout"); + close(saved_stdout); + return -1; + } + close(saved_stdout); + } + + // Restore stderr + if (saved_stderr != -1) { + if (dup2(saved_stderr, STDERR_FILENO) == -1) { + perror("dup2 restore stderr"); + close(saved_stderr); + return -1; + } + close(saved_stderr); + } + + return 0; } // TODO: Allow background process to run lua script static int run_command_background(lua_State *L, char ***commands) { - pid_t pid = fork(); - - if (pid < 0) { - perror("fork"); - return -1; - } else if (pid == 0) { - execvp(commands[0][0], commands[0]); - perror("execvp background"); - exit(EXIT_FAILURE); - } - - printf("Process %d running in background\n", pid); - return 0; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } else if (pid == 0) { + execvp(commands[0][0], commands[0]); + perror("execvp background"); + exit(EXIT_FAILURE); + } + + printf("Process %d running in background\n", pid); + return 0; } int lush_execute_chain(lua_State *L, char ***commands, int num_commands) { - if (commands[0][0][0] == '\0') { - return 0; - } - - int num_actions = (num_commands + 1) / 2; - int last_result = 0; - - for (int i = 0; i < num_actions; i++) { - // Handle &&, ||, and ; operators - if (i > 0 && commands[0] != NULL) { - commands--; - if (last_result != 0) { - if (is_operator(commands[0][0]) == OP_AND) { - commands += 3; - continue; - } - } else { - if (is_operator(commands[0][0]) == OP_OR) { - commands += 3; - continue; - } - } - commands++; - - if (is_operator(commands[0][0]) == OP_SEMICOLON) { - commands++; - } - } - - // Handle other operations - if (commands[1] != NULL) { - int op_type = is_operator(commands[1][0]); - if (op_type == OP_PIPE) { - char ***pipe_commands = - malloc(sizeof(char **) * (num_actions - i)); - int pipe_count = 0; - - while (i < num_actions - 1 && op_type == OP_PIPE) { - pipe_commands[pipe_count++] = commands[0]; - commands += 2; - i++; - if (i < num_actions - 1) { - op_type = is_operator(commands[1][0]); - } else { - break; - } - } - - pipe_commands[pipe_count++] = commands[0]; - last_result = lush_execute_pipeline(pipe_commands, pipe_count); - - free(pipe_commands); - commands += 2; - continue; - } else if (op_type == OP_BACKGROUND) { - last_result = run_command_background(L, commands); - commands += 2; - continue; - } else if (op_type >= OP_REDIRECT_STDOUT && - op_type <= OP_APPEND_BOTH) { - last_result = run_command_redirect(L, commands, op_type); - commands += 3; - continue; - } - } - // Run the command or move past the operator - if (commands[0] != NULL && !is_operator(commands[0][0])) { - last_result = run_command(L, commands); - commands += 2; - } else { - commands++; - } - } - - return 0; + if (commands[0][0][0] == '\0') { + return 0; + } + + int num_actions = (num_commands + 1) / 2; + int last_result = 0; + + for (int i = 0; i < num_actions; i++) { + // Handle &&, ||, and ; operators + if (i > 0 && commands[0] != NULL) { + commands--; + if (last_result != 0) { + if (is_operator(commands[0][0]) == OP_AND) { + commands += 3; + continue; + } + } else { + if (is_operator(commands[0][0]) == OP_OR) { + commands += 3; + continue; + } + } + commands++; + + if (is_operator(commands[0][0]) == OP_SEMICOLON) { + commands++; + } + } + + // Handle other operations + if (commands[1] != NULL) { + int op_type = is_operator(commands[1][0]); + if (op_type == OP_PIPE) { + char ***pipe_commands = + malloc(sizeof(char **) * (num_actions - i)); + int pipe_count = 0; + + while (i < num_actions - 1 && op_type == OP_PIPE) { + pipe_commands[pipe_count++] = commands[0]; + commands += 2; + i++; + if (i < num_actions - 1) { + op_type = is_operator(commands[1][0]); + } else { + break; + } + } + + pipe_commands[pipe_count++] = commands[0]; + last_result = lush_execute_pipeline(pipe_commands, pipe_count); + + free(pipe_commands); + commands += 2; + continue; + } else if (op_type == OP_BACKGROUND) { + last_result = run_command_background(L, commands); + commands += 2; + continue; + } else if (op_type >= OP_REDIRECT_STDOUT && + op_type <= OP_APPEND_BOTH) { + last_result = run_command_redirect(L, commands, op_type); + commands += 3; + continue; + } + } + // Run the command or move past the operator + if (commands[0] != NULL && !is_operator(commands[0][0])) { + last_result = run_command(L, commands); + commands += 2; + } else { + commands++; + } + } + + return 0; } int lush_execute_pipeline(char ***commands, int num_commands) { - // no command given - if (commands[0][0][0] == '\0') { - return 0; - } - - // create pipes for each command - int **pipes = malloc((num_commands - 1) * sizeof(int *)); - for (int i = 0; i < num_commands - 1; i++) { - pipes[i] = - malloc(2 * sizeof(int)); // pipes[i][0] = in, pipes[i][1] = out - if (pipe(pipes[i]) == -1) { - perror("pipe"); - return 0; - } - } - - // execute commands in the pipeline - for (int i = 0; i < num_commands - 1; i++) { - int input_fd = (i == 0) ? STDIN_FILENO : pipes[i - 1][0]; - int output_fd = pipes[i][1]; - - lush_execute_command(commands[i], input_fd, output_fd); - close(output_fd); // no longer need to write to this pipe - } - - // execute last or only command - int input_fd = - (num_commands > 1) ? pipes[num_commands - 2][0] : STDIN_FILENO; - int output_fd = STDOUT_FILENO; - lush_execute_command(commands[num_commands - 1], input_fd, output_fd); - - // close pipes - for (int i = 0; i < num_commands - 1; i++) { - close(pipes[i][0]); - close(pipes[i][1]); - free(pipes[i]); - } - free(pipes); - return 0; + // no command given + if (commands[0][0][0] == '\0') { + return 0; + } + + // create pipes for each command + int **pipes = malloc((num_commands - 1) * sizeof(int *)); + for (int i = 0; i < num_commands - 1; i++) { + pipes[i] = + malloc(2 * sizeof(int)); // pipes[i][0] = in, pipes[i][1] = out + if (pipe(pipes[i]) == -1) { + perror("pipe"); + return 0; + } + } + + // execute commands in the pipeline + for (int i = 0; i < num_commands - 1; i++) { + int input_fd = (i == 0) ? STDIN_FILENO : pipes[i - 1][0]; + int output_fd = pipes[i][1]; + + lush_execute_command(commands[i], input_fd, output_fd); + close(output_fd); // no longer need to write to this pipe + } + + // execute last or only command + int input_fd = + (num_commands > 1) ? pipes[num_commands - 2][0] : STDIN_FILENO; + int output_fd = STDOUT_FILENO; + lush_execute_command(commands[num_commands - 1], input_fd, output_fd); + + // close pipes + for (int i = 0; i < num_commands - 1; i++) { + close(pipes[i][0]); + close(pipes[i][1]); + free(pipes[i]); + } + free(pipes); + return 0; } static void build_alt_command(char *buffer, char **args) { - size_t offset = 0; - - for (int i = 0; args[i]; i++) { - if (offset + strlen(args[i]) + 1 < BUFFER_SIZE) { - strcat(buffer, args[i]); - strcat(buffer, " "); - offset = strlen(buffer); - } else { - fprintf(stderr, "command too long\n"); - exit(EXIT_FAILURE); - } - } + size_t offset = 0; + + for (int i = 0; args[i]; i++) { + if (offset + strlen(args[i]) + 1 < BUFFER_SIZE) { + strcat(buffer, args[i]); + strcat(buffer, " "); + offset = strlen(buffer); + } else { + fprintf(stderr, "command too long\n"); + exit(EXIT_FAILURE); + } + } } int lush_execute_command(char **args, int input_fd, int output_fd) { - // create child - pid_t pid; - int status; - - struct sigaction sa; - - if ((pid = fork()) == 0) { - // child process content - - // restore default sigint for child - sa.sa_handler = SIG_DFL; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGINT, &sa, NULL); - - // redirect in and out fd's if needed - if (input_fd != STDIN_FILENO) { - dup2(input_fd, STDIN_FILENO); - close(input_fd); - } - - if (output_fd != STDOUT_FILENO) { - dup2(output_fd, STDOUT_FILENO); - close(output_fd); - } - - // execute the command - if (execvp(args[0], args) == -1) { - if (alt_shell) { - char command[BUFFER_SIZE] = {0}; - build_alt_command(command, args); - execlp(alt_shell, alt_shell, "-c", command, (char *)NULL); - perror("alt shell"); - } else { - perror("lush"); - exit(EXIT_FAILURE); - } - } - } else if (pid < 0) { - // forking failed - perror("fork"); - exit(EXIT_FAILURE); - } else { - // parent process - do { - waitpid(pid, &status, WUNTRACED); - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - } - - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } - return -1; + // create child + pid_t pid; + int status; + + struct sigaction sa; + + if ((pid = fork()) == 0) { + // child process content + + // restore default sigint for child + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + + // redirect in and out fd's if needed + if (input_fd != STDIN_FILENO) { + dup2(input_fd, STDIN_FILENO); + close(input_fd); + } + + if (output_fd != STDOUT_FILENO) { + dup2(output_fd, STDOUT_FILENO); + close(output_fd); + } + + // execute the command + if (execvp(args[0], args) == -1) { + if (alt_shell) { + char command[BUFFER_SIZE] = {0}; + build_alt_command(command, args); + execlp(alt_shell, alt_shell, "-c", command, (char *)NULL); + perror("alt shell"); + } else { + perror("lush"); + exit(EXIT_FAILURE); + } + } + } else if (pid < 0) { + // forking failed + perror("fork"); + exit(EXIT_FAILURE); + } else { + // parent process + do { + waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return -1; } int lush_run(lua_State *L, char ***commands, int num_commands) { - if (commands[0][0] == NULL) { - // no command given - return 0; - } + if (commands[0][0] == NULL) { + // no command given + return 0; + } - return lush_execute_chain(L, commands, num_commands); + return lush_execute_chain(L, commands, num_commands); } static void background_handler(int sig) { - // Reap all child processes - while (waitpid(-1, NULL, WNOHANG) > 0) - ; + // Reap all child processes + while (waitpid(-1, NULL, WNOHANG) > 0) + ; } + int main(int argc, char *argv[]) { - // check if the --version arg was passed - if (argc > 1 && strcmp(argv[1], "--version") == 0) { + // check if the --version arg was passed + if (argc > 1 && strcmp(argv[1], "--version") == 0) { #ifdef LUSH_VERSION - printf("Lunar Shell, version %s\n", LUSH_VERSION); + printf("Lunar Shell, version %s\n", LUSH_VERSION); #endif - return 0; - } - - // check if being run in command string mode - if (argc > 2 && strcmp(argv[1], "-c") == 0) { - // init Lua state - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - lua_register_api(L); - lua_run_init(L); - - // execute the command provided - char *command = argv[2]; - char *expanded_command = lush_resolve_aliases(command); - char **commands = lush_split_commands(expanded_command); - int status = 0; - char ***args = lush_split_args(commands, &status); - - if (status == -1) { - fprintf(stderr, "lush: Expected end of quoted string\n"); - } else { - lush_expand_globs(args); - } - - if (lush_run(L, args, status) != 0) { - exit(1); - } - - // clean up - for (int i = 0; args[i]; i++) { - free(args[i]); - } - free(args); - free(commands); - free(expanded_command); - lua_close(L); - return 0; - } - - // init lua state - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - lua_register_api(L); - lua_run_init(L); - - // if a lua function is passed on load run non-interactively - if (argc > 1) { - char *ext = strrchr(argv[1], '.'); - if (ext) { - ext++; - if (strcmp(ext, "lua") == 0) { - int status = 0; - argv++; - char ***args = lush_split_args(argv, &status); - - if (status == -1) { - fprintf(stderr, "lush: Expected end of quoted string\n"); - } else if (lush_run(L, args, status) != 0) { - exit(1); - } - - for (int i = 0; args[i]; i++) { - free(args[i]); - } - free(args); - return 0; - } - } - } - - // eat ^C in main - struct sigaction sa_int; - sa_int.sa_handler = SIG_IGN; - sa_int.sa_flags = 0; - sigemptyset(&sa_int.sa_mask); - sigaction(SIGINT, &sa_int, NULL); - - struct sigaction sa_bk; - sa_bk.sa_handler = &background_handler; - sigemptyset(&sa_bk.sa_mask); - sa_bk.sa_flags = SA_RESTART; - sigaction(SIGCHLD, &sa_bk, NULL); - - // set custom envars - char hostname[256]; - gethostname(hostname, sizeof(hostname)); - setenv("HOSTNAME", hostname, 1); - char *cwd = getcwd(NULL, 0); - setenv("OLDPWD", cwd, 1); - free(cwd); - - int status = 0; - while (true) { - // Prompt - char *prompt = get_prompt(); - - printf("%s ", prompt); - char *line = lush_read_line(); - lush_push_history(line); - printf("\n"); - if (line == NULL || strlen(line) == 0) { - free(line); - continue; - } - char *expanded_line = lush_resolve_aliases(line); - char **commands = lush_split_commands(expanded_line); - char ***args = lush_split_args(commands, &status); - if (status == -1) { - fprintf(stderr, "lush: Expected end of quoted string\n"); - } else { - lush_expand_globs(args); - } - - if (lush_run(L, args, status) != 0) { - exit(1); - } - - for (int i = 0; args[i]; i++) { - free(args[i]); - } - // add last line to history - free(prompt); - free(args); - free(commands); - free(expanded_line); - free(line); - } - lua_close(L); - if (prompt_format != NULL) - free(prompt_format); - if (aliases != NULL) - free(aliases); - if (alt_shell != NULL) - free(alt_shell); - return 0; + return 0; + } + + // init lua state + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + lua_register_api(L); + lua_run_init(L); + + // check if being run in command string mode + if (argc > 2 && strcmp(argv[1], "-c") == 0) { + // execute the command provided + char *command = argv[2]; + char *expanded_command = lush_resolve_aliases(command); + char **commands = lush_split_commands(expanded_command); + int status = 0; + char ***args = lush_split_args(commands, &status); + + if (status == -1) { + fprintf(stderr, "lush: Expected end of quoted string\n"); + } else { + lush_expand_globs(args); + } + + if (lush_run(L, args, status) != 0) { + exit(1); + } + + // clean up + for (int i = 0; args[i]; i++) { + free(args[i]); + } + free(args); + free(commands); + free(expanded_command); + lua_close(L); + return 0; + } + + // This is the corrected logic for running a script file non-interactively. + if (argc > 1) { + char *ext = strrchr(argv[1], '.'); + if (ext && strcmp(ext, ".lua") == 0) { + const char *script_name = argv[1]; + // The arguments for the script start at argv[2]. + // We create a pointer to that part of the array. + char **script_args = (argc > 2) ? &argv[2] : NULL; + + // Call the script loader directly with the script and its arguments. + if (lua_load_script(L, script_name, script_args) != 0) { + exit(1); // Exit if the script had an error + } + + lua_close(L); // Clean up and exit + return 0; + } + } + + // --- Interactive Shell Mode --- + + // eat ^C in main + struct sigaction sa_int; + sa_int.sa_handler = SIG_IGN; + sa_int.sa_flags = 0; + sigemptyset(&sa_int.sa_mask); + sigaction(SIGINT, &sa_int, NULL); + + struct sigaction sa_bk; + sa_bk.sa_handler = &background_handler; + sigemptyset(&sa_bk.sa_mask); + sa_bk.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa_bk, NULL); + + // set custom envars + char hostname[256]; + gethostname(hostname, sizeof(hostname)); + setenv("HOSTNAME", hostname, 1); + char *cwd = getcwd(NULL, 0); + setenv("OLDPWD", cwd, 1); + free(cwd); + + int status = 0; + while (true) { + // Prompt + char *prompt = get_prompt(); + + printf("%s ", prompt); + fflush(stdout); + char *line = lush_read_line(); + lush_push_history(line); + printf("\n"); + if (line == NULL || strlen(line) == 0) { + free(line); + free(prompt); + continue; + } + char *expanded_line = lush_resolve_aliases(line); + char **commands = lush_split_commands(expanded_line); + char ***args = lush_split_args(commands, &status); + if (status == -1) { + fprintf(stderr, "lush: Expected end of quoted string\n"); + } else { + lush_expand_globs(args); + } + + if (lush_run(L, args, status) != 0) { + exit(1); + } + + for (int i = 0; args[i]; i++) { + free(args[i]); + } + // add last line to history + free(prompt); + free(args); + free(commands); + free(expanded_line); + free(line); + } + lua_close(L); + if (prompt_format != NULL) + free(prompt_format); + if (aliases != NULL) + free(aliases); + if (alt_shell != NULL) + free(alt_shell); + return 0; } + +#include "../lib/compat53/c-api/compat-5.3.c" diff --git a/test/compat_test.lua b/test/compat_test.lua new file mode 100644 index 0000000..bd7ef90 --- /dev/null +++ b/test/compat_test.lua @@ -0,0 +1,60 @@ +-- test/compat_test.lua +-- A small test suite for the lua-compat-5.3 layer. +-- Can be run in two ways: +-- 1. ./lush test/compat_test.lua (runs all tests) +-- 2. ./lush test/compat_test.lua test_table_unpack (runs a single test) + +local tests = {} + +function tests.test_table_unpack() + print("--- Running test: table.unpack ---") + local my_table = { "a", "b", "c" } + -- The compat layer provides table.unpack for Lua 5.1. + local x, y, z = table.unpack(my_table) + assert(x == "a", "unpack failed for first element") + assert(y == "b", "unpack failed for second element") + assert(z == "c", "unpack failed for third element") + print("...SUCCESS!") +end + +function tests.test_math_log_base() + print("--- Running test: math.log with base ---") + -- Lua 5.1's math.log only takes one argument. The compat layer adds the base. + local result = math.log(100, 10) + -- Use a small epsilon for floating point comparison + assert(math.abs(result - 2) < 1e-9, "math.log(100, 10) should be 2") + print("...SUCCESS!") +end + +function tests.test_string_rep_separator() + print("--- Running test: string.rep with separator ---") + -- Lua 5.1's string.rep doesn't have the separator argument. + local result = string.rep("a", 3, "-") + assert(result == "a-a-a", "string.rep with separator failed, got: " .. tostring(result)) + print("...SUCCESS!") +end + + +-- NOTE: The lush C host provides arguments in a global table named 'args', not 'arg'. +local test_to_run = args and args[1] + +if test_to_run and tests[test_to_run] then + -- If a valid test name is provided, run only that test. + local success, err = pcall(tests[test_to_run]) + if not success then + print("...FAILURE: " .. err) + end +elseif test_to_run then + -- If an invalid name is provided, show an error. + print("ERROR: Test function '" .. test_to_run .. "' not found.") +else + -- If no specific test is requested, run all of them. + print("--- Running all compatibility tests ---") + for name, func in pairs(tests) do + local success, err = pcall(func) + if not success then + print("...FAILURE on test '" .. name .. "': " .. err) + end + end + print("--- All tests complete ---") +end