Shift things around
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled

This commit is contained in:
Your Name
2025-05-17 10:18:25 +12:00
parent 9eb9707c2e
commit 93e563948f
61 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,11 @@
#ifndef ASSERT_HPP
#define ASSERT_HPP
#define ASSERT(condition, message) \
if (!(condition)) { \
std::cerr << "Assertion failed: " << message << std::endl; \
std::exit(1); \
}
#endif // ASSERT_HPP

View File

@ -0,0 +1,42 @@
#include "b64ed.hpp"
#include <vector>
// Custom base64 encoding/decoding tables
static const std::string custom_base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+_";
std::string base64_encode(const std::string &in) {
std::string out;
int val = 0, valb = -6;
for (unsigned char c : in) {
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(custom_base64_chars[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6) out.push_back(custom_base64_chars[((val << 8) >> (valb + 8)) & 0x3F]);
while (out.size() % 4) out.push_back('=');
return out;
}
std::string base64_decode(const std::string &in) {
std::vector<int> T(256, -1);
for (int i = 0; i < 64; i++) T[custom_base64_chars[i]] = i;
std::string out;
int val = 0, valb = -8;
for (unsigned char c : in) {
if (T[c] == -1) break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
}

View File

@ -0,0 +1,9 @@
#ifndef B64ED_HPP
#define B64ED_HPP
#include <string>
std::string base64_decode(const std::string &in);
std::string base64_encode(const std::string &in);
#endif

View File

@ -0,0 +1,180 @@
#include "directories.hpp"
#include "config.hpp"
#include "server_env_manager.hpp"
#include <iostream>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;
namespace dropshell {
namespace localfile {
std::string dropshell_json() {
// Try ~/.config/dropshell/dropshell.json
std::string homedir = localpath::current_user_home();
if (!homedir.empty()) {
fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / "dropshell.json";
return user_path.string();
}
return std::string();
}
std::string server_json(const std::string &server_name) {
std::string serverpath = localpath::server(server_name);
return (serverpath.empty() ? "" : (fs::path(serverpath) / "server.json").string());
}
std::string service_env(const std::string &server_name, const std::string &service_name) {
std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / "service.env").string());
}
std::string template_info_env(const std::string &server_name, const std::string &service_name)
{
std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / ".template_info.env").string());
}
} // namespace localfile
// ------------------------------------------------------------------------------------------
namespace localpath {
std::string server(const std::string &server_name) {
for (std::filesystem::path dir : gConfig().get_local_server_definition_paths())
if (fs::exists(dir / server_name))
return dir / server_name;
return "";
}
std::string service(const std::string &server_name, const std::string &service_name) {
std::string serverpath = localpath::server(server_name);
return ((serverpath.empty() || service_name.empty()) ? "" : (serverpath+"/"+service_name));
}
std::string remote_versions(const std::string &server_name, const std::string &service_name)
{
std::string template_cache_path = gConfig().get_local_template_cache_path();
return ((template_cache_path.empty() || service_name.empty()) ? "" :
(template_cache_path+"/remote_versions/"+service_name+".json"));
}
std::string agent(){
return current_user_home() + "/.local/dropshell_agent";
}
std::string current_user_home(){
char * homedir = std::getenv("HOME");
if (homedir)
{
std::filesystem::path homedir_path(homedir);
return fs::canonical(homedir_path).string();
}
std::cerr << "Warning: Couldn't determine user directory" << std::endl;
return std::string();
}
} // namespace localpath
// ------------------------------------------------------------------------------------------
// remote paths
// DROPSHELL_DIR
// |-- backups
// |-- temp_files
// |-- agent
// |-- services
// |-- service name
// |-- config
// |-- service.env
// |-- template
// |-- (script files)
// |-- config
// |-- service.env
// |-- (other config files for specific server&service)
namespace remotefile {
std::string service_env(const std::string &server_name, const std::string &service_name)
{
return remotepath::service_config(server_name, service_name) + "/service.env";
}
}
namespace remotepath {
std::string DROPSHELL_DIR(const std::string &server_name)
{
return server_env_manager(server_name).get_DROPSHELL_DIR();
}
std::string services(const std::string &server_name)
{
std::string dsp = DROPSHELL_DIR(server_name);
return (dsp.empty() ? "" : (dsp + "/services"));
}
std::string service(const std::string &server_name, const std::string &service_name)
{
std::string services_path = services(server_name);
return (services_path.empty() ? "" : (services_path + "/" + service_name));
}
std::string service_config(const std::string &server_name, const std::string &service_name)
{
std::string service_path = service(server_name, service_name);
return (service_path.empty() ? "" : (service_path + "/config"));
}
std::string service_template(const std::string &server_name, const std::string &service_name)
{
std::string service_path = service(server_name, service_name);
return (service_path.empty() ? "" : (service_path + "/template"));
}
std::string backups(const std::string &server_name)
{
std::string dsp = DROPSHELL_DIR(server_name);
return (dsp.empty() ? "" : (dsp + "/backups"));
}
std::string temp_files(const std::string &server_name)
{
std::string dsp = DROPSHELL_DIR(server_name);
return (dsp.empty() ? "" : (dsp + "/temp_files"));
}
std::string agent(const std::string &server_name)
{
std::string dsp = DROPSHELL_DIR(server_name);
return (dsp.empty() ? "" : (dsp + "/agent"));
}
std::string service_env(const std::string &server_name, const std::string &service_name)
{
std::string service_path = service_config(server_name, service_name);
return (service_path.empty() ? "" : (service_path + "/service.env"));
}
} // namespace remotepath
// ------------------------------------------------------------------------------------------
// Utility functions
std::string get_parent(const std::filesystem::path path)
{
if (path.empty())
return std::string();
return path.parent_path().string();
}
std::string get_child(const std::filesystem::path path)
{
if (path.empty())
return std::string();
return path.filename().string();
}
} // namespace dropshell

View File

@ -0,0 +1,103 @@
#ifndef DIRECTORIES_HPP
#define DIRECTORIES_HPP
#include <string>
#include <filesystem>
namespace dropshell {
// all functions return empty string on failure
//------------------------------------------------------------------------------------------------
// local user config directories
// ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent/bb64
// server_definition_path
// |-- <server_name>
// |-- server.json
// |-- services
// |-- <service_name>
// |-- service.env
// |-- .template_info.env
// |-- (...other config files for specific server&service...)
// backup path
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// temp files path
// template cache path
// |-- templates
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- _default.env
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// |-- remote_versions
// | |-- server_name-service_name.json
namespace localfile {
// ~/.config/dropshell/dropshell.json
std::string dropshell_json();
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
namespace localpath {
std::string server(const std::string &server_name);
std::string service(const std::string &server_name, const std::string &service_name);
std::string remote_versions(const std::string &server_name, const std::string &service_name);
std::string agent();
std::string current_user_home();
} // namespace local
//------------------------------------------------------------------------------------------------
// remote paths
// DROPSHELL_DIR
// |-- backups
// |-- temp_files
// |-- agent
// | |-- bb64
// | |-- (other agent files, including _allservicesstatus.sh)
// |-- services
// |-- service name
// |-- config
// |-- service.env
// |-- template
// |-- (script files)
// |-- config
// |-- service.env
// |-- (other config files for specific server&service)
namespace remotefile {
std::string service_env(const std::string &server_name, const std::string &service_name);
} // namespace remotefile
namespace remotepath {
std::string DROPSHELL_DIR(const std::string &server_name);
std::string services(const std::string &server_name);
std::string service(const std::string &server_name, const std::string &service_name);
std::string service_config(const std::string &server_name, const std::string &service_name);
std::string service_template(const std::string &server_name, const std::string &service_name);
std::string backups(const std::string &server_name);
std::string temp_files(const std::string &server_name);
std::string agent(const std::string &server_name);
} // namespace remotepath
//------------------------------------------------------------------------------------------------
// utility functions
std::string get_parent(const std::filesystem::path path);
std::string get_child(const std::filesystem::path path);
} // namespace dropshell
#endif // DIRECTORIES_HPP

View File

@ -0,0 +1,89 @@
#include "envmanager.hpp"
#include "utils/utils.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <regex>
#include <cstdlib> // For std::getenv
namespace dropshell {
envmanager::envmanager(std::string path) : m_path(path) {
}
envmanager::~envmanager() {
}
bool envmanager::load() {
std::ifstream file(m_path);
if (!file.is_open()) {
return false;
}
m_variables.clear();
std::string line;
while (std::getline(file, line)) {
line=trim(line);
// Skip empty lines and comments
if (line.empty() || line[0] == '#') {
continue;
}
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// trim whitespace from the key and value
m_variables[dequote(trim(key))] = dequote(trim(value));
}
}
file.close();
return true;
}
void envmanager::save() {
std::ofstream file(m_path);
if (!file.is_open()) {
return;
}
for (const auto& pair : m_variables) {
file << pair.first << "=" << quote(pair.second) << std::endl;
}
file.close();
}
std::string envmanager::get_variable(std::string key) const {
key = dequote(trim(key));
// Use case-insensitive comparison to find the key
for (const auto& pair : m_variables) {
if (pair.first == key) {
return pair.second;
}
}
return "";
}
void envmanager::get_all_variables(std::map<std::string, std::string>& variables) const {
variables = m_variables;
}
void envmanager::add_variables(std::map<std::string, std::string> variables) {
for (auto& pair : variables) {
set_variable(pair.first, pair.second);
}
}
void envmanager::set_variable(std::string key, std::string value) {
m_variables[dequote(trim(key))] = dequote(trim(value));
}
void envmanager::clear_variables() {
m_variables.clear();
}
} // namespace dropshell

View File

@ -0,0 +1,47 @@
#ifndef ENV_MANAGER_HPP
#define ENV_MANAGER_HPP
#include <string>
#include <map>
#include <vector>
namespace dropshell {
// envmanager is a class that manages the environment files for the application.
// it is responsible for loading the environment files, and providing a class to access the variables.
// it can also save the environment files.
class envmanager {
public:
envmanager(std::string path);
~envmanager();
// load all variables from the environment file
bool load();
// save all variables to the environment file
void save();
// get variables from the environment files. Trim whitespace from the values.
// keys are case-sensitive.
std::string get_variable(std::string key) const;
void get_all_variables(std::map<std::string, std::string>& variables) const;
// add variables to the environment files.
// trim whitespace from the values.
void add_variables(std::map<std::string, std::string> variables);
void set_variable(std::string key, std::string value);
void clear_variables();
private:
std::string m_path;
std::map<std::string, std::string> m_variables;
};
// utility functions
std::string trim(std::string str);
std::string dequote(std::string str);
std::string multi2string(std::vector<std::string> values);
std::vector<std::string> string2multi(std::string values);
} // namespace dropshell
#endif

View File

@ -0,0 +1,179 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <iostream>
#include <string>
#include <cstdlib>
#include <sstream>
#include "utils/assert.hpp"
#include "execute.hpp"
#include "utils/utils.hpp"
#include "utils/b64ed.hpp"
#include "config.hpp"
#include "utils/directories.hpp"
namespace dropshell
{
bool EXITSTATUSCHECK(int ret)
{
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
}
bool execute_local_command_interactive(const sCommand &command)
{
if (command.get_command_to_run().empty())
return false;
std::string full_command = command.construct_cmd(localpath::agent()); // Get the command string
pid_t pid = fork();
if (pid == -1)
{
// Fork failed
perror("fork failed");
return false;
}
else if (pid == 0)
{
int rval = system(full_command.c_str());
exit(rval);
}
else
{
// Parent process
int ret;
// Wait for the child process to complete
waitpid(pid, &ret, 0);
return EXITSTATUSCHECK(ret);
}
}
bool execute_local_command_and_capture_output(const sCommand &command, std::string *output)
{
ASSERT(output != nullptr, "Output string must be provided");
if (command.get_command_to_run().empty())
return false;
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1";
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe)
{
return false;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr)
{
(*output) += buffer;
}
int ret = pclose(pipe);
return EXITSTATUSCHECK(ret);
}
bool execute_local_command(std::string command, std::string *output, cMode mode)
{
return execute_local_command("", command, {}, output, mode);
}
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> &env_vars, std::string *output, cMode mode)
{
sCommand command(directory_to_run_in, command_to_run, env_vars);
if (hasFlag(mode, cMode::Interactive))
{
ASSERT(!hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together");
return execute_local_command_interactive(command);
}
if (hasFlag(mode, cMode::CaptureOutput))
{
ASSERT(output != nullptr, "Capture output mode requires an output string to be provided");
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
return execute_local_command_and_capture_output(command, output);
}
if (command.get_command_to_run().empty())
return false;
bool silent = hasFlag(mode, cMode::Silent);
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1" + (silent ? " > /dev/null" : "");
int ret = system(full_cmd.c_str());
bool ok = EXITSTATUSCHECK(ret);
if (!ok && !silent)
{
std::cerr << "Error: Failed to execute command: " << std::endl;
std::cerr << full_cmd << std::endl;
}
return ok;
}
bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &remote_command, cMode mode, std::string *output)
{
if (remote_command.get_command_to_run().empty())
return false;
ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
<< ssh_info.user << "@" << ssh_info.host;
std::string remote_agent_path = remotepath::agent(ssh_info.server_ID);
bool rval = execute_local_command(
"", // directory to run in
ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path), // local command to run
{}, // environment variables
output, // output string
mode // mode
);
if (!rval && !hasFlag(mode, cMode::Silent))
{
std::cerr << std::endl
<< std::endl;
std::cerr << "Error: Failed to execute ssh command:" << std::endl;
std::cerr << "\033[90m" << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path) << "\033[0m" << std::endl;
std::cerr << std::endl
<< std::endl;
}
return rval;
}
std::string sCommand::makesafecmd(std::string agent_path, const std::string &command) const
{
if (command.empty())
return "";
std::string encoded = base64_encode(dequote(trim(command)));
std::string commandstr = agent_path + "/bb64 " + encoded;
return commandstr;
}
std::string sCommand::construct_cmd(std::string agent_path) const
{
if (mCmd.empty())
return "";
// need to construct to change directory and set environment variables
std::string cmdstr;
if (!mDir.empty())
cmdstr += "cd " + quote(mDir) + " && ";
if (!mVars.empty())
for (const auto &env_var : mVars)
cmdstr += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " ";
cmdstr += mCmd;
if (!agent_path.empty())
cmdstr = makesafecmd(agent_path, cmdstr);
return cmdstr;
}
} // namespace dropshell

View File

@ -0,0 +1,74 @@
#ifndef EXECUTE_HPP
#define EXECUTE_HPP
#include <string>
#include <map>
namespace dropshell {
class sCommand;
// mode bitset
enum class cMode {
Defaults = 0,
Interactive = 1,
Silent = 2,
CaptureOutput = 4
};
inline cMode operator&(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & static_cast<int>(rhs));}
inline cMode operator+(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) | static_cast<int>(rhs));}
inline cMode operator-(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & ~static_cast<int>(rhs));}
inline cMode operator|(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) | static_cast<int>(rhs));}
inline cMode operator|=(cMode & lhs, cMode rhs) {return lhs = lhs | rhs;}
inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;}
typedef struct sSSHInfo {
std::string host;
std::string user;
std::string port;
std::string server_ID; // dropshell name for server.
} sSSHInfo;
bool execute_local_command(std::string command, std::string * output = nullptr, cMode mode = cMode::Defaults);
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults);
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr);
// ------------------------------------------------------------------------------------------------
// class to hold a command to run on the remote server.
class sCommand {
public:
sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars) :
mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {}
std::string get_directory_to_run_in() const { return mDir; }
std::string get_command_to_run() const { return mCmd; }
const std::map<std::string, std::string>& get_env_vars() const { return mVars; }
void add_env_var(const std::string& key, const std::string& value) { mVars[key] = value; }
bool empty() const { return mCmd.empty(); }
std::string construct_cmd(std::string agent_path) const;
private:
std::string makesafecmd(std::string agent_path, const std::string& command) const;
private:
std::string mDir;
std::string mCmd;
std::map<std::string, std::string> mVars;
};
bool EXITSTATUSCHECK(int ret);
} // namespace dropshell
#endif

131
source/src/utils/hash.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "utils/hash.hpp"
#define XXH_INLINE_ALL
#include "contrib/xxhash.hpp"
#include <fstream>
#include <filesystem>
#include <iostream>
namespace dropshell {
uint64_t hash_file(const std::string &path) {
// Create hash state
XXH64_state_t* const state = XXH64_createState();
if (state == nullptr) {
std::cerr << "Failed to create hash state" << std::endl;
return 0;
}
// Initialize state with seed 0
XXH64_hash_t const seed = 0; /* or any other value */
if (XXH64_reset(state, seed) == XXH_ERROR) return 0;
// Open file
std::ifstream file(path, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << path << std::endl;
XXH64_freeState(state);
return 0;
}
// Read file in chunks and update hash
const size_t buffer_size = 4096;
char buffer[buffer_size];
while (file.read(buffer, buffer_size)) {
if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) {
std::cerr << "Failed to update hash" << std::endl;
XXH64_freeState(state);
return 0;
}
}
// Handle any remaining bytes
if (file.gcount() > 0) {
if (XXH64_update(state, buffer, file.gcount()) == XXH_ERROR) {
std::cerr << "Failed to update hash" << std::endl;
XXH64_freeState(state);
return 0;
}
}
// Get final hash
XXH64_hash_t hash = XXH64_digest(state);
XXH64_freeState(state);
return hash;
}
uint64_t hash_directory_recursive(const std::string &path) {
// Create hash state
XXH64_state_t* const state = XXH64_createState();
if (state == nullptr) {
std::cerr << "Failed to create hash state" << std::endl;
return 0;
}
// Initialize state with seed 0
XXH64_hash_t const seed = 0; /* or any other value */
if (XXH64_reset(state, seed) == XXH_ERROR) {
std::cerr << "Failed to reset hash state" << std::endl;
XXH64_freeState(state);
return 0;
}
try {
// Iterate through all files in directory recursively
for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) {
if (entry.is_regular_file()) {
// Get file hash
XXH64_hash_t file_hash = hash_file(entry.path().string());
XXH64_update(state, &file_hash, sizeof(file_hash));
}
}
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Filesystem error: " << e.what() << std::endl;
XXH64_freeState(state);
return 0;
}
// Get final hash
XXH64_hash_t hash = XXH64_digest(state);
XXH64_freeState(state);
return hash;
}
uint64_t hash_path(const std::string &path) {
if (!std::filesystem::exists(path)) {
std::cerr << "Path does not exist: " << path << std::endl;
return 0;
}
if (std::filesystem::is_directory(path)) {
return hash_directory_recursive(path);
} else if (std::filesystem::is_regular_file(path)) {
return hash_file(path);
} else {
std::cerr << "Path is neither a file nor a directory: " << path << std::endl;
return 0;
}
}
void hash_demo(const std::string & path)
{
std::cout << "Hashing path: " << path << std::endl;
auto start = std::chrono::high_resolution_clock::now();
XXH64_hash_t hash = hash_path(path);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Hash: " << hash << " (took " << duration.count() << "ms)" << std::endl;
}
int hash_demo_raw(const std::string & path)
{
if (!std::filesystem::exists(path)) {
std::cout << 0 <<std::endl; return 1;
}
XXH64_hash_t hash = hash_path(path);
std::cout << hash << std::endl;
return 0;
}
} // namespace dropshell

22
source/src/utils/hash.hpp Normal file
View File

@ -0,0 +1,22 @@
#ifndef HASH_HPP
#define HASH_HPP
#include <string>
#include <cstdint>
namespace dropshell {
uint64_t hash_file(const std::string &path);
uint64_t hash_directory_recursive(const std::string &path);
uint64_t hash_path(const std::string &path);
void hash_demo(const std::string & path);
int hash_demo_raw(const std::string & path);
} // namespace dropshell
#endif

25578
source/src/utils/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_DIAGNOSTIC_POSITIONS
#define JSON_DIAGNOSTIC_POSITIONS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_DIAGNOSTIC_POSITIONS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View File

@ -0,0 +1,292 @@
#include "tableprint.hpp"
#include <iomanip>
#include <algorithm>
#include <locale>
#include <cwchar>
#include <string>
#include <sstream>
#include <codecvt>
#include <iostream>
#include <map>
enum kTextColors {
kTextColor_Default,
kTextColor_Red,
kTextColor_Green,
kTextColor_Yellow,
kTextColor_Blue,
kTextColor_Magenta,
kTextColor_Cyan,
kTextColor_White,
kTextColor_DarkGrey,
kTextColor_DarkYellow,
kTextColor_LightGrey
};
struct coloredText {
std::string text;
kTextColors color;
};
const std::map<std::string, coloredText> kReplacements = {
{":tick:", {"+", kTextColor_Green}},
{":cross:", {"x", kTextColor_Red}},
{":warning:", {"!", kTextColor_Yellow}},
{":info:", {"i", kTextColor_Blue}},
{":check:", {"+", kTextColor_Green}},
{":x:", {"x", kTextColor_Red}},
{":error:", {"!", kTextColor_Red}},
{":question:", {"?", kTextColor_DarkGrey}},
{":greytick:", {"+", kTextColor_LightGrey}},
{":greycross:", {"x", kTextColor_LightGrey}}
};
// Helper function to get ANSI color code
std::string get_color_code(kTextColors color) {
switch (color) {
case kTextColor_Red: return "\033[1;31m";
case kTextColor_Green: return "\033[1;32m";
case kTextColor_Yellow: return "\033[1;33m";
case kTextColor_Blue: return "\033[1;34m";
case kTextColor_Magenta: return "\033[1;35m";
case kTextColor_Cyan: return "\033[1;36m";
case kTextColor_White: return "\033[1;37m";
case kTextColor_DarkGrey: return "\033[90m";
case kTextColor_DarkYellow: return "\033[38;5;142m";
case kTextColor_LightGrey: return "\033[38;5;250m";
default: return "\033[0m";
}
}
int get_codepoints(const std::string& str) {
int num_code_points = 0;
for (char byte: str) {
if((byte & 0xC0) != 0x80) {
num_code_points++;
}
}
return num_code_points;
}
int get_visible_length(const std::string& str) {
int length = get_codepoints(str);
size_t pos = 0;
while (pos < str.length()) {
for (const auto& [key, value] : kReplacements) {
if (str.compare(pos, key.length(), key) == 0) {
length = length - get_codepoints(key) + get_codepoints(value.text);
pos += key.length();
break;
}
}
pos++;
}
return length;
}
std::string process_cell(std::string str,std::string rowcolor) {
std::string result;
size_t pos = 0;
while (pos < str.length()) {
bool found_replacement = false;
for (const auto& [key, value] : kReplacements) {
if (str.compare(pos, key.length(), key) == 0) {
found_replacement = true;
result += get_color_code(value.color) + value.text + rowcolor;
pos += key.length();
break;
}
}
if (!found_replacement) {
result += str[pos];
pos++;
}
}
return result;
}
std::string width_print_centered(std::string str,int width, std::string rowcolor) {
size_t padding = (width - get_visible_length(str));
size_t lpad = padding/2;
size_t rpad = padding - lpad;
std::ostringstream oss;
oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor) <<
std::string(rpad, ' ') << "\033[0m";
// std::cout << "str = "<<str <<std::endl;
// std::cout << "width = "<<width <<std::endl;
// std::cout << "padding = "<<padding <<std::endl;
// std::cout << "get_visible_length(str) = "<<get_visible_length(str) <<std::endl;
// std::cout << "get_codepoints(str) = "<<get_codepoints(str) <<std::endl;
// std::cout << "oss.str() = ["<<oss.str() <<"]"<<std::endl;
return oss.str();
}
std::string width_print_left(std::string str,int width, std::string rowcolor) {
size_t padding = (width - get_visible_length(str));
size_t lpad = (padding>1 ? 1 : 0);
size_t rpad = padding - lpad;
std::ostringstream oss;
oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor)<< std::string(rpad, ' ') << "\033[0m";
return oss.str();
}
tableprint::tableprint(const std::string title, bool compact) : title(title), mCompact(compact) {
// Set locale for wide character support
std::setlocale(LC_ALL, "");
}
tableprint::~tableprint() {}
void tableprint::set_title(const std::string title) {
this->title = title;
}
void tableprint::add_row(const std::vector<std::string>& row) {
std::vector<std::string> trimmed_row;
for (const auto& cell : row) {
// Trim whitespace from start and end
auto start = cell.find_first_not_of(" \t");
auto end = cell.find_last_not_of(" \t");
if (start == std::string::npos) {
trimmed_row.push_back("");
} else {
trimmed_row.push_back(cell.substr(start, end - start + 1));
}
}
rows.push_back(trimmed_row);
}
void tableprint::print() {
if (rows.empty()) return;
// Calculate column widths based on header text
std::vector<size_t> col_widths(rows[0].size(), 0);
for (size_t i = 0; i < rows[0].size(); ++i) {
col_widths[i] = rows[0][i].length();
}
// Adjust widths for any cells with replacements
for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) {
const auto& row = rows[row_idx];
for (size_t i = 0; i < row.size(); ++i) {
int l = get_visible_length(row[i]);
if (l > col_widths[i])
col_widths[i] = l;
}
}
// Debug output
// std::cerr << "Column widths: ";
// for (size_t width : col_widths) {
// std::cerr << width << " ";
// }
// std::cerr << std::endl;
// Calculate total table width
size_t total_width = 0;
for (size_t width : col_widths) {
total_width += width + 2; // +2 for padding
}
total_width += col_widths.size() - 1; // Add space for vertical borders
// Print title if it exists
if (!title.empty()) {
std::cout << "\033[90m"; // Dark grey color for borders
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "-";
}
std::cout << "+" << std::endl;
std::cout << "|"; // White color for title
size_t title_width = 0;
for (size_t width : col_widths) {
title_width += width + 2; // +2 for padding
}
title_width += col_widths.size() - 1; // Add space for vertical borders
std::cout << width_print_centered(title,title_width,"\033[1;37m");
std::cout << "\033[90m|" << std::endl;
// Use └─┴─┘ for bottom of title box to connect with table
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "-";
}
std::cout << "+" << std::endl;
} else {
// Print top border if no title
std::cout << "\033[90m"; // Dark grey color for borders
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "+";
}
std::cout << "+" << std::endl;
}
// Print header
std::cout << "|";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << width_print_centered(rows[0][i],col_widths[i]+2,"\033[1;36m");
if (i < rows[0].size() - 1) {
std::cout << "\033[90m|\033[1;36m"; // Border color then back to cyan
} else {
std::cout << "\033[90m|"; // Just border color for last column
}
}
std::cout << std::endl;
// Print header separator
if (!mCompact) {
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
}
std::cout << "+" << std::endl;
}
// Print rows
for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) {
const auto& row = rows[row_idx];
std::cout << "|";
for (size_t i = 0; i < row.size(); ++i) {
// Set the appropriate color for the row
std::string rowcolor = (row_idx % 2 == 1) ? "\033[38;5;142m" : "\033[38;5;250m";
std::cout << width_print_left(row[i],col_widths[i]+2,rowcolor);
std::cout << "\033[90m" << "|";
}
std::cout << std::endl;
// Print row separator if not the last row
if (row_idx < rows.size() - 1 && !mCompact) {
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
}
std::cout << "+" << std::endl;
}
}
// Print bottom border
std::cout << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
}
std::cout << "+" << "\033[0m" << std::endl; // Reset color
}

View File

@ -0,0 +1,25 @@
# ifndef TABLEPRINT_HPP
# define TABLEPRINT_HPP
#include <vector>
#include <string>
#include <iostream>
// tableprint is a class that prints a table of strings.
// formatted to look nice with colored headings and rows.
// converts :tick: to a green tick and :cross: to a red cross.
// assumes the first row is the header.
class tableprint {
public:
tableprint(const std::string title = "", bool compact = false);
~tableprint();
void add_row(const std::vector<std::string>& row);
void print();
void set_title(const std::string title);
private:
std::vector<std::vector<std::string>> rows;
std::string title;
bool mCompact;
};
# endif

374
source/src/utils/utils.cpp Normal file
View File

@ -0,0 +1,374 @@
#include "utils.hpp"
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <filesystem>
#include <regex>
#include <random>
namespace dropshell {
void maketitle(const std::string& title) {
std::cout << std::string(title.length() + 4, '-') << std::endl;
std::cout << "| " << title << " |" << std::endl;
std::cout << std::string(title.length() + 4, '-') << std::endl;
}
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line) {
std::ifstream input_file(file_path);
std::vector<std::string> file_lines;
std::string line;
if (!input_file.is_open()) {
std::cerr << "Error: Unable to open file: " << file_path << std::endl;
return false;
}
while (std::getline(input_file, line)) {
if (line.find(search_string) != std::string::npos)
file_lines.push_back(replacement_line);
else
file_lines.push_back(line);
}
input_file.close();
std::ofstream output_file(file_path);
if (!output_file.is_open())
{
std::cerr << "Error: Unable to open file: " << file_path << std::endl;
return false;
}
for (const auto& modified_line : file_lines)
output_file << modified_line << "\n";
output_file.close();
return true;
}
std::string trim(std::string str) {
// Trim leading whitespace
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
// Trim trailing whitespace
str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), str.end());
return str;
}
std::string dequote(std::string str)
{
if (str.length() < 2)
return str;
if (str.front() == '"' && str.back() == '"') {
return str.substr(1, str.length() - 2);
}
return str;
}
std::string quote(std::string str)
{
return "\""+str+"\"";
}
std::string halfquote(std::string str)
{
return "'" + str + "'";
}
std::string escapequotes(std::string str)
{
return std::regex_replace(str, std::regex("\""), "\\\"");
}
std::string multi2string(std::vector<std::string> values)
{
std::string result;
for (const auto& value : values) {
// remove any " contained in the string value, if present
result += dequote(trim(value)) + ",";
}
if (!result.empty())
result.pop_back(); // Remove the last comma
return result;
}
std::vector<std::string> string2multi(std::string values)
{
std::vector<std::string> result;
values = dequote(trim(values));
// Return values separated by commas, but ignore commas within quotes
bool inside_quotes = false;
std::string current_item;
for (char c : values) {
if (c == '"') {
inside_quotes = !inside_quotes;
} else if (c == ',' && !inside_quotes) {
if (!current_item.empty()) {
std::string final = dequote(trim(current_item));
if (!final.empty())
result.push_back(final);
current_item.clear();
}
} else {
current_item += c;
}
}
// Add the last item if not empty
if (!current_item.empty()) {
std::string final = dequote(trim(current_item));
if (!final.empty())
result.push_back(final);
}
return result;
}
int str2int(const std::string &str)
{
try {
return std::stoi(str);
} catch (const std::exception& e) {
std::cerr << "Error: Invalid integer string: [" << str << "]" << std::endl;
return 0;
}
}
void recursive_copy(const std::string & source, const std::string & destination) {
try {
if (std::filesystem::is_directory(source)) {
if (!std::filesystem::exists(destination)) {
std::filesystem::create_directory(destination);
}
for (const auto& entry : std::filesystem::directory_iterator(source)) {
recursive_copy(entry.path(), destination / entry.path().filename());
}
} else if (std::filesystem::is_regular_file(source)) {
std::filesystem::copy(source, destination, std::filesystem::copy_options::overwrite_existing);
}
} catch (const std::filesystem::filesystem_error& ex) {
std::cerr << "Error copying " << source << " to " << destination << ": " << ex.what() << std::endl;
}
}
void ensure_directories_exist(std::vector<std::string> directories)
{
for (const auto& directory : directories) {
if (!std::filesystem::exists(directory)) {
std::filesystem::create_directories(directory);
}
}
}
//https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/
void constructLps(const std::string &pat, std::vector<int> &lps) {
// len stores the length of longest prefix which
// is also a suffix for the previous index
int len = 0;
// lps[0] is always 0
lps[0] = 0;
int i = 1;
while (i < pat.length()) {
// If characters match, increment the size of lps
if (pat[i] == pat[len]) {
len++;
lps[i] = len;
i++;
}
// If there is a mismatch
else {
if (len != 0) {
// Update len to the previous lps value
// to avoid reduntant comparisons
len = lps[len - 1];
}
else {
// If no matching prefix found, set lps[i] to 0
lps[i] = 0;
i++;
}
}
}
}
std::vector<int> search(const std::string &pat, const std::string &txt) {
int n = txt.length();
int m = pat.length();
std::vector<int> lps(m);
std::vector<int> res;
constructLps(pat, lps);
// Pointers i and j, for traversing
// the text and pattern
int i = 0;
int j = 0;
while (i < n) {
// If characters match, move both pointers forward
if (txt[i] == pat[j]) {
i++;
j++;
// If the entire pattern is matched
// store the start index in result
if (j == m) {
res.push_back(i - j);
// Use LPS of previous index to
// skip unnecessary comparisons
j = lps[j - 1];
}
}
// If there is a mismatch
else {
// Use lps value of previous index
// to avoid redundant comparisons
if (j != 0)
j = lps[j - 1];
else
i++;
}
}
return res;
}
int count_substring(const std::string &substring, const std::string &text) {
std::vector<int> positions = search(substring, text);
return positions.size();
}
std::vector<std::string> split(const std::string& str, const std::string& delimiter) {
std::vector<std::string> tokens;
size_t start = 0;
size_t end = 0;
while ((end = str.find(delimiter, start)) != std::string::npos) {
tokens.push_back(str.substr(start, end - start));
start = end + delimiter.length();
}
// Add the last token
tokens.push_back(str.substr(start));
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 random_alphanumeric_string(int length)
{
static std::mt19937 generator(std::random_device{}());
static const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
std::uniform_int_distribution<> distribution(0, chars.size() - 1);
std::string random_string;
for (int i = 0; i < length; ++i) {
random_string += chars[distribution(generator)];
}
return random_string;
}
std::string requote(std::string str) {
return quote(trim(dequote(trim(str))));
}
int die(const std::string & msg) {
std::cerr << msg << std::endl;
return 1;
}
std::string safearg(const std::vector<std::string> & args, int index)
{
if (index<0 || index >= args.size()) return "";
return args[index];
}
std::string safearg(int argc, char *argv[], int index)
{
if (index<0 || index >= argc) return "";
return argv[index];
}
void print_left_aligned(const std::string & str, int width) {
std::cout << left_align(str, width);
}
void print_centered(const std::string & str, int width) {
std::cout << center_align(str, width);
}
void print_right_aligned(const std::string & str, int width) {
std::cout << right_align(str, width);
}
std::string left_align(const std::string & str, int width) {
if (static_cast<int>(str.size()) >= width)
return str;
return str + std::string(width - str.size(), ' ');
}
std::string right_align(const std::string & str, int width) {
if (static_cast<int>(str.size()) >= width)
return str;
return std::string(width - str.size(), ' ') + str;
}
std::string center_align(const std::string & str, int width) {
int pad = width - static_cast<int>(str.size());
if (pad <= 0)
return str;
int pad_left = pad / 2;
int pad_right = pad - pad_left;
return std::string(pad_left, ' ') + str + std::string(pad_right, ' ');
}
} // namespace dropshell

View File

@ -0,0 +1,56 @@
#pragma once
#include <string>
#include <vector>
namespace dropshell {
/**
* Prints a formatted title surrounded by a box of dashes.
*
* @param title The title string to display
*/
void maketitle(const std::string& title);
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line);
// utility functions
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 escapequotes(std::string str);
std::string multi2string(std::vector<std::string> values);
std::vector<std::string> string2multi(std::string values);
std::vector<std::string> split(const std::string& str, const std::string& delimiter);
int str2int(const std::string & str);
void recursive_copy(const std::string & source, const std::string & destination);
void ensure_directories_exist(std::vector<std::string> directories);
// KMP algorithm
std::vector<int> 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);
std::string random_alphanumeric_string(int length);
int die(const std::string & msg);
std::string safearg(int argc, char *argv[], int index);
std::string safearg(const std::vector<std::string> & args, int index);
void print_left_aligned(const std::string & str, int width);
void print_centered(const std::string & str, int width);
void print_right_aligned(const std::string & str, int width);
std::string left_align(const std::string & str, int width);
std::string right_align(const std::string & str, int width);
std::string center_align(const std::string & str, int width);
} // namespace dropshell