Skip to content

Commit eed796f

Browse files
committed
realpath: Add realpath.py
1 parent 220539e commit eed796f

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

src/realpath.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/python3
2+
3+
import os
4+
import sys
5+
from optparse import OptionParser
6+
7+
# Note: os.path is used instead of pathlib because certain functionality such as
8+
# os.path.normpath() lack a pathlib equivalent.
9+
10+
11+
def realpath(opts, filenames: list[str]):
12+
endchar = "\0" if opts.zero else "\n"
13+
14+
if opts.relative_to:
15+
filenames = (os.path.join(opts.relative_to, name) for name in filenames)
16+
17+
failed = False
18+
19+
for name in filenames:
20+
try:
21+
if not opts.symlink_mode:
22+
if opts.can_mode == "e":
23+
# raise an error if missing
24+
os.path.realpath(name, strict=True)
25+
26+
name = os.path.abspath(name)
27+
else:
28+
if opts.symlink_mode == "L":
29+
# resolve instances of ".." first
30+
name = os.path.normpath(name)
31+
32+
name = os.path.realpath(name, strict=opts.can_mode == "e")
33+
34+
if not opts.can_mode:
35+
# raise an error if directory missing
36+
os.path.realpath(os.path.dirname(name), strict=True)
37+
except OSError as e:
38+
failed = True
39+
40+
if not opts.quiet:
41+
print(e, file=sys.stderr)
42+
else:
43+
if opts.relative_to and not opts.relative_base:
44+
name = os.path.relpath(name, opts.relative_to)
45+
elif opts.relative_base and not opts.relative_to:
46+
name = os.path.relpath(name, opts.relative_base)
47+
48+
print(name, end=endchar)
49+
50+
if failed:
51+
sys.exit(1)
52+
53+
54+
if __name__ == "__main__":
55+
parser = OptionParser(
56+
usage="Usage: %prog [OPTION]... FILE...",
57+
description="Print the resolved path of each FILE.",
58+
add_help_option=False,
59+
)
60+
parser.add_option("--help", action="help", help="show usage information and exit")
61+
62+
parser.add_option(
63+
"-q", "--quiet", action="store_true", help="suppress error messages"
64+
)
65+
66+
parser.add_option(
67+
"-e",
68+
"--canonicalize-existing",
69+
dest="can_mode",
70+
action="store_const",
71+
const="e",
72+
help="all path components must exist",
73+
)
74+
parser.add_option(
75+
"-m",
76+
"--canonicalize-missing",
77+
dest="can_mode",
78+
action="store_const",
79+
const="m",
80+
help="no path components need exist or be a directory",
81+
)
82+
83+
parser.add_option(
84+
"-L",
85+
"--logical",
86+
dest="symlink_mode",
87+
action="store_const",
88+
const="L",
89+
help="resolve '..' components before symlinks",
90+
)
91+
parser.add_option(
92+
"-P",
93+
"--physical",
94+
dest="symlink_mode",
95+
action="store_const",
96+
const="P",
97+
default="P",
98+
help="resolve symlinks as encountered (default)",
99+
)
100+
parser.add_option(
101+
"-s",
102+
"--strip",
103+
"--no-symlinks",
104+
dest="symlink_mode",
105+
action="store_const",
106+
const="",
107+
help="do not resolve symlinks",
108+
)
109+
110+
parser.add_option(
111+
"--relative-to", metavar="DIR", help="resolve the path relative to DIR"
112+
)
113+
parser.add_option(
114+
"--relative-base",
115+
default="",
116+
metavar="DIR",
117+
help="print absolute paths except those below DIR",
118+
)
119+
120+
parser.add_option(
121+
"-z",
122+
"--zero",
123+
action="store_true",
124+
help="terminate outputs with NUL instead of newline",
125+
)
126+
127+
opts, args = parser.parse_args()
128+
129+
if not args:
130+
parser.error("missing operand")
131+
132+
realpath(opts, args)

0 commit comments

Comments
 (0)