added pwd, cd, and now handles escape charaters and quotes, still need to test windows support
This commit is contained in:
parent
87839ef981
commit
328b96a33e
|
|
@ -5,6 +5,7 @@
|
|||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace sh
|
||||
{
|
||||
|
|
@ -13,19 +14,34 @@ namespace sh
|
|||
exit,
|
||||
echo,
|
||||
type,
|
||||
pwd,
|
||||
cd,
|
||||
unknown
|
||||
};
|
||||
|
||||
|
||||
inline const std::map<const std::string_view, const Command>commandMap
|
||||
{
|
||||
{"exit", Command::exit},
|
||||
{"echo", Command::echo},
|
||||
{"type", Command::type}
|
||||
{"exit", Command::exit},
|
||||
{"echo", Command::echo},
|
||||
{"type", Command::type},
|
||||
{"pwd" , Command::pwd },
|
||||
{"cd" , Command::cd }
|
||||
};
|
||||
|
||||
inline constexpr std::string_view doubleQuoteEscapeChars = "\"$`\n\\";
|
||||
|
||||
inline constexpr std::string_view tokenDelimiters = " \t";
|
||||
|
||||
void tokenize(std::vector<std::string>& tokens, const std::string& input);
|
||||
|
||||
Command getCommand(std::string_view input);
|
||||
|
||||
std::optional<std::filesystem::path> isExec(const std::string& input);
|
||||
|
||||
void printType(const std::string& input);
|
||||
|
||||
std::filesystem::path getWorkingDir();
|
||||
|
||||
void changeDir(const std::vector<std::string>& tokens);
|
||||
}
|
||||
162
src/main.cpp
162
src/main.cpp
|
|
@ -3,49 +3,141 @@
|
|||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#include "shellUtils.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#define WIN_EXEC .append(".exe") // executables on windows need .exe to run
|
||||
#else
|
||||
#define WIN_EXEC
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << std::unitbuf;
|
||||
std::cerr << std::unitbuf;
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
bool exit {false};
|
||||
while (!exit)
|
||||
std::cout << std::unitbuf;
|
||||
std::cerr << std::unitbuf;
|
||||
|
||||
bool shouldExit {false};
|
||||
while (!shouldExit)
|
||||
{
|
||||
tokens.clear();
|
||||
|
||||
std::string input;
|
||||
std::string line;
|
||||
bool finshedInput{};
|
||||
|
||||
std::cout << "$ ";
|
||||
|
||||
while (!finshedInput and std::getline(std::cin, line))
|
||||
{
|
||||
std::string input;
|
||||
std::cout << "$ ";
|
||||
std::getline(std::cin, input);
|
||||
std::optional<std::filesystem::path> result;
|
||||
switch (sh::Command command = sh::getCommand(input))
|
||||
auto end = line.size() - 1;
|
||||
if (line[end] == '\\')
|
||||
{
|
||||
if (end == 0)
|
||||
{
|
||||
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(' '))WIN_EXEC);
|
||||
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)WIN_EXEC);
|
||||
break;
|
||||
std::cout << "> " ;
|
||||
}
|
||||
else
|
||||
{
|
||||
input += line.substr(0, line.size() - 1);
|
||||
std::cout << "> ";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
input += line;
|
||||
finshedInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
//trim whitespace and tabs from start and end
|
||||
const size_t start = input.find_first_not_of(" \t");
|
||||
if (start == std::string::npos)
|
||||
{// the input was all whitespace and tabs
|
||||
input.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t end = input.find_last_not_of(" \t");
|
||||
input = input.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
if (input.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//we can now assume there is actual text from the input
|
||||
sh::tokenize(tokens, input);
|
||||
|
||||
std::optional<std::filesystem::path> result;
|
||||
|
||||
switch (sh::Command command = sh::getCommand(tokens[0]))
|
||||
{
|
||||
case sh::Command::exit:
|
||||
shouldExit = true;
|
||||
break;
|
||||
case sh::Command::echo:
|
||||
for (int i = 1; i < tokens.size(); i++)
|
||||
{
|
||||
std::cout << tokens[i] << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
break;
|
||||
case sh::Command::unknown:
|
||||
result = sh::isExec(tokens[0]);
|
||||
if (result)
|
||||
{
|
||||
std::vector<char*> args;
|
||||
args.push_back(const_cast<char*>(result.value().filename().c_str()));// have exe path on seperate element for execv
|
||||
|
||||
for (size_t i = 1; i < tokens.size(); i++)
|
||||
{
|
||||
args.push_back(const_cast<char*>(tokens[i].c_str()));
|
||||
}
|
||||
|
||||
args.push_back(nullptr);
|
||||
#ifdef _WIN32
|
||||
intptr_t status = _spawnv(_P_WAIT, result.value().c_str(), args.data());
|
||||
|
||||
if (status == -1)
|
||||
{
|
||||
perror("\n\nError executing program\n\n");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
pid_t pid = fork();
|
||||
if (pid == 0)
|
||||
{// child process
|
||||
execv(result.value().c_str(), args.data());
|
||||
perror("\n\nError executing program\n\n");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << tokens[0] << ": command not found\n";
|
||||
}
|
||||
break;
|
||||
case sh::Command::type:
|
||||
sh::printType(tokens[1]);
|
||||
break;
|
||||
case sh::Command::pwd:
|
||||
std::cout << std::filesystem::current_path().string() << "\n";
|
||||
break;
|
||||
case sh::Command::cd:
|
||||
sh::changeDir(tokens);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,90 @@
|
|||
#include "shellUtils.h"
|
||||
|
||||
#include <complex>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define HOME "USERPROFILE"
|
||||
#else
|
||||
#define HOME "HOME"
|
||||
#endif
|
||||
|
||||
|
||||
namespace sh
|
||||
{
|
||||
void tokenize(std::vector<std::string>& tokens, const std::string& input)
|
||||
{
|
||||
bool inSingleQuote{false};
|
||||
bool inDoubleQuote{false};
|
||||
|
||||
std::string currentToken;
|
||||
for (size_t i = 0; i < input.size(); i++)
|
||||
{
|
||||
const char c = input[i];
|
||||
if (c == '\\' and !inSingleQuote)
|
||||
{
|
||||
if (i + 1 < input.size())
|
||||
{
|
||||
if (inDoubleQuote and (doubleQuoteEscapeChars.find(input[i+1]) == std::string::npos))
|
||||
{
|
||||
currentToken += input[i];
|
||||
}
|
||||
currentToken += input[i+1];
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\'' and !inDoubleQuote)
|
||||
{
|
||||
inSingleQuote = !inSingleQuote;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"' and !inSingleQuote)
|
||||
{
|
||||
inDoubleQuote = !inDoubleQuote;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tokenDelimiters.find(c) != std::string::npos) and !inSingleQuote and !inDoubleQuote)
|
||||
{
|
||||
if (!currentToken.empty())
|
||||
{
|
||||
tokens.emplace_back(currentToken);
|
||||
currentToken.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
currentToken += c;
|
||||
}
|
||||
|
||||
if (!currentToken.empty())
|
||||
{
|
||||
tokens.emplace_back(currentToken);
|
||||
}
|
||||
|
||||
// std::istringstream tokens {input};
|
||||
// std::string currentToken;
|
||||
// while (tokens >> currentToken)
|
||||
// {
|
||||
// // check for escaped white space
|
||||
// if (currentToken.back() == '\\')
|
||||
// {//add whitespace to token
|
||||
// currentToken.append(1, ' ');
|
||||
// }
|
||||
// tokenContainer.emplace_back(currentToken);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
Command getCommand(std::string_view input)
|
||||
{
|
||||
if (commandMap.contains(input.
|
||||
substr(0, input.find(' '))))
|
||||
if (commandMap.contains(input.substr(0, input.find(' '))))
|
||||
{
|
||||
return commandMap.at(input.substr(0, input.find(' ')));
|
||||
}
|
||||
|
|
@ -24,36 +97,44 @@ namespace sh
|
|||
std::optional<std::filesystem::path> isExec(const std::string& input)
|
||||
{
|
||||
|
||||
const std::string_view pathEnv {std::getenv("PATH")};
|
||||
#if WIN32
|
||||
std::string pathEnv {std::getenv("PATH")};
|
||||
#ifdef _WIN32
|
||||
constexpr char delimiter {';'};
|
||||
#else
|
||||
constexpr char delimiter {':'};
|
||||
#endif
|
||||
//add our current path, put a delimiter so std::string::find doesn't skip the last token
|
||||
pathEnv.append(1, delimiter);
|
||||
pathEnv.append(std::filesystem::current_path().string());
|
||||
pathEnv.append(1, delimiter);
|
||||
|
||||
size_t startCharIndex{};
|
||||
size_t nextDelimIndex{pathEnv.find(delimiter)};
|
||||
std::filesystem::path currentPath;
|
||||
|
||||
while (nextDelimIndex != std::string::npos)
|
||||
{
|
||||
std::filesystem::path currentPath {pathEnv.substr(startCharIndex, nextDelimIndex - startCharIndex)};
|
||||
std::filesystem::path fullPath;
|
||||
fullPath = currentPath / input;
|
||||
|
||||
currentPath = pathEnv.substr(startCharIndex, nextDelimIndex - startCharIndex);
|
||||
currentPath /= input;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
if (!fs::exists(fullPath))
|
||||
if (!fs::exists(currentPath))
|
||||
{
|
||||
startCharIndex = nextDelimIndex + 1;
|
||||
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((fs::status(fullPath).permissions() & fs::perms::owner_exec) != fs::perms::owner_exec)
|
||||
{
|
||||
if ((fs::status(currentPath).permissions() & fs::perms::owner_exec) != fs::perms::owner_exec)
|
||||
{// fullPath exists but cant executed
|
||||
startCharIndex = nextDelimIndex + 1;
|
||||
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
return currentPath;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
@ -70,11 +151,62 @@ namespace sh
|
|||
if (const auto fullPath {isExec(input)})
|
||||
{
|
||||
std::cout << input;
|
||||
std::cout << " is " << fullPath.value() << "\n";
|
||||
std::cout << " is " << fullPath.value().string() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << input;
|
||||
std::cout << ": not found\n";
|
||||
}
|
||||
|
||||
void changeDir(const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() > 2)
|
||||
{
|
||||
std::cerr << "cd: too many arguments\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokens.size() < 2)
|
||||
{// no arguments go home
|
||||
#if _WIN32
|
||||
return;
|
||||
#endif
|
||||
const char* result = nullptr;
|
||||
result = std::getenv(HOME);
|
||||
if (result == nullptr) //might not have home defined
|
||||
{
|
||||
result = "/";
|
||||
}
|
||||
std::filesystem::current_path(result);
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path expandedPath = tokens[1];
|
||||
if (tokens[1].front() == '~')
|
||||
{
|
||||
const char* result = nullptr;
|
||||
result = std::getenv(HOME);
|
||||
if (result == nullptr) //might not have home defined
|
||||
{
|
||||
result = "/";
|
||||
}
|
||||
expandedPath = result;
|
||||
if (tokens[1].size() > 1)
|
||||
{
|
||||
expandedPath /= tokens[1].substr(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(expandedPath))
|
||||
{
|
||||
std::filesystem::current_path(expandedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "cd: " << expandedPath.string() << ": No such file or directory\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue