1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "tools/gn/err.h"
6#include "tools/gn/filesystem_utils.h"
7#include "tools/gn/functions.h"
8#include "tools/gn/parse_tree.h"
9#include "tools/gn/scope.h"
10#include "tools/gn/value.h"
11
12namespace functions {
13
14namespace {
15
16// Corresponds to the various values of "what" in the function call.
17enum What {
18  WHAT_FILE,
19  WHAT_NAME,
20  WHAT_EXTENSION,
21  WHAT_DIR,
22  WHAT_ABSPATH,
23  WHAT_GEN_DIR,
24  WHAT_OUT_DIR,
25};
26
27// Returns the directory containing the input (resolving it against the
28// |current_dir|), regardless of whether the input is a directory or a file.
29SourceDir DirForInput(const SourceDir& current_dir,
30                      const std::string& input_string) {
31  if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
32    // Input is a directory.
33    return current_dir.ResolveRelativeDir(input_string);
34  }
35
36  // Input is a directory.
37  return current_dir.ResolveRelativeFile(input_string).GetDir();
38}
39
40std::string GetOnePathInfo(const Settings* settings,
41                           const SourceDir& current_dir,
42                           What what,
43                           const Value& input,
44                           Err* err) {
45  if (!input.VerifyTypeIs(Value::STRING, err))
46    return std::string();
47  const std::string& input_string = input.string_value();
48  if (input_string.empty()) {
49    *err = Err(input, "Calling get_path_info on an empty string.");
50    return std::string();
51  }
52
53  switch (what) {
54    case WHAT_FILE: {
55      return FindFilename(&input_string).as_string();
56    }
57    case WHAT_NAME: {
58      std::string file = FindFilename(&input_string).as_string();
59      size_t extension_offset = FindExtensionOffset(file);
60      if (extension_offset == std::string::npos)
61        return file;
62      // Trim extension and dot.
63      return file.substr(0, extension_offset - 1);
64    }
65    case WHAT_EXTENSION: {
66      return FindExtension(&input_string).as_string();
67    }
68    case WHAT_DIR: {
69      base::StringPiece dir_incl_slash = FindDir(&input_string);
70      if (dir_incl_slash.empty())
71        return std::string(".");
72      // Trim slash since this function doesn't return trailing slashes. The
73      // times we don't do this are if the result is "/" and "//" since those
74      // slashes can't be trimmed.
75      if (dir_incl_slash == "/")
76        return std::string("/.");
77      if (dir_incl_slash == "//")
78        return std::string("//.");
79      return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string();
80    }
81    case WHAT_GEN_DIR: {
82      return DirectoryWithNoLastSlash(
83          GetGenDirForSourceDir(settings,
84                                DirForInput(current_dir, input_string)));
85    }
86    case WHAT_OUT_DIR: {
87      return DirectoryWithNoLastSlash(
88          GetOutputDirForSourceDir(settings,
89                                   DirForInput(current_dir, input_string)));
90    }
91    case WHAT_ABSPATH: {
92      if (!input_string.empty() && input_string[input_string.size() - 1] == '/')
93        return current_dir.ResolveRelativeDir(input_string).value();
94      else
95        return current_dir.ResolveRelativeFile(input_string).value();
96    }
97    default:
98      NOTREACHED();
99      return std::string();
100  }
101}
102
103}  // namespace
104
105const char kGetPathInfo[] = "get_path_info";
106const char kGetPathInfo_HelpShort[] =
107    "get_path_info: Extract parts of a file or directory name.";
108const char kGetPathInfo_Help[] =
109    "get_path_info: Extract parts of a file or directory name.\n"
110    "\n"
111    "  get_path_info(input, what)\n"
112    "\n"
113    "  The first argument is either a string representing a file or\n"
114    "  directory name, or a list of such strings. If the input is a list\n"
115    "  the return value will be a list containing the result of applying the\n"
116    "  rule to each item in the input.\n"
117    "\n"
118    "Possible values for the \"what\" parameter\n"
119    "\n"
120    "  \"file\"\n"
121    "      The substring after the last slash in the path, including the name\n"
122    "      and extension. If the input ends in a slash, the empty string will\n"
123    "      be returned.\n"
124    "        \"foo/bar.txt\" => \"bar.txt\"\n"
125    "        \"bar.txt\" => \"bar.txt\"\n"
126    "        \"foo/\" => \"\"\n"
127    "        \"\" => \"\"\n"
128    "\n"
129    "  \"name\"\n"
130    "     The substring of the file name not including the extension.\n"
131    "        \"foo/bar.txt\" => \"bar\"\n"
132    "        \"foo/bar\" => \"bar\"\n"
133    "        \"foo/\" => \"\"\n"
134    "\n"
135    "  \"extension\"\n"
136    "      The substring following the last period following the last slash,\n"
137    "      or the empty string if not found. The period is not included.\n"
138    "        \"foo/bar.txt\" => \"txt\"\n"
139    "        \"foo/bar\" => \"\"\n"
140    "\n"
141    "  \"dir\"\n"
142    "      The directory portion of the name, not including the slash.\n"
143    "        \"foo/bar.txt\" => \"foo\"\n"
144    "        \"//foo/bar\" => \"//foo\"\n"
145    "        \"foo\" => \".\"\n"
146    "\n"
147    "      The result will never end in a slash, so if the resulting\n"
148    "      is empty, the system (\"/\") or source (\"//\") roots, a \".\"\n"
149    "      will be appended such that it is always legal to append a slash\n"
150    "      and a filename and get a valid path.\n"
151    "\n"
152    "  \"out_dir\"\n"
153    "      The output file directory corresponding to the path of the\n"
154    "      given file, not including a trailing slash.\n"
155    "        \"//foo/bar/baz.txt\" => \"//out/Default/obj/foo/bar\"\n"
156
157    "  \"gen_dir\"\n"
158    "      The generated file directory corresponding to the path of the\n"
159    "      given file, not including a trailing slash.\n"
160    "        \"//foo/bar/baz.txt\" => \"//out/Default/gen/foo/bar\"\n"
161    "\n"
162    "  \"abspath\"\n"
163    "      The full absolute path name to the file or directory. It will be\n"
164    "      resolved relative to the currebt directory, and then the source-\n"
165    "      absolute version will be returned. If the input is system-\n"
166    "      absolute, the same input will be returned.\n"
167    "        \"foo/bar.txt\" => \"//mydir/foo/bar.txt\"\n"
168    "        \"foo/\" => \"//mydir/foo/\"\n"
169    "        \"//foo/bar\" => \"//foo/bar\"  (already absolute)\n"
170    "        \"/usr/include\" => \"/usr/include\"  (already absolute)\n"
171    "\n"
172    "      If you want to make the path relative to another directory, or to\n"
173    "      be system-absolute, see rebase_path().\n"
174    "\n"
175    "Examples\n"
176    "  sources = [ \"foo.cc\", \"foo.h\" ]\n"
177    "  result = get_path_info(source, \"abspath\")\n"
178    "  # result will be [ \"//mydir/foo.cc\", \"//mydir/foo.h\" ]\n"
179    "\n"
180    "  result = get_path_info(\"//foo/bar/baz.cc\", \"dir\")\n"
181    "  # result will be \"//foo/bar\"\n"
182    "\n"
183    "  # Extract the source-absolute directory name,\n"
184    "  result = get_path_info(get_path_info(path, \"dir\"), \"abspath\")\n";
185
186Value RunGetPathInfo(Scope* scope,
187                     const FunctionCallNode* function,
188                     const std::vector<Value>& args,
189                     Err* err) {
190  if (args.size() != 2) {
191    *err = Err(function, "Expecting two arguments to get_path_info.");
192    return Value();
193  }
194
195  // Extract the "what".
196  if (!args[1].VerifyTypeIs(Value::STRING, err))
197    return Value();
198  What what;
199  if (args[1].string_value() == "file") {
200    what = WHAT_FILE;
201  } else if (args[1].string_value() == "name") {
202    what = WHAT_NAME;
203  } else if (args[1].string_value() == "extension") {
204    what = WHAT_EXTENSION;
205  } else if (args[1].string_value() == "dir") {
206    what = WHAT_DIR;
207  } else if (args[1].string_value() == "out_dir") {
208    what = WHAT_OUT_DIR;
209  } else if (args[1].string_value() == "gen_dir") {
210    what = WHAT_GEN_DIR;
211  } else if (args[1].string_value() == "abspath") {
212    what = WHAT_ABSPATH;
213  } else {
214    *err = Err(args[1], "Unknown value for 'what'.");
215    return Value();
216  }
217
218  const SourceDir& current_dir = scope->GetSourceDir();
219  if (args[0].type() == Value::STRING) {
220    return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
221                                          args[0], err));
222  } else if (args[0].type() == Value::LIST) {
223    const std::vector<Value>& input_list = args[0].list_value();
224    Value result(function, Value::LIST);
225    for (size_t i = 0; i < input_list.size(); i++) {
226      result.list_value().push_back(Value(function,
227          GetOnePathInfo(scope->settings(), current_dir, what,
228                         input_list[i], err)));
229      if (err->has_error())
230        return Value();
231    }
232    return result;
233  }
234
235  *err = Err(args[0], "Path must be a string or a list of strings.");
236  return Value();
237}
238
239}  // namespace functions
240