Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MISC] Add quotes to strings #224

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ OPTIONS

Eating Numbers
-i, --int (signed 32 bit integer)
Desc. Default: 0.
Desc. Default: 0

Common options
-h, --help
Expand All @@ -144,7 +144,7 @@ OPTIONS
Export the help page information. Value must be one of [html, man,
ctd, cwl].
--version-check (bool)
Whether to check for the newest app version. Default: true.
Whether to check for the newest app version. Default: true

VERSION
Last update:
Expand Down
103 changes: 91 additions & 12 deletions include/sharg/detail/format_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class format_base
*
* \details Special characters considered are `"`, `\`, `&`, `<` and `>`.
*/
std::string escape_special_xml_chars(std::string const & original)
static std::string escape_special_xml_chars(std::string const & original)
{
std::string escaped;
escaped.reserve(original.size()); // will be at least as long
Expand Down Expand Up @@ -183,6 +183,62 @@ class format_base

return tmp;
}

/*!\brief Returns the default message for the help page.
* \tparam option_type The type of the option.
* \tparam default_type The type of the default value.
* \param[in] option The option to get the default message for.
* \param[in] value The default value to get the default message for.
* \returns The default message for the help page (" Default: <default-value>. ").
* \details
* `value` is either `config.default_message`, or the same as `option`.
* If the `option_type` is a std::string or std::filesystem::path, the value is quoted.
* If the `option_type` is a container of std::string or std::filesystem::path, each individual value is quoted;
* if a `config.default_message` is provided, it will not be quoted.
*/
template <typename option_type, typename default_type>
static std::string get_default_message(option_type const & SHARG_DOXYGEN_ONLY(option), default_type const & value)
SGSSGene marked this conversation as resolved.
Show resolved Hide resolved
{
static_assert(std::same_as<option_type, default_type> || std::same_as<default_type, std::string>);

std::stringstream message{};
message << " Default: ";

if constexpr (detail::is_container_option<option_type>)
{
// If we have a list of strings, we want to quote each string.
if constexpr (std::same_as<std::ranges::range_value_t<default_type>, std::string>)
{
auto view = std::views::transform(value,
[](auto const & val)
{
return std::quoted(val);
});
message << detail::to_string(view);
}
else // Otherwise we just print the list or the default_message without quotes.
{
message << detail::to_string(value);
}
}
else
{
static constexpr bool option_is_string = std::same_as<option_type, std::string>;
static constexpr bool option_is_path = std::same_as<option_type, std::filesystem::path>;
static constexpr bool value_is_string = std::same_as<default_type, std::string>;

// Quote: std::string (from value + default_message), and std::filesystem::path (default_message).
// std::filesystem::path is quoted by the STL's operator<< in detail::to_string.
static constexpr bool needs_string_quote = option_is_string || (option_is_path && value_is_string);

if constexpr (needs_string_quote)
message << std::quoted(value);
else
message << detail::to_string(value);
}

return message.str();
}
};

/*!\brief The format that contains all helper functions needed in all formats for
Expand Down Expand Up @@ -227,12 +283,14 @@ class format_help_base : public format_base
{
std::string id = prep_id_for_help(config.short_id, config.long_id) + " " + option_type_and_list_info(value);
std::string info{config.description};

if (config.default_message.empty())
info += ((config.required) ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
info += ((config.required) ? std::string{} : get_default_message(value, value));
else
info += detail::to_string(" Default: ", config.default_message, ". ");
info += get_default_message(value, config.default_message);

info += config.validator.get_help_page_message();
if (auto const & validator_message = config.validator.get_help_page_message(); !validator_message.empty())
info += ". " + validator_message;

store_help_page_element(
[this, id, info]()
Expand Down Expand Up @@ -262,20 +320,41 @@ class format_help_base : public format_base
template <typename option_type, typename validator_t>
void add_positional_option(option_type & value, config<validator_t> const & config)
{
// a list at the end may be empty and thus have a default value
auto positional_default_message = [&value]() -> std::string
{
if constexpr (detail::is_container_option<option_type>)
{
return get_default_message(value, value);
}
else
{
(void)value; // Silence unused variable warning.
return {};
}
};

auto positional_validator_message = [&config]() -> std::string
{
if (auto const & validator_message = config.validator.get_help_page_message(); !validator_message.empty())
return ". " + validator_message;
else
return {};
};

positional_option_calls.push_back(
[this, &value, description = config.description, validator = config.validator]()
[this,
&value,
default_message = positional_default_message(),
validator_message = positional_validator_message(),
description = config.description]()
{
++positional_option_count;
derived_t().print_list_item(detail::to_string("\\fBARGUMENT-",
positional_option_count,
"\\fP ",
option_type_and_list_info(value)),
description +
// a list at the end may be empty and thus have a default value
((detail::is_container_option<option_type>)
? detail::to_string(" Default: ", value, ". ")
: std::string{" "})
+ validator.get_help_page_message());
description + default_message + validator_message);
});
}

Expand Down Expand Up @@ -343,7 +422,7 @@ class format_help_base : public format_base
+ detail::supported_exports + ".");
if (version_check_dev_decision == update_notifications::on)
derived_t().print_list_item("\\fB--version-check\\fP (bool)",
"Whether to check for the newest app version. Default: true.");
"Whether to check for the newest app version. Default: true");

if (!meta.examples.empty())
{
Expand Down
44 changes: 34 additions & 10 deletions include/sharg/detail/format_tdl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,14 @@ class format_tdl : format_base
void add_option(option_type & value, config<validator_t> const & config)
{
auto description = config.description;
description += (config.required ? std::string{" "} : detail::to_string(" Default: ", value, ". "));
description += config.validator.get_help_page_message();

if (config.default_message.empty())
description += ((config.required) ? std::string{} : get_default_message(value, value));
else
description += get_default_message(value, config.default_message);

if (auto const & validator_message = config.validator.get_help_page_message(); !validator_message.empty())
description += ". " + validator_message;

auto tags = std::set<std::string>{};
if (config.required)
Expand Down Expand Up @@ -263,19 +269,37 @@ class format_tdl : format_base
template <typename option_type, typename validator_t>
void add_positional_option(option_type & value, config<validator_t> const & config)
{
std::string msg = config.validator.get_help_page_message();
// a list at the end may be empty and thus have a default value
auto positional_default_message = [&value]() -> std::string
{
if constexpr (detail::is_container_option<option_type>)
{
return get_default_message(value, value);
}
else
{
(void)value; // Silence unused variable warning.
return {};
}
};

auto positional_validator_message = [&config]() -> std::string
{
if (auto const & validator_message = config.validator.get_help_page_message(); !validator_message.empty())
return ". " + validator_message;
else
return {};
};

positional_option_calls.push_back(
[this, &value, config, msg](std::string_view)
[this,
config,
default_message = positional_default_message(),
validator_message = positional_validator_message()](std::string_view)
{
auto id = "positional_" + std::to_string(positional_option_count);
++positional_option_count;
auto description =
config.description +
// a list at the end may be empty and thus have a default value
((detail::is_container_option<option_type>) ? detail::to_string(" Default: ", value, ". ")
: std::string{" "})
+ msg;
auto description = config.description + default_message + validator_message;

parameters.push_back(tdl::Node{
.name = id,
Expand Down
13 changes: 10 additions & 3 deletions include/sharg/detail/to_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ static std::string const supported_exports =
"[html, man]";
#endif

//!\brief Concept for views whose value type is ostreamable.
template <typename container_t>
concept is_ostreamable_view = std::ranges::view<container_t> && ostreamable<std::ranges::range_value_t<container_t>>;

/*!\brief Streams all parameters via std::ostringstream and returns a concatenated string.
* \ingroup misc
* \tparam value_types Must be sharg::ostreamable (stream << value).
Expand All @@ -42,7 +46,11 @@ std::string to_string(value_types &&... values)

auto print = [&stream](auto && val)
{
if constexpr (is_container_option<std::remove_cvref_t<decltype(val)>>)
using value_t = std::remove_cvref_t<decltype(val)>;

// When passing a `std::vector<std::string> | std::views::transform(...)` which returns `std::quoted(str)` for
// each element, the `std::quoted`'s return value does not model a range, but is ostreamable.
if constexpr (is_container_option<value_t> || is_ostreamable_view<value_t>)
{
if (val.empty())
{
Expand All @@ -58,8 +66,7 @@ std::string to_string(value_types &&... values)
stream << ']';
}
}
else if constexpr (std::is_same_v<std::remove_cvref_t<decltype(val)>, int8_t>
|| std::is_same_v<std::remove_cvref_t<decltype(val)>, uint8_t>)
else if constexpr (std::is_same_v<value_t, int8_t> || std::is_same_v<value_t, uint8_t>)
{
stream << static_cast<int16_t>(val);
}
Expand Down
88 changes: 88 additions & 0 deletions test/api_stability/1.1.1/0001-API-Update-TDL.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
From 724ad34dea87caf2dde5f19af51d7b5527acf3a2 Mon Sep 17 00:00:00 2001
From: Simon Gene Gottlieb <[email protected]>
Date: Thu, 5 Oct 2023 13:43:30 +0200
Subject: [PATCH 1/2] [API] Update TDL

---
test/unit/detail/format_ctd_test.cpp | 2 +-
test/unit/detail/format_cwl_test.cpp | 22 +++++++++++-----------
2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/test/unit/detail/format_ctd_test.cpp b/test/unit/detail/format_ctd_test.cpp
index fa6a5b6..352acaa 100644
--- a/test/unit/detail/format_ctd_test.cpp
+++ b/test/unit/detail/format_ctd_test.cpp
@@ -110,7 +110,7 @@ TEST_F(format_ctd_test, empty_information)
// Create the dummy parser.
sharg::parser parser{"default", argv.size(), argv.data()};
parser.info.date = "December 01, 1994";
- parser.info.version = "1.1.1";
+ parser.info.version = "1.1.2-rc.1";
parser.info.man_page_title = "default_man_page_title";
parser.info.short_description = "A short description here.";

diff --git a/test/unit/detail/format_cwl_test.cpp b/test/unit/detail/format_cwl_test.cpp
index d2bb143..5591a88 100644
--- a/test/unit/detail/format_cwl_test.cpp
+++ b/test/unit/detail/format_cwl_test.cpp
@@ -20,12 +20,12 @@ TEST(format_cwl_test, empty_information)
parser.info.man_page_title = "default_man_page_title";
parser.info.short_description = "A short description here.";

- std::string expected_short = "inputs:\n"
- " {}\n"
- "outputs:\n"
- " {}\n"
- "label: default\n"
+ std::string expected_short = "label: default\n"
"doc: \"\"\n"
+ "inputs:\n"
+ " []\n"
+ "outputs:\n"
+ " []\n"
"cwlVersion: v1.2\n"
"class: CommandLineTool\n"
"baseCommand:\n"
@@ -75,7 +75,9 @@ TEST(format_cwl_test, full_information)
parser.info.examples.push_back("example");
parser.info.examples.push_back("example2");

- std::string expected_short = "inputs:\n"
+ std::string expected_short = "label: default\n"
+ "doc: \"description\\ndescription2\\n\"\n"
+ "inputs:\n"
" int:\n"
" doc: \"this is a int option. Default: 5. \"\n"
" type: long?\n"
@@ -87,9 +89,7 @@ TEST(format_cwl_test, full_information)
" inputBinding:\n"
" prefix: --jint\n"
"outputs:\n"
- " {}\n"
- "label: default\n"
- "doc: \"description\\ndescription2\\n\"\n"
+ " []\n"
"cwlVersion: v1.2\n"
"class: CommandLineTool\n"
"baseCommand:\n"
@@ -193,6 +193,8 @@ TEST(format_cwl_test, subparser)
sub_parser.info.examples.push_back("example2");

std::string expected_short =
+ "label: default-index\n"
+ "doc: \"\"\n"
"inputs:\n"
" int:\n"
" doc: \"this is a int option. Default: 5. \"\n"
@@ -251,8 +253,6 @@ TEST(format_cwl_test, subparser)
" type: Directory?\n"
" outputBinding:\n"
" glob: $(inputs.path05)\n"
- "label: default-index\n"
- "doc: \"\"\n"
"cwlVersion: v1.2\n"
"class: CommandLineTool\n"
"baseCommand:\n"
--
2.43.0

Loading
Loading