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

Skip to content

Commit 00526df

Browse files
committed
cat: Add cat.py
1 parent d8b544d commit 00526df

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

src/cat.py

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#!/usr/bin/python3
2+
3+
import itertools
4+
import sys
5+
from io import BufferedReader
6+
from optparse import OptionParser
7+
from typing import BinaryIO, Generator
8+
9+
10+
def squeeze_blank_lines(io: BinaryIO) -> Generator[bytes]:
11+
was_blank = False
12+
13+
for line in io:
14+
is_blank = len(line) < 2
15+
16+
if was_blank and is_blank:
17+
continue
18+
19+
yield line
20+
was_blank = is_blank
21+
22+
23+
def number_lines(io: BinaryIO) -> Generator[bytes]:
24+
for i, _ in enumerate(io):
25+
yield f"{i + 1:>6} ".encode()
26+
27+
28+
def number_nonblank_lines(io: BinaryIO) -> Generator[bytes]:
29+
i = 1
30+
for line in io:
31+
if len(line) > 1:
32+
yield f"{i:>6} ".encode()
33+
i += 1
34+
else:
35+
yield b""
36+
37+
38+
def format_chars(
39+
data: bytes, show_ends: bool, show_tabs: bool, show_nonprinting: bool
40+
) -> Generator[bytes]:
41+
for n in data:
42+
if n == ord(b"\n"):
43+
if show_ends:
44+
yield b"$\n"
45+
continue
46+
elif n == ord(b"\t"):
47+
if show_tabs:
48+
yield b"^I"
49+
continue
50+
elif show_nonprinting and (n < 32 or n > 126):
51+
if n < 32:
52+
yield b"^" + (n + 64).to_bytes()
53+
elif n == 127:
54+
yield b"^?"
55+
elif n < 128 + 32:
56+
yield b"M-^" + (n - 128 + 64).to_bytes()
57+
elif n < 128 + 127:
58+
yield b"M-" + (n - 128).to_bytes()
59+
else:
60+
yield b"M-?"
61+
continue
62+
63+
yield n.to_bytes()
64+
65+
66+
def format_lines(io: BinaryIO, *args) -> Generator[bytes]:
67+
for line in io:
68+
yield b"".join(format_chars(line, *args))
69+
70+
71+
def cat_io(opts, io: BinaryIO) -> None:
72+
if opts.squeeze_blank:
73+
io = squeeze_blank_lines(io)
74+
75+
io1, io2 = itertools.tee(io, 2)
76+
gen1, gen2 = None, None
77+
78+
if opts.number_nonblank:
79+
gen1 = number_nonblank_lines(io1)
80+
elif opts.number:
81+
gen1 = number_lines(io1)
82+
83+
if opts.show_ends or opts.show_tabs or opts.show_nonprinting:
84+
gen2 = format_lines(io2, opts.show_ends, opts.show_tabs, opts.show_nonprinting)
85+
else:
86+
gen2 = io2
87+
88+
if gen1 and gen2:
89+
for part1, part2 in zip(gen1, gen2, strict=True):
90+
sys.stdout.buffer.write(part1 + part2)
91+
sys.stdout.buffer.flush()
92+
else:
93+
for line in gen2:
94+
sys.stdout.buffer.write(line)
95+
sys.stdout.buffer.flush()
96+
97+
98+
def readlines_stdin() -> Generator[bytes]:
99+
while True:
100+
if line := sys.stdin.buffer.readline():
101+
yield line
102+
else:
103+
# EOF (Ctrl-D)
104+
break
105+
106+
107+
def cat(opts, filenames: list[str]):
108+
generators = [
109+
readlines_stdin() if name == "-" else open(name, "rb") for name in filenames
110+
]
111+
112+
try:
113+
cat_io(opts, itertools.chain(*generators))
114+
except KeyboardInterrupt:
115+
print()
116+
sys.exit(130)
117+
finally:
118+
for gen in generators:
119+
# Close opened files other than stdin.
120+
if isinstance(gen, BufferedReader):
121+
gen.close()
122+
123+
124+
if __name__ == "__main__":
125+
parser = OptionParser(
126+
usage="Usage: %prog [OPTION]... [FILE]...",
127+
description="Concatenate each FILE to standard output.",
128+
add_help_option=False,
129+
)
130+
parser.add_option("--help", action="help", help="show usage information and exit")
131+
132+
parser.add_option(
133+
"-E", "--show-ends", action="store_true", help="append '$' to each LF"
134+
)
135+
parser.add_option(
136+
"-T", "--show-tabs", action="store_true", help="convert TABs to '^I'"
137+
)
138+
parser.add_option(
139+
"-v",
140+
"--show-nonprinting",
141+
action="store_true",
142+
help="show nonprinting characters except LF and TAB with ^ and M- notation",
143+
)
144+
parser.add_option(
145+
"-A", "--show-all", action="store_true", help="equivalent to -vET"
146+
)
147+
parser.add_option("-e", action="store_true", help="equivalent to -vE")
148+
parser.add_option("-t", action="store_true", help="equivalent to -vT")
149+
150+
parser.add_option(
151+
"-n", "--number", action="store_true", help="number each output line"
152+
)
153+
parser.add_option(
154+
"-b",
155+
"--number-nonblank",
156+
action="store_true",
157+
help="number each nonempty output line (overrides -n)",
158+
)
159+
160+
parser.add_option(
161+
"-s",
162+
"--squeeze-blank",
163+
action="store_true",
164+
help="collapse repeated empty output lines",
165+
)
166+
167+
parser.add_option(
168+
"-u", action="store_true", help="(ignored; present for POSIX compatibility)"
169+
)
170+
171+
opts, args = parser.parse_args()
172+
173+
if opts.show_all:
174+
opts.show_ends = True
175+
opts.show_tabs = True
176+
opts.show_nonprinting = True
177+
elif opts.e:
178+
opts.show_ends = True
179+
opts.show_nonprinting = True
180+
elif opts.t:
181+
opts.show_tabs = True
182+
opts.show_nonprinting = True
183+
184+
cat(opts, args or ["-"])

0 commit comments

Comments
 (0)