From 60907e5e02c07eba5aa433be1e8c3ca783a47f7b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 24 May 2025 17:00:43 +1200 Subject: [PATCH] List tidy --- source/src/commands/backupdata.cpp | 2 +- source/src/commands/create-service.cpp | 2 +- source/src/commands/destroy.cpp | 2 +- source/src/commands/install.cpp | 8 +-- source/src/commands/list.cpp | 67 ++++++++++++++++--------- source/src/commands/restoredata.cpp | 4 +- source/src/commands/shared_commands.cpp | 16 +++--- source/src/commands/shared_commands.hpp | 20 ++++---- source/src/commands/ssh.cpp | 6 +-- source/src/commands/start.cpp | 2 +- source/src/commands/stop.cpp | 2 +- source/src/commands/uninstall.cpp | 2 +- source/src/servers.cpp | 48 +++++++++--------- source/src/servers.hpp | 8 +-- source/src/service_runner.cpp | 4 +- source/src/utils/directories.cpp | 2 +- source/src/utils/tableprint.cpp | 21 ++++++++ source/src/utils/tableprint.hpp | 1 + 18 files changed, 128 insertions(+), 89 deletions(-) diff --git a/source/src/commands/backupdata.cpp b/source/src/commands/backupdata.cpp index 970eb90..6b62cd6 100644 --- a/source/src/commands/backupdata.cpp +++ b/source/src/commands/backupdata.cpp @@ -53,7 +53,7 @@ namespace dropshell namespace shared_commands { - bool backupdata_service(const server_config &server_env, const std::string &service) + bool backupdata_service(const ServerConfig &server_env, const std::string &service) { ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name()); std::string server = server_env.get_server_name(); diff --git a/source/src/commands/create-service.cpp b/source/src/commands/create-service.cpp index 3a57425..5877cba 100644 --- a/source/src/commands/create-service.cpp +++ b/source/src/commands/create-service.cpp @@ -103,7 +103,7 @@ namespace dropshell if (server_name.empty() || template_name.empty() || service_name.empty()) return false; - server_config server_info(server_name); + ServerConfig server_info(server_name); if (!server_info.is_valid()) { error << "Server " << server_name << " is not valid" << std::endl; diff --git a/source/src/commands/destroy.cpp b/source/src/commands/destroy.cpp index 7406fa2..6d2bc01 100644 --- a/source/src/commands/destroy.cpp +++ b/source/src/commands/destroy.cpp @@ -52,7 +52,7 @@ namespace dropshell bool nuke_service(const std::string &server, const std::string &service) { - server_config server_env(server); + ServerConfig server_env(server); // step 1 - nuke on remote server. if (server_env.is_valid()) diff --git a/source/src/commands/install.cpp b/source/src/commands/install.cpp index ae3de15..e847875 100644 --- a/source/src/commands/install.cpp +++ b/source/src/commands/install.cpp @@ -63,7 +63,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // install service over ssh : SHARED COMMAND // ------------------------------------------------------------------------------------------------ - bool install_service(const server_config &server_env, const std::string &service) + bool install_service(const ServerConfig &server_env, const std::string &service) { std::string server = server_env.get_server_name(); LocalServiceInfo service_info = get_service_info(server_env.get_server_name(), service); @@ -271,7 +271,7 @@ namespace dropshell return 0; } - int install_server(const server_config &server) + int install_server(const ServerConfig &server) { // install the dropshell agent on the given server. maketitle("Installing dropshell agent on " + server.get_server_name(), sColour::INFO); @@ -321,7 +321,7 @@ namespace dropshell return rval; // install the dropshell agent on all servers. - std::vector servers = get_configured_servers(); + std::vector servers = get_configured_servers(); for (const auto &server : servers) { rval = install_server(server); @@ -364,7 +364,7 @@ namespace dropshell return 1; } - server_config server_env(server); + ServerConfig server_env(server); ASSERT(server_env.is_valid(), "Invalid server environment for " + server); if (safearg(ctx.args, 1) == "all") { diff --git a/source/src/commands/list.cpp b/source/src/commands/list.cpp index 6034dea..05b610f 100644 --- a/source/src/commands/list.cpp +++ b/source/src/commands/list.cpp @@ -83,44 +83,61 @@ void list_servers() { tableprint tp("All DropShell Servers"); tp.add_row({"Name", "Address", "User", "Health", "Ports"}); - info << "Checking "< tServiceStatusMap; + std::vector service_status_maps; + + typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair; + std::vector server_user_pairs; + for (const auto& server : servers) + for (const auto& user : server.get_users()) + server_user_pairs.push_back({server, user}); + + // mutex for the tableprint + std::mutex tp_mutex; + + info << "Checking "< status = shared_commands::get_all_services_status(server.get_server_name(),user.user); + std::string serviceticks = ""; + std::string ports_used_str = ""; + std::set ports_used; - std::set ports_used; - std::string serviceticks = ""; - for (const auto& [service_name, service_status] : status) { - ports_used.insert(service_status.ports.begin(), service_status.ports.end()); - serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; - } - std::string ports_used_str = ""; - for (const auto& port : ports_used) - ports_used_str += std::to_string(port) + " "; + std::map status = shared_commands::get_all_services_status(sup.server.get_server_name(),sup.user.user); - tp.add_row({(first ? server.get_server_name() : ""), (first ? server.get_SSH_HOST() : ""), user.user, serviceticks, ports_used_str}); - first = false; - } + for (const auto& [service_name, service_status] : status) { + ports_used.insert(service_status.ports.begin(), service_status.ports.end()); + serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; + } - ++checked; - // print out a tick character for each server checked. - info << checked << " ✓ " << std::flush; + for (const auto& port : ports_used) + ports_used_str += std::to_string(port) + " "; + // critical section + { + std::lock_guard lock(tp_mutex); + tp.add_row({sup.server.get_server_name(), sup.server.get_SSH_HOST(), sup.user.user, serviceticks, ports_used_str}); + + ++checked; + // print out a tick character for each server checked. + info << checked << " ✓ " << std::flush; + } }); task->wait(); info << std::endl << std::endl; + + tp.sort({0,2}); + tp.print(); } @@ -128,7 +145,7 @@ void list_servers() { void show_server_details(const std::string& server_name) { - server_config env(server_name); + ServerConfig env(server_name); if (!env.is_valid()) { error << "Error: Invalid server environment file: " << server_name << std::endl; return; diff --git a/source/src/commands/restoredata.cpp b/source/src/commands/restoredata.cpp index 4847c4f..2d1ab85 100644 --- a/source/src/commands/restoredata.cpp +++ b/source/src/commands/restoredata.cpp @@ -92,7 +92,7 @@ namespace dropshell std::string service = ctx.args[1]; std::string backup_arg = ctx.args[2]; - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; @@ -189,7 +189,7 @@ namespace dropshell { // installing fresh service info << "4) Install of fresh service..." << std::endl; - server_config server_env(server); + ServerConfig server_env(server); if (!shared_commands::install_service(server_env, service)) return 1; } diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index fcf6a72..c3df40c 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -21,7 +21,7 @@ namespace dropshell if (ctx.args.size() == 0) { // just the command, no args yet. // list servers - std::vector servers = get_configured_servers(); + std::vector servers = get_configured_servers(); for (const auto &server : servers) { rawout << server.get_server_name() << std::endl; @@ -54,7 +54,7 @@ namespace dropshell bool rsync_tree_to_remote( const std::string &local_path, const std::string &remote_path, - const server_config &server_env, + const ServerConfig &server_env, bool silent, std::string user) { @@ -85,7 +85,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // cRemoteTempFolder : SHARED CLASS // ------------------------------------------------------------------------------------------------ - cRemoteTempFolder::cRemoteTempFolder(const server_config &server_env, std::string user) : + cRemoteTempFolder::cRemoteTempFolder(const ServerConfig &server_env, std::string user) : mServerEnv(server_env), mUser(user) { std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10); @@ -110,7 +110,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // get_all_services_status : SHARED COMMAND // ------------------------------------------------------------------------------------------------ - std::map get_all_services_status(const server_config & server_env) + std::map get_all_services_status(const ServerConfig & server_env) { std::map status; for (const auto& user : server_env.get_users()) { @@ -119,7 +119,7 @@ namespace dropshell return status; } - std::map get_all_services_status(const server_config & server_env, std::string user) + std::map get_all_services_status(const ServerConfig & server_env, std::string user) { std::map status; std::string server_name = server_env.get_server_name(); @@ -216,7 +216,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ HealthStatus is_healthy(const std::string &server, const std::string &service) { - server_config env(server); + ServerConfig env(server); if (!env.is_valid()) { error << "Server service not initialized" << std::endl; @@ -309,7 +309,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // scp_file_to_remote : SHARED COMMAND // ------------------------------------------------------------------------------------------------ - bool scp_file_to_remote(const server_config &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user) + bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user) { if (!server_env.is_valid()) { @@ -324,7 +324,7 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // scp_file_from_remote : SHARED COMMAND // ------------------------------------------------------------------------------------------------ - bool scp_file_from_remote(const server_config &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user) + bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user) { if (!server_env.is_valid()) { diff --git a/source/src/commands/shared_commands.hpp b/source/src/commands/shared_commands.hpp index dbfd637..b0a1b66 100644 --- a/source/src/commands/shared_commands.hpp +++ b/source/src/commands/shared_commands.hpp @@ -31,26 +31,26 @@ namespace dropshell class cRemoteTempFolder { public: - cRemoteTempFolder(const server_config &server_env, std::string user); // create a temp folder on the remote server + cRemoteTempFolder(const ServerConfig &server_env, std::string user); // create a temp folder on the remote server ~cRemoteTempFolder(); // delete the temp folder on the remote server std::string path() const; // get the path to the temp folder on the remote server private: std::string mPath; - const server_config &mServerEnv; + const ServerConfig &mServerEnv; std::string mUser; }; bool rsync_tree_to_remote( const std::string &local_path, const std::string &remote_path, - const server_config &server_env, + const ServerConfig &server_env, bool silent, std::string user); std::string get_arch(); - std::map get_all_services_status(const server_config & server_env); - std::map get_all_services_status(const server_config & server_env, std::string user); + std::map get_all_services_status(const ServerConfig & server_env); + std::map get_all_services_status(const ServerConfig & server_env, std::string user); std::string healthtick(const std::string &server, const std::string &service); std::string HealthStatus2String(HealthStatus status); @@ -81,20 +81,20 @@ namespace dropshell std::string mDatetime; }; - bool scp_file_to_remote(const server_config &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user); - bool scp_file_from_remote(const server_config &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user); + bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user); + bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user); // defined in backupdata.cpp, used by restoredata.cpp. - bool backupdata_service(const server_config &server_env, const std::string& service); + bool backupdata_service(const ServerConfig &server_env, const std::string& service); // defined in uninstall.cpp - bool uninstall_service(const server_config &server_env, const std::string &service); + bool uninstall_service(const ServerConfig &server_env, const std::string &service); // defined in nuke.cpp bool nuke_service(const std::string &server, const std::string &service); // defined in install.cpp - bool install_service(const server_config &server_env, const std::string &service); + bool install_service(const ServerConfig &server_env, const std::string &service); // defined in create-service.cpp bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override=""); diff --git a/source/src/commands/ssh.cpp b/source/src/commands/ssh.cpp index 27f3a59..97701a0 100644 --- a/source/src/commands/ssh.cpp +++ b/source/src/commands/ssh.cpp @@ -42,7 +42,7 @@ namespace dropshell bool ssh_into_server(const std::string &server, std::string user) { - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; @@ -54,7 +54,7 @@ namespace dropshell bool ssh_into_service(const std::string &server, const std::string &service) { - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; @@ -109,7 +109,7 @@ namespace dropshell server = arg1; // get the first user from the server.env file, and ssh in as that user. - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; diff --git a/source/src/commands/start.cpp b/source/src/commands/start.cpp index 44853a6..2bc41bc 100644 --- a/source/src/commands/start.cpp +++ b/source/src/commands/start.cpp @@ -44,7 +44,7 @@ namespace dropshell bool start_service(const std::string &server, const std::string &service) { - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; diff --git a/source/src/commands/stop.cpp b/source/src/commands/stop.cpp index f8aee6c..039a932 100644 --- a/source/src/commands/stop.cpp +++ b/source/src/commands/stop.cpp @@ -44,7 +44,7 @@ namespace dropshell bool stop_service(const std::string &server, const std::string &service) { - server_config server_env(server); + ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; diff --git a/source/src/commands/uninstall.cpp b/source/src/commands/uninstall.cpp index 822e11c..2f2f2de 100644 --- a/source/src/commands/uninstall.cpp +++ b/source/src/commands/uninstall.cpp @@ -42,7 +42,7 @@ namespace dropshell } uninstall_command_register; namespace shared_commands { - bool uninstall_service(const server_config & server_env, const std::string &service) + bool uninstall_service(const ServerConfig & server_env, const std::string &service) { ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name()); std::string server = server_env.get_server_name(); diff --git a/source/src/servers.cpp b/source/src/servers.cpp index 07a5450..53907cd 100644 --- a/source/src/servers.cpp +++ b/source/src/servers.cpp @@ -23,7 +23,7 @@ namespace dropshell { - server_config::server_config(const std::string &server_name) : mValid(false), mServerName(server_name) + ServerConfig::ServerConfig(const std::string &server_name) : mValid(false), mServerName(server_name) { if (server_name.empty()) return; @@ -110,22 +110,22 @@ namespace dropshell } } - std::string server_config::get_SSH_HOST() const + std::string ServerConfig::get_SSH_HOST() const { return get_variable("SSH_HOST"); } - std::string server_config::get_SSH_PORT() const + std::string ServerConfig::get_SSH_PORT() const { return get_variable("SSH_PORT"); } - std::vector server_config::get_users() const + std::vector ServerConfig::get_users() const { return mUsers; } - std::string server_config::get_user_dir(const std::string &user) const + std::string ServerConfig::get_user_dir(const std::string &user) const { for (const auto &u : mUsers) { @@ -137,12 +137,12 @@ namespace dropshell return ""; } - std::string server_config::get_server_name() const + std::string ServerConfig::get_server_name() const { return mServerName; } - std::string server_config::get_user_for_service(const std::string &service) const + std::string ServerConfig::get_user_for_service(const std::string &service) const { return dropshell::get_user_for_service(mServerName, service); } @@ -158,7 +158,7 @@ namespace dropshell return ""; } - sSSHInfo server_config::get_SSH_INFO(std::string user) const + sSSHInfo ServerConfig::get_SSH_INFO(std::string user) const { ASSERT(!user.empty(), "User is empty, cannot get SSH info."); // Find user in mUsers vector @@ -169,24 +169,24 @@ namespace dropshell return sSSHInfo(get_SSH_HOST(), it->user, get_SSH_PORT(), get_server_name(), it->dir); } - bool server_config::hasRootUser() const + bool ServerConfig::hasRootUser() const { auto it = std::find_if(mUsers.begin(), mUsers.end(),[](const UserConfig &u) { return u.user == "root"; }); return it != mUsers.end(); } - bool server_config::hasDocker() const + bool ServerConfig::hasDocker() const { return get_variable("HAS_DOCKER") == "true"; } - bool server_config::hasRootDocker() const + bool ServerConfig::hasRootDocker() const { return get_variable("DOCKER_ROOTLESS") == "false"; } - bool server_config::hasUser(const std::string &user) const + bool ServerConfig::hasUser(const std::string &user) const { auto it = std::find_if(mUsers.begin(), mUsers.end(), [&user](const UserConfig &u) @@ -194,19 +194,19 @@ namespace dropshell return it != mUsers.end(); } - bool server_config::check_remote_dir_exists(const std::string &dir_path, std::string user) const + bool ServerConfig::check_remote_dir_exists(const std::string &dir_path, std::string user) const { sCommand scommand("", "test -d " + quote(dir_path), {}); return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); } - bool server_config::check_remote_file_exists(const std::string &file_path, std::string user) const + bool ServerConfig::check_remote_file_exists(const std::string &file_path, std::string user) const { sCommand scommand("", "test -f " + quote(file_path), {}); return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); } - bool server_config::check_remote_items_exist(const std::vector &file_paths, std::string user) const + bool ServerConfig::check_remote_items_exist(const std::vector &file_paths, std::string user) const { // convert file_paths to a single string, separated by spaces std::string file_paths_str; @@ -229,7 +229,7 @@ namespace dropshell return true; } - bool server_config::remove_remote_dir( + bool ServerConfig::remove_remote_dir( const std::string &dir_path, bool silent, std::string user) const { std::filesystem::path path(dir_path); @@ -258,7 +258,7 @@ namespace dropshell return execute_ssh_command(sshinfo, scommand, mode); } - bool server_config::run_remote_template_command( + bool ServerConfig::run_remote_template_command( const std::string &service_name, const std::string &command, std::vector args, @@ -280,7 +280,7 @@ namespace dropshell return execute_ssh_command(get_SSH_INFO(user), scommand.value(), mode); } - bool server_config::run_remote_template_command_and_capture_output( + bool ServerConfig::run_remote_template_command_and_capture_output( const std::string &service_name, const std::string &command, std::vector args, @@ -300,7 +300,7 @@ namespace dropshell return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output); } - std::string server_config::get_variable(const std::string &name) const + std::string ServerConfig::get_variable(const std::string &name) const { auto it = mVariables.find(name); if (it == mVariables.end()) @@ -310,7 +310,7 @@ namespace dropshell return it->second; } - std::optional server_config::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector args, const bool silent) const + std::optional ServerConfig::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector args, const bool silent) const { if (command.empty()) return std::nullopt; @@ -346,9 +346,9 @@ namespace dropshell return sc; } - std::vector get_configured_servers() + std::vector get_configured_servers() { - std::vector servers; + std::vector servers; std::vector lsdp = gConfig().get_local_server_definition_paths(); if (lsdp.empty()) @@ -367,7 +367,7 @@ namespace dropshell if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_') continue; - server_config env(server_name); + ServerConfig env(server_name); if (!env.is_valid()) { std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl; @@ -430,7 +430,7 @@ namespace dropshell void get_all_used_commands(std::set &commands) { - std::vector servers = get_configured_servers(); + std::vector servers = get_configured_servers(); for (const auto &server : servers) { auto services = get_server_services_info(server.get_server_name()); diff --git a/source/src/servers.hpp b/source/src/servers.hpp index cd207c3..8f9ce35 100644 --- a/source/src/servers.hpp +++ b/source/src/servers.hpp @@ -32,10 +32,10 @@ namespace dropshell // SSH_PORT // the following replacements are made in the values: // ${USER} -> the username of the user running dropshell - class server_config + class ServerConfig { public: - server_config(const std::string &server_name); + ServerConfig(const std::string &server_name); bool is_valid() const { return mValid; } @@ -85,11 +85,11 @@ namespace dropshell std::map mVariables; std::vector mUsers; bool mValid; - }; // class server_config + }; // class ServerConfig - std::vector get_configured_servers(); + std::vector get_configured_servers(); std::string get_user_for_service(const std::string &server, const std::string &service); diff --git a/source/src/service_runner.cpp b/source/src/service_runner.cpp index a919bab..af44dcf 100644 --- a/source/src/service_runner.cpp +++ b/source/src/service_runner.cpp @@ -73,7 +73,7 @@ // private: // std::string mServer; -// server_config mServerEnv; +// ServerConfig mServerEnv; // LocalServiceInfo mServiceInfo; // std::string mService; // bool mValid; @@ -240,7 +240,7 @@ // return false; // } -// server_config env(server_name); +// ServerConfig env(server_name); // if (!env.is_valid()) { // std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; // return false; diff --git a/source/src/utils/directories.cpp b/source/src/utils/directories.cpp index 71a0e9e..441ad67 100644 --- a/source/src/utils/directories.cpp +++ b/source/src/utils/directories.cpp @@ -173,7 +173,7 @@ namespace localpath { std::string remotepath::DROPSHELL_DIR() const { - return server_config(mServer_name).get_user_dir(mUser); + return ServerConfig(mServer_name).get_user_dir(mUser); } std::string remotepath::services() const diff --git a/source/src/utils/tableprint.cpp b/source/src/utils/tableprint.cpp index 380e3f9..2d8ad19 100644 --- a/source/src/utils/tableprint.cpp +++ b/source/src/utils/tableprint.cpp @@ -147,6 +147,27 @@ void tableprint::set_title(const std::string title) { this->title = title; } +// gives the columns to sort by, starting at 0. +void tableprint::sort(std::vector sort_columns) +{ + // Skip header row and sort remaining rows + if (rows.size() <= 1) return; // Only header or empty table + + // Create a custom comparator that compares rows based on the specified columns + auto comparator = [this, &sort_columns](const std::vector& a, const std::vector& b) { + for (int col : sort_columns) { + if (col >= 0 && col < a.size() && col < b.size()) { + int cmp = a[col].compare(b[col]); + if (cmp != 0) return cmp < 0; + } + } + return false; // Equal rows maintain original order + }; + + // Sort rows starting from index 1 (after header) + std::sort(rows.begin() + 1, rows.end(), comparator); +} + void tableprint::add_row(const std::vector& row) { std::vector trimmed_row; for (const auto& cell : row) { diff --git a/source/src/utils/tableprint.hpp b/source/src/utils/tableprint.hpp index 2fc9ec8..d0208f8 100644 --- a/source/src/utils/tableprint.hpp +++ b/source/src/utils/tableprint.hpp @@ -16,6 +16,7 @@ class tableprint { void add_row(const std::vector& row); void print(); void set_title(const std::string title); + void sort(std::vector sort_columns); private: std::vector> rows; std::string title;