
#include "cmCMP0053ConfigureTemplate.h"

#include "cmCommandArgumentParserHelper.h"
#include "cmSystemTools.h"

cmCMP0053ConfigureTemplate::cmCMP0053ConfigureTemplate(cmMakefile const* mf)
  : MF(mf)
{
  this->cmAtVarRegex.compile("(@[A-Za-z_0-9/.+-]+@)");
}

cmake::MessageType cmCMP0053ConfigureTemplate::ExpandVariablesInString(
  cmState::Snapshot stateSnapshot, std::string& errorstr, std::string& source,
  bool escapeQuotes, bool noEscapes, bool atOnly, const char* filename,
  long line, bool removeEmpty, bool replaceAt,
  std::map<std::string, const char*>* substitutions,
  std::vector<std::string>& uninitializedSubstitutions) const
{
  cmake::MessageType mtype = cmake::LOG;
  std::string original;
  bool compareResults = false;

  std::string newResult;
  std::string newErrorstr;
  cmake::MessageType newError = cmake::LOG;

  switch (this->MF->GetPolicyStatus(cmPolicies::CMP0053)) {
    case cmPolicies::WARN: {
      // Save the original string for the warning.
      original = source;
      newResult = source;
      compareResults = true;
      newError = cmConfigureTemplate::ExpandVariablesInString(
        stateSnapshot, newErrorstr, newResult, escapeQuotes, noEscapes, atOnly,
        filename, line, removeEmpty, replaceAt, CM_NULLPTR,
        uninitializedSubstitutions);
    }
    case cmPolicies::OLD:
      mtype = this->ExpandVariablesInStringOld(errorstr, source, escapeQuotes,
                                               noEscapes, atOnly, filename,
                                               line, removeEmpty, true);
      break;
    case cmPolicies::REQUIRED_IF_USED:
    case cmPolicies::REQUIRED_ALWAYS:
    // Messaging here would be *very* verbose.
    case cmPolicies::NEW:
      mtype = cmConfigureTemplate::ExpandVariablesInString(
        stateSnapshot, errorstr, source, escapeQuotes, noEscapes, atOnly,
        filename, line, removeEmpty, replaceAt, substitutions,
        uninitializedSubstitutions);
      break;
  }

  if (mtype == cmake::LOG && compareResults &&
      (newResult != source || newError != mtype)) {
    std::string msg = cmPolicies::GetPolicyWarning(cmPolicies::CMP0053);
    msg += "\n";

    std::string msg_input = original;
    cmSystemTools::ReplaceString(msg_input, "\n", "\n  ");
    msg += "For input:\n  '";
    msg += msg_input;
    msg += "'\n";

    std::string msg_old = source;
    cmSystemTools::ReplaceString(msg_old, "\n", "\n  ");
    msg += "the old evaluation rules produce:\n  '";
    msg += msg_old;
    msg += "'\n";

    if (newError == mtype) {
      std::string msg_new = newResult;
      cmSystemTools::ReplaceString(msg_new, "\n", "\n  ");
      msg += "but the new evaluation rules produce:\n  '";
      msg += msg_new;
      msg += "'\n";
    } else {
      std::string msg_err = newErrorstr;
      cmSystemTools::ReplaceString(msg_err, "\n", "\n  ");
      msg += "but the new evaluation rules produce an error:\n  ";
      msg += msg_err;
      msg += "\n";
    }

    msg +=
      "Using the old result for compatibility since the policy is not set.";

    this->MF->IssueMessage(cmake::AUTHOR_WARNING, msg);
  }

  return mtype;
}

cmake::MessageType cmCMP0053ConfigureTemplate::ExpandVariablesInStringOld(
  std::string& errorstr, std::string& source, bool escapeQuotes,
  bool noEscapes, bool atOnly, const char* filename, long line,
  bool removeEmpty, bool replaceAt) const
{
  // Fast path strings without any special characters.
  if (source.find_first_of("$@\\") == source.npos) {
    return cmake::LOG;
  }

  // Special-case the @ONLY mode.
  if (atOnly) {
    // Store an original copy of the input.
    std::string input = source;

    // Start with empty output.
    source = "";

    // Look for one @VAR@ at a time.
    const char* in = input.c_str();
    while (this->cmAtVarRegex.find(in)) {
      // Get the range of the string to replace.
      const char* first = in + this->cmAtVarRegex.start();
      const char* last = in + this->cmAtVarRegex.end();

      // Store the unchanged part of the string now.
      source.append(in, first - in);

      // Lookup the definition of VAR.
      std::string var(first + 1, last - first - 2);
      if (const char* val = this->MF->GetDefinition(var)) {
        // Store the value in the output escaping as requested.
        if (escapeQuotes) {
          source.append(cmSystemTools::EscapeQuotes(val));
        } else {
          source.append(val);
        }
      }

      // Continue looking for @VAR@ further along the string.
      in = last;
    }

    // Append the rest of the unchanged part of the string.
    source.append(in);

    return cmake::LOG;
  }

  // This method replaces ${VAR} and @VAR@ where VAR is looked up
  // with GetDefinition(), if not found in the map, nothing is expanded.
  // It also supports the $ENV{VAR} syntax where VAR is looked up in
  // the current environment variables.

  cmCommandArgumentParserHelper parser;
  parser.SetMakefile(this->MF);
  parser.SetLineFile(line, filename);
  parser.SetEscapeQuotes(escapeQuotes);
  parser.SetNoEscapeMode(noEscapes);
  parser.SetReplaceAtSyntax(replaceAt);
  parser.SetRemoveEmpty(removeEmpty);
  int res = parser.ParseString(source.c_str(), 0);
  const char* emsg = parser.GetError();
  cmake::MessageType mtype = cmake::LOG;
  if (res && !emsg[0]) {
    source = parser.GetResult();
  } else {
    // Construct the main error message.
    std::ostringstream error;
    error << "Syntax error in cmake code ";
    if (filename && line > 0) {
      // This filename and line number may be more specific than the
      // command context because one command invocation can have
      // arguments on multiple lines.
      error << "at\n"
            << "  " << filename << ":" << line << "\n";
    }
    error << "when parsing string\n"
          << "  " << source << "\n";
    error << emsg;

    // If the parser failed ("res" is false) then this is a real
    // argument parsing error, so the policy applies.  Otherwise the
    // parser reported an error message without failing because the
    // helper implementation is unhappy, which has always reported an
    // error.
    mtype = cmake::FATAL_ERROR;
    if (!res) {
      // This is a real argument parsing error.  Use policy CMP0010 to
      // decide whether it is an error.
      switch (this->MF->GetPolicyStatus(cmPolicies::CMP0010)) {
        case cmPolicies::WARN:
          error << "\n" << cmPolicies::GetPolicyWarning(cmPolicies::CMP0010);
        case cmPolicies::OLD:
          // OLD behavior is to just warn and continue.
          mtype = cmake::AUTHOR_WARNING;
          break;
        case cmPolicies::REQUIRED_IF_USED:
        case cmPolicies::REQUIRED_ALWAYS:
          error << "\n"
                << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0010);
        case cmPolicies::NEW:
          // NEW behavior is to report the error.
          break;
      }
    }
    errorstr = error.str();
  }
  return mtype;
}
