UNPKG

xtutils

Version:

Thuku's assorted general purpose typescript/javascript library.

283 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._filepath = exports._basename = void 0; const utils_1 = require("../utils"); /** * Get validated basename from file path value * * - splits path separators `[\\/]` uses last entry * - trims spaces, invalidates empty * - invalidates illegal characters (i.e. `:?"<>|*`) * - invalidates invalid names (i.e. `'...', 'name.', 'name...'`) * * @param value - parse path value * @param dots - allow dot nav ~ `'.' | '..'` (default: `false`) * @param _strict - strict parsing ~ returns blank (`''`) values on error * @param _failure - error handling ~ `0` = ignore, '1' = warn, `2` = throw error (default `0`) * @returns `IBasename` basename (stringable) * @throws `IBasenameError` */ const _basename = (value, dots = false, _strict = false, _failure) => { const failure = [0, 1, 2].includes(_failure = parseInt(_failure + '')) ? _failure : 0; //basename item const item = { value, basename: '', name: '', ext: '', error: '', illegal: [], invalid: [], toString(prop) { const key = prop && ['basename', 'name', 'ext', 'error'] .includes(prop = (0, utils_1._str)(prop, true).toLowerCase()) ? prop + '' : 'basename'; return this[key]; }, }; //parse value let m = null; const val = item.name = item.basename = (0, utils_1._str)((0, utils_1._str)(value, true).split(/[\\\/]/g).pop(), true); if (m = val.match(/([^\\/]*)$/i)) { item.name = m[1]; if (m = item.name.match(/(.*)(\.([-_0-9a-zA-Z]+))$/i)) { item.name = m[1]; item.ext = m[3]; } } try { const errors = []; if (!val) errors.push('The basename string value is empty'); else { //dots if (!dots && ['..', '..'].includes(val)) { item.invalid.push(val); errors.push(`The basename "${val}" dots not allowed`); } //invalid if (/^\.\.[\.]+$/.test(val) || /[^\.][\.]+$/.test(val)) { if (!item.invalid.length) item.invalid.push(val); errors.push(`The basename "${val}" format is invalid`); } //illegal if (m = val.match(/[\:\?\"\<\>\|\*]/g)) { item.illegal.push(...m); errors.push(`The basename "${val}" contains illegal characters (:?"<>|*) => "${m.join('')}"`); } } if (errors.length) throw new Error(item.error = errors.join('; ') + '.'); //error - set, throw return item; //result } catch (e) { if (_strict) { //strict - clear item.basename = ''; item.name = ''; item.ext = ''; } if (failure) { //failure - custom error class BasenameError extends Error { name = 'BasenameError'; item = item; } const error = new BasenameError(`${e.message || e}`); if (failure === 2) throw error; //throw else console.warn(error + '', { item }); //warn } return item; //result } }; exports._basename = _basename; /** * Get normalized file/directory path (validates basename) * * - trims spaces, silently omits empty * - invalidates illegal path name characters (i.e. `:?"<>|*`) * - invalidates invalid path name dots (i.e. `'...', 'name.', 'name...'`) * - invalidates outbound root dot nav * - normalizes dot path (i.e. `'/.'` => `'/'`, `'a/b/./c' => 'a/b/c'`, `'./a/../b/c' => './b/c'`) ignores out of bound (i.e. `'C:/a/../../b/c' => 'C:/b/c'`) * - normalizes drive letter (i.e. `'c:\\a.txt' => 'C:\\a.txt'`, `'c:'` => `'C:\\'`) * * @param value - parse path value * @param separator - result path separator ~ `'' | '/' | '\\'` (default `''` = unchanged) * @param _strict - strict parsing ~ returns blank (`''`) values on error * @param _type - path type (default `''`) ~ name used in error message (i.e. `'The ${_type} path...'`) * @param _failure - error handling ~ `0` = ignore, '1' = warn, `2` = throw error (default `0`) * @returns `IFilePath` normalized path (stringable) */ const _filepath = (value, separator, _strict = false, _type, _failure) => { const sep = ['', '/', '\\'].includes(separator = (0, utils_1._str)(separator, true)) ? separator : ''; const type = (_type = (0, utils_1._str)(_type, true).replace(/path\s*$/i, '').trim()) ? _type + ' ' : ''; const failure = [0, 1, 2].includes(_failure = parseInt(_failure + '')) ? _failure : 0; //normalized path item const item = { value, root: '', drive: '', path: '', dir: '', basename: '', name: '', ext: '', error: '', illegal: [], invalid: [], toString(prop) { let key = prop && ['file', 'root', 'drive', 'path', 'dir', 'basename', 'name', 'ext', 'error'] .includes(prop = (0, utils_1._str)(prop, true).toLowerCase()) ? prop + '' : 'path'; if (key === 'file') { if (!(!this.error && this.basename)) return ''; key = 'path'; } return this[key]; }, }; //parse path let path = (0, utils_1._str)(value, true); try { let root = '', drive = '', m = null; const items = []; const path_parts = (0, utils_1._split)(path, /[\\\/]/); for (let i = 0; i < path_parts.length; i++) { let [part, div] = path_parts[i]; div = div ? (sep ? sep : div) : ''; if (!i) { if (/[a-z]\:/i.test(part)) root = drive = part.toUpperCase() + ((sep ? sep : div) || '\\'); else if (!part && div) root = div; if (root) continue; } items.push([part, div]); } //parse items - trim basename const trimmed_parts = []; for (let i = 0; i < items.length; i++) { let [part, div] = items[i]; part = (0, utils_1._str)(part, true); if (i && !part) continue; //skip blank ('') entries trimmed_parts.push([part, div]); } //parse trimmed - normalize dot path const norm_parts = []; for (let i = 0; i < trimmed_parts.length; i++) { let [part, div] = trimmed_parts[i]; if (part === '.' && (!i && root || i)) { //match dot path ('.') (at start with root, not at start) if (i && i === trimmed_parts.length - 1) trimmed_parts[i - 1][1] = ''; //if last remove previous separator continue; //skip unnecessary dot path ('.') } norm_parts.push([part, div]); } //parse normalized - validate parts const invalid = new Set(); const illegal = new Set(); const outbound = []; const parts = []; for (let i = 0; i < norm_parts.length; i++) { const [part, div] = norm_parts[i]; //part entry try { (0, exports._basename)(part, true, false, 2); //validate basename } catch (e) { if (Array.isArray(e?.item?.invalid) && e.item.invalid.length) { for (const v of e.item.invalid) invalid.add(v); } if (Array.isArray(e?.item?.illegal) && e.item.illegal.length) { for (const v of e.item.illegal) illegal.add(v); } } if (part === '..') { //dot path ('..') nav if (parts.length) { //pop parent const p = parts.length - 1; if (p > -1 && !!parts[p][0] && !['.', '..'].includes(parts[p][0])) { parts.pop(); continue; } } else if (root) { //root parent - outbound outbound.push([part, div]); if (drive) continue; //ignore when root is drive } } parts.push([part, div]); //add entry } if (root && parts.length && !parts[0][0] && parts[0][1]) parts[0][1] = ''; //fix root separator if (outbound.length) outbound.push(...parts); //outbound entries //update item - set root, drive, dir, basename, ext, dir item.root = root; item.drive = drive; //join parts - update dir, path let tmp_path = ''; for (const part of parts) tmp_path += part.join(''); item.dir = item.path = path = root + tmp_path.replace(/[\\/]$/, ''); //update item - set basename, name, ext, dir const end = parts.pop(); if (end && !['', '.', '..'].includes(end[0])) { const basename = (0, exports._basename)(end[0]); item.basename = basename.basename; item.name = basename.name; item.ext = basename.ext; //join parts - update dir tmp_path = ''; for (const part of parts) tmp_path += part.join(''); item.dir = root + tmp_path.replace(/[\\/]$/, ''); } //check errors - update item const errors = []; tmp_path = ''; for (const part of outbound) tmp_path += part.join(''); const outbound_path = tmp_path ? root + tmp_path : ''; if (outbound_path) errors.push(`The ${type}root (${root}) dot nav path is outbound "${outbound_path}" => "${path}"`); //outbound if (invalid.size) errors.push(`The ${type}path contains invalid name${invalid.size > 1 ? 's' : ''} (${(0, utils_1._jsonStringify)([...invalid])})`); //invalid if (illegal.size) errors.push(`The ${type}path contains illegal characters (:?"<>|*) => "${[...illegal].join('')}"`); //illegal if (errors.length) { //throw errors item.invalid = [...invalid]; item.illegal = [...illegal]; throw new Error(item.error = errors.join('; ') + '.'); //error - set, throw } return item; //<< result - IFilePath } catch (e) { if (_strict) { //strict - clear item.root = ''; item.drive = ''; item.path = ''; item.dir = ''; item.basename = ''; item.name = ''; item.ext = ''; } if (failure) { //failure - custom error class NormPathError extends Error { name = 'NormPathError'; item = item; } const error = new NormPathError(`${e.message || e}`); if (failure === 2) throw error; //throw else console.warn(error + '', { item }); //warn } return item; //<< result - IFilePath (failed) } }; exports._filepath = _filepath; //# sourceMappingURL=_path.js.map