Program Listing for File utils.h#

Return to documentation for file (public/utils.h)

#ifndef f3d_utils_h
#define f3d_utils_h

#include "exception.h"
#include "export.h"

#include <map>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

namespace f3d
{
class F3D_EXPORT utils
{
public:
  static unsigned int textDistance(const std::string& strA, const std::string& strB);

  // clang-format off
  static std::vector<std::string> tokenize(std::string_view str);
  // clang-format on

  struct tokenize_exception : public exception
  {
    explicit tokenize_exception(const std::string& what = "");
  };

  class string_template
  {
    std::vector<std::pair<std::string, bool>> fragments;

  public:
    explicit string_template(const std::string& templateString);

    template<typename F>
    string_template& substitute(F lookup);

    string_template& substitute(const std::map<std::string, std::string>& lookup);

    std::string str() const;

    std::vector<std::string> variables() const;

    struct lookup_error : public std::out_of_range
    {
      explicit lookup_error(const std::string& what = "")
        : std::out_of_range(what)
      {
      }
    };
  };
};

//------------------------------------------------------------------------------
inline utils::string_template::string_template(const std::string& templateString)
{
  const std::string varName = "[\\w_.%:-]+";
  const std::string escapedVar = "(\\{(\\{" + varName + "\\})\\})";
  const std::string substVar = "(\\{(" + varName + ")\\})";
  const std::regex escapedVarRe(escapedVar);
  const std::regex substVarRe(substVar);

  const auto callback = [&](const std::string& m)
  {
    if (std::regex_match(m, escapedVarRe))
    {
      this->fragments.emplace_back(std::regex_replace(m, escapedVarRe, "$2"), false);
    }
    else if (std::regex_match(m, substVarRe))
    {
      this->fragments.emplace_back(std::regex_replace(m, substVarRe, "$2"), true);
    }
    else
    {
      this->fragments.emplace_back(m, false);
    }
  };

  const std::regex re(escapedVar + "|" + substVar);
  std::sregex_token_iterator begin(templateString.begin(), templateString.end(), re, { -1, 0 });
  std::for_each(begin, std::sregex_token_iterator(), callback);
}

//------------------------------------------------------------------------------
template<typename F>
utils::string_template& utils::string_template::substitute(F lookup)
{
  for (auto& [fragment, isVariable] : this->fragments)
  {
    if (isVariable)
    {
      try
      {
        fragment = lookup(fragment);
        isVariable = false;
      }
      catch (const lookup_error&)
      {
        /* leave variable as is */
      }
    }
  }
  return *this;
}

//------------------------------------------------------------------------------
inline utils::string_template& utils::string_template::substitute(
  const std::map<std::string, std::string>& lookup)
{
  return this->substitute(
    [&](const std::string& key)
    {
      try
      {
        return lookup.at(key);
      }
      catch (const std::out_of_range&)
      {
        throw lookup_error(key);
      }
    });
}

//------------------------------------------------------------------------------
inline std::string utils::string_template::str() const
{
  std::ostringstream ss;
  // cppcheck-suppress unassignedVariable
  // (false positive, fixed in cppcheck 2.8)
  for (const auto& [fragment, isVariable] : this->fragments)
  {
    if (isVariable)
    {
      ss << "{" << fragment << "}";
    }
    else
    {
      ss << fragment;
    }
  }
  return ss.str();
}

//------------------------------------------------------------------------------
inline std::vector<std::string> utils::string_template::variables() const
{
  std::vector<std::string> variables;
  for (const auto& [fragment, isVariable] : this->fragments)
  {
    if (isVariable)
    {
      variables.emplace_back(fragment);
    }
  }
  return variables;
}
}

#endif