Skip to content

Commit

Permalink
feat: escape special characters in the path depending on the platform
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmlnc committed May 14, 2023
1 parent cc5e9cc commit 1dfd43c
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 45 deletions.
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This package provides methods for traversing the file system and returning pathn
* [Helpers](#helpers)
* [generateTasks](#generatetaskspatterns-options)
* [isDynamicPattern](#isdynamicpatternpattern-options)
* [escapePath](#escapepathpattern)
* [escapePath](#escapepathpath)
* [Options](#options-3)
* [Common](#common)
* [concurrency](#concurrency)
Expand Down Expand Up @@ -264,21 +264,32 @@ 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.

```js
fg.escapePath('!abc'); // \\!abc
fg.escapePath('C:/Program Files (x86)'); // C:/Program Files \\(x86\\)
```

##### pattern
* 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;

* Required: `true`
* Type: `string`
```js
fg.escapePath('!abc');
// \\!abc
fg.escapePath('[OpenSource] mrmlnc – fast-glob (Deluxe Edition) 2014') + '/*.flac'
// \\[OpenSource\\] mrmlnc – fast-glob \\(Deluxe Edition\\) 2014/*.flac

Any string, for example, a path to a file.
fg.posix.escapePath('C:\\Program Files (x86)\\**\\*');
// C:\\\\Program Files \\(x86\\)\\*\\*\\*
fg.win32.escapePath('C:\\Program Files (x86)\\**\\*');
// Windows: C:\\Program Files \\(x86\\)\\**\\*
```

## Options

Expand Down Expand Up @@ -811,3 +822,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
24 changes: 24 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,28 @@ describe('Package', () => {
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('.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);
});
});
});
});
18 changes: 17 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,27 @@ 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 namespace posix {
export function escapePath(source: string): PatternInternal {
assertPatternsInput(source);

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

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

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

function getWorks<T>(source: PatternInternal | PatternInternal[], _Provider: new (settings: Settings) => Provider<T>, options?: OptionsInternal): T[] {
Expand Down
68 changes: 41 additions & 27 deletions src/utils/path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ 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('.removeLeadingDotCharacters', () => {
it('should return path without changes', () => {
assert.strictEqual(util.removeLeadingDotSegment('../a/b'), '../a/b');
assert.strictEqual(util.removeLeadingDotSegment('~/a/b'), '~/a/b');
assert.strictEqual(util.removeLeadingDotSegment('/a/b'), '/a/b');
assert.strictEqual(util.removeLeadingDotSegment('a/b'), 'a/b');

assert.strictEqual(util.removeLeadingDotSegment('..\\a\\b'), '..\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('~\\a\\b'), '~\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('\\a\\b'), '\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('a\\b'), 'a\\b');
});

it('should return path without leading dit characters', () => {
assert.strictEqual(util.removeLeadingDotSegment('./a/b'), 'a/b');
assert.strictEqual(util.removeLeadingDotSegment('.\\a\\b'), 'a\\b');
});
});

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

describe('.removeLeadingDotCharacters', () => {
it('should return path without changes', () => {
assert.strictEqual(util.removeLeadingDotSegment('../a/b'), '../a/b');
assert.strictEqual(util.removeLeadingDotSegment('~/a/b'), '~/a/b');
assert.strictEqual(util.removeLeadingDotSegment('/a/b'), '/a/b');
assert.strictEqual(util.removeLeadingDotSegment('a/b'), 'a/b');

assert.strictEqual(util.removeLeadingDotSegment('..\\a\\b'), '..\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('~\\a\\b'), '~\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('\\a\\b'), '\\a\\b');
assert.strictEqual(util.removeLeadingDotSegment('a\\b'), 'a\\b');
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('+('), '\\+\\(');
});
});

it('should return path without leading dit characters', () => {
assert.strictEqual(util.removeLeadingDotSegment('./a/b'), 'a/b');
assert.strictEqual(util.removeLeadingDotSegment('.\\a\\b'), 'a\\b');
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('+('), '\\+\\(');
});
});
});
24 changes: 19 additions & 5 deletions src/utils/path.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import * as os from 'os';
import * as path from 'path';

import { Pattern } from '../types';

const IS_WINDOWS_PLATFORM = os.platform() === 'win32';
const LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2; // ./ or .\\
const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g;
/*
* All non-escaped special characters.
* Posix: ()*?[\]{|}, !+@ before (, ! at the beginning, \\ before non-special characters.
* Windows: (){}, !+@ before (, ! at the beginning.
*/
const POSIX_UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\()|\\(?![!()*+?@[\]{|}]))/g;
const WINDOWS_UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([(){}]|^!|[!+@](?=\())/g;

/**
* Designed to work only with simple paths: `dir\\file`.
Expand All @@ -16,10 +24,6 @@ export function makeAbsolute(cwd: string, filepath: string): string {
return path.resolve(cwd, filepath);
}

export function escape(pattern: Pattern): Pattern {
return pattern.replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2');
}

export function removeLeadingDotSegment(entry: string): string {
// We do not use `startsWith` because this is 10x slower than current implementation for some cases.
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
Expand All @@ -33,3 +37,13 @@ export function removeLeadingDotSegment(entry: string): string {

return entry;
}

export const escape = IS_WINDOWS_PLATFORM ? escapeWindowsPath : escapePosixPath;

export function escapeWindowsPath(pattern: Pattern): Pattern {
return pattern.replace(WINDOWS_UNESCAPED_GLOB_SYMBOLS_RE, '\\$2');
}

export function escapePosixPath(pattern: Pattern): Pattern {
return pattern.replace(POSIX_UNESCAPED_GLOB_SYMBOLS_RE, '\\$2');
}

0 comments on commit 1dfd43c

Please sign in to comment.