Files
Synthese-PSM_LARRA/Scripts/pyCreateTh/Lib/general_fonctions.py
T
Alex38Lyon 74e5b3f800 pyCreateTh
2025-07-02 14:17:37 +02:00

332 lines
13 KiB
Python

"""
#############################################################################################
general_fonctions.py for pyCreateTh.py
#############################################################################################
"""
import os, logging, sys, re, configparser, unicodedata
import Lib.global_data as global_data
import tkinter as tk
from tkinter import filedialog
#################################################################################################
# Couleurs ANSI par niveau de log
#################################################################################################
COLOR_CODES = {
logging.DEBUG: "\033[94m", # Bleu
logging.INFO: "\033[92m", # Vert
logging.WARNING: "\033[95m", # MAGENTA
logging.ERROR: "\033[91m", # Rouge
logging.CRITICAL: "\033[1;91m", # Rouge vif
}
RESET = "\033[0m"
#################################################################################################
# Codes de couleur ANSI
class Colors:
BLACK = '\033[90m'
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
WHITE = '\033[97m'
ERROR = '\033[91m'
WARNING = '\033[95m'
HEADER = '\033[96m'
DEBUG = '\033[94m' # Bleu
INFO = '\033[92m' # Vert
CRITICAL = '\033[1;91m', # Rouge vif
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
#################################################################################################
def safe_relpath(path):
"""
Renvoie un chemin relatif si possible, sinon un chemin partiel à partir du dossier de référence.
"""
abs_path = os.path.abspath(path)
ref_path = os.path.abspath(os.getcwd())
try:
valeur = "~\\" + os.path.relpath(path, ref_path)
return valeur
except ValueError:
max_depth = 4 # Profondeur maximale pour tronquer le chemin
# Disques différents, afficher le chemin relatif partiel depuis la racine commune
path_parts = abs_path.split(os.sep)
ref_parts = ref_path.split(os.sep)
while path_parts and ref_parts and path_parts[0] == ref_parts[0]:
path_parts.pop(0)
ref_parts.pop(0)
result = os.path.join(*path_parts) if path_parts else os.path.basename(path)
# Si max_depth est défini, tronque le chemin
if max_depth is not None:
parts = result.split(os.sep)
if len(parts) > max_depth:
result = os.path.join("~\\" , *parts[-max_depth:])
return result
#################################################################################################
# Coloration des messages d'aide d'arg #
#################################################################################################
def colored_help(parser):
"""
Affiche l'aide colorée pour les arguments de la ligne de commande.
Args:
parser (argparse.ArgumentParser): Le parseur d'arguments.
Returns:
None
"""
# Captures the help output
help_text = parser.format_help()
# Coloration des différentes parties
colored_help_text = help_text.replace(
'usage:', f'{Colors.ERROR}usage:{Colors.ENDC}'
).replace(
'options:', f'{Colors.GREEN}options:{Colors.ENDC}'
).replace('positional arguments:', f'{Colors.BLUE}positional arguments:{Colors.ENDC}'
).replace(', --help', f'{Colors.BLUE}, --help:{Colors.ENDC}'
).replace('elp:', f'{Colors.BLUE}elp{Colors.ENDC}')
# Surligner les arguments
for action in parser._actions:
if action.option_strings:
# Colorer les options (--xyz)
for opt in action.option_strings:
colored_help_text = colored_help_text.replace(opt, f'{Colors.BLUE}{opt}{Colors.ENDC}').replace('--help', f'{Colors.BLUE}--help:{Colors.ENDC}')
# Imprimer le texte coloré
print(colored_help_text)
sys.exit(1)
#################################################################################################
# Mise au format des noms #
#################################################################################################
def sanitize_filename(th_name):
"""
Cleans a string to make it compatible with filenames on Windows, Linux, and macOS.
Replaces special and accented characters with compatible characters.
Replaces parentheses with underscores and enforces proper casing.
Args:
th_name (str): The filename to clean.
Returns:
str: The cleaned and compatible string.
"""
# Unicode normalization to replace accented characters with their non-accented equivalents
th_name = unicodedata.normalize('NFKD', th_name).encode('ASCII', 'ignore').decode('ASCII')
# Replace parentheses with underscores
th_name = th_name.replace('(', '_').replace(')', '_')
# Replace illegal characters with an underscore
th_name = re.sub(r'[<>:"/\\|?*\']', '_', th_name) # Illegal on Windows
th_name = re.sub(r'\s+', '_', th_name) # Spaces to underscores
th_name = re.sub(r'[^a-zA-Z0-9._-]', '_', th_name) # Keep only allowed chars
# Convert to lowercase, then capitalize the first letter
# th_name = th_name.lower().capitalize()
# th_name = th_name.capitalize()
# Suppression des underscores en début et fin
th_name = th_name.strip('_')
return th_name or "default_filename" # Avoid empty result
#################################################################################################
def select_file_tk_window():
"""
Ouvre une boite de dialogue tkinter pour sélectionner un fichier.
Returns:
str: Le chemin complet du fichier sélectionné.
"""
# Créer une instance de la fenêtre tkinter
root = tk.Tk()
# Cacher la fenêtre principale
root.withdraw()
# Afficher la boite de dialogue de sélection de fichier
file_path = filedialog.askopenfilename(
title="Select your file",
filetypes=[
("Compatibles files", "*.th *.mak *.dat *.tro"),
("MAK files", "*.mak"),
("TH files", "*.th"),
("DAT files", "*.dat"),
("TRO files", "*.tro"),
("All files", "*.*")]
)
return file_path # Retourner le chemin complet du fichier sélectionné
#################################################################################################
def read_config(config_file):
"""
Lit le fichier de configuration et initialise les variables globales.
Args:
config_file (str): Le chemin vers le fichier de configuration.
Returns:
None
"""
# Initialize the configparser to read .ini files
config = configparser.ConfigParser()
config.read(config_file, encoding="utf-8")
if 'Survey_Data' in config and 'Author' in config['Survey_Data']:
global_data.Author = config['Survey_Data']['Author']
if 'Survey_Data' in config and 'Copyright1' in config['Survey_Data']:
global_data.Copyright = config['Survey_Data']['Copyright1'] + "\n" + config['Survey_Data']['Copyright2'] + "\n" + config['Survey_Data']['Copyright3'] + "\n"
if 'Survey_Data' in config and 'Copyright_Short' in config['Survey_Data']:
global_data.CopyrightShort = config['Survey_Data']['Copyright_Short']
if 'Survey_Data' in config and 'map_comment' in config['Survey_Data']:
global_data.mapComment = config['Survey_Data']['map_comment']
if 'Survey_Data' in config and 'club' in config['Survey_Data']:
global_data.club = config['Survey_Data']['club']
if 'Survey_Data' in config and 'thanksto' in config['Survey_Data']:
global_data.thanksto = config['Survey_Data']['thanksto']
if 'Survey_Data' in config and 'datat' in config['Survey_Data']:
global_data.datat = config['Survey_Data']['datat']
if 'Survey_Data' in config and 'wpage' in config['Survey_Data']:
global_data.wpage = config['Survey_Data']['wpage']
if 'Survey_Data' in config and 'cs' in config['Survey_Data']:
global_data.cs = config['Survey_Data']['cs']
if 'Application_Data' in config and 'template_path' in config['Application_Data']:
global_data.templatePath = config['Application_Data']['template_path']
if 'Application_Data' in config and 'station_by_scrap' in config['Application_Data']:
global_data.stationByScrap = int(config['Application_Data']['station_by_scrap'])
if 'Application_Data' in config and 'final_therion_exe' in config['Application_Data']:
global_data.finalTherionExe = bool(config['Application_Data']['final_therion_exe'])
if 'Application_Data' in config and 'therion_path' in config['Application_Data']:
global_data.therionPath = config['Application_Data']['therion_path']
if 'Application_Data' in config and 'therion_path' in config['Application_Data']:
global_data.SurveyPrefixName = config['Application_Data']['survey_prefix_name']
if 'Application_Data' in config and 'shot_lines_in_th2_files' in config['Application_Data']:
global_data.linesInTh2 = bool(config['Application_Data']['shot_lines_in_th2_files'])
if 'Application_Data' in config and 'station_name_in_th2_files' in config['Application_Data']:
global_data.stationNamesInTh2 = bool(config['Application_Data']['station_name_in_th2_files'])
if 'Application_Data' in config and 'kSmooth' in config['Application_Data']:
global_data.kSmooth = float(config['Application_Data']['kSmooth'])
#################################################################################################
# Supprime les codes ANSI (pour l'écriture dans les fichiers)
#################################################################################################
def strip_ansi_codes(text):
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text)
#################################################################################################
# Formatter pour la console avec couleurs
#################################################################################################
class ConsoleFormatter(logging.Formatter):
def format(self, record):
color = COLOR_CODES.get(record.levelno, "")
message = super().format(record)
return f"{color}{message}{RESET}"
#################################################################################################
# Formatter pour le fichier avec "!!!" sur les erreurs
#################################################################################################
class FileFormatter(logging.Formatter):
def format(self, record):
clean_msg = strip_ansi_codes(record.getMessage())
prefix = "!!! " if record.levelno >= logging.ERROR else ""
record_copy = logging.LogRecord(
name=record.name,
level=record.levelno,
pathname=record.pathname,
lineno=record.lineno,
msg=f"{prefix}{clean_msg}",
args=(),
exc_info=record.exc_info,
func=record.funcName,
sinfo=record.stack_info
)
return super().format(record_copy)
#################################################################################################
# Fonction de configuration du logger
#################################################################################################
def setup_logger(logfile="app.log", debug_log=False):
logger = logging.getLogger("Logger")
logger.setLevel(logging.DEBUG)
logger.handlers.clear()
min_level = logging.DEBUG if debug_log else logging.INFO
# Console stderr handler — affichage à l'écran avec couleurs
stderr_handler = logging.StreamHandler(sys.stderr)
stderr_handler.setLevel(min_level)
stderr_formatter = ConsoleFormatter("%(levelname)s: %(message)s") # <-- Ta classe personnalisée
stderr_handler.setFormatter(stderr_formatter)
logger.addHandler(stderr_handler)
# File handler — fichier de log
file_handler = logging.FileHandler(logfile, encoding="utf-8")
file_handler.setLevel(min_level)
file_formatter = FileFormatter("%(asctime)s - %(levelname)s - %(message)s") # <-- Ta classe personnalisée
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
return logger
#################################################################################################
def release_log_file(logger):
handlers = logger.handlers[:]
for handler in handlers:
if isinstance(handler, logging.FileHandler):
handler.close()
logger.removeHandler(handler)