aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2024-05-21 08:00:23 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2024-05-21 08:00:23 +0200
commitaf8046d5bcf2aec918b1802a7719a09ce0111662 (patch)
tree7fc10ed55b12f0edce0ec7cd0d85effc032369bf
parent6f8d29930130160ff8db5c3905bf4a692f6bf6e7 (diff)
Add $path.complete(), $path.try_normalize(), $path.try_actualize()
-rw-r--r--libbuild2/functions-path.cxx418
-rw-r--r--tests/function/path/testscript76
2 files changed, 329 insertions, 165 deletions
diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx
index 7dfbd67..2f7f159 100644
--- a/libbuild2/functions-path.cxx
+++ b/libbuild2/functions-path.cxx
@@ -193,6 +193,35 @@ namespace build2
#endif
}
+ template <typename P>
+ static bool
+ try_normalize (P& p)
+ {
+ try
+ {
+ p.normalize ();
+ return true;
+ }
+ catch (const invalid_path&) {}
+
+ return false;
+ }
+
+ template <typename P>
+ static bool
+ try_actualize (P& p)
+ {
+ try
+ {
+ p.normalize (true);
+ return true;
+ }
+ catch (const invalid_path&) {}
+ catch (const system_error&) {}
+
+ return false;
+ }
+
void
path_functions (function_map& m)
{
@@ -416,140 +445,6 @@ namespace build2
return convert<path> (move (ns)).sup (convert_to_base<path> (move (v)));
};
- // $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;};
- f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;};
-
- f["canonicalize"] += [](paths v)
- {
- for (auto& p: v)
- p.canonicalize ();
- return v;
- };
-
- f["canonicalize"] += [](dir_paths v)
- {
- for (auto& p: v)
- p.canonicalize ();
- return v;
- };
-
- f[".canonicalize"] += [](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.canonicalize ();
- else
- n.value = convert<path> (move (n)).canonicalize ().string ();
- }
- return ns;
- };
-
- // $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;};
-
- f["normalize"] += [](paths v)
- {
- for (auto& p: v)
- p.normalize ();
- return v;
- };
-
- f["normalize"] += [](dir_paths v)
- {
- for (auto& p: v)
- p.normalize ();
- return v;
- };
-
- f[".normalize"] += [](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.normalize ();
- else
- n.value = convert<path> (move (n)).normalize ().string ();
- }
- return ns;
- };
-
- // $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.
- //
- {
- auto e (f.insert ("actualize", false));
-
- e += [](path p) {p.normalize (true); return p;};
- e += [](dir_path p) {p.normalize (true); return p;};
-
- e += [](paths v)
- {
- for (auto& p: v)
- p.normalize (true);
- return v;
- };
-
- e += [](dir_paths v)
- {
- for (auto& p: v)
- p.normalize (true);
- return v;
- };
- }
-
- f.insert (".actualize", false) += [](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.normalize (true);
- else
- n.value = convert<path> (move (n)).normalize (true).string ();
- }
- return ns;
- };
-
// $directory(<paths>)
// $path.directory(<untyped>)
//
@@ -775,6 +670,261 @@ namespace build2
return extension (convert<path> (move (ns)));
};
+ // $complete(<paths>)
+ // $path.complete(<untyped>)
+ //
+ // Complete the path (or list of paths) by prepending the current working
+ // directory unless the path is already absolute.
+ //
+ f["complete"] += [](path p) {p.complete (); return p;};
+ f["complete"] += [](dir_path p) {p.complete (); return p;};
+
+ f["complete"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.complete ();
+ return v;
+ };
+
+ f["complete"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.complete ();
+ return v;
+ };
+
+ f[".complete"] += [](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.complete ();
+ else
+ n.value = convert<path> (move (n)).complete ().string ();
+ }
+ 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.
+ //
+ f["canonicalize"] += [](path p) {p.canonicalize (); return p;};
+ f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;};
+
+ f["canonicalize"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f["canonicalize"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f[".canonicalize"] += [](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.canonicalize ();
+ else
+ n.value = convert<path> (move (n)).canonicalize ().string ();
+ }
+ return ns;
+ };
+
+ // $normalize(<paths>)
+ // $path.normalize(<untyped>)
+ // $try_normalize(<path>)
+ // $path.try_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.
+ //
+ // If the resulting path would be invalid, the `$normalize()` version
+ // issues diagnostics and fails while the `$try_normalize()` version
+ // returns `null`. Note that `$try_normalize()` only accepts a single
+ // path.
+ //
+ f["normalize"] += [](path p) {p.normalize (); return p;};
+ f["normalize"] += [](dir_path p) {p.normalize (); return p;};
+
+ f["normalize"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.normalize ();
+ return v;
+ };
+
+ f["normalize"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.normalize ();
+ return v;
+ };
+
+ f[".normalize"] += [](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.normalize ();
+ else
+ n.value = convert<path> (move (n)).normalize ().string ();
+ }
+ return ns;
+ };
+
+ f["try_normalize"] += [](path p)
+ {
+ return try_normalize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ f["try_normalize"] += [](dir_path p)
+ {
+ return try_normalize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ f[".try_normalize"] += [](names ns)
+ {
+ if (ns.size () != 1)
+ throw invalid_argument ("multiple paths");
+
+ name& n (ns.front ());
+
+ bool r;
+ if (n.directory ())
+ r = try_normalize (n.dir);
+ else
+ {
+ path p (convert<path> (move (n)));
+ if ((r = try_normalize (p)))
+ n.value = move (p).string ();
+ }
+
+ return r ? value (move (ns)) : value (nullptr);
+ };
+
+ // $actualize(<paths>)
+ // $path.actualize(<untyped>)
+ // $try_actualize(<path>)
+ // $path.try_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.
+ //
+ // Only an absolute path can be actualized. If a path component does not
+ // exist, then its (and all subsequent) spelling is unchanged. Note that
+ // this is a potentially expensive operation.
+ //
+ // If the resulting path would be invalid or in case of filesystem errors
+ // (other than non-existent component), the `$actualize()` version issues
+ // diagnostics and fails while the `$try_actualize()` version returns
+ // `null`. Note that `$try_actualize()` only accepts a single path.
+ //
+ // Note that this function is not pure.
+ //
+ {
+ auto e (f.insert ("actualize", false));
+
+ e += [](path p) {p.normalize (true); return p;};
+ e += [](dir_path p) {p.normalize (true); return p;};
+
+ e += [](paths v)
+ {
+ for (auto& p: v)
+ p.normalize (true);
+ return v;
+ };
+
+ e += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.normalize (true);
+ return v;
+ };
+ }
+
+ f.insert (".actualize", false) += [](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.normalize (true);
+ else
+ n.value = convert<path> (move (n)).normalize (true).string ();
+ }
+ return ns;
+ };
+
+ {
+ auto e (f.insert ("try_actualize", false));
+
+ e += [](path p)
+ {
+ return try_actualize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ e += [](dir_path p)
+ {
+ return try_actualize (p) ? value (move (p)) : value (nullptr);
+ };
+ }
+
+ f.insert (".try_actualize", false) += [](names ns)
+ {
+ if (ns.size () != 1)
+ throw invalid_argument ("multiple paths");
+
+ name& n (ns.front ());
+
+ bool r;
+ if (n.directory ())
+ r = try_actualize (n.dir);
+ else
+ {
+ path p (convert<path> (move (n)));
+ if ((r = try_actualize (p)))
+ n.value = move (p).string ();
+ }
+
+ return r ? value (move (ns)) : value (nullptr);
+ };
+
+
+ // Note that we currently do not expose realize(). For one, it might be
+ // tricky to handle CWD overrides (on POSIX we just call realize(3)).
+ // Also, our implementation for Windows currently does not handle
+ // symlinks.
+
+
// $size(<paths>)
// $size(<path>)
//
diff --git a/tests/function/path/testscript b/tests/function/path/testscript
index 98491ea..d49e9e5 100644
--- a/tests/function/path/testscript
+++ b/tests/function/path/testscript
@@ -110,37 +110,6 @@ s = ($posix ? '/' : '\')
$* <'print $path.super_path(a/b/c, c/a)' >"false" : false
}
-: canonicalize
-:
-{
- $* <'print $canonicalize([path] a/b)' >"a$(s)b" : path
- $* <'print $canonicalize([paths] a/b a/c)' >"a$(s)b a$(s)c" : paths
- $* <'print $canonicalize([dir_path] a/b)' >"a$(s)b$s" : dir-path
- $* <'print $canonicalize([dir_paths] a/b a/c/)' >"a$(s)b$s a$(s)c$s" : dir-paths
- $* <'print $path.canonicalize(a/b)' >"a$(s)b" : untyped
- $* <'print $path.canonicalize(a/b/ a/c)' >"a$(s)b$s a$(s)c" : mixed
-}
-
-: normalize
-:
-{
- $* <'print $normalize([path] a/../b)' >"b" : path
- $* <'print $normalize([paths] a/../b a/../c)' >"b c" : paths
- $* <'print $normalize([dir_path] a/../b)' >"b$s" : dir-path
- $* <'print $normalize([dir_paths] a/../b a/../c/)' >"b$s c$s" : dir-paths
- $* <'print $path.normalize(a/../b)' >"b" : untyped
- $* <'print $path.normalize(a/../b/ a/../c)' >"b$s c" : mixed
-}
-
-: actualize
-:
-if! $posix
-{
- mkdir Foo;
- $* <'print $path.actualize($out_base/foo)' >~'/.+\\Foo/'
-}
-
-
: directory
:
{
@@ -235,6 +204,51 @@ if! $posix
EOO
}
+: complete
+:
+{
+ $* <'print $complete([path] a)' >"$~$(s)a" : path
+ $* <'print $complete([dir_path] a)' >"$~$(s)a$(s)" : dir-path
+ $* <'print $path.complete(a)' >"$~$(s)a" : untyped
+}
+
+: canonicalize
+:
+{
+ $* <'print $canonicalize([path] a/b)' >"a$(s)b" : path
+ $* <'print $canonicalize([paths] a/b a/c)' >"a$(s)b a$(s)c" : paths
+ $* <'print $canonicalize([dir_path] a/b)' >"a$(s)b$s" : dir-path
+ $* <'print $canonicalize([dir_paths] a/b a/c/)' >"a$(s)b$s a$(s)c$s" : dir-paths
+ $* <'print $path.canonicalize(a/b)' >"a$(s)b" : untyped
+ $* <'print $path.canonicalize(a/b/ a/c)' >"a$(s)b$s a$(s)c" : mixed
+}
+
+: normalize
+:
+{
+ $* <'print $normalize([path] a/../b)' >"b" : path
+ $* <'print $normalize([paths] a/../b a/../c)' >"b c" : paths
+ $* <'print $normalize([dir_path] a/../b)' >"b$s" : dir-path
+ $* <'print $normalize([dir_paths] a/../b a/../c/)' >"b$s c$s" : dir-paths
+ $* <'print $path.normalize(a/../b)' >"b" : untyped
+ $* <'print $path.normalize(a/../b/ a/../c)' >"b$s c" : mixed
+}
+
+: try_normalize
+:
+{
+ $* <'print $try_normalize([path] a/../b)' >"b" : valid
+ $* <'print $path.try_normalize($root_directory($src_root)/..)' >"[null]" : invalid
+}
+
+: actualize
+:
+if! $posix
+{
+ mkdir Foo;
+ $* <'print $path.actualize($out_base/foo)' >~'/.+\\Foo/'
+}
+
: sort
:
{