diff options
Diffstat (limited to 'libbuild2/functions-path.cxx')
-rw-r--r-- | libbuild2/functions-path.cxx | 475 |
1 files changed, 432 insertions, 43 deletions
diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx index b0acc63..4b114f5 100644 --- a/libbuild2/functions-path.cxx +++ b/libbuild2/functions-path.cxx @@ -1,7 +1,7 @@ // file : libbuild2/functions-path.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#include <libbutl/path-pattern.mxx> +#include <libbutl/path-pattern.hxx> #include <libbuild2/function.hxx> #include <libbuild2/variable.hxx> @@ -10,6 +10,9 @@ using namespace std; namespace build2 { + extern bool + functions_sort_flags (optional<names>); // functions-builtin.cxx + static value path_thunk (const scope* base, vector_view<value> args, @@ -96,6 +99,20 @@ namespace build2 } } + template <typename P> + static inline P + relative (const P& p, const dir_path& d) + { + try + { + return p.relative (d); // Note: cannot move due to diagnostics. + } + catch (const invalid_path&) + { + fail << "'" << p << "' cannot be made relative to '" << d << "'" << endf; + } + } + using butl::path_match; // Return true if a path matches the pattern. See path_match() overloads @@ -137,14 +154,66 @@ namespace build2 return path_match (entry, pattern, *start); } + // Don't fail for absolute paths on Windows and, for example, just return + // c:/foo for c:\foo. + // + template <typename P> + static inline string + posix_string (P&& p) + { +#ifndef _WIN32 + return move (p).posix_string (); +#else + if (p.relative ()) + return move (p).posix_string (); + + // Note: also handles root directories. + // + dir_path d (p.root_directory ()); + return d.string () + '/' + p.leaf (d).posix_string (); +#endif + } + + // Similar to the above don't fail for absolute paths on Windows. + // + template <typename P> + static inline string + posix_representation (P&& p) + { +#ifndef _WIN32 + return move (p).posix_representation (); +#else + if (p.relative ()) + return move (p).posix_representation (); + + // Note: also handles root directories. + // + dir_path d (p.root_directory ()); + return d.string () + '/' + p.leaf (d).posix_representation (); +#endif + } + void path_functions (function_map& m) { function_family f (m, "path", &path_thunk); - // string + // $string(<paths>) + // + // Return the traditional string representation of a path (or a list of + // string representations for a list of paths). In particular, for + // directory paths, the traditional representation does not include the + // trailing directory separator (except for the POSIX root directory). See + // `$representation()` below for the precise string representation. // - f["string"] += [](path p) {return move (p).string ();}; + + // Note that we must handle NULL values (relied upon by the parser + // to provide conversion semantics consistent with untyped values). + // + f["string"] += [](path* p) + { + return p != nullptr ? move (*p).string () : string (); + }; f["string"] += [](paths v) { @@ -162,7 +231,53 @@ namespace build2 return r; }; - // representation + // $posix_string(<paths>) + // $path.posix_string(<untyped>) + // + // Return the traditional string representation of a path (or a list of + // string representations for a list of paths) using the POSIX directory + // separators (forward slashes). + // + f["posix_string"] += [](path p) {return posix_string (move (p));}; + f["posix_string"] += [](dir_path p) {return posix_string (move (p));}; + + f["posix_string"] += [](paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_string (move (p))); + return r; + }; + + f["posix_string"] += [](dir_paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_string (move (p))); + return r; + }; + + f[".posix_string"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of strings. + // + for (name& n: ns) + { + n = n.directory () + ? posix_string (move (n.dir)) + : posix_string (convert<path> (move (n))); + } + return ns; + }; + + // $representation(<paths>) + // + // Return the precise string representation of a path (or a list of string + // representations for a list of paths). In particular, for directory + // paths, the precise representation includes the trailing directory + // separator. See `$string()` above for the traditional string + // representation. // f["representation"] += [](path p) {return move (p).representation ();}; @@ -182,8 +297,61 @@ namespace build2 return r; }; - // canonicalize + // $posix_representation(<paths>) + // $path.posix_representation(<untyped>) + // + // Return the precise string representation of a path (or a list of string + // representations for a list of paths) using the POSIX directory + // separators (forward slashes). // + f["posix_representation"] += [](path p) + { + return posix_representation (move (p)); + }; + + f["posix_representation"] += [](dir_path p) + { + return posix_representation (move (p)); + }; + + f["posix_representation"] += [](paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_representation (move (p))); + return r; + }; + + f["posix_representation"] += [](dir_paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_representation (move (p))); + return r; + }; + + f[".posix_representation"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of strings. + // + for (name& n: ns) + { + n = n.directory () + ? posix_representation (move (n.dir)) + : posix_representation (convert<path> (move (n))); + } + return ns; + }; + + // $canonicalize(<paths>) + // $path.canonicalize(<untyped>) + // + // Canonicalize the path (or list of paths) by converting all the + // directory separators to the canonical form for the host platform. Note + // that multiple directory separators are not collapsed. + // + // @@ TODO: add ability to specify alternative separator. // f["canonicalize"] += [](path p) {p.canonicalize (); return p;}; @@ -219,7 +387,13 @@ namespace build2 return ns; }; - // normalize + // $normalize(<paths>) + // $path.normalize(<untyped>) + // + // Normalize the path (or list of paths) by collapsing the `.` and `..` + // components if possible, collapsing multiple directory separators, and + // converting all the directory separators to the canonical form for the + // host platform. // f["normalize"] += [](path p) {p.normalize (); return p;}; f["normalize"] += [](dir_path p) {p.normalize (); return p;}; @@ -254,7 +428,16 @@ namespace build2 return ns; }; - // actualize + // $actualize(<paths>) + // $path.actualize(<untyped>) + // + // Actualize the path (or list of paths) by first normalizing it and then + // for host platforms with case-insensitive filesystems obtaining the + // actual spelling of the path. + // + // Note that only an absolute path can be actualized. If a path component + // does not exist, then its (and all subsequent) spelling is + // unchanged. This is a potentially expensive operation. // // Note that this function is not pure. // @@ -295,7 +478,12 @@ namespace build2 return ns; }; - // directory + // $directory(<paths>) + // $path.directory(<untyped>) + // + // Return the directory part of a path (or a list of directory parts for a + // list of paths) or an empty path if there is no directory. A directory of + // a root directory is an empty path. // f["directory"] += &path::directory; @@ -329,41 +517,58 @@ namespace build2 return ns; }; - // base + // $root_directory(<paths>) + // $path.root_directory(<untyped>) // - f["base"] += &path::base; + // Return the root directory of a path (or a list of root directories for + // a list of paths) or an empty path if the specified path is not + // absolute. + // + f["root_directory"] += &path::root_directory; - f["base"] += [](paths v) + f["root_directory"] += [](paths v) { - for (path& p: v) - p = p.base (); - return v; + dir_paths r; + for (const path& p: v) + r.push_back (p.root_directory ()); + return r; }; - f["base"] += [](dir_paths v) + f["root_directory"] += [](dir_paths v) { for (dir_path& p: v) - p = p.base (); + p = p.root_directory (); return v; }; - f[".base"] += [](names ns) + f[".root_directory"] += [](names ns) { // For each path decide based on the presence of a trailing slash - // whether it is a directory. Return as untyped list of (potentially - // mixed) paths. + // whether it is a directory. Return as list of directory names. // for (name& n: ns) { if (n.directory ()) - n.dir = n.dir.base (); + n.dir = n.dir.root_directory (); else - n.value = convert<path> (move (n)).base ().string (); + n = convert<path> (move (n)).root_directory (); } return ns; }; - // leaf + // $leaf(<paths>) + // $path.leaf(<untyped>) + // $leaf(<paths>, <dir-path>) + // $path.leaf(<untyped>, <dir-path>) + // + // First form (one argument): return the last component of a path (or a + // list of last components for a list of paths). + // + // Second form (two arguments): return a path without the specified + // directory part (or a list of paths without the directory part for a + // list of paths). Return an empty path if the paths are the same. Issue + // diagnostics and fail if the directory is not a prefix of the + // path. Note: expects both paths to be normalized. // f["leaf"] += &path::leaf; @@ -402,7 +607,92 @@ namespace build2 return ns; }; - // extension + // $relative(<paths>, <dir-path>) + // $path.relative(<untyped>, <dir-path>) + // + // Return the path relative to the specified directory that is equivalent + // to the specified path (or a list of relative paths for a list of + // specified paths). Issue diagnostics and fail if a relative path cannot + // be derived (for example, paths are on different drives on Windows). + // + f["relative"] += [](path p, dir_path d) + { + return relative (p, d); + }; + + f["relative"] += [](paths v, dir_path d) + { + for (path& p: v) + p = relative (p, d); + return v; + }; + + f["relative"] += [](dir_paths v, dir_path d) + { + for (dir_path& p: v) + p = relative (p, d); + return v; + }; + + f[".relative"] += [](names ns, dir_path d) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir = relative (n.dir, d); + else + n.value = relative (convert<path> (move (n)), d).string (); + } + return ns; + }; + + // $base(<paths>) + // $path.base(<untyped>) + // + // Return the base part (without the extension) of a path (or a list of + // base parts for a list of paths). + // + f["base"] += &path::base; + + f["base"] += [](paths v) + { + for (path& p: v) + p = p.base (); + return v; + }; + + f["base"] += [](dir_paths v) + { + for (dir_path& p: v) + p = p.base (); + return v; + }; + + f[".base"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir = n.dir.base (); + else + n.value = convert<path> (move (n)).base ().string (); + } + return ns; + }; + + // $extension(<path>) + // $path.extension(<untyped>) + // + // Return the extension part (without the dot) of a path or empty string + // if there is no extension. // f["extension"] += &extension; @@ -411,34 +701,118 @@ namespace build2 return extension (convert<path> (move (ns))); }; - // $path.match(<val>, <pat> [, <start>]) + // $size(<paths>) + // $size(<path>) + // + // First form: return the number of elements in the paths sequence. + // + // Second form: return the number of characters (bytes) in the path. Note + // that for `dir_path` the result does not include the trailing directory + // separator (except for the POSIX root directory). + // + // + f["size"] += [] (paths v) {return v.size ();}; + f["size"] += [] (dir_paths v) {return v.size ();}; + + f["size"] += [] (path v) {return v.size ();}; + f["size"] += [] (dir_path v) {return v.size ();}; + + // $sort(<paths>[, <flags>]) // - // Match a filesystem entry name against a name pattern (both are strings), - // or a filesystem entry path against a path pattern. For the latter case - // the start directory may also be required (see below). The semantics of - // the pattern and name/entry arguments is determined according to the + // Sort paths in ascending order. Note that on host platforms with a + // case-insensitive filesystem the order is case-insensitive. + // + // The following flags are supported: + // + // dedup - in addition to sorting also remove duplicates + // + f["sort"] += [](paths v, optional<names> fs) + { + sort (v.begin (), v.end ()); + + if (functions_sort_flags (move (fs))) + v.erase (unique (v.begin(), v.end()), v.end ()); + + return v; + }; + + f["sort"] += [](dir_paths v, optional<names> fs) + { + sort (v.begin (), v.end ()); + + if (functions_sort_flags (move (fs))) + v.erase (unique (v.begin(), v.end()), v.end ()); + + return v; + }; + + // $find(<paths>, <path>) + // + // Return true if the paths sequence contains the specified path. Note + // that on host platforms with a case-insensitive filesystem the + // comparison is case-insensitive. + // + f["find"] += [](paths vs, value v) + { + return find (vs.begin (), vs.end (), + convert<path> (move (v))) != vs.end (); + }; + + f["find"] += [](dir_paths vs, value v) + { + return find (vs.begin (), vs.end (), + convert<dir_path> (move (v))) != vs.end (); + }; + + // $find_index(<paths>, <path>) + // + // Return the index of the first element in the paths sequence that is + // equal to the specified path or `$size(paths)` if none is found. Note + // that on host platforms with a case-insensitive filesystem the + // comparison is case-insensitive. + // + f["find_index"] += [](paths vs, value v) + { + auto i (find (vs.begin (), vs.end (), convert<path> (move (v)))); + return i != vs.end () ? i - vs.begin () : vs.size (); + }; + + f["find_index"] += [](dir_paths vs, value v) + { + auto i (find (vs.begin (), vs.end (), convert<dir_path> (move (v)))); + return i != vs.end () ? i - vs.begin () : vs.size (); + }; + + // $path.match(<entry>, <pattern>[, <start-dir>]) + // + // Match a filesystem entry name against a name pattern (both are + // strings), or a filesystem entry path against a path pattern. For the + // latter case the start directory may also be required (see below). The + // pattern is a shell-like wildcard pattern. The semantics of the + // <pattern> and <entry> arguments is determined according to the // following rules: // - // - The arguments must be of the string or path types, or be untyped. + // 1. The arguments must be of the string or path types, or be untyped. // - // - If one of the arguments is typed, then the other one must be of the - // same type or be untyped. In the later case, an untyped argument is - // converted to the type of the other argument. + // 2. If one of the arguments is typed, then the other one must be of the + // same type or be untyped. In the later case, an untyped argument is + // converted to the type of the other argument. // - // - If both arguments are untyped and the start directory is specified, - // then the arguments are converted to the path type. + // 3. If both arguments are untyped and the start directory is specified, + // then the arguments are converted to the path type. // - // - If both arguments are untyped and the start directory is not - // specified, then, if one of the arguments is syntactically a path (the - // value contains a directory separator), convert them to the path type, - // otherwise to the string type (match as names). + // 4. If both arguments are untyped and the start directory is not + // specified, then, if one of the arguments is syntactically a path (the + // value contains a directory separator), then they are converted to the + // path type, otherwise -- to the string type (match as names). // - // If pattern and entry paths are both either absolute or relative and - // non-empty, and the first pattern component is not a self-matching - // wildcard (doesn't contain ***), then the start directory is not - // required, and is ignored if specified. Otherwise, the start directory - // must be specified and be an absolute path. + // If pattern and entry paths are both either absolute or relative and not + // empty, and the first pattern component is not a self-matching wildcard + // (doesn't contain `***`), then the start directory is not required, and + // is ignored if specified. Otherwise, the start directory must be + // specified and be an absolute path. // + // Name matching. // f[".match"] += [](string name, string pattern) @@ -498,6 +872,11 @@ namespace build2 // function_family b (m, "builtin", &path_thunk); + // Note that while we should normally handle NULL values (relied upon by + // the parser to provide concatenation semantics consistent with untyped + // values), the result will unlikely be what the user expected, especially + // if the NULL value is on the LHS. So for now we keep it a bit tighter. + // b[".concat"] += &concat_path_string; b[".concat"] += &concat_dir_path_string; @@ -510,5 +889,15 @@ namespace build2 { return concat_dir_path_string (move (l), convert<string> (move (ur))); }; + + b[".concat"] += [](dir_path l, dir_path r) + { + return value (move (l /= r)); + }; + + b[".concat"] += [](dir_path l, path r) + { + return value (path_cast<path> (move (l)) /= r); + }; } } |