lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] master bb64784 013/156: Add a helper allowing to int


From: Greg Chicares
Subject: [lmi-commits] [lmi] master bb64784 013/156: Add a helper allowing to interpolate variables in strings
Date: Tue, 30 Jan 2018 17:21:51 -0500 (EST)

branch: master
commit bb647843dd723c611405752b56e03b13d091aa85
Author: Vadim Zeitlin <address@hidden>
Commit: Vadim Zeitlin <address@hidden>

    Add a helper allowing to interpolate variables in strings
    
    This is convenient to use when generating illustrations as inserting the
    variable references directly in the string is much less verbose than
    concatenating many strings together.
---
 Makefile.am                 |  8 ++++
 interpolate_string.cpp      | 96 +++++++++++++++++++++++++++++++++++++++++++++
 interpolate_string.hpp      | 45 +++++++++++++++++++++
 interpolate_string_test.cpp | 70 +++++++++++++++++++++++++++++++++
 ledger_pdf_generator_wx.cpp | 16 ++++----
 objects.make                |  7 ++++
 6 files changed, 234 insertions(+), 8 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 288754b..a100537 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -110,6 +110,7 @@ TESTS = \
     test_ieee754 \
     test_input_seq \
     test_input \
+    test_interpolate_string \
     test_irc7702a \
     test_istream_to_string \
     test_loads \
@@ -328,6 +329,7 @@ liblmi_common_sources = \
     input_sequence_parser.cpp \
     input_xml_io.cpp \
     interest_rates.cpp \
+    interpolate_string.cpp \
     ledger.cpp \
     ledger_base.cpp \
     ledger_invariant.cpp \
@@ -778,6 +780,11 @@ test_input_LDADD = \
   $(BOOST_LIBS) \
   $(XMLWRAPP_LIBS)
 
+test_interpolate_string_SOURCES = \
+  $(common_test_objects) \
+  interpolate_string.cpp \
+  interpolate_string_test.cpp
+
 test_irc7702a_SOURCES = \
   $(common_test_objects) \
   ihs_irc7702a.cpp \
@@ -1170,6 +1177,7 @@ noinst_HEADERS = \
     input_sequence_interval.hpp \
     input_sequence_parser.hpp \
     interest_rates.hpp \
+    interpolate_string.hpp \
     istream_to_string.hpp \
     ledger.hpp \
     ledger_base.hpp \
diff --git a/interpolate_string.cpp b/interpolate_string.cpp
new file mode 100644
index 0000000..dae1f02
--- /dev/null
+++ b/interpolate_string.cpp
@@ -0,0 +1,96 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "interpolate_string.hpp"
+
+#include "alert.hpp"
+
+#include <stdexcept>
+
+std::string interpolate_string
+    (char const* s
+    ,std::function<std::string (std::string const&)> const& lookup
+    )
+{
+    std::string out;
+
+    // This is probably not going to be enough as replacements of the
+    // interpolated variables tend to be longer than the variables names
+    // themselves, but it's difficult to estimate the resulting string length
+    // any better than this.
+    out.reserve(strlen(s));
+
+    for(char const* p = s; *p; ++p)
+        {
+        if(p[0] == '$' && p[1] == '{')
+            {
+            std::string name;
+            for(p += 2;; ++p)
+                {
+                if(*p == '\0')
+                    {
+                    alarum()
+                        << "Unmatched opening brace at position "
+                        << (p - s - 1 - name.length())
+                        << std::flush
+                        ;
+                    }
+
+                if(*p == '}')
+                    {
+                    // We don't check here if name is not empty, as there is no
+                    // real reason to do it. Empty variable name may seem
+                    // strange, but why not allow using "${}" to insert
+                    // something into the interpolated string, after all?
+                    out += lookup(name);
+                    break;
+                    }
+
+                if(p[0] == '$' && p[1] == '{')
+                    {
+                    // We don't allow nested interpolations, so this can only
+                    // be result of an error, e.g. a forgotten '}' before it.
+                    alarum()
+                        << "Unexpected nested interpolation at position "
+                        << (p - s + 1)
+                        << " (outer interpolation starts at "
+                        << (p - s - 1 - name.length())
+                        << ")"
+                        << std::flush
+                        ;
+                    }
+
+                // We don't impose any restrictions on the kind of characters
+                // that can occur in the variable names neither because there
+                // just doesn't seem to be anything to gain from it.
+                name += *p;
+                }
+            }
+        else
+            {
+            out += *p;
+            }
+        }
+
+    return out;
+}
diff --git a/interpolate_string.hpp b/interpolate_string.hpp
new file mode 100644
index 0000000..66c7445
--- /dev/null
+++ b/interpolate_string.hpp
@@ -0,0 +1,45 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef interpolate_string_hpp
+#define interpolate_string_hpp
+
+#include "config.hpp"
+
+#include <functional>
+#include <string>
+
+/// Interpolate string containing embedded variable references.
+///
+/// Return the input string after replacing all ${variable} references in it
+/// with the value of the variable as returned by the provided function.
+///
+/// To allow embedding literal "${" fragment into the returned string, create a
+/// pseudo-variable returning these characters as its expansion, there is no
+/// built-in way to escape these characters.
+///
+/// Throw if the lookup function throws or if the string uses invalid syntax.
+std::string interpolate_string
+    (char const* s
+    ,std::function<std::string (std::string const&)> const& lookup
+    );
+
+#endif // interpolate_string_hpp
diff --git a/interpolate_string_test.cpp b/interpolate_string_test.cpp
new file mode 100644
index 0000000..24e2b6d
--- /dev/null
+++ b/interpolate_string_test.cpp
@@ -0,0 +1,70 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "interpolate_string.hpp"
+
+#include "test_tools.hpp"
+
+int test_main(int, char*[])
+{
+    auto const test_interpolate = [](char const* s)
+        {
+        return interpolate_string(s, [](std::string const& k) { return k; });
+        };
+
+    // Check that basic interpolation works.
+    BOOST_TEST_EQUAL( test_interpolate(""),             "" );
+    BOOST_TEST_EQUAL( test_interpolate("${foo}"),       "foo" );
+    BOOST_TEST_EQUAL( test_interpolate("${foo}bar"),    "foobar" );
+    BOOST_TEST_EQUAL( test_interpolate("foo${}bar"),    "foobar" );
+    BOOST_TEST_EQUAL( test_interpolate("foo${bar}"),    "foobar" );
+    BOOST_TEST_EQUAL( test_interpolate("${foo}${bar}"), "foobar" );
+
+    // Should throw if the input syntax is invalid.
+    BOOST_TEST_THROW
+        (test_interpolate("${x")
+        ,std::runtime_error
+        ,lmi_test::what_regex("Unmatched opening brace")
+        );
+    BOOST_TEST_THROW
+        (test_interpolate("${x${y}}")
+        ,std::runtime_error
+        ,lmi_test::what_regex("Unexpected nested interpolation")
+        );
+
+    // Or because the lookup function throws.
+    BOOST_TEST_THROW
+        (interpolate_string
+            ("${x}"
+            ,[](std::string const& s) -> std::string
+                {
+                throw std::runtime_error("no such variable '" + s + "'");
+                }
+            )
+            ,std::runtime_error
+        ,"no such variable 'x'"
+        );
+
+
+    return EXIT_SUCCESS;
+}
diff --git a/ledger_pdf_generator_wx.cpp b/ledger_pdf_generator_wx.cpp
index 432f9c9..a645ef3 100644
--- a/ledger_pdf_generator_wx.cpp
+++ b/ledger_pdf_generator_wx.cpp
@@ -28,6 +28,7 @@
 #include "force_linking.hpp"
 #include "global_settings.hpp"
 #include "html.hpp"
+#include "interpolate_string.hpp"
 #include "ledger.hpp"
 #include "ledger_invariant.hpp"
 #include "pdf_writer_wx.hpp"
@@ -309,14 +310,13 @@ class cover_page : public page
         auto const footer_html = tag::p[attr::align("center")]
             (tag::font[attr::size("-1")]
                 (text::from
-                    (invar.InsCoShortName
-                    +"Financial Group is a marketing name for "
-                    +invar.InsCoName
-                    +"("
-                    +invar.InsCoShortName
-                    +") and its affiliated company and sales representatives, "
-                    +invar.InsCoAddr
-                    +"."
+                    (interpolate_string
+                        ("${InsCoShortName} Financial Group is a marketing "
+                         "name for ${InsCoName} (${InsCoShortName}) and its "
+                         "affiliated company and sales representatives, "
+                         "${InsCoAddr}.",
+                         [=](std::string const& k) { return 
invar.value_str(k); }
+                        )
                     )
                 )
             );
diff --git a/objects.make b/objects.make
index c7eda34..d6ebba3 100644
--- a/objects.make
+++ b/objects.make
@@ -215,6 +215,7 @@ common_common_objects := \
   input_sequence_parser.o \
   input_xml_io.o \
   interest_rates.o \
+  interpolate_string.o \
   ledger.o \
   ledger_base.o \
   ledger_invariant.o \
@@ -424,6 +425,7 @@ unit_test_targets := \
   ieee754_test \
   input_sequence_test \
   input_test \
+  interpolate_string_test \
   irc7702a_test \
   istream_to_string_test \
   loads_test \
@@ -693,6 +695,11 @@ input_test$(EXEEXT): \
   xml_lmi.o \
   yare_input.o \
 
+interpolate_string_test$(EXEEXT): \
+  $(common_test_objects) \
+  interpolate_string.o \
+  interpolate_string_test.o \
+
 irc7702a_test$(EXEEXT): \
   $(boost_filesystem_objects) \
   $(common_test_objects) \



reply via email to

[Prev in Thread] Current Thread [Next in Thread]