testprogram/ is local test data, not part of the tool: untracked and gitignored so it never comes back. verify.py now prints SKIP and exits 0 when the test pair is absent instead of blowing up. Adds config.NAME / config.VERSION (sab2sax 0.0.0.001). Every dialog title bar goes through config.title(), and the CLI prints the same line at startup, so it is clear which build produced a file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
189 lines
6.1 KiB
Python
189 lines
6.1 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 = "sab2sax"
|
|
VERSION = "0.0.0.001"
|
|
|
|
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):
|
|
return ask_open_file("Select the .sax application file", [SAX, 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())
|