#include "CmdHelper.hpp" #include "TerminalHelper.hpp" #include "StructFormatter.hpp" #include "AccessibleValue.hpp" #include #include #include #include #include /* TODO: do not re-allocated ctx and file for each loading in future. this will be implemented by free all objects within doc. split encoding and temp folder setting and load command only have file name arg. */ 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 CmdSplitter::Convert(const std::string& u8cmd) { // set up variables std::deque result; std::string buffer; mBuffer = &buffer; mResult = &result; mState = mPreState = StateType::SPACE; // split for (auto it = u8cmd.begin(); it != u8cmd.end(); ++it) { mCmdChar = (*it); if (!std::isprint(mCmdChar)) continue; // skip all invalid characters 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& 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(v); return true; } bool ArgParser::ParseString(const std::deque& 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& cmd, const size_t expected_index, const std::vector& 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 == "items") this->ProcItems(cmds); else if (subcmd == "help") this->PrintHelp(); else if (subcmd == "exit") break; else { this->PrintCommonError("No such command \"%s\".", 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 [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 [page]\n", f); fputs("items\n", f); fputs("\tDescription: Set up how many items should be listed in one page when using \"ls\" command.\n", f); fputs("\tSyntax: items \n", f); fputs("exit\n", f); fputs("\tDescription: Exit program\n", f); fputs("\tSyntax: exit\n", f); } void InteractiveCmd::PrintArgParseError(const std::deque& 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); std::fputc('\n', stdout); } #pragma endregion #pragma region Command Processors void InteractiveCmd::ProcLoad(const std::deque& 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).c_str(), Unvirt::AccessibleValue::GetCkErrorDescription(err).c_str() ); if (m_Doc != nullptr) delete m_Doc; if (m_File != nullptr) delete m_File; if (m_Ctx != nullptr) delete m_Ctx; m_Doc = nullptr; m_File = nullptr; m_Ctx = nullptr; } } void InteractiveCmd::ProcUnLoad(const std::deque& 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; m_Doc = nullptr; m_File = nullptr; m_Ctx = nullptr; } void InteractiveCmd::ProcInfo(const std::deque& 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& cmd) { // static values of switches static const std::vector c_AllowedSwitches{ "obj", "mgr" }; // check pre-requirement if (!HasOpenedFile()) { this->PrintCommonError("No loaded file."); return; } // check requirement size_t pos = 0u; std::string sw; if (!ArgParser::ParseSwitch(cmd, pos, c_AllowedSwitches, sw)) { this->PrintArgParseError(cmd, pos); return; } ++pos; int32_t gotten_page; if (!ArgParser::ParseInt(cmd, pos, gotten_page) || gotten_page <= 0) { gotten_page = 0; // asssume as zero } size_t page = static_cast(gotten_page); // show list if (sw == c_AllowedSwitches[0]) { // obj list if (page * this->m_PageLen >= m_Doc->m_FileObjects.size()) { this->PrintCommonError("Page out of range."); return; } Unvirt::StructFormatter::PrintObjectList(this->m_Doc->m_FileObjects, page, this->m_PageLen); } else { // mgr list if (page * this->m_PageLen >= m_Doc->m_FileManagersData.size()) { this->PrintCommonError("Page out of range."); return; } Unvirt::StructFormatter::PrintManagerList(this->m_Doc->m_FileManagersData, page, this->m_PageLen); } } void InteractiveCmd::ProcItems(const std::deque& 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(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(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("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("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& 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 */ }