From 0578e7ea036bb7f04468c0aebde3269712bf61cf Mon Sep 17 00:00:00 2001
From: "Jeroen F.J. Laros" <jlaros@fixedpoint.nl>
Date: Mon, 6 Feb 2023 21:48:39 +0100
Subject: [PATCH] Added stream insertion operator.

---
 api/Stream.h                                | 31 +++++++++++-
 test/CMakeLists.txt                         |  1 +
 test/src/Stream/test_insertion_operator.cpp | 53 +++++++++++++++++++++
 3 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 test/src/Stream/test_insertion_operator.cpp

diff --git a/api/Stream.h b/api/Stream.h
index e81c71ba..b20fe548 100644
--- a/api/Stream.h
+++ b/api/Stream.h
@@ -128,6 +128,35 @@ class Stream : public Print
 
 #undef NO_IGNORE_CHAR
 
+// Structure to store a value and a modifier.
+template <class T>
+struct Format {
+  T data;
+  int modifier;
+};
+
+// Helper function that creates a `Format` object.
+template <class T>
+Format<T> format(T const data, int const modifier) {
+  Format<T> fmt {data, modifier};
+  return fmt;
+}
+
+// Stream insertion operator for plain data types.
+template <class T>
+Stream& operator <<(Stream& stream, T const data) {
+  stream.print(data);
+  return stream;
+}
+
+// Stream insertion operator with modifiers (e.g., BIN, HEX, number of digits, etc.).
+template <class T>
+Stream& operator <<(Stream& stream, Format<T> const& parameters) {
+  stream.print(parameters.data, parameters.modifier);
+  return stream;
+}
+
 }
 
-using arduino::Stream;
\ No newline at end of file
+using arduino::Stream;
+using arduino::operator <<;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 0f6200b2..1431a427 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -55,6 +55,7 @@ set(TEST_SRCS
   src/Stream/test_find.cpp
   src/Stream/test_findUntil.cpp
   src/Stream/test_getTimeout.cpp
+  src/Stream/test_insertion_operator.cpp
   src/Stream/test_parseFloat.cpp
   src/Stream/test_parseInt.cpp
   src/Stream/test_readBytes.cpp
diff --git a/test/src/Stream/test_insertion_operator.cpp b/test/src/Stream/test_insertion_operator.cpp
new file mode 100644
index 00000000..7de74705
--- /dev/null
+++ b/test/src/Stream/test_insertion_operator.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 Arduino.  All rights reserved.
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include <catch.hpp>
+
+#include <StreamMock.h>
+
+/**************************************************************************************
+ * TEST CODE
+ **************************************************************************************/
+
+TEST_CASE ("Testing 'Format' initialisation", "[Stream-insertion-operator-01]") {
+  arduino::Format<char> fmt {'a', 2};
+  REQUIRE(fmt.data == 'a');
+  REQUIRE(fmt.modifier == 2);
+}
+
+TEST_CASE ("Testing 'format' helper function", "[Stream-insertion-operator-02]") {
+  arduino::Format<char> fmt {arduino::format('a', 2)};
+  REQUIRE(fmt.data == 'a');
+  REQUIRE(fmt.modifier == 2);
+}
+
+TEST_CASE ("Testing basic insertion operator", "[Stream-insertion-operator-03]") {
+  StreamMock mock;
+  mock << 'a' << 12 << 'b' << 34;  // Note we cannot test C strings because `StreamMock` has its own << operator.
+  REQUIRE(mock.available() == 6);
+
+  char buf[10] {};
+  mock.readBytes(buf, 6);
+  REQUIRE(not strncmp(buf, "a12b34", 6));
+}
+
+TEST_CASE ("Testing insertion operator with modifiers", "[Stream-insertion-operator-04]") {
+  StreamMock mock;
+  mock << arduino::format(1.2, 4);
+  REQUIRE(mock.available() == 6);
+
+  char buf[10] {};
+  mock.readBytes(buf, 6);
+  REQUIRE(not strncmp(buf, "1.2000", 6));
+
+  mock << arduino::format(12, BIN);
+  REQUIRE(mock.available() == 4);
+
+  mock.readBytes(buf, 4);
+  REQUIRE(not strncmp(buf, "1100", 4));
+}