253 lines
9.5 KiB
Python
253 lines
9.5 KiB
Python
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() |