Files
FCCS/fccs_config.py

140 lines
4.5 KiB
Python

"""
Shared configuration, logging, and utilities for the FCCS extraction tool.
"""
import os
import re
import sys
import argparse
import configparser
from datetime import datetime
DEFAULT_CONFIG = "config.ini"
def parse_args():
"""Parse the optional --config argument common to all scripts."""
parser = argparse.ArgumentParser()
parser.add_argument(
"--config",
default=None,
help=f"Path to config file (default: {DEFAULT_CONFIG} in script directory)",
)
return parser.parse_args()
def load_config(path=None):
"""Read config.ini and return a ConfigParser object."""
if path is None:
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
path = os.path.join(script_dir, DEFAULT_CONFIG)
if not os.path.exists(path):
print(f"ERROR: config file not found: {path}")
sys.exit(1)
cfg = configparser.ConfigParser()
cfg.read(path, encoding="utf-8")
return cfg
def make_logger(log_file):
"""Return a log() function that writes timestamped lines to console and file."""
def log(msg):
line = f"[{datetime.now():%Y-%m-%d %H:%M:%S}] {msg}"
print(line)
try:
with open(log_file, "a", encoding="utf-8") as f:
f.write(line + "\n")
except Exception:
pass
return log
def load_folder_list(path):
"""Load known FCCS folder names from file, one per line."""
if not os.path.exists(path):
print(f"ERROR: folder list file not found: {path}")
sys.exit(1)
folders = []
with open(path, "r", encoding="utf-8") as f:
for line in f:
s = line.strip()
if s and not s.startswith("#"):
folders.append(s)
return folders
def build_folder_patterns(templates):
"""
Convert folder template strings into regex patterns for matching.
Templates use YYYY as a year placeholder (e.g. 'YYYY Tax Documents').
Non-recurring folders (no YYYY) are matched literally.
A built-in pattern for 'UltraTax CS MM-DD-YYYY' is always included.
Returns a list of (compiled_regex, template_name, folder_type) tuples,
sorted longest-first to prevent partial matches.
folder_type is one of:
"yyyy" — recurring folder with year prefix (captured in group 1)
"ultratax" — UltraTax CS folder with date suffix (captured in group 1)
"static" — non-recurring folder, no decomposition needed
"""
patterns = []
# Built-in: UltraTax CS folders (auto-generated by UltraTax integration)
patterns.append((
re.compile(r"UltraTax CS (\d{2}-\d{2}-\d{4})$"),
"UltraTax CS",
"ultratax",
))
for tmpl in templates:
if "YYYY" in tmpl:
# Replace YYYY with captured 4-digit year pattern, escape the rest
parts = tmpl.split("YYYY")
regex_str = re.escape(parts[0]) + r"(\d{4})" + re.escape(parts[1])
patterns.append((re.compile(regex_str + "$"), tmpl, "yyyy"))
else:
# Non-recurring folder — exact match
regex_str = re.escape(tmpl)
patterns.append((re.compile(regex_str + "$"), tmpl, "static"))
# Sort by regex pattern length (longest first) to avoid partial matches
patterns.sort(key=lambda p: len(p[0].pattern), reverse=True)
return patterns
def decompose_folder_path(match, folder_type, template_name):
"""
Decompose a matched folder name into nested path components that
recreate the FCCS UI folder structure.
Returns a tuple of path parts to be joined with os.path.join().
Examples:
yyyy: "YYYY Tax Documents" matched "2025 Tax Documents"
→ ("Tax Documents", "2025")
ultratax: "UltraTax CS 12-31-2008"
→ ("UltraTax CS", "12-31-2008")
static: "Permanent File"
→ ("Permanent File",)
"""
if folder_type == "yyyy":
year = match.group(1)
# Strip "YYYY " from template to get the base folder name
base_name = template_name.replace("YYYY", "").strip()
return (base_name, year)
elif folder_type == "ultratax":
date = match.group(1)
return ("UltraTax CS", date)
else:
return (template_name,)
def load_lines(path):
"""Read non-blank, non-comment lines from a file."""
if not os.path.exists(path):
return []
with open(path, "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]