From 42eef15c57807a3e9b0ecf75a4d3218e5c1ea3c6 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Tue, 21 Feb 2023 20:26:28 +0100 Subject: [PATCH 1/3] [MISC] Rename is_terminal to input_is_terminal --- include/sharg/detail/format_help.hpp | 6 +++--- include/sharg/detail/terminal.hpp | 8 +++++--- include/sharg/detail/version_check.hpp | 6 +++--- test/unit/detail/version_check_test.hpp | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/sharg/detail/format_help.hpp b/include/sharg/detail/format_help.hpp index 8a3e4273..fd8ecd17 100644 --- a/include/sharg/detail/format_help.hpp +++ b/include/sharg/detail/format_help.hpp @@ -241,17 +241,17 @@ class format_help : public format_help_base assert(it != str.end()); if (*it == 'I') { - if (is_terminal()) + if (input_is_terminal()) result.append("\033[4m"); } else if (*it == 'B') { - if (is_terminal()) + if (input_is_terminal()) result.append("\033[1m"); } else if (*it == 'P') { - if (is_terminal()) + if (input_is_terminal()) result.append("\033[0m"); } else diff --git a/include/sharg/detail/terminal.hpp b/include/sharg/detail/terminal.hpp index f2525b36..d3a12f0b 100644 --- a/include/sharg/detail/terminal.hpp +++ b/include/sharg/detail/terminal.hpp @@ -28,14 +28,16 @@ namespace sharg::detail { // ---------------------------------------------------------------------------- -// Function is_terminal() +// Function input_is_terminal() // ---------------------------------------------------------------------------- -/*!\brief Check whether we are printing to a terminal. +/*!\brief Check whether the input is a terminal. * \ingroup parser * \return True if code is run in a terminal, false otherwise. + * \details + * For example "./some_binary --help | less" will return false. "./some_binary --help" will return true. */ -inline bool is_terminal() +inline bool input_is_terminal() { #ifndef _WIN32 return isatty(STDIN_FILENO); diff --git a/include/sharg/detail/version_check.hpp b/include/sharg/detail/version_check.hpp index fe7f5f19..c046107a 100644 --- a/include/sharg/detail/version_check.hpp +++ b/include/sharg/detail/version_check.hpp @@ -287,7 +287,7 @@ class version_checker * * ASK: Ask the user or default the decision once a day. * * If the cookie content is "ASK" and the timestamp is older than a day we ask the user, - * if possible (sharg::detail::is_terminal()), what he wants to do, set the according cookie for the next time + * if possible (sharg::detail::input_is_terminal()), what he wants to do, set the according cookie for the next time * and continue. If we cannot ask the user, the default kicks in (do the check). */ bool decide_if_check_is_performed(update_notifications developer_approval, std::optional user_approval) @@ -336,7 +336,7 @@ class version_checker // nor did the the cookie tell us what to do. We will now ask the user if possible or do the check by default. write_cookie("ASK"); // Ask again next time when we read the cookie, if this is not overwritten. - if (detail::is_terminal()) // LCOV_EXCL_START + if (detail::input_is_terminal()) // LCOV_EXCL_START { std::cerr << R"( ####################################################################### @@ -385,7 +385,7 @@ class version_checker } } } - else // if !detail::is_terminal() + else // if !detail::input_is_terminal() { return false; // default: do not check version today, if you cannot ask the user } diff --git a/test/unit/detail/version_check_test.hpp b/test/unit/detail/version_check_test.hpp index 3319f330..96382cf8 100644 --- a/test/unit/detail/version_check_test.hpp +++ b/test/unit/detail/version_check_test.hpp @@ -250,7 +250,7 @@ TEST_F(version_check, option_on) } // Note that we cannot test interactiveness because google test captures std::cin and thus -// sharg::detail::is_terminal() is always false +// sharg::detail::input_is_terminal() is always false TEST_F(version_check, option_implicitely_on) { char const * argv[2] = {app_name.c_str(), "-f"}; From b066bf61be9277f446d841f10ac1f809f82ea853 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Tue, 21 Feb 2023 20:34:10 +0100 Subject: [PATCH 2/3] [MISC] Move std::exit for special formats to parser --- include/sharg/detail/format_base.hpp | 2 -- include/sharg/detail/format_help.hpp | 6 ------ include/sharg/parser.hpp | 4 ++++ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/sharg/detail/format_base.hpp b/include/sharg/detail/format_base.hpp index 2ef93751..2fc8fabe 100644 --- a/include/sharg/detail/format_base.hpp +++ b/include/sharg/detail/format_base.hpp @@ -357,8 +357,6 @@ class format_help_base : public format_base print_legal(); derived_t().print_footer(); - - std::exit(EXIT_SUCCESS); // program should not continue from here } /*!\brief Adds a print_section call to parser_set_up_calls. diff --git a/include/sharg/detail/format_help.hpp b/include/sharg/detail/format_help.hpp index fd8ecd17..407efb97 100644 --- a/include/sharg/detail/format_help.hpp +++ b/include/sharg/detail/format_help.hpp @@ -422,8 +422,6 @@ class format_short_help : public format_help print_synopsis(); print_line("Try -h or --help for more information.\n", true); - - std::exit(EXIT_SUCCESS); } }; @@ -450,8 +448,6 @@ class format_version : public format_help print_header(); print_version(); - - std::exit(EXIT_SUCCESS); // program should not continue from here } }; @@ -526,8 +522,6 @@ DAMAGE.)"}; << in_bold("This program contains SeqAn code licensed under the following terms:\n") << std::string(80, '-') << '\n' << seqan_license << '\n'; - - std::exit(EXIT_SUCCESS); } }; diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index ccbea689..ad1840b8 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -438,6 +438,10 @@ class parser }, format); parse_was_called = true; + + // Exit after parsing any special format. + if (!std::holds_alternative(format)) + std::exit(EXIT_SUCCESS); } /*!\brief Returns a reference to the sub-parser instance if From 59c557526e2c99865109b5b78dca033c2b527f10 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Tue, 21 Feb 2023 21:04:20 +0100 Subject: [PATCH 3/3] [FEATURE] Open man page --- include/sharg/detail/format_man.hpp | 36 +++++++++++++++++++++++++++-- include/sharg/detail/terminal.hpp | 15 ++++++++++++ include/sharg/parser.hpp | 11 +++++++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/include/sharg/detail/format_man.hpp b/include/sharg/detail/format_man.hpp index 215fa70e..15ceb5ab 100644 --- a/include/sharg/detail/format_man.hpp +++ b/include/sharg/detail/format_man.hpp @@ -13,6 +13,7 @@ #pragma once #include +#include namespace sharg::detail { @@ -38,6 +39,9 @@ class format_man : public format_help_base //!\brief Befriend the base class to give access to the private member functions. friend base_type; + //!\brief Whether to call man and open the man page. + bool open_man_page{false}; + public: /*!\name Constructors, destructor and assignment * \{ @@ -52,10 +56,38 @@ class format_man : public format_help_base //!\copydoc sharg::detail::format_help_base::format_help_base format_man(std::vector const & names, update_notifications const version_updates, - bool const advanced = false) : - base_type{names, version_updates, advanced} {}; + bool const advanced = false, + bool const open_man_page = false) : + base_type{names, version_updates, advanced}, + open_man_page{open_man_page} {}; //!\} + /*!\brief Initiates the printing of the man page to std::cout or opens it in man. + * \param[in] parser_meta The meta information that are needed for a detailed man page. + */ + void parse(parser_meta_data & parser_meta) + { + if (!open_man_page) + return base_type::parse(parser_meta); + + sharg::test::tmp_filename tmp_file{parser_meta.app_name.c_str()}; + + { + std::ofstream out{tmp_file.get_path()}; + std::streambuf * coutbuf = std::cout.rdbuf(); + std::cout.rdbuf(out.rdbuf()); + + base_type::parse(parser_meta); + + std::cout.rdbuf(coutbuf); + } + + std::string command{"/usr/bin/man -l "}; + command += tmp_file.get_path().c_str(); + if (std::system(command.c_str()) != 0) + throw sharg::parser_error{"Unexpected failure."}; // LCOV_EXCL_LINE + } + private: //!\brief Prints a help page header in man page format to std::cout. void print_header() diff --git a/include/sharg/detail/terminal.hpp b/include/sharg/detail/terminal.hpp index d3a12f0b..73f423c3 100644 --- a/include/sharg/detail/terminal.hpp +++ b/include/sharg/detail/terminal.hpp @@ -46,6 +46,21 @@ inline bool input_is_terminal() #endif } +/*!\brief Check whether the output is interactive. + * \ingroup parser + * \return True if code is run in a terminal, false otherwise. + * \details + * For example "./some_binary --help | less" will return false. "./some_binary --help" will return true. + */ +inline bool output_is_terminal() +{ +#ifndef _WIN32 + return isatty(STDOUT_FILENO); +#else + return false; +#endif +} + // ---------------------------------------------------------------------------- // Function get_terminal_size() // ---------------------------------------------------------------------------- diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index ad1840b8..a111cef2 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -758,6 +758,7 @@ class parser executable_name.emplace_back(argv[0]); bool special_format_was_set{false}; + bool const use_man = detail::output_is_terminal() && !system("which /usr/bin/man > /dev/null 2>&1"); for (int i = 1, argv_len = argc; i < argv_len; ++i) // start at 1 to skip binary name { @@ -793,12 +794,18 @@ class parser if (arg == "-h" || arg == "--help") { - format = detail::format_help{subcommands, version_check_dev_decision, false}; + if (use_man) + format = detail::format_man{subcommands, version_check_dev_decision, false, true}; + else + format = detail::format_help{subcommands, version_check_dev_decision, false}; special_format_was_set = true; } else if (arg == "-hh" || arg == "--advanced-help") { - format = detail::format_help{subcommands, version_check_dev_decision, true}; + if (use_man) + format = detail::format_man{subcommands, version_check_dev_decision, true, true}; + else + format = detail::format_help{subcommands, version_check_dev_decision, true}; special_format_was_set = true; } else if (arg == "--version")