update cmd dispatcher

This commit is contained in:
yyc12345 2023-08-27 12:30:12 +08:00
parent 8a1f71e855
commit 3252e61c0d
8 changed files with 921 additions and 518 deletions

View File

@ -4,7 +4,7 @@ The Library for CMO File Read/Write. Also the Minimalist Virtools Environment.
## Status
This is a long time project and I am stucked at the analyze of CKBeObject. I don't know its class layout, And I don't know its complex `CKBeObject::Save()`.
This is a long time project.
This project welcome everyone's contribution, except the employee of Dassault, which created Virtools.
## Introduction
@ -35,9 +35,8 @@ There are 3 lists which indicate our accept guideline.
These features will be accepted as soon as possible.
* The save steps of Virtools file.
* The bug fix of Virtools file reader.
* Class layout, `Save()` and `Load()` functions of following `CKObject` based classes.
* Class layout, `Load()` functions of following `CKObject` based classes.
- `CKBeObject`
- `CKGroup`
- `CKMaterial`
@ -46,7 +45,7 @@ These features will be accepted as soon as possible.
- `CKRenderObject`
- `CK3dEntity`
- `CK3dObject`
* Class layout, `SaveData()` and `LoadData()` functions of following `CKBaseManager` based classes.
* Class layout, and `LoadData()` functions of following `CKBaseManager` based classes.
- `CKAttributeManager`
- `CKBehaviorManager`
@ -55,6 +54,8 @@ These features will be accepted as soon as possible.
These features are in plan, but not urge to merge.
* The `CK_ID` remap system of Reader & Writer.
* Any Save functions.
* The save steps of Virtools file.
* Other CK classes implementations.
* Non-Virtools 2.1 implementations.
@ -74,4 +75,5 @@ This project require:
* zlib
* iconv (non-Windows system required)
It can be compiled on Windows via sln file, or on Linux platform via CMake file.
It can be compiled on Windows via sln file, or on Linux platform via CMake file.
However CMake may not be updated in time because I develop this project on Windows frequently.

View File

@ -1,32 +1,10 @@
#include "CmdHelper.hpp"
#include "TerminalHelper.hpp"
#include "StructFormatter.hpp"
#include "AccessibleValue.hpp"
#include <CKMinContext.hpp>
#include <CKFile.hpp>
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <functional>
namespace Unvirt::CmdHelper {
static FILE* fout = stdout;
#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) {
std::deque<std::string> CmdSplitter::Convert(const std::string& u8cmd) {
// set up variables
std::deque<std::string> result;
std::string buffer;
@ -35,8 +13,8 @@ namespace Unvirt::CmdHelper {
mState = mPreState = StateType::SPACE;
// split
for (auto it = u8cmd.begin(); it != u8cmd.end(); ++it) {
mCmdChar = (*it);
for (auto& c : u8cmd) {
mCmdChar = c;
// skip all invalid characters, \0 and etc.
// mCmdChar >= 0 to ensure all non-ASCII UTF8 char can be accepted directly.
@ -82,367 +60,292 @@ namespace Unvirt::CmdHelper {
return result;
}
void CmdSplitter::ProcSpace(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::SINGLE;
break;
case '"':
mState = StateType::DOUBLE;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
break; // skip blank
default:
mBuffer->push_back(mCmdChar);
mState = StateType::NORMAL;
break;
}
}
void CmdSplitter::ProcSingle(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::NORMAL;
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::SINGLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
void CmdSplitter::ProcDouble(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mState = StateType::NORMAL;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::DOUBLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
void CmdSplitter::ProcEscape(void) {
// add itself
mBuffer->push_back(mCmdChar);
// restore state
mState = mPreState;
}
void CmdSplitter::ProcNormal(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
mResult->push_back(*mBuffer);
mBuffer->clear();
mState = StateType::SPACE;
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
#pragma endregion
#pragma region ArgParser
#pragma region Abstract Node
bool ArgParser::ParseInt(const std::deque<std::string>& cmd, const size_t expected_index, int32_t& result) {
if (expected_index >= cmd.size()) {
result = 0;
AbstractNode* AbstractNode::Then(AbstractNode* node) {
// check literal duplication
for (auto& pnode : m_Nodes) {
if (pnode->IsConflictWith(node))
throw std::invalid_argument("conflict node.");
}
// add into list
m_Nodes.emplace_back(node);
return this;
}
AbstractNode* AbstractNode::Executes(ExecutionFct fct, const char* cmt) {
if (m_Execution != nullptr)
throw std::invalid_argument("duplicated executions.");
m_Execution = fct;
m_Comment = cmt;
return this;
}
void AbstractNode::Help(HelpDocument& doc) {
// add self
BeginHelp(doc);
// check terminal
if (m_Execution != nullptr) {
doc.Terminate(m_Comment);
}
// iterate children
for (auto& pnode : m_Nodes) {
pnode->Help(doc);
}
// pop self
EndHelp(doc);
}
bool AbstractNode::Consume(std::deque<std::string>& arglist, ArgumentsMap& argmap) {
// if no data can consume, return
if (arglist.empty()) return false;
// backup current value
std::string cur = arglist.front();
// consume self
if (!BeginAccept(cur, argmap)) {
// fail to consume self. not matched. return
return false;
}
// pop front for following code
arglist.pop_front();
#define CONSUME_DEFER \
arglist.push_front(cur); \
EndAccept(argmap);
if (arglist.empty()) {
// this is must be a terminal.
// check whether we have execution.
if (m_Execution == nullptr) {
CONSUME_DEFER;
return false;
} else {
m_Execution(argmap);
CONSUME_DEFER;
return true;
}
} else {
// have following command, try match them
// iterate literal and argument to check terminal
for (auto& pnode : m_Nodes) {
if (pnode->Consume(arglist, argmap)) {
CONSUME_DEFER;
return true;
}
}
// if still nothing to match, return false
CONSUME_DEFER;
return false;
}
#undef CONSUME_DEFER
}
#pragma endregion
#pragma region Argument Impl
bool IntArgument::BeginParse(const std::string& val) {
char* pend = nullptr;
errno = 0;
int64_t v = std::strtoll(cmd[expected_index].c_str(), &pend, 10);
int64_t v = std::strtoll(val.c_str(), &pend, 10);
if (pend == cmd[expected_index].c_str() || errno == ERANGE) {
result = 0;
if (pend == val.c_str() || errno == ERANGE) return false;
// check limit
int32_t value = static_cast<int32_t>(v);
if (m_IntLimit != nullptr && !m_IntLimit(value)) {
return false;
}
result = static_cast<int>(v);
m_ParsedData = new int32_t(value);
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;
}
void IntArgument::EndParse() {
delete reinterpret_cast<int32_t*>(m_ParsedData);
m_ParsedData = nullptr;
}
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;
}
bool StringArgument::BeginParse(const std::string& strl) {
// string always accept every text
m_ParsedData = new std::string(strl);
return true;
}
for (const auto& sw : switches) {
if (cmd[expected_index] == sw) {
gotten = cmd[expected_index];
void StringArgument::EndParse() {
delete reinterpret_cast<std::string*>(m_ParsedData);
m_ParsedData = nullptr;
}
#pragma endregion
#pragma region Command Root
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) {
// if no data can consume, return
if (arglist.empty()) return false;
// create a argument map
ArgumentsMap amap;
// and we only just need iterate all children
for (auto& pnode : m_Nodes) {
if (pnode->Consume(arglist, amap)) {
return true;
}
}
gotten.clear();
// no matched
return false;
}
#pragma endregion
HelpDocument* CommandRoot::RootHelp() {
HelpDocument* doc = new HelpDocument();
#pragma region InteractiveCmd Misc
// we only just need iterate all children
for (auto& pnode : m_Nodes) {
pnode->Help(*doc);
}
InteractiveCmd::InteractiveCmd() :
m_CmdSplitter(), m_PageLen(10),
m_Ctx(nullptr), m_File(nullptr), m_Doc(nullptr) {
// create context and file
m_Ctx = new LibCmo::CK2::CKMinContext();
m_File = new LibCmo::CK2::CKFile(m_Ctx);
// bind callback
m_Ctx->SetPrintCallback(std::bind(&InteractiveCmd::PrintMinContextMsg, this, std::placeholders::_1));
}
InteractiveCmd::~InteractiveCmd() {
// delete doc if necessary
if (m_Doc != nullptr) delete m_Doc;
// delete file and ctx
delete m_File;
delete m_Ctx;
}
bool InteractiveCmd::HasOpenedFile(void) {
return m_Doc != nullptr;
}
void InteractiveCmd::ClearDocument(void) {
if (m_Doc == nullptr) return;
// clear doc
delete m_Doc;
m_Doc = nullptr;
// clear all loaded objects
m_Ctx->ClearCKObject();
}
void InteractiveCmd::PrintMinContextMsg(const std::string& msg) {
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("[CKMinContext] ")), fout);
fputs(msg.c_str(), fout);
fputc('\n', fout);
return doc;
}
#pragma endregion
#pragma region InteractiveCmd Dispatch
#pragma region Help Document
void InteractiveCmd::Run(void) {
std::string u8cmd;
void HelpDocument::Terminate(std::string& command_desc) {
// create new result and copy stack
ResultItem result(command_desc);
result.m_ArgDesc.insert(result.m_ArgDesc.end(), m_Stack.begin(), m_Stack.end());
// add into result
m_Results.emplace_back(std::move(result));
}
while (true) {
// get command
GetCmdLine(u8cmd);
void HelpDocument::Print() {
fputs("Command Help:\n", stdout);
// 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;
for (auto& item : m_Results) {
for (auto& cmd : item.m_ArgDesc) {
fputs(cmd.m_Name.c_str(), stdout);
fputc(' ', stdout);
}
std::string subcmd(cmds.front());
cmds.pop_front();
fputc('\n', stdout);
// 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 == "encoding") this->ProcEncoding(cmds);
else if (subcmd == "temp") this->ProcTemp(cmds);
else if (subcmd == "help") this->PrintHelp();
else if (subcmd == "exit") break;
else {
this->PrintCommonError("No such command \"%s\".", subcmd.c_str());
this->PrintHelp();
if (!item.m_CmdDesc.empty()) {
fputs(item.m_CmdDesc.c_str(), stdout);
fputc('\n', stdout);
}
for (auto& cmd : item.m_ArgDesc) {
if (!cmd.m_Desc.empty()) {
fprintf(stdout, "\t%s: %s\n", cmd.m_Name.c_str(), cmd.m_Desc.c_str());
}
}
fputc('\n', stdout);
}
}
void InteractiveCmd::GetCmdLine(std::string& u8cmd) {
fputs("Unvirt> ", fout);
#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
}
void InteractiveCmd::PrintHelp(void) {
fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("Allowed Subcommands: \n")), fout);
fputs("load\n", fout);
fputs("\tDescription: Load a Virtools composition.\n", fout);
fputs("\tSyntax: load <file path>\n", fout);
fputs("unload\n", fout);
fputs("\tDescription: Release loaded Virtools composition.\n", fout);
fputs("\tSyntax: unload\n", fout);
fputs("info\n", fout);
fputs("\tDescription: Show the header info of loaded Virtools composition.\n", fout);
fputs("\tSyntax: info\n", fout);
fputs("ls\n", fout);
fputs("\tDescription: List something about loaded Virtools composition.\n", fout);
fputs("\tSyntax: ls <obj | mgr> [page]\n", fout);
fputs("items\n", fout);
fputs("\tDescription: Set up how many items should be listed in one page when using \"ls\" command.\n", fout);
fputs("\tSyntax: items <num>\n", fout);
fputs("encoding\n", fout);
fputs("\tDescription: Set the encoding series for CKMinContext.\n", fout);
fputs("\tSyntax: encoding [encoding name1] [encoding name2] [encoding name3] ...\n", fout);
fputs("temp\n", fout);
fputs("\tDescription: Set the Temp path for CKMinContext.\n", fout);
fputs("\tSyntax: temp <temp path>\n", fout);
fputs("exit\n", fout);
fputs("\tDescription: Exit program\n", fout);
fputs("\tSyntax: exit\n", fout);
}
void InteractiveCmd::PrintArgParseError(const std::deque<std::string>& cmd, size_t pos) {
if (pos >= cmd.size()) {
fprintf(fout, UNVIRT_TERMCOL_LIGHT_RED(("Lost argument at position %zu.\n")), pos);
} else {
fprintf(fout, 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, fout);
std::vfprintf(fout, u8_fmt, argptr);
std::fputs(UNVIRT_TERMCOLTAIL, fout);
va_end(argptr);
std::fputc('\n', fout);
}
#pragma endregion
#pragma region InteractiveCmd 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;
}
// proc
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()
);
this->ClearDocument();
}
}
void InteractiveCmd::ProcUnLoad(const std::deque<std::string>& cmd) {
// check pre-requirement
if (!HasOpenedFile()) {
this->PrintCommonError("No loaded file.");
return;
}
// free all
this->ClearDocument();
}
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 values of switches
static const std::vector<std::string> 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 = 1; // asssume as 1
}
if (gotten_page <= 0) {
this->PrintCommonError("Page out of range.");
return;
}
size_t page = static_cast<size_t>(gotten_page) - 1;
// 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<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);
}
void InteractiveCmd::ProcEncoding(const std::deque<std::string>& cmd) {
// create list first
std::vector<std::string> encoding_list;
// get list item
size_t pos = 0u;
while (true) {
std::string pending;
if (!ArgParser::ParseString(cmd, pos, pending)) {
break; // no more encoding, break
}
// add and move to next
++pos;
encoding_list.push_back(std::move(pending));
}
// apply list
this->m_Ctx->SetEncoding(encoding_list);
}
void InteractiveCmd::ProcTemp(const std::deque<std::string>& cmd) {
// check requirement
size_t pos = 0u;
std::string temppath;
if (!ArgParser::ParseString(cmd, pos, temppath)) {
this->PrintArgParseError(cmd, pos);
return;
}
// assign
m_Ctx->SetTempPath(temppath.c_str());
}
#pragma endregion
}

View File

@ -1,26 +1,19 @@
#pragma once
#include <VTUtils.hpp>
#include <CKDefines.hpp>
#include <VTAll.hpp>
#include <string>
#include <vector>
#include <functional>
#include <deque>
#include <unordered_map>
#include <stdexcept>
#include <cinttypes>
#include <initializer_list>
namespace Unvirt::CmdHelper {
class CmdSplitter {
public:
CmdSplitter();
CmdSplitter(const CmdSplitter&) = delete;
CmdSplitter& operator=(const CmdSplitter&) = delete;
~CmdSplitter();
const std::deque<std::string> Convert(const std::string& u8cmd);
private:
char mCmdChar;
std::string* mBuffer;
std::deque<std::string>* mResult;
enum class StateType : int {
SPACE,
SINGLE,
@ -28,142 +21,312 @@ namespace Unvirt::CmdHelper {
ESCAPE,
NORMAL
};
public:
CmdSplitter() :
mCmdChar(0), mBuffer(nullptr), mResult(nullptr),
mState(StateType::NORMAL), mPreState(StateType::NORMAL) {}
~CmdSplitter() {}
LIBCMO_DISABLE_COPY_MOVE(CmdSplitter);
std::deque<std::string> Convert(const std::string& u8cmd);
protected:
char mCmdChar;
std::string* mBuffer;
std::deque<std::string>* mResult;
StateType mState, mPreState;
inline void ProcSpace(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::SINGLE;
break;
case '"':
mState = StateType::DOUBLE;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
break; // skip blank
default:
mBuffer->push_back(mCmdChar);
mState = StateType::NORMAL;
break;
}
}
inline void ProcSingle(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::NORMAL;
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::SINGLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
inline void ProcDouble(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mState = StateType::NORMAL;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::DOUBLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
inline void ProcEscape(void) {
// add itself
mBuffer->push_back(mCmdChar);
// restore state
mState = mPreState;
}
inline void ProcNormal(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
mResult->push_back(*mBuffer);
mBuffer->clear();
mState = StateType::SPACE;
break;
default:
mBuffer->push_back(mCmdChar);
break;
}
}
void ProcSpace(void);
void ProcSingle(void);
void ProcDouble(void);
void ProcEscape(void);
void ProcNormal(void);
};
class ArgParser {
class HelpDocument {
public:
ArgParser() {}
ArgParser(const ArgParser&) = delete;
ArgParser& operator=(const ArgParser&) = delete;
~ArgParser() {}
HelpDocument() : m_Stack(), m_Results() {}
~HelpDocument() {}
LIBCMO_DISABLE_COPY_MOVE(HelpDocument);
static bool ParseInt(const std::deque<std::string>& cmd, const size_t expected_index, int32_t& result);
static bool ParseString(const std::deque<std::string>& cmd, const size_t expected_index, std::string& result);
static bool ParseSwitch(const std::deque<std::string>& cmd, const size_t expected_index, const std::vector<std::string>& switches, std::string& gotten);
void PushLiteral(const std::string& literal_name) {
m_Stack.emplace_back(StackItem(literal_name, ""));
}
void PushArgument(const std::string& arg_name, const std::string& arg_desc) {
m_Stack.emplace_back(StackItem(arg_name, arg_desc));
}
void Pop() {
m_Stack.pop_back();
}
void Terminate(std::string& command_desc);
void Print();
protected:
struct StackItem {
StackItem() : m_Name(), m_Desc() {}
StackItem(const std::string& name, const std::string& desc) : m_Name(name), m_Desc(desc) {}
LIBCMO_DEFAULT_COPY_MOVE(StackItem);
std::string m_Name;
std::string m_Desc;
};
std::deque<StackItem> m_Stack;
struct ResultItem {
ResultItem() : m_CmdDesc(), m_ArgDesc() {}
ResultItem(const std::string& desc) : m_CmdDesc(desc), m_ArgDesc() {}
LIBCMO_DEFAULT_COPY_MOVE(ResultItem);
std::string m_CmdDesc;
std::vector<StackItem> m_ArgDesc;
};
std::vector<ResultItem> m_Results;
};
class InteractiveCmd {
class AbstractArgument;
class ArgumentsMap {
public:
InteractiveCmd();
InteractiveCmd(const InteractiveCmd&) = delete;
InteractiveCmd& operator=(const InteractiveCmd&) = delete;
~InteractiveCmd();
ArgumentsMap() : m_Data() {}
~ArgumentsMap() {}
LIBCMO_DISABLE_COPY_MOVE(ArgumentsMap);
void Run(void);
void Add(const std::string& k, AbstractArgument* v) {
m_Data.emplace(std::make_pair(k, v));
}
void Remove(const std::string& k) {
m_Data.erase(k);
}
AbstractArgument* Get(const char* k) const {
auto finder = m_Data.find(k);
if (finder == m_Data.end()) throw std::invalid_argument("No such argument name.");
return finder->second;
}
private:
void GetCmdLine(std::string& u8cmd);
void PrintHelp(void);
void PrintArgParseError(const std::deque<std::string>& cmd, size_t pos);
void PrintCommonError(const char* u8_fmt, ...);
void ProcLoad(const std::deque<std::string>& cmd);
void ProcUnLoad(const std::deque<std::string>& cmd);
void ProcInfo(const std::deque<std::string>& cmd);
void ProcLs(const std::deque<std::string>& cmd);
void ProcItems(const std::deque<std::string>& cmd);
void ProcEncoding(const std::deque<std::string>& cmd);
void ProcTemp(const std::deque<std::string>& cmd);
bool HasOpenedFile(void);
void ClearDocument(void);
void PrintMinContextMsg(const std::string& msg);
CmdSplitter m_CmdSplitter;
size_t m_PageLen;
LibCmo::CK2::CKMinContext* m_Ctx;
LibCmo::CK2::CKFile* m_File;
LibCmo::CK2::CKFileDocument* m_Doc;
protected:
std::unordered_map<std::string, AbstractArgument*> m_Data;
};
}
class CommandRoot;
using ExecutionFct = std::function<void(const ArgumentsMap&)>;
class AbstractNode {
friend class CommandRoot;
public:
AbstractNode() : m_Execution(nullptr), m_Nodes(), m_Comment() {}
virtual ~AbstractNode() {
for (auto& ptr : m_Nodes) {
delete ptr;
}
}
LIBCMO_DISABLE_COPY_MOVE(AbstractNode);
AbstractNode* Then(AbstractNode*);
AbstractNode* Executes(ExecutionFct, const char*);
protected:
void Help(HelpDocument&);
bool Consume(std::deque<std::string>&, ArgumentsMap&);
virtual bool IsLiteral() = 0;
virtual bool IsConflictWith(AbstractNode*) = 0;
virtual void BeginHelp(HelpDocument&) = 0;
virtual void EndHelp(HelpDocument&) = 0;
virtual bool BeginAccept(const std::string&, ArgumentsMap&) = 0;
virtual void EndAccept(ArgumentsMap&) = 0;
std::vector<AbstractNode*> m_Nodes;
ExecutionFct m_Execution;
std::string m_Comment;
};
class CommandRoot : public AbstractNode {
public:
CommandRoot() : AbstractNode() {}
virtual ~CommandRoot() {}
LIBCMO_DISABLE_COPY_MOVE(CommandRoot);
// Root use special consume and help functions.
bool RootConsume(std::deque<std::string>&);
HelpDocument* RootHelp();
protected:
virtual bool IsLiteral() override { throw std::logic_error("Root can not be called."); }
virtual bool IsConflictWith(AbstractNode*) override { throw std::logic_error("Root can not be called."); }
virtual void BeginHelp(HelpDocument&) override { throw std::logic_error("Root can not be called."); }
virtual void EndHelp(HelpDocument&) override { throw std::logic_error("Root can not be called."); }
virtual bool BeginAccept(const std::string&, ArgumentsMap&) override { throw std::logic_error("Root can not be called."); }
virtual void EndAccept(ArgumentsMap&) override { throw std::logic_error("Root can not be called."); }
};
class Literal : public AbstractNode {
friend class Choice;
public:
Literal(const char* words) : AbstractNode(), m_Literal(words) {
if (m_Literal.empty() || words == nullptr) throw std::invalid_argument("Invalid literal.");
}
virtual ~Literal() {}
LIBCMO_DISABLE_COPY_MOVE(Literal);
protected:
virtual bool IsLiteral() override { return true; }
virtual bool IsConflictWith(AbstractNode* node) override {
Literal* pliteral = dynamic_cast<Literal*>(node);
if (pliteral == nullptr) return false;
return pliteral->m_Literal == this->m_Literal;
}
virtual void BeginHelp(HelpDocument& doc) override {
doc.PushLiteral(m_Literal);
}
virtual void EndHelp(HelpDocument& doc) override {
doc.Pop();
}
virtual bool BeginAccept(const std::string& strl, ArgumentsMap&) override { return strl == m_Literal; }
virtual void EndAccept(ArgumentsMap&) override {}
std::string m_Literal;
};
class Choice : public AbstractNode {
public:
Choice(const char* argname, const std::initializer_list<std::string>& vocabulary) :
AbstractNode(), m_ChoiceName(argname == nullptr ? "" : argname), m_Vocabulary(vocabulary) {
if (m_ChoiceName.empty() || argname == nullptr) throw std::invalid_argument("Invalid choice name.");
if (m_Vocabulary.size() < 2) throw std::invalid_argument("Too less vocabulary. At least 2 items.");
}
virtual ~Choice() {}
LIBCMO_DISABLE_COPY_MOVE(Choice)
protected:
virtual bool IsLiteral() override { return true; }
virtual bool IsConflictWith(AbstractNode* node) override {
Literal* pliteral = dynamic_cast<Literal*>(node);
if (pliteral != nullptr) {
for (const auto& word : m_Vocabulary) {
if (word == pliteral->m_Literal)
return true;
}
return false;
}
Choice* pchoice = dynamic_cast<Choice*>(node);
if (pchoice != nullptr) {
for (const auto& thisword : m_Vocabulary) {
for (const auto& thatword : pchoice->m_Vocabulary) {
if (thisword == thatword)
return true;
}
}
return false;
}
return false;
}
virtual void BeginHelp(HelpDocument& doc) override {
std::string switches;
for (const auto& item : m_Vocabulary) {
if (!switches.empty()) switches += " | ";
switches += item;
}
doc.PushLiteral("[" + switches + "]");
}
virtual void EndHelp(HelpDocument& doc) override {
doc.Pop();
}
virtual bool BeginAccept(const std::string& strl, ArgumentsMap& amap) override {
for (const auto& item : m_Vocabulary) {
if (strl == item) {
}
}
}
virtual void EndAccept(ArgumentsMap&) override {}
std::string m_ChoiceName;
std::vector<std::string> m_Vocabulary;
};
class AbstractArgument : public AbstractNode {
public:
AbstractArgument(const char* argname, const char* argdesc) : AbstractNode(),
m_ArgName(argname == nullptr ? "" : argname), m_ArgDesc(argdesc), m_Accepted(false), m_ParsedData(nullptr) {
if (m_ArgName.empty() || argname == nullptr) throw std::invalid_argument("Invalid argument name.");
}
virtual ~AbstractArgument() {}
LIBCMO_DISABLE_COPY_MOVE(AbstractArgument);
template<class T>
T* GetData() { return reinterpret_cast<T*>(m_ParsedData); }
protected:
virtual bool BeginParse(const std::string&) = 0;
virtual void EndParse() = 0;
virtual bool IsLiteral() override { return false; }
virtual bool IsConflictWith(AbstractNode* node) override {
return false;
}
virtual void BeginHelp(HelpDocument& doc) override {
std::string newargname = "<";
newargname.append(m_ArgName);
newargname.append(">");
doc.PushArgument(newargname, m_ArgDesc);
}
virtual void EndHelp(HelpDocument& doc) override {
doc.Pop();
}
virtual bool BeginAccept(const std::string& strl, ArgumentsMap& amap) override {
m_Accepted = BeginParse(strl);
if (m_Accepted) amap.Add(m_ArgName, this);
return m_Accepted;
}
virtual void EndAccept(ArgumentsMap& amap) override {
if (m_Accepted) {
amap.Remove(m_ArgName);
EndParse();
m_Accepted = false;
}
}
std::string m_ArgName;
std::string m_ArgDesc;
bool m_Accepted;
void* m_ParsedData;
};
/**
* @brief Return true mean this value can accept.
*/
using IntLimit = std::function<bool(int32_t)>;
class IntArgument : public AbstractArgument {
public:
IntArgument(const char* argname, const char* argdesc = nullptr, IntLimit limit = nullptr) :
AbstractArgument(argname, argdesc), m_IntLimit(limit) {}
virtual ~IntArgument() {}
LIBCMO_DISABLE_COPY_MOVE(IntArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
IntLimit m_IntLimit;
};
class StringArgument : public AbstractArgument {
public:
StringArgument(const char* argname, const char* argdesc = nullptr) : AbstractArgument(argname, argdesc) {}
virtual ~StringArgument() {}
LIBCMO_DISABLE_COPY_MOVE(StringArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
};
class EncodingArgument : public AbstractArgument {
public:
EncodingArgument(const char* argname, const char* argdesc = nullptr) : AbstractArgument(argname, argdesc) {}
virtual ~EncodingArgument() {}
LIBCMO_DISABLE_COPY_MOVE(EncodingArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
};
}

View File

@ -1,32 +1,4 @@
#include "AccessibleValue.hpp"
#include "TerminalHelper.hpp"
#include "StructFormatter.hpp"
#include "CmdHelper.hpp"
#include <VTAll.hpp>
#include <CK2/CKContext.hpp>
#include <CK2/CKFile.hpp>
#include <cstdio>
#include <iostream>
namespace Unvirt {
class UnvirtContext {
public:
UnvirtContext() {}
~UnvirtContext() {}
void Run() {
Unvirt::TerminalHelper::EnsureTerminalColor();
Unvirt::TerminalHelper::EnsureTerminalEncoding();
}
private:
};
}
#include "UnvirtContext.hpp"
int main(int argc, char* argv[]) {

View File

@ -181,6 +181,7 @@
<ClCompile Include="TerminalHelper.cpp" />
<ClCompile Include="StringHelper.cpp" />
<ClCompile Include="Unvirt.cpp" />
<ClCompile Include="UnvirtContext.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="AccessibleValue.hpp" />
@ -188,6 +189,7 @@
<ClInclude Include="StructFormatter.hpp" />
<ClInclude Include="TerminalHelper.hpp" />
<ClInclude Include="StringHelper.hpp" />
<ClInclude Include="UnvirtContext.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -33,6 +33,9 @@
<ClCompile Include="StructFormatter.cpp">
<Filter>Sources</Filter>
</ClCompile>
<ClCompile Include="UnvirtContext.cpp">
<Filter>Sources</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AccessibleValue.hpp">
@ -50,5 +53,8 @@
<ClInclude Include="StructFormatter.hpp">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="UnvirtContext.hpp">
<Filter>Headers</Filter>
</ClInclude>
</ItemGroup>
</Project>

298
Unvirt/UnvirtContext.cpp Normal file
View File

@ -0,0 +1,298 @@
#include "UnvirtContext.hpp"
namespace Unvirt::Context {
static FILE* fout = stdout;
#pragma region Encoding Arg
// Copy from Gamepiaynmo/BallanceModLoader
std::vector<std::string> SplitString(const std::string& str, const std::string& de) {
size_t lpos, pos = 0;
std::vector<std::string> res;
lpos = str.find_first_not_of(de, pos);
while (lpos != std::string::npos) {
pos = str.find_first_of(de, lpos);
res.push_back(str.substr(lpos, pos - lpos));
if (pos == std::string::npos) break;
lpos = str.find_first_not_of(de, pos);
}
if (pos != std::string::npos)
res.push_back("");
return res;
}
bool EncodingArgument::BeginParse(const std::string& strl) {
// encoding always accept every text
m_ParsedData = new std::vector<std::string>(SplitString(strl, ","));
return true;
}
void EncodingArgument::EndParse() {
delete reinterpret_cast<std::vector<std::string>*>(m_ParsedData);
m_ParsedData = nullptr;
}
#pragma endregion
#pragma region UnvirtContext Misc
UnvirtContext::UnvirtContext() :
m_Root(), m_Splitter(), m_Help(nullptr),
m_PageLen(10u), m_Ctx(nullptr), m_FileReader(nullptr) {
// create command root
CmdHelper::CommandRoot* root = &m_Root;
root->Then(
(new CmdHelper::Literal("deepload"))->Then(
(new CmdHelper::StringArgument("filepath", "The path to loading file."))->Executes(
std::bind(&UnvirtContext::ProcLoad, this, true, std::placeholders::_1),
"Load a Virtools composition deeply. Load file to CKObject stage."
)
)
);
root->Then(
(new CmdHelper::Literal("shallowload"))->Then(
(new CmdHelper::StringArgument("filepath", "The path to loading file."))->Executes(
std::bind(&UnvirtContext::ProcLoad, this, false, std::placeholders::_1),
"Load a Virtools composition shallowly. Load file to CKStateChunk stage."
)
)
);
root->Then(
(new CmdHelper::Literal("unload"))->Executes(
std::bind(&UnvirtContext::ProcUnLoad, this, std::placeholders::_1),
"Release loaded Virtools composition."
)
);
root->Then(
(new CmdHelper::Literal("info"))->Executes(
std::bind(&UnvirtContext::ProcInfo, this, std::placeholders::_1),
"Show the header info of loaded Virtools composition."
)
);
root->Then(
(new CmdHelper::Literal("foo"))->Then(
(new CmdHelper::Literal("bar"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void {
fprintf(stdout, "foobar!\n");
}, "simple foobar")
)->Then(
(new CmdHelper::IntArgument("bar", "the calling target 1"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void {
fprintf(stdout, "foo%" PRIi32 "!\n", *(amap.Get("bar")->GetData<int32_t>()));
}, "specialized foo bar")
)
)->Then(
(new CmdHelper::Literal("call"))->Then(
(new CmdHelper::IntArgument("bar", "the calling taget 2"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void {
fprintf(stdout, "call%" PRIi32 "!\n", *(amap.Get("bar")->GetData<int32_t>()));
}, "calling someone")
)
);
// create help
m_Help = root->RootHelp();
// create context
m_Ctx = new LibCmo::CK2::CKContext();
m_Ctx->SetOutputCallback(std::bind(&UnvirtContext::PrintContextMsg, this, std::placeholders::_1));
}
UnvirtContext::~UnvirtContext() {
if (m_Help != nullptr)
delete m_Help;
}
bool UnvirtContext::HasOpenedFile() {
return m_FileReader != nullptr;
}
void UnvirtContext::ClearDocument() {
if (m_FileReader == nullptr) return;
delete m_FileReader;
m_FileReader = nullptr;
}
void UnvirtContext::PrintContextMsg(LibCmo::CK2::CKSTRING msg) {
if (msg != nullptr) {
fprintf(fout, UNVIRT_TERMCOL_LIGHT_YELLOW(("[CKContext] ")) "%s\n", msg);
}
}
void UnvirtContext::PrintCommonError(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
std::fputs(UNVIRT_TERMCOLHDR_LIGHT_RED, fout);
std::vfprintf(fout, u8_fmt, argptr);
std::fputs(UNVIRT_TERMCOLTAIL, fout);
va_end(argptr);
std::fputc('\n', fout);
}
#pragma endregion
#pragma region Main Run
void UnvirtContext::Run() {
Unvirt::TerminalHelper::EnsureTerminalColor();
Unvirt::TerminalHelper::EnsureTerminalEncoding();
std::string u8cmd;
while (true) {
// get command
TerminalHelper::GetCmdLine(u8cmd);
// split cmd and parse it
auto cmds = m_Splitter.Convert(u8cmd);
// get sub command
if (!m_Root.RootConsume(cmds)) {
this->PrintCommonError("Command syntax error \"%s\".", u8cmd.c_str());
m_Help->Print();
}
if (m_OrderExit) {
break;
}
}
}
#pragma endregion
#pragma region Proc Detail
void UnvirtContext::ProcLoad(bool is_deep, const CmdHelper::ArgumentsMap& amap) {
// check pre-requirement
if (HasOpenedFile()) {
PrintCommonError("Already have a opened file. Close it before calling \"load\".");
return;
}
std::string filepath = *amap.Get("filepath")->GetData<std::string>();
// proc
m_FileReader = new LibCmo::CK2::CKFileReader(m_Ctx);
LibCmo::CK2::CKERROR err;
if (is_deep) {
err = m_FileReader->DeepLoad(filepath.c_str());
} else {
err = m_FileReader->ShallowLoad(filepath.c_str());
}
if (err != LibCmo::CK2::CKERROR::CKERR_OK) {
// fail to load. release all.
PrintCommonError("Fail to open file. Function return: %s\n\t%s",
Unvirt::AccessibleValue::GetCkErrorName(err).c_str(),
Unvirt::AccessibleValue::GetCkErrorDescription(err).c_str()
);
ClearDocument();
}
}
void UnvirtContext::ProcUnLoad(const CmdHelper::ArgumentsMap& amap) {
// check pre-requirement
if (!HasOpenedFile()) {
this->PrintCommonError("No loaded file.");
return;
}
// free all
ClearDocument();
}
void UnvirtContext::ProcInfo(const CmdHelper::ArgumentsMap& amap) {
// check pre-requirement
if (!HasOpenedFile()) {
PrintCommonError("No loaded file.");
return;
}
// print
Unvirt::StructFormatter::PrintCKFileInfo(m_FileReader->GetFileInfo());
}
void UnvirtContext::ProcLs(ViewPart parts, const CmdHelper::ArgumentsMap& amap) {
// check pre-requirement
if (!HasOpenedFile()) {
this->PrintCommonError("No loaded file.");
return;
}
// check requirement
int32_t gotten_page = *amap.Get("page")->GetData<int32_t>();
if (gotten_page <= 0) {
PrintCommonError("Page out of range.");
return;
}
size_t page = static_cast<size_t>(gotten_page) - 1;
// show list
switch (parts) {
case ViewPart::Objects:
{
// obj list
if (page * m_PageLen >= m_FileReader->GetFileObjects().size()) {
PrintCommonError("Page out of range.");
return;
}
Unvirt::StructFormatter::PrintObjectList(m_FileReader->GetFileObjects(), page, this->m_PageLen);
break;
}
case ViewPart::Managers:
{
// mgr list
if (page * m_PageLen >= m_FileReader->GetManagersData().size()) {
PrintCommonError("Page out of range.");
return;
}
Unvirt::StructFormatter::PrintManagerList(m_FileReader->GetManagersData(), page, this->m_PageLen);
break;
}
}
}
void UnvirtContext::ProcData(ViewPart parts, const CmdHelper::ArgumentsMap& amap) {
}
void UnvirtContext::ProcChunk(ViewPart parts, const CmdHelper::ArgumentsMap& amap) {
}
void UnvirtContext::ProcItems(const CmdHelper::ArgumentsMap& amap) {
// check requirement
int32_t count = *amap.Get("count")->GetData<int32_t>();
if (count <= 0) {
PrintCommonError("Value out of range.");
return;
}
// assign
m_PageLen = static_cast<size_t>(count);
}
void UnvirtContext::ProcEncoding(const CmdHelper::ArgumentsMap& amap) {
}
void UnvirtContext::ProcTemp(const CmdHelper::ArgumentsMap& amap) {
// check requirement
std::string temppath = *amap.Get("temppath")->GetData<std::string>();
// assign
m_Ctx->SetTempPath(temppath.c_str());
}
void UnvirtContext::ProcExit(const CmdHelper::ArgumentsMap& amap) {
m_OrderExit = true;
}
#pragma endregion
}

57
Unvirt/UnvirtContext.hpp Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include "AccessibleValue.hpp"
#include "TerminalHelper.hpp"
#include "StructFormatter.hpp"
#include "CmdHelper.hpp"
#include <VTAll.hpp>
#include <CK2/CKContext.hpp>
#include <CK2/CKFile.hpp>
#include <cstdio>
#include <iostream>
namespace Unvirt::Context {
class UnvirtContext {
public:
UnvirtContext();
~UnvirtContext();
LIBCMO_DISABLE_COPY_MOVE(UnvirtContext);
void Run();
protected:
enum class ViewPart {
Objects, Managers
};
void PrintCommonError(const char* u8_fmt, ...);
void ProcLoad(bool is_deep, const CmdHelper::ArgumentsMap& amap);
void ProcUnLoad(const CmdHelper::ArgumentsMap& amap);
void ProcInfo(const CmdHelper::ArgumentsMap& amap);
void ProcLs(ViewPart parts, const CmdHelper::ArgumentsMap& amap);
void ProcData(ViewPart parts, const CmdHelper::ArgumentsMap& amap);
void ProcChunk(ViewPart parts, const CmdHelper::ArgumentsMap& amap);
void ProcItems(const CmdHelper::ArgumentsMap& amap);
void ProcEncoding(const CmdHelper::ArgumentsMap& amap);
void ProcTemp(const CmdHelper::ArgumentsMap& amap);
void ProcExit(const CmdHelper::ArgumentsMap& amap);
protected:
bool HasOpenedFile();
void ClearDocument();
void PrintContextMsg(LibCmo::CK2::CKSTRING msg);
CmdHelper::CommandRoot m_Root;
CmdHelper::HelpDocument* m_Help;
CmdHelper::CmdSplitter m_Splitter;
size_t m_PageLen;
bool m_OrderExit;
LibCmo::CK2::CKContext* m_Ctx;
LibCmo::CK2::CKFileReader* m_FileReader;
};
}