changeset 530:7cd7b2cdf923

Common: import to_int / to_uint
author David Demelier <markand@malikania.fr>
date Thu, 16 Nov 2017 23:51:01 +0100
parents b3a0f61a35fe
children d71c59eb04c3
files irccdctl/main.cpp irccdctl/rule_add_cli.cpp irccdctl/rule_edit_cli.cpp irccdctl/rule_move_cli.cpp irccdctl/server_connect_cli.cpp libcommon/irccd/string_util.hpp libirccd/irccd/config.cpp tests/util/main.cpp
diffstat 8 files changed, 145 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/main.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/irccdctl/main.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -134,7 +134,7 @@
     if ((it = sc.find("port")) == sc.end())
         throw std::invalid_argument("missing port parameter");
 
-    port = string_util::to_number<std::uint16_t>(it->value());
+    port = string_util::to_uint<std::uint16_t>(it->value());
 
     if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
 #if defined(HAVE_SSL)
@@ -310,8 +310,7 @@
     if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end())
         throw std::invalid_argument("missing port argument (-p or --port)");
 
-    auto port = string_util::to_number<std::uint16_t>(it->second);
-
+    auto port = string_util::to_uint<std::uint16_t>(it->second);
 
     return std::make_unique<ip_connection>(service, host, port);
 }
--- a/irccdctl/rule_add_cli.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/irccdctl/rule_add_cli.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -73,9 +73,9 @@
 
     // Index.
     if (result.count("-i") > 0)
-        json["index"] = string_util::to_number<unsigned>(result.find("-i")->second);
+        json["index"] = string_util::to_uint<unsigned>(result.find("-i")->second);
     if (result.count("--index") > 0)
-        json["index"] = string_util::to_number<unsigned>(result.find("--index")->second);
+        json["index"] = string_util::to_uint<unsigned>(result.find("--index")->second);
 
     // And action.
     if (copy[0] != "accept" && copy[0] != "drop")
--- a/irccdctl/rule_edit_cli.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/irccdctl/rule_edit_cli.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -94,7 +94,7 @@
     }
 
     // Index.
-    json["index"] = string_util::to_number<unsigned>(copy[0]);
+    json["index"] = string_util::to_uint<unsigned>(copy[0]);
 
     request(ctl, json);
 }
--- a/irccdctl/rule_move_cli.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/irccdctl/rule_move_cli.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/string_util.hpp>
+
 #include "rule_move_cli.hpp"
 
 namespace irccd {
@@ -32,15 +34,8 @@
     if (args.size() < 2)
         throw std::invalid_argument("rule-move requires 2 arguments");
 
-    int from = 0;
-    int to = 0;
-
-    try {
-        from = std::stoi(args[0]);
-        to = std::stoi(args[1]);
-    } catch (...) {
-        throw std::invalid_argument("invalid number");
-    }
+    int from = string_util::to_int<int>(args[0]);
+    int to = string_util::to_int<int>(args[1]);
 
     request(ctl, {
         { "command",    "rule-move" },
--- a/irccdctl/server_connect_cli.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/irccdctl/server_connect_cli.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -69,12 +69,8 @@
         { "host", copy[1] }
     });
 
-    if (copy.size() == 3) {
-        if (!string_util::is_int(copy[2]))
-            throw std::invalid_argument("invalid port number");
-
-        object["port"] = std::stoi(copy[2]);
-    }
+    if (copy.size() == 3)
+        object["port"] = string_util::to_int(copy[2]);
 
     if (result.count("-S") > 0 || result.count("--ssl-verify") > 0)
         object["sslVerify"] = true;
--- a/libcommon/irccd/string_util.hpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/libcommon/irccd/string_util.hpp	Thu Nov 16 23:51:01 2017 +0100
@@ -26,12 +26,15 @@
 
 #include "sysconfig.hpp"
 
+#include <cassert>
 #include <ctime>
 #include <initializer_list>
 #include <limits>
 #include <regex>
 #include <sstream>
+#include <stdexcept>
 #include <string>
+#include <type_traits>
 #include <unordered_map>
 
 #include <boost/format.hpp>
@@ -366,40 +369,99 @@
 }
 
 /**
- * Try to convert the string into number.
- *
- * This function will try to convert the string to number in the limits of T.
- *
- * If the string is not a number or if the converted value is out of range than
- * specified boundaries, an exception is thrown.
- *
- * By default, the function will use numeric limits from T.
- *
- * \param number the string to convert
- * \param min the minimum (defaults to T minimum)
- * \param max the maximum (defaults to T maximum)
- * \return the converted value
- * \throw std::invalid_argument if number is not a string
- * \throw std::out_of_range if the number is not between min and max
+ * \cond HIDDEN_SYMBOLS
  */
+
+namespace detail {
+
+inline std::invalid_argument make_invalid_argument(const std::string& str)
+{
+    std::ostringstream oss;
+
+    oss << "invalid number '" << str << "'";
+
+    return std::invalid_argument(oss.str());
+}
+
 template <typename T>
-inline T to_number(const std::string& number,
-                   T min = std::numeric_limits<T>::min(),
-                   T max = std::numeric_limits<T>::max())
+inline std::out_of_range make_out_of_range(const std::string& str, T min, T max)
 {
-    static_assert(std::is_integral<T>::value, "T must be integer type");
+    std::ostringstream oss;
+
+    oss << "number '" << str << "' is out of range ";
+    oss << min << ".." << max;
+
+    return std::out_of_range(oss.str());
+}
+
+} // !detail
+
+/**
+ * \endcond
+ */
 
-    std::conditional_t<std::is_unsigned<T>::value, unsigned long long, long long> value;
+/**
+ * Convert the given string into a signed integer.
+ *
+ * \param str the string to convert
+ * \param min the minimum value allowed
+ * \param max the maximum value allowed
+ * \throw std::invalid_argument if the number was not parsed
+ * \throw std::out_or_range if the argument is out of the specified range
+ */
+template <typename T = int>
+T to_int(const std::string& str, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
+{
+    static_assert(std::is_signed<T>::value, "must be signed");
+
+    char* end;
+    auto v = std::strtoll(str.c_str(), &end, 10);
+
+    if (*end != '\0')
+        throw detail::make_invalid_argument(str);
+    if (v < min || v > max)
+        throw detail::make_out_of_range(str, min, max);
 
-    if (std::is_unsigned<T>::value)
-        value = std::stoull(number);
-    else
-        value = std::stoll(number);
+    return static_cast<T>(v);
+}
 
-    if (value < min || value > max)
-        throw std::out_of_range("out of range");
+/**
+ * Convert the given string into an unsigned integer.
+ *
+ * In contrast to the [std::strtoull][strtoull] function, this functions
+ * verifies if the string starts with minus sign and throws an exception if any.
+ *
+ * Note, for this you need to have a trimmed string which contains no leading
+ * whitespaces.
+ *
+ * \pre string must be trimmed
+ * \param str the string to convert
+ * \param min the minimum value allowed
+ * \param max the maximum value allowed
+ * \throw std::invalid_argument if the number was not parsed
+ * \throw std::out_or_range if the argument is out of the specified range
+ *
+ * [strtoull]: http://en.cppreference.com/w/cpp/string/byte/strtoul
+ */
+template <typename T = unsigned>
+T to_uint(const std::string& str, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
+{
+    static_assert(std::is_unsigned<T>::value, "must be unsigned");
 
-    return static_cast<T>(value);
+    assert(str.empty() || !std::isspace(str[0]));
+
+    if (str.size() > 0U && str[0] == '-')
+        throw detail::make_out_of_range(str, min, max);
+
+    char* end;
+    auto v = std::strtoull(str.c_str(), &end, 10);
+
+    if (*end != '\0')
+        throw detail::make_invalid_argument(str);
+    if (v < min || v > max)
+        throw detail::make_out_of_range(str, min, max);
+
+    return v;
 }
 
 } // !string_util
--- a/libirccd/irccd/config.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/libirccd/irccd/config.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -133,7 +133,7 @@
         throw std::invalid_argument("transport: missing 'port' parameter");
 
     try {
-        port = string_util::to_number<std::uint16_t>(it->value());
+        port = string_util::to_uint<std::uint16_t>(it->value());
     } catch (const std::exception&) {
         throw std::invalid_argument(string_util::sprintf("transport: invalid port number: %s", it->value()));
     }
@@ -343,13 +343,13 @@
     // Reconnect and ping timeout
     try {
         if ((it = sc.find("port")) != sc.end())
-            sv->set_port(string_util::to_number<std::uint16_t>(it->value()));
+            sv->set_port(string_util::to_uint<std::uint16_t>(it->value()));
         if ((it = sc.find("reconnect-tries")) != sc.end())
-            sv->set_reconnect_tries(string_util::to_number<std::int8_t>(it->value()));
+            sv->set_reconnect_tries(string_util::to_int<std::int8_t>(it->value()));
         if ((it = sc.find("reconnect-timeout")) != sc.end())
-            sv->set_reconnect_delay(string_util::to_number<std::uint16_t>(it->value()));
+            sv->set_reconnect_delay(string_util::to_uint<std::uint16_t>(it->value()));
         if ((it = sc.find("ping-timeout")) != sc.end())
-            sv->set_ping_timeout(string_util::to_number<std::uint16_t>(it->value()));
+            sv->set_ping_timeout(string_util::to_uint<std::uint16_t>(it->value()));
     } catch (const std::exception&) {
         log::warning(string_util::sprintf("server %s: invalid number for %s: %s",
             sv->name(), it->key(), it->value()));
--- a/tests/util/main.cpp	Thu Nov 16 23:31:28 2017 +0100
+++ b/tests/util/main.cpp	Thu Nov 16 23:51:01 2017 +0100
@@ -417,47 +417,58 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * string_util::to_number function
+ * string_util::to_int function
  * ------------------------------------------------------------------
  */
 
-BOOST_AUTO_TEST_SUITE(to_number)
+BOOST_AUTO_TEST_SUITE(to_int)
 
-BOOST_AUTO_TEST_CASE(correct)
+BOOST_AUTO_TEST_CASE(signed_to_int)
 {
-    /* unsigned */
-    BOOST_REQUIRE_EQUAL(50u, string_util::to_number<std::uint8_t>("50"));
-    BOOST_REQUIRE_EQUAL(5000u, string_util::to_number<std::uint16_t>("5000"));
-    BOOST_REQUIRE_EQUAL(50000u, string_util::to_number<std::uint32_t>("50000"));
-    BOOST_REQUIRE_EQUAL(500000u, string_util::to_number<std::uint64_t>("500000"));
+    BOOST_TEST(to_int("10")                     == 10);
+    BOOST_TEST(to_int<std::int8_t>("-10")       == -10);
+    BOOST_TEST(to_int<std::int8_t>("10")        == 10);
+    BOOST_TEST(to_int<std::int16_t>("-1000")    == -1000);
+    BOOST_TEST(to_int<std::int16_t>("1000")     == 1000);
+    BOOST_TEST(to_int<std::int32_t>("-1000")    == -1000);
+    BOOST_TEST(to_int<std::int32_t>("1000")     == 1000);
+}
 
-    /* signed */
-    BOOST_REQUIRE_EQUAL(-50, string_util::to_number<std::int8_t>("-50"));
-    BOOST_REQUIRE_EQUAL(-500, string_util::to_number<std::int16_t>("-500"));
-    BOOST_REQUIRE_EQUAL(-5000, string_util::to_number<std::int32_t>("-5000"));
-    BOOST_REQUIRE_EQUAL(-50000, string_util::to_number<std::int64_t>("-50000"));
+BOOST_AUTO_TEST_CASE(signed_to_int64)
+{
+    BOOST_TEST(to_int<std::int64_t>("-9223372036854775807") == -9223372036854775807LL);
+    BOOST_TEST(to_int<std::int64_t>("9223372036854775807") == 9223372036854775807LL);
 }
 
-BOOST_AUTO_TEST_CASE(incorrect)
+BOOST_AUTO_TEST_CASE(unsigned_to_uint)
 {
-    /* unsigned */
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint16_t>("80000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("-125"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint16_t>("-25000"), std::out_of_range);
+    BOOST_TEST(to_uint("10")                    == 10U);
+    BOOST_TEST(to_uint<std::uint8_t>("10")       == 10U);
+    BOOST_TEST(to_uint<std::uint16_t>("1000")    == 1000U);
+    BOOST_TEST(to_uint<std::uint32_t>("1000")    == 1000U);
+}
+
+BOOST_AUTO_TEST_CASE(unsigned_to_uint64)
+{
+    BOOST_TEST(to_uint<std::uint64_t>("18446744073709551615") == 18446744073709551615ULL);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
 
-    /* signed */
-    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::int16_t>("80000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("-300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::int16_t>("-80000"), std::out_of_range);
+BOOST_AUTO_TEST_SUITE(errors)
 
-    /* not numbers */
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("nonono"), std::invalid_argument);
+BOOST_AUTO_TEST_CASE(invalid_argument)
+{
+    BOOST_REQUIRE_THROW(to_int("plopation"), std::invalid_argument);
+    BOOST_REQUIRE_THROW(to_uint("plopation"), std::invalid_argument);
+}
 
-    /* custom ranges */
-    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("50", 0, 10), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("-50", -10, 10), std::out_of_range);
+BOOST_AUTO_TEST_CASE(out_of_range)
+{
+    BOOST_REQUIRE_THROW(to_int<std::int8_t>("1000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(to_int<std::int8_t>("-1000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(to_uint<std::uint8_t>("1000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(to_uint<std::uint8_t>("-1000"), std::out_of_range);
 }
 
 BOOST_AUTO_TEST_SUITE_END()