528 lines
12 KiB
JavaScript
528 lines
12 KiB
JavaScript
import * as $bool from "../gleam_stdlib/gleam/bool.mjs";
|
|
import * as $list from "../gleam_stdlib/gleam/list.mjs";
|
|
import * as $option from "../gleam_stdlib/gleam/option.mjs";
|
|
import { None, Some } from "../gleam_stdlib/gleam/option.mjs";
|
|
import * as $result from "../gleam_stdlib/gleam/result.mjs";
|
|
import * as $string from "../gleam_stdlib/gleam/string.mjs";
|
|
import { is_windows } from "./filepath_ffi.mjs";
|
|
import { Ok, Error, toList, Empty as $Empty, prepend as listPrepend, isEqual } from "./gleam.mjs";
|
|
|
|
const codepoint_z_up = 122;
|
|
|
|
const codepoint_a_up = 97;
|
|
|
|
const codepoint_z = 90;
|
|
|
|
const codepoint_a = 65;
|
|
|
|
const codepoint_colon = 58;
|
|
|
|
const codepoint_backslash = 92;
|
|
|
|
const codepoint_slash = 47;
|
|
|
|
function remove_trailing_slash(path) {
|
|
let $ = $string.ends_with(path, "/");
|
|
if ($) {
|
|
return $string.drop_end(path, 1);
|
|
} else {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
function relative(loop$path) {
|
|
while (true) {
|
|
let path = loop$path;
|
|
if (path.charCodeAt(0) === 47) {
|
|
let path$1 = path.slice(1);
|
|
loop$path = path$1;
|
|
} else {
|
|
return path;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Join two paths together.
|
|
*
|
|
* This function does not expand `..` or `.` segments, use the `expand`
|
|
* function to do this.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* join("/usr/local", "bin")
|
|
* // -> "/usr/local/bin"
|
|
* ```
|
|
*/
|
|
export function join(left, right) {
|
|
let _block;
|
|
if (right === "/") {
|
|
_block = left;
|
|
} else if (right.charCodeAt(0) === 47) {
|
|
if (left === "") {
|
|
_block = relative(right);
|
|
} else if (left === "/") {
|
|
_block = right;
|
|
} else {
|
|
let _pipe = remove_trailing_slash(left);
|
|
let _pipe$1 = $string.append(_pipe, "/");
|
|
_block = $string.append(_pipe$1, relative(right));
|
|
}
|
|
} else if (left === "") {
|
|
_block = relative(right);
|
|
} else if (left === "/") {
|
|
_block = left + right;
|
|
} else {
|
|
let _pipe = remove_trailing_slash(left);
|
|
let _pipe$1 = $string.append(_pipe, "/");
|
|
_block = $string.append(_pipe$1, relative(right));
|
|
}
|
|
let _pipe = _block;
|
|
return remove_trailing_slash(_pipe);
|
|
}
|
|
|
|
/**
|
|
* Split a path into its segments, using `/` as the path separator.
|
|
*
|
|
* Typically you would want to use `split` instead of this function, but if you
|
|
* want non-Windows path behaviour on a Windows system then you can use this
|
|
* function.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* split("/usr/local/bin", "bin")
|
|
* // -> ["/", "usr", "local", "bin"]
|
|
* ```
|
|
*/
|
|
export function split_unix(path) {
|
|
let _block;
|
|
let $ = $string.split(path, "/");
|
|
if ($ instanceof $Empty) {
|
|
_block = $;
|
|
} else {
|
|
let $1 = $.tail;
|
|
if ($1 instanceof $Empty) {
|
|
let $2 = $.head;
|
|
if ($2 === "") {
|
|
_block = toList([]);
|
|
} else {
|
|
_block = $;
|
|
}
|
|
} else {
|
|
let $2 = $.head;
|
|
if ($2 === "") {
|
|
let rest = $1;
|
|
_block = listPrepend("/", rest);
|
|
} else {
|
|
_block = $;
|
|
}
|
|
}
|
|
}
|
|
let _pipe = _block;
|
|
return $list.filter(_pipe, (x) => { return x !== ""; });
|
|
}
|
|
|
|
function pop_windows_drive_specifier(path) {
|
|
let start = $string.slice(path, 0, 3);
|
|
let codepoints = $string.to_utf_codepoints(start);
|
|
let $ = $list.map(codepoints, $string.utf_codepoint_to_int);
|
|
if ($ instanceof $Empty) {
|
|
return [new None(), path];
|
|
} else {
|
|
let $1 = $.tail;
|
|
if ($1 instanceof $Empty) {
|
|
return [new None(), path];
|
|
} else {
|
|
let $2 = $1.tail;
|
|
if ($2 instanceof $Empty) {
|
|
return [new None(), path];
|
|
} else {
|
|
let $3 = $2.tail;
|
|
if ($3 instanceof $Empty) {
|
|
let drive = $.head;
|
|
let colon = $1.head;
|
|
let slash = $2.head;
|
|
if (
|
|
(((slash === 47) || (slash === 92)) && (colon === 58)) && (((drive >= 65) && (drive <= 90)) || ((drive >= 97) && (drive <= 122)))
|
|
) {
|
|
let drive_letter = $string.slice(path, 0, 1);
|
|
let drive$1 = $string.lowercase(drive_letter) + ":/";
|
|
let path$1 = $string.drop_start(path, 3);
|
|
return [new Some(drive$1), path$1];
|
|
} else {
|
|
return [new None(), path];
|
|
}
|
|
} else {
|
|
return [new None(), path];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Split a path into its segments, using `/` and `\` as the path separators. If
|
|
* there is a drive letter at the start of the path then it is lowercased.
|
|
*
|
|
* Typically you would want to use `split` instead of this function, but if you
|
|
* want Windows path behaviour on a non-Windows system then you can use this
|
|
* function.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* split("/usr/local/bin", "bin")
|
|
* // -> ["/", "usr", "local", "bin"]
|
|
* ```
|
|
*/
|
|
export function split_windows(path) {
|
|
let $ = pop_windows_drive_specifier(path);
|
|
let drive = $[0];
|
|
let path$1 = $[1];
|
|
let _block;
|
|
let _pipe = $string.split(path$1, "/");
|
|
_block = $list.flat_map(
|
|
_pipe,
|
|
(_capture) => { return $string.split(_capture, "\\"); },
|
|
);
|
|
let segments = _block;
|
|
let _block$1;
|
|
if (drive instanceof Some) {
|
|
let drive$1 = drive[0];
|
|
_block$1 = listPrepend(drive$1, segments);
|
|
} else {
|
|
_block$1 = segments;
|
|
}
|
|
let segments$1 = _block$1;
|
|
if (segments$1 instanceof $Empty) {
|
|
return segments$1;
|
|
} else {
|
|
let $1 = segments$1.tail;
|
|
if ($1 instanceof $Empty) {
|
|
let $2 = segments$1.head;
|
|
if ($2 === "") {
|
|
return toList([]);
|
|
} else {
|
|
return segments$1;
|
|
}
|
|
} else {
|
|
let $2 = segments$1.head;
|
|
if ($2 === "") {
|
|
let rest = $1;
|
|
return listPrepend("/", rest);
|
|
} else {
|
|
return segments$1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Split a path into its segments.
|
|
*
|
|
* When running on Windows both `/` and `\` are treated as path separators, and
|
|
* if the path starts with a drive letter then the drive letter then it is
|
|
* lowercased.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* split("/usr/local/bin", "bin")
|
|
* // -> ["/", "usr", "local", "bin"]
|
|
* ```
|
|
*/
|
|
export function split(path) {
|
|
let $ = is_windows();
|
|
if ($) {
|
|
return split_windows(path);
|
|
} else {
|
|
return split_unix(path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the base name of a path, that is the name of the file without the
|
|
* containing directory.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* base_name("/usr/local/bin")
|
|
* // -> "bin"
|
|
* ```
|
|
*/
|
|
export function base_name(path) {
|
|
return $bool.guard(
|
|
path === "/",
|
|
"",
|
|
() => {
|
|
let _pipe = path;
|
|
let _pipe$1 = split(_pipe);
|
|
let _pipe$2 = $list.last(_pipe$1);
|
|
return $result.unwrap(_pipe$2, "");
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the file extension of a path.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* extension("src/main.gleam")
|
|
* // -> Ok("gleam")
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* extension("package.tar.gz")
|
|
* // -> Ok("gz")
|
|
* ```
|
|
*/
|
|
export function extension(path) {
|
|
let file = base_name(path);
|
|
let $ = $string.split(file, ".");
|
|
if ($ instanceof $Empty) {
|
|
return new Error(undefined);
|
|
} else {
|
|
let $1 = $.tail;
|
|
if ($1 instanceof $Empty) {
|
|
let rest = $1;
|
|
return $list.last(rest);
|
|
} else {
|
|
let $2 = $1.tail;
|
|
if ($2 instanceof $Empty) {
|
|
let $3 = $.head;
|
|
if ($3 === "") {
|
|
return new Error(undefined);
|
|
} else {
|
|
let extension$1 = $1.head;
|
|
return new Ok(extension$1);
|
|
}
|
|
} else {
|
|
let rest = $1;
|
|
return $list.last(rest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the extension from a file, if it has any.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* strip_extension("src/main.gleam")
|
|
* // -> "src/main"
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* strip_extension("package.tar.gz")
|
|
* // -> "package.tar"
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* strip_extension("src/gleam")
|
|
* // -> "src/gleam"
|
|
* ```
|
|
*/
|
|
export function strip_extension(path) {
|
|
let $ = extension(path);
|
|
if ($ instanceof Ok) {
|
|
let extension$1 = $[0];
|
|
return $string.drop_end(path, $string.length(extension$1) + 1);
|
|
} else {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
function get_directory_name(loop$path, loop$acc, loop$segment) {
|
|
while (true) {
|
|
let path = loop$path;
|
|
let acc = loop$acc;
|
|
let segment = loop$segment;
|
|
if (path instanceof $Empty) {
|
|
return acc;
|
|
} else {
|
|
let $ = path.head;
|
|
if ($ === "/") {
|
|
let rest = path.tail;
|
|
loop$path = rest;
|
|
loop$acc = acc + segment;
|
|
loop$segment = "/";
|
|
} else {
|
|
let first = $;
|
|
let rest = path.tail;
|
|
loop$path = rest;
|
|
loop$acc = acc;
|
|
loop$segment = segment + first;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the directory name of a path, that is the path without the file name.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* directory_name("/usr/local/bin")
|
|
* // -> "/usr/local"
|
|
* ```
|
|
*/
|
|
export function directory_name(path) {
|
|
let path$1 = remove_trailing_slash(path);
|
|
if (path$1.charCodeAt(0) === 47) {
|
|
let rest = path$1.slice(1);
|
|
return get_directory_name($string.to_graphemes(rest), "/", "");
|
|
} else {
|
|
return get_directory_name($string.to_graphemes(path$1), "", "");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a path is absolute.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* is_absolute("/usr/local/bin")
|
|
* // -> True
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* is_absolute("usr/local/bin")
|
|
* // -> False
|
|
* ```
|
|
*/
|
|
export function is_absolute(path) {
|
|
return $string.starts_with(path, "/");
|
|
}
|
|
|
|
function expand_segments(loop$path, loop$base) {
|
|
while (true) {
|
|
let path = loop$path;
|
|
let base = loop$base;
|
|
if (path instanceof $Empty) {
|
|
return new Ok($string.join($list.reverse(base), "/"));
|
|
} else if (base instanceof $Empty) {
|
|
let $ = path.head;
|
|
if ($ === "..") {
|
|
return new Error(undefined);
|
|
} else if ($ === ".") {
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = base;
|
|
} else {
|
|
let s = $;
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = listPrepend(s, base);
|
|
}
|
|
} else {
|
|
let $ = base.tail;
|
|
if ($ instanceof $Empty) {
|
|
let $1 = path.head;
|
|
if ($1 === "..") {
|
|
let $2 = base.head;
|
|
if ($2 === "") {
|
|
return new Error(undefined);
|
|
} else {
|
|
let path$1 = path.tail;
|
|
let base$1 = $;
|
|
loop$path = path$1;
|
|
loop$base = base$1;
|
|
}
|
|
} else if ($1 === ".") {
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = base;
|
|
} else {
|
|
let s = $1;
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = listPrepend(s, base);
|
|
}
|
|
} else {
|
|
let $1 = path.head;
|
|
if ($1 === "..") {
|
|
let path$1 = path.tail;
|
|
let base$1 = $;
|
|
loop$path = path$1;
|
|
loop$base = base$1;
|
|
} else if ($1 === ".") {
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = base;
|
|
} else {
|
|
let s = $1;
|
|
let path$1 = path.tail;
|
|
loop$path = path$1;
|
|
loop$base = listPrepend(s, base);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function root_slash_to_empty(segments) {
|
|
if (segments instanceof $Empty) {
|
|
return segments;
|
|
} else {
|
|
let $ = segments.head;
|
|
if ($ === "/") {
|
|
let rest = segments.tail;
|
|
return listPrepend("", rest);
|
|
} else {
|
|
return segments;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expand `..` and `.` segments in a path.
|
|
*
|
|
* If the path has a `..` segment that would go up past the root of the path
|
|
* then an error is returned. This may be useful to example to ensure that a
|
|
* path specified by a user does not go outside of a directory.
|
|
*
|
|
* If the path is absolute then the result will always be absolute.
|
|
*
|
|
* ## Examples
|
|
*
|
|
* ```gleam
|
|
* expand("/usr/local/../bin")
|
|
* // -> Ok("/usr/bin")
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* expand("/tmp/../..")
|
|
* // -> Error(Nil)
|
|
* ```
|
|
*
|
|
* ```gleam
|
|
* expand("src/../..")
|
|
* // -> Error("..")
|
|
* ```
|
|
*/
|
|
export function expand(path) {
|
|
let is_absolute$1 = is_absolute(path);
|
|
let _block;
|
|
let _pipe = path;
|
|
let _pipe$1 = split(_pipe);
|
|
let _pipe$2 = root_slash_to_empty(_pipe$1);
|
|
let _pipe$3 = expand_segments(_pipe$2, toList([]));
|
|
_block = $result.map(_pipe$3, remove_trailing_slash);
|
|
let result = _block;
|
|
let $ = is_absolute$1 && (isEqual(result, new Ok("")));
|
|
if ($) {
|
|
return new Ok("/");
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|