Skip to content

Commit ac48d57

Browse files
committed
Add support for ~user syntax expansion (current user only)
Fixes #1
1 parent a901e25 commit ac48d57

4 files changed

Lines changed: 96 additions & 2 deletions

File tree

index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
/**
22
Convert a tilde path to an absolute path: `~/dev` → `/Users/sindresorhus/dev`.
33
4+
Also expands `~username` when the username matches the current user.
5+
46
@example
57
```
68
import untildify from 'untildify';
79
810
untildify('~/dev');
911
//=> '/Users/sindresorhus/dev'
12+
13+
// If current user is 'sindresorhus'
14+
untildify('~sindresorhus/dev');
15+
//=> '/Users/sindresorhus/dev'
1016
```
1117
*/
1218
export default function untildify(pathWithTilde: string): string;

index.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,38 @@
11
import os from 'node:os';
22

3-
const homeDirectory = os.homedir();
3+
let homeDirectory;
4+
let currentUser;
45

56
export default function untildify(pathWithTilde) {
67
if (typeof pathWithTilde !== 'string') {
78
throw new TypeError(`Expected a string, got ${typeof pathWithTilde}`);
89
}
910

10-
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
11+
if (homeDirectory === undefined) {
12+
homeDirectory = os.homedir();
13+
}
14+
15+
// Handle regular ~ expansion (current user)
16+
if (homeDirectory && /^~(?=$|\/|\\)/.test(pathWithTilde)) {
17+
return pathWithTilde.replace(/^~/, homeDirectory);
18+
}
19+
20+
// Handle ~username expansion (only for current user)
21+
const userMatch = pathWithTilde.match(/^~([^/\\]+)(.*)/);
22+
if (userMatch) {
23+
if (currentUser === undefined) {
24+
currentUser = os.userInfo().username;
25+
}
26+
27+
if (currentUser) {
28+
const username = userMatch[1];
29+
const rest = userMatch[2];
30+
if (username === currentUser) {
31+
return homeDirectory + rest;
32+
}
33+
}
34+
}
35+
36+
// Return unchanged if no expansion occurred
37+
return pathWithTilde;
1138
}

readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ import untildify from 'untildify';
1515

1616
untildify('~/dev');
1717
//=> '/Users/sindresorhus/dev'
18+
19+
// Assuming the current user is 'sindresorhus'
20+
untildify('~sindresorhus/dev');
21+
//=> '/Users/sindresorhus/dev'
1822
```
1923

24+
## Features
25+
26+
- Expands `~` to the current user's home directory
27+
- Expands `~username` to the home directory when username matches the current user
28+
- Returns path unchanged if username doesn't match the current user
29+
2030
## Related
2131

2232
See [tildify](https://github.com/sindresorhus/tildify) for the inverse.

test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os from 'node:os';
12
import test from 'ava';
23
import esmock from 'esmock';
34

@@ -49,3 +50,53 @@ test('paths with regex replacement patterns', t => {
4950
t.true(expandsTildePrefixWithHome('~/$1'));
5051
t.true(expandsTildePrefixWithHome('~/$&'));
5152
});
53+
54+
// Tests for ~user expansion
55+
test('~user expansion for current user', async t => {
56+
// Use the actual untildify function without mocking for this test
57+
const {default: realUntildify} = await import('./index.js');
58+
const currentUser = os.userInfo().username;
59+
const currentHome = os.homedir();
60+
61+
// ~currentuser should expand to current user's home
62+
t.is(realUntildify(`~${currentUser}`), currentHome);
63+
t.is(realUntildify(`~${currentUser}/`), `${currentHome}/`);
64+
t.is(realUntildify(`~${currentUser}/dev`), `${currentHome}/dev`);
65+
});
66+
67+
test('~user expansion for other users', async t => {
68+
// Use the actual untildify function without mocking for this test
69+
const {default: realUntildify} = await import('./index.js');
70+
71+
// Other users should return unchanged (simplified implementation)
72+
t.is(realUntildify('~root'), '~root');
73+
t.is(realUntildify('~root/'), '~root/');
74+
t.is(realUntildify('~otheruser/test'), '~otheruser/test');
75+
});
76+
77+
test('~user expansion for non-existent users', async t => {
78+
// Use the actual untildify function without mocking for this test
79+
const {default: realUntildify} = await import('./index.js');
80+
81+
// Non-existent users should return unchanged
82+
t.is(realUntildify('~nonexistentuser123'), '~nonexistentuser123');
83+
t.is(realUntildify('~nonexistentuser123/'), '~nonexistentuser123/');
84+
t.is(realUntildify('~nonexistentuser123/dev'), '~nonexistentuser123/dev');
85+
});
86+
87+
test('~user expansion edge cases', async t => {
88+
// Use the actual untildify function without mocking for this test
89+
const {default: realUntildify} = await import('./index.js');
90+
91+
// Should not expand if not at the beginning
92+
t.is(realUntildify('foo~bar'), 'foo~bar');
93+
t.is(realUntildify('/~user'), '/~user');
94+
t.is(realUntildify('abc~def/ghi'), 'abc~def/ghi');
95+
96+
// Empty username after ~ (just ~ alone should still work)
97+
t.is(realUntildify('~'), os.homedir());
98+
99+
// Username containing special characters that don't typically appear in usernames
100+
t.is(realUntildify('~user!!invalid'), '~user!!invalid');
101+
t.is(realUntildify('~user@host'), '~user@host');
102+
});

0 commit comments

Comments
 (0)