Add StringFormatter and formatAndJoin to stringutil
authorMark Abraham <mark.j.abraham@gmail.com>
Sun, 18 May 2014 10:48:46 +0000 (12:48 +0200)
committerRoland Schulz <roland@utk.edu>
Fri, 12 Dec 2014 19:57:19 +0000 (14:57 -0500)
The StringFormatter functor wraps formatString so that we can write
one-liners that take a container, format its objects to strings, and
join them with a separator into one string for e.g. logging output.

Re-implemented joinString in terms of formatAndJoin, by using an
IdentitfyFormatter, thus eliminating duplication of implementation.

Added some tests for joinString also.

Applied new functionality to reporting GPU status information.

Change-Id: I424543a1ca4e214c438cc39f104f087e7e70030a

src/gromacs/gmxlib/gmx_detect_hardware.cpp
src/gromacs/utility/stringutil.h
src/gromacs/utility/tests/stringutil.cpp

index eb1aa55b01008400d8df221916942499a4430c53..b2993963f1754c73cc990bfb6ff537bc2463473b 100644 (file)
@@ -154,27 +154,6 @@ static void print_gpu_detection_stats(FILE                 *fplog,
     }
 }
 
-/*! \brief Helper function for writing a string of GPU IDs.
- *
- * \param[in] ids  A container of integer GPU IDs
- * \return         A comma-separated string of GPU IDs */
-template <typename Container>
-static std::string makeGpuIdsString(const Container &ids)
-{
-    std::string output;
-
-    if (0 != ids.size())
-    {
-        typename Container::const_iterator it = ids.begin();
-        output += gmx::formatString("%d", *it);
-        for (++it; it != ids.end(); ++it)
-        {
-            output += gmx::formatString(",%d", *it);
-        }
-    }
-    return output;
-}
-
 /*! \brief Helper function for reporting GPU usage information
  * in the mdrun log file
  *
@@ -204,9 +183,9 @@ makeGpuUsageReport(const gmx_gpu_info_t *gpu_info,
     {
         // gpu_opt->cuda_dev_compatible is only populated during auto-selection
         std::string gpuIdsString =
-            makeGpuIdsString(gmx::ConstArrayRef<int>(gpu_opt->cuda_dev_compatible,
-                                                     gpu_opt->cuda_dev_compatible +
-                                                     gpu_opt->ncuda_dev_compatible));
+            formatAndJoin(gmx::constArrayRefFromArray(gpu_opt->cuda_dev_compatible,
+                                                      gpu_opt->ncuda_dev_compatible),
+                          ",", gmx::StringFormatter("%d"));
         bool bPluralGpus = gpu_opt->ncuda_dev_compatible > 1;
         output += gmx::formatString("%d compatible GPU%s %s present, with ID%s %s\n",
                                     gpu_opt->ncuda_dev_compatible,
@@ -222,7 +201,8 @@ makeGpuUsageReport(const gmx_gpu_info_t *gpu_info,
         {
             gpuIdsInUse.push_back(get_gpu_device_id(gpu_info, gpu_opt, i));
         }
-        std::string gpuIdsString = makeGpuIdsString(gpuIdsInUse);
+        std::string gpuIdsString =
+            formatAndJoin(gpuIdsInUse, ",", gmx::StringFormatter("%d"));
         int         numGpusInUse = gmx_count_gpu_dev_unique(gpu_info, gpu_opt);
         bool        bPluralGpus  = numGpusInUse > 1;
 
index b799608de6a009e38a975844ffaecb412cdd6fd7..5617fbb899a6503407d091f458d0a6db683026f4 100644 (file)
@@ -118,18 +118,88 @@ std::string stripString(const std::string &str);
  */
 std::string formatString(const char *fmt, ...);
 
-/*! \brief
- * Splits a string to whitespace separated tokens.
+/*! \brief Function object that wraps a call to formatString() that
+ * expects a single conversion argument, for use with algorithms. */
+class StringFormatter
+{
+    public:
+        /*! \brief Constructor
+         *
+         * \param[in] format The printf-style format string that will
+         *     be applied to convert values of type T to
+         *     string. Exactly one argument to the conversion
+         *     specification(s) in `format` is supported. */
+        explicit StringFormatter(const char *format) : format_(format)
+        {
+        }
+
+        //! Implements the formatting functionality
+        template <typename T>
+        std::string operator()(const T &value) const
+        {
+            return formatString(format_, value);
+        }
+
+    private:
+        //! Format string to use
+        const char *format_;
+};
+
+/*! \brief Function object to implement the same interface as
+ * `StringFormatter` to use with strings that should not be formatted
+ * further. */
+class IdentityFormatter
+{
+    public:
+        //! Implements the formatting non-functionality
+        std::string operator()(const std::string &value) const
+        {
+            return value;
+        }
+};
+
+/*! \brief Formats all the range as strings, and then joins them with
+ * a separator in between.
  *
- * \param[in] str  String to process.
- * \returns   \p str split into tokens at each whitespace sequence.
+ * \param[in] begin      Iterator the beginning of the range to join.
+ * \param[in] end        Iterator the end of the range to join.
+ * \param[in] separator  String to put in between the joined strings.
+ * \param[in] formatter  Function object to format the objects in
+ *     `container` as strings
+ * \returns   All objects in the range from `begin` to `end` formatted
+ *     as strings and concatenated with `separator` between each pair.
  * \throws    std::bad_alloc if out of memory.
+ */
+template <typename InputIterator, typename FormatterType>
+std::string formatAndJoin(InputIterator begin, InputIterator end, const char *separator, const FormatterType &formatter)
+{
+    std::string result;
+    const char *currentSeparator = "";
+    for (InputIterator i = begin; i != end; ++i)
+    {
+        result.append(currentSeparator);
+        result.append(formatter(*i));
+        currentSeparator = separator;
+    }
+    return result;
+}
+
+/*! \brief Formats all elements of the container as strings, and then
+ * joins them with a separator in between.
  *
- * This function works like `split` in Python, i.e., leading and trailing
- * whitespace is ignored, and consecutive whitespaces are treated as a single
- * separator.
+ * \param[in] container  Objects to join.
+ * \param[in] separator  String to put in between the joined strings.
+ * \param[in] formatter  Function object to format the objects in
+ *     `container` as strings
+ * \returns   All objects from `container` formatted as strings and
+ *     concatenated with `separator` between each pair.
+ * \throws    std::bad_alloc if out of memory.
  */
-std::vector<std::string> splitString(const std::string &str);
+template <typename ContainerType, typename FormatterType>
+std::string formatAndJoin(const ContainerType &container, const char *separator, const FormatterType &formatter)
+{
+    return formatAndJoin(container.begin(), container.end(), separator, formatter);
+}
 
 /*! \brief
  * Joins strings from a range with a separator in between.
@@ -145,16 +215,9 @@ template <typename InputIterator>
 std::string joinStrings(InputIterator begin, InputIterator end,
                         const char *separator)
 {
-    std::string result;
-    const char *currentSeparator = "";
-    for (InputIterator i = begin; i != end; ++i)
-    {
-        result.append(currentSeparator);
-        result.append(*i);
-        currentSeparator = separator;
-    }
-    return result;
+    return formatAndJoin(begin, end, separator, IdentityFormatter());
 }
+
 /*! \brief
  * Joins strings from a container with a separator in between.
  *
@@ -201,6 +264,19 @@ std::string concatenateStrings(const char * const (&sarray)[count])
     return concatenateStrings(sarray, count);
 }
 
+/*! \brief
+ * Splits a string to whitespace separated tokens.
+ *
+ * \param[in] str  String to process.
+ * \returns   \p str split into tokens at each whitespace sequence.
+ * \throws    std::bad_alloc if out of memory.
+ *
+ * This function works like `split` in Python, i.e., leading and trailing
+ * whitespace is ignored, and consecutive whitespaces are treated as a single
+ * separator.
+ */
+std::vector<std::string> splitString(const std::string &str);
+
 /*! \brief
  * Replace all occurrences of a string with another string.
  *
index dcdc5cba0c7553cf641afc1faf01f724e97c482f..068df9246845c5e86be08216470f59bcb261f4f1 100644 (file)
@@ -53,6 +53,8 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "gromacs/utility/arrayref.h"
+
 #include "testutils/refdata.h"
 #include "testutils/stringtest.h"
 
@@ -140,6 +142,45 @@ TEST(FormatStringTest, HandlesLongStrings)
     EXPECT_EQ("x10", longString.substr(1999));
 }
 
+/********************************************************************
+ * Tests for StringFormatter
+ */
+
+TEST(StringFormatterTest, HandlesBasicFormatting)
+{
+    int value = 103;
+    EXPECT_EQ("103", gmx::StringFormatter("%d") (value));
+    EXPECT_EQ("null", gmx::StringFormatter("null") (value));
+}
+
+/********************************************************************
+ * Tests for formatAndJoin
+ */
+
+TEST(formatAndJoinTest, Works)
+{
+    const char * const words[] = { "The", "quick", "brown", "fox" };
+    EXPECT_EQ("The       .quick     .brown     .fox       ",
+              gmx::formatAndJoin(gmx::ConstArrayRef<const char *>(words), ".",
+                                 gmx::StringFormatter("%-10s")));
+
+    const int values[] = { 0, 1, 4 };
+    EXPECT_EQ("0,1,4", gmx::formatAndJoin(gmx::ConstArrayRef<int>(values), ",",
+                                          gmx::StringFormatter("%d")));
+}
+
+/********************************************************************
+ * Tests for joinStrings
+ */
+
+TEST(JoinStringsTest, Works)
+{
+    const char * const               words[] = { "The", "quick", "brown", "fox" };
+    gmx::ConstArrayRef<const char *> refToWords(words);
+    EXPECT_EQ("The; quick; brown; fox", gmx::joinStrings(refToWords.begin(), refToWords.end(), "; "));
+    EXPECT_EQ("The-quick-brown-fox", gmx::joinStrings(refToWords, "-"));
+}
+
 /********************************************************************
  * Tests for concatenateStrings()
  */