LCOV - code coverage report
Current view: top level - src/esys/repo/cli - appbase.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 164 282 58.2 %
Date: 2026-08-01 10:43:40 Functions: 24 43 55.8 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/cli/appbase.cpp
       3             :  * \brief
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020-2023 Michel Gillet
       9             :  * Distributed under the MIT License.
      10             :  * (See accompanying file LICENSE.txt or
      11             :  * copy at https://opensource.org/licenses/MIT)
      12             :  *
      13             :  * __legal_e__
      14             :  * \endcond
      15             :  *
      16             :  */
      17             : 
      18             : #include "esys/repo/esysrepo_prec.h"
      19             : #include "esys/repo/cli/appbase.h"
      20             : #include "esys/repo/version.h"
      21             : #include "esys/repo/cli/cmd.h"
      22             : #include "esys/repo/libgit2/git.h"
      23             : 
      24             : #include <esys/trace/call.h>
      25             : #include <esys/trace/macros.h>
      26             : 
      27             : #include <boost/program_options/cmdline.hpp>
      28             : #include <boost/program_options/options_description.hpp>
      29             : #include <boost/program_options/parsers.hpp>
      30             : #include <boost/filesystem.hpp>
      31             : #include <boost/tokenizer.hpp>
      32             : 
      33             : #ifndef WIN32
      34             : #include <boost/process/search_path.hpp>
      35             : #endif
      36             : 
      37             : #ifdef WIN32
      38             : #include <stdio.h>
      39             : 
      40             : #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
      41             : #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
      42             : #endif
      43             : 
      44             : #endif
      45             : 
      46             : #include <iostream>
      47             : #include <iomanip>
      48             : #include <cassert>
      49             : 
      50             : namespace esys::repo::cli
      51             : {
      52             : 
      53          17 : AppBase::AppBase()
      54             : {
      55          17 :     esys::trace::Call::set_differentiate_thread(true);
      56             : 
      57          17 :     m_user_folder = std::make_shared<UserFolder>();
      58          17 :     ETRC_SET_THREAD_NAME("AppBase1");
      59          17 : }
      60             : 
      61         101 : AppBase::~AppBase() = default;
      62             : 
      63           0 : void AppBase::set_os(std::ostream &os)
      64             : {
      65           0 :     m_os = &os;
      66           0 : }
      67             : 
      68          16 : std::ostream *AppBase::get_os()
      69             : {
      70          16 :     return m_os;
      71             : }
      72             : 
      73         161 : void AppBase::register_cmd(Cmd *cmd)
      74             : {
      75         161 :     assert(find_cmd(cmd->get_name()) == nullptr);
      76             : 
      77         161 :     m_cmds.push_back(cmd);
      78         161 :     m_map_cmds[cmd->get_name()] = cmd;
      79         161 : }
      80             : 
      81           4 : std::vector<Cmd *> &AppBase::get_cmds()
      82             : {
      83           4 :     return m_cmds;
      84             : }
      85             : 
      86           0 : const std::vector<Cmd *> &AppBase::get_cmds() const
      87             : {
      88           0 :     return m_cmds;
      89             : }
      90             : 
      91          35 : Cmd *AppBase::find_cmd(const std::string &name)
      92             : {
      93          35 :     auto it = m_map_cmds.find(name);
      94          35 :     if (it == m_map_cmds.end()) return nullptr;
      95          35 :     return it->second;
      96             : }
      97             : 
      98          21 : void AppBase::set_args(const std::vector<std::string> &args)
      99             : {
     100          21 :     m_args = args;
     101          21 : }
     102             : 
     103          17 : const std::vector<std::string> &AppBase::get_args() const
     104             : {
     105          17 :     return m_args;
     106             : }
     107             : 
     108           0 : void AppBase::set_args(int argc, char **argv)
     109             : {
     110           0 :     std::vector<std::string> args;
     111             : 
     112           0 :     m_argc = argc;
     113             : 
     114             : #ifdef WIN32
     115             :     m_executable = argv[0];
     116             :     m_executable = boost::filesystem::absolute(m_executable).lexically_normal().make_preferred();
     117             : #else
     118           0 :     boost::filesystem::path temp_path = argv[0];
     119           0 :     m_executable = boost::process::search_path(temp_path.filename());
     120             : #endif
     121             : 
     122           0 :     for (int i = 1; i < argc; ++i)
     123             :     {
     124             : #ifdef WIN32
     125             :         std::vector<std::string> temp_args = po::split_winmain(argv[i]);
     126             :         for (auto &temp_arg : temp_args) args.push_back(temp_arg);
     127             : #else
     128           0 :         std::string temp = argv[i];
     129           0 :         args.push_back(temp);
     130             : #endif
     131           0 :     }
     132             : 
     133           0 :     set_args(args);
     134           0 : }
     135             : 
     136          21 : int AppBase::parse_and_configure()
     137             : {
     138          21 :     auto rresult = get_user_folder()->populate_all_pathes();
     139          21 :     if (rresult.error())
     140             :     {
     141           0 :         warn("Couldn't populate the paths of the user folder");
     142             :     }
     143             : 
     144          21 :     int result = parse_args();
     145          21 :     if (result < 0)
     146             :     {
     147           0 :         error("command line parsing error.");
     148           0 :         print_help(std::cout);
     149             :         return result;
     150             :     }
     151             : 
     152          21 :     if (get_cmd().empty())
     153             :     {
     154           5 :         if (is_help())
     155             :         {
     156           0 :             print_help(std::cout);
     157             :             return 1;
     158             :         }
     159           5 :         if (is_doc())
     160             :         {
     161           0 :             print_doc(std::cout);
     162             :             return 1;
     163             :         }
     164           5 :         if (is_version())
     165             :         {
     166             :             // Same as "esysrepo version" — common CLI convention.
     167           2 :             m_vm.insert(std::make_pair("command", po::variable_value(std::string("version"), false)));
     168             :         }
     169             :         else
     170             :         {
     171           4 :             print_help(std::cout);
     172             :             return -1;
     173             :         }
     174             :     }
     175             : 
     176          17 :     auto cmd_obj = find_cmd(get_cmd());
     177          17 :     if (cmd_obj == nullptr)
     178             :     {
     179           0 :         error("unknown command " + get_cmd() + ".");
     180           0 :         print_help(std::cout);
     181             :         return -1;
     182             :     }
     183             : 
     184          17 :     cmd_obj->set_logger_if(get_logger_if());
     185          17 :     {
     186          17 :         std::vector<std::string> args = get_args();
     187             :         // Cmd parsers are strict and do not know --version; map it to the command name.
     188          34 :         if (get_cmd() == "version")
     189             :         {
     190           3 :             for (auto &arg : args)
     191             :             {
     192           2 :                 if (arg == "--version")
     193             :                 {
     194           1 :                     arg = "version";
     195             :                     break;
     196             :                 }
     197             :             }
     198             :         }
     199          17 :         cmd_obj->set_args(args);
     200          17 :     }
     201          17 :     cmd_obj->set_user_folder(get_user_folder());
     202             : 
     203          17 :     if (is_doc())
     204             :     {
     205           0 :         cmd_obj->print_doc(std::cout);
     206             :         return 1;
     207             :     }
     208             : 
     209          17 :     if (is_help())
     210             :     {
     211           0 :         cmd_obj->print_help(std::cout);
     212             :         return 1;
     213             :     }
     214             : 
     215          17 :     return cmd_obj->parse_and_configure();
     216          21 : }
     217             : 
     218          21 : int AppBase::parse_args()
     219             : {
     220          21 :     po::positional_options_description p;
     221          21 :     p.add("command", 1).add("subargs", -1);
     222             : 
     223          21 :     if (m_common_desc == nullptr)
     224             :     {
     225          17 :         m_common_desc = std::make_shared<po::options_description>("Options");
     226             :         // clang-format off
     227          17 :         m_common_desc->add_options()
     228          17 :             ("help,h", "Print help message")
     229          17 :             ("doc,d", "Print documentation")
     230          17 :             ("version", "Print version information (same as 'version' command)")
     231          17 :             ("command", po::value< std::string>(), "Command to execute")
     232          17 :             ("subargs", po::value<std::vector<std::string>>(), "Arguments for command")
     233          17 :             ("time", po::value<bool>()->default_value(false)->implicit_value(true), "Display timestamp")
     234          17 :             ("delta-time", po::value<bool>()->default_value(false)->implicit_value(true), "Display delta timestamp")
     235          17 :             ("public", po::value<bool>()->default_value(false)->implicit_value(true), "Only use https when possible")
     236          17 :             ("log", po::value<std::vector<std::string>>(), "Log execution to a file")
     237          17 :             ("verbose", po::value<int>()->default_value(0)->implicit_value(1), "Set verbose level")
     238          34 :             ("debug", po::value<bool>()->default_value(false)->implicit_value(true), "Set debug mode")
     239          17 :             ("workspace,w", po::value<std::string>(), "the path to the workspade to use")
     240          17 :             ("folder,f", po::value<std::string>(), "the folder to work with")
     241          17 :             ("trace", "enable execution trace to a file and to the console")
     242          17 :             ("trace-file", "enable execution trace to a file")
     243          17 :             ("trace-cons", "enable execution trace to the console")
     244             :             ;
     245             :         // clang-format on
     246             :     }
     247             : 
     248          21 :     int result = parse(m_args, *m_common_desc.get(), p, m_vm, false);
     249             : 
     250          21 :     setup_console_and_logs();
     251          21 :     return result;
     252          21 : }
     253             : 
     254          56 : std::string AppBase::get_cmd()
     255             : {
     256         214 :     if (m_vm.count("command")) return m_vm["command"].as<std::string>();
     257           5 :     return "";
     258             : }
     259             : 
     260          21 : bool AppBase::get_debug() const
     261             : {
     262          63 :     if (m_vm.count("debug")) return m_vm["debug"].as<bool>();
     263             :     return false;
     264             : }
     265             : 
     266          21 : bool AppBase::is_trace_to_file() const
     267             : {
     268          42 :     if (m_vm.count("trace")) return true;
     269          42 :     if (m_vm.count("trace-file")) return true;
     270             :     return false;
     271             : }
     272             : 
     273          21 : bool AppBase::is_trace_to_console() const
     274             : {
     275          42 :     if (m_vm.count("trace")) return true;
     276          42 :     if (m_vm.count("trace-cons")) return true;
     277             :     return false;
     278             : }
     279             : 
     280          22 : bool AppBase::is_help() const
     281             : {
     282          44 :     if (m_vm.count("help")) return true;
     283             :     return false;
     284             : }
     285             : 
     286          22 : bool AppBase::is_doc() const
     287             : {
     288          44 :     if (m_vm.count("doc")) return true;
     289             :     return false;
     290             : }
     291             : 
     292           5 : bool AppBase::is_version() const
     293             : {
     294          10 :     return m_vm.count("version") > 0;
     295             : }
     296             : 
     297          21 : int AppBase::setup_console_and_logs()
     298             : {
     299          21 :     bool debug = get_debug();
     300          21 :     bool trace_cons = is_trace_to_console();
     301          21 :     bool trace_file = is_trace_to_file();
     302             : 
     303          21 :     if (m_logger_mngr == nullptr) m_logger_mngr = esys::log::Mngr::get();
     304             : 
     305          21 :     std::vector<std::string> plugin_search_folders;
     306             : 
     307          21 :     int result = esys::log::Mngr::get()->find_plugin_folders(plugin_search_folders);
     308          21 :     if (result < 0) std::cout << "ERROR: can't find ESysLog plugin folder." << std::endl;
     309          21 :     if (debug)
     310             :     {
     311           0 :         std::cout << "plugin_search_folder = " << std::endl;
     312           0 :         for (auto idx = 0; idx < plugin_search_folders.size(); ++idx)
     313             :         {
     314           0 :             std::cout << "[" << idx << "] " << plugin_search_folders[idx] << std::endl;
     315             :         }
     316             :     }
     317             : 
     318             :     // m_logger_mngr->set_search_folder(plugin_search_folder.make_preferred().string());
     319          21 :     m_logger = m_logger_mngr->new_logger(esys::log::LoggerType::SPDLOG, "ESysRepo");
     320          21 :     if (m_logger != nullptr)
     321             :     {
     322          42 :         set_logger_if(m_logger);
     323             : 
     324          21 :         if (debug && !trace_cons && !trace_file)
     325             :         {
     326           0 :             m_logger->add_console("[%^%l%$] %v", esys::log::Level::DEBUG);
     327           0 :             if (!get_log_file_path().empty()) m_logger->add_basic_file(get_log_file_path());
     328           0 :             m_logger->set_log_level(esys::log::Level::DEBUG);
     329           0 :             m_logger->set_debug_level(DFT_LOGGER_DEBUG_LEVEL);
     330           0 :             m_logger->set_flush_log_level(esys::log::Level::DEBUG);
     331             :         }
     332          21 :         else if (trace_cons || trace_file)
     333             :         {
     334           0 :             if (trace_cons)
     335           0 :                 m_logger->add_console("[%^%l%$] %v", esys::log::Level::TRACE);
     336           0 :             else if (debug)
     337           0 :                 m_logger->add_console("[%^%l%$] %v", esys::log::Level::DEBUG);
     338             :             else
     339           0 :                 m_logger->add_console("[%^%l%$] %v", esys::log::Level::INFO);
     340           0 :             if (!get_log_file_path().empty() && !trace_file) m_logger->add_basic_file(get_log_file_path());
     341           0 :             m_logger->set_log_level(esys::log::Level::TRACE);
     342           0 :             m_logger->set_debug_level(DFT_LOGGER_DEBUG_LEVEL);
     343           0 :             m_logger->set_flush_log_level(esys::log::Level::TRACE);
     344             :         }
     345             :         else
     346             :         {
     347          21 :             m_logger->add_console("[%^%l%$] %v", esys::log::Level::INFO);
     348          21 :             if (!get_log_file_path().empty())
     349          21 :                 m_logger->add_basic_file(get_log_file_path(), false, esys::log::Level::INFO);
     350          21 :             m_logger->set_log_level(esys::log::Level::INFO);
     351          21 :             m_logger->set_flush_log_level(esys::log::Level::INFO);
     352             :         }
     353          21 :         if (trace_file)
     354             :         {
     355           0 :             boost::filesystem::path traces_folder;
     356             : 
     357           0 :             if ((get_user_folder() != nullptr) && (get_user_folder()->get_user_config() != nullptr))
     358           0 :                 traces_folder = get_user_folder()->get_user_config()->get_traces_path();
     359             :             else
     360           0 :                 traces_folder = boost::filesystem::current_path();
     361           0 :             if (!boost::filesystem::exists(traces_folder))
     362             :             {
     363           0 :                 boost::filesystem::create_directories(traces_folder);
     364             :             }
     365           0 :             boost::filesystem::path traces_file = traces_folder / "file.txt";
     366           0 :             m_logger->add_rotating_file(traces_file.string(), 100, 10, esys::log::Level::TRACE);
     367           0 :         }
     368          21 :         if (trace_file || trace_cons)
     369             :         {
     370           0 :             m_trace = std::make_shared<esys::log::Trace>();
     371           0 :             m_trace->set_logger(m_logger);
     372             : 
     373           0 :             m_trace_logger = std::make_shared<esys::trace::Logger>();
     374           0 :             m_trace_logger->set_log_if(m_trace);
     375           0 :             esys::trace::Call::set_default_logger(m_trace_logger.get());
     376           0 :             esys::trace::Call::enable();
     377             :         }
     378             :     }
     379          21 :     return 0;
     380          21 : }
     381             : 
     382           0 : void AppBase::setup_terminal()
     383             : {
     384             : #ifdef WIN32
     385             :     // Enable VT sequences when attached to a real console. Piped/redirected
     386             :     // stdout (Cursor agent, CI, `> file`) is not an error — just skip quietly.
     387             :     HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
     388             :     if (hOut == INVALID_HANDLE_VALUE || hOut == nullptr) return;
     389             : 
     390             :     DWORD dwMode = 0;
     391             :     if (!GetConsoleMode(hOut, &dwMode)) return;
     392             : 
     393             :     dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
     394             :     SetConsoleMode(hOut, dwMode);
     395             : #endif
     396           0 : }
     397             : 
     398          16 : int AppBase::run()
     399             : {
     400          32 :     if (m_vm.count("command"))
     401             :     {
     402          32 :         std::string cmd = m_vm["command"].as<std::string>();
     403             : 
     404          16 :         auto cmd_obj = find_cmd(cmd);
     405          16 :         if (cmd_obj != nullptr)
     406             :         {
     407          16 :             m_git = std::make_shared<esys::repo::libgit2::Git>();
     408          16 :             m_vm.clear();
     409          16 :             cmd_obj->set_logger_if(get_logger_if());
     410          16 :             cmd_obj->set_console_os(get_os());
     411          16 :             return cmd_obj->run();
     412             :         }
     413             :         else
     414             :         {
     415           0 :             error("Comamnd '" + cmd + "' is not known.");
     416           0 :             return -1;
     417             :         }
     418          16 :     }
     419             : 
     420           0 :     error("A comamnd must be provided.");
     421           0 :     return -1;
     422             : }
     423             : 
     424           0 : void AppBase::set_error_msg(const std::string &error_msg)
     425             : {
     426           0 :     m_error_msg = error_msg;
     427           0 : }
     428             : 
     429           0 : const std::string &AppBase::get_error_msg()
     430             : {
     431           0 :     return m_error_msg;
     432             : }
     433             : 
     434             : struct CmpCmdName
     435             : {
     436          54 :     bool operator()(const Cmd *a, const Cmd *b) const
     437             :     {
     438          54 :         return a->get_name() < b->get_name();
     439             :     }
     440             : };
     441             : 
     442           4 : void AppBase::print_help(std::ostream &os)
     443             : {
     444           4 :     std::ostringstream oss;
     445             : 
     446           4 :     oss << "Usage: esysrepo [cmd] [cmd_options] [common_options]" << std::endl << std::endl;
     447             : 
     448           4 :     oss << "Commands:" << std::endl;
     449             : 
     450           4 :     std::vector<Cmd *> cmds = get_cmds();
     451           4 :     CmpCmdName cmp_cmd_name;
     452             : 
     453           4 :     std::sort(cmds.begin(), cmds.end(), cmp_cmd_name);
     454             : 
     455          35 :     for (auto cmd : cmds)
     456             :     {
     457          31 :         oss << "    " << std::setw(15) << std::left << cmd->get_name() << cmd->get_description() << std::endl;
     458             :     }
     459           4 :     oss << std::endl;
     460             : 
     461           4 :     oss << "'esysrepo [cmd] --help' to get information about a cmd." << std::endl;
     462           4 :     oss << "'esysrepo [cmd] --doc' to get documentation about a cmd." << std::endl;
     463           4 :     oss << std::endl;
     464             : 
     465           4 :     if (m_common_desc == nullptr) return;
     466           4 :     oss << *m_common_desc;
     467             : 
     468           4 :     os << oss.str();
     469           4 : }
     470             : 
     471           0 : void AppBase::print_doc(std::ostream &os)
     472             : {
     473           0 : }
     474             : 
     475           0 : std::string AppBase::get_input_filename()
     476             : {
     477           0 :     std::string result = m_vm["input"].as<std::string>();
     478             : 
     479           0 :     return result;
     480             : }
     481             : 
     482           0 : std::string AppBase::get_output_filename()
     483             : {
     484           0 :     std::string result;
     485             : 
     486           0 :     if (m_vm.count("output") != 0) return m_vm["output"].as<std::string>();
     487             : 
     488           0 :     boost::filesystem::path filename = get_input_filename();
     489           0 :     boost::filesystem::path output = filename.parent_path() / (filename.stem().string() + ".md");
     490           0 :     return output.make_preferred().string();
     491           0 : }
     492             : 
     493          21 : int AppBase::parse(const std::vector<std::string> &args, po::options_description &desc,
     494             :                    po::positional_options_description &p, po::variables_map &vm, bool strict)
     495             : {
     496          21 :     bool parse_error = false;
     497             : 
     498          21 :     m_to_parse_further.clear();
     499          21 :     m_vm.clear();
     500             : 
     501          21 :     try
     502             :     {
     503          21 :         if (strict == false)
     504             :         {
     505          21 :             po::parsed_options parsed =
     506          42 :                 po::command_line_parser(args).options(desc).positional(p).allow_unregistered().run();
     507          21 :             m_to_parse_further = po::collect_unrecognized(parsed.options, po::include_positional);
     508          21 :             po::store(parsed, vm);
     509          21 :         }
     510             :         else
     511             :         {
     512           0 :             po::parsed_options parsed = po::command_line_parser(args).options(desc).positional(p).run();
     513           0 :             m_to_parse_further = po::collect_unrecognized(parsed.options, po::include_positional);
     514           0 :             po::store(parsed, vm);
     515           0 :         }
     516          21 :         po::notify(m_vm);
     517             :     }
     518           0 :     catch (po::error &e)
     519             :     {
     520           0 :         parse_error = true;
     521           0 :         set_error_msg(e.what());
     522           0 :         std::cout << e.what() << std::endl << std::flush;
     523           0 :     }
     524             : 
     525           0 :     if (parse_error) return -1;
     526             :     return 0;
     527             : }
     528             : 
     529             : /*AppBase::CmdFctType AppBase::find_cmd_fct(const std::string &cmd)
     530             : {
     531             :     auto it = m_map_commands.find(cmd);
     532             : 
     533             :     if (it == m_map_commands.end()) return nullptr;
     534             : 
     535             :     return it->second;
     536             : } */
     537             : 
     538          42 : const std::string &AppBase::get_log_file_path()
     539             : {
     540          42 :     m_log_file_path = "log.txt";
     541          42 :     return m_log_file_path;
     542             : }
     543             : 
     544           0 : void AppBase::set_logger_mngr(std::shared_ptr<esys::log::Mngr> logger_mngr)
     545             : {
     546           0 :     m_logger_mngr = logger_mngr;
     547           0 : }
     548             : 
     549           0 : std::string AppBase::get_string(const std::string &name)
     550             : {
     551           0 :     if (m_vm.count(name)) return m_vm[name].as<std::string>();
     552           0 :     return "";
     553             : }
     554             : 
     555           0 : void AppBase::set_version(const std::string &version)
     556             : {
     557           0 :     m_version = version;
     558           0 : }
     559             : 
     560           0 : const std::string &AppBase::get_version() const
     561             : {
     562           0 :     return m_version;
     563             : }
     564             : 
     565           0 : std::shared_ptr<po::options_description> AppBase::get_common_desc()
     566             : {
     567           0 :     return m_common_desc;
     568             : }
     569             : 
     570           0 : int AppBase::groups_str_to_groups(const std::string &groups_str, std::vector<std::string> &groups)
     571             : {
     572           0 :     typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
     573           0 :     boost::char_separator<char> sep(", ");
     574           0 :     tokenizer tokens(groups_str, sep);
     575             : 
     576           0 :     groups.clear();
     577             : 
     578           0 :     for (tokenizer::iterator it = tokens.begin(); it != tokens.end(); ++it) groups.push_back(*it);
     579             : 
     580           0 :     return 0;
     581           0 : }
     582             : 
     583           0 : std::string AppBase::get_workspace_folder()
     584             : {
     585           0 :     return get_string("workspace");
     586             : }
     587             : 
     588           0 : std::string AppBase::get_folder()
     589             : {
     590           0 :     return get_string("folder");
     591             : }
     592             : 
     593          38 : std::shared_ptr<UserFolder> AppBase::get_user_folder()
     594             : {
     595          38 :     return m_user_folder;
     596             : }
     597             : 
     598           0 : std::vector<std::string> AppBase::get_sub_args()
     599             : {
     600           0 :     if (m_vm.count("subargs") == 0) return std::vector<std::string>();
     601             : 
     602           0 :     return m_vm["subargs"].as<std::vector<std::string>>();
     603             : }
     604             : 
     605             : } // namespace esys::repo::cli

Generated by: LCOV version 1.14