Skip to content

Commit

Permalink
infer common roots (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperchupuDev authored Aug 24, 2024
1 parent b298cc8 commit 696d4fa
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lint:fix": "biome lint --fix --unsafe",
"test": "tsx --test",
"test:cov": "tsx --test --experimental-test-coverage",
"test:only": "tsx --test --test-only",
"typecheck": "tsc --noEmit"
},
"main": "dist/index.js",
Expand Down
79 changes: 60 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,19 @@ export interface GlobOptions {
onlyFiles?: boolean;
}

let root: string;
interface InternalProperties {
root: string;
commonPath: string[] | null;
depthOffset: number;
}

function normalizePattern(pattern: string, expandDirectories: boolean, cwd: string) {
function normalizePattern(
pattern: string,
expandDirectories: boolean,
cwd: string,
properties: InternalProperties,
isIgnore: boolean
) {
let result: string = pattern;
if (pattern.endsWith('/')) {
result = pattern.slice(0, -1);
Expand All @@ -37,24 +47,48 @@ function normalizePattern(pattern: string, expandDirectories: boolean, cwd: stri
const parentDirectoryMatch = /^(\/?\.\.)+/.exec(result);
if (parentDirectoryMatch?.[0]) {
const potentialRoot = posix.join(cwd, parentDirectoryMatch[0]);
if (root.length > potentialRoot.length) {
root = potentialRoot;
if (properties.root.length > potentialRoot.length) {
properties.root = potentialRoot;
properties.depthOffset = -(parentDirectoryMatch[0].length + 1) / 3;
}
} else if (!isIgnore && properties.depthOffset >= 0) {
const current = result.split('/');
properties.commonPath ??= current;

const newCommonPath = [];

for (let i = 0; i < Math.min(properties.commonPath.length, current.length); i++) {
const part = current[i];
if (properties.commonPath[i] === part && !/[\!\*\{\}\(\)]/.test(part)) {
newCommonPath.push(part);
} else {
newCommonPath.pop();
break;
}
}

properties.depthOffset = newCommonPath.length;
properties.commonPath = newCommonPath;
properties.root = `${cwd}/${newCommonPath.join('/')}`;
}

return result;
}

function processPatterns({ patterns, ignore = [], expandDirectories = true }: GlobOptions, cwd: string) {
function processPatterns(
{ patterns, ignore = [], expandDirectories = true }: GlobOptions,
cwd: string,
properties: InternalProperties
) {
const matchPatterns: string[] = [];
const ignorePatterns: string[] = ignore.map(p => normalizePattern(p, expandDirectories, cwd));
const ignorePatterns: string[] = ignore.map(p => normalizePattern(p, expandDirectories, cwd, properties, true));

if (!patterns) {
return { match: ['**/*'], ignore: ignorePatterns };
}

for (let pattern of patterns) {
pattern = normalizePattern(pattern, expandDirectories, cwd);
pattern = normalizePattern(pattern, expandDirectories, cwd, properties, false);
if (pattern.startsWith('!') && pattern[1] !== '(') {
ignorePatterns.push(pattern.slice(1));
} else {
Expand All @@ -66,24 +100,29 @@ function processPatterns({ patterns, ignore = [], expandDirectories = true }: Gl
}

// TODO: this is slow, find a better way to do this
function getRelativePath(path: string, cwd: string) {
function getRelativePath(path: string, cwd: string, root: string) {
return posix.relative(cwd, `${root}/${path}`);
}

function processPath(path: string, cwd: string, isDirectory: boolean, absolute?: boolean) {
function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean) {
const relativePath = absolute ? path.slice(root.length + 1) : path;
if (root === cwd) {
return isDirectory ? relativePath.slice(0, -1) : relativePath;
}

return getRelativePath(relativePath, cwd);
return getRelativePath(relativePath, cwd, root);
}

function crawl(options: GlobOptions, cwd: string, sync: false): Promise<string[]>;
function crawl(options: GlobOptions, cwd: string, sync: true): string[];
function crawl(options: GlobOptions, cwd: string, sync: boolean) {
root = cwd;
const processed = processPatterns(options, cwd);
const properties = {
root: cwd,
commonPath: null,
depthOffset: 0
};

const processed = processPatterns(options, cwd, properties);

const matcher = picomatch(processed.match, {
dot: options.dot,
Expand All @@ -96,14 +135,14 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) {

const fdirOptions: Partial<FdirOptions> = {
// use relative paths in the matcher
filters: [(p, isDirectory) => matcher(processPath(p, cwd, isDirectory, options.absolute))],
exclude: (_, p) => exclude(processPath(p, cwd, true, true)),
filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))],
exclude: (_, p) => exclude(processPath(p, cwd, properties.root, true, true)),
pathSeparator: '/',
relativePaths: true
};

if (options.deep) {
fdirOptions.maxDepth = options.deep;
fdirOptions.maxDepth = Math.round(options.deep - properties.depthOffset);
}

if (options.absolute) {
Expand All @@ -119,15 +158,17 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) {
fdirOptions.includeDirs = true;
}

const api = new fdir(fdirOptions).crawl(root);
const api = new fdir(fdirOptions).crawl(properties.root);

if (cwd === root || options.absolute) {
if (cwd === properties.root || options.absolute) {
return sync ? api.sync() : api.withPromise();
}

return sync
? api.sync().map(p => getRelativePath(p, cwd))
: api.withPromise().then(paths => paths.map(p => getRelativePath(p, cwd)));
? api.sync().map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : ''))
: api
.withPromise()
.then(paths => paths.map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : '')));
}

export function glob(patterns: string[], options?: Omit<GlobOptions, 'patterns'>): Promise<string[]>;
Expand Down
16 changes: 16 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ test('leading ../', async () => {
assert.deepEqual(files.sort(), ['../b/a.ts', '../b/b.ts']);
});

test('leading ../ plus normal pattern', async () => {
const files = await glob({ patterns: ['../b/*.ts', 'a.ts'], cwd: path.join(cwd, 'a') });
assert.deepEqual(files.sort(), ['../b/a.ts', '../b/b.ts', 'a.ts']);
});

test('leading ../ with absolute on', async () => {
const files = await glob({ patterns: ['../b/*.ts'], absolute: true, cwd: path.join(cwd, 'a') });
assert.deepEqual(files.sort(), [`${cwd.replaceAll('\\', '/')}/b/a.ts`, `${cwd.replaceAll('\\', '/')}/b/b.ts`]);
Expand All @@ -104,6 +109,17 @@ test('deep', async () => {
assert.deepEqual(files3.sort(), []);
});

test('deep with ../', async () => {
const files = await glob({ patterns: ['../.deep/a/a/*.ts', 'a.ts'], deep: 3, cwd: path.join(cwd, 'a') });
assert.deepEqual(files.sort(), ['../.deep/a/a/a.ts', 'a.ts']);

const files2 = await glob({ patterns: ['../.deep/a/a/*.ts', 'a.ts'], deep: 2, cwd: path.join(cwd, 'a') });
assert.deepEqual(files2.sort(), ['../.deep/a/a/a.ts', 'a.ts']);

const files3 = await glob({ patterns: ['../.deep/a/a/*.ts', 'a.ts'], deep: 1, cwd: path.join(cwd, 'a') });
assert.deepEqual(files3.sort(), ['a.ts']);
});

test('absolute + dot', async () => {
const files = await glob({ patterns: ['a/a.ts'], dot: true, cwd: path.join(cwd, '.a'), absolute: true });
assert.deepEqual(files.sort(), [`${cwd.replaceAll('\\', '/')}/.a/a/a.ts`]);
Expand Down

0 comments on commit 696d4fa

Please sign in to comment.