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

Skip to content

Commit 077e948

Browse files
committed
Fix typing woes
The codebase is now typechecker-friendly. Also fixed some random bugs here and there that could have been detected with static typechecking in place.
1 parent f2f7c84 commit 077e948

35 files changed

+139
-111
lines changed

userland/__init__.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99

1010

1111
def print_usage(prog_name: str) -> None:
12-
try:
13-
version = importlib.metadata.version(__package__)
14-
except importlib.metadata.PackageNotFoundError:
15-
version = "(unknown version)"
12+
version = "(unknown version)"
13+
14+
if __package__:
15+
try:
16+
version = importlib.metadata.version(__package__)
17+
except importlib.metadata.PackageNotFoundError:
18+
pass
1619

1720
print(
1821
f"python-userland v{version}",
1922
f"Usage: {prog_name} APPLET [ARGUMENT]...",
2023
f"{7 * " "}{prog_name} --list",
2124
"\nAvailable applets:",
2225
", ".join(sorted(applets)),
23-
sep="\n"
26+
sep="\n",
2427
)
2528

2629

userland/core/command.py

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import sys
2-
from optparse import OptionParser
2+
from optparse import OptionParser, Values
33
from typing import Any, Callable
44

55
from .users import OptionParserUsersMixin
66

77

88
class ExtendedOptionParser(OptionParserUsersMixin, OptionParser):
9-
def __init__(self, usage: tuple[str], **kwargs):
9+
def __init__(self, usage: str | tuple[str, ...], **kwargs):
1010
super().__init__(
11-
usage="Usage: " + f"\n{7 * " "}".join(usage),
11+
usage="Usage: "
12+
+ f"\n{7 * " "}".join(usage if isinstance(usage, tuple) else (usage,)),
1213
add_help_option=False,
1314
**kwargs,
1415
)
@@ -40,17 +41,12 @@ def expect_nargs(self, args: list[str], nargs: int | tuple[int] | tuple[int, int
4041

4142
def command(parser: OptionParser | None = None):
4243
def create_utility(
43-
func: Callable[[dict[str, Any], list[Any]], int],
44+
func: Callable[[Values, list[Any]], int],
4445
) -> Callable[[], None]:
45-
if parser:
46-
47-
def execute_utility():
48-
sys.exit(func(*parser.parse_args()))
49-
50-
else:
51-
52-
def execute_utility():
53-
sys.exit(func({}, sys.argv[1:]))
46+
def execute_utility():
47+
sys.exit(
48+
func(*parser.parse_args()) if parser else func(Values(), sys.argv[1:])
49+
)
5450

5551
return execute_utility
5652

userland/core/io.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import contextlib
22
import os
33
import sys
4-
from typing import Any, Generator
4+
from typing import Any, Generator, IO
55

66

77
def perror(*errors: Any) -> None:
@@ -12,7 +12,7 @@ def perror(*errors: Any) -> None:
1212

1313

1414
@contextlib.contextmanager
15-
def safe_open(*args, **kwargs):
15+
def safe_open(*args, **kwargs) -> Generator[IO | None]:
1616
try:
1717
with open(*args, **kwargs) as io:
1818
yield io

userland/core/paths.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import sys
22
from pathlib import Path
3-
from typing import Generator, Literal
3+
from typing import Generator, Iterable, Literal
44

55

66
def traverse_files(
7-
filenames: list[str],
7+
filenames: Iterable[str],
88
recurse_mode: Literal["L", "H", "P"] | None = None,
99
preserve_root: bool = False,
1010
) -> Generator[Path | None]:

userland/utilities/basename.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727

2828
@core.command(parser)
29-
def python_userland_basename(opts, args):
29+
def python_userland_basename(opts, args: list[str]):
3030
parser.expect_nargs(args, (1,))
3131

3232
if opts.suffix:

userland/utilities/cat.py

+19-19
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import itertools
22
import sys
33
from io import BufferedReader
4-
from typing import BinaryIO, Generator
4+
from typing import Generator, Iterable
55

66
from .. import core
77

88

9-
def squeeze_blank_lines(io: BinaryIO) -> Generator[bytes]:
9+
def squeeze_blank_lines(stream: Iterable[bytes]) -> Generator[bytes]:
1010
was_blank = False
1111

12-
for line in io:
12+
for line in stream:
1313
is_blank = len(line) < 2
1414

1515
if was_blank and is_blank:
@@ -19,14 +19,14 @@ def squeeze_blank_lines(io: BinaryIO) -> Generator[bytes]:
1919
was_blank = is_blank
2020

2121

22-
def number_lines(io: BinaryIO) -> Generator[bytes]:
23-
for i, _ in enumerate(io):
22+
def number_lines(stream: Iterable[bytes]) -> Generator[bytes]:
23+
for i, _ in enumerate(stream):
2424
yield f"{i + 1:>6} ".encode()
2525

2626

27-
def number_nonblank_lines(io: BinaryIO) -> Generator[bytes]:
27+
def number_nonblank_lines(stream: Iterable[bytes]) -> Generator[bytes]:
2828
i = 1
29-
for line in io:
29+
for line in stream:
3030
if len(line) > 1:
3131
yield f"{i:>6} ".encode()
3232
i += 1
@@ -62,16 +62,16 @@ def format_chars(
6262
yield n.to_bytes()
6363

6464

65-
def format_lines(io: BinaryIO, *args) -> Generator[bytes]:
66-
for line in io:
65+
def format_lines(stream: Iterable[bytes], *args) -> Generator[bytes]:
66+
for line in stream:
6767
yield b"".join(format_chars(line, *args))
6868

6969

70-
def cat_io(opts, io: BinaryIO) -> None:
70+
def cat_io(opts, stream: Iterable[bytes]) -> None:
7171
if opts.squeeze_blank:
72-
io = squeeze_blank_lines(io)
72+
stream = squeeze_blank_lines(stream)
7373

74-
io1, io2 = itertools.tee(io, 2)
74+
io1, io2 = itertools.tee(stream, 2)
7575
gen1, gen2 = None, None
7676

7777
if opts.number_nonblank:
@@ -95,7 +95,7 @@ def cat_io(opts, io: BinaryIO) -> None:
9595

9696

9797
parser = core.ExtendedOptionParser(
98-
usage=("%prog [OPTION]... [FILE]...",),
98+
usage="%prog [OPTION]... [FILE]...",
9999
description="Concatenate each FILE to standard output.",
100100
)
101101

@@ -134,7 +134,7 @@ def cat_io(opts, io: BinaryIO) -> None:
134134

135135

136136
@core.command(parser)
137-
def python_userland_cat(opts, args):
137+
def python_userland_cat(opts, args: list[str]):
138138
if opts.show_all:
139139
opts.show_ends = True
140140
opts.show_tabs = True
@@ -146,26 +146,26 @@ def python_userland_cat(opts, args):
146146
opts.show_tabs = True
147147
opts.show_nonprinting = True
148148

149-
generators: list[Generator[bytes]] = []
149+
streams: list[Iterable[bytes]] = []
150150
failed = False
151151

152152
for name in args or ["-"]:
153153
if name == "-":
154-
generators.append(core.readlines_stdin_raw())
154+
streams.append(core.readlines_stdin_raw())
155155
else:
156156
try:
157-
generators.append(open(name, "rb"))
157+
streams.append(open(name, "rb"))
158158
except OSError as e:
159159
failed = True
160160
core.perror(e)
161161

162162
try:
163-
cat_io(opts, itertools.chain(*generators))
163+
cat_io(opts, itertools.chain(*streams))
164164
except KeyboardInterrupt:
165165
print()
166166
return 130
167167
finally:
168-
for gen in generators:
168+
for gen in streams:
169169
# Close opened files other than stdin.
170170
if isinstance(gen, BufferedReader):
171171
gen.close()

userland/utilities/chgrp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125

126126

127127
@core.command(parser)
128-
def python_userland_chgrp(opts, args):
128+
def python_userland_chgrp(opts, args: list[str]):
129129
parser.expect_nargs(args, (1,))
130130

131131
from_uid: int | None = None

userland/utilities/chown.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126

127127

128128
@core.command(parser)
129-
def python_userland_chown(opts, args):
129+
def python_userland_chown(opts, args: list[str]):
130130
parser.expect_nargs(args, (1,))
131131

132132
from_uid: int | None = None
@@ -137,6 +137,8 @@ def python_userland_chown(opts, args):
137137

138138
chown_args = {"follow_symlinks": opts.dereference}
139139

140+
owner_spec: str
141+
140142
if opts.reference:
141143
try:
142144
ref_stat = Path(opts.reference).stat(follow_symlinks=True)
@@ -146,6 +148,12 @@ def python_userland_chown(opts, args):
146148

147149
chown_args["user"] = ref_stat.st_uid
148150
chown_args["group"] = ref_stat.st_gid
151+
152+
owner_spec = (
153+
core.user_display_name_from_id(ref_stat.st_uid)
154+
+ ":"
155+
+ core.group_display_name_from_id(ref_stat.st_gid)
156+
)
149157
else:
150158
parser.expect_nargs(args, (2,))
151159
owner_spec = args.pop(0)

userland/utilities/clear.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# clear(1), roughly modelled off the ncurses implementation.
55

66
parser = core.ExtendedOptionParser(
7-
usage=("%prog [OPTION]...",),
7+
usage="%prog [OPTION]...",
88
description="Clear the terminal screen.",
99
)
1010

@@ -14,7 +14,7 @@
1414

1515

1616
@core.command(parser)
17-
def python_userland_clear(opts, args):
17+
def python_userland_clear(opts, args: list[str]):
1818
if args:
1919
return 1
2020

userland/utilities/dirname.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
parser = core.ExtendedOptionParser(
7-
usage=("%prog [OPTION]... NAME...",),
7+
usage="%prog [OPTION]... NAME...",
88
description=(
99
"Print each path NAME with the last component removed,"
1010
" or '.' if NAME is the only component."
@@ -20,7 +20,7 @@
2020

2121

2222
@core.command(parser)
23-
def python_userland_dirname(opts, args):
23+
def python_userland_dirname(opts, args: list[str]):
2424
parser.expect_nargs(args, (1,))
2525

2626
for path in map(PurePath, args):

userland/utilities/echo.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _process_args(self, largs, rargs, values):
3535

3636

3737
parser = PassthroughOptionParser(
38-
usage=("%prog [OPTION]... [STRING]...",),
38+
usage="%prog [OPTION]... [STRING]...",
3939
description="Print STRING(s) to standard output.",
4040
)
4141
parser.disable_interspersed_args()
@@ -57,7 +57,7 @@ def _process_args(self, largs, rargs, values):
5757

5858

5959
@core.command(parser)
60-
def python_userland_echo(opts, args):
60+
def python_userland_echo(opts, args: list[str]):
6161
string = " ".join(args)
6262

6363
if opts.escapes:

userland/utilities/env.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
parser = core.ExtendedOptionParser(
8-
usage=("%prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]",),
8+
usage="%prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]",
99
description="Run a program in a modified environment or print environment variables.",
1010
)
1111
parser.disable_interspersed_args()
@@ -50,7 +50,7 @@
5050

5151

5252
@core.command(parser)
53-
def python_userland_env(opts, args):
53+
def python_userland_env(opts, args: list[str]):
5454
if args and args[0] == "-":
5555
opts.ignore_environment = True
5656
del args[0]

userland/utilities/factor.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import math
2-
from typing import Generator, Iterable
2+
from typing import Generator, Iterable, cast
33

44
from .. import core
55

@@ -76,7 +76,7 @@ def factorize(
7676
if factor == n:
7777
break
7878

79-
n //= factor
79+
n //= cast(int, factor)
8080

8181

8282
def format_exponents(factors: Iterable[int]) -> str:
@@ -100,15 +100,15 @@ def format_exponents(factors: Iterable[int]) -> str:
100100

101101

102102
parser = core.ExtendedOptionParser(
103-
usage=("%prog [OPTION] [NUMBER]...",),
103+
usage="%prog [OPTION] [NUMBER]...",
104104
description="Compute and print the prime factors of each positive integer NUMBER.",
105105
)
106106

107107
parser.add_option("-h", "--exponents", action="store_true")
108108

109109

110110
@core.command(parser)
111-
def python_userland_factor(opts, args):
111+
def python_userland_factor(opts, args: list[str]):
112112
failed = False
113113

114114
try:

userland/utilities/false.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
@core.command()
8-
def python_userland_false(_, args):
8+
def python_userland_false(_, args: list[str]):
99
if args and args[0] == "--help":
1010
print(
1111
f"""\

userland/utilities/groups.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
parser = core.ExtendedOptionParser(
9-
usage=("%prog [USERNAME]...",),
9+
usage="%prog [USERNAME]...",
1010
description="Print a list of groups for each USERNAME or the current user.",
1111
)
1212

userland/utilities/hostid.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
parser = core.ExtendedOptionParser(
5-
usage=("%prog",),
5+
usage="%prog",
66
description="Print a 32-bit numeric host machine identifier.",
77
epilog="This implementation gives an all-zero identifier.",
88
)

0 commit comments

Comments
 (0)