Skip to content

Commit ec1725b

Browse files
committed
feat: file path utility functions
1 parent bf0a468 commit ec1725b

File tree

6 files changed

+141
-3
lines changed

6 files changed

+141
-3
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ type FileStat = {
169169
- Write content to a file.
170170
- Default encoding of `data` is assumed utf8.
171171

172+
#### Utility functions.
173+
174+
`Util.basename(path: string, separator?: string): string`
175+
176+
- Get the file/folder name from the end of the path.
177+
- Default path `separator` is `/`.
178+
179+
`Util.dirname(path: string, separator?: string): string`
180+
181+
- Get the path containing the file/folder.
182+
- Default path `separator` is `/`.
183+
184+
`Util.extname(path: string, separator?: string): string`
185+
186+
- Get the file extension.
187+
- Default path `separator` is `/`.
188+
172189
## Testing
173190

174191
For ease of testing, this library contains a mock implementation:

jest/react-native-file-access.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import type {
77
FileStat,
88
FsStat,
99
HashAlgorithm,
10+
Util as UtilFunctions,
1011
} from 'react-native-file-access';
1112

13+
export const Util: typeof UtilFunctions =
14+
require('react-native-file-access/lib/commonjs/util').Util;
15+
1216
export const Dirs = {
1317
CacheDir: '/mock/CacheDir',
1418
DatabaseDir: '/mock/DatabaseDir',

src/__tests__/util.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Util } from '../util';
2+
3+
it('extracts basename', () => {
4+
expect(Util.basename('')).toBe('');
5+
expect(Util.basename('/')).toBe('');
6+
expect(Util.basename('/abc/def')).toBe('def');
7+
expect(Util.basename('abc/')).toBe('abc');
8+
expect(Util.basename('abc//def/')).toBe('def');
9+
expect(Util.basename('/abc.def/hij.klm')).toBe('hij.klm');
10+
11+
expect(Util.basename('\\abc\\def', '\\')).toBe('def');
12+
13+
expect(Util.basename('/abc.def/hij.klm', '$')).toBe('/abc.def/hij.klm');
14+
expect(Util.basename('$abc.def$hij.klm', '$')).toBe('hij.klm');
15+
expect(Util.basename('abc$$def$', '$')).toBe('def');
16+
17+
expect(Util.basename('abc::def:', '::')).toBe('def:');
18+
});
19+
20+
it('extracts dirname', () => {
21+
expect(Util.dirname('')).toBe('.');
22+
expect(Util.dirname('/')).toBe('/');
23+
expect(Util.dirname('/abc/def')).toBe('/abc');
24+
expect(Util.dirname('abc/')).toBe('.');
25+
expect(Util.dirname('abc//def/')).toBe('abc');
26+
expect(Util.dirname('/abc.def/hij.klm')).toBe('/abc.def');
27+
expect(Util.dirname('/ab////cd/ef/gh.ij')).toBe('/ab/cd/ef');
28+
29+
expect(Util.dirname('\\abc\\def', '\\')).toBe('\\abc');
30+
31+
expect(Util.dirname('/abc.def/hij.klm', '$')).toBe('.');
32+
expect(Util.dirname('$abc.def$hij.klm', '$')).toBe('$abc.def');
33+
expect(Util.dirname('abc$$def$', '$')).toBe('abc');
34+
35+
expect(Util.dirname('abc::def:', '::')).toBe('abc');
36+
});
37+
38+
it('extracts extname', () => {
39+
expect(Util.extname('')).toBe('');
40+
expect(Util.extname('.hidden')).toBe('');
41+
expect(Util.extname('abc/def/.hidden')).toBe('');
42+
expect(Util.extname('abc.def')).toBe('def');
43+
expect(Util.extname('abc.def/')).toBe('');
44+
expect(Util.extname('a.b')).toBe('b');
45+
expect(Util.extname('/a.b')).toBe('b');
46+
expect(Util.extname('/abc/d.ef/g.hi.jkl')).toBe('jkl');
47+
expect(Util.extname('/abc/def.ghi/jkl')).toBe('');
48+
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export type {
2424
HashAlgorithm,
2525
} from './types';
2626

27+
export { Util } from './util';
28+
2729
/**
2830
* ID tracking next fetch request.
2931
*/

src/util.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Escape for use as literal string in a regex.
3+
*/
4+
function regexEscape(literal: string) {
5+
return literal.replace(/[\^$\\.*+?()[\]{}|]/g, '\\$&');
6+
}
7+
8+
/**
9+
* Condense consecutive separators.
10+
*/
11+
function normalizeSeparator(path: string, separator: string) {
12+
const sepRe = new RegExp(`(${regexEscape(separator)}){2,}`, 'g');
13+
return path.replace(sepRe, separator.replaceAll('$', '$$$$'));
14+
}
15+
16+
/**
17+
* Split path on last separator.
18+
*/
19+
function splitPath(path: string, separator: string) {
20+
let norm = normalizeSeparator(path, separator);
21+
if (norm === separator) {
22+
return { dir: separator, base: '' };
23+
}
24+
if (norm.endsWith(separator)) {
25+
norm = norm.substring(0, norm.length - separator.length);
26+
}
27+
const idx = norm.lastIndexOf(separator);
28+
if (idx === -1) {
29+
return { dir: '.', base: norm };
30+
}
31+
return {
32+
dir: norm.substring(0, idx),
33+
base: norm.substring(idx + separator.length),
34+
};
35+
}
36+
37+
export const Util = {
38+
/**
39+
* Get the file/folder name from the end of the path.
40+
*/
41+
basename(path: string, separator = '/') {
42+
return splitPath(path, separator).base;
43+
},
44+
45+
/**
46+
* Get the path containing the file/folder.
47+
*/
48+
dirname(path: string, separator = '/') {
49+
return splitPath(path, separator).dir;
50+
},
51+
52+
/**
53+
* Get the file extension.
54+
*/
55+
extname(path: string, separator = '/') {
56+
const extIdx = path.lastIndexOf('.');
57+
if (extIdx <= 0) {
58+
return '';
59+
}
60+
61+
const sepIdx = path.lastIndexOf(separator);
62+
if (sepIdx === -1 || extIdx > sepIdx + separator.length) {
63+
return path.substring(extIdx + 1);
64+
}
65+
return '';
66+
},
67+
};

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2871,9 +2871,9 @@ camelcase@^6.0.0, camelcase@^6.2.0:
28712871
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
28722872

28732873
caniuse-lite@^1.0.30001219:
2874-
version "1.0.30001228"
2875-
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
2876-
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
2874+
version "1.0.30001317"
2875+
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz"
2876+
integrity sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==
28772877

28782878
capture-exit@^2.0.0:
28792879
version "2.0.0"

0 commit comments

Comments
 (0)