Skip to content

Commit

Permalink
add support for leading ../ in patterns (#18)
Browse files Browse the repository at this point in the history
* add support for leading `../` in patterns

* add test for leading `./`
  • Loading branch information
SuperchupuDev authored Aug 22, 2024
1 parent 1b51f7b commit 2f36404
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 17 deletions.
65 changes: 50 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'node:path';
import path, { posix } from 'node:path';
import { type Options as FdirOptions, fdir } from 'fdir';
import picomatch from 'picomatch';

Expand All @@ -14,18 +14,34 @@ export interface GlobOptions {
onlyFiles?: boolean;
}

let root: string;

function normalizePattern(pattern: string, expandDirectories: boolean, cwd: string) {
let result: string = pattern;
if (pattern.endsWith('/')) {
result = pattern.slice(0, -1);
}
// using a directory as entry should match all files inside it
if (!pattern.endsWith('*') && expandDirectories) {
if (!result.endsWith('*') && expandDirectories) {
result += '/**';
}
if (result.startsWith(cwd.replace(/\\/g, '/'))) {
result = path.relative(cwd, result).replace(/\\/g, '/');

if (result.startsWith(cwd)) {
return posix.relative(cwd, result);
}

if (result.startsWith('./')) {
result = result.slice(2);
}

const parentDirectoryMatch = /^(\/?\.\.)+/.exec(result);
if (parentDirectoryMatch?.[0]) {
const potentialRoot = posix.join(cwd, parentDirectoryMatch[0]);
if (root.length > potentialRoot.length) {
root = potentialRoot;
}
}

return result;
}

Expand All @@ -49,13 +65,24 @@ function processPatterns({ patterns, ignore = [], expandDirectories = true }: Gl
return { match: matchPatterns, ignore: ignorePatterns };
}

function processPath(path: string, cwd: string, absolute?: boolean) {
const pathWithoutTrailingSlash = path.endsWith('/') ? path.slice(0, -1) : path;
// TODO: this is slow, find a better way to do this
function getRelativePath(path: string, cwd: string) {
return posix.relative(cwd, `${root}/${path}`);
}

function processPath(path: string, cwd: 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 absolute ? pathWithoutTrailingSlash.slice(cwd.length + 1) : pathWithoutTrailingSlash;
return getRelativePath(relativePath, cwd);
}

function getFdirBuilder(options: GlobOptions, cwd: string) {
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 matcher = picomatch(processed.match, {
Expand All @@ -69,8 +96,8 @@ function getFdirBuilder(options: GlobOptions, cwd: string) {

const fdirOptions: Partial<FdirOptions> = {
// use relative paths in the matcher
filters: [p => matcher(processPath(p, cwd, options.absolute))],
exclude: (_, p) => exclude(p.slice(cwd.length + 1).slice(0, -1)),
filters: [(p, isDirectory) => matcher(processPath(p, cwd, isDirectory, options.absolute))],
exclude: (_, p) => exclude(processPath(p, cwd, true, true)),
pathSeparator: '/',
relativePaths: true
};
Expand All @@ -92,7 +119,15 @@ function getFdirBuilder(options: GlobOptions, cwd: string) {
fdirOptions.includeDirs = true;
}

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

if (cwd === 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)));
}

export function glob(patterns: string[], options?: Omit<GlobOptions, 'patterns'>): Promise<string[]>;
Expand All @@ -103,9 +138,9 @@ export async function glob(patternsOrOptions: string[] | GlobOptions, options?:
}

const opts = Array.isArray(patternsOrOptions) ? { ...options, patterns: patternsOrOptions } : patternsOrOptions;
const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();
const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/');

return getFdirBuilder(opts, cwd).crawl(cwd).withPromise();
return crawl(opts, cwd, false);
}

export function globSync(patterns: string[], options?: Omit<GlobOptions, 'patterns'>): string[];
Expand All @@ -116,7 +151,7 @@ export function globSync(patternsOrOptions: string[] | GlobOptions, options?: Gl
}

const opts = Array.isArray(patternsOrOptions) ? { ...options, patterns: patternsOrOptions } : patternsOrOptions;
const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();
const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/');

return getFdirBuilder(opts, cwd).crawl(cwd).sync();
return crawl(opts, cwd, true);
}
16 changes: 14 additions & 2 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ test('handle absolute patterns to some extent', async () => {
assert.deepEqual(files.sort(), ['a/a.ts']);
});

test('leading ../', async () => {
const files = await glob({ patterns: ['../b/*.ts'], cwd: path.join(cwd, 'a') });
assert.deepEqual(files.sort(), ['../b/a.ts', '../b/b.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`]);
});

test('bracket expanding', async () => {
const files = await glob({ patterns: ['a/{a,b}.ts'], cwd });
assert.deepEqual(files.sort(), ['a/a.ts', 'a/b.ts']);
Expand Down Expand Up @@ -130,9 +140,11 @@ test('using extglob patterns', async () => {
});

test('pattern normalization', async () => {
const files1 = await glob({ patterns: ['a/'], cwd });
const files2 = await glob({ patterns: ['a'], cwd });
const files1 = await glob({ patterns: ['a'], cwd });
const files2 = await glob({ patterns: ['a/'], cwd });
const files3 = await glob({ patterns: ['./a'], cwd });
assert.deepEqual(files1, files2);
assert.deepEqual(files1, files3);
});

test('negative patterns in options', async () => {
Expand Down

0 comments on commit 2f36404

Please sign in to comment.