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;
|