You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2018/08/06 20:27:44 UTC

[trafficserver] branch master updated: BWF: Add "FirstOf" for better handling of printing alternates for null strings.

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new f2ac4a8  BWF: Add "FirstOf" for better handling of printing alternates for null strings.
f2ac4a8 is described below

commit f2ac4a8a665a4071dcbe8fc0ff7ae21285a6e18e
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Fri Aug 3 12:46:00 2018 -0500

    BWF: Add "FirstOf" for better handling of printing alternates for null strings.
---
 .../internal-libraries/buffer-writer.en.rst        | 50 ++++++++++++++++++----
 lib/ts/bwf_std_format.h                            | 31 ++++++++++++++
 lib/ts/unit-tests/test_BufferWriterFormat.cc       | 31 ++++++++++++++
 3 files changed, 103 insertions(+), 9 deletions(-)

diff --git a/doc/developer-guide/internal-libraries/buffer-writer.en.rst b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
index adbf871..18530e1 100644
--- a/doc/developer-guide/internal-libraries/buffer-writer.en.rst
+++ b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
@@ -762,10 +762,41 @@ These are the existing format classes in header file ``bfw_std_format.h``. All a
 
 .. class:: Errno
 
-   Formating for :code:`errno`.
+   Formatting for :code:`errno`. Generically the formatted output is the short name, the description,
+   and the numeric value. A format type of ``d`` will generate just the numeric value, while a format
+   type of ``s`` will generate just the short name and description.
 
    .. function:: Errno(int errno)
 
+      Initialize the instance with the error value :arg:`errno`.
+
+.. function:: template < typename ... Args > FirstOf(Args && ... args)
+
+   Print the first non-empty string in an argument list. All arguments must be convertible to
+   :code:`std::string_view`.
+
+   By far the most common case is the two argument case used to print a special string if the base
+   string is null or empty. For instance, something like this::
+
+      w.print("{}", name != nullptr ? name : "<void>")
+
+   This could also be done like::
+
+      w.print("{}", ts::bwf::FirstOf(name, "<void>"));
+
+   In addition, if the first argument is a local variable that exists only to do the empty check, that
+   variable can eliminated entirely. E.g.::
+
+      const char * name = thing.get_name();
+      w.print("{}", name != nullptr ? name : "<void>")
+
+   can be simplified to
+
+      w.print("{}", ts::bwf::FirstOf(thing.get_name(), "<void>"));
+
+   In general avoiding ternary operators in the print argument list makes the code cleaner and
+   easier to understand.
+
 .. class:: Date
 
    Date formatting in the :code:`strftime` style.
@@ -782,17 +813,18 @@ These are the existing format classes in header file ``bfw_std_format.h``. All a
       Therefore if the current time is to be printed the default constructor can be used.
 
    When used the format specification can take an extention of "local" which formats the time as
-   local time. Otherwise it is GMT.
-   ``w.print("{}", Date("%H:%M"));`` will print the hour and minute as GMT values. ``w.print("{::local}", Date("%H:%M"));`` will
-   When used the format specification can take an extention of "local" which formats the time as local time. Otherwise it is GMT.
-   ``w.print("{}", Date("%H:%M"));`` will print the hour and minute as GMT values. ``w.print("{::local}", Date("%H:%M"));`` will
-   print the hour and minute in the local time zone. ``w.print("{::gmt}"), ...);`` will output in GMT if additional explicitness is
-   desired.
+   local time. Otherwise it is GMT. ``w.print("{}", Date("%H:%M"));`` will print the hour and minute
+   as GMT values. ``w.print("{::local}", Date("%H:%M"));`` will When used the format specification
+   can take an extention of "local" which formats the time as local time. Otherwise it is GMT.
+   ``w.print("{}", Date("%H:%M"));`` will print the hour and minute as GMT values.
+   ``w.print("{::local}", Date("%H:%M"));`` will print the hour and minute in the local time zone.
+   ``w.print("{::gmt}"), ...);`` will output in GMT if additional explicitness is desired.
 
 .. class:: OptionalAffix
 
-   Affix support for printing optional strings. This enables printing a string such the affixes are printed only if the string is not
-   empty. An empty string (or :code:`nullptr`) yields no output. A common situation in which is this is useful is code like ::
+   Affix support for printing optional strings. This enables printing a string such the affixes are
+   printed only if the string is not empty. An empty string (or :code:`nullptr`) yields no output. A
+   common situation in which is this is useful is code like ::
 
       printf("%s%s", data ? data : "", data ? " " : "");
 
diff --git a/lib/ts/bwf_std_format.h b/lib/ts/bwf_std_format.h
index d90da57..6c7d9d4 100644
--- a/lib/ts/bwf_std_format.h
+++ b/lib/ts/bwf_std_format.h
@@ -24,6 +24,7 @@
 #pragma once
 
 #include <atomic>
+#include <array>
 #include <string_view>
 #include <ts/TextView.h>
 #include <ts/BufferWriterForward.h>
@@ -66,6 +67,36 @@ namespace bwf
     Date(std::string_view fmt = DEFAULT_FORMAT);
   };
 
+  namespace detail
+  {
+    // Special case conversions - these handle nullptr because the @c std::string_view spec is stupid.
+    inline std::string_view FirstOfConverter(std::nullptr_t) { return std::string_view{}; }
+    inline std::string_view
+    FirstOfConverter(char const *s)
+    {
+      return std::string_view{s ? s : ""};
+    }
+    // Otherwise do any compliant conversion.
+    template <typename T>
+    std::string_view
+    FirstOfConverter(T &&t)
+    {
+      return t;
+    }
+  } // namespace detail
+  /// Print the first of a list of strings that is not an empty string.
+  /// All arguments must be convertible to @c std::string.
+  template <typename... Args>
+  std::string_view
+  FirstOf(Args &&... args)
+  {
+    std::array<std::string_view, sizeof...(args)> strings{{detail::FirstOfConverter(args)...}};
+    for (auto &s : strings) {
+      if (!s.empty())
+        return s;
+    }
+    return std::string_view{};
+  };
   /** For optional printing strings along with suffixes and prefixes.
    *  If the wrapped string is null or empty, nothing is printed. Otherwise the prefix, string,
    *  and suffix are printed. The default are a single space for suffix and nothing for the prefix.
diff --git a/lib/ts/unit-tests/test_BufferWriterFormat.cc b/lib/ts/unit-tests/test_BufferWriterFormat.cc
index 0f229dc..49e6933 100644
--- a/lib/ts/unit-tests/test_BufferWriterFormat.cc
+++ b/lib/ts/unit-tests/test_BufferWriterFormat.cc
@@ -22,6 +22,7 @@
  */
 
 #include "catch.hpp"
+#include "../../../tests/include/catch.hpp"
 #include <chrono>
 #include <iostream>
 #include <ts/BufferWriter.h>
@@ -535,6 +536,36 @@ TEST_CASE("bwstring std formats", "[libts][bwprint]")
 
   // Verify these compile and run, not really much hope to check output.
   w.reset().print("|{}|   |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y"));
+
+  w.reset().print("name = {}", ts::bwf::FirstOf("Persia"));
+  REQUIRE(w.view() == "name = Persia");
+  w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave"));
+  REQUIRE(w.view() == "name = Persia");
+  w.reset().print("name = {}", ts::bwf::FirstOf("", "Evil Dave"));
+  REQUIRE(w.view() == "name = Evil Dave");
+  w.reset().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave"));
+  REQUIRE(w.view() == "name = Evil Dave");
+  w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif"));
+  REQUIRE(w.view() == "name = Persia");
+  w.reset().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif"));
+  REQUIRE(w.view() == "name = Persia");
+  w.reset().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif"));
+  REQUIRE(w.view() == "name = Leif");
+
+  const char *empty{nullptr};
+  std::string s1{"Persia"};
+  std::string_view s2{"Evil Dave"};
+  ts::TextView s3{"Leif"};
+  w.reset().print("name = {}", ts::bwf::FirstOf(empty, s3));
+  REQUIRE(w.view() == "name = Leif");
+  w.reset().print("name = {}", ts::bwf::FirstOf(s2, s3));
+  REQUIRE(w.view() == "name = Evil Dave");
+  w.reset().print("name = {}", ts::bwf::FirstOf(s1, empty, s2));
+  REQUIRE(w.view() == "name = Persia");
+  w.reset().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3));
+  REQUIRE(w.view() == "name = Evil Dave");
+  w.reset().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1));
+  REQUIRE(w.view() == "name = Leif");
 }
 
 // Normally there's no point in running the performance tests, but it's worth keeping the code