Py_sax2sab/config.py
arda.aydin@ontrol.com.tr 2dcc1704a6 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>
2026-08-12 23:05:15 +03:00

197 lines
6.4 KiB
Python

"""Configuration: sedona home lives in `system.properties` next to this file.
Same key sedonac itself uses, but written as a plain Windows path:
sedona.home=C:\\path\\to\\sedona
We only *read* this file, so there is no reason to escape anything. Java's
Properties (what sedonac uses) treats `\\` as an escape, so its own files double
them up -- `\\\\path\\\\to`. Both parse here: `\\\\` collapses to `\\`, and every
other backslash is kept literally, so `C:\\niagara` does not turn into a newline.
If the key is missing, or the directory no longer holds `manifests/`, a folder
chooser is shown and the answer is written back to `system.properties`.
"""
import os, sys
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")
KEY = "sedona.home"
def title(text):
"""Every dialog carries the program name and version in its title bar."""
return "%s %s - %s" % (NAME, VERSION, text)
# ---------- .properties ----------
def _unescape(v):
"""Collapse `\\\\` to `\\`; leave any other backslash alone.
Accepts both our plain paths and java-escaped ones, and never mistakes a
path segment for an escape (`C:\\niagara` stays `C:\\niagara`).
"""
out, i = [], 0
while i < len(v):
if v[i] == "\\" and i + 1 < len(v) and v[i + 1] == "\\":
i += 1
out.append(v[i])
i += 1
return "".join(out)
def read_props(path=PROPS):
props = {}
if not os.path.exists(path):
return props
with open(path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
if not line or line[0] in "#!" or "=" not in line:
continue
k, v = line.split("=", 1)
props[k.strip()] = _unescape(v.strip()) # last wins
return props
def write_prop(key, value, path=PROPS):
"""Set `key`, keeping every other line of the file intact."""
lines = []
if os.path.exists(path):
with open(path, "r", encoding="utf-8", errors="replace") as f:
lines = f.read().splitlines()
new = "%s=%s" % (key, value)
done = False
for i, line in enumerate(lines):
s = line.strip()
if s and s[0] not in "#!" and "=" in s and s.split("=", 1)[0].strip() == key:
lines[i] = new if not done else "#" + line # comment out later dupes
done = True
if not done:
lines.append(new)
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
# ---------- dialogs ----------
def _tk():
"""A hidden root window, or None when no GUI is available."""
try:
import tkinter as tk
root = tk.Tk()
root.withdraw()
return root
except Exception:
return None
def ask_directory(text, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.askdirectory(title=title(text), initialdir=initial or HERE) or None
finally:
root.destroy()
def ask_open_file(text, filetypes, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.askopenfilename(title=title(text), filetypes=filetypes,
initialdir=initial or HERE) or None
finally:
root.destroy()
def ask_save_file(text, filetypes, defaultextension, initialfile=None, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.asksaveasfilename(title=title(text), filetypes=filetypes,
defaultextension=defaultextension,
initialfile=initialfile,
initialdir=initial or HERE) or None
finally:
root.destroy()
# ---------- api ----------
def is_sedona_home(path):
return bool(path) and os.path.isdir(os.path.join(path, "manifests"))
def get_sedona_home(interactive=True):
"""Path from system.properties, prompting (and saving) when unusable."""
home = read_props().get(KEY)
if is_sedona_home(home):
return home
if not interactive:
raise SystemExit("%s not set (or has no manifests/) in %s" % (KEY, PROPS))
why = "not set in" if not home else "invalid (%s), update" % home
print("sedona.home %s %s -- please choose the sedona installation folder" % (why, PROPS))
while True:
picked = ask_directory("Select Sedona home (the folder containing manifests/)",
home if home and os.path.isdir(home) else None)
if not picked:
raise SystemExit("no sedona home selected")
picked = os.path.normpath(picked)
if is_sedona_home(picked):
write_prop(KEY, picked)
print("saved %s=%s to %s" % (KEY, picked, PROPS))
return picked
print(" %s has no manifests/ subfolder -- try again" % picked)
SAX = ("Sedona app XML", "*.sax")
SAB = ("Sedona app binary", "*.sab")
ANY = ("All files", "*.*")
def choose_input_file(initial=None):
"""Pick the file to convert -- direction follows its extension."""
return ask_open_file("Select the .sax or .sab application file to convert",
[("Sedona app (*.sax, *.sab)", "*.sax *.sab"), SAX, SAB, ANY],
initial)
def choose_sax_file(initial=None):
"""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):
"""Save-as for the .sab produced from a .sax."""
return ask_save_file("Save the .sab application file", [SAB, ANY],
".sab", initialfile, initial)
def choose_sax_output(initialfile=None, initial=None):
"""Save-as for the .sax produced from a .sab."""
return ask_save_file("Save the .sax application file", [SAX, ANY],
".sax", initialfile, initial)
if __name__ == "__main__":
print(get_sedona_home())