├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── gleam.toml ├── manifest.toml ├── src ├── filepath.gleam ├── filepath_ffi.erl └── filepath_ffi.mjs └── test └── filepath_test.gleam /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test-windows: 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: erlef/setup-beam@v1 15 | with: 16 | otp-version: "27" 17 | gleam-version: "1.9.0" 18 | rebar3-version: "3" 19 | - run: gleam test --target erlang 20 | - run: gleam test --target javascript 21 | 22 | test-linux: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: erlef/setup-beam@v1 27 | with: 28 | otp-version: "27" 29 | gleam-version: "1.9.0" 30 | rebar3-version: "3" 31 | - run: gleam test --target erlang 32 | - run: gleam test --target javascript 33 | - run: gleam format --check src test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.1.2 - 2025-04-01 4 | 5 | - Fixed a bug with `expand` adding a trailing slash on Windows. 6 | 7 | ## v1.1.1 - 2025-03-14 8 | 9 | - Fixed a bug with `join` not giving correct results when joining to the 10 | filesystem root. 11 | 12 | ## v1.1.0 - 2024-11-19 13 | 14 | - Updated for `gleam_stdlib` v0.43.0. 15 | 16 | ## v1.0.0 - 2024-02-08 17 | 18 | - All existing functions now support Windows paths when run on Windows. 19 | - The `filepath` module gains the `split_unix` and `split_windows` functions. 20 | 21 | ## v0.2.0 - 2024-02-08 22 | 23 | - The `filepath` module gains the `strip_extension` function. 24 | - Fixed a bug where the `extension` function could return the incorrect value 25 | for files with no extension in a directory with a `.` in its name. 26 | 27 | ## v0.1.0 - 2023-11-11 28 | 29 | - Initial Release. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filepath 2 | 3 | Work with file paths in Gleam! 4 | 5 | [![Package Version](https://img.shields.io/hexpm/v/filepath)](https://hex.pm/packages/filepath) 6 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/filepath/) 7 | 8 | This package works on both Unix and Windows systems, and can run on both Erlang 9 | or JavaScript runtimes. 10 | 11 | ```sh 12 | gleam add filepath 13 | ``` 14 | ```gleam 15 | import filepath 16 | 17 | pub fn main() { 18 | let path = filepath.join("/home/lucy", "pokemon-cards") 19 | // -> "/home/lucy/pokemon-cards" 20 | } 21 | ``` 22 | 23 | Documentation can be found here: . 24 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "filepath" 2 | version = "1.1.2" 3 | description = "Work with file paths in Gleam!" 4 | licences = ["Apache-2.0"] 5 | repository = { type = "github", user = "lpil", repo = "filepath" } 6 | links = [ 7 | { title = "Website", href = "https://gleam.run" }, 8 | { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, 9 | ] 10 | 11 | [dependencies] 12 | gleam_stdlib = ">= 0.43.0 and < 2.0.0" 13 | 14 | [dev-dependencies] 15 | gleeunit = "~> 1.0" 16 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_stdlib", version = "0.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" }, 6 | { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 7 | ] 8 | 9 | [requirements] 10 | gleam_stdlib = { version = ">= 0.43.0 and < 2.0.0" } 11 | gleeunit = { version = "~> 1.0" } 12 | -------------------------------------------------------------------------------- /src/filepath.gleam: -------------------------------------------------------------------------------- 1 | //// Work with file paths in Gleam! 2 | //// 3 | //// This library expects paths to be valid unicode. If you need to work with 4 | //// non-unicode paths you will need to convert them to unicode before using 5 | //// this library. 6 | 7 | // References: 8 | // https://github.com/erlang/otp/blob/master/lib/stdlib/src/filename.erl 9 | // https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/path.ex 10 | // https://github.com/elixir-lang/elixir/blob/main/lib/elixir/test/elixir/path_test.exs 11 | // https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/path/match.go 12 | 13 | import gleam/bool 14 | import gleam/list 15 | import gleam/option.{type Option, None, Some} 16 | import gleam/result 17 | import gleam/string 18 | 19 | @external(erlang, "filepath_ffi", "is_windows") 20 | @external(javascript, "./filepath_ffi.mjs", "is_windows") 21 | fn is_windows() -> Bool 22 | 23 | /// Join two paths together. 24 | /// 25 | /// This function does not expand `..` or `.` segments, use the `expand` 26 | /// function to do this. 27 | /// 28 | /// ## Examples 29 | /// 30 | /// ```gleam 31 | /// join("/usr/local", "bin") 32 | /// // -> "/usr/local/bin" 33 | /// ``` 34 | /// 35 | pub fn join(left: String, right: String) -> String { 36 | case left, right { 37 | _, "/" -> left 38 | "", _ -> relative(right) 39 | "/", "/" <> _ -> right 40 | "/", _ -> left <> right 41 | _, _ -> 42 | remove_trailing_slash(left) 43 | |> string.append("/") 44 | |> string.append(relative(right)) 45 | } 46 | |> remove_trailing_slash 47 | } 48 | 49 | fn relative(path: String) -> String { 50 | case path { 51 | "/" <> path -> relative(path) 52 | _ -> path 53 | } 54 | } 55 | 56 | fn remove_trailing_slash(path: String) -> String { 57 | case string.ends_with(path, "/") { 58 | True -> string.drop_end(path, 1) 59 | False -> path 60 | } 61 | } 62 | 63 | // TODO: Windows support 64 | /// Split a path into its segments. 65 | /// 66 | /// When running on Windows both `/` and `\` are treated as path separators, and 67 | /// if the path starts with a drive letter then the drive letter then it is 68 | /// lowercased. 69 | /// 70 | /// ## Examples 71 | /// 72 | /// ```gleam 73 | /// split("/usr/local/bin", "bin") 74 | /// // -> ["/", "usr", "local", "bin"] 75 | /// ``` 76 | /// 77 | pub fn split(path: String) -> List(String) { 78 | case is_windows() { 79 | True -> split_windows(path) 80 | False -> split_unix(path) 81 | } 82 | } 83 | 84 | /// Split a path into its segments, using `/` as the path separator. 85 | /// 86 | /// Typically you would want to use `split` instead of this function, but if you 87 | /// want non-Windows path behaviour on a Windows system then you can use this 88 | /// function. 89 | /// 90 | /// ## Examples 91 | /// 92 | /// ```gleam 93 | /// split("/usr/local/bin", "bin") 94 | /// // -> ["/", "usr", "local", "bin"] 95 | /// ``` 96 | /// 97 | pub fn split_unix(path: String) -> List(String) { 98 | case string.split(path, "/") { 99 | [""] -> [] 100 | ["", ..rest] -> ["/", ..rest] 101 | rest -> rest 102 | } 103 | |> list.filter(fn(x) { x != "" }) 104 | } 105 | 106 | /// Split a path into its segments, using `/` and `\` as the path separators. If 107 | /// there is a drive letter at the start of the path then it is lowercased. 108 | /// 109 | /// Typically you would want to use `split` instead of this function, but if you 110 | /// want Windows path behaviour on a non-Windows system then you can use this 111 | /// function. 112 | /// 113 | /// ## Examples 114 | /// 115 | /// ```gleam 116 | /// split("/usr/local/bin", "bin") 117 | /// // -> ["/", "usr", "local", "bin"] 118 | /// ``` 119 | /// 120 | pub fn split_windows(path: String) -> List(String) { 121 | let #(drive, path) = pop_windows_drive_specifier(path) 122 | 123 | let segments = 124 | string.split(path, "/") 125 | |> list.flat_map(string.split(_, "\\")) 126 | 127 | let segments = case drive { 128 | Some(drive) -> [drive, ..segments] 129 | None -> segments 130 | } 131 | 132 | case segments { 133 | [""] -> [] 134 | ["", ..rest] -> ["/", ..rest] 135 | rest -> rest 136 | } 137 | } 138 | 139 | const codepoint_slash = 47 140 | 141 | const codepoint_backslash = 92 142 | 143 | const codepoint_colon = 58 144 | 145 | const codepoint_a = 65 146 | 147 | const codepoint_z = 90 148 | 149 | const codepoint_a_up = 97 150 | 151 | const codepoint_z_up = 122 152 | 153 | fn pop_windows_drive_specifier(path: String) -> #(Option(String), String) { 154 | let start = string.slice(from: path, at_index: 0, length: 3) 155 | let codepoints = string.to_utf_codepoints(start) 156 | case list.map(codepoints, string.utf_codepoint_to_int) { 157 | [drive, colon, slash] 158 | if { slash == codepoint_slash || slash == codepoint_backslash } 159 | && colon == codepoint_colon 160 | && { 161 | drive >= codepoint_a 162 | && drive <= codepoint_z 163 | || drive >= codepoint_a_up 164 | && drive <= codepoint_z_up 165 | } 166 | -> { 167 | let drive_letter = string.slice(from: path, at_index: 0, length: 1) 168 | let drive = string.lowercase(drive_letter) <> ":/" 169 | let path = string.drop_start(path, 3) 170 | #(Some(drive), path) 171 | } 172 | _ -> #(None, path) 173 | } 174 | } 175 | 176 | /// Get the file extension of a path. 177 | /// 178 | /// ## Examples 179 | /// 180 | /// ```gleam 181 | /// extension("src/main.gleam") 182 | /// // -> Ok("gleam") 183 | /// ``` 184 | /// 185 | /// ```gleam 186 | /// extension("package.tar.gz") 187 | /// // -> Ok("gz") 188 | /// ``` 189 | /// 190 | pub fn extension(path: String) -> Result(String, Nil) { 191 | let file = base_name(path) 192 | case string.split(file, ".") { 193 | ["", _] -> Error(Nil) 194 | [_, extension] -> Ok(extension) 195 | [_, ..rest] -> list.last(rest) 196 | _ -> Error(Nil) 197 | } 198 | } 199 | 200 | /// Remove the extension from a file, if it has any. 201 | /// 202 | /// ## Examples 203 | /// 204 | /// ```gleam 205 | /// strip_extension("src/main.gleam") 206 | /// // -> "src/main" 207 | /// ``` 208 | /// 209 | /// ```gleam 210 | /// strip_extension("package.tar.gz") 211 | /// // -> "package.tar" 212 | /// ``` 213 | /// 214 | /// ```gleam 215 | /// strip_extension("src/gleam") 216 | /// // -> "src/gleam" 217 | /// ``` 218 | /// 219 | pub fn strip_extension(path: String) -> String { 220 | case extension(path) { 221 | Ok(extension) -> 222 | // Since the extension string doesn't have a leading `.` 223 | // we drop a grapheme more to remove that as well. 224 | string.drop_end(path, string.length(extension) + 1) 225 | Error(Nil) -> path 226 | } 227 | } 228 | 229 | // TODO: windows support 230 | /// Get the base name of a path, that is the name of the file without the 231 | /// containing directory. 232 | /// 233 | /// ## Examples 234 | /// 235 | /// ```gleam 236 | /// base_name("/usr/local/bin") 237 | /// // -> "bin" 238 | /// ``` 239 | /// 240 | pub fn base_name(path: String) -> String { 241 | use <- bool.guard(when: path == "/", return: "") 242 | 243 | path 244 | |> split 245 | |> list.last 246 | |> result.unwrap("") 247 | } 248 | 249 | // TODO: windows support 250 | /// Get the directory name of a path, that is the path without the file name. 251 | /// 252 | /// ## Examples 253 | /// 254 | /// ```gleam 255 | /// directory_name("/usr/local/bin") 256 | /// // -> "/usr/local" 257 | /// ``` 258 | /// 259 | pub fn directory_name(path: String) -> String { 260 | let path = remove_trailing_slash(path) 261 | case path { 262 | "/" <> rest -> get_directory_name(string.to_graphemes(rest), "/", "") 263 | _ -> get_directory_name(string.to_graphemes(path), "", "") 264 | } 265 | } 266 | 267 | fn get_directory_name( 268 | path: List(String), 269 | acc: String, 270 | segment: String, 271 | ) -> String { 272 | case path { 273 | ["/", ..rest] -> get_directory_name(rest, acc <> segment, "/") 274 | [first, ..rest] -> get_directory_name(rest, acc, segment <> first) 275 | [] -> acc 276 | } 277 | } 278 | 279 | // TODO: windows support 280 | /// Check if a path is absolute. 281 | /// 282 | /// ## Examples 283 | /// 284 | /// ```gleam 285 | /// is_absolute("/usr/local/bin") 286 | /// // -> True 287 | /// ``` 288 | /// 289 | /// ```gleam 290 | /// is_absolute("usr/local/bin") 291 | /// // -> False 292 | /// ``` 293 | /// 294 | pub fn is_absolute(path: String) -> Bool { 295 | string.starts_with(path, "/") 296 | } 297 | 298 | //TODO: windows support 299 | /// Expand `..` and `.` segments in a path. 300 | /// 301 | /// If the path has a `..` segment that would go up past the root of the path 302 | /// then an error is returned. This may be useful to example to ensure that a 303 | /// path specified by a user does not go outside of a directory. 304 | /// 305 | /// If the path is absolute then the result will always be absolute. 306 | /// 307 | /// ## Examples 308 | /// 309 | /// ```gleam 310 | /// expand("/usr/local/../bin") 311 | /// // -> Ok("/usr/bin") 312 | /// ``` 313 | /// 314 | /// ```gleam 315 | /// expand("/tmp/../..") 316 | /// // -> Error(Nil) 317 | /// ``` 318 | /// 319 | /// ```gleam 320 | /// expand("src/../..") 321 | /// // -> Error("..") 322 | /// ``` 323 | /// 324 | pub fn expand(path: String) -> Result(String, Nil) { 325 | let is_absolute = is_absolute(path) 326 | let result = 327 | path 328 | |> split 329 | |> root_slash_to_empty 330 | |> expand_segments([]) 331 | |> result.map(remove_trailing_slash) 332 | 333 | case is_absolute && result == Ok("") { 334 | True -> Ok("/") 335 | False -> result 336 | } 337 | } 338 | 339 | fn expand_segments( 340 | path: List(String), 341 | base: List(String), 342 | ) -> Result(String, Nil) { 343 | case base, path { 344 | // Going up past the root (empty string in this representation) 345 | [""], ["..", ..] -> Error(Nil) 346 | 347 | // Going up past the top of a relative path 348 | [], ["..", ..] -> Error(Nil) 349 | 350 | // Going up successfully 351 | [_, ..base], ["..", ..path] -> expand_segments(path, base) 352 | 353 | // Discarding `.` 354 | _, [".", ..path] -> expand_segments(path, base) 355 | 356 | // Adding a segment 357 | _, [s, ..path] -> expand_segments(path, [s, ..base]) 358 | 359 | // Done! 360 | _, [] -> Ok(string.join(list.reverse(base), "/")) 361 | } 362 | } 363 | 364 | fn root_slash_to_empty(segments: List(String)) -> List(String) { 365 | case segments { 366 | ["/", ..rest] -> ["", ..rest] 367 | _ -> segments 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/filepath_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(filepath_ffi). 2 | 3 | -export([is_windows/0]). 4 | 5 | is_windows() -> 6 | case os:type() of 7 | {win32, _} -> true; 8 | _ -> false 9 | end. 10 | -------------------------------------------------------------------------------- /src/filepath_ffi.mjs: -------------------------------------------------------------------------------- 1 | export function is_windows() { 2 | return ( 3 | globalThis?.process?.platform === "win32" || 4 | globalThis?.Deno?.build?.os === "windows" 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /test/filepath_test.gleam: -------------------------------------------------------------------------------- 1 | import filepath 2 | import gleeunit 3 | import gleeunit/should 4 | 5 | pub fn main() { 6 | gleeunit.main() 7 | } 8 | 9 | @external(erlang, "filepath_ffi", "is_windows") 10 | @external(javascript, "./filepath_ffi.mjs", "is_windows") 11 | fn is_windows() -> Bool 12 | 13 | fn windows_only(f: fn() -> whatever) -> Nil { 14 | case is_windows() { 15 | True -> { 16 | f() 17 | Nil 18 | } 19 | False -> Nil 20 | } 21 | } 22 | 23 | pub fn split_0_test() { 24 | filepath.split("") 25 | |> should.equal([]) 26 | } 27 | 28 | pub fn split_1_test() { 29 | filepath.split("file") 30 | |> should.equal(["file"]) 31 | } 32 | 33 | pub fn split_2_test() { 34 | filepath.split("/usr/local/bin") 35 | |> should.equal(["/", "usr", "local", "bin"]) 36 | } 37 | 38 | pub fn split_3_test() { 39 | use <- windows_only 40 | filepath.split("C:\\one\\two") 41 | |> should.equal(["c:/", "one", "two"]) 42 | } 43 | 44 | pub fn split_4_test() { 45 | use <- windows_only 46 | filepath.split("C:/one/two") 47 | |> should.equal(["c:/", "one", "two"]) 48 | } 49 | 50 | pub fn split_unix_0_test() { 51 | filepath.split_unix("") 52 | |> should.equal([]) 53 | } 54 | 55 | pub fn split_unix_1_test() { 56 | filepath.split_unix("file") 57 | |> should.equal(["file"]) 58 | } 59 | 60 | pub fn split_unix_2_test() { 61 | filepath.split_unix("/usr/local/bin") 62 | |> should.equal(["/", "usr", "local", "bin"]) 63 | } 64 | 65 | pub fn split_unix_3_test() { 66 | filepath.split_unix("C:\\one\\two") 67 | |> should.equal(["C:\\one\\two"]) 68 | } 69 | 70 | pub fn split_unix_4_test() { 71 | filepath.split_unix("C:/one/two") 72 | |> should.equal(["C:", "one", "two"]) 73 | } 74 | 75 | pub fn split_windows_0_test() { 76 | filepath.split_windows("") 77 | |> should.equal([]) 78 | } 79 | 80 | pub fn split_windows_1_test() { 81 | filepath.split_windows("file") 82 | |> should.equal(["file"]) 83 | } 84 | 85 | pub fn split_windows_2_test() { 86 | filepath.split_windows("/usr/local/bin") 87 | |> should.equal(["/", "usr", "local", "bin"]) 88 | } 89 | 90 | pub fn split_windows_3_test() { 91 | filepath.split_windows("C:\\one\\two") 92 | |> should.equal(["c:/", "one", "two"]) 93 | } 94 | 95 | pub fn split_windows_4_test() { 96 | filepath.split_windows("C:/one/two") 97 | |> should.equal(["c:/", "one", "two"]) 98 | } 99 | 100 | pub fn split_windows_5_test() { 101 | filepath.split_windows("::\\one\\two") 102 | |> should.equal(["::", "one", "two"]) 103 | } 104 | 105 | pub fn split_windows_6_test() { 106 | filepath.split_windows("::/one/two") 107 | |> should.equal(["::", "one", "two"]) 108 | } 109 | 110 | pub fn join_0_test() { 111 | filepath.join("/one", "two") 112 | |> should.equal("/one/two") 113 | } 114 | 115 | pub fn join_1_test() { 116 | filepath.join("~", "one") 117 | |> should.equal("~/one") 118 | } 119 | 120 | pub fn join_2_test() { 121 | filepath.join("", "two") 122 | |> should.equal("two") 123 | } 124 | 125 | pub fn join_3_test() { 126 | filepath.join("two", "") 127 | |> should.equal("two") 128 | } 129 | 130 | pub fn join_4_test() { 131 | filepath.join("", "/two") 132 | |> should.equal("two") 133 | } 134 | 135 | pub fn join_5_test() { 136 | filepath.join("/two", "") 137 | |> should.equal("/two") 138 | } 139 | 140 | pub fn join_6_test() { 141 | filepath.join("one", "/two") 142 | |> should.equal("one/two") 143 | } 144 | 145 | pub fn join_7_test() { 146 | filepath.join("/one", "/two") 147 | |> should.equal("/one/two") 148 | } 149 | 150 | pub fn join_8_test() { 151 | filepath.join("/one", "/two") 152 | |> should.equal("/one/two") 153 | } 154 | 155 | pub fn join_9_test() { 156 | filepath.join("/one", "./two") 157 | |> should.equal("/one/./two") 158 | } 159 | 160 | pub fn join_10_test() { 161 | filepath.join("/one", "/") 162 | |> should.equal("/one") 163 | } 164 | 165 | pub fn join_11_test() { 166 | filepath.join("/one", "/two/three/") 167 | |> should.equal("/one/two/three") 168 | } 169 | 170 | pub fn join_12_test() { 171 | filepath.join("/", "one") 172 | |> should.equal("/one") 173 | } 174 | 175 | pub fn extension_0_test() { 176 | filepath.extension("file") 177 | |> should.equal(Error(Nil)) 178 | } 179 | 180 | pub fn extension_1_test() { 181 | filepath.extension("file.txt") 182 | |> should.equal(Ok("txt")) 183 | } 184 | 185 | pub fn extension_2_test() { 186 | filepath.extension("file.txt.gz") 187 | |> should.equal(Ok("gz")) 188 | } 189 | 190 | pub fn extension_3_test() { 191 | filepath.extension("one.two/file.txt.gz") 192 | |> should.equal(Ok("gz")) 193 | } 194 | 195 | pub fn extension_4_test() { 196 | filepath.extension("one.two/file") 197 | |> should.equal(Error(Nil)) 198 | } 199 | 200 | pub fn extension_5_test() { 201 | filepath.extension(".env") 202 | |> should.equal(Error(Nil)) 203 | } 204 | 205 | pub fn base_name_0_test() { 206 | filepath.base_name("file") 207 | |> should.equal("file") 208 | } 209 | 210 | pub fn base_name_1_test() { 211 | filepath.base_name("file.txt") 212 | |> should.equal("file.txt") 213 | } 214 | 215 | pub fn base_name_2_test() { 216 | filepath.base_name("/") 217 | |> should.equal("") 218 | } 219 | 220 | pub fn base_name_3_test() { 221 | filepath.base_name("/file") 222 | |> should.equal("file") 223 | } 224 | 225 | pub fn base_name_4_test() { 226 | filepath.base_name("/one/two/three.txt") 227 | |> should.equal("three.txt") 228 | } 229 | 230 | pub fn directory_name_0_test() { 231 | filepath.directory_name("file") 232 | |> should.equal("") 233 | } 234 | 235 | pub fn directory_name_1_test() { 236 | filepath.directory_name("/one") 237 | |> should.equal("/") 238 | } 239 | 240 | pub fn directory_name_2_test() { 241 | filepath.directory_name("/one/two") 242 | |> should.equal("/one") 243 | } 244 | 245 | pub fn directory_name_3_test() { 246 | filepath.directory_name("one/two") 247 | |> should.equal("one") 248 | } 249 | 250 | pub fn directory_name_4_test() { 251 | filepath.directory_name("~/one") 252 | |> should.equal("~") 253 | } 254 | 255 | pub fn directory_name_5_test() { 256 | filepath.directory_name("/one/two/three/four") 257 | |> should.equal("/one/two/three") 258 | } 259 | 260 | pub fn directory_name_6_test() { 261 | filepath.directory_name("/one/two/three/four/") 262 | |> should.equal("/one/two/three") 263 | } 264 | 265 | pub fn directory_name_7_test() { 266 | filepath.directory_name("one/two/three/four") 267 | |> should.equal("one/two/three") 268 | } 269 | 270 | pub fn is_absolute_0_test() { 271 | filepath.is_absolute("") 272 | |> should.equal(False) 273 | } 274 | 275 | pub fn is_absolute_1_test() { 276 | filepath.is_absolute("file") 277 | |> should.equal(False) 278 | } 279 | 280 | pub fn is_absolute_2_test() { 281 | filepath.is_absolute("/usr/local/bin") 282 | |> should.equal(True) 283 | } 284 | 285 | pub fn is_absolute_3_test() { 286 | filepath.is_absolute("usr/local/bin") 287 | |> should.equal(False) 288 | } 289 | 290 | pub fn is_absolute_4_test() { 291 | filepath.is_absolute("../usr/local/bin") 292 | |> should.equal(False) 293 | } 294 | 295 | pub fn is_absolute_5_test() { 296 | filepath.is_absolute("./usr/local/bin") 297 | |> should.equal(False) 298 | } 299 | 300 | pub fn is_absolute_6_test() { 301 | filepath.is_absolute("/") 302 | |> should.equal(True) 303 | } 304 | 305 | pub fn expand_0_test() { 306 | filepath.expand("one") 307 | |> should.equal(Ok("one")) 308 | } 309 | 310 | pub fn expand_1_test() { 311 | filepath.expand("/one") 312 | |> should.equal(Ok("/one")) 313 | } 314 | 315 | pub fn expand_2_test() { 316 | filepath.expand("/..") 317 | |> should.equal(Error(Nil)) 318 | } 319 | 320 | pub fn expand_3_test() { 321 | filepath.expand("/one/two/..") 322 | |> should.equal(Ok("/one")) 323 | } 324 | 325 | pub fn expand_4_test() { 326 | filepath.expand("/one/two/../..") 327 | |> should.equal(Ok("/")) 328 | } 329 | 330 | pub fn expand_5_test() { 331 | filepath.expand("/one/two/../../..") 332 | |> should.equal(Error(Nil)) 333 | } 334 | 335 | pub fn expand_6_test() { 336 | filepath.expand("/one/two/../../three") 337 | |> should.equal(Ok("/three")) 338 | } 339 | 340 | pub fn expand_7_test() { 341 | filepath.expand("one") 342 | |> should.equal(Ok("one")) 343 | } 344 | 345 | pub fn expand_8_test() { 346 | filepath.expand("..") 347 | |> should.equal(Error(Nil)) 348 | } 349 | 350 | pub fn expand_9_test() { 351 | filepath.expand("one/two/..") 352 | |> should.equal(Ok("one")) 353 | } 354 | 355 | pub fn expand_10_test() { 356 | filepath.expand("one/two/../..") 357 | |> should.equal(Ok("")) 358 | } 359 | 360 | pub fn expand_11_test() { 361 | filepath.expand("one/two/../../..") 362 | |> should.equal(Error(Nil)) 363 | } 364 | 365 | pub fn expand_12_test() { 366 | filepath.expand("one/two/../../three") 367 | |> should.equal(Ok("three")) 368 | } 369 | 370 | pub fn expand_13_test() { 371 | filepath.expand("/one/.") 372 | |> should.equal(Ok("/one")) 373 | } 374 | 375 | pub fn expand_14_test() { 376 | filepath.expand("/one/./two") 377 | |> should.equal(Ok("/one/two")) 378 | } 379 | 380 | pub fn expand_15_test() { 381 | filepath.expand("/one/") 382 | |> should.equal(Ok("/one")) 383 | } 384 | 385 | pub fn expand_16_test() { 386 | filepath.expand("/one/../") 387 | |> should.equal(Ok("/")) 388 | } 389 | 390 | pub fn strip_extension_1_test() { 391 | filepath.strip_extension("src/gleam") 392 | |> should.equal("src/gleam") 393 | } 394 | 395 | pub fn strip_extension_2_test() { 396 | filepath.strip_extension("src/gleam.toml") 397 | |> should.equal("src/gleam") 398 | } 399 | 400 | pub fn strip_extension_3_test() { 401 | filepath.strip_extension("package.tar.gz") 402 | |> should.equal("package.tar") 403 | } 404 | 405 | pub fn strip_extension_4_test() { 406 | filepath.strip_extension("one.two/package") 407 | |> should.equal("one.two/package") 408 | } 409 | 410 | pub fn strip_extension_5_test() { 411 | filepath.strip_extension(".env") 412 | |> should.equal(".env") 413 | } 414 | --------------------------------------------------------------------------------