diff --git a/src/config.cpp b/src/config.cpp index 7273a9d..a2b3e2a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -61,10 +61,13 @@ bool config::save_config() std::string dropshell_base = homedir + "/.dropshell"; mConfig["tempfiles"] = dropshell_base + "/tmp"; mConfig["backups"] = dropshell_base + "/backups"; - mConfig["template_cache"] = dropshell_base + "/cache"; - mConfig["template_registry_urls"] = { + mConfig["template_cache"] = dropshell_base + "/template_cache"; + mConfig["template_registry_URLs"] = { "https://templates.dropshell.app" }; + mConfig["template_local_paths"] = { + dropshell_base + "/local_templates" + }; mConfig["server_definition_paths"] = { dropshell_base + "/servers" }; @@ -102,7 +105,7 @@ std::string config::get_local_template_cache_path() { } std::vector config::get_template_registry_urls() { - nlohmann::json template_registry_urls = mConfig["template_registry_urls"]; + nlohmann::json template_registry_urls = mConfig["template_registry_URLs"]; std::vector urls; for (auto &url : template_registry_urls) { urls.push_back(url); @@ -110,11 +113,25 @@ std::vector config::get_template_registry_urls() { return urls; } +std::vector config::get_template_local_paths() +{ + nlohmann::json template_local_paths = mConfig["template_local_paths"]; + std::vector paths; + for (auto &path : template_local_paths) { + if (path.is_string() && !path.empty()) + paths.push_back(path); + } + return paths; +} + std::vector config::get_local_server_definition_paths() { nlohmann::json server_definition_paths = mConfig["server_definition_paths"]; std::vector paths; for (auto &path : server_definition_paths) { - paths.push_back(path); + if (path.is_string() && !path.empty()) + paths.push_back(path); + else + std::cerr << "Warning: Invalid server definition path: " << path << std::endl; } return paths; } diff --git a/src/config.hpp b/src/config.hpp index 33a9882..5eb5830 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -22,7 +22,9 @@ class config { std::string get_local_template_cache_path(); std::vector get_template_registry_urls(); + std::vector get_template_local_paths(); std::vector get_local_server_definition_paths(); + std::string get_template_upload_registry_url(); std::string get_template_upload_registry_token(); diff --git a/src/server_env_manager.cpp b/src/server_env_manager.cpp index 21144b0..2fd0adb 100644 --- a/src/server_env_manager.cpp +++ b/src/server_env_manager.cpp @@ -1,14 +1,17 @@ #include "server_env_manager.hpp" -#include "utils/envmanager.hpp" #include "utils/directories.hpp" #include "utils/utils.hpp" #include "services.hpp" #include "contrib/base64.hpp" #include "templates.hpp" +#include "utils/utils.hpp" +#include "utils/json.hpp" #include #include #include +#include + namespace dropshell { @@ -17,7 +20,7 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( return; // Construct the full path to server.env - std::string server_env_path = localfile::server_env(server_name); + std::string server_env_path = localfile::server_json(server_name); // Check if file exists if (!std::filesystem::exists(server_env_path)) { @@ -27,11 +30,16 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( try { // Use envmanager to handle the environment file - envmanager env_manager(server_env_path); - env_manager.load(); - - // Get all variables - env_manager.get_all_variables_substituted(mVariables); + nlohmann::json server_env_json = nlohmann::json::parse(std::ifstream(server_env_path)); + if (server_env_json.empty()) { + std::cerr << "Error: Failed to parse server environment file: " + server_env_path << std::endl; + return; + } + + // get the variables from the json + for (const auto& var : server_env_json.items()) { + mVariables[var.key()] = replace_with_environment_variables_like_bash(var.value()); + } // Verify required variables exist for (const auto& var : {"SSH_HOST", "SSH_USER", "SSH_PORT", "DROPSHELL_DIR"}) { @@ -51,6 +59,25 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( } } +bool server_env_manager::create_server_env(const std::string &server_env_path, const std::string &SSH_HOST, const std::string &SSH_USER, const std::string &SSH_PORT, const std::string &DROPSHELL_DIR) +{ + nlohmann::json server_env_json; + server_env_json["SSH_HOST"] = SSH_HOST; + server_env_json["SSH_USER"] = SSH_USER; + server_env_json["SSH_PORT"] = SSH_PORT; + server_env_json["DROPSHELL_DIR"] = DROPSHELL_DIR; + + try { + std::ofstream server_env_file(server_env_path); + server_env_file << server_env_json.dump(4); + server_env_file.close(); + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to create server environment file: " + std::string(e.what()) << std::endl; + return false; + } +} + std::string server_env_manager::get_variable(const std::string& name) const { auto it = mVariables.find(name); if (it == mVariables.end()) { diff --git a/src/server_env_manager.hpp b/src/server_env_manager.hpp index ea3abf2..1ca65cc 100644 --- a/src/server_env_manager.hpp +++ b/src/server_env_manager.hpp @@ -8,7 +8,7 @@ #include #include #include -#include "utils/envmanager.hpp" +#include namespace dropshell { // class to hold a command to run on the remote server. @@ -47,6 +47,14 @@ std::string makesafecmd(const std::string& command); class server_env_manager { public: server_env_manager(const std::string& server_name); + + static bool create_server_env( + const std::string& server_env_path, + const std::string& SSH_HOST, + const std::string& SSH_USER, + const std::string& SSH_PORT, + const std::string& DROPSHELL_DIR); + std::string get_variable(const std::string& name) const; // trivial getters. @@ -83,6 +91,8 @@ class server_env_manager { bool mValid; }; + + } // namespace dropshell diff --git a/src/servers.cpp b/src/servers.cpp index d414f6c..0880e30 100644 --- a/src/servers.cpp +++ b/src/servers.cpp @@ -19,14 +19,12 @@ namespace dropshell { std::vector get_configured_servers() { std::vector servers; - std::vector local_config_directories = gConfig().get_local_config_directories(); - if (local_config_directories.empty()) + std::vector lsdp = gConfig().get_local_server_definition_paths(); + if (lsdp.empty()) return servers; - for (int i = 0; i < local_config_directories.size(); i++) { - std::string servers_dir = localpath::config_servers(i); + for (auto servers_dir : lsdp) { if (!servers_dir.empty() && std::filesystem::exists(servers_dir)) { - for (const auto& entry : std::filesystem::directory_iterator(servers_dir)) { if (std::filesystem::is_directory(entry)) { std::string server_name = entry.path().filename().string(); @@ -52,12 +50,12 @@ std::vector get_configured_servers() { ServerInfo get_server_info(const std::string &server_name) { - std::vector local_config_directories = gConfig().get_local_config_directories(); - if (local_config_directories.empty()) + std::vector lsdp = gConfig().get_local_server_definition_paths(); + if (lsdp.empty()) return ServerInfo(); - for (auto &config_dir : local_config_directories) { - std::string server_dir = config_dir + "/servers/" + server_name; + for (auto &config_dir : lsdp) { + std::string server_dir = config_dir + "/" + server_name; if (std::filesystem::exists(server_dir)) { server_env_manager env(server_name); if (!env.is_valid()) { @@ -177,8 +175,12 @@ void create_server(const std::string &server_name) } // 2. create a new directory in the user config directory - std::string config_servers_dir = localpath::config_servers(); - std::string server_dir = config_servers_dir + "/" + server_name; + auto lsdp = gConfig().get_local_server_definition_paths(); + if (lsdp.empty() || lsdp[0].empty()) { + std::cerr << "Error: Local server definition path not found - is DropShell initialised?" << std::endl; + return; + } + std::string server_dir = lsdp[0] + "/" + server_name; std::filesystem::create_directory(server_dir); // 3. create a template server.env file in the server directory diff --git a/src/services.cpp b/src/services.cpp index f05014e..eb58bcd 100644 --- a/src/services.cpp +++ b/src/services.cpp @@ -20,24 +20,25 @@ std::vector get_server_services_info(const std::string& server if (server_name.empty()) return services; - std::vector local_config_directories = gConfig().get_local_config_directories(); - if (local_config_directories.empty()) { - std::cerr << "Error: No local config directories found" << std::endl; + std::vector local_server_definition_paths = gConfig().get_local_server_definition_paths(); + if (local_server_definition_paths.empty()) { + std::cerr << "Error: No local server definition paths found" << std::endl; std::cerr << "Run 'dropshell init' to initialise DropShell" << std::endl; return services; } - for (int i = 0; i < localpath::num_config_directories(); i++) { - std::string serverpath = localpath::config_servers(i); - if (serverpath.empty()) { + for (const auto& server_definition_path : local_server_definition_paths) { + fs::path serverpath = server_definition_path + "/" + server_name; + if (serverpath.string().empty()) { std::cerr << "Error: Server directory not found: " << serverpath << std::endl; return services; } - fs::path server_dir = fs::path(serverpath) / server_name; - if (fs::exists(server_dir)) { - for (const auto& entry : fs::directory_iterator(server_dir)) { + fs::path servicepath = serverpath / "services"; + if (fs::exists(servicepath)) { + for (const auto& entry : fs::directory_iterator(servicepath)) { if (fs::is_directory(entry)) { - auto service = get_service_info(server_name, entry.path().filename().string()); + std::string service_name = entry.path().filename().string(); + auto service = get_service_info(server_name, service_name); if (!service.template_name.empty()) { services.push_back(service); } @@ -45,7 +46,6 @@ std::vector get_server_services_info(const std::string& server } } // end of for (int i = 0; i < getNumConfigDirectories(); i++) } - return services; } @@ -125,7 +125,7 @@ std::set list_backups(const std::string &server_name, const std::st return backups; } - std::string backups_dir = localpath::backups_path(); + std::string backups_dir = gConfig().get_local_backup_path(); if (backups_dir.empty()) return backups; diff --git a/src/templates.cpp b/src/templates.cpp index 18dc304..2ff846f 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -16,8 +16,8 @@ namespace dropshell { - bool get_templates(std::vector& templates) { - templates.clear(); + std::set template_source_local::get_template_list() { + std::set templates; // Helper function to add templates from a directory auto add_templates_from_dir = [&templates](const std::string& dir_path) { @@ -25,68 +25,47 @@ return; for (const auto& entry : std::filesystem::directory_iterator(dir_path)) - if (entry.is_directory()) { - - template_info info(entry.path().filename().string(), entry.path().string()); - - // Check if template with same name already exists - bool duplicate = false; - auto it = std::find_if(templates.begin(), templates.end(), - [&info](const template_info& t) { return t.template_name == info.template_name; }); - duplicate = (it!=templates.end()); - - if (!duplicate) - templates.push_back(info); - } + if (entry.is_directory()) + templates.insert(entry.path().filename().string()); }; - // add templates from the local config directories - std::vector template_config_directories; - get_all_template_config_directories(template_config_directories); - for (const auto& path : template_config_directories) { - add_templates_from_dir(path); - } - - return true; + add_templates_from_dir(mLocalPath); + return templates; } - - bool get_template_info(const std::string& template_name, template_info& info) { - // add templates from the local config directories - std::vector paths_to_search; - get_all_template_config_directories(paths_to_search); - - for (const auto& path : paths_to_search) { - std::filesystem::path full_path = path + "/" + template_name; - if (std::filesystem::exists(full_path)) - { - info.template_name = template_name; - info.local_template_path = full_path.string(); - return true; - } - } - std::cout << "Warning: Template '" << template_name << "' not found" << std::endl; - return false; + bool template_source_local::has_template(const std::string& template_name) { + return std::filesystem::exists(mLocalPath / template_name); } - bool template_command_exists(const std::string &template_name, const std::string &command) - { - template_info info; - if (!get_template_info(template_name, info)) { - return false; + std::string template_source_local::template_command_filename(const std::string& template_name, const std::string& command) { + std::vector commands = { + command, + command + ".sh" + }; + + for (const auto& cmd : commands) { + std::filesystem::path path = mLocalPath / template_name / cmd; + if (std::filesystem::exists(path)) + return cmd; } - - std::string path = info.local_template_path + "/" + command + ".sh"; - return (std::filesystem::exists(path)); + return ""; + } + + template_info template_source_local::get_template_info(const std::string& template_name) { + std::filesystem::path path = mLocalPath / template_name; + + if (!std::filesystem::exists(path)) + return template_info(); + + return template_info( + template_name, + mLocalPath.string(), + path + ); } - void list_templates() { - std::vector templates; - - if (!get_templates(templates)) { - std::cerr << "Error: Failed to get templates" << std::endl; - return; - } + void template_manager::list_templates() { + auto templates = get_template_list(); if (templates.empty()) { std::cout << "No templates found." << std::endl; @@ -94,44 +73,55 @@ } std::cout << "Available templates:" << std::endl; - std::cout << std::left << std::setw(20) << "Name" << "Path" << std::endl; - std::cout << std::string(60, '-') << std::endl; - + + // sort templates alphabetically. + std::sort(templates.begin(), templates.end()); + // print templates. + std::cout << std::string(60, '-') << std::endl; + bool first = true; for (const auto& t : templates) { - std::cout << std::left << std::setw(20) << t.template_name << t.local_template_path << std::endl; + std::cout << (first ? "" : ", ") << t; + first = false; } + std::cout << std::endl; + std::cout << std::string(60, '-') << std::endl; } - void create_template(const std::string& template_name) { + void template_manager::create_template(const std::string& template_name) { // 1. Create a new directory in the user templates directory - std::vector local_config_directories = gConfig().get_local_config_directories(); + std::vector local_server_definition_paths = gConfig().get_local_server_definition_paths(); - if (local_config_directories.empty()) { - std::cerr << "Error: No local config directories found" << std::endl; + if (local_server_definition_paths.empty()) { + std::cerr << "Error: No local server definition paths found" << std::endl; std::cerr << "Run 'dropshell init' to initialise DropShell" << std::endl; return; } - template_info info; - if (get_template_info(template_name, info)) { - std::cerr << "Error: Template '" << template_name << "' already exists at " << info.local_template_path << std::endl; + auto info = get_template_info(template_name); + if (info.is_set()) { + std::cerr << "Error: Template '" << template_name << "' already exists at " << info.locationID() << std::endl; return; } - std::string new_template_path = localpath::config_templates() + "/" + template_name; + auto local_template_paths = gConfig().get_template_local_paths(); + if (local_template_paths.empty()) { + std::cerr << "Error: No local template paths found" << std::endl; + std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl; + return; + } + std::string new_template_path = local_template_paths[0] + "/" + template_name; // Create the new template directory std::filesystem::create_directories(new_template_path); // 2. Copy the example template from the system templates directory - std::string system_templates_dir = localpath::system_templates(); - std::string example_template_path = system_templates_dir + "/example-nginx"; - - if (!std::filesystem::exists(example_template_path)) { - std::cerr << "Error: Example template not found at " << example_template_path << std::endl; + auto example_info = gTemplateManager().get_template_info("example-nginx"); + if (!example_info.is_set()) { + std::cerr << "Error: Example template not found" << std::endl; return; } - + std::string example_template_path = example_info.local_template_path(); + // Copy all files from example template to new template for (const auto& entry : std::filesystem::recursive_directory_iterator(example_template_path)) { std::string relative_path = entry.path().string().substr(example_template_path.length()); @@ -144,10 +134,9 @@ } } - // modify the TEMPLATE=example line in the service.env file to TEMPLATE= + // modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE= std::string search_string = "TEMPLATE="; std::string replacement_line = "TEMPLATE=" + template_name; - // replace the line in the example/service.env file with the replacement line std::string service_env_path = new_template_path + "/example/.template_info.env"; if (!replace_line_in_file(service_env_path, search_string, replacement_line)) { std::cerr << "Error: Failed to replace TEMPLATE= line in the .template_info.env file" << std::endl; @@ -175,24 +164,10 @@ std::cout << std::endl; std::cout << "Template '" << template_name << "' created at " << new_template_path << std::endl; + test_template(new_template_path); } - bool get_all_template_config_directories(std::vector &template_config_directories) - { - template_config_directories.clear(); - for (int i = 0; i < localpath::num_config_directories(); i++) { - std::string config_templates_path = localpath::config_templates(i); - if (config_templates_path.empty()) { - std::cerr << "Error: Templates directory not found: " << config_templates_path << std::endl; - return false; - } - template_config_directories.push_back(config_templates_path); - } - template_config_directories.push_back(localpath::system_templates()); - return true; - } - - bool required_file(std::string path, std::string template_name) + bool template_manager::required_file(std::string path, std::string template_name) { if (!std::filesystem::exists(path)) { std::cerr << "Error: " << path << " file not found in template " << template_name << std::endl; @@ -201,7 +176,7 @@ return true; } - bool test_template(const std::string &template_path) + bool template_manager::test_template(const std::string &template_path) { std::string template_name = std::filesystem::path(template_path).filename().string(); @@ -256,4 +231,10 @@ return true; } + template_manager & gTemplateManager() + { + static template_manager instance; + return instance; + } + } // namespace dropshell diff --git a/src/templates.hpp b/src/templates.hpp index b0649b7..bd1da32 100644 --- a/src/templates.hpp +++ b/src/templates.hpp @@ -1,39 +1,98 @@ - #include #include #include +#include +#include + +#include "utils/json.hpp" namespace dropshell { +typedef enum template_source_type { + TEMPLATE_SOURCE_TYPE_LOCAL, + TEMPLATE_SOURCE_TYPE_REGISTRY, + TEMPLATE_SOURCE_NOT_SET +} template_source_type; + class template_info { public: - template_info() {} - template_info(std::string n, std::string p) : template_name(n), local_template_path(p) {} - - std::string template_name; - std::string local_template_path; + template_info() : mIsSet(false) {} + template_info(const std::string& template_name, const std::string& location_id, const std::filesystem::path& local_template_path) : mTemplateName(template_name), mLocationID(location_id), mTemplateLocalPath(local_template_path), mIsSet(true) {} + virtual ~template_info() {} + bool is_set() { return mIsSet; } + std::string name() { return mTemplateName; } + std::string locationID() { return mLocationID; } + std::filesystem::path local_template_path() { return mTemplateLocalPath; } + private: + std::string mTemplateName; + std::string mLocationID; + std::filesystem::path mTemplateLocalPath; // source or cache. + bool mIsSet; }; - // templates are stored in multiple locations: - // 1. /opt/dropshell/templates - // 2. CONFIG_DIR/templates +class template_source_interface { + public: + virtual ~template_source_interface() {} + virtual std::set get_template_list() = 0; + virtual bool has_template(const std::string& template_name) = 0; + virtual template_info get_template_info(const std::string& template_name) = 0; + virtual std::string template_command_filename(const std::string& template_name,const std::string& command) = 0; +}; +class template_source_registry : public template_source_interface { + public: + template_source_registry(std::string URL) : mURL(URL) {} - bool get_templates(std::vector& templates); - bool get_template_info(const std::string& template_name, template_info& info); - bool template_command_exists(const std::string& template_name,const std::string& command); - void list_templates(); + ~template_source_registry() {} + std::set get_template_list(); + bool has_template(const std::string& template_name); + template_info get_template_info(const std::string& template_name); + std::string template_command_filename(const std::string& template_name,const std::string& command); + private: + std::filesystem::path get_cache_dir(); + private: + std::string mURL; + std::vector mTemplates; // cached list. +}; - // create a template - // 1. create a new directory in the user templates directory - // 2. copy the example template from the system templates directory into the new directory - // 3. print out the README.txt file in the new template directory, and the path to the new template - void create_template(const std::string& template_name); +class template_source_local : public template_source_interface { + public: + template_source_local(std::string local_path) : mLocalPath(local_path) {} + ~template_source_local() {} + std::set get_template_list(); + bool has_template(const std::string& template_name); + template_info get_template_info(const std::string& template_name); + std::string template_command_filename(const std::string& template_name,const std::string& command); + private: + std::filesystem::path mLocalPath; +}; - bool get_all_template_config_directories(std::vector& template_config_directories); +class template_manager { + public: + template_manager() : mLoaded(false) {} + ~template_manager() {} + std::set get_template_list(); + bool has_template(const std::string& template_name); + template_info get_template_info(const std::string& template_name); + + std::string template_command_filename(const std::string& template_name,const std::string& command); + void create_template(const std::string& template_name); + bool test_template(const std::string& template_path); + + void list_templates(); + + private: + void load_sources(); + bool required_file(std::string path, std::string template_name); + + private: + bool mLoaded; + std::vector> mSources; +}; + +template_manager & gTemplateManager(); - bool test_template(const std::string& template_path); } // namespace dropshell diff --git a/src/utils/directories.cpp b/src/utils/directories.cpp index 927fcd7..56e2187 100644 --- a/src/utils/directories.cpp +++ b/src/utils/directories.cpp @@ -22,9 +22,9 @@ namespace localfile { return std::string(); } - std::string server_env(const std::string &server_name) { + std::string server_json(const std::string &server_name) { std::string serverpath = localpath::server(server_name); - return (serverpath.empty() ? "" : (fs::path(serverpath) / "server.env").string()); + return (serverpath.empty() ? "" : (fs::path(serverpath) / "server.json").string()); } std::string service_env(const std::string &server_name, const std::string &service_name) { diff --git a/src/utils/directories.hpp b/src/utils/directories.hpp index d0ab45a..713d662 100644 --- a/src/utils/directories.hpp +++ b/src/utils/directories.hpp @@ -14,7 +14,7 @@ namespace dropshell { // server_definition_path // |-- - // |-- server.env + // |-- server.json // |-- services // |-- // |-- service.env @@ -42,7 +42,7 @@ namespace dropshell { namespace localfile { // ~/.config/dropshell/dropshell.json std::string dropshell_json(); - std::string server_env(const std::string &server_name); + std::string server_json(const std::string &server_name); std::string service_env(const std::string &server_name, const std::string &service_name); std::string template_info_env(const std::string &server_name, const std::string &service_name); } // namespace localfile diff --git a/src/utils/envmanager.cpp b/src/utils/envmanager.cpp index 8a1916b..31bbee8 100644 --- a/src/utils/envmanager.cpp +++ b/src/utils/envmanager.cpp @@ -72,18 +72,6 @@ void envmanager::get_all_variables(std::map& variables variables = m_variables; } -std::string envmanager::get_variable_substituted(std::string key) const { - std::string value = get_variable(key); - return expand_patterns(value); -} - -void envmanager::get_all_variables_substituted(std::map& variables) const { - variables.clear(); - for (const auto& pair : m_variables) { - variables[pair.first] = expand_patterns(pair.second); - } -} - void envmanager::add_variables(std::map variables) { for (auto& pair : variables) { set_variable(pair.first, pair.second); @@ -98,26 +86,4 @@ void envmanager::clear_variables() { m_variables.clear(); } -std::string envmanager::expand_patterns(std::string str) const { - // Combined regex pattern for both ${var} and $var formats - std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); - std::string result = str; - std::smatch match; - - while (std::regex_search(result, match, var_pattern)) { - // match[1] will contain capture from ${var} format - // match[2] will contain capture from $var format - std::string var_name = match[1].matched ? match[1].str() : match[2].str(); - - // Get value from system environment variables - const char* env_value = std::getenv(var_name.c_str()); - std::string value = env_value ? env_value : ""; - - result = result.replace(match.position(), match.length(), value); - } - - // dequote the result - return result; -} - } // namespace dropshell diff --git a/src/utils/envmanager.hpp b/src/utils/envmanager.hpp index a8d560b..45236f2 100644 --- a/src/utils/envmanager.hpp +++ b/src/utils/envmanager.hpp @@ -25,20 +25,12 @@ class envmanager { std::string get_variable(std::string key) const; void get_all_variables(std::map& variables) const; - // get variables, but replace patterns ${var} and $var with the actual environment variable in the returned string. - // trim whitespace from the values. - std::string get_variable_substituted(std::string key) const; - void get_all_variables_substituted(std::map& variables) const; - // add variables to the environment files. // trim whitespace from the values. void add_variables(std::map variables); void set_variable(std::string key, std::string value); void clear_variables(); - private: - std::string expand_patterns(std::string str) const; - private: std::string m_path; std::map m_variables; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index b29f968..d892ae9 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace dropshell { void maketitle(const std::string& title) { @@ -268,4 +269,32 @@ std::vector split(const std::string& str, const std::string& delimi return tokens; } + +std::string replace_with_environment_variables_like_bash(std::string str) { + // Combined regex pattern for both ${var} and $var formats + std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); + std::string result = str; + std::smatch match; + + while (std::regex_search(result, match, var_pattern)) { + // match[1] will contain capture from ${var} format + // match[2] will contain capture from $var format + std::string var_name = match[1].matched ? match[1].str() : match[2].str(); + + // Get value from system environment variables + const char* env_value = std::getenv(var_name.c_str()); + std::string value = env_value ? env_value : ""; + + result = result.replace(match.position(), match.length(), value); + } + + // dequote the result + return result; +} + + +std::string requote(std::string str) { + return quote(trim(dequote(trim(str)))); +} + } // namespace dropshell \ No newline at end of file diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index aa46886..ab8ce7e 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -20,6 +20,8 @@ std::string trim(std::string str); std::string dequote(std::string str); std::string quote(std::string str); std::string halfquote(std::string str); +std::string requote(std::string str); + std::string multi2string(std::vector values); std::vector string2multi(std::string values); std::vector split(const std::string& str, const std::string& delimiter); @@ -34,4 +36,6 @@ void ensure_directories_exist(std::vector directories); std::vector search(const std::string &pat, const std::string &txt); int count_substring(const std::string &substring, const std::string &text); +std::string replace_with_environment_variables_like_bash(std::string str); + } // namespace dropshell \ No newline at end of file