Path

let separator_in (windows: Bool): String

\\ on Windows, / everywhere else.

Every function in this module has an _in form taking the convention as its first argument, and a host form that reads it from Os.platform(). The pair exists because the Windows branch is UNREACHABLE on a Unix machine, not merely untested, so a Windows-only path bug could otherwise only be found by CI, on a machine nobody can attach a debugger to. lsp.uri_to_path_in is the same shape for the same reason, and two releases shipped bugs of exactly this kind before it existed.

They are also what to call when you are building a path FOR another platform rather than for the one you are running on.

let separator (): String

let is_absolute_in (windows: Bool) (p: String): Bool

Rooted: /x anywhere, and on Windows also C:/x and a //server UNC path. C:x (a drive-RELATIVE path) is deliberately not absolute; it means "the current directory on drive C", which is a relative path with a drive attached.

let is_absolute (p: String): Bool

let clean_in (windows: Bool) (p: String): String

Removes duplicate separators, . segments, and resolvable .. segments, and drops a trailing separator. An empty result is ., matching Go's filepath.Clean. The empty string is not a path, and . is the one it means.

let clean (p: String): String

let join_all_in (windows: Bool) (parts: Array[String]): String

--- join ---

Empty segments are skipped, so join("", "b") is b rather than /b; joining nothing to something must not turn a relative path absolute.

A later ABSOLUTE segment does not discard the earlier ones: join("a", "/b") is a/b, matching Go's filepath.Join. Python goes the other way and lets /b win, which is convenient exactly until a caller passes user input as the second argument and silently escapes the directory they meant to stay inside.

let join_all (parts: Array[String]): String

let join_in (windows: Bool) (a: String) (b: String): String

Joins two path segments with the platform separator, cleaning the result.

A trailing separator on a or a leading one on b does not produce a doubled separator.

An absolute b does NOT win: join("a", "/b") is a/b, not /b. This is join_all([a, b]) and inherits its rule, which is Go's; see the reasoning there. If b may be absolute and should be taken as-is, test it with Path.is_absolute and use it directly.

This documentation said the opposite until 0.0.30, and the package loader was written against the sentence rather than the code: an absolute dependency path was resolved relative to the manifest, producing /home/x/opt/parsec and blaming the user's manifest for a path the compiler had invented.

let join (a: String) (b: String): String

let basename_in (windows: Bool) (p: String): String

The last component. "" is ., a bare root is itself, and a trailing separator is ignored (a/b/ is b).

let basename (p: String): String

let dirname_in (windows: Bool) (p: String): String

Everything before the last component, cleaned. A path with no separator is ., the directory it is in.

let dirname (p: String): String

let extension_in (windows: Bool) (p: String): String

The extension of the last segment, without its dot, or empty when there is none.

A leading dot does not start an extension: .gitignore is a name, not an extension, and returns nothing. archive.tar.gz gives gz -- the LAST dot wins, so stem and extension always recombine into the original name.

let extension (p: String): String

let stem_in (windows: Bool) (p: String): String

The last component without its extension.

let stem (p: String): String

let with_extension_in (windows: Bool) (p: String) (ext: String): String

p with a different extension, given without a dot. An empty ext removes it.

let with_extension (p: String) (ext: String): String