path.js 3.62 KB
/*
 Copyright 2012-2015, Yahoo Inc.
 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';

const path = require('path');
let parsePath = path.parse;
let SEP = path.sep;
const origParser = parsePath;
const origSep = SEP;

function makeRelativeNormalizedPath(str, sep) {
    const parsed = parsePath(str);
    let root = parsed.root;
    let dir;
    let file = parsed.base;
    let quoted;
    let pos;

    // handle a weird windows case separately
    if (sep === '\\') {
        pos = root.indexOf(':\\');
        if (pos >= 0) {
            root = root.substring(0, pos + 2);
        }
    }
    dir = parsed.dir.substring(root.length);

    if (str === '') {
        return [];
    }

    if (sep !== '/') {
        quoted = new RegExp(sep.replace(/\W/g, '\\$&'), 'g');
        dir = dir.replace(quoted, '/');
        file = file.replace(quoted, '/'); // excessively paranoid?
    }

    if (dir !== '') {
        dir = `${dir}/${file}`;
    } else {
        dir = file;
    }
    if (dir.substring(0, 1) === '/') {
        dir = dir.substring(1);
    }
    dir = dir.split(/\/+/);
    return dir;
}

class Path {
    constructor(strOrArray) {
        if (Array.isArray(strOrArray)) {
            this.v = strOrArray;
        } else if (typeof strOrArray === 'string') {
            this.v = makeRelativeNormalizedPath(strOrArray, SEP);
        } else {
            throw new Error(
                `Invalid Path argument must be string or array:${strOrArray}`
            );
        }
    }

    toString() {
        return this.v.join('/');
    }

    hasParent() {
        return this.v.length > 0;
    }

    parent() {
        if (!this.hasParent()) {
            throw new Error('Unable to get parent for 0 elem path');
        }
        const p = this.v.slice();
        p.pop();
        return new Path(p);
    }

    elements() {
        return this.v.slice();
    }

    name() {
        return this.v.slice(-1)[0];
    }

    contains(other) {
        let i;
        if (other.length > this.length) {
            return false;
        }
        for (i = 0; i < other.length; i += 1) {
            if (this.v[i] !== other.v[i]) {
                return false;
            }
        }
        return true;
    }

    ancestorOf(other) {
        return other.contains(this) && other.length !== this.length;
    }

    descendantOf(other) {
        return this.contains(other) && other.length !== this.length;
    }

    commonPrefixPath(other) {
        const len = this.length > other.length ? other.length : this.length;
        let i;
        const ret = [];

        for (i = 0; i < len; i += 1) {
            if (this.v[i] === other.v[i]) {
                ret.push(this.v[i]);
            } else {
                break;
            }
        }
        return new Path(ret);
    }

    static compare(a, b) {
        const al = a.length;
        const bl = b.length;

        if (al < bl) {
            return -1;
        }

        if (al > bl) {
            return 1;
        }

        const astr = a.toString();
        const bstr = b.toString();
        return astr < bstr ? -1 : astr > bstr ? 1 : 0;
    }
}

['push', 'pop', 'shift', 'unshift', 'splice'].forEach(fn => {
    Object.defineProperty(Path.prototype, fn, {
        value(...args) {
            return this.v[fn](...args);
        }
    });
});

Object.defineProperty(Path.prototype, 'length', {
    enumerable: true,
    get() {
        return this.v.length;
    }
});

module.exports = Path;
Path.tester = {
    setParserAndSep(p, sep) {
        parsePath = p;
        SEP = sep;
    },
    reset() {
        parsePath = origParser;
        SEP = origSep;
    }
};