369 lines
9.6 KiB
JavaScript
369 lines
9.6 KiB
JavaScript
const path = require("path");
|
||
const normalize = require("normalize-path");
|
||
const fs = require("fs");
|
||
|
||
function TemplatePath() {}
|
||
|
||
/**
|
||
* @returns {String} the absolute path to Eleventy’s project directory.
|
||
*/
|
||
TemplatePath.getWorkingDir = function () {
|
||
return TemplatePath.normalize(path.resolve("."));
|
||
};
|
||
|
||
/**
|
||
* Returns the directory portion of a path.
|
||
* Works for directory and file paths and paths ending in a glob pattern.
|
||
*
|
||
* @param {String} path A path
|
||
* @returns {String} the directory portion of a path.
|
||
*/
|
||
TemplatePath.getDir = function (path) {
|
||
if (TemplatePath.isDirectorySync(path)) {
|
||
return path;
|
||
}
|
||
|
||
return TemplatePath.getDirFromFilePath(path);
|
||
};
|
||
|
||
/**
|
||
* Returns the directory portion of a path that either points to a file
|
||
* or ends in a glob pattern. If `path` points to a directory,
|
||
* the returned value will have its last path segment stripped
|
||
* due to how [`path.parse`][1] works.
|
||
*
|
||
* [1]: https://nodejs.org/api/path.html#path_path_parse_path
|
||
*
|
||
* @param {String} path A path
|
||
* @returns {String} the directory portion of a path.
|
||
*/
|
||
TemplatePath.getDirFromFilePath = function (filePath) {
|
||
return path.parse(filePath).dir || ".";
|
||
};
|
||
|
||
/**
|
||
* Returns the last path segment in a path (no leading/trailing slashes).
|
||
*
|
||
* Assumes [`path.parse`][1] was called on `path` before.
|
||
*
|
||
* [1]: https://nodejs.org/api/path.html#path_path_parse_path
|
||
*
|
||
* @param {String} path A path
|
||
* @returns {String} the last path segment in a path
|
||
*/
|
||
TemplatePath.getLastPathSegment = function (path) {
|
||
if (!path.includes("/")) {
|
||
return path;
|
||
}
|
||
|
||
// Trim a trailing slash if there is one
|
||
path = path.replace(/\/$/, "");
|
||
|
||
return path.substr(path.lastIndexOf("/") + 1);
|
||
};
|
||
|
||
/**
|
||
* @param {String} path A path
|
||
* @returns {String[]} an array of paths pointing to each path segment of the
|
||
* provided `path`.
|
||
*/
|
||
TemplatePath.getAllDirs = function (path) {
|
||
// Trim a trailing slash if there is one
|
||
path = path.replace(/\/$/, "");
|
||
|
||
if (!path.includes("/")) {
|
||
return [path];
|
||
}
|
||
|
||
return path
|
||
.split("/")
|
||
.map((segment, index, array) => array.slice(0, index + 1).join("/"))
|
||
.filter((path) => path !== ".")
|
||
.reverse();
|
||
};
|
||
|
||
/**
|
||
* Normalizes a path, resolving single-dot and double-dot segments.
|
||
*
|
||
* Node.js’ [`path.normalize`][1] is called to strip a possible leading `"./"` segment.
|
||
*
|
||
* [1]: https://nodejs.org/api/path.html#path_path_normalize_path
|
||
*
|
||
* @param {String} thePath The path that should be normalized.
|
||
* @returns {String} the normalized path.
|
||
*/
|
||
TemplatePath.normalize = function (thePath) {
|
||
return normalize(path.normalize(thePath));
|
||
};
|
||
|
||
/**
|
||
* Joins all given path segments together.
|
||
*
|
||
* It uses Node.js’ [`path.join`][1] method and the [normalize-path][2] package.
|
||
*
|
||
* [1]: https://nodejs.org/api/path.html#path_path_join_paths
|
||
* [2]: https://www.npmjs.com/package/normalize-path
|
||
*
|
||
* @param {String[]} paths An arbitrary amount of path segments.
|
||
* @returns {String} the normalized and joined path.
|
||
*/
|
||
TemplatePath.join = function (...paths) {
|
||
return normalize(path.join(...paths));
|
||
};
|
||
|
||
/**
|
||
* Joins the given URL path segments and normalizes the resulting path.
|
||
* Maintains a single trailing slash if the last URL path argument
|
||
* had at least one.
|
||
*
|
||
* @param {String[]} urlPaths
|
||
* @returns {String} a normalized URL path described by the given URL path segments.
|
||
*/
|
||
TemplatePath.normalizeUrlPath = function (...urlPaths) {
|
||
const urlPath = path.posix.join(...urlPaths);
|
||
return urlPath.replace(/\/+$/, "/");
|
||
};
|
||
|
||
/**
|
||
* Joins the given path segments. Since the first path is absolute,
|
||
* the resulting path will be absolute as well.
|
||
*
|
||
* @param {String[]} paths
|
||
* @returns {String} the absolute path described by the given path segments.
|
||
*/
|
||
TemplatePath.absolutePath = function (...paths) {
|
||
let i = 0;
|
||
// check all the paths before we short circuit from the first index
|
||
for (let p of paths) {
|
||
if (path.isAbsolute(p) && i > 0) {
|
||
throw new Error(
|
||
`Only the first parameter to Template.absolutePath can be an absolute path. Received: ${p} from ${paths}`
|
||
);
|
||
}
|
||
i++;
|
||
}
|
||
|
||
let j = 0;
|
||
for (let p of paths) {
|
||
if (j === 0 && path.isAbsolute(p)) {
|
||
return TemplatePath.join(...paths);
|
||
}
|
||
j++;
|
||
}
|
||
|
||
return TemplatePath.join(TemplatePath.getWorkingDir(), ...paths);
|
||
};
|
||
|
||
/**
|
||
* Turns an absolute path into a path relative to the project directory.
|
||
*
|
||
* @param {String} absolutePath
|
||
* @returns {String} the relative path.
|
||
*/
|
||
TemplatePath.relativePath = function (absolutePath) {
|
||
return TemplatePath.stripLeadingSubPath(
|
||
absolutePath,
|
||
TemplatePath.getWorkingDir()
|
||
);
|
||
};
|
||
|
||
/**
|
||
* Adds a leading dot-slash segment to each path in the `paths` array.
|
||
*
|
||
* @param {String[]} paths
|
||
* @returns {String[]}
|
||
*/
|
||
TemplatePath.addLeadingDotSlashArray = function (paths) {
|
||
return paths.map((path) => TemplatePath.addLeadingDotSlash(path));
|
||
};
|
||
|
||
/**
|
||
* Adds a leading dot-slash segment to `path`.
|
||
*
|
||
* @param {String} path
|
||
* @returns {String}
|
||
*/
|
||
TemplatePath.addLeadingDotSlash = function (pathArg) {
|
||
if (pathArg === "." || pathArg === "..") {
|
||
return pathArg + "/";
|
||
}
|
||
|
||
if (
|
||
path.isAbsolute(pathArg) ||
|
||
pathArg.startsWith("./") ||
|
||
pathArg.startsWith("../")
|
||
) {
|
||
return pathArg;
|
||
}
|
||
|
||
return "./" + pathArg;
|
||
};
|
||
|
||
/**
|
||
* Removes a leading dot-slash segment.
|
||
*
|
||
* @param {String} path
|
||
* @returns {String} the `path` without a leading dot-slash segment.
|
||
*/
|
||
TemplatePath.stripLeadingDotSlash = function (path) {
|
||
return typeof path === "string" ? path.replace(/^\.\//, "") : path;
|
||
};
|
||
|
||
/**
|
||
* Determines whether a path starts with a given sub path.
|
||
*
|
||
* @param {String} path A path
|
||
* @param {String} subPath A path
|
||
* @returns {Boolean} whether `path` starts with `subPath`.
|
||
*/
|
||
TemplatePath.startsWithSubPath = function (path, subPath) {
|
||
path = TemplatePath.normalize(path);
|
||
subPath = TemplatePath.normalize(subPath);
|
||
|
||
return path.startsWith(subPath);
|
||
};
|
||
|
||
/**
|
||
* Removes the `subPath` at the start of `path` if present
|
||
* and returns the remainding path.
|
||
*
|
||
* @param {String} path A path
|
||
* @param {String} subPath A path
|
||
* @returns {String} the `path` without `subPath` at the start of it.
|
||
*/
|
||
TemplatePath.stripLeadingSubPath = function (path, subPath) {
|
||
path = TemplatePath.normalize(path);
|
||
subPath = TemplatePath.normalize(subPath);
|
||
|
||
if (subPath !== "." && path.startsWith(subPath)) {
|
||
return path.substr(subPath.length + 1);
|
||
}
|
||
|
||
return path;
|
||
};
|
||
|
||
/**
|
||
* @param {String} path A path
|
||
* @returns {Boolean} whether `path` points to an existing directory.
|
||
*/
|
||
TemplatePath.isDirectorySync = function (path) {
|
||
return fs.existsSync(path) && fs.statSync(path).isDirectory();
|
||
};
|
||
|
||
/**
|
||
* @param {String} path A path
|
||
* @returns {Boolean} whether `path` points to an existing directory.
|
||
*/
|
||
TemplatePath.isDirectory = async function (path) {
|
||
return new Promise((resolve) => {
|
||
fs.stat(path, (err, stats) => {
|
||
if (stats) {
|
||
resolve(stats.isDirectory());
|
||
}
|
||
resolve(false);
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Appends a recursive wildcard glob pattern to `path`
|
||
* unless `path` is not a directory; then, `path` is assumed to be a file path
|
||
* and is left unchaged.
|
||
*
|
||
* @param {String} path
|
||
* @returns {String}
|
||
*/
|
||
TemplatePath.convertToRecursiveGlobSync = function (path) {
|
||
if (path === "") {
|
||
return "./**";
|
||
}
|
||
|
||
path = TemplatePath.addLeadingDotSlash(path);
|
||
|
||
if (TemplatePath.isDirectorySync(path)) {
|
||
return path + (!path.endsWith("/") ? "/" : "") + "**";
|
||
}
|
||
|
||
return path;
|
||
};
|
||
|
||
/**
|
||
* Appends a recursive wildcard glob pattern to `path`
|
||
* unless `path` is not a directory; then, `path` is assumed to be a file path
|
||
* and is left unchaged.
|
||
*
|
||
* @param {String} path
|
||
* @returns {String}
|
||
*/
|
||
TemplatePath.convertToRecursiveGlob = async function (path) {
|
||
if (path === "") {
|
||
return "./**";
|
||
}
|
||
|
||
path = TemplatePath.addLeadingDotSlash(path);
|
||
|
||
if (await TemplatePath.isDirectory(path)) {
|
||
return path + (!path.endsWith("/") ? "/" : "") + "**";
|
||
}
|
||
|
||
return path;
|
||
};
|
||
|
||
/**
|
||
* Returns the extension of the path without the leading dot.
|
||
* If the path has no extensions, the empty string is returned.
|
||
*
|
||
* @param {String} thePath
|
||
* @returns {String} the path’s extension if it exists;
|
||
* otherwise, the empty string.
|
||
*/
|
||
TemplatePath.getExtension = function (thePath) {
|
||
return path.extname(thePath).replace(/^\./, "");
|
||
};
|
||
|
||
/**
|
||
* Removes the extension from a path.
|
||
*
|
||
* @param {String} path
|
||
* @param {String} extension
|
||
* @returns {String}
|
||
*/
|
||
TemplatePath.removeExtension = function (path, extension = undefined) {
|
||
if (extension === undefined) {
|
||
return path;
|
||
}
|
||
|
||
const pathExtension = TemplatePath.getExtension(path);
|
||
if (pathExtension !== "" && extension.endsWith(pathExtension)) {
|
||
return path.substring(0, path.lastIndexOf(pathExtension) - 1);
|
||
}
|
||
|
||
return path;
|
||
};
|
||
|
||
/**
|
||
* Accepts a relative file path that is using a standard directory separator and
|
||
* normalizes it using the local operating system separator.
|
||
* e.g. `./my/dir/` stays `./my/dir/` on *nix and becomes `.\\my\\dir\\` on Windows
|
||
*
|
||
* @param {String} filePath
|
||
* @returns {String} a file path with the correct local directory separator.
|
||
*/
|
||
TemplatePath.normalizeOperatingSystemFilePath = function (filePath, sep = "/") {
|
||
return filePath.split(sep).join(path.sep);
|
||
};
|
||
|
||
/**
|
||
* Accepts a relative file path with the local operating system directory separator and
|
||
* normalizes it using a forward slash directory separator. (Leaves trailing slash as-is)
|
||
* e.g. `./my/dir/` stays `./my/dir/` on *nix and becomes `.\\my\\dir\\` on Windows
|
||
*
|
||
* @param {String} filePath
|
||
* @returns {String} a file path with the correct local directory separator.
|
||
*/
|
||
TemplatePath.standardizeFilePath = function (filePath, sep = "/") {
|
||
return TemplatePath.addLeadingDotSlash(filePath.split(path.sep).join(sep));
|
||
};
|
||
|
||
module.exports = TemplatePath;
|