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