Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(path): single file exports #3510

Merged
merged 11 commits into from
Aug 7, 2023
62 changes: 62 additions & 0 deletions path/basename.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isWindows } from "../_util/os.ts";
import { CHAR_COLON } from "./_constants.ts";
import {
assertPath,
isPathSeparator,
isPosixPathSeparator,
isWindowsDeviceRoot,
lastPathSegment,
stripSuffix,
stripTrailingSeparators,
} from "./_util.ts";

function posixBasename(path: string, suffix = ""): string {
const lastSegment = lastPathSegment(path, isPosixPathSeparator);
const strippedSegment = stripTrailingSeparators(
lastSegment,
isPosixPathSeparator,
);
return suffix ? stripSuffix(strippedSegment, suffix) : strippedSegment;
}

function windowsBasename(path: string, suffix = ""): string {
// Check for a drive letter prefix so as not to mistake the following
// path separator as an extra separator at the end of the path that can be
// disregarded
let start = 0;
if (path.length >= 2) {
const drive = path.charCodeAt(0);
if (isWindowsDeviceRoot(drive)) {
if (path.charCodeAt(1) === CHAR_COLON) start = 2;
}
}

const lastSegment = lastPathSegment(path, isPathSeparator, start);
const strippedSegment = stripTrailingSeparators(lastSegment, isPathSeparator);
return suffix ? stripSuffix(strippedSegment, suffix) : strippedSegment;
}

/**
* Return the last portion of a `path`.
* Trailing directory separators are ignored, and optional suffix is removed.
*
* @param path - path to extract the name from.
* @param [suffix] - suffix to remove from extracted name.
*/
export function basename(path: string, suffix = ""): string {
assertPath(path);
if (path.length === 0) return path;
if (typeof suffix !== "string") {
throw new TypeError(
`Suffix must be a string. Received ${JSON.stringify(suffix)}`,
);
}

if (isWindows) {
return windowsBasename(path, suffix);
}
return posixBasename(path, suffix);
lino-levan marked this conversation as resolved.
Show resolved Hide resolved
}
144 changes: 144 additions & 0 deletions path/dirname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isWindows } from "../_util/os.ts";
import { CHAR_COLON } from "./_constants.ts";
import {
assertPath,
isPathSeparator,
isPosixPathSeparator,
isWindowsDeviceRoot,
stripTrailingSeparators,
} from "./_util.ts";

function posixDirname(path: string): string {
let end = -1;
let matchedNonSeparator = false;

for (let i = path.length - 1; i >= 1; --i) {
if (isPosixPathSeparator(path.charCodeAt(i))) {
if (matchedNonSeparator) {
end = i;
break;
}
} else {
matchedNonSeparator = true;
}
}

// No matches. Fallback based on provided path:
//
// - leading slashes paths
// "/foo" => "/"
// "///foo" => "/"
// - no slash path
// "foo" => "."
if (end === -1) {
return isPosixPathSeparator(path.charCodeAt(0)) ? "/" : ".";
}

return stripTrailingSeparators(
path.slice(0, end),
isPosixPathSeparator,
);
}

function windowsDirname(path: string): string {
const len = path.length;
let rootEnd = -1;
let end = -1;
let matchedSlash = true;
let offset = 0;
const code = path.charCodeAt(0);

// Try to match a root
if (len > 1) {
if (isPathSeparator(code)) {
// Possible UNC root

rootEnd = offset = 1;

if (isPathSeparator(path.charCodeAt(1))) {
// Matched double path separator at beginning
let j = 2;
let last = j;
// Match 1 or more non-path separators
for (; j < len; ++j) {
if (isPathSeparator(path.charCodeAt(j))) break;
}
if (j < len && j !== last) {
// Matched!
last = j;
// Match 1 or more path separators
for (; j < len; ++j) {
if (!isPathSeparator(path.charCodeAt(j))) break;
}
if (j < len && j !== last) {
// Matched!
last = j;
// Match 1 or more non-path separators
for (; j < len; ++j) {
if (isPathSeparator(path.charCodeAt(j))) break;
}
if (j === len) {
// We matched a UNC root only
return path;
}
if (j !== last) {
// We matched a UNC root with leftovers

// Offset by 1 to include the separator after the UNC root to
// treat it as a "normal root" on top of a (UNC) root
rootEnd = offset = j + 1;
}
}
}
}
} else if (isWindowsDeviceRoot(code)) {
// Possible device root

if (path.charCodeAt(1) === CHAR_COLON) {
rootEnd = offset = 2;
if (len > 2) {
if (isPathSeparator(path.charCodeAt(2))) rootEnd = offset = 3;
}
}
}
} else if (isPathSeparator(code)) {
// `path` contains just a path separator, exit early to avoid
// unnecessary work
return path;
}

for (let i = len - 1; i >= offset; --i) {
if (isPathSeparator(path.charCodeAt(i))) {
if (!matchedSlash) {
end = i;
break;
}
} else {
// We saw the first non-path separator
matchedSlash = false;
}
}

if (end === -1) {
if (rootEnd === -1) return ".";
else end = rootEnd;
}
return stripTrailingSeparators(path.slice(0, end), isPosixPathSeparator);
}

/**
* Return the directory path of a `path`.
* @param path - path to extract the directory from.
*/
export function dirname(path: string): string {
assertPath(path);
lino-levan marked this conversation as resolved.
Show resolved Hide resolved
if (path.length === 0) return ".";

if (isWindows) {
return windowsDirname(path);
}
return posixDirname(path);
}
139 changes: 139 additions & 0 deletions path/extname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isWindows } from "../_util/os.ts";
import { CHAR_COLON, CHAR_DOT } from "./_constants.ts";
import {
assertPath,
isPathSeparator,
isPosixPathSeparator,
isWindowsDeviceRoot,
} from "./_util.ts";

function posixExtname(path: string): string {
let startDot = -1;
let startPart = 0;
let end = -1;
let matchedSlash = true;
// Track the state of characters (if any) we see before our first dot and
// after any path separator we find
let preDotState = 0;
for (let i = path.length - 1; i >= 0; --i) {
const code = path.charCodeAt(i);
if (isPosixPathSeparator(code)) {
// If we reached a path separator that was not part of a set of path
// separators at the end of the string, stop now
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
// We saw the first non-path separator, mark this as the end of our
// extension
matchedSlash = false;
end = i + 1;
}
if (code === CHAR_DOT) {
// If this is our first dot, mark it as the start of our extension
if (startDot === -1) startDot = i;
else if (preDotState !== 1) preDotState = 1;
} else if (startDot !== -1) {
// We saw a non-dot and non-path separator before our dot, so we should
// have a good chance at having a non-empty extension
preDotState = -1;
}
}

if (
startDot === -1 ||
end === -1 ||
// We saw a non-dot character immediately before the dot
preDotState === 0 ||
// The (right-most) trimmed path component is exactly '..'
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
) {
return "";
}
return path.slice(startDot, end);
}

function windowsExtname(path: string): string {
let start = 0;
let startDot = -1;
let startPart = 0;
let end = -1;
let matchedSlash = true;
// Track the state of characters (if any) we see before our first dot and
// after any path separator we find
let preDotState = 0;

// Check for a drive letter prefix so as not to mistake the following
// path separator as an extra separator at the end of the path that can be
// disregarded

if (
path.length >= 2 &&
path.charCodeAt(1) === CHAR_COLON &&
isWindowsDeviceRoot(path.charCodeAt(0))
) {
start = startPart = 2;
}

for (let i = path.length - 1; i >= start; --i) {
const code = path.charCodeAt(i);
if (isPathSeparator(code)) {
// If we reached a path separator that was not part of a set of path
// separators at the end of the string, stop now
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
// We saw the first non-path separator, mark this as the end of our
// extension
matchedSlash = false;
end = i + 1;
}
if (code === CHAR_DOT) {
// If this is our first dot, mark it as the start of our extension
if (startDot === -1) startDot = i;
else if (preDotState !== 1) preDotState = 1;
} else if (startDot !== -1) {
// We saw a non-dot and non-path separator before our dot, so we should
// have a good chance at having a non-empty extension
preDotState = -1;
}
}

if (
startDot === -1 ||
end === -1 ||
// We saw a non-dot character immediately before the dot
preDotState === 0 ||
// The (right-most) trimmed path component is exactly '..'
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
) {
return "";
}
return path.slice(startDot, end);
}

/**
* @deprecated (will be removed after 1.0.0) Import from `std/path/extname.ts` instead.
*
* Return the extension of the `path` with leading period.
* @param path with extension
* @returns extension (ex. for `file.ts` returns `.ts`)
*/
export function extname(path: string): string {
assertPath(path);

if (isWindows) {
return windowsExtname(path);
}
return posixExtname(path);
}
23 changes: 23 additions & 0 deletions path/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isWindows } from "../_util/os.ts";
import { FormatInputPathObject } from "./_interface.ts";
import { _format } from "./_util.ts";

/**
* Generate a path from `FormatInputPathObject` object.
* @param pathObject with path
*/
export function format(pathObject: FormatInputPathObject): string {
if (pathObject === null || typeof pathObject !== "object") {
throw new TypeError(
`The "pathObject" argument must be of type Object. Received type ${typeof pathObject}`,
);
}

if (isWindows) {
return _format("\\", pathObject);
}
return _format("/", pathObject);
}
Loading