diff --git a/CHANGELOG.md b/CHANGELOG.md index fc448e9..9ed1b70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Add `resolve` function, which resolves relative paths to absolute paths based on the current working directory. ## v2.3.2 - 26 December 2025 - Fix bug where unknown errors were not properly converted to the Unknown variant in Erlang ffi. diff --git a/gleam.toml b/gleam.toml index b974558..cfb8b9d 100644 --- a/gleam.toml +++ b/gleam.toml @@ -4,7 +4,7 @@ description = "Basic file operations that work on all targets" licences = ["Apache-2.0"] repository = { type = "github", user = "bcpeinhardt", repo = "simplifile" } -gleam = ">= 1.9.0" +gleam = ">= 1.11.0" # links = [{ title = "Website", href = "https://gleam.run" }] [javascript.deno] diff --git a/src/simplifile.gleam b/src/simplifile.gleam index b38c40c..58dfd62 100644 --- a/src/simplifile.gleam +++ b/src/simplifile.gleam @@ -743,3 +743,29 @@ pub fn current_directory() -> Result(String, FileError) { @external(erlang, "file", "get_cwd") fn erl_do_current_directory() -> Result(List(UtfCodepoint), FileError) + +/// Converts a relative path to an absolute path which starts in the current working directory. +/// +/// Returns an error if the relative path could not be resolved. +/// +/// # Example: +/// ```gleam +/// // Resolving a relative path resolves the full absolute path. +/// // Assume the current working directory is /home/lucy. +/// assert resolve("./tmp/../gleam") == Ok("/home/lucy/gleam") +/// +/// // Resolving an absolute path returns that absolute path. +/// assert resolve("/tmp/gleam") == Ok("/tmp/gleam") +/// +/// // Tried to go two directories back, but was only able to go one back. Path is unresolvable. +/// assert resolve("/tmp/../..") == Error(Enoent) +/// ``` +pub fn resolve(path path: String) -> Result(String, FileError) { + do_resolve(path) + |> filepath.expand + |> result.replace_error(Enoent) +} + +@external(erlang, "filename", "absname") +@external(javascript, "./simplifile_js.mjs", "resolve") +fn do_resolve(path: String) -> String diff --git a/src/simplifile_js.mjs b/src/simplifile_js.mjs index 1c003df..8a4bcac 100644 --- a/src/simplifile_js.mjs +++ b/src/simplifile_js.mjs @@ -398,3 +398,13 @@ function cast_error(error_code) { return new $simplifile.Unknown(error_code); } } + +/** + * Resolves a relative path to an absolute path based on the current working directory. + * + * @param {string} filepath The file path to resolve + * @returns {string} The resolved file path + */ +export function resolve(filepath) { + return path.resolve(filepath); +} diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 0cbe3c2..157d76b 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -781,3 +781,23 @@ pub fn unknown_errors_return_unknown_test() { @external(erlang, "simplifile_erl", "create_directory") @external(javascript, "./simplifile_js.mjs", "createDirectory") fn create_directory_with_bad_arg(arg: #(Nil, Nil)) -> Result(Nil, FileError) + +pub fn resolve_empty_path_test() { + assert simplifile.resolve("") == simplifile.current_directory() +} + +pub fn resolve_relative_path_test() { + let assert Ok(actual) = simplifile.resolve("./simplifile_test.gleam") + let assert Ok(cwd) = simplifile.current_directory() + let expected = cwd <> "/simplifile_test.gleam" + + assert actual == expected +} + +// only works on Unix(-like) systems +pub fn resolve_absolute_path_test() { + let initial = "/bin/hello" + let assert Ok(actual) = simplifile.resolve(initial) + + assert initial == actual +}