import re import csv import tkinter as tk from tkinter import filedialog, messagebox import subprocess import os from datetime import datetime import shutil import platform try: from openpyxl import load_workbook XLSX_AVAILABLE = True except ImportError: XLSX_AVAILABLE = False def parse_logs(log_text): lines = log_text.splitlines() if lines and '=~' in lines[0]: lines = lines[1:] pairs = [] i = 0 while i < len(lines): line = lines[i].strip() if not line: i += 1 continue # Updated regex to capture any units (lb, kg, g, etc.) match = re.match(r'^GROSS\s+(\d+)\s*([a-zA-Z]+)$', line) if match: weight = int(match.group(1)) units = match.group(2) i += 2 # Skip to timestamp line if i < len(lines): ts_line = lines[i].strip() if re.match(r'^\d{2}:\d{2} (AM|PM) \d{2}/\d{2}/\d{2}$', ts_line): # Convert timestamp to Excel-friendly 12-hour format try: dt = datetime.strptime(ts_line, '%I:%M %p %m/%d/%y') excel_ts = dt.strftime('%m/%d/%Y %I:%M %p') pairs.append((weight, units, excel_ts)) print((weight, units, excel_ts)) except ValueError: i += 1 continue i += 1 # Move to next weight entry else: i += 1 else: i += 1 return pairs def write_csv1(pairs, filename): with open(filename, 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['WEIGHT', 'UNITS', 'TIME']) for weight, units, timestamp in pairs: writer.writerow([weight, units, timestamp]) def write_csv2(pairs, filename): with open(filename, 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['GROSS_WT', 'TARE_WT', 'NET_WT', 'GROSS_T', 'TARE_T', 'GROSS_UNITS', 'TARE_UNITS', 'NET_UNITS']) for j in range(0, len(pairs), 2): if j + 1 < len(pairs): gross_weight, gross_units, gross_time = pairs[j] tare_weight, tare_units, tare_time = pairs[j + 1] net = gross_weight - tare_weight net_units = tare_units if (tare_units != gross_units): net_units = 'MISMATCH' net = 'UNIT MISMATCH' writer.writerow([gross_weight, tare_weight, net, gross_time, tare_time, gross_units, tare_units, net_units]) if (len(pairs) % 2): # if odd number of items gross_weight, gross_units, gross_time = pairs[-1] writer.writerow([gross_weight, '', '', gross_time, '', gross_units, '', '']) def write_xlsx(pairs, output_filename, template_path='template.xlsx'): """Write sequential data to XLSX file using template""" if not XLSX_AVAILABLE: raise ImportError("openpyxl is required for XLSX export. Install with: pip install openpyxl") if not os.path.exists(template_path): raise FileNotFoundError(f"Template file not found: {template_path}") # Load the template workbook (data_only=False preserves formulas) wb = load_workbook(template_path, data_only=False) # Check if SEQUENTIAL sheet exists if 'SEQUENTIAL' not in wb.sheetnames: raise ValueError("Template does not contain a 'SEQUENTIAL' sheet") # Get the SEQUENTIAL sheet ws = wb['SEQUENTIAL'] # Clear existing data (starting from row 2, assuming row 1 has headers) # First, delete all rows below the header if ws.max_row > 1: ws.delete_rows(2, ws.max_row - 1) # Write new data starting from row 2 for row_idx, (weight, units, timestamp) in enumerate(pairs, start=2): ws.cell(row=row_idx, column=1, value=weight) ws.cell(row=row_idx, column=2, value=units) ws.cell(row=row_idx, column=3, value=timestamp) # Set COMBINED as the active sheet (default sheet when opened) if 'COMBINED' in wb.sheetnames: wb.active = wb.sheetnames.index('COMBINED') # Save to new filename (never overwrite template) wb.save(output_filename) def show_files_in_explorer(files): """Open file explorer and highlight the generated files""" if not files: return # Get the directory of the first file directory = os.path.dirname(os.path.abspath(files[0])) system = platform.system() if system == 'Windows': # On Windows, use explorer with /select to highlight the file # If multiple files, just select the first one subprocess.run(['explorer', '/select,', os.path.abspath(files[0])]) elif system == 'Darwin': # macOS # On Mac, use 'open' with -R to reveal in Finder subprocess.run(['open', '-R', os.path.abspath(files[0])]) else: # Linux and others # On Linux, just open the directory subprocess.run(['xdg-open', directory]) def run_update_script(): try: # Run update.sh script result = subprocess.run(['sh', 'C:\\Program Files\\rslogger-merger\\update.sh'], capture_output=True, text=True, cwd=os.getcwd()) if result.returncode == 0: messagebox.showinfo("Success", f"update.sh executed successfully!\n\nOutput:\n{result.stdout}") else: messagebox.showerror("Error", f"update.sh failed with return code {result.returncode}\n\nError:\n{result.stderr}") except FileNotFoundError: messagebox.showerror("Error", "update.sh not found in current directory") except Exception as e: messagebox.showerror("Error", f"Failed to run update.sh: {str(e)}") def select_input_file(): filename = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("All files", "*.*")]) if filename: input_file_var.set(filename) def process_files(): input_file = input_file_var.get() if not input_file: messagebox.showerror("Error", "Please select an input file.") return generate_csv = csv_var.get() generate_xlsx = xlsx_var.get() if not generate_csv and not generate_xlsx: messagebox.showerror("Error", "Please select at least one output format (CSV or XLSX).") return # Auto-generate output filenames base = os.path.splitext(input_file)[0] output_file1 = f"{base}.sequential.csv" output_file2 = f"{base}.joined.csv" output_xlsx = f"{base}.xlsx" generated_files = [] error_messages = [] try: with open(input_file, 'r') as f: log_text = f.read() pairs = parse_logs(log_text) # Generate CSV files if checked if generate_csv: try: write_csv1(pairs, output_file1) generated_files.append(output_file1) write_csv2(pairs, output_file2) generated_files.append(output_file2) except Exception as e: error_messages.append(f"CSV export failed: {str(e)}") # Generate XLSX file if checked if generate_xlsx: try: write_xlsx(pairs, output_xlsx) generated_files.append(output_xlsx) except ImportError as e: error_messages.append(f"XLSX export skipped: {str(e)}") except Exception as e: error_messages.append(f"XLSX export failed: {str(e)}") # Build success message if generated_files: files_list = "\n".join(generated_files) error_info = "" if error_messages: error_info = "\n\nWarnings:\n" + "\n".join(error_messages) # Custom dialog with "Show the files" button response = messagebox.askquestion("Success", f"Files generated:\n{files_list}{error_info}\n\nShow the files?", icon='info') if response == 'yes': show_files_in_explorer(generated_files) else: messagebox.showerror("Error", "No files were generated.\n\n" + "\n".join(error_messages)) except Exception as e: messagebox.showerror("Error", f"An error occurred: {str(e)}") root = tk.Tk() root.title("Log to CSV Converter") root.geometry("500x200") input_file_var = tk.StringVar() csv_var = tk.BooleanVar(value=False) # CSV unchecked by default xlsx_var = tk.BooleanVar(value=True) # XLSX checked by default # Input file selection input_frame = tk.Frame(root) input_frame.pack(pady=5, padx=10, fill='x') tk.Label(input_frame, text="Select Input Log File:").pack(side='left') tk.Entry(input_frame, textvariable=input_file_var, width=40).pack(side='left', padx=5) tk.Button(input_frame, text="Browse Input", bg="#2196F3", command=select_input_file).pack(side='left') # Output format checkboxes format_frame = tk.Frame(root) format_frame.pack(pady=5, padx=10, fill='x') tk.Label(format_frame, text="Output Formats:").pack(side='left') tk.Checkbutton(format_frame, text="CSV", variable=csv_var).pack(side='left', padx=10) tk.Checkbutton(format_frame, text="XLSX", variable=xlsx_var).pack(side='left', padx=10) # Convert button tk.Button(root, text="CONVERT", command=process_files, font=("Arial", 14), relief="raised", bg="#4CAF50", fg="white").pack(pady=10, padx=10, fill='x') # Update script button tk.Button(root, text="Update this Application", command=run_update_script, font=("Arial", 12), relief="raised").pack(pady=5, padx=10, fill='x') root.mainloop()