diff --git a/ARRCON/Help.hpp b/ARRCON/Help.hpp index 50c53ab..357bc47 100644 --- a/ARRCON/Help.hpp +++ b/ARRCON/Help.hpp @@ -18,7 +18,7 @@ struct Help { Help(const std::filesystem::path& program_name) : _program_name{ program_name } {} friend std::ostream& operator<<(std::ostream& os, const Help& help) { - os << help._program_name.generic_string() << ' ' << ((help._program_name != std::string_view(DEFAULT_PROGRAM_NAME)) ? "("s + DEFAULT_PROGRAM_NAME + ") "s : "") << "v" << ARRCON_VERSION << '\n' + return os << help._program_name.generic_string() << ' ' << ((help._program_name != std::string_view(DEFAULT_PROGRAM_NAME)) ? "("s + DEFAULT_PROGRAM_NAME + ") "s : "") << "v" << ARRCON_VERSION << '\n' << "Another Remote-CONsole Client.\n" << '\n' << "USAGE:\n" @@ -40,6 +40,7 @@ struct Help { << "-d --delay Time in milliseconds to wait between each command in commandline mode." << '\n' << "-n --no-color Disable colorized console output." << '\n' << "-Q --no-prompt Disables the prompt in interactive mode, and command echo in commandline mode." << '\n' + << "--print-env Prints all recognized environment variables, their values, and descriptions." << '\n' << "--write-ini (Over)write the configuration file with the default values & exit." << '\n' << '\n' << "MODES:\n" @@ -53,6 +54,56 @@ struct Help { << " Each line will be executed as a command in commandline mode after any arguments." << '\n' << " Input received from STDIN follows the same rules as script files." ; - return os.flush(); } }; + +struct EnvHelp { + std::string program_name; + EnvHelp(const std::string& programName) : program_name{ programName } {} + + friend std::ostream& operator<<(std::ostream& os, const EnvHelp& help) + { + std::string var{ help.program_name + "_CONFIG_DIR" }; + // ARRCON_CONFIG_DIR + os + << Global.palette.set(UIElem::ENV_VAR) << var << Global.palette.reset() << '\n' + << " " << "Current Value: " << env::getvar(var).value_or("") << '\n' + << " " << "Description:\n" + << " " << "Overrides the default config location." << '\n' + << " " << "When this is set, config files in other locations are ignored." << '\n' + << '\n' + ; + // ARRCON_HOST + var = { help.program_name + "_HOST" }; + os + << Global.palette.set(UIElem::ENV_VAR) << var << Global.palette.reset() << '\n' + << " " << "Current Value: " << env::getvar(var).value_or("") << '\n' + << " " << "Description:\n" + << " " << "Overrides the default target hostname." << '\n' + << " " << "This overrides the " << Global.palette.set(UIElem::INI_KEY_HIGHLIGHT) << "sDefaultHost" << Global.palette.reset() << " key in the INI file." << '\n' + << '\n' + ; + // ARRCON_PORT + var = { help.program_name + "_PORT" }; + os + << Global.palette.set(UIElem::ENV_VAR) << var << Global.palette.reset() << '\n' + << " " << "Current Value: " << env::getvar(var).value_or("") << '\n' + << " " << "Description:\n" + << " " << "Overrides the default target port." << '\n' + << " " << "This overrides the " << Global.palette.set(UIElem::INI_KEY_HIGHLIGHT) << "sDefaultPort" << Global.palette.reset() << " key in the INI file." << '\n' + << '\n' + ; + // ARRCON_PASS + var = { help.program_name + "_PASS" }; + os + << Global.palette.set(UIElem::ENV_VAR) << var << Global.palette.reset() << '\n' + << " " << "Current Value: " << env::getvar(var).value_or("") << '\n' + << " " << "Description:\n" + << " " << "Overrides the default target password." << '\n' + << " " << "This overrides the " << Global.palette.set(UIElem::INI_KEY_HIGHLIGHT) << "sDefaultPass" << Global.palette.reset() << " key in the INI file." << '\n' + ; + return os; + } +}; + + diff --git a/ARRCON/config.hpp b/ARRCON/config.hpp index 1a5447c..4d43932 100644 --- a/ARRCON/config.hpp +++ b/ARRCON/config.hpp @@ -22,7 +22,7 @@ namespace config { std::filesystem::path home_path; public: - Locator(const std::filesystem::path& program_dir, const std::filesystem::path& program_name) : program_location{ program_dir }, name_no_ext{ [](auto&& p) -> std::string { const std::string s{ p.generic_string() }; if (const auto pos{ s.find('.') }; pos < s.size()) return s.substr(0ull, pos); return s; }(program_name) }, env_path{ env::getvar(name_no_ext + "_CONFIG_DIR").value_or("") }, home_path{ env::get_home() } {} + Locator(const std::filesystem::path& program_dir, const std::string& program_name_no_extension) : program_location{ program_dir }, name_no_ext{ program_name_no_extension }, env_path{ env::getvar(name_no_ext + "_CONFIG_DIR").value_or("") }, home_path{ env::get_home() } {} std::string getEnvironmentVariableName(const std::string& suffix = "_CONFIG_DIR") const { @@ -107,6 +107,13 @@ namespace config { return true; } + inline void load_environment(const std::string& programNameNoExt) + { + Global.target.hostname = env::getvar(programNameNoExt + "_HOST").value_or(Global.target.hostname); + Global.target.port = env::getvar(programNameNoExt + "_PORT").value_or(Global.target.port); + Global.target.password = env::getvar(programNameNoExt + "_PASS").value_or(Global.target.password); + } + /** * @brief Save the INI configuration file. * @param path Location to save config. diff --git a/ARRCON/globals.h b/ARRCON/globals.h index 6544567..aec0294 100644 --- a/ARRCON/globals.h +++ b/ARRCON/globals.h @@ -16,10 +16,10 @@ #include #include -/// @brief The default program name on each platform. + /// @brief The default program name on each platform. inline constexpr const auto DEFAULT_PROGRAM_NAME{ #ifdef OS_WIN - "ARRCON.exe" + "ARRCON.exe" #else "ARRCON" #endif @@ -36,6 +36,8 @@ using SOCKET = unsigned long long; #define ISSUE_REPORT_URL "https://github.com/radj307/ARRCON/issues" + + /** * @struct HostInfo * @brief Contains the connection info for a single target. @@ -68,6 +70,7 @@ enum class UIElem : unsigned char { HOST_INFO, // list-hosts command (hostname/port) INI_KEY_HIGHLIGHT, // used by some exceptions to highlight INI keys. NO_RESPONSE, // interactive mode no response message + ENV_VAR, // --print-env }; static struct { @@ -82,6 +85,7 @@ static struct { std::make_pair(UIElem::HOST_INFO, color::light_gray), std::make_pair(UIElem::INI_KEY_HIGHLIGHT, color::intense_yellow), std::make_pair(UIElem::NO_RESPONSE, color::orange), + std::make_pair(UIElem::ENV_VAR, color::intense_yellow), }; const HostInfo DEFAULT_TARGET{ // default target "localhost", @@ -89,6 +93,7 @@ static struct { "" }; HostInfo target{ DEFAULT_TARGET }; // active target + HostInfo env_target{}; /// @brief When true, response packets are not printed to the terminal bool quiet{ false }; @@ -162,3 +167,4 @@ inline timespec make_timeout(const std::chrono::milliseconds& ms) } #define SELECT(nfds, rd, wr, ex, timeout) pselect(nfds, rd, wr, ex, timeout, nullptr) #endif // #ifdef OS_WIN + diff --git a/ARRCON/main.cpp b/ARRCON/main.cpp index 75aa24a..85b9074 100644 --- a/ARRCON/main.cpp +++ b/ARRCON/main.cpp @@ -16,6 +16,16 @@ #undef read #undef write +inline HostInfo get_environment_target(std::string programNameNoExt) +{ + programNameNoExt = str::toupper(programNameNoExt); + return{ + env::getvar(programNameNoExt + "_HOST").value_or(""), + env::getvar(programNameNoExt + "_PORT").value_or(""), + env::getvar(programNameNoExt + "_PASS").value_or("") + }; +} + /** * @brief Retrieve the user's specified connection target. * @param args Arguments from main(). @@ -26,7 +36,7 @@ */ inline HostInfo get_target_info(const opt::ParamsAPI2& args, const config::HostList& hostlist) { - + const auto host{ args.typegetv_any('H', "host") }, //< Argument: [-H|--host] port{ args.typegetv_any('P', "port") }, //< Argument: [-P|--port] @@ -82,7 +92,7 @@ inline void handle_hostfile_arguments(const opt::ParamsAPI2& args, config::HostL // save-host else if (const auto save_host{ args.typegetv("save-host") }; save_host.has_value()) { std::stringstream message_buffer; // save the messages in a buffer to prevent misleading messages in the event of a file writing error - + const auto target_info{ from_hostinfo(target) }; const auto& [existing, added] { @@ -249,7 +259,7 @@ int main(const int argc, char** argv) Global.palette.setDefaultResetSequence(color::reset_f); std::cout << term::EnableANSI; // enable ANSI escape sequences on windows - const opt::ParamsAPI2 args{ argc, argv, 'H', "host", 'S', "saved", 'P', "port", 'p', "pass", 'd', "delay", 'f', "file", "save-host", "remove-host"}; // parse arguments + const opt::ParamsAPI2 args{ argc, argv, 'H', "host", 'S', "saved", 'P', "port", 'p', "pass", 'd', "delay", 'f', "file", "save-host", "remove-host" }; // parse arguments // check for disable colors argument: if (const auto arg{ args.typegetv_any('n', "no-color") }; arg.has_value()) @@ -259,7 +269,15 @@ int main(const int argc, char** argv) env::PATH PATH{ argv[0] }; const auto& [myDir, myName] { PATH.resolve_split(argv[0]) }; - const config::Locator cfg_path(myDir, myName); + const std::string myNameNoExt{ + [](auto&& p) -> std::string { + const std::string s{ p.generic_string() }; + if (const auto pos{ s.find('.') }; pos < s.size()) + return str::toupper(s.substr(0ull, pos)); + return str::toupper(s); + }(myName) }; + + const config::Locator cfg_path(myDir, myNameNoExt); Global.EnvVar_CONFIG_DIR = cfg_path.getEnvironmentVariableName(); // Argument: [-q|--quiet] @@ -276,6 +294,11 @@ int main(const int argc, char** argv) std::cout << ARRCON_VERSION << std::endl; return 0; } + // Argument: [--print-env] + if (args.check("print-env")) { + std::cout << EnvHelp(myNameNoExt) << std::endl; + return 0; + } // Get the INI file's path std::filesystem::path ini_path{ cfg_path.from_extension(".ini") }; @@ -284,6 +307,9 @@ int main(const int argc, char** argv) if (file::exists(ini_path)) config::load_ini(ini_path); + // load target-specifier environment variables + config::load_environment(myNameNoExt); + if (args.empty() && !Global.allow_no_args) { std::cerr << Help(myName) << std::endl << std::endl; throw make_exception( @@ -299,7 +325,7 @@ int main(const int argc, char** argv) // Initialize the hostlist config::HostList hosts; - const auto hostfile_path{ cfg_path.from_extension(".hosts")}; + const auto hostfile_path{ cfg_path.from_extension(".hosts") }; if (file::exists(hostfile_path)) // load the hostfile if it exists hosts = config::load_hostfile(hostfile_path); @@ -345,9 +371,11 @@ int main(const int argc, char** argv) return 0; } catch (const std::exception& ex) { ///< catch exceptions std::cerr << term::get_error(!Global.no_color) << ex.what() << std::endl; + std::cerr << term::get_info(!Global.no_color) << "You can report bugs here: " << ISSUE_REPORT_URL << std::endl; return -1; } catch (...) { ///< catch all other exceptions std::cerr << term::get_crit(!Global.no_color) << "An unknown exception occurred!" << std::endl; + std::cerr << term::get_info(!Global.no_color) << "Please report this exception here: " << ISSUE_REPORT_URL << std::endl; return -1; } } \ No newline at end of file