summaryrefslogtreecommitdiff
path: root/gn/tools/gn/substitution_pattern.cc
blob: 9b5e815f9400f9c94da59453081e045e1ee358cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/substitution_pattern.h"

#include <stddef.h>

#include "base/strings/string_number_conversions.h"
#include "tools/gn/build_settings.h"
#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/value.h"

SubstitutionPattern::Subrange::Subrange() : type(SUBSTITUTION_LITERAL) {}

SubstitutionPattern::Subrange::Subrange(SubstitutionType t,
                                        const std::string& l)
    : type(t), literal(l) {}

SubstitutionPattern::Subrange::~Subrange() = default;

SubstitutionPattern::SubstitutionPattern() : origin_(nullptr) {}

SubstitutionPattern::SubstitutionPattern(const SubstitutionPattern& other) =
    default;

SubstitutionPattern::~SubstitutionPattern() = default;

bool SubstitutionPattern::Parse(const Value& value, Err* err) {
  if (!value.VerifyTypeIs(Value::STRING, err))
    return false;
  return Parse(value.string_value(), value.origin(), err);
}

bool SubstitutionPattern::Parse(const std::string& str,
                                const ParseNode* origin,
                                Err* err) {
  DCHECK(ranges_.empty());  // Should only be called once.

  size_t cur = 0;
  while (true) {
    size_t next = str.find("{{", cur);

    // Pick up everything from the previous spot to here as a literal.
    if (next == std::string::npos) {
      if (cur != str.size())
        ranges_.push_back(Subrange(SUBSTITUTION_LITERAL, str.substr(cur)));
      break;
    } else if (next > cur) {
      ranges_.push_back(
          Subrange(SUBSTITUTION_LITERAL, str.substr(cur, next - cur)));
    }

    // Find which specific pattern this corresponds to.
    bool found_match = false;
    for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES;
         i++) {
      const char* cur_pattern = kSubstitutionNames[i];
      size_t cur_len = strlen(cur_pattern);
      if (str.compare(next, cur_len, cur_pattern) == 0) {
        ranges_.push_back(Subrange(static_cast<SubstitutionType>(i)));
        cur = next + cur_len;
        found_match = true;
        break;
      }
    }

    // Expect all occurrances of {{ to resolve to a pattern.
    if (!found_match) {
      // Could make this error message more friendly if it comes up a lot. But
      // most people will not be writing substitution patterns and the code
      // to exactly indicate the error location is tricky.
      *err = Err(origin, "Unknown substitution pattern",
                 "Found a {{ at offset " + base::NumberToString(next) +
                     " and did not find a known substitution following it.");
      ranges_.clear();
      return false;
    }
  }

  origin_ = origin;

  // Fill required types vector.
  SubstitutionBits bits;
  FillRequiredTypes(&bits);
  bits.FillVector(&required_types_);
  return true;
}

// static
SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) {
  Err err;
  SubstitutionPattern pattern;
  CHECK(pattern.Parse(str, nullptr, &err)) << err.message();
  return pattern;
}

std::string SubstitutionPattern::AsString() const {
  std::string result;
  for (const auto& elem : ranges_) {
    if (elem.type == SUBSTITUTION_LITERAL)
      result.append(elem.literal);
    else
      result.append(kSubstitutionNames[elem.type]);
  }
  return result;
}

void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
  for (const auto& elem : ranges_) {
    if (elem.type != SUBSTITUTION_LITERAL)
      bits->used[static_cast<size_t>(elem.type)] = true;
  }
}

bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
                                        Err* err) const {
  if (ranges_.empty()) {
    *err = Err(origin_, "This is empty but I was expecting an output file.");
    return false;
  }

  if (ranges_[0].type == SUBSTITUTION_LITERAL) {
    // If the first thing is a literal, it must start with the output dir.
    if (!EnsureStringIsInOutputDir(build_settings->build_dir(),
                                   ranges_[0].literal, origin_, err))
      return false;
  } else {
    // Otherwise, the first subrange must be a pattern that expands to
    // something in the output directory.
    if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
      *err =
          Err(origin_, "File is not inside output directory.",
              "The given file should be in the output directory. Normally you\n"
              "would specify\n\"$target_out_dir/foo\" or "
              "\"{{source_gen_dir}}/foo\".");
      return false;
    }
  }

  return true;
}