From 94a2aecff1134ce6af287cdf5759af67912b949a Mon Sep 17 00:00:00 2001 From: Zsombor Szabo-Antalovszky <zsombors@stud.ntnu.no> Date: Thu, 1 May 2025 13:34:16 +0200 Subject: [PATCH] Releasing version 0.4. See changelog for more info. Documentation will be updated shortly --- noshx.cpp | 229 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 163 insertions(+), 66 deletions(-) diff --git a/noshx.cpp b/noshx.cpp index 09ae759..77a58b0 100644 --- a/noshx.cpp +++ b/noshx.cpp @@ -30,14 +30,16 @@ * * CHANGELOG * - * Noémi Shell X ver. 0.3 + * Noémi Shell X ver. 0.4 * - * This version introduces the 'snatch' command which can be used quite like 'apt install'. It can download commands via download links. - * From this version, the commands 'noemi' and 'noemi -l' are no longer included in this code, but can be installed by running 'snatch noemi'. - * This MIGHT change in the future, but for now, it is what it is. + * This version introduces a lot of new features. Firstly, all the commands available on the simple Nosh (for windows) is not available for NoshX too. + * (Plus many more of course. Nosh is not updated parallel to NoshX). This version introduces something I was planning on for a long time. + * That is autocomplete. To do that, nosh is using the appropriate GNU library. More GNU features are also available, just not tested. */ +// Includes + #include <iostream> #include <string> // Handling strings #include <vector> @@ -51,18 +53,69 @@ #include <cmath> #include <curl/curl.h> #include "snatch.h" +#include <readline/readline.h> // For autocomplete +#include <readline/history.h> // For autocomplete -const char VERSION[] = "0.3"; ///< Change version number here for the entire code +const char VERSION[] = "0.4"; ///< Change version number here for the entire code #define STRLEN 100 ///< Max string length #define WIDTH 10 ///< Width between columns char vault[STRLEN]; ///< Storing custom info - using namespace std; namespace fs = std::filesystem; +// These are the commands available for autocomplete +// Not all commands are available for autocomplete, to optimize for user experience + +vector<string> commands = { + "pwd", "about", "exec", "store", "snatch", + "help", "clear", "cls", "list", "tap", + "crush", "crush" +}; + +char **command_complete(const char *text, int start, int end) { + vector<string> matches; + + // If no text has been typed yet, return NULL to prevent autocomplete + if (start == 0 && text[0] == '\0') { + return nullptr; + } + + // Trim the input text to remove trailing spaces/tabs + string trimmed_text = string(text); + + // Handle built-in commands like "exit" + vector<string> builtins = {"exit", "help", "cd", "ls"}; // Add all your built-ins here + + for (const auto &command : builtins) { + if (command.find(trimmed_text) == 0) { + matches.push_back(command); + } + } + + // Find matches for the autocomplete based on the trimmed text + for (const auto &command : commands) { + if (command.find(trimmed_text) == 0) { + matches.push_back(command); + } + } + + // If no matches, return nullptr + if (matches.empty()) return nullptr; + + // Allocate memory for the results + char **result = (char **)malloc(sizeof(char *) * (matches.size() + 1)); + for (size_t i = 0; i < matches.size(); ++i) { + result[i] = strdup(matches[i].c_str()); + } + result[matches.size()] = nullptr; + + return result; +} + + // Declaring functions void handle_pwd(const vector<string>& args); bool is_builtin_command(const vector<string>& args); @@ -70,73 +123,93 @@ void handle_storage(const vector<string>& args); void handle_now(const vector<string>& args); void handle_cd(const vector<string>& args); void handle_snatch(const vector<string>& args); +void handle_list(const vector<string>& args); +void handle_tap(const vector<string>& args); +void handle_mkdir(const vector<string>& args); +void handle_crush(const vector<string>& args); +void handle_help(const vector<string>& args); +void handle_read(const vector<string>& args); +void handle_exec(const vector<string>& args); +void handle_grasp(const vector<string>& args); +void handle_rn(const vector<string>& args); // To be implemented commands: -//void handle_list(const vector<string>& args); -//void handle_read(const vector<string>& args, string filename); -//void handle_grasp(const vector<string>& args); -//void handle_tap(const vector<string>& args); -//void handle_crush(const vector<string>& args); -//void handle_mkdir(const vector<string>& args); -//void handle_rn(const vector<string>& args); -//void handle_exec(const vector<string>& args); -//void handle_help(const vector<string>& args); + + + /** * The main function */ int main() { - - cout << "Noémi Shell X (noshx) ver. " << VERSION << "\n" + cout << "Noémi Shell X (noshx) ver. 1.0\n" << "Copyright (C) 2025 Zsombor Szabo-Antalovszky\n\n" << "Type 'help' or '?' for list of available commands\n\n"; + // Set the completion function + rl_attempted_completion_function = command_complete; string command; // Storing command while (true) { - + // Get current working directory char buffer[PATH_MAX]; string cwd = getcwd(buffer, sizeof(buffer)); + // Check if the path is under the home directory const char* home = getenv("HOME"); if (home != nullptr) { string home_str = home; if (cwd.find(home_str) == 0) { // cwd starts with home cwd.replace(0, home_str.length(), "~"); } -} - - cout << "\033[1;33mnoshx: \033[0m" // Yellow "noshx:" - << "\033[1;34m" << cwd << "\033[0m" // Bold magenta cwd - << "$\033[0m "; // Normal > - - getline(cin, command); - - // On 'exit' command - if (command == "exit"){ + } + + // Set the prompt with color formatting + string prompt = "\033[1;33mnoshx: \033[0m" // Yellow "noshx:" + "\033[1;34m" + cwd + "\033[0m" // Bold magenta cwd + "$\033[0m "; // Normal > + + // Use readline for input with autocompletion + char *input = readline(prompt.c_str()); // Convert string to const char* using c_str() + + // If input is NULL, exit + if (input == nullptr) { + break; + } + + // Add the input to history + add_history(input); + + // Convert to string for easier handling + command = string(input); + free(input); // Free the input buffer after use + + // On 'exit' command, break the loop + if (command == "exit") { cout << "Exiting...\n"; break; } + // Split command into arguments vector<string> args; string temp; bool in_quotes = false; for (size_t i = 0; i < command.size(); ++i) { char ch = command[i]; - + if (ch == '"') { // Toggle in_quotes state on encountering a quote in_quotes = !in_quotes; - } + } else if (ch == ' ' && !in_quotes) { // Treat space as separator only if not in quotes if (!temp.empty()) { args.push_back(temp); temp.clear(); } - } + } else { temp += ch; } @@ -162,7 +235,6 @@ int main() { } } - return 0; // Terminate program with success } @@ -202,9 +274,9 @@ void handle_help(const vector<string>& args){ << "about --version: Displays shell version only\n" << "exec: Execute an executable file\n" << "exit: Exit the shell\n" - //<< "grasp -d: Search through directories for other directories\n" - //<< "grasp -f: Search through directories for files\n" - //<< "list: List the files or other directories whithin a directory\n" + << "grasp -d: Search through directories for other directories\n" + << "grasp -f: Search through directories for files\n" + << "list: List the files or other directories whithin a directory\n" << "mkdir: Create a new directory whithin current directory\n" << "noemi: Draw a heart shape made of # characters\n" << "noemi -l: Draw a heart shape made of # characters with more delay\n" @@ -212,7 +284,8 @@ void handle_help(const vector<string>& args){ //<< "rn: rename a file or a directory\n" << "snatch: Install additional available commands for NoshX\n" << "store: Save a string of maximum 100 characters during runtime\n" - << "store -g: Show the string saved with store\n\n"; + << "store -g: Show the string saved with store\n" + << "tap: Quickly create a file of any type\n\n"; } /** @@ -220,17 +293,27 @@ void handle_help(const vector<string>& args){ * * @param args for arguments */ -void handle_exec(const vector<string>& args){ - if (args.size() != 2){ +void handle_exec(const vector<string>& args) { + if (args.size() != 2) { cout << "Usage: exec <file_to_execute>\n"; - return; // Exit early + return; } string path = args[1]; - system(path.c_str()); // Runs the executable + // If no '/' in the path, assume current directory + if (path.find('/') == string::npos) { + path = "./" + path; + } + + int result = system(path.c_str()); + + if (result == -1) { + cout << "Failed to execute: " << path << endl; + } } + /** * Gets and prints out the current date * @@ -389,12 +472,12 @@ bool is_builtin_command(const vector<string>& args){ } if (args[0] == "exec"){ - //handle_exec(args); + handle_exec(args); return true; } if (args[0] == "list") { - //handle_list(args); + handle_list(args); return true; } @@ -404,32 +487,32 @@ bool is_builtin_command(const vector<string>& args){ } if (args[0] == "read") { - //handle_read(args, args[1]); + handle_read(args); return true; } if (args[0] == "grasp"){ - //handle_grasp(args); + handle_grasp(args); return true; } if (args[0] == "tap"){ - //handle_tap(args); + handle_tap(args); return true; } if (args[0] == "crush"){ - //handle_crush(args); + handle_crush(args); return true; } if (args[0] == "mkdir"){ - //handle_mkdir(args); + handle_mkdir(args); return true; } if (args[0] == "rn"){ - //handle_rn(args); + handle_rn(args); return true; } @@ -447,7 +530,7 @@ bool is_builtin_command(const vector<string>& args){ * @param args for arguments. */ -/* + void handle_list(const vector<string>& args){ fs::path target_path = fs::current_path(); @@ -472,7 +555,7 @@ void handle_list(const vector<string>& args){ // If there is an error reading directory cerr << "list: Error reading directory: " << e.what() << endl; } -}*/ +} /** * Handling the 'cd' command for changing directories @@ -532,14 +615,16 @@ void handle_list(const vector<string>& args){ * @param args for arguments. */ -/* -void handle_read(const vector<string>& args, string filename) { + +void handle_read(const vector<string>& args) { // If there is no argument after command if (args.size() != 2) { cout << "Usage: read <filename>\n"; return; // Exit early } + string filename = args[1]; + // Open the file for reading ifstream file(filename); if (!file.is_open()) { @@ -561,7 +646,8 @@ void handle_read(const vector<string>& args, string filename) { file.close(); // CLosing the file } -*/ + + /** * Handling the 'store' command for storing a string of maximum 100 characters during runtime. * @@ -589,13 +675,23 @@ void handle_storage(const vector<string>& args) { } } +string expand_path(const string& path) { + if (!path.empty() && path[0] == '~') { + const char* home = getenv("HOME"); + if (home != nullptr) { + return string(home) + path.substr(1); + } + } + return path; +} + + /** * Handling the 'grasp' command for filtering results * * @param args for arguments. */ -/* void handle_grasp(const vector<string>& args){ if (args.size() != 4){ cout << "Usage: grasp <type> <directory> <pattern>\n"; @@ -603,7 +699,7 @@ void handle_grasp(const vector<string>& args){ } const string type = args[1]; - const string dirPath = args[2]; + const string dirPath = expand_path(args[2]); const string pattern = args[3]; // IF the type argument is invalid @@ -660,7 +756,7 @@ void handle_grasp(const vector<string>& args){ } } -*/ + /** * Handling the 'tap' command for creating files. @@ -668,7 +764,7 @@ void handle_grasp(const vector<string>& args){ * @param args for arguments. */ -/* + void handle_tap(const vector<string>& args){ if (args.size() != 2){ cout << "Usage: tap <filename>\n"; @@ -686,14 +782,13 @@ void handle_tap(const vector<string>& args){ file.close(); } -*/ /** * Handling the 'crush' command for removing files and directories. * * @param args for arguments. */ -/* + void handle_crush(const vector<string>& args) { if (args.size() != 3){ @@ -745,7 +840,6 @@ void handle_crush(const vector<string>& args) { } } -*/ /** * Handling the 'mkdir' command for creating directories. @@ -753,7 +847,7 @@ void handle_crush(const vector<string>& args) { * @param args for arguments. */ -/* + void handle_mkdir(const vector<string>& args) { if (args.size() != 2){ cout << "Usage: mkdir <directory_name>\n"; @@ -762,25 +856,28 @@ void handle_mkdir(const vector<string>& args) { const string dirName = args[1]; - if (fs::exists(dirName)) { + if (fs::is_directory(dirName)) { cout << "Directory already exists: " << dirName << endl; return; } + if (fs::exists(dirName)){ + cout << "File already exists: " << dirName << endl; + return; + } + // Create the directory if (!fs::create_directory(dirName)) { cout << "Failed to create directory: " << dirName << endl; } } -*/ + /** * Handling the 'rn' command for renaming files and directories. * * @param args for arguments. */ - -/* void handle_rn(const vector<string>& args){ if (args.size() != 3){ cout << "Usage: rn <filename> <rename>\n"; @@ -798,4 +895,4 @@ void handle_rn(const vector<string>& args){ { cerr << "Error: " << e.what() << '\n'; } -} */ \ No newline at end of file +} \ No newline at end of file -- GitLab