From 13a5af70aebd85f5c21cba346f22eadd98fe333c Mon Sep 17 00:00:00 2001
From: Maxime Coste <frrrwww@gmail.com>
Date: Mon, 30 Mar 2015 23:05:24 +0100
Subject: [PATCH] Add a format function for printf like formatting

---
 src/string.cc     | 42 ++++++++++++++++++++++++++++++++++++++++++
 src/string.hh     | 22 ++++++++++++++++++++++
 src/unit_tests.cc |  2 ++
 3 files changed, 66 insertions(+)

diff --git a/src/string.cc b/src/string.cc
index df7bfa68..ae454264 100644
--- a/src/string.cc
+++ b/src/string.cc
@@ -205,4 +205,46 @@ Vector<StringView> wrap_lines(StringView text, CharCount max_width)
     return lines;
 }
 
+String format(StringView fmt, ArrayView<const StringView> params)
+{
+    ByteCount size = fmt.length();
+    for (auto& s : params) size += s.length();
+    String res;
+    res.reserve(size);
+
+    int implicitIndex = 0;
+    for (auto it = fmt.begin(), end = fmt.end(); it != end;)
+    {
+        auto opening = std::find(it, end, '{');
+        res += StringView{it, opening};
+        if (opening == end)
+            break;
+
+        if (opening != it && res.back() == '\\')
+        {
+            res.back() = '{';
+            it = opening + 1;
+        }
+        else
+        {
+            auto closing = std::find(it, end, '}');
+            if (closing == end)
+                throw runtime_error("Format string error, unclosed '{'");
+            int index;
+            if (closing == opening + 1)
+                index = implicitIndex;
+            else
+                index = str_to_int({opening+1, closing});
+
+            if (index >= params.size())
+                throw runtime_error("Format string parameter index too big");
+
+            res += params[index];
+            implicitIndex = index+1;
+            it = closing+1;
+        }
+    }
+    return res;
+}
+
 }
diff --git a/src/string.hh b/src/string.hh
index 5e90e98d..41dbccb8 100644
--- a/src/string.hh
+++ b/src/string.hh
@@ -5,6 +5,7 @@
 #include "utf8.hh"
 #include "hash.hh"
 #include "vector.hh"
+#include "array_view.hh"
 
 #include <string>
 #include <climits>
@@ -276,6 +277,27 @@ String expand_tabs(StringView line, CharCount tabstop, CharCount col = 0);
 
 Vector<StringView> wrap_lines(StringView text, CharCount max_width);
 
+namespace detail
+{
+
+template<typename T> using IsString = std::is_convertible<T, StringView>;
+
+template<typename T, class = typename std::enable_if<!IsString<T>::value>::type>
+String format_param(const T& val) { return to_string(val); }
+
+template<typename T, class = typename std::enable_if<IsString<T>::value>::type>
+StringView format_param(const T& val) { return val; }
+
+}
+
+String format(StringView fmt, ArrayView<const StringView> params);
+
+template<typename... Types>
+String format(StringView fmt, Types... params)
+{
+    return format(fmt, ArrayView<const StringView>{{detail::format_param(params)...}});
+}
+
 }
 
 #endif // string_hh_INCLUDED
diff --git a/src/unit_tests.cc b/src/unit_tests.cc
index ec9bb64c..9fb1bae2 100644
--- a/src/unit_tests.cc
+++ b/src/unit_tests.cc
@@ -137,6 +137,8 @@ void test_string()
     kak_assert(subsequence_match("tchou kanaky", "knk"));
     kak_assert(subsequence_match("tchou kanaky", "tchou kanaky"));
     kak_assert(not subsequence_match("tchou kanaky", "tchou  kanaky"));
+
+    kak_assert(format("Youhou {1} {} {0} \\{}", 10, "hehe", 5) == "Youhou hehe 5 10 {}");
 }
 
 void test_keys()