commit f58e278637a885b0291cdc17392b28891e62c593 Author: King_DuckZ Date: Thu Apr 9 02:17:32 2020 +0200 First import Emulates gcc-config -l so far. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5248c6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +compile_commands.json +tags +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4251c0f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subprojects/cxxopts"] + path = subprojects/cxxopts + url = https://github.com/jarro2783/cxxopts.git diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..dcc4e56 --- /dev/null +++ b/meson.build @@ -0,0 +1,14 @@ +project('user-gcc', 'cpp', + version: '0.1.0', + meson_version: '>=0.50.0', + default_options: ['buildtype=debug', 'cpp_std=gnu++17'] +) + +cxxopts_incl = include_directories('subprojects/cxxopts/include', is_system: true) + +cpp = meson.get_compiler('cpp') +conf = configuration_data() +conf.set('PROJECT_NAME', meson.project_name()) +conf.set('PROJECT_VERSION', meson.project_version()) + +subdir('src') diff --git a/src/chost.cpp b/src/chost.cpp new file mode 100644 index 0000000..7d0dc58 --- /dev/null +++ b/src/chost.cpp @@ -0,0 +1,83 @@ +#include "chost.hpp" +#include "env.hpp" +#include "run_cmd.hpp" +#include "gentoofunctions.hpp" +#include "replace_bash_vars.hpp" +#include +#include + +namespace duck { + namespace { + std::string try_real_hard_to_find_CHOST() { + assert(false); + throw std::runtime_error("try_real_hard_to_find_CHOST() not implemented"); + //# + //# First we read make.conf + //# + + //local varname=${1:-CHOST} + //local conf=${EROOT}/etc/portage/make.conf + //if [[ ! -e ${conf} && -e ${EROOT}/etc/make.conf ]] ; then + // conf=${EROOT}/etc/make.conf + //fi + //local ret=$(source "${conf}" 2>/dev/null ; echo ${!varname}) + //if [[ -z ${ret} ]] ; then + // # newer portage supports spaces between the var and = + // # CHOST = "this-is-retarded" + // ret=$(eval $( + // ${SED} -n \ + // -e 's:[[:space:]]::g' \ + // -e "/^${varname}=/p" \ + // "${conf}" + // ) ; echo ${!varname} + // ) + //fi + + //if [[ -n ${ret} ]] ; then + // echo ${ret} + // return 0 + //fi + + //# + //# Then we try /etc/env.d/gcc/config-${CTARGET} + //# + //if [[ -s ${EROOT}/etc/env.d/gcc/config-${CTARGET} ]] ; then + // ret=$(split_gcc_ver $(show_var CURRENT "${EROOT}"/etc/env.d/gcc/config-${CTARGET})) + // echo ${ret% *} + //fi + } + } //unnamed namespace + + std::string chost() { + using std::string; + + { + // If it's set in the env, trust the setting. If it's wrong, + // then that's the caller's problem. + auto ret = env("CHOST"); + if (ret and not ret->empty()) + return string(*ret); + } + + const int python_ret = run_cmd_retcode("python -V &>/dev/null"); + std::string chost_str; + + if (not python_ret) { + chost_str = run_cmd("portageq", "envvar", "CHOST").out; + } + else { + ewarn("Python seems to be broken, attempting to locate CHOST ourselves ..."); + chost_str = try_real_hard_to_find_CHOST(); + } + + if (chost_str.empty()) { + eerror(replace_bash_vars("${argv0}: Could not get portage CHOST!")); + eerror(replace_bash_vars("${argv0}: You should verify that CHOST is set in one of these places:")); + eerror(replace_bash_vars("${argv0}: - ${EROOT}/etc/portage/make.conf")); + eerror(replace_bash_vars("${argv0}: - active environment")); + throw std::runtime_error(replace_bash_vars("${argv0}: Could not get portage CHOST!")); + } + + return chost_str; + } +} //namespace duck diff --git a/src/chost.hpp b/src/chost.hpp new file mode 100644 index 0000000..b01de60 --- /dev/null +++ b/src/chost.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace duck { + std::string chost(); +} //namespace duck diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..7dff64c --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,4 @@ +#pragma once + +#define PROJECT_NAME "@PROJECT_NAME@" +#define PROJECT_VERSION "@PROJECT_VERSION@" diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 0000000..2a14775 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,76 @@ +#include "env.hpp" +#include +#include +#include + +#if defined(__GNU_LIBRARY__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) || __GLIBC__ > 2) +# define HasSecureGetenv +#endif + +namespace duck { +namespace { +const char* raw_fetch_env (const char* name) { + assert(name); + if (not name) + return nullptr; + +#if defined(HasSecureGetenv) + const char* const val_ptr = secure_getenv(name); +#else + const char* const val_ptr = getenv(name); +#endif + + return val_ptr; +} + +} //unnamed namespace + +std::string_view env (const char* name, std::string_view def) noexcept { + const auto ret = raw_fetch_env(name); + return (ret ? std::string_view(ret) : def); +} + +std::optional env (const char* name) noexcept { + using optional = std::optional; + const auto ret = raw_fetch_env(name); + return (ret ? optional{ret} : optional{}); +} + +std::string_view env_throw (const char* name) { + const auto ret = raw_fetch_env(name); + if (ret) { + return {ret}; + } + else { + if (name) { + throw std::runtime_error( + std::string("Environment variable \"") + name + "\" not set" + ); + } + else { + throw std::runtime_error("Environment variable name is null"); + } + } +} + +bool is_env_set (const char* name) noexcept { + const auto ret = raw_fetch_env(name); + return not not ret; +} + +std::string_view env (const std::string& name, std::string_view def) noexcept { + return env(name.c_str(), def); +} + +std::optional env (const std::string& name) noexcept { + return env(name.c_str()); +} + +std::string_view env_throw (const std::string& name) { + return env_throw(name.c_str()); +} + +bool is_env_set (const std::string& name) noexcept { + return is_env_set(name.c_str()); +} +} //namespace duck diff --git a/src/env.hpp b/src/env.hpp new file mode 100644 index 0000000..d02c906 --- /dev/null +++ b/src/env.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +namespace duck { + +std::string_view env (const char* name, std::string_view def) noexcept; +std::optional env (const char* name) noexcept; +std::string_view env_throw (const char* name); +bool is_env_set (const char* name) noexcept; + +std::string_view env (const std::string& name, std::string_view def) noexcept; +std::optional env (const std::string& name) noexcept; +std::string_view env_throw (const std::string& name); +bool is_env_set (const std::string& name) noexcept; + +} //namespace duck diff --git a/src/gentoofunctions.cpp b/src/gentoofunctions.cpp new file mode 100644 index 0000000..c16e3a1 --- /dev/null +++ b/src/gentoofunctions.cpp @@ -0,0 +1,153 @@ +#include "gentoofunctions.hpp" +#include "env.hpp" +#include "string_view_cat.hpp" +#include "config.h" +#include "run_cmd.hpp" +#include +#if __cplusplus == 201703L +# include +#else +# error "If make_array() has been moved out of experimental please fix" +#endif +#include + +namespace duck { + namespace { + std::string_view g_last_e_cmd; + + enum YesNoIdk { + No, Yes, Idk + }; + + std::string lowercased (std::string_view in) { + std::string retval('\0', in.size()); + std::transform( + in.begin(), + in.end(), + retval.begin(), + [](char c)->char{return std::tolower(c);} + ); + return retval; + } + + template + bool has_item (I begin, const I& end, const T& val) { + auto it = std::find(begin, end, val); + return it != end; + } + + YesNoIdk yesno_impl (std::string_view val) { + using std::experimental::make_array; + using std::string_view; + + auto lcval = lowercased(val); + auto yesses = make_array("yes", "true", "on", "1"); + auto noes = make_array("no", "false", "off", "0"); + if (has_item(yesses.begin(), yesses.end(), lcval)) { + return Yes; + } + else if (has_item(noes.begin(), noes.end(), lcval)) { + return No; + } + else { + return Idk; + } + } + + void generic_eprint ( + std::ostream& stream, + const char* quiet_var, + const char* msg_prefix_var, + std::string_view log_pri, + std::string_view log_tag, + std::string_view new_e_cmd, + std::string_view msg, + const char* newline + ) { + if (yesno(env(quiet_var, ""))) { + return; + } + else { + if (not yesno(env("RC_ENDCOL", "")) and g_last_e_cmd == "ebegin") { + stream << '\n'; + } + stream << ' ' << env(msg_prefix_var, "") << '*' + << env("NORMAL", "") << ' ' << env("RC_INDENTATION", "") + << msg << newline; + } + + esyslog(log_pri, log_tag, msg); + g_last_e_cmd = new_e_cmd; + } + } //unnamed namespace + + void vewarn (std::string_view msg) { + if (yesno(env("EINFO_VERBOSE", ""), true)) + ewarn(msg); + } + + void ewarn (std::string_view msg) { + generic_eprint( + std::clog, + "EINFO_QUIET", + "WARN", + "daemon.warning", + PROJECT_NAME, + "ewarn", + msg, + "\n" + ); + } + + void esyslog(std::string_view pri, std::string_view tag, std::string_view msg) { + if (not env("EINFO_LOG", "").empty() and not run_cmd_retcode("command -v logger > /dev/null 2>&1")) { + if (msg.empty()) + return; + + run_cmd("logger", "-p", pri, "-t", tag, "--", msg); + } + } + + bool yesno (const std::string& var, bool force_quiet) { + { + const auto ret = yesno_impl(var); + switch (ret) { + case Yes: return true; + case No: return false; + default: break; + } + } + + { + auto val = env(var, ""); + if (val.empty()) + return false; + + const auto ret = yesno_impl(val); + switch (ret) { + case Yes: return true; + case No: return false; + default: + vewarn(std::string("\"") + var + "\" (\"" + val + "\") is not set properly"); + return false; + } + } + } + + bool yesno (std::string_view var, bool force_quiet) { + return yesno(to_string(var), force_quiet); + } + + void eerror (std::string_view msg) { + generic_eprint( + std::clog, + "EERROR_QUIET", + "BAD", + "daemon.err", + "rc-scripts", + "eerrorn", + msg, + "\n" + ); + } +} //namespace duck diff --git a/src/gentoofunctions.hpp b/src/gentoofunctions.hpp new file mode 100644 index 0000000..35400ec --- /dev/null +++ b/src/gentoofunctions.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace duck { + void vewarn (std::string_view msg); + void ewarn (std::string_view msg); + bool yesno (const std::string& var, bool force_quiet=false); + bool yesno (std::string_view var, bool force_quiet=false); + void esyslog(std::string_view pri, std::string_view tag, std::string_view msg); + void eerror (std::string_view msg); +} //namespace duck diff --git a/src/list_profiles.cpp b/src/list_profiles.cpp new file mode 100644 index 0000000..240c58a --- /dev/null +++ b/src/list_profiles.cpp @@ -0,0 +1,164 @@ +#include "list_profiles.hpp" +#include "syspaths.hpp" +#include "config.h" +#include "chost.hpp" +#include "to_var_map.hpp" +#include "load_file.hpp" +#include "string_view_cat.hpp" +#include "gentoofunctions.hpp" +#include +#include +#include +#include +#include +#include + +namespace duck { + namespace fs = std::filesystem; + + namespace { + [[gnu::pure]] + bool starts_with (std::string_view s, std::string_view prefix) { + return (s.substr(0, std::min(prefix.size(), s.size())) == prefix); + } + + std::vector decompose_version (const std::string& ver) { + std::vector ret; + int curr = 0; + bool push_last = false; + for (char c : ver) { + if (c >= '0' and c <= '9') { + curr = (curr * 10) + (c - '0'); + push_last = true; + } + else if (push_last) { + ret.push_back(curr); + curr = 0; + push_last = false; + } + } + if (push_last) + ret.push_back(curr); + return ret; + } + + [[gnu::pure]] + bool version_comp (const std::string& a, const std::string& b) { + std::vector va(decompose_version(a)); + std::vector vb(decompose_version(b)); + const std::size_t sz = std::max(va.size(), vb.size()); + va.resize(sz, 0); + vb.resize(sz, 0); + + for (std::size_t z = 0; z < sz; ++z) { + if (va[z] < vb[z]) + return true; + else if (va[z] > vb[z]) + return false; + } + return false; + } + + std::string current_compiler (const fs::path& config_path, const std::string& ctarget) { + if (fs::is_regular_file(config_path)) { + auto text = load_file(config_path); + auto var_map = to_var_map(text); + auto found_it = var_map.find("CURRENT"); + if (var_map.end() == found_it) + return {}; + else + return to_string(found_it->second); + } + else { + return {}; + } + } + } //unnamed namespace + + std::vector version_sorted_paths (const fs::path path) { + std::vector ret; + for (const fs::directory_entry& curr : fs::directory_iterator(path)) { + std::string filename = curr.path().filename(); + if (not starts_with(filename, "config") and curr.is_regular_file()) + ret.push_back(curr); + } + std::regex version_reg( + R"(-(\d+(?:\.\d)*(?:r\d+)?))", + std::regex_constants::ECMAScript | std::regex_constants::optimize + ); + std::sort( + ret.begin(), + ret.end(), + [&version_reg](const fs::path& a, const fs::path& b) { + using std::regex_match; + std::smatch ma, mb; + std::string sa = a.filename(); + std::string sb = b.filename(); + return version_comp( + (regex_search(sa, ma, version_reg) ? ma[1] : std::string("0")), + (regex_search(sb, mb, version_reg) ? mb[1] : std::string("0")) + ); + } + ); + return ret; + } + + void list_profiles(const SysPaths& sp) { + using std::string; + + if (sp.root() != "/") { + //echo "Using gcc-config info in ${ROOT}" + std::cout << "Using " << PROJECT_NAME << " info in " << sp.root() << '\n'; + } + + std::string chost = duck::chost(); + const std::string& ctarget = chost; + + std::string_view filter; + auto config_path = sp.gcc_env_d() / ("config-" + ctarget); + auto s = fs::status(config_path); + if (not fs::is_regular_file(s)) { + filter = ctarget; + } + + const std::string current = current_compiler(config_path, ctarget); + std::string_view current_native(current); + auto version_paths = version_sorted_paths(sp.gcc_env_d()); + std::string text; + std::size_t z = 0; + std::string target; + for (const auto& ver_path : version_paths) { + text = load_file(ver_path); + auto var_map = to_var_map(text); + auto curr_ctarget = var_map["CTARGET"]; + std::string filename = ver_path.filename(); + + if (curr_ctarget.empty()) { + if (starts_with(filename, chost + "-")) + curr_ctarget = chost; + else + ewarn("broken config file: " + std::string(ver_path)); + } + + ++z; + if (not filter.empty() and filter != curr_ctarget) + continue; + if (target != curr_ctarget) { + if (z > 1) + std::cout << '\n'; + target = to_string(curr_ctarget); + curr_ctarget = ""; + } + + std::cout << " [" << z << "] " << filename; + auto colours = sp.colours(); + if (filename == current_native) { + std::cout << ' ' << colours["GOOD"] << '*' << colours["NORMAL"]; + } + else if (fs::is_regular_file(sp.gcc_env_d() / ("config-" + target)) and filename == current) { + std::cout << ' ' << colours["HILITE"] << '*' << colours["NORMAL"]; + } + std::cout << '\n'; + } + } +} //namespace duck diff --git a/src/list_profiles.hpp b/src/list_profiles.hpp new file mode 100644 index 0000000..5ecbe48 --- /dev/null +++ b/src/list_profiles.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace duck { + class SysPaths; + + void list_profiles(const SysPaths& syspaths); +} //namespace duck diff --git a/src/load_file.cpp b/src/load_file.cpp new file mode 100644 index 0000000..cfd80a8 --- /dev/null +++ b/src/load_file.cpp @@ -0,0 +1,14 @@ +#include "load_file.hpp" +#include + +namespace duck { + std::string load_file (const std::string& path) { + std::ifstream ifs(path); + ifs >> std::noskipws; + + return std::string( + std::istreambuf_iterator(ifs), + std::istreambuf_iterator() + ); + } +} //namespace duck diff --git a/src/load_file.hpp b/src/load_file.hpp new file mode 100644 index 0000000..39dce60 --- /dev/null +++ b/src/load_file.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace duck { + std::string load_file (const std::string& path); +} //namespace duck diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..144bf30 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include "config.h" +#include "list_profiles.hpp" +#include "syspaths.hpp" + +namespace { + const char g_simple_colours[] = + "GOOD=\033[32;01m\n" + "WARN=\033[33;01m\n" + "BAD=\033[31;01m\n" + "HILITE=\033[36;01m\n" + "BRACKET=\033[34;01m\n" + "NORMAL=\033[0m\n" + ; +} //unnamed namespace + +int main(int argc, char* argv[]) { + using std::string; + cxxopts::Options options(PROJECT_NAME, "Change the current compiler profile, or give info about profiles"); + options.add_options() + ("h,help", "Show this help and quit") + ("version", "Print version info") + ("l,list-profiles", "Print a list of available profiles") //, cxxopts::value()->default_value("")) + ; + const int orig_argc = argc; + auto command = options.parse(argc, argv); + + if (command.count("help") or 1 == orig_argc) { + std::cout << options.help() << std::endl; + return 0; + } + if (command.count("version")) { + std::cout << PROJECT_NAME << " v" << PROJECT_VERSION << std::endl; + return 0; + } + + duck::SysPaths syspaths; + syspaths.set_colours(g_simple_colours); +#if !defined(NDEBUG) + std::cout << syspaths << '\n'; +#endif + + if (command.count("list-profiles")) { + duck::list_profiles(syspaths); + } + + return 0; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..84656b6 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,26 @@ +fslib_dep = cpp.find_library('stdc++fs', required: false) +cpp.has_header('pstream.h', required: true) + +project_config_file = configure_file( + input: 'config.h.in', + output: 'config.h', + configuration: conf +) + +executable(meson.project_name(), + 'main.cpp', + 'env.cpp', + project_config_file, + 'list_profiles.cpp', + 'syspaths.cpp', + 'chost.cpp', + 'run_cmd.cpp', + 'gentoofunctions.cpp', + 'replace_bash_vars.cpp', + 'to_var_map.cpp', + 'load_file.cpp', + 'var_map.cpp', + install: true, + dependencies: [fslib_dep], + include_directories: [cxxopts_incl], +) diff --git a/src/replace_bash_vars.cpp b/src/replace_bash_vars.cpp new file mode 100644 index 0000000..94f1bf8 --- /dev/null +++ b/src/replace_bash_vars.cpp @@ -0,0 +1,94 @@ +#include "replace_bash_vars.hpp" +#include "config.h" +#include "env.hpp" +#include "string_view_cat.hpp" +#include +#include + +namespace duck { + namespace { + struct VarInfo { + std::string name; + std::size_t orig_len{}; + }; + + //std::string replaced_all (std::string text, std::string_view search, std::string_view repl) { + // std::size_t pos = 0; + // while ((pos = text.find(search, pos)) != std::string::npos) { + // text.replace(pos, search.size(), repl); + // pos += repl.size(); + // } + // return text; + //} + + VarInfo extract_var_name (std::string_view text) { + assert(not text.empty()); + assert(text.front() == '$'); + if (text.size() == 1) { + return {}; + } + else if (text.size() == 2) { + if (std::isalnum(text[1])) + return {to_string(text.substr(1, 1)), 2}; + return {}; + } + else { + assert(text.size() >= 3); + if (text[1] == '{') { + const auto end = text.find('}', 2); + if (text.npos == end) { + return {}; + } + else { + std::string_view token(text.substr(2, end - 2)); + std::string retval(token.empty() || token.front() != '!' ? to_string(token) : to_string(token.substr(1))); + if (not token.empty() and token.front() == '!') { + return {to_string(env(retval, "")), end + 1}; + } + else { + return {retval, end + 1}; + } + } + } + else { + std::size_t z; + for (z = 1; z < text.size() && std::isalnum(text[z]); ++z) { + } + return {to_string(text.substr(1, z - 1)), z + 1}; + } + } + } + } //unnamed namespace + + std::string replace_bash_vars (std::string_view text) { + std::size_t pos = 0; + std::size_t prev_start = 0; + std::string rtext; + rtext.reserve(text.size()); + + while ((pos = text.find('$', pos)) != std::string::npos) { + if (0 == pos or rtext[pos-1] != '\\') { + auto var = extract_var_name(text.substr(pos)); + if (not var.orig_len) { + ++pos; + continue; + } + + assert(prev_start <= pos); + rtext.append(text.begin() + prev_start, text.begin() + pos); + if (var.name == "argv0") + rtext += PROJECT_NAME; + else + rtext += env(var.name, ""); + pos += var.orig_len; + prev_start = pos; + } + else { + ++pos; + } + } + + rtext.append(text.begin() + prev_start, text.end()); + return rtext; + } +} //namespace duck diff --git a/src/replace_bash_vars.hpp b/src/replace_bash_vars.hpp new file mode 100644 index 0000000..3400bd5 --- /dev/null +++ b/src/replace_bash_vars.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace duck { + std::string replace_bash_vars (std::string_view text); +} //namespace duck diff --git a/src/run_cmd.cpp b/src/run_cmd.cpp new file mode 100644 index 0000000..fb8f40a --- /dev/null +++ b/src/run_cmd.cpp @@ -0,0 +1,74 @@ +#include "run_cmd.hpp" +#include +#include +#if !defined(NDEBUG) +# include +# include +#endif +#include +#include +#include +#include +#include + +namespace duck::detail { + namespace { + void trim_eol (std::string& val) { + const auto pos = val.find_last_not_of('\n'); + if (val.npos != pos) + val.resize(pos + 1); + } + } //unnamed namespace + + RunCmdOutput run_cmd(const std::string& cmd, const std::vector& argv) { + assert(not argv.empty()); + assert(argv.front() == cmd); + +#if !defined(NDEBUG) + std::cout << "Running command: " << cmd; + std::for_each(argv.begin(), argv.end(), [](const auto& s){std::cout << ' ' << s;}); + std::cout << '\n'; +#endif + + const redi::pstream::pmode mode = redi::pstreams::pstdout | redi::pstreams::pstderr; + redi::ipstream ips(cmd, argv, mode); + + std::streamsize n; + RunCmdOutput ret; + std::array buf; + std::array finished {false, false}; + + while (not finished[0] or not finished[1]) { + if (not finished[0]) { + while ((n = ips.err().readsome(buf.data(), buf.size())) > 0) + ret.err.append(buf.begin(), buf.begin() + n); + if (ips.eof()) { + finished[0] = true; + if (not finished[1]) + ips.clear(); + } + } + + if (!finished[1]) { + while ((n = ips.out().readsome(buf.data(), buf.size())) > 0) + ret.out.append(buf.begin(), buf.begin() + n); + if (ips.eof()) { + finished[1] = true; + if (!finished[0]) + ips.clear(); + } + } + } + + trim_eol(ret.out); + trim_eol(ret.err); + return ret; + } +} //namespace detail::duck + +namespace duck { + int run_cmd_retcode (const char* command) { + assert(command); + return system(command); + } +} //namespace duck diff --git a/src/run_cmd.hpp b/src/run_cmd.hpp new file mode 100644 index 0000000..3d31f44 --- /dev/null +++ b/src/run_cmd.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "string_view_cat.hpp" +#include +#include +#include +#include + +namespace duck { + struct RunCmdOutput { + std::string out; + std::string err; + }; + + namespace detail { + RunCmdOutput run_cmd(const std::string& cmd, const std::vector& argv); + } //namespace detail + + template + inline RunCmdOutput run_cmd (std::string command, Args&&... args) { + using duck::to_string; + + std::vector argv { + command, + to_string(std::forward(args))... + }; + return detail::run_cmd(command, argv); + } + + int run_cmd_retcode (const char* command); +} //namespace duck diff --git a/src/string_view_cat.hpp b/src/string_view_cat.hpp new file mode 100644 index 0000000..7977c44 --- /dev/null +++ b/src/string_view_cat.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace duck { + inline std::string to_string (std::string_view sv) { + return std::string((sv.empty() ? static_cast("") : sv.data()), sv.size()); + } + + inline std::string to_string (const char* str) { + return std::string(str ? str : static_cast("")); + } + + inline std::string to_string (std::string str) { + return str; + } + + inline std::string operator+ (std::string_view a, std::string_view b) { + std::string ret(to_string(a)); + ret += b; + return ret; + } + + inline std::string operator+ (std::string_view a, const std::string& b) { + std::string ret(to_string(a)); + ret += b; + return ret; + } + + inline std::string operator+ (std::string a, std::string_view b) { + a += b; + return a; + } + + inline std::string operator+ (const char* a, std::string_view b) { + return to_string(a) + b; + } +} //namespace duck diff --git a/src/syspaths.cpp b/src/syspaths.cpp new file mode 100644 index 0000000..9cda062 --- /dev/null +++ b/src/syspaths.cpp @@ -0,0 +1,68 @@ +#include "syspaths.hpp" +#include "env.hpp" +#include "to_var_map.hpp" +#include + +namespace duck { + namespace { + std::string fix_eprefix(std::string_view p) { + if (p.size() >= 2 and p.front() == '@' and p.back() == '@') + return ""; + else + return std::string(p); + } + + std::string_view remove_front_slash(std::string_view text) { + const std::size_t pos = text.find_first_not_of('/'); + return (text.npos == pos ? text : text.substr(pos)); + } + } //unnamed namespace + + SysPaths::SysPaths() : + m_env(to_var_map_ignore_ownership(environ)), + m_root(duck::env("ROOT", "/")), + m_eprefix(fix_eprefix(duck::env("EPREFIX", ""))), + m_eroot(m_root / remove_front_slash(std::string(m_eprefix))), + m_env_d(m_eroot / "etc/env.d"), + m_gcc_env_d(m_env_d / "gcc") + { + + } + + void SysPaths::set_colours (std::string_view colours) { + m_colours = to_var_map_ignore_ownership(colours); + } + + auto SysPaths::root() const -> const path_type& { + return m_root; + } + + auto SysPaths::eroot() const -> const path_type& { + return m_eroot; + } + + auto SysPaths::eprefix() const -> const path_type& { + return m_eprefix; + } + + auto SysPaths::env_d() const -> const path_type& { + return m_env_d; + } + + auto SysPaths::gcc_env_d() const -> const path_type& { + return m_gcc_env_d; + } + +#if !defined(NDEBUG) + std::ostream& operator<< (std::ostream& oss, const SysPaths& sp) { + oss << "SysPaths {\n" << + "\tROOT: " << sp.root() << '\n' << + "\tEPREFIX: " << sp.eprefix() << '\n' << + "\tEROOT: " << sp.eroot() << '\n' << + "\tENV_D: " << sp.env_d() << '\n' << + "\tGCC_ENV_D: " << sp.gcc_env_d() << '\n' << + '}'; + return oss; + } +#endif +} //namespace duck diff --git a/src/syspaths.hpp b/src/syspaths.hpp new file mode 100644 index 0000000..a2d5169 --- /dev/null +++ b/src/syspaths.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "var_map.hpp" +#include +#include +#if !defined(NDEBUG) +# include +#endif +#include +#include + +namespace duck { + class SysPaths { + public: + typedef std::filesystem::path path_type; + + SysPaths(); + ~SysPaths() noexcept = default; + + void set_colours (std::string_view colours); + VarMapView colours() const { return {&m_colours}; } + VarMapView env() const { return {&m_env}; } + + const path_type& root() const; + const path_type& eroot() const; + const path_type& eprefix() const; + const path_type& env_d() const; + const path_type& gcc_env_d() const; + + private: + VarMap m_env; + VarMap m_colours; + path_type m_root; + path_type m_eprefix; + path_type m_eroot; + path_type m_env_d; + path_type m_gcc_env_d; + }; + +#if !defined(NDEBUG) + std::ostream& operator<< (std::ostream&, const SysPaths& sp); +#endif +} //namespace duck diff --git a/src/to_var_map.cpp b/src/to_var_map.cpp new file mode 100644 index 0000000..3051cf0 --- /dev/null +++ b/src/to_var_map.cpp @@ -0,0 +1,87 @@ +#include "to_var_map.hpp" +#include "string_view_cat.hpp" +#include +#include +#include +#include + +namespace duck { + namespace { + std::string_view trimmed (std::string_view text) { + using std::find_if_not; + + auto isspace_func = [](char c) {return std::isspace(c);}; + const auto new_start_it = find_if_not(text.cbegin(), text.cend(), isspace_func); + const std::size_t new_start = (new_start_it == text.cend() ? text.size() : static_cast(new_start_it - text.cbegin())); + + const auto new_end_it = find_if_not(text.crbegin(), text.crend(), isspace_func); + const std::size_t new_end = text.size() - (new_end_it == text.crend() ? std::size_t{} : static_cast(new_end_it - text.crbegin())); + + assert(new_end <= text.size()); + assert(new_end >= new_start); + return text.substr(new_start, new_end - new_start); + } + + std::pair split_key_val(std::string_view text) { + const auto eq_pos = text.find('='); + if (eq_pos == text.npos) { + throw std::runtime_error("Unrecognised line \"" + text + "\": expected key=value format not found"); + } + + return std::make_pair( + text.substr(0, eq_pos), + text.substr(eq_pos + 1) + ); + } + + std::string_view unquoted (std::string_view text) { + std::string_view ret = trimmed(text); + if (ret.size() < 2) + return ret; + else if (ret.front() == '"' and ret.back() == '"') + return ret.substr(1, ret.size() - 2); + else + return ret; + } + + void add_key_val_text_to_map (std::string_view text, VarMap& out) { + std::string_view line = trimmed(text); + if (line.empty() or line.front() == '#') + return; + + auto new_item = split_key_val(line); + out[new_item.first] = unquoted(new_item.second); + } + } //unnamed namespace + + VarMap to_var_map (std::string& text) { + return to_var_map_ignore_ownership(text); + } + + VarMap to_var_map_ignore_ownership (std::string_view text) { + VarMap ret; + std::size_t pos = 0; + std::size_t start = 0; + while ((pos = text.find('\n', pos)) != text.npos) { + add_key_val_text_to_map(text.substr(start, pos - start), ret); + start = pos + 1; + ++pos; + } + + add_key_val_text_to_map(text.substr(start), ret); + return ret; + } + + VarMap to_var_map_ignore_ownership (char** env) { + assert(env); + + VarMap ret; + std::size_t z = 0; + while (env[z]) { + std::string_view line(env[z]); + add_key_val_text_to_map(line, ret); + ++z; + } + return ret; + } +} //namespace duck diff --git a/src/to_var_map.hpp b/src/to_var_map.hpp new file mode 100644 index 0000000..5fe597b --- /dev/null +++ b/src/to_var_map.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "var_map.hpp" +#include + +namespace duck { + //parameter is non-const so caller hopefully remembers to keep it alive + //(ie: they can't just pass a temporary in) + VarMap to_var_map (std::string& text); + + VarMap to_var_map_ignore_ownership (std::string_view text); + VarMap to_var_map_ignore_ownership (char** env); +} //namespace duck diff --git a/src/var_map.cpp b/src/var_map.cpp new file mode 100644 index 0000000..5ef7c0c --- /dev/null +++ b/src/var_map.cpp @@ -0,0 +1,22 @@ +#include "var_map.hpp" +#include + +namespace duck { + VarMapView::VarMapView (const VarMap* vm) : + m_map(vm) + { + assert(m_map); + } + + std::string_view VarMapView::operator[] (std::string_view key) const { + if (key.empty()) + return ""; + + assert(m_map); + auto it_found = m_map->find(key); + if (m_map->end() != it_found) + return it_found->second; + else + return ""; + } +} //namespace duck diff --git a/src/var_map.hpp b/src/var_map.hpp new file mode 100644 index 0000000..962d83f --- /dev/null +++ b/src/var_map.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace duck { + typedef std::unordered_map VarMap; + + class VarMapView { + public: + VarMapView (const VarMap* vm); + std::string_view operator[] (std::string_view key) const; + + private: + const VarMap* m_map; + }; +} //namespace duck diff --git a/subprojects/cxxopts b/subprojects/cxxopts new file mode 160000 index 0000000..b0f67a0 --- /dev/null +++ b/subprojects/cxxopts @@ -0,0 +1 @@ +Subproject commit b0f67a06de3446aa97a4943ad0ad6086460b2b61