Skip to content

Commit

Permalink
Merge pull request #392 from mrmlnc/ISSUE-240_p2p
Browse files Browse the repository at this point in the history
ISSUE-240: Method to convert path to pattern
  • Loading branch information
mrmlnc committed May 24, 2023
2 parents 4c60799 + 1fed462 commit 1d9d3ce
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 34 deletions.
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

0 comments on commit 1d9d3ce

Please sign in to comment.