diff --git a/CMakeLists.txt b/CMakeLists.txt index fe0ee7d..d9d4aa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,9 @@ project(shell) set(CMAKE_CXX_STANDARD 23) -add_executable(shell src/main.cpp) +add_executable(shell src/main.cpp + src/shellUtils.cpp + include/shellUtils.h) target_link_libraries(shell PRIVATE readline) +target_include_directories(shell PRIVATE include) \ No newline at end of file diff --git a/include/shellUtils.h b/include/shellUtils.h new file mode 100644 index 0000000..86d2a5c --- /dev/null +++ b/include/shellUtils.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace sh +{ + enum class Command + { + exit, + echo, + type, + unknown + }; + + inline const std::mapcommandMap + { + {"exit", Command::exit}, + {"echo", Command::echo}, + {"type", Command::type} + }; + + Command getCommand(std::string_view input); + + std::optional isExec(const std::string& input); + + void printType(const std::string& input); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index df28b27..754b1cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,46 @@ #include +#include +#include +#include +#include + +#include "shellUtils.h" int main() { - std::cout << "Hello, World!" << std::endl; - return 0; -} \ No newline at end of file + std::cout << std::unitbuf; + std::cerr << std::unitbuf; + + bool exit {false}; + while (!exit) + { + std::string input; + std::cout << std::unitbuf; + std::cout << "$ "; + std::getline(std::cin, input); + std::optional result; + switch (sh::Command command = sh::getCommand(input)) + { + case sh::Command::exit: + exit = true; + break; + case sh::Command::echo: + std::cout << input.substr(5) << "\n"; + break; + case sh::Command::unknown: + result = sh::isExec(input.substr(0, input.find(' '))); + if (result) + { + std::system(input.c_str()); + } + else + { + std::cout << input << ": command not found\n"; + } + break; + case sh::Command::type: + sh::printType(input.substr(5)); + break; + } + } +} diff --git a/src/shellUtils.cpp b/src/shellUtils.cpp new file mode 100644 index 0000000..59b9203 --- /dev/null +++ b/src/shellUtils.cpp @@ -0,0 +1,78 @@ +#include "shellUtils.h" + +#include +#include +#include +#include +#include + +namespace sh +{ + Command getCommand(std::string_view input) + { + if (commandMap.contains(input.substr(0, input.find(' ')))) + { + return commandMap.at(input.substr(0, input.find(' '))); + } + else + { + return Command::unknown; + } + } + + std::optional isExec(const std::string& input) + { + + const std::string pathEnv {std::getenv("PATH")}; +#if _WIN32 + constexpr char delimiter {';'}; +#else + constexpr char delimiter {':'}; +#endif + size_t startCharIndex{}; + size_t nextDelimIndex{pathEnv.find(delimiter)}; + while (nextDelimIndex != std::string::npos) + { + std::string currentPath {pathEnv.substr(startCharIndex, nextDelimIndex - startCharIndex)}; + std::string fullPath {currentPath + "/" + input}; + namespace fs = std::filesystem; + + if (!fs::exists(fullPath)) + { + startCharIndex = nextDelimIndex + 1; + nextDelimIndex = pathEnv.find(delimiter, startCharIndex); + continue; + } + + if ((fs::status(fullPath).permissions() & fs::perms::owner_exec) != fs::perms::owner_exec) + { + startCharIndex = nextDelimIndex + 1; + nextDelimIndex = pathEnv.find(delimiter, startCharIndex); + continue; + } + + return fullPath; + } + return std::nullopt; + } + + void printType(const std::string& input) + { + const std::string command {input.substr(0, input.find(' '))}; + if (commandMap.contains(command)) + { + std::cout << command; + std::cout << " is a shell builtin\n"; + return; + } + + if (auto fullPath {isExec(input)}) + { + std::cout << command << " is " << fullPath.value() << "\n"; + return; + } + + std::cout << command; + std::cout << ": not found\n"; + } +}