Skip to content

Commit 7d80d66

Browse files
committed
relink.py: Check that dirs exist.
1 parent ddde636 commit 7d80d66

2 files changed

Lines changed: 121 additions & 299 deletions

File tree

relink.py

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import argparse
1111
import logging
1212

13-
DEFAULT_SOURCE_ROOT = '/glade/campaign/cesm/cesmdata/cseg/inputdata/'
14-
DEFAULT_TARGET_ROOT = '/glade/campaign/collections/gdex/data/d651077/cesmdata/inputdata/'
13+
DEFAULT_SOURCE_ROOT = "/glade/campaign/cesm/cesmdata/cseg/inputdata/"
14+
DEFAULT_TARGET_ROOT = (
15+
"/glade/campaign/collections/gdex/data/d651077/cesmdata/inputdata/"
16+
)
1517

1618
# Set up logger
1719
logger = logging.getLogger(__name__)
1820

21+
1922
def find_and_replace_owned_files(source_dir, target_dir, username):
2023
"""
2124
Finds files owned by a specific user in a source directory tree,
@@ -41,7 +44,7 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
4144
"Searching for files owned by '%s' (UID: %s) in '%s'...",
4245
username,
4346
user_uid,
44-
source_dir
47+
source_dir,
4548
)
4649

4750
for dirpath, _, filenames in os.walk(source_dir):
@@ -56,7 +59,7 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
5659

5760
file_uid = os.stat(file_path).st_uid
5861
except FileNotFoundError:
59-
continue # Skip if file was deleted during traversal
62+
continue # Skip if file was deleted during traversal
6063

6164
if file_uid == user_uid:
6265
logger.info("Found owned file: %s", file_path)
@@ -71,7 +74,7 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
7174
"Warning: Corresponding file not found in '%s' "
7275
"for '%s'. Skipping.",
7376
target_dir,
74-
file_path
77+
file_path,
7578
)
7679
continue
7780

@@ -80,7 +83,7 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
8083

8184
# Remove the original file
8285
try:
83-
os.rename(link_name, link_name+".tmp")
86+
os.rename(link_name, link_name + ".tmp")
8487
logger.info("Deleted original file: %s", link_name)
8588
except OSError as e:
8689
logger.error("Error deleting file %s: %s. Skipping.", link_name, e)
@@ -91,11 +94,36 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
9194
# Create parent directories for the link if they don't exist
9295
os.makedirs(os.path.dirname(link_name), exist_ok=True)
9396
os.symlink(link_target, link_name)
94-
os.remove(link_name+".tmp")
95-
logger.info("Created symbolic link: %s -> %s", link_name, link_target)
97+
os.remove(link_name + ".tmp")
98+
logger.info(
99+
"Created symbolic link: %s -> %s", link_name, link_target
100+
)
96101
except OSError as e:
97-
os.rename(link_name+".tmp", link_name)
98-
logger.error("Error creating symlink for %s: %s. Skipping.", link_name, e)
102+
os.rename(link_name + ".tmp", link_name)
103+
logger.error(
104+
"Error creating symlink for %s: %s. Skipping.", link_name, e
105+
)
106+
107+
108+
def validate_directory(path):
109+
"""
110+
Validate that the path exists and is a directory.
111+
112+
Args:
113+
path (str): The path to validate.
114+
115+
Returns:
116+
str: The absolute path if valid.
117+
118+
Raises:
119+
argparse.ArgumentTypeError: If path doesn't exist or is not a directory.
120+
"""
121+
if not os.path.exists(path):
122+
raise argparse.ArgumentTypeError(f"Directory '{path}' does not exist")
123+
if not os.path.isdir(path):
124+
raise argparse.ArgumentTypeError(f"'{path}' is not a directory")
125+
return os.path.abspath(path)
126+
99127

100128
def parse_arguments():
101129
"""
@@ -107,41 +135,43 @@ def parse_arguments():
107135
"""
108136
parser = argparse.ArgumentParser(
109137
description=(
110-
'Find files owned by a user and replace them with symbolic links to a target directory.'
138+
"Find files owned by a user and replace them with symbolic links to a target directory."
111139
)
112140
)
113141
parser.add_argument(
114-
'--source-root',
142+
"--source-root",
143+
type=validate_directory,
115144
default=DEFAULT_SOURCE_ROOT,
116145
help=(
117-
f'The root of the directory tree to search for files (default: {DEFAULT_SOURCE_ROOT})'
118-
)
146+
f"The root of the directory tree to search for files (default: {DEFAULT_SOURCE_ROOT})"
147+
),
119148
)
120149
parser.add_argument(
121-
'--target-root',
150+
"--target-root",
151+
type=validate_directory,
122152
default=DEFAULT_TARGET_ROOT,
123153
help=(
124-
f'The root of the directory tree where files should be moved to '
125-
f'(default: {DEFAULT_TARGET_ROOT})'
126-
)
154+
f"The root of the directory tree where files should be moved to "
155+
f"(default: {DEFAULT_TARGET_ROOT})"
156+
),
127157
)
128158

129159
# Verbosity options (mutually exclusive)
130160
verbosity_group = parser.add_mutually_exclusive_group()
131161
verbosity_group.add_argument(
132-
'-v', '--verbose',
133-
action='store_true',
134-
help='Enable verbose output'
162+
"-v", "--verbose", action="store_true", help="Enable verbose output"
135163
)
136164
verbosity_group.add_argument(
137-
'-q', '--quiet',
138-
action='store_true',
139-
help='Quiet mode (show only warnings and errors)'
165+
"-q",
166+
"--quiet",
167+
action="store_true",
168+
help="Quiet mode (show only warnings and errors)",
140169
)
141170

142171
return parser.parse_args()
143172

144-
if __name__ == '__main__':
173+
174+
if __name__ == "__main__":
145175

146176
args = parse_arguments()
147177

@@ -153,13 +183,9 @@ def parse_arguments():
153183
else:
154184
LOG_LEVEL = logging.INFO
155185

156-
logging.basicConfig(
157-
level=LOG_LEVEL,
158-
format='%(message)s',
159-
stream=sys.stdout
160-
)
161-
162-
my_username = os.environ['USER']
186+
logging.basicConfig(level=LOG_LEVEL, format="%(message)s", stream=sys.stdout)
187+
188+
my_username = os.environ["USER"]
163189

164190
# --- Execution ---
165191
find_and_replace_owned_files(args.source_root, args.target_root, my_username)

0 commit comments

Comments
 (0)