94 lines
2.4 KiB
JavaScript
94 lines
2.4 KiB
JavaScript
'use strict'
|
|
|
|
const { readdir: _readdir, readdirSync } = require('fs')
|
|
const { platform } = require('os')
|
|
const { isAbsolute, normalize } = require('path')
|
|
const { promisify: pify } = require('util')
|
|
|
|
const readdir = pify(_readdir)
|
|
const isWindows = platform() === 'win32'
|
|
const delimiter = isWindows ? '\\' : '/'
|
|
|
|
module.exports = {
|
|
trueCasePath: _trueCasePath({ sync: false }),
|
|
trueCasePathSync: _trueCasePath({ sync: true })
|
|
}
|
|
|
|
function getRelevantFilePathSegments(filePath) {
|
|
return filePath.split(delimiter).filter((s) => s !== '')
|
|
}
|
|
|
|
function escapeString(str) {
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|
|
|
|
function matchCaseInsensitive(fileOrDirectory, directoryContents, filePath) {
|
|
const caseInsensitiveRegex = new RegExp(
|
|
`^${escapeString(fileOrDirectory)}$`,
|
|
'i'
|
|
)
|
|
for (const file of directoryContents) {
|
|
if (caseInsensitiveRegex.test(file)) return file
|
|
}
|
|
throw new Error(
|
|
`[true-case-path]: Called with ${filePath}, but no matching file exists`
|
|
)
|
|
}
|
|
|
|
function _trueCasePath({ sync }) {
|
|
return (filePath, basePath) => {
|
|
if (basePath) {
|
|
if (!isAbsolute(basePath)) {
|
|
throw new Error(
|
|
`[true-case-path]: basePath argument must be absolute. Received "${basePath}"`
|
|
)
|
|
}
|
|
basePath = normalize(basePath)
|
|
}
|
|
filePath = normalize(filePath)
|
|
const segments = getRelevantFilePathSegments(filePath)
|
|
if (isAbsolute(filePath)) {
|
|
if (basePath) {
|
|
throw new Error(
|
|
'[true-case-path]: filePath must be relative when used with basePath'
|
|
)
|
|
}
|
|
basePath = isWindows
|
|
? segments.shift().toUpperCase() // drive letter
|
|
: ''
|
|
} else if (!basePath) {
|
|
basePath = process.cwd()
|
|
}
|
|
return sync
|
|
? iterateSync(basePath, filePath, segments)
|
|
: iterateAsync(basePath, filePath, segments)
|
|
}
|
|
}
|
|
|
|
function iterateSync(basePath, filePath, segments) {
|
|
return segments.reduce(
|
|
(realPath, fileOrDirectory) =>
|
|
realPath +
|
|
delimiter +
|
|
matchCaseInsensitive(
|
|
fileOrDirectory,
|
|
readdirSync(realPath + delimiter),
|
|
filePath
|
|
),
|
|
basePath
|
|
)
|
|
}
|
|
|
|
async function iterateAsync(basePath, filePath, segments) {
|
|
return await segments.reduce(
|
|
async (realPathPromise, fileOrDirectory) =>
|
|
(await realPathPromise) +
|
|
delimiter +
|
|
matchCaseInsensitive(
|
|
fileOrDirectory,
|
|
await readdir((await realPathPromise) + delimiter),
|
|
filePath
|
|
),
|
|
basePath
|
|
)
|
|
}
|