summaryrefslogtreecommitdiff
path: root/gn/tools/gn/function_get_path_info.cc
blob: 905092cd50a8b8b2def41094546897033c20c722 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// 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 <stddef.h>

#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/value.h"

namespace functions {

namespace {

// Corresponds to the various values of "what" in the function call.
enum What {
  WHAT_FILE,
  WHAT_NAME,
  WHAT_EXTENSION,
  WHAT_DIR,
  WHAT_ABSPATH,
  WHAT_GEN_DIR,
  WHAT_OUT_DIR,
};

// Returns the directory containing the input (resolving it against the
// |current_dir|), regardless of whether the input is a directory or a file.
SourceDir DirForInput(const Settings* settings,
                      const SourceDir& current_dir,
                      const Value& input,
                      Err* err) {
  // Input should already have been validated as a string.
  const std::string& input_string = input.string_value();

  if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
    // Input is a directory.
    return current_dir.ResolveRelativeDir(
        input, err, settings->build_settings()->root_path_utf8());
  }

  // Input is a file.
  return current_dir
      .ResolveRelativeFile(input, err,
                           settings->build_settings()->root_path_utf8())
      .GetDir();
}

std::string GetOnePathInfo(const Settings* settings,
                           const SourceDir& current_dir,
                           What what,
                           const Value& input,
                           Err* err) {
  if (!input.VerifyTypeIs(Value::STRING, err))
    return std::string();
  const std::string& input_string = input.string_value();
  if (input_string.empty()) {
    *err = Err(input, "Calling get_path_info on an empty string.");
    return std::string();
  }

  switch (what) {
    case WHAT_FILE: {
      return FindFilename(&input_string).as_string();
    }
    case WHAT_NAME: {
      std::string file = FindFilename(&input_string).as_string();
      size_t extension_offset = FindExtensionOffset(file);
      if (extension_offset == std::string::npos)
        return file;
      // Trim extension and dot.
      return file.substr(0, extension_offset - 1);
    }
    case WHAT_EXTENSION: {
      return FindExtension(&input_string).as_string();
    }
    case WHAT_DIR: {
      base::StringPiece dir_incl_slash = FindDir(&input_string);
      if (dir_incl_slash.empty())
        return std::string(".");
      // Trim slash since this function doesn't return trailing slashes. The
      // times we don't do this are if the result is "/" and "//" since those
      // slashes can't be trimmed.
      if (dir_incl_slash == "/")
        return std::string("/.");
      if (dir_incl_slash == "//")
        return std::string("//.");
      return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string();
    }
    case WHAT_GEN_DIR: {
      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
          BuildDirContext(settings),
          DirForInput(settings, current_dir, input, err), BuildDirType::GEN));
    }
    case WHAT_OUT_DIR: {
      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
          BuildDirContext(settings),
          DirForInput(settings, current_dir, input, err), BuildDirType::OBJ));
    }
    case WHAT_ABSPATH: {
      bool as_dir =
          !input_string.empty() && input_string[input_string.size() - 1] == '/';

      return current_dir.ResolveRelativeAs(
          !as_dir, input, err, settings->build_settings()->root_path_utf8(),
          &input_string);
    }
    default:
      NOTREACHED();
      return std::string();
  }
}

}  // namespace

const char kGetPathInfo[] = "get_path_info";
const char kGetPathInfo_HelpShort[] =
    "get_path_info: Extract parts of a file or directory name.";
const char kGetPathInfo_Help[] =
    R"(get_path_info: Extract parts of a file or directory name.

  get_path_info(input, what)

  The first argument is either a string representing a file or directory name,
  or a list of such strings. If the input is a list the return value will be a
  list containing the result of applying the rule to each item in the input.

Possible values for the "what" parameter

  "file"
      The substring after the last slash in the path, including the name and
      extension. If the input ends in a slash, the empty string will be
      returned.
        "foo/bar.txt" => "bar.txt"
        "bar.txt" => "bar.txt"
        "foo/" => ""
        "" => ""

  "name"
     The substring of the file name not including the extension.
        "foo/bar.txt" => "bar"
        "foo/bar" => "bar"
        "foo/" => ""

  "extension"
      The substring following the last period following the last slash, or the
      empty string if not found. The period is not included.
        "foo/bar.txt" => "txt"
        "foo/bar" => ""

  "dir"
      The directory portion of the name, not including the slash.
        "foo/bar.txt" => "foo"
        "//foo/bar" => "//foo"
        "foo" => "."

      The result will never end in a slash, so if the resulting is empty, the
      system ("/") or source ("//") roots, a "." will be appended such that it
      is always legal to append a slash and a filename and get a valid path.

  "out_dir"
      The output file directory corresponding to the path of the given file,
      not including a trailing slash.
        "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar"

  "gen_dir"
      The generated file directory corresponding to the path of the given file,
      not including a trailing slash.
        "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar"

  "abspath"
      The full absolute path name to the file or directory. It will be resolved
      relative to the current directory, and then the source- absolute version
      will be returned. If the input is system- absolute, the same input will
      be returned.
        "foo/bar.txt" => "//mydir/foo/bar.txt"
        "foo/" => "//mydir/foo/"
        "//foo/bar" => "//foo/bar"  (already absolute)
        "/usr/include" => "/usr/include"  (already absolute)

      If you want to make the path relative to another directory, or to be
      system-absolute, see rebase_path().

Examples
  sources = [ "foo.cc", "foo.h" ]
  result = get_path_info(source, "abspath")
  # result will be [ "//mydir/foo.cc", "//mydir/foo.h" ]

  result = get_path_info("//foo/bar/baz.cc", "dir")
  # result will be "//foo/bar"

  # Extract the source-absolute directory name,
  result = get_path_info(get_path_info(path, "dir"), "abspath"
)";

Value RunGetPathInfo(Scope* scope,
                     const FunctionCallNode* function,
                     const std::vector<Value>& args,
                     Err* err) {
  if (args.size() != 2) {
    *err = Err(function, "Expecting two arguments to get_path_info.");
    return Value();
  }

  // Extract the "what".
  if (!args[1].VerifyTypeIs(Value::STRING, err))
    return Value();
  What what;
  if (args[1].string_value() == "file") {
    what = WHAT_FILE;
  } else if (args[1].string_value() == "name") {
    what = WHAT_NAME;
  } else if (args[1].string_value() == "extension") {
    what = WHAT_EXTENSION;
  } else if (args[1].string_value() == "dir") {
    what = WHAT_DIR;
  } else if (args[1].string_value() == "out_dir") {
    what = WHAT_OUT_DIR;
  } else if (args[1].string_value() == "gen_dir") {
    what = WHAT_GEN_DIR;
  } else if (args[1].string_value() == "abspath") {
    what = WHAT_ABSPATH;
  } else {
    *err = Err(args[1], "Unknown value for 'what'.");
    return Value();
  }

  const SourceDir& current_dir = scope->GetSourceDir();
  if (args[0].type() == Value::STRING) {
    return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
                                          args[0], err));
  } else if (args[0].type() == Value::LIST) {
    const std::vector<Value>& input_list = args[0].list_value();
    Value result(function, Value::LIST);
    for (const auto& cur : input_list) {
      result.list_value().push_back(Value(
          function,
          GetOnePathInfo(scope->settings(), current_dir, what, cur, err)));
      if (err->has_error())
        return Value();
    }
    return result;
  }

  *err = Err(args[0], "Path must be a string or a list of strings.");
  return Value();
}

}  // namespace functions