had to error handle the reog script to add safe handling of weird folder paths with odd characaters

This commit is contained in:
2026-07-21 07:24:50 -05:00
parent 9116d1c04a
commit 07472711e7
3 changed files with 73 additions and 38 deletions

View File

@@ -97,6 +97,19 @@ def parse_filename(filename, folder_patterns):
return None
def safe_component(name):
"""Make a single path component safe to create on Windows.
Windows cannot create a directory whose name ends in a space or a period —
os.makedirs raises FileNotFoundError (WinError 3). Client names such as
'FARR GROUP, P.L.' (trailing period) or ones with a stray trailing space
trigger this, so strip trailing dots/spaces. Falls back to '_' if the name
is entirely dots/spaces.
"""
cleaned = name.rstrip(" .")
return cleaned if cleaned else "_"
def main():
args = parse_args()
cfg = load_config(args.config)
@@ -125,55 +138,75 @@ def main():
success = 0
failed = 0
errored = 0
for filename in files:
result = parse_filename(filename, folder_patterns)
# Defensive per-file: a single bad path (e.g. an un-creatable name) must
# not abort the whole run — log it and move on to the next file.
try:
result = parse_filename(filename, folder_patterns)
if result is None:
# Still recover the client from the leading tokens so the file lands
# in that client's folder (in an _unparsed subfolder to flag it),
# rather than a single top-level bucket. Fall back to the top-level
# _unparsed only when even the client can't be determined.
client_name = client_name_from_filename(filename)
if client_name:
dest_dir = os.path.join(output_dir, client_name, "_unparsed")
log(f" UNPARSED (filed under {client_name}): {filename}")
if result is None:
# Still recover the client from the leading tokens so the file
# lands in that client's folder (in an _unparsed subfolder to
# flag it), rather than a single top-level bucket. Fall back to
# the top-level _unparsed only when even the client is unknown.
client_name = client_name_from_filename(filename)
if client_name:
dest_dir = os.path.join(output_dir,
safe_component(client_name),
"_unparsed")
log(f" UNPARSED (filed under {client_name}): {filename}")
else:
dest_dir = os.path.join(output_dir, "_unparsed")
log(f" UNPARSED (no client): {filename}")
dest_filename = filename # keep original name for review
parsed = False
else:
dest_dir = os.path.join(output_dir, "_unparsed")
log(f" UNPARSED (no client): {filename}")
failed += 1
else:
drawer_id, client_name, folder_parts, date, doc_name = result
dest_dir = os.path.join(
output_dir,
client_name,
*folder_parts,
)
drawer_id, client_name, folder_parts, date, doc_name = result
dest_dir = os.path.join(
output_dir,
safe_component(client_name),
*(safe_component(p) for p in folder_parts),
)
dest_filename = doc_name
parsed = True
os.makedirs(dest_dir, exist_ok=True)
src = os.path.join(export_dir, filename)
dst = os.path.join(dest_dir, dest_filename)
# Handle duplicate filenames
if os.path.exists(dst):
base, fext = os.path.splitext(dest_filename)
counter = 1
while os.path.exists(dst):
dst = os.path.join(dest_dir, f"{base}_{counter}{fext}")
counter += 1
log(f" DUPLICATE renamed: {dest_filename} -> {os.path.basename(dst)}")
shutil.copy2(src, dst)
except Exception as e:
log(f" ERROR processing {filename}: {e}")
errored += 1
continue
# Count only after a successful copy so the totals stay accurate.
if parsed:
success += 1
os.makedirs(dest_dir, exist_ok=True)
src = os.path.join(export_dir, filename)
# For parsed files, use just the document name; for unparsed, keep original
dest_filename = doc_name if result else filename
dst = os.path.join(dest_dir, dest_filename)
# Handle duplicate filenames
if os.path.exists(dst):
base, fext = os.path.splitext(dest_filename)
counter = 1
while os.path.exists(dst):
dst = os.path.join(dest_dir, f"{base}_{counter}{fext}")
counter += 1
log(f" DUPLICATE renamed: {dest_filename} -> {os.path.basename(dst)}")
shutil.copy2(src, dst)
else:
failed += 1
log("=" * 60)
log(f"Reorganization complete: {success} organized, {failed} unparsed")
log(f"Reorganization complete: {success} organized, {failed} unparsed, "
f"{errored} errored")
if failed:
log("Review unparsed files in each client's _unparsed subfolder "
f"(and {os.path.join(output_dir, '_unparsed')} for any without a "
"detectable client).")
if errored:
log(f"{errored} file(s) could not be placed due to errors — see the "
"ERROR lines above; these were left in the export folder.")
log("=" * 60)