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
12 namespace functions {
13
14 namespace {
15
16 // Corresponds to the various values of "what" in the function call.
17 enum 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.
DirForInput(const SourceDir & current_dir,const std::string & input_string)29 SourceDir 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
GetOnePathInfo(const Settings * settings,const SourceDir & current_dir,What what,const Value & input,Err * err)40 std::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
105 const char kGetPathInfo[] = "get_path_info";
106 const char kGetPathInfo_HelpShort[] =
107 "get_path_info: Extract parts of a file or directory name.";
108 const 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
RunGetPathInfo(Scope * scope,const FunctionCallNode * function,const std::vector<Value> & args,Err * err)186 Value 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