Rework the "cli" to a server instead
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
import config
|
||||
import eos.config
|
||||
@@ -50,7 +50,7 @@ def _import_single_fit(raw_text: str) -> tuple[object, set[str]]:
|
||||
import_type, import_data = Port.importFitFromBuffer(cleaned_text)
|
||||
|
||||
if not import_data or len(import_data) != 1:
|
||||
raise SystemExit("Expected exactly one fit in input; got %d" % (len(import_data) if import_data else 0))
|
||||
raise ValueError("Expected exactly one fit in input; got %d" % (len(import_data) if import_data else 0))
|
||||
|
||||
fit = import_data[0]
|
||||
|
||||
@@ -69,19 +69,19 @@ def _add_projected_fit(s_fit, target_fit, projected_fit, amount: int) -> None:
|
||||
fit = s_fit.getFit(target_fit.ID)
|
||||
projected = s_fit.getFit(projected_fit.ID, projected=True)
|
||||
if projected is None:
|
||||
raise SystemExit("Projected fit %s is not available" % projected_fit.ID)
|
||||
raise ValueError("Projected fit %s is not available" % projected_fit.ID)
|
||||
|
||||
if projected in fit.projectedFits and projected.ID in fit.projectedFitDict:
|
||||
projection_info = projected.getProjectionInfo(fit.ID)
|
||||
if projection_info is None:
|
||||
raise SystemExit("Projection info missing for projected fit %s" % projected_fit.ID)
|
||||
raise ValueError("Projection info missing for projected fit %s" % projected_fit.ID)
|
||||
else:
|
||||
fit.projectedFitDict[projected.ID] = projected
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(projected)
|
||||
projection_info = projected.getProjectionInfo(fit.ID)
|
||||
if projection_info is None:
|
||||
raise SystemExit("Projection info missing after linking projected fit %s" % projected_fit.ID)
|
||||
raise ValueError("Projection info missing after linking projected fit %s" % projected_fit.ID)
|
||||
|
||||
projection_info.amount = amount
|
||||
projection_info.active = True
|
||||
@@ -91,7 +91,7 @@ def _add_command_fit(s_fit, target_fit, command_fit) -> None:
|
||||
fit = s_fit.getFit(target_fit.ID)
|
||||
command = s_fit.getFit(command_fit.ID)
|
||||
if command is None:
|
||||
raise SystemExit("Command fit %s is not available" % command_fit.ID)
|
||||
raise ValueError("Command fit %s is not available" % command_fit.ID)
|
||||
|
||||
if command in fit.commandFits or command.ID in fit.commandFitDict:
|
||||
return
|
||||
@@ -101,7 +101,7 @@ def _add_command_fit(s_fit, target_fit, command_fit) -> None:
|
||||
eos.db.saveddata_session.refresh(command)
|
||||
info = command.getCommandInfo(fit.ID)
|
||||
if info is None:
|
||||
raise SystemExit("Command info missing for command fit %s" % command_fit.ID)
|
||||
raise ValueError("Command info missing for command fit %s" % command_fit.ID)
|
||||
info.active = True
|
||||
|
||||
|
||||
@@ -320,92 +320,95 @@ def _build_output(main_fit) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Headless pyfa CLI fit statistics exporter")
|
||||
parser.add_argument(
|
||||
"data",
|
||||
nargs="?",
|
||||
help="JSON payload (inline). If omitted, read from stdin.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--savepath",
|
||||
help="Override pyfa save path (saveddata, DB, logs). Defaults to standard user location.",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def _load_payload(data: str) -> dict:
|
||||
try:
|
||||
payload = json.loads(data)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SystemExit("Invalid JSON: %s" % exc) from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise SystemExit("Top-level JSON must be an object")
|
||||
return payload
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = _parse_args(argv)
|
||||
_init_pyfa(args.savepath)
|
||||
|
||||
def compute_stats(payload: dict, savepath: str | None = None) -> dict:
|
||||
if "fit" not in payload or not isinstance(payload["fit"], str):
|
||||
raise ValueError("Payload must contain a 'fit' field with EFT/text export")
|
||||
_init_pyfa(savepath)
|
||||
from service.fit import Fit as FitService
|
||||
|
||||
s_fit = FitService.getInstance()
|
||||
if s_fit.character is None:
|
||||
from eos.saveddata.character import Character as saveddata_Character
|
||||
s_fit.character = saveddata_Character.getAll5()
|
||||
|
||||
raw = args.data if args.data is not None else sys.stdin.read()
|
||||
payload = _load_payload(raw)
|
||||
|
||||
if "fit" not in payload or not isinstance(payload["fit"], str):
|
||||
raise SystemExit("Payload must contain a 'fit' field with EFT/text export")
|
||||
|
||||
main_fit, _ = _import_single_fit(payload["fit"])
|
||||
|
||||
projected_defs = payload.get("projected_fits", [])
|
||||
command_defs = payload.get("command_fits", [])
|
||||
|
||||
projected_fits: list[tuple[object, int]] = []
|
||||
for entry in projected_defs:
|
||||
if not isinstance(entry, dict):
|
||||
raise SystemExit("Each projected_fits entry must be an object")
|
||||
raise ValueError("Each projected_fits entry must be an object")
|
||||
fit_text = entry.get("fit")
|
||||
count = entry.get("count")
|
||||
if not isinstance(fit_text, str):
|
||||
raise SystemExit("Each projected_fits entry must contain a string 'fit'")
|
||||
raise ValueError("Each projected_fits entry must contain a string 'fit'")
|
||||
if not isinstance(count, int) or count <= 0:
|
||||
raise SystemExit("Each projected_fits entry must contain a positive integer 'count'")
|
||||
raise ValueError("Each projected_fits entry must contain a positive integer 'count'")
|
||||
pf, _ = _import_single_fit(fit_text)
|
||||
projected_fits.append((pf, count))
|
||||
|
||||
command_fits: list[object] = []
|
||||
for entry in command_defs:
|
||||
if not isinstance(entry, dict):
|
||||
raise SystemExit("Each command_fits entry must be an object")
|
||||
raise ValueError("Each command_fits entry must be an object")
|
||||
fit_text = entry.get("fit")
|
||||
if not isinstance(fit_text, str):
|
||||
raise SystemExit("Each command_fits entry must contain a string 'fit'")
|
||||
raise ValueError("Each command_fits entry must contain a string 'fit'")
|
||||
cf, _ = _import_single_fit(fit_text)
|
||||
command_fits.append(cf)
|
||||
|
||||
for pf, count in projected_fits:
|
||||
_add_projected_fit(s_fit, main_fit, pf, count)
|
||||
for cf in command_fits:
|
||||
_add_command_fit(s_fit, main_fit, cf)
|
||||
|
||||
s_fit.recalc(main_fit)
|
||||
s_fit.fill(main_fit)
|
||||
return _build_output(main_fit)
|
||||
|
||||
output = _build_output(main_fit)
|
||||
json.dump(output, sys.stdout, indent=2, sort_keys=True)
|
||||
sys.stdout.write("\n")
|
||||
return 0
|
||||
|
||||
def _run_http_server(port: int, savepath: str | None) -> None:
|
||||
class SimulateHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
if self.path != "/simulate":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length).decode("utf-8")
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
except json.JSONDecodeError as exc:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(("Invalid JSON: %s" % exc).encode("utf-8"))
|
||||
return
|
||||
if not isinstance(payload, dict):
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Top-level JSON must be an object")
|
||||
return
|
||||
try:
|
||||
output = compute_stats(payload, savepath)
|
||||
except ValueError as e:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
return
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
return
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(output, indent=2, sort_keys=True).encode("utf-8"))
|
||||
self.wfile.write(b"\n")
|
||||
|
||||
with HTTPServer(("", port), SimulateHandler) as httpd:
|
||||
print("POST /simulate on http://127.0.0.1:%s" % port, file=sys.stderr, flush=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
_run_http_server(9123, None)
|
||||
|
||||
Reference in New Issue
Block a user