Add a window, and run.py as the entry point
gui.py: one button per direction, "SAB file to SAX file" and "SAX file to SAB file". The button decides which extension the chooser offers and which way the conversion goes. The log names the chosen file and its folder before the work starts, then the output file and folder when it finishes, each value on its own line. Output lands next to the input under the timestamped name, so there is no save dialog and nothing is overwritten. Sedona home sits at the top with a Change... button that validates manifests/ and rewrites system.properties. run.py: no arguments opens the window, any argument goes to the command line converter. pythonw run.py for a shortcut with no console. config: NAME/VERSION are now "SAB - SAX Sedona Files Converter 0.0.0.002", carried by the window title, every dialog title and the CLI banner. Adds choose_sab_input() - the existing choose_sab_file() is the save dialog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1fdfed90dd
commit
2dcc1704a6
52
README.md
52
README.md
@ -10,13 +10,46 @@ runtime lib).
|
||||
Verified byte-identical to `sedonac.exe` in both directions on a 189-component / 217-link app across
|
||||
18 kits.
|
||||
|
||||
## Usage
|
||||
## Run it
|
||||
|
||||
python sab2sax.py [in.sax|in.sab] [out]
|
||||
python run.py the window
|
||||
pythonw run.py the window with no console behind it
|
||||
python run.py app.sab [out] straight to the command line converter
|
||||
|
||||
`run.py` is the single entry point: no arguments opens the GUI, any argument is handed to the
|
||||
command line converter. Double-click it (or a shortcut to `pythonw run.py`) and you get the window.
|
||||
|
||||
## The window
|
||||
|
||||
python run.py (or: python gui.py)
|
||||
|
||||
Title bar carries the name and version (`SAB - SAX Sedona Files Converter 0.0.0.002`). One button
|
||||
per direction — **SAB file to SAX file** and **SAX file to SAB file** — and the button you press
|
||||
decides which extension the file chooser offers. The log shows the chosen file and its folder
|
||||
*before* the conversion runs, then the output file and its folder when it finishes:
|
||||
|
||||
sab -> sax
|
||||
INPUT FILE:
|
||||
app.sab
|
||||
INPUT PATH:
|
||||
C:\apps
|
||||
OUTPUT FILE:
|
||||
app-sab2sax-20260728-140609.sax
|
||||
OUTPUT PATH:
|
||||
C:\apps
|
||||
wrote 61294 bytes
|
||||
|
||||
The result is written next to the input under that timestamped name, so nothing is overwritten and
|
||||
no save dialog gets in the way. Sedona home is shown at the top with a `Change...` button that
|
||||
rewrites `system.properties`.
|
||||
|
||||
## The command line
|
||||
|
||||
python run.py [in.sax|in.sab] [out] (or: python sab2sax.py ...)
|
||||
|
||||
Like `sedonac`, the direction follows the input's extension: feed it a `.sax` and you get a `.sab`,
|
||||
feed it a `.sab` and you get a `.sax`. Both arguments are optional, so you can run it by
|
||||
double-click:
|
||||
feed it a `.sab` and you get a `.sax`. Both arguments are optional — `sab2sax.py` on its own asks for
|
||||
what it needs:
|
||||
|
||||
* no input → a file chooser opens, listing `.sax` and `.sab`;
|
||||
* no output → a save dialog opens next to the input, pre-filled with a timestamped name:
|
||||
@ -63,10 +96,11 @@ set the key by hand instead.
|
||||
|
||||
## Version
|
||||
|
||||
`config.NAME` / `config.VERSION` — currently `sab2sax 0.0.0.001`. Every dialog title bar reads
|
||||
`sab2sax 0.0.0.001 - Select the .sax or .sab application file to convert`, and the CLI prints the
|
||||
same line when it starts, so it is obvious which build produced a file. Bump it in
|
||||
[config.py](config.py) and both follow.
|
||||
`config.NAME` / `config.VERSION` — currently `SAB - SAX Sedona Files Converter 0.0.0.002`. The window
|
||||
title and every dialog title bar carry it; a chooser reads `SAB - SAX Sedona Files Converter
|
||||
0.0.0.002 - Select the .sax or .sab application file to convert`, and the CLI prints the same line
|
||||
when it starts, so it is obvious which build produced a file. Bump it in [config.py](config.py) and
|
||||
all of them follow.
|
||||
|
||||
## Verify
|
||||
|
||||
@ -100,6 +134,8 @@ Exit code is 0 on pass, 1 on failure. A malformed file fails with a one-line rea
|
||||
|
||||
## Files
|
||||
|
||||
run.py entry point: window with no arguments, CLI with them
|
||||
gui.py the window: a button per direction
|
||||
sab2sax.py the CLI: dispatches on the input extension
|
||||
sab.py sax -> sab encoder
|
||||
sax.py sab -> sax decoder
|
||||
|
||||
14
config.py
14
config.py
@ -14,8 +14,8 @@ chooser is shown and the answer is written back to `system.properties`.
|
||||
"""
|
||||
import os, sys
|
||||
|
||||
NAME = "sab2sax"
|
||||
VERSION = "0.0.0.001"
|
||||
NAME = "SAB - SAX Sedona Files Converter"
|
||||
VERSION = "0.0.0.002"
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PROPS = os.path.join(HERE, "system.properties")
|
||||
@ -169,7 +169,15 @@ def choose_input_file(initial=None):
|
||||
|
||||
|
||||
def choose_sax_file(initial=None):
|
||||
return ask_open_file("Select the .sax application file", [SAX, ANY], initial)
|
||||
"""Open a .sax -- the input of a sax -> sab conversion."""
|
||||
return ask_open_file("Select the .sax application file to convert to .sab",
|
||||
[SAX, ANY], initial)
|
||||
|
||||
|
||||
def choose_sab_input(initial=None):
|
||||
"""Open a .sab -- the input of a sab -> sax conversion."""
|
||||
return ask_open_file("Select the .sab application file to convert to .sax",
|
||||
[SAB, ANY], initial)
|
||||
|
||||
|
||||
def choose_sab_file(initialfile=None, initial=None):
|
||||
|
||||
166
gui.py
Normal file
166
gui.py
Normal file
@ -0,0 +1,166 @@
|
||||
"""A window with two buttons, one per direction.
|
||||
|
||||
python gui.py
|
||||
|
||||
Pick a direction, pick a file; the log shows what was chosen before the
|
||||
conversion runs and where the result landed afterwards. Output goes next to the
|
||||
input under a timestamped name, so nothing is ever overwritten.
|
||||
"""
|
||||
import os, sys, traceback
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
|
||||
import config, sab, sax
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
root.title("%s %s" % (config.NAME, config.VERSION))
|
||||
root.minsize(620, 340)
|
||||
|
||||
frame = ttk.Frame(root, padding=12)
|
||||
frame.pack(fill="both", expand=True)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.rowconfigure(3, weight=1)
|
||||
|
||||
# sedona home
|
||||
home_box = ttk.LabelFrame(frame, text="Sedona home", padding=(8, 4))
|
||||
home_box.grid(row=0, column=0, sticky="ew")
|
||||
home_box.columnconfigure(0, weight=1)
|
||||
self.home_var = tk.StringVar(value="(not set)")
|
||||
ttk.Label(home_box, textvariable=self.home_var).grid(row=0, column=0, sticky="w")
|
||||
ttk.Button(home_box, text="Change...", command=self.change_home).grid(row=0, column=1, padx=(8, 0))
|
||||
|
||||
# the two directions
|
||||
buttons = ttk.Frame(frame, padding=(0, 12))
|
||||
buttons.grid(row=1, column=0, sticky="ew")
|
||||
buttons.columnconfigure(0, weight=1)
|
||||
buttons.columnconfigure(1, weight=1)
|
||||
self.b_sab2sax = ttk.Button(buttons, text="SAB file to SAX file", padding=10,
|
||||
command=lambda: self.run("sab"))
|
||||
self.b_sab2sax.grid(row=0, column=0, sticky="ew", padx=(0, 6))
|
||||
self.b_sax2sab = ttk.Button(buttons, text="SAX file to SAB file", padding=10,
|
||||
command=lambda: self.run("sax"))
|
||||
self.b_sax2sab.grid(row=0, column=1, sticky="ew", padx=(6, 0))
|
||||
|
||||
self.status = tk.StringVar(value="Choose a direction.")
|
||||
ttk.Label(frame, textvariable=self.status).grid(row=2, column=0, sticky="w")
|
||||
|
||||
# log
|
||||
log_box = ttk.Frame(frame)
|
||||
log_box.grid(row=3, column=0, sticky="nsew", pady=(8, 0))
|
||||
log_box.columnconfigure(0, weight=1)
|
||||
log_box.rowconfigure(0, weight=1)
|
||||
self.log_text = tk.Text(log_box, height=10, wrap="word", state="disabled")
|
||||
self.log_text.grid(row=0, column=0, sticky="nsew")
|
||||
bar = ttk.Scrollbar(log_box, orient="vertical", command=self.log_text.yview)
|
||||
bar.grid(row=0, column=1, sticky="ns")
|
||||
self.log_text.configure(yscrollcommand=bar.set)
|
||||
|
||||
self.home = None
|
||||
root.after(50, self.first_home)
|
||||
|
||||
# ---------- helpers ----------
|
||||
def log(self, line=""):
|
||||
self.log_text.configure(state="normal")
|
||||
self.log_text.insert("end", line + "\n")
|
||||
self.log_text.see("end")
|
||||
self.log_text.configure(state="disabled")
|
||||
self.root.update_idletasks() # so it is on screen before the work starts
|
||||
|
||||
def set_home(self, path):
|
||||
self.home = path
|
||||
self.home_var.set(path or "(not set)")
|
||||
|
||||
def first_home(self):
|
||||
"""Resolve sedona home at startup; the folder chooser opens if it isn't set."""
|
||||
try:
|
||||
self.set_home(config.get_sedona_home())
|
||||
self.log("sedona home: %s" % self.home)
|
||||
except SystemExit as e:
|
||||
self.log("no sedona home: %s" % e)
|
||||
messagebox.showwarning(config.title("Sedona home"),
|
||||
"No sedona home selected.\n\n"
|
||||
"Set it with Change... before converting.")
|
||||
|
||||
def change_home(self):
|
||||
picked = config.ask_directory("Select Sedona home (the folder containing manifests/)",
|
||||
self.home)
|
||||
if not picked:
|
||||
return
|
||||
picked = os.path.normpath(picked)
|
||||
if not config.is_sedona_home(picked):
|
||||
messagebox.showerror(config.title("Sedona home"),
|
||||
"%s has no manifests/ subfolder." % picked)
|
||||
return
|
||||
config.write_prop(config.KEY, picked)
|
||||
self.set_home(picked)
|
||||
self.log("sedona home: %s (saved to %s)" % (picked, config.PROPS))
|
||||
|
||||
# ---------- conversion ----------
|
||||
def run(self, direction):
|
||||
"""direction: 'sab' means sab -> sax, 'sax' means sax -> sab."""
|
||||
if not config.is_sedona_home(self.home):
|
||||
messagebox.showerror(config.title("Sedona home"),
|
||||
"Set a valid sedona home first (Change...).")
|
||||
return
|
||||
|
||||
src = config.choose_sab_input() if direction == "sab" else config.choose_sax_file()
|
||||
if not src:
|
||||
self.status.set("Cancelled.")
|
||||
return
|
||||
src = os.path.abspath(src)
|
||||
|
||||
# show what was chosen, before doing anything with it
|
||||
self.log()
|
||||
self.log("%s -> %s" % (direction, "sax" if direction == "sab" else "sab"))
|
||||
self.log(" INPUT FILE:")
|
||||
self.log(" %s" % os.path.basename(src))
|
||||
self.log(" INPUT PATH:")
|
||||
self.log(" %s" % os.path.dirname(src))
|
||||
self.status.set("Converting %s ..." % os.path.basename(src))
|
||||
|
||||
if direction == "sab":
|
||||
out = os.path.join(os.path.dirname(src), sax.default_sax_name(src))
|
||||
work = lambda: sax.decode(src, self.home, out)
|
||||
else:
|
||||
out = os.path.join(os.path.dirname(src), sab.default_sab_name(src))
|
||||
work = lambda: sab.encode(src, self.home, out)
|
||||
|
||||
self.busy(True)
|
||||
try:
|
||||
n = work()
|
||||
except Exception as e:
|
||||
self.busy(False)
|
||||
self.log(" FAILED: %s" % e)
|
||||
self.status.set("Failed.")
|
||||
traceback.print_exc()
|
||||
messagebox.showerror(config.title("Conversion failed"), str(e))
|
||||
return
|
||||
self.busy(False)
|
||||
|
||||
self.log(" OUTPUT FILE:")
|
||||
self.log(" %s" % os.path.basename(out))
|
||||
self.log(" OUTPUT PATH:")
|
||||
self.log(" %s" % os.path.dirname(out))
|
||||
self.log(" wrote %d bytes" % n)
|
||||
self.status.set("Done: %s" % os.path.basename(out))
|
||||
|
||||
def busy(self, on):
|
||||
state = "disabled" if on else "normal"
|
||||
self.b_sab2sax.configure(state=state)
|
||||
self.b_sax2sab.configure(state=state)
|
||||
self.root.configure(cursor="watch" if on else "")
|
||||
self.root.update_idletasks()
|
||||
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
App(root)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
22
run.py
Normal file
22
run.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""Start here.
|
||||
|
||||
python run.py the window
|
||||
pythonw run.py the window, without a console behind it
|
||||
python run.py app.sab [out] straight to the command line converter
|
||||
|
||||
No arguments means the GUI; any argument is passed through to sab2sax.py, so
|
||||
the same entry point serves a double-click and a script.
|
||||
"""
|
||||
import sys
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) > 1:
|
||||
import sab2sax
|
||||
return sab2sax.main(argv) or 0
|
||||
import gui
|
||||
return gui.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Loading…
Reference in New Issue
Block a user