Add utility functions needed for rect-based event targeting
[chromium-blink-merge.git] / tools / gn / function_rebase_path.cc
blob68c6f1facaf610efe20457a6050b234dc88a5f18
1 // Copyright (c) 2013 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.
5 #include "tools/gn/build_settings.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/settings.h"
11 #include "tools/gn/source_dir.h"
12 #include "tools/gn/source_file.h"
13 #include "tools/gn/value.h"
15 namespace functions {
17 namespace {
19 enum SeparatorConversion {
20 SEP_NO_CHANGE, // Don't change.
21 SEP_TO_SYSTEM, // Slashes to system ones.
22 SEP_FROM_SYSTEM // System ones to slashes.
25 // Does the specified path separator conversion in-place.
26 void ConvertSlashes(std::string* str, SeparatorConversion mode) {
27 #if defined(OS_WIN)
28 switch (mode) {
29 case SEP_NO_CHANGE:
30 break;
31 case SEP_TO_SYSTEM:
32 for (size_t i = 0; i < str->size(); i++) {
33 if ((*str)[i] == '/')
34 (*str)[i] = '\\';
36 break;
37 case SEP_FROM_SYSTEM:
38 for (size_t i = 0; i < str->size(); i++) {
39 if ((*str)[i] == '\\')
40 (*str)[i] = '/';
42 break;
44 #else
45 DCHECK(str->find('\\') == std::string::npos)
46 << "Filename contains a backslash on a non-Windows platform.";
47 #endif
50 bool EndsInSlash(const std::string& s) {
51 return !s.empty() && (s[s.size() - 1] == '/' || s[s.size() - 1] == '\\');
54 // We want the output to match the input in terms of ending in a slash or not.
55 // Through all the transformations, these can get added or removed in various
56 // cases.
57 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
58 if (EndsInSlash(input)) {
59 if (!EndsInSlash(*output)) // Preserve same slash type as input.
60 output->push_back(input[input.size() - 1]);
61 } else {
62 if (EndsInSlash(*output))
63 output->resize(output->size() - 1);
67 // Returns true if the given value looks like a directory, otherwise we'll
68 // assume it's a file.
69 bool ValueLooksLikeDir(const std::string& value) {
70 if (value.empty())
71 return true;
72 size_t value_size = value.size();
74 // Count the number of dots at the end of the string.
75 size_t num_dots = 0;
76 while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
77 num_dots++;
79 if (num_dots == value.size())
80 return true; // String is all dots.
82 if (value[value_size - num_dots - 1] == '/' ||
83 value[value_size - num_dots - 1] == '\\')
84 return true; // String is a [back]slash followed by 0 or more dots.
86 // Anything else.
87 return false;
90 Value ConvertOnePath(const Scope* scope,
91 const FunctionCallNode* function,
92 const Value& value,
93 const SourceDir& from_dir,
94 const SourceDir& to_dir,
95 bool convert_to_system_absolute,
96 SeparatorConversion separator_conversion,
97 Err* err) {
98 Value result; // Ensure return value optimization.
100 if (!value.VerifyTypeIs(Value::STRING, err))
101 return result;
102 const std::string& string_value = value.string_value();
104 bool looks_like_dir = ValueLooksLikeDir(string_value);
106 // System-absolute output special case.
107 if (convert_to_system_absolute) {
108 base::FilePath system_path;
109 if (looks_like_dir) {
110 system_path = scope->settings()->build_settings()->GetFullPath(
111 from_dir.ResolveRelativeDir(string_value));
112 } else {
113 system_path = scope->settings()->build_settings()->GetFullPath(
114 from_dir.ResolveRelativeFile(string_value));
116 result = Value(function, FilePathToUTF8(system_path));
117 if (looks_like_dir)
118 MakeSlashEndingMatchInput(string_value, &result.string_value());
119 ConvertPathToSystem(&result.string_value());
120 return result;
123 if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
124 *err = Err(function, "System-absolute directories are not supported for "
125 "the source or dest dir for rebase_path. It would be nice to add this "
126 "if you're so inclined!");
127 return result;
130 result = Value(function, Value::STRING);
131 if (looks_like_dir) {
132 result.string_value() = RebaseSourceAbsolutePath(
133 from_dir.ResolveRelativeDir(string_value).value(),
134 to_dir);
135 MakeSlashEndingMatchInput(string_value, &result.string_value());
136 } else {
137 result.string_value() = RebaseSourceAbsolutePath(
138 from_dir.ResolveRelativeFile(string_value).value(),
139 to_dir);
142 ConvertSlashes(&result.string_value(), separator_conversion);
143 return result;
146 } // namespace
148 const char kRebasePath[] = "rebase_path";
149 const char kRebasePath_Help[] =
150 "rebase_path: Rebase a file or directory to another location.\n"
151 "\n"
152 " converted = rebase_path(input, current_base, new_base,\n"
153 " [path_separators])\n"
154 "\n"
155 " Takes a string argument representing a file name, or a list of such\n"
156 " strings and converts it/them to be relative to a different base\n"
157 " directory.\n"
158 "\n"
159 " When invoking the compiler or scripts, GN will automatically convert\n"
160 " sources and include directories to be relative to the build directory.\n"
161 " However, if you're passing files directly in the \"args\" array or\n"
162 " doing other manual manipulations where GN doesn't know something is\n"
163 " a file name, you will need to convert paths to be relative to what\n"
164 " your tool is expecting.\n"
165 "\n"
166 " The common case is to use this to convert paths relative to the\n"
167 " current directory to be relative to the build directory (which will\n"
168 " be the current directory when executing scripts).\n"
169 "\n"
170 "Arguments\n"
171 "\n"
172 " input\n"
173 " A string or list of strings representing file or directory names\n"
174 " These can be relative paths (\"foo/bar.txt\", system absolte paths\n"
175 " (\"/foo/bar.txt\"), or source absolute paths (\"//foo/bar.txt\").\n"
176 "\n"
177 " current_base\n"
178 " Directory representing the base for relative paths in the input.\n"
179 " If this is not an absolute path, it will be treated as being\n"
180 " relative to the current build file. Use \".\" to convert paths\n"
181 " from the current BUILD-file's directory.\n"
182 "\n"
183 " new_base\n"
184 " The directory to convert the paths to be relative to. As with the\n"
185 " current_base, this can be a relative path, which will be treated\n"
186 " as being relative to the current BUILD-file's directory.\n"
187 "\n"
188 " As a special case, if new_base is the empty string, all paths\n"
189 " will be converted to system-absolute native style paths with\n"
190 " system path separators. This is useful for invoking external\n"
191 " programs.\n"
192 "\n"
193 " path_separators\n"
194 " On Windows systems, indicates whether and how path separators\n"
195 " should be converted as part of the transformation. It can be one\n"
196 " of the following strings:\n"
197 " - \"none\" Perform no changes on path separators. This is the\n"
198 " default if this argument is unspecified.\n"
199 " - \"to_system\" Convert to the system path separators\n"
200 " (backslashes on Windows).\n"
201 " - \"from_system\" Convert system path separators to forward\n"
202 " slashes.\n"
203 "\n"
204 " On Posix systems there are no path separator transformations\n"
205 " applied. If the new_base is empty (specifying absolute output)\n"
206 " this parameter should not be supplied since paths will always be\n"
207 " converted,\n"
208 "\n"
209 "Return value\n"
210 "\n"
211 " The return value will be the same type as the input value (either a\n"
212 " string or a list of strings). All relative and source-absolute file\n"
213 " names will be converted to be relative to the requested output\n"
214 " System-absolute paths will be unchanged.\n"
215 "\n"
216 "Example\n"
217 "\n"
218 " # Convert a file in the current directory to be relative to the build\n"
219 " # directory (the current dir when executing compilers and scripts).\n"
220 " foo = rebase_path(\"myfile.txt\", \".\", root_build_dir)\n"
221 " # might produce \"../../project/myfile.txt\".\n"
222 "\n"
223 " # Convert a file to be system absolute:\n"
224 " foo = rebase_path(\"myfile.txt\", \".\", \"\")\n"
225 " # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
226 " # \"/home/you/source/project/myfile.txt\" on Linux.\n"
227 "\n"
228 " # Convert a file's path separators from forward slashes to system\n"
229 " # slashes.\n"
230 " foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
231 "\n"
232 " # Typical usage for converting to the build directory for a script.\n"
233 " custom(\"myscript\") {\n"
234 " # Don't convert sources, GN will automatically convert these to be\n"
235 " # relative to the build directory when it contructs the command\n"
236 " # line for your script.\n"
237 " sources = [ \"foo.txt\", \"bar.txt\" ]\n"
238 "\n"
239 " # Extra file args passed manually need to be explicitly converted\n"
240 " # to be relative to the build directory:\n"
241 " args = [\n"
242 " \"--data\",\n"
243 " rebase_path(\"//mything/data/input.dat\", \".\", root_build_dir),\n"
244 " \"--rel\",\n"
245 " rebase_path(\"relative_path.txt\", \".\", root_build_dir)\n"
246 " ]\n"
247 " }\n";
249 Value RunRebasePath(Scope* scope,
250 const FunctionCallNode* function,
251 const std::vector<Value>& args,
252 Err* err) {
253 Value result;
255 // Inputs.
256 if (args.size() != 3 && args.size() != 4) {
257 *err = Err(function->function(), "rebase_path takes 3 or 4 args.");
258 return result;
260 const Value& inputs = args[0];
262 // From path.
263 if (!args[1].VerifyTypeIs(Value::STRING, err))
264 return result;
265 const SourceDir& current_dir = scope->GetSourceDir();
266 SourceDir from_dir = current_dir.ResolveRelativeDir(args[1].string_value());
268 // To path.
269 if (!args[2].VerifyTypeIs(Value::STRING, err))
270 return result;
271 bool convert_to_system_absolute = false;
272 SourceDir to_dir;
273 if (args[2].string_value().empty()) {
274 convert_to_system_absolute = true;
275 } else {
276 to_dir = current_dir.ResolveRelativeDir(args[2].string_value());
279 // Path conversion.
280 SeparatorConversion sep_conversion = SEP_NO_CHANGE;
281 if (args.size() == 4) {
282 if (convert_to_system_absolute) {
283 *err = Err(function, "Can't specify slash conversion.",
284 "You specified absolute system path output by using an empty string "
285 "for the desination directory on rebase_path(). In this case, you "
286 "can't specify slash conversion.");
287 return result;
290 if (!args[3].VerifyTypeIs(Value::STRING, err))
291 return result;
292 const std::string& sep_string = args[3].string_value();
293 if (sep_string == "to_system") {
294 sep_conversion = SEP_TO_SYSTEM;
295 } else if (sep_string == "from_system") {
296 sep_conversion = SEP_FROM_SYSTEM;
297 } else if (sep_string != "none") {
298 *err = Err(args[3], "Invalid path separator conversion mode.",
299 "I was expecting \"none\", \"to_system\", or \"from_system\" and\n"
300 "you gave me \"" + args[3].string_value() + "\".");
301 return result;
305 if (inputs.type() == Value::STRING) {
306 return ConvertOnePath(scope, function, inputs,
307 from_dir, to_dir, convert_to_system_absolute,
308 sep_conversion, err);
310 } else if (inputs.type() == Value::LIST) {
311 result = Value(function, Value::LIST);
312 result.list_value().reserve(inputs.list_value().size());
314 for (size_t i = 0; i < inputs.list_value().size(); i++) {
315 result.list_value().push_back(
316 ConvertOnePath(scope, function, inputs.list_value()[i],
317 from_dir, to_dir, convert_to_system_absolute,
318 sep_conversion, err));
319 if (err->has_error()) {
320 result = Value();
321 return result;
324 return result;
327 *err = Err(function->function(),
328 "rebase_path requires a list or a string.");
329 return result;
332 } // namespace functions