// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. // Distributed under the MIT License (http://opensource.org/licenses/MIT) #ifndef CSYS_SYSTEM_H #define CSYS_SYSTEM_H #pragma once #include "csys/command.h" #include "csys/autocomplete.h" #include "csys/history.h" #include "csys/item.h" #include "csys/script.h" #include #include #include namespace csys { class CSYS_API System { public: /*! * \brief Initialize system object */ System(); /*! * \brief * Move constructor * \param rhs * System to be copied. */ System(System &&rhs) = default; /*! * \brief * Copy constructor * \param rhs * System to be copied. */ System(const System &rhs); /*! * \brief * Move assignment operator * \param rhs * System to be copied. */ System &operator=(System &&rhs) = default; /*! * \brief * Copy assigment operator. * \param rhs * System to be copied. */ System &operator=(const System &rhs); /*! * \brief * Parse given command line input and run it * \param line * Command line string */ void RunCommand(const std::string &line); /*! * \brief * Get console registered command autocomplete tree * \return * Autocomplete Ternary Search Tree */ AutoComplete &CmdAutocomplete(); /*! * \brief * Get console registered variables autocomplete tree * \return * Autocomplete Ternary Search Tree */ AutoComplete &VarAutocomplete(); /*! * \brief * Get command history container * \return * Command history vector */ CommandHistory &History(); /*! * \brief * Get console items * \return * Console items container */ std::vector &Items(); /*! * \brief * Creates a new item entry to log information * \param type * Log type (COMMAND, LOG, WARNING, ERROR) * \return * Reference to console items obj */ ItemLog &Log(ItemType type = ItemType::eLOG); /*! * \brief * Run the given script * \param script_name * Script to be executed * * \note * If script exists but its not loaded, this methods will load the script and proceed to run it. */ void RunScript(const std::string &script_name); /*! * \brief * Get registered command container * \return * Commands container */ std::unordered_map> &Commands(); /*! * \brief * Get registered scripts container * \return * Scripts container */ std::unordered_map> &Scripts(); /*! * \brief * Registers a command within the system to be invokable * \tparam Fn * Decltype of the function to invoke when command is ran * \tparam Args * List of arguments that match that of the argument list within the function Fn of type csys::Arg * \param name * Non-whitespace separating name of the command. Whitespace will be dropped * \param description * Description describing what the command does * \param function * A non-member function to run when command is called * \param args * List of csys::Args that matches that of the argument list of 'function' */ template void RegisterCommand(const String &name, const String &description, Fn function, Args... args) { // Check if function can be called with the given arguments and is not part of a class static_assert(std::is_invocable_v, "Arguments specified do not match that of the function"); static_assert(!std::is_member_function_pointer_v, "Non-static member functions are not allowed"); // Move to command size_t name_index = 0; auto range = name.NextPoi(name_index); // Command already registered if (m_Commands.find(name.m_String) != m_Commands.end()) throw csys::Exception("ERROR: Command already exists"); // Check if command has a name else if (range.first == name.End()) { Log(eERROR) << "Empty command name given" << csys::endl; return; } // Get command name std::string command_name = name.m_String.substr(range.first, range.second - range.first); // Command contains more than one word if (name.NextPoi(name_index).first != name.End()) throw csys::Exception("ERROR: Whitespace separated command names are forbidden"); // Register for autocomplete. if (m_RegisterCommandSuggestion) { m_CommandSuggestionTree.Insert(command_name); m_VariableSuggestionTree.Insert(command_name); } // Add commands to system m_Commands[name.m_String] = std::make_unique>(name, description, function, args...); // Make help command for command just added auto help = [this, command_name]() { Log(eLOG) << m_Commands[command_name]->Help() << csys::endl; }; m_Commands["help " + command_name] = std::make_unique>("help " + command_name, "Displays help info about command " + command_name, help); } /*! * \brief * Register's a variable within the system * \tparam T * Type of the variable * \tparam Types * Type of arguments that type T can be constructed with * \param name * Name of the variable * \param var * The variable to register * \param args * List of csys::Arg to be used for the construction of type T * \note * Type T requires an assignment operator, and constructor that takes type 'Types...' * Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends */ template void RegisterVariable(const String &name, T &var, Arg... args) { static_assert(std::is_constructible_v, "Type of var 'T' can not be constructed with types of 'Types'"); static_assert(sizeof... (Types) != 0, "Empty variadic list"); // Register get command auto var_name = RegisterVariableAux(name, var); // Register set command auto setter = [&var](Types... params){ var = T(params...); }; m_Commands["set " + var_name] = std::make_unique...>>("set " + var_name, "Sets the variable " + var_name, setter, args...); } /*! * \brief * Register's a variable within the system * \tparam T * Type of the variable * \tparam Types * Type of arguments that type T can be constructed with * \param name * Name of the variable * \param var * The variable to register * \param setter * Custom setter that runs when command set 'name' is invoked * \note * The setter must have dceltype of void(decltype(var)&, Types...) * Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends */ template void RegisterVariable(const String &name, T &var, void(*setter)(T&, Types...)) { // Register get command auto var_name = RegisterVariableAux(name, var); // Register set command auto setter_l = [&var, setter](Types... args){ setter(var, args...); }; m_Commands["set " + var_name] = std::make_unique...>>("set " + var_name, "Sets the variable " + var_name, setter_l, Arg("")...); } /*! * \brief * Register script into console system * \param name * Script name * \param path * Scrip path */ void RegisterScript(const std::string &name, const std::string &path); /*! * \brief * Unregister command from console system * \param cmd_name * Command to unregister */ void UnregisterCommand(const std::string &cmd_name); /*! * \brief * Unregister variable from console system * \param var_name * Variable to unregister */ void UnregisterVariable(const std::string &var_name); /*! * \brief * Unregister script from console system * \param script_name * Script to unregister */ void UnregisterScript(const std::string &script_name); protected: template std::string RegisterVariableAux(const String &name, T &var) { // Disable. m_RegisterCommandSuggestion = false; // Make sure only one word was passed in size_t name_index = 0; auto range = name.NextPoi(name_index); if (name.NextPoi(name_index).first != name.End()) throw csys::Exception("ERROR: Whitespace separated variable names are forbidden"); // Get variable name std::string var_name = name.m_String.substr(range.first, range.second - range.first); // Get Command const auto GetFunction = [this, &var]() { m_ItemLog.log(eLOG) << var << endl; }; // Register get command m_Commands["get " + var_name] = std::make_unique>("get " + var_name, "Gets the variable " + var_name, GetFunction); // Enable again. m_RegisterCommandSuggestion = true; // Register variable m_VariableSuggestionTree.Insert(var_name); return var_name; } void ParseCommandLine(const String &line); //!< Parse command line and execute command std::unordered_map> m_Commands; //!< Registered command container AutoComplete m_CommandSuggestionTree; //!< Autocomplete Ternary Search Tree for commands AutoComplete m_VariableSuggestionTree; //!< Autocomplete Ternary Search Tree for registered variables CommandHistory m_CommandHistory; //!< History of executed commands ItemLog m_ItemLog; //!< Console Items (Logging) std::unordered_map> m_Scripts; //!< Scripts bool m_RegisterCommandSuggestion = true; //!< Flag that determines if commands will be registered for autocomplete. }; } #ifdef CSYS_HEADER_ONLY #include "csys/system.inl" #endif #endif // void (T&, int, float...); // registaerVariable("name", var, Arg...); //c // registaerVariable("name", var, setter); //d