Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6383f56

Browse files
committed
chgrp: Move a bunch of stuff to the core modules
1 parent 31256bf commit 6383f56

File tree

4 files changed

+102
-70
lines changed

4 files changed

+102
-70
lines changed

userland/core/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .command import *
22
from .io import *
3+
from .paths import *
34
from .users import *

userland/core/paths.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sys
2+
from pathlib import Path
3+
from typing import Generator, Literal
4+
5+
6+
def traverse_files(
7+
filenames: list[str],
8+
recurse_mode: Literal["L", "H", "P"] | None = None,
9+
preserve_root: bool = False,
10+
) -> Generator[Path | None]:
11+
if not recurse_mode:
12+
yield from map(Path, filenames)
13+
return
14+
15+
def traverse(file: Path) -> Generator[Path]:
16+
for child in file.iterdir():
17+
if child.is_dir(follow_symlinks=recurse_mode == "L"):
18+
yield from traverse(child)
19+
yield child
20+
21+
for file in map(Path, filenames):
22+
if preserve_root and file.root == str(file):
23+
print(
24+
f"recursive operation on '{file}' prevented;"
25+
" use --no-preserve-root to override",
26+
file=sys.stderr,
27+
)
28+
yield None
29+
continue
30+
31+
if file.is_dir(follow_symlinks=recurse_mode in set("HL")):
32+
yield from traverse(file)
33+
else:
34+
yield file

userland/core/users.py

+50-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
import grp
23
import pwd
34

@@ -8,7 +9,7 @@ def parse_onwer_spec(
89
parser: OptionParser, owner_spec: str
910
) -> tuple[int | None, int | None]:
1011
"""
11-
Process a string in the form ``[USER][:GROUP]`` and return the UID and GID.
12+
Accept a string in the form ``[USER][:[GROUP]]`` and return the UID and GID.
1213
Either or both may be None if omitted from the input string.
1314
An appropriate parser error is thrown if obtaining the UID or GID fails.
1415
"""
@@ -18,21 +19,55 @@ def parse_onwer_spec(
1819
gid: int | None = None
1920

2021
if tokens[0]:
21-
if tokens[0].isdecimal():
22-
uid = int(tokens[0])
23-
else:
24-
try:
25-
uid = pwd.getpwnam(tokens[0])
26-
except KeyError:
27-
parser.error(f"invalid user: '{tokens}'")
22+
uid = parse_user(parser, tokens[0])
2823

2924
if len(tokens) > 1 and tokens[1]:
30-
if tokens[1].isdecimal():
31-
gid = int(tokens[1])
32-
else:
33-
try:
34-
gid = grp.getgrnam(tokens[1])
35-
except KeyError:
36-
parser.error(f"invalid group: '{tokens}'")
25+
gid = parse_group(parser, tokens[1])
3726

3827
return uid, gid
28+
29+
30+
@functools.lru_cache(1000)
31+
def parse_user(parser: OptionParser, user: str) -> int:
32+
"""
33+
Accept a string representing a username or UID and return the UID.
34+
An appropriate parser error is thrown if obtaining the UID fails.
35+
"""
36+
if user.isdecimal():
37+
return int(user)
38+
39+
try:
40+
return pwd.getpwnam(user).pw_uid
41+
except KeyError:
42+
parser.error(f"invalid user: {user}")
43+
44+
45+
@functools.lru_cache(1000)
46+
def parse_group(parser: OptionParser, group: str) -> int:
47+
"""
48+
Accept a string representing a group name or GID and return the GID.
49+
An appropriate parser error is thrown if obtaining the GID fails.
50+
"""
51+
if group.isdecimal():
52+
return int(group)
53+
54+
try:
55+
return grp.getgrnam(group).gr_gid
56+
except KeyError:
57+
parser.error(f"invalid group: {group}")
58+
59+
60+
@functools.lru_cache(1000)
61+
def user_display_name_from_id(uid: int) -> str:
62+
try:
63+
return pwd.getpwuid(uid).pw_name
64+
except KeyError:
65+
return str(uid)
66+
67+
68+
@functools.lru_cache(1000)
69+
def group_display_name_from_id(gid: int) -> str:
70+
try:
71+
return grp.getgrgid(gid).gr_name
72+
except KeyError:
73+
return str(gid)

userland/utilities/chgrp.py

+17-55
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import grp
2-
import pwd
31
import shutil
42
import sys
53
from pathlib import Path
@@ -152,18 +150,22 @@ def python_userland_chgrp(opts, args):
152150
if not args:
153151
parser.error(f"missing operand after '{gname}'")
154152

155-
if gname.isdecimal():
156-
gid = int(gname)
157-
else:
158-
try:
159-
gid = grp.getgrnam(gname).gr_gid
160-
except KeyError:
161-
parser.error(f"invalid group: '{gname}'")
153+
gid = core.parse_group(parser, gname)
162154

163155
failed = False
164156

165-
def chown(file: Path) -> None:
166-
nonlocal failed
157+
for file in core.traverse_files(
158+
(
159+
tqdm(args, ascii=True, desc="Changing group ownership")
160+
if opts.progress
161+
else args
162+
),
163+
recurse_mode=opts.recurse_mode if opts.recursive else None,
164+
preserve_root=opts.preserve_root,
165+
):
166+
if not file:
167+
failed = True
168+
continue
167169

168170
try:
169171
stat = file.stat(follow_symlinks=opts.dereference)
@@ -177,12 +179,9 @@ def chown(file: Path) -> None:
177179
f"failed to change group of '{file}' to {gname or gid}",
178180
file=sys.stderr,
179181
)
180-
return
182+
continue
181183

182-
try:
183-
prev_gname = grp.getgrgid(prev_gid).gr_name
184-
except KeyError:
185-
prev_gname = str(prev_gid)
184+
prev_gname = core.group_display_name_from_id(prev_gid)
186185

187186
# Note: while it's possible, we do not check if prev_gid == gid at
188187
# this point because even if they are the same, an error should be
@@ -193,7 +192,7 @@ def chown(file: Path) -> None:
193192
):
194193
if opts.verbosity > 2:
195194
print(f"group of '{file}' retained as {prev_gname}")
196-
return
195+
continue
197196

198197
try:
199198
shutil.chown(file, group=gid, follow_symlinks=opts.dereference)
@@ -206,49 +205,12 @@ def chown(file: Path) -> None:
206205
f"failed to change group of '{file}' to {gname or gid}",
207206
file=sys.stderr,
208207
)
209-
return
208+
continue
210209

211210
if prev_gid == gid:
212211
if opts.verbosity > 2:
213212
print(f"group of '{file}' retained as {prev_gname}")
214213
elif opts.verbosity > 1:
215214
print(f"changed group of '{file}' from {prev_gname} to {gname or gid}")
216215

217-
files = map(
218-
Path,
219-
(
220-
tqdm(args, ascii=True, desc="Changing group ownership")
221-
if opts.progress
222-
else args
223-
),
224-
)
225-
226-
if opts.recursive:
227-
228-
def traverse(file: Path) -> None:
229-
for child in file.iterdir():
230-
if child.is_dir(follow_symlinks=opts.recurse_mode == "L"):
231-
traverse(child)
232-
chown(file)
233-
234-
for file in files:
235-
if file.is_dir(
236-
follow_symlinks=opts.recurse_mode == "H" or opts.recurse_mode == "L"
237-
):
238-
if opts.preserve_root and file.root == str(file):
239-
failed = True
240-
print(
241-
f"recursive operation on '{file}' prevented;"
242-
" use --no-preserve-root to override",
243-
file=sys.stderr,
244-
)
245-
continue
246-
247-
traverse(file)
248-
else:
249-
chown(file)
250-
else:
251-
for file in files:
252-
chown(file)
253-
254216
return int(failed)

0 commit comments

Comments
 (0)