Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE-240: Method to convert path to pattern #392

Merged
merged 2 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 55 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ This package provides methods for traversing the file system and returning pathn
* [Helpers](#helpers)
* [generateTasks](#generatetaskspatterns-options)
* [isDynamicPattern](#isdynamicpatternpattern-options)
* [escapePath](#escapepathpattern)
* [escapePath](#escapepathpath)
* [convertPathToPattern](#convertpathtopatternpath)
* [Options](#options-3)
* [Common](#common)
* [concurrency](#concurrency)
Expand Down Expand Up @@ -268,21 +269,57 @@ Any correct pattern.

See [Options](#options-3) section.

#### `escapePath(pattern)`
#### `escapePath(path)`

Returns a path with escaped special characters (`*?|(){}[]`, `!` at the beginning of line, `@+!` before the opening parenthesis).
Returns the path with escaped special characters depending on the platform.

* Posix:
* `*?|(){}[]`;
* `!` at the beginning of line;
* `@+!` before the opening parenthesis;
* `\\` before non-special characters;
* Windows:
* `(){}`
* `!` at the beginning of line;
* `@+!` before the opening parenthesis;
* Characters like `*?|[]` cannot be used in the path ([windows_naming_conventions][windows_naming_conventions]), so they will not be escaped;

```js
fg.escapePath('!abc'); // \\!abc
fg.escapePath('C:/Program Files (x86)'); // C:/Program Files \\(x86\\)
fg.escapePath('!abc');
// \\!abc
fg.escapePath('[OpenSource] mrmlnc – fast-glob (Deluxe Edition) 2014') + '/*.flac'
// \\[OpenSource\\] mrmlnc – fast-glob \\(Deluxe Edition\\) 2014/*.flac

fg.posix.escapePath('C:\\Program Files (x86)\\**\\*');
// C:\\\\Program Files \\(x86\\)\\*\\*\\*
fg.win32.escapePath('C:\\Program Files (x86)\\**\\*');
// Windows: C:\\Program Files \\(x86\\)\\**\\*
```

##### pattern
#### `convertPathToPattern(path)`

* Required: `true`
* Type: `string`
Converts a path to a pattern depending on the platform, including special character escaping.

* Posix. Works similarly to the `fg.posix.escapePath` method.
* Windows. Works similarly to the `fg.win32.escapePath` method, additionally converting backslashes to forward slashes in cases where they are not escape characters (`!()+@{}`).

Any string, for example, a path to a file.
```js
fg.convertPathToPattern('[OpenSource] mrmlnc – fast-glob (Deluxe Edition) 2014') + '/*.flac';
// \\[OpenSource\\] mrmlnc – fast-glob \\(Deluxe Edition\\) 2014/*.flac

fg.convertPathToPattern('C:/Program Files (x86)/**/*');
// Posix: C:/Program Files \\(x86\\)/\\*\\*/\\*
// Windows: C:/Program Files \\(x86\\)/**/*

fg.convertPathToPattern('C:\\Program Files (x86)\\**\\*');
// Posix: C:\\\\Program Files \\(x86\\)\\*\\*\\*
// Windows: C:/Program Files \\(x86\\)/**/*

fg.posix.convertPathToPattern('\\\\?\\c:\\Program Files (x86)') + '/**/*';
// Posix: \\\\\\?\\\\c:\\\\Program Files \\(x86\\)/**/* (broken pattern)
fg.win32.convertPathToPattern('\\\\?\\c:\\Program Files (x86)') + '/**/*';
// Windows: //?/c:/Program Files \\(x86\\)/**/*
```

## Options

Expand Down Expand Up @@ -673,11 +710,11 @@ Always use forward-slashes in glob expressions (patterns and [`ignore`](#ignore)
```ts
[
'directory/*',
path.join(process.cwd(), '**').replace(/\\/g, '/')
fg.convertPathToPattern(process.cwd()) + '/**'
]
```

> :book: Use the [`normalize-path`][npm_normalize_path] or the [`unixify`][npm_unixify] package to convert Windows-style path to a Unix-style path.
> :book: Use the [`.convertPathToPattern`](#convertpathtopatternpath) package to convert Windows-style path to a Unix-style path.
Read more about [matching with backslashes][micromatch_backslashes].

Expand All @@ -698,7 +735,7 @@ Refers to Bash. You need to escape special characters:
fg.sync(['\\(special-*file\\).txt']) // ['(special-*file).txt']
```

Read more about [matching special characters as literals][picomatch_matching_special_characters_as_literals].
Read more about [matching special characters as literals][picomatch_matching_special_characters_as_literals]. Or use the [`.escapePath`](#escapepathpath).

## How to exclude directory from reading?

Expand All @@ -724,11 +761,15 @@ You have to understand that if you write the pattern to exclude directories, the

## How to use UNC path?

You cannot use [Uniform Naming Convention (UNC)][unc_path] paths as patterns (due to syntax), but you can use them as [`cwd`](#cwd) directory.
You cannot use [Uniform Naming Convention (UNC)][unc_path] paths as patterns (due to syntax) directly, but you can use them as [`cwd`](#cwd) directory or use the `fg.convertPathToPattern` method.

```ts
// cwd
fg.sync('*', { cwd: '\\\\?\\C:\\Python27' /* or //?/C:/Python27 */ });
fg.sync('Python27/*', { cwd: '\\\\?\\C:\\' /* or //?/C:/ */ });

// .convertPathToPattern
fg.sync(fg.convertPathToPattern('\\\\?\\c:\\Python27') + '/*');
```

## Compatible with `node-glob`?
Expand Down Expand Up @@ -815,3 +856,4 @@ This software is released under the terms of the MIT license.
[zotac_bi323]: https://www.zotac.com/ee/product/mini_pcs/zbox-bi323
[nodejs_thread_pool]: https://nodejs.org/en/docs/guides/dont-block-the-event-loop
[libuv_thread_pool]: http://docs.libuv.org/en/v1.x/threadpool.html
[windows_naming_conventions]: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
57 changes: 57 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,61 @@ describe('Package', () => {
assert.strictEqual(actual, expected);
});
});

describe('.convertPathToPattern', () => {
it('should return a pattern', () => {
// In posix system \\ is a escaping character and it will be escaped before non-special characters.
const posix = 'C:\\\\Program Files \\(x86\\)\\*\\*\\*';
const windows = 'C:/Program Files \\(x86\\)/**/*';
const expected = tests.platform.isWindows() ? windows : posix;

const actual = fg.convertPathToPattern('C:\\Program Files (x86)\\**\\*');

assert.strictEqual(actual, expected);
});
});

describe('posix', () => {
describe('.escapePath', () => {
it('should return escaped path', () => {
const expected = '/directory/\\*\\*/\\*';

const actual = fg.posix.escapePath('/directory/*\\*/*');

assert.strictEqual(actual, expected);
});
});

describe('.convertPathToPattern', () => {
it('should return a pattern', () => {
const expected = 'a\\*.txt';

const actual = fg.posix.convertPathToPattern('a\\*.txt');

assert.strictEqual(actual, expected);
});
});
});

describe('win32', () => {
describe('.escapePath', () => {
it('should return escaped path', () => {
const expected = 'C:\\Program Files \\(x86\\)\\**\\*';

const actual = fg.win32.escapePath('C:\\Program Files (x86)\\**\\*');

assert.strictEqual(actual, expected);
});
});

describe('.convertPathToPattern', () => {
it('should return a pattern', () => {
const expected = 'C:/Program Files \\(x86\\)/**/*';

const actual = fg.win32.convertPathToPattern('C:\\Program Files (x86)\\**\\*');

assert.strictEqual(actual, expected);
});
});
});
});
36 changes: 35 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,45 @@ namespace FastGlob {
return utils.pattern.isDynamicPattern(source, settings);
}

export function escapePath(source: PatternInternal): PatternInternal {
export function escapePath(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.escape(source);
}

export function convertPathToPattern(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.convertPathToPattern(source);
}

export namespace posix {
export function escapePath(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.escapePosixPath(source);
}

export function convertPathToPattern(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.convertPosixPathToPattern(source);
}
}

export namespace win32 {
export function escapePath(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.escapeWindowsPath(source);
}

export function convertPathToPattern(source: string): PatternInternal {
assertPatternsInput(source);

return utils.path.convertWindowsPathToPattern(source);
}
}
}

function getWorks<T>(source: PatternInternal | PatternInternal[], _Provider: new (settings: Settings) => Provider<T>, options?: OptionsInternal): T[] {
Expand Down
124 changes: 109 additions & 15 deletions src/utils/path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,7 @@ describe('Utils → Path', () => {
});
});

describe('.escapePattern', () => {
it('should return pattern with escaped glob symbols', () => {
assert.strictEqual(util.escape('!abc'), '\\!abc');
assert.strictEqual(util.escape('*'), '\\*');
assert.strictEqual(util.escape('?'), '\\?');
assert.strictEqual(util.escape('()'), '\\(\\)');
assert.strictEqual(util.escape('{}'), '\\{\\}');
assert.strictEqual(util.escape('[]'), '\\[\\]');
assert.strictEqual(util.escape('@('), '\\@\\(');
assert.strictEqual(util.escape('!('), '\\!\\(');
assert.strictEqual(util.escape('*('), '\\*\\(');
assert.strictEqual(util.escape('?('), '\\?\\(');
assert.strictEqual(util.escape('+('), '\\+\\(');
});

describe('.escape', () => {
it('should return pattern without additional escape characters', () => {
assert.strictEqual(util.escape('\\!abc'), '\\!abc');
assert.strictEqual(util.escape('\\*'), '\\*');
Expand All @@ -55,6 +41,34 @@ describe('Utils → Path', () => {
});
});

describe('.escapePosixPattern', () => {
it('should return pattern with escaped glob symbols', () => {
assert.strictEqual(util.escapePosixPath('!abc'), '\\!abc');
assert.strictEqual(util.escapePosixPath('*'), '\\*');
assert.strictEqual(util.escapePosixPath('?'), '\\?');
assert.strictEqual(util.escapePosixPath('\\'), '\\\\');
assert.strictEqual(util.escapePosixPath('()'), '\\(\\)');
assert.strictEqual(util.escapePosixPath('{}'), '\\{\\}');
assert.strictEqual(util.escapePosixPath('[]'), '\\[\\]');
assert.strictEqual(util.escapePosixPath('@('), '\\@\\(');
assert.strictEqual(util.escapePosixPath('!('), '\\!\\(');
assert.strictEqual(util.escapePosixPath('*('), '\\*\\(');
assert.strictEqual(util.escapePosixPath('?('), '\\?\\(');
assert.strictEqual(util.escapePosixPath('+('), '\\+\\(');
});
});

describe('.escapeWindowsPattern', () => {
it('should return pattern with escaped glob symbols', () => {
assert.strictEqual(util.escapeWindowsPath('!abc'), '\\!abc');
assert.strictEqual(util.escapeWindowsPath('()'), '\\(\\)');
assert.strictEqual(util.escapeWindowsPath('{}'), '\\{\\}');
assert.strictEqual(util.escapeWindowsPath('@('), '\\@\\(');
assert.strictEqual(util.escapeWindowsPath('!('), '\\!\\(');
assert.strictEqual(util.escapeWindowsPath('+('), '\\+\\(');
});
});

describe('.removeLeadingDotCharacters', () => {
it('should return path without changes', () => {
assert.strictEqual(util.removeLeadingDotSegment('../a/b'), '../a/b');
Expand All @@ -73,4 +87,84 @@ describe('Utils → Path', () => {
assert.strictEqual(util.removeLeadingDotSegment('.\\a\\b'), 'a\\b');
});
});

describe('.convertPathToPattern', () => {
it('should return a pattern', () => {
assert.strictEqual(util.convertPathToPattern('.{directory}'), '.\\{directory\\}');
});
});

describe('.convertPosixPathToPattern', () => {
it('should escape special characters', () => {
assert.strictEqual(util.convertPosixPathToPattern('./**\\*'), './\\*\\*\\*');
});
});

describe('.convertWindowsPathToPattern', () => {
it('should escape special characters', () => {
assert.strictEqual(util.convertPosixPathToPattern('.{directory}'), '.\\{directory\\}');
});

it('should do nothing with escaped glob symbols', () => {
assert.strictEqual(util.convertWindowsPathToPattern('\\!\\'), '\\!/');
assert.strictEqual(util.convertWindowsPathToPattern('\\+\\'), '\\+/');
assert.strictEqual(util.convertWindowsPathToPattern('\\@\\'), '\\@/');
assert.strictEqual(util.convertWindowsPathToPattern('\\(\\'), '\\(/');
assert.strictEqual(util.convertWindowsPathToPattern('\\)\\'), '\\)/');
assert.strictEqual(util.convertWindowsPathToPattern('\\{\\'), '\\{/');
assert.strictEqual(util.convertWindowsPathToPattern('\\}\\'), '\\}/');

assert.strictEqual(util.convertWindowsPathToPattern('.\\*'), './*');
assert.strictEqual(util.convertWindowsPathToPattern('.\\**'), './**');
assert.strictEqual(util.convertWindowsPathToPattern('.\\**\\*'), './**/*');

assert.strictEqual(util.convertWindowsPathToPattern('a\\{b,c\\d,{b,c}}'), 'a\\{b,c/d,\\{b,c\\}\\}');
});

it('should convert slashes', () => {
assert.strictEqual(util.convertWindowsPathToPattern('/'), '/');
assert.strictEqual(util.convertWindowsPathToPattern('\\'), '/');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\'), '//');
assert.strictEqual(util.convertWindowsPathToPattern('\\/'), '//');
assert.strictEqual(util.convertWindowsPathToPattern('\\/\\'), '///');
});

it('should convert relative paths', () => {
assert.strictEqual(util.convertWindowsPathToPattern('file.txt'), 'file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('./file.txt'), './file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('.\\file.txt'), './file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('../file.txt'), '../file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('..\\file.txt'), '../file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('.\\file.txt'), './file.txt');
});

it('should convert absolute paths', () => {
assert.strictEqual(util.convertWindowsPathToPattern('/.file.txt'), '/.file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('/root/.file.txt'), '/root/.file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\.file.txt'), '/.file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\root\\.file.txt'), '/root/.file.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\root/.file.txt'), '/root/.file.txt');
});

it('should convert traditional DOS paths', () => {
assert.strictEqual(util.convertWindowsPathToPattern('D:ShipId.txt'), 'D:ShipId.txt');
assert.strictEqual(util.convertWindowsPathToPattern('D:/ShipId.txt'), 'D:/ShipId.txt');
assert.strictEqual(util.convertWindowsPathToPattern('D://ShipId.txt'), 'D://ShipId.txt');

assert.strictEqual(util.convertWindowsPathToPattern('D:\\ShipId.txt'), 'D:/ShipId.txt');
assert.strictEqual(util.convertWindowsPathToPattern('D:\\\\ShipId.txt'), 'D://ShipId.txt');
assert.strictEqual(util.convertWindowsPathToPattern('D:\\/ShipId.txt'), 'D://ShipId.txt');
});

it('should convert UNC paths', () => {
assert.strictEqual(util.convertWindowsPathToPattern('\\\\system07\\'), '//system07/');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\system07\\c$\\'), '//system07/c$/');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\Server02\\Share\\Foo.txt'), '//Server02/Share/Foo.txt');

assert.strictEqual(util.convertWindowsPathToPattern('\\\\127.0.0.1\\c$\\File.txt'), '//127.0.0.1/c$/File.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\.\\c:\\File.txt'), '//./c:/File.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\?\\c:\\File.txt'), '//?/c:/File.txt');
assert.strictEqual(util.convertWindowsPathToPattern('\\\\.\\UNC\\LOCALHOST\\c$\\File.txt'), '//./UNC/LOCALHOST/c$/File.txt');
});
});
});
Loading