libcmo21/Unvirt/CmdHelper.cpp
2023-03-03 16:05:32 +08:00

726 lines
20 KiB
C++

#include "CmdHelper.hpp"
#include "TerminalHelper.hpp"
#include "StructFormatter.hpp"
#include "AccessibleValue.hpp"
#include <CKMinContext.hpp>
#include <CKFile.hpp>
#include <iostream>
#include <cstdio>
#include <cstdarg>
/* TODO:
do not re-allocated ctx and file for each loading in future.
this will be implemented by free all objects within doc.
*/
namespace Unvirt::CmdHelper {
#pragma region CmdSplitter
CmdSplitter::CmdSplitter() :
mCmdChar(0), mBuffer(nullptr), mResult(nullptr),
mState(StateType::NORMAL), mPreState(StateType::NORMAL) {
;
}
CmdSplitter::~CmdSplitter() {
;
}
const std::deque<std::string> CmdSplitter::Convert(const std::string& u8cmd) {
// set up variables
std::deque<std::string> result;
std::string buffer;
mBuffer = &buffer;
mResult = &result;
mState = mPreState = StateType::SPACE;
// split
for (auto it = u8cmd.begin(); it != u8cmd.end(); ++it) {
mCmdChar = (*it);
switch (mState) {
case StateType::SPACE:
ProcSpace();
break;
case StateType::SINGLE:
ProcSingle();
break;
case StateType::DOUBLE:
ProcDouble();
break;
case StateType::ESCAPE:
ProcEscape();
break;
case StateType::NORMAL:
ProcNormal();
break;
}
}
// final proc
switch (mState) {
case StateType::SPACE:
break;
case StateType::NORMAL:
// push the last one
mResult->push_back(*mBuffer);
break;
case StateType::SINGLE:
case StateType::DOUBLE:
case StateType::ESCAPE:
// error
result.clear();
break;
}
// return value
return result;
}
#pragma endregion
#pragma region ArgParser
bool ArgParser::ParseInt(const std::deque<std::string>& cmd, const size_t expected_index, int32_t& result) {
if (expected_index >= cmd.size()) {
result = 0;
return false;
}
char* pend = nullptr;
errno = 0;
int64_t v = std::strtoll(cmd[expected_index].c_str(), &pend, 10);
if (pend == cmd[expected_index].c_str() || errno == ERANGE) {
result = 0;
return false;
}
result = static_cast<int>(v);
return true;
}
bool ArgParser::ParseString(const std::deque<std::string>& cmd, const size_t expected_index, std::string& result) {
if (expected_index >= cmd.size()) {
result.clear();
return false;
} else {
result = cmd[expected_index];
return true;
}
}
bool ArgParser::ParseSwitch(const std::deque<std::string>& cmd, const size_t expected_index, const std::vector<std::string>& switches, std::string& gotten) {
if (expected_index >= cmd.size()) {
gotten.clear();
return false;
}
for (const auto& sw : switches) {
if (cmd[expected_index] == sw) {
gotten = cmd[expected_index];
return true;
}
}
gotten.clear();
return false;
}
#pragma endregion
#pragma region InteractiveCmd
InteractiveCmd::InteractiveCmd() :
m_CmdSplitter(), m_PageLen(10),
m_Ctx(nullptr), m_File(nullptr), m_Doc(nullptr) {
}
InteractiveCmd::~InteractiveCmd() {
if (m_Doc != nullptr) delete m_Doc;
if (m_File != nullptr) delete m_File;
if (m_Ctx != nullptr) delete m_Ctx;
}
void InteractiveCmd::Run(void) {
std::string u8cmd;
while (true) {
// get command
GetCmdLine(u8cmd);
// split cmd and parse it
auto cmds = m_CmdSplitter.Convert(u8cmd);
// get sub command
if (cmds.size() < 1u) {
this->PrintCommonError("No command specified!");
this->PrintHelp();
continue;
}
std::string subcmd(cmds.front());
cmds.pop_front();
// dispatch command
if (subcmd == "load") this->ProcLoad(cmds);
else if (subcmd == "unload") this->ProcUnLoad(cmds);
else if (subcmd == "info") this->ProcInfo(cmds);
else if (subcmd == "ls") this->ProcLs(cmds);
else if (subcmd == "page") this->ProcPage(cmds);
else if (subcmd == "help") this->PrintHelp();
else if (subcmd == "exit") break;
else {
this->PrintCommonError("No such command \"\".", subcmd.c_str());
this->PrintHelp();
}
}
}
void InteractiveCmd::GetCmdLine(std::string& u8cmd) {
fputs("Unvirt> ", stdout);
#if defined(LIBCMO_OS_WIN32)
std::wstring wcmd;
std::getline(std::wcin, wcmd);
LibCmo::EncodingHelper::WcharToChar(wcmd, u8cmd, CP_UTF8);
#else
std::getline(std::cin, u8cmd);
#endif
}
bool InteractiveCmd::HasOpenedFile(void) {
return (m_Ctx != nullptr || m_File == nullptr || m_Doc != nullptr);
}
void InteractiveCmd::PrintHelp(void) {
FILE* f = stdout;
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("Allowed Subcommands: \n")), f);
fputs("load\n", f);
fputs("\tDescription: Load a Virtools composition.\n", f);
fputs("\tSyntax: load <file path> [encoding] [temp path]\n", f);
fputs("unload\n", f);
fputs("\tDescription: Release loaded Virtools composition.\n", f);
fputs("\tSyntax: unload\n", f);
fputs("info\n", f);
fputs("\tDescription: Show the header info of loaded Virtools composition.\n", f);
fputs("\tSyntax: info\n", f);
fputs("ls\n", f);
fputs("\tDescription: List something about loaded Virtools composition.\n", f);
fputs("\tSyntax: ls <obj | mgr> [page]\n", f);
fputs("page\n", f);
fputs("\tDescription: Set up how many items should be listed in one page when using \"ls\" command.\n", f);
fputs("\tSyntax: page <num>\n", f);
fputs("exit\n", f);
fputs("\tDescription: Exit program\n", f);
fputs("\tSyntax: exit\n", f);
}
void InteractiveCmd::PrintArgParseError(const std::deque<std::string>& cmd, size_t pos) {
if (pos >= cmd.size()) {
fprintf(stdout, UNVIRT_TERMCOL_LIGHT_RED(("Lost argument at position %zu.\n")), pos);
} else {
fprintf(stdout, UNVIRT_TERMCOL_LIGHT_RED(("Unexpected argument \"%s\".\n")), cmd[pos].c_str());
}
// arg error always print help
this->PrintHelp();
}
void InteractiveCmd::PrintCommonError(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
std::fputs(UNVIRT_TERMCOLHDR_LIGHT_RED, stdout);
std::vfprintf(stdout, u8_fmt, argptr);
std::fputs(UNVIRT_TERMCOLTAIL, stdout);
va_end(argptr);
}
#pragma endregion
#pragma region Command Processors
void InteractiveCmd::ProcLoad(const std::deque<std::string>& cmd) {
// check pre-requirement
if (HasOpenedFile()) {
this->PrintCommonError("Already have a opened file. Close it before calling \"load\".");
return;
}
// check requirement
size_t pos = 0u;
std::string filepath;
if (!ArgParser::ParseString(cmd, pos, filepath)) {
this->PrintArgParseError(cmd, pos);
return;
}
++pos;
std::string encoding;
if (!ArgParser::ParseString(cmd, pos, encoding)) {
this->PrintArgParseError(cmd, pos);
return;
}
++pos;
std::string temppath;
if (!ArgParser::ParseString(cmd, pos, temppath)) {
this->PrintArgParseError(cmd, pos);
return;
}
// proc
m_Ctx = new LibCmo::CK2::CKMinContext();
m_Ctx->SetEncoding(encoding.c_str());
m_Ctx->SetTempPath(temppath.c_str());
m_File = new LibCmo::CK2::CKFile(m_Ctx);
m_Doc = new LibCmo::CK2::CKFileDocument();
LibCmo::CK2::CKERROR err = m_File->DeepLoad(filepath.c_str(), &m_Doc);
if (err != LibCmo::CK2::CKERROR::CKERR_OK) {
// fail to load. release all.
this->PrintCommonError("Fail to open file. Function return: %s\n%s",
Unvirt::AccessibleValue::GetCkErrorName(err),
Unvirt::AccessibleValue::GetCkErrorDescription(err)
);
if (m_Doc != nullptr) delete m_Doc;
if (m_File != nullptr) delete m_File;
if (m_Ctx != nullptr) delete m_Ctx;
}
}
void InteractiveCmd::ProcUnLoad(const std::deque<std::string>& cmd) {
// check pre-requirement
if (!HasOpenedFile()) {
this->PrintCommonError("No loaded file.");
return;
}
// free all
if (m_Doc != nullptr) delete m_Doc;
if (m_File != nullptr) delete m_File;
if (m_Ctx != nullptr) delete m_Ctx;
}
void InteractiveCmd::ProcInfo(const std::deque<std::string>& cmd) {
// check pre-requirement
if (!HasOpenedFile()) {
this->PrintCommonError("No loaded file.");
return;
}
// print
Unvirt::StructFormatter::PrintCKFileInfo(m_Doc->m_FileInfo);
}
void InteractiveCmd::ProcLs(const std::deque<std::string>& cmd) {
static const std::vector<std::string> c_AllowedSwitches{
"obj", "mgr"
};
}
void InteractiveCmd::ProcPage(const std::deque<std::string>& cmd) {
// check requirement
size_t pos = 0u;
int32_t count;
if (!ArgParser::ParseInt(cmd, pos, count) || count <= 0) {
this->PrintArgParseError(cmd, pos);
return;
}
// assign
m_PageLen = static_cast<size_t>(count);
}
#pragma endregion
/*
#pragma region OptionsDescription
OptionsDescription::OptionsDescription() :
mLongNameDict(), mShortNameMapping(), mPositionalArgMapping() {
;
}
OptionsDescription::~OptionsDescription() { ; }
void OptionsDescription::AddOption(
const char* fullname, char shortname, CmdArgType type, const char* description) {
// pre-check
if (fullname == nullptr ||
fullname[0] == '\0' ||
fullname[0] == '-')
throw std::invalid_argument("Invalid Option Long Name.");
// construct data
std::string sfullname(fullname);
OptionDescription data{
fullname, shortname, type, (description != nullptr ? description : "")
};
// check requirement
if (mLongNameDict.contains(sfullname)) throw std::invalid_argument("Duplicated Option Long Name.");
if (shortname != '\0')
if (mShortNameMapping.contains(shortname)) throw std::invalid_argument("Duplicated Option Short Name.");
// add them
mShortNameMapping.emplace(shortname, sfullname);
mLongNameDict.emplace(sfullname, std::move(data));
}
void OptionsDescription::AddPositionalOption(const char* corresponding_longname) {
// pre-check
if (corresponding_longname == nullptr) throw std::invalid_argument("Invalid Option Long Name.");
// construct data
std::string fullname(corresponding_longname);
// check requirement
if (!mLongNameDict.contains(fullname)) throw std::invalid_argument("No Matched Option.");
if (!mPositionalArgMapping.empty()) {
for (const auto& i : mPositionalArgMapping) {
if (i == fullname)throw std::invalid_argument("Duplicated Option.");
}
}
// set value
mPositionalArgMapping.push_back(std::move(fullname));
}
OptionDescription* OptionsDescription::GetDescByLongName(const std::string& longname) {
const auto search = mLongNameDict.find(longname);
if (search == mLongNameDict.end()) return nullptr;
return &(*search).second;
}
OptionDescription* OptionsDescription::GetDescByShortName(const char shortname) {
const auto search = mShortNameMapping.find(shortname);
if (search == mShortNameMapping.end()) return nullptr;
return GetDescByLongName((*search).second);
}
OptionDescription* OptionsDescription::GetDescByPosition(size_t pos) {
if (pos >= mPositionalArgMapping.size()) return nullptr;
return GetDescByLongName(mPositionalArgMapping[pos]);
}
void OptionsDescription::PrintHelp(FILE* f) {
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("Allowed Options: \n")), f);
for (const auto& [key, value] : mLongNameDict) {
fprintf(f, "--%s\t%s\n", value.mLongName.c_str(), value.mDescription.c_str());
}
if (!mPositionalArgMapping.empty()) {
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("\nPositional Options: \n")), f);
for (const auto& i : mPositionalArgMapping) {
fprintf(f, "[%s] ", i.c_str());
}
}
}
#pragma endregion
#pragma region VariablesMap
VariablesMap::VariablesMap() : mDataPair() {
;
}
VariablesMap::~VariablesMap() {
this->Clear();
}
void VariablesMap::Clear(void) {
for (const auto& [key, value] : mDataPair) {
if (value.mData != nullptr) free(value.mData);
}
mDataPair.clear();
}
bool VariablesMap::AddPair(const std::string& name, CmdArgType t, const std::string& val) {
if (mDataPair.contains(name)) return false;
AnyVariable var;
switch (t) {
case Unvirt::CmdHelper::CmdArgType::NONE:
{
var.mDataBasicSize = 1;
var.mData = nullptr;
break;
}
case Unvirt::CmdHelper::CmdArgType::STRING:
{
var.mDataBasicSize = sizeof(char);
var.mData = malloc(val.size() + 1);
if (var.mData == nullptr) break;
memcpy(var.mData, val.c_str(), val.size() + 1);
break;
}
case Unvirt::CmdHelper::CmdArgType::INT:
{
var.mDataBasicSize = sizeof(int);
var.mData = malloc(sizeof(int));
if (var.mData == nullptr) break;
char* pend = nullptr;
errno = 0;
int64_t v = std::strtoll(val.c_str(), &pend, 10);
if (pend == val.c_str() || errno == ERANGE) v = INT64_C(0);
*((int*)var.mData) = static_cast<int>(v);
break;
}
default:
throw std::invalid_argument("Invalid Option Type.");
}
mDataPair.emplace(name, std::move(var));
return true;
}
#pragma endregion
#pragma region ExecEnvironment
ExecEnvironment::ExecEnvironment() :
mVtFile(nullptr), mVtFileEnv(nullptr) {
;
}
ExecEnvironment::~ExecEnvironment() {
if (mVtFile != nullptr) delete mVtFile;
if (mVtFileEnv != nullptr) delete mVtFileEnv;
}
void ExecEnvironment::ProcLoad(OptionsDescription& od, VariablesMap& vm) {
if (mVtFile != nullptr || mVtFileEnv != nullptr) {
printf(UNVIRT_TERMCOL_LIGHT_RED(("Please close current opened Vrtools file first.\n")));
return;
}
const char* filename = vm.GetData<char>("file");
if (filename == nullptr) {
printf(UNVIRT_TERMCOL_LIGHT_RED(("You should specify a file first.\n")));
od.PrintHelp(stdout);
return;
}
mVtFileEnv = new LibCmo::Utils::VirtoolsContext();
const char* enc = vm.GetData<char>("encoding");
mVtFileEnv->NameEncoding = enc == nullptr ? "" : enc;
mVtFile = new LibCmo::CKFile(*mVtFileEnv);
mVtFile->Load(filename, LibCmo::CK_LOAD_FLAGS::CK_LOAD_DEFAULT);
}
void ExecEnvironment::ProcInfo(OptionsDescription& od, VariablesMap& vm) {
printf(UNVIRT_TERMCOL_LIGHT_RED(("Sorry. This feature is not supported now.\n")));
}
void ExecEnvironment::ProcClear(OptionsDescription& od, VariablesMap& vm) {
if (mVtFile == nullptr && mVtFileEnv == nullptr) {
printf(UNVIRT_TERMCOL_LIGHT_RED(("Virtools file already is empty.\n")));
return;
}
if (mVtFile != nullptr) delete mVtFile;
if (mVtFileEnv != nullptr) delete mVtFileEnv;
mVtFile = nullptr;
mVtFileEnv = nullptr;
}
void ExecEnvironment::ProcExportSql(OptionsDescription& od, VariablesMap& vm) {
printf(UNVIRT_TERMCOL_LIGHT_RED(("Sorry. This feature is not supported now.\n")));
}
#pragma endregion
#pragma region InteractiveCmd
InteractiveCmd::InteractiveCmd() :
mCmdDispatcher(), mExecEnv(), mVm(), mBlank(), mExitRunFlag(false), mCmdSplitter() {
// add load subcommand
CmdRegisteryEntry entryLoad;
const std::string entryLoadName = "load";
entryLoad.mSubCmdDesc = "Load Virtools file.";
entryLoad.mOptDesc.AddOption("file", 'f', CmdArgType::STRING, "The loaded Virtools file.");
entryLoad.mOptDesc.AddPositionalOption("file");
entryLoad.mOptDesc.AddOption("encoding", 'e', CmdArgType::STRING, "The encoding to decode Virtools string.");
entryLoad.mOptDesc.AddPositionalOption("encoding");
entryLoad.mOptDesc.AddOption("temp", 't', CmdArgType::STRING, "The temp folder used by engine.");
entryLoad.mOptDesc.AddPositionalOption("temp");
entryLoad.mBindProc = std::bind(&ExecEnvironment::ProcLoad, &this->mExecEnv, std::placeholders::_1, std::placeholders::_2);
//mCmdDispatcher.emplace("load", std::move(entryLoad));
CmdRegisteryEntry entryInfo;
entryInfo.mSubCmdDesc = "Show loaded Virtools file header info.";
entryInfo.mBindProc = std::bind(&ExecEnvironment::ProcInfo, &this->mExecEnv, std::placeholders::_1, std::placeholders::_2);
//mCmdDispatcher.emplace("info", std::move(entryInfo));
CmdRegisteryEntry entryClear;
entryClear.mSubCmdDesc = "Clear current loaded Virtools file.";
entryClear.mBindProc = std::bind(&ExecEnvironment::ProcClear, &this->mExecEnv, std::placeholders::_1, std::placeholders::_2);
//mCmdDispatcher.emplace("clear", std::move(entryClear));
CmdRegisteryEntry entryExportSql;
entryExportSql.mSubCmdDesc = "Export loaded Virtools file as a SQList database file.";
entryExportSql.mOptDesc.AddOption("file", 'f', CmdArgType::STRING, "The exported SQL file.");
entryExportSql.mOptDesc.AddPositionalOption("file");
entryExportSql.mBindProc = std::bind(&ExecEnvironment::ProcExportSql, &this->mExecEnv, std::placeholders::_1, std::placeholders::_2);
//mCmdDispatcher.emplace("sql", std::move(entryExportSql));
CmdRegisteryEntry entryExit;
entryExit.mSubCmdDesc = "Exit this interactive commander.";
entryExit.mBindProc = std::bind(&InteractiveCmd::ProcExit, this, std::placeholders::_1, std::placeholders::_2);
//mCmdDispatcher.emplace("exit", std::move(entryExit));
}
InteractiveCmd::~InteractiveCmd() {
;
}
void InteractiveCmd::Run(void) {
std::string u8cmd;
mExitRunFlag = false;
while (!mExitRunFlag) {
// get command
GetCmdLine(u8cmd);
// split cmd and parse it
CmdParser(mCmdSplitter.Convert(u8cmd));
}
}
void InteractiveCmd::GetCmdLine(std::string& u8cmd) {
#if defined(LIBCMO_OS_WIN32)
std::wstring wcmd;
std::getline(std::wcin, wcmd);
LibCmo::Encoding::WcharToChar(wcmd, u8cmd, CP_UTF8);
#else
std::getline(std::cin, u8cmd);
#endif
}
void InteractiveCmd::CmdParser(const std::vector<std::string>& args) {
FILE* f = stdout;
if (args.size() == 0) {
fputs(UNVIRT_TERMCOL_LIGHT_RED(("Error! Fail to get subcommand token.\n")), f);
PrintHelp(f);
return;
}
auto arg = args.begin();
auto subcmd = mCmdDispatcher.find(*arg);
if (subcmd == mCmdDispatcher.end()) {
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! No such subcommand \"%s\"! \n")), arg->c_str());
PrintHelp(f);
return;
}
// analyze options
++arg;
auto& optsDesc = subcmd->second.mOptDesc;
mVm.Clear();
int position_counter = 0;
while (true) {
if (arg == args.end()) break;
const std::string& opt = *arg;
OptionDescription* optDesc;
if (opt.starts_with("--")) {
// long name
optDesc = optsDesc.GetDescByLongName(opt.substr(2));
} else if (opt.starts_with("-")) {
// short name
if (opt.size() != 2u) {
// invalid short name
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! Invalid short name option \"%s\"! \n")), opt.c_str());
optsDesc.PrintHelp(f);
return;
}
optDesc = optsDesc.GetDescByShortName(opt[1]);
} else {
// position
optDesc = optsDesc.GetDescByPosition(position_counter++);
}
// invalid option
if (optDesc == nullptr) {
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! Invalid option \"%s\"! \n")), opt.c_str());
optsDesc.PrintHelp(f);
return;
}
// get value
bool add_success = true;
switch (optDesc->mType) {
case CmdArgType::NONE:
// just a switch
add_success = mVm.AddPair(optDesc->mLongName, optDesc->mType, this->mBlank);
++arg;
if (!add_success) {
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! Duplicated option \"%s\"! \n")), opt.c_str());
optsDesc.PrintHelp(f);
return;
}
break;
case CmdArgType::INT:
case CmdArgType::STRING:
// check next value
++arg;
if (arg == args.end()) {
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! Option \"%s\" lost parameter! \n")), opt.c_str());
optsDesc.PrintHelp(f);
return;
}
add_success = mVm.AddPair(optDesc->mLongName, optDesc->mType, *arg);
++arg;
if (!add_success) {
fprintf(f, UNVIRT_TERMCOL_LIGHT_RED(("Error! Duplicated option \"%s\"! \n")), opt.c_str());
optsDesc.PrintHelp(f);
return;
}
break;
default:
throw std::invalid_argument("Invalid Option Type.");
}
}
// execute proc
subcmd->second.mBindProc(optsDesc, mVm);
}
void InteractiveCmd::PrintHelp(FILE* f) {
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("Allowed Subcommands: \n")), f);
for (const auto& [key, value] : mCmdDispatcher) {
fprintf(f, "%s\t- %s\n", key.c_str(), value.mSubCmdDesc.c_str());
}
}
void InteractiveCmd::ProcExit(OptionsDescription&, VariablesMap&) {
mExitRunFlag = true;
}
#pragma endregion
*/
}