feat: placeholder evaluation with bounds (min, max)
This commit is contained in:
parent
85bec5c4e2
commit
37fc063bc7
1 changed files with 43 additions and 3 deletions
|
|
@ -29,7 +29,18 @@ def _safe_eval(expr: str, ctx: dict) -> str:
|
|||
return str(math.ceil(val))
|
||||
return str(val)
|
||||
|
||||
_placeholder_re = re.compile(r"\{([^{}]+)\}")
|
||||
_placeholder_re = re.compile(r"\{([^{}|]+)(?:\|([^{}]+))?\}")
|
||||
|
||||
def _safe_eval_value(expr: str, ctx: dict):
|
||||
node = ast.parse(expr, mode="eval")
|
||||
for n in ast.walk(node):
|
||||
if not isinstance(n, ALLOWED_NODES):
|
||||
raise ValueError("disallowed expression")
|
||||
if isinstance(n, ast.Name) and n.id not in ALLOWED_NAMES:
|
||||
raise ValueError("unknown name")
|
||||
if isinstance(n, ast.Call):
|
||||
raise ValueError("calls not allowed")
|
||||
return eval(compile(node, "<expr>", "eval"), {"__builtins__": {}}, ctx)
|
||||
|
||||
def render_name(name_template: str, start: Optional[date], end: Optional[date]) -> str:
|
||||
if not name_template:
|
||||
|
|
@ -40,12 +51,41 @@ def render_name(name_template: str, start: Optional[date], end: Optional[date])
|
|||
nights = max(days - 1, 0)
|
||||
ctx = {"days": days, "nights": nights}
|
||||
|
||||
def parse_opts(optstr: str):
|
||||
opts = {}
|
||||
if not optstr:
|
||||
return opts
|
||||
for part in optstr.split(","):
|
||||
if "=" in part:
|
||||
k, v = part.split("=", 1)
|
||||
opts[k.strip()] = v.strip()
|
||||
return opts
|
||||
|
||||
def repl(m):
|
||||
expr = m.group(1).strip()
|
||||
opts_str = m.group(2)
|
||||
opts = parse_opts(opts_str)
|
||||
try:
|
||||
return _safe_eval(expr, ctx)
|
||||
val = _safe_eval_value(expr, ctx)
|
||||
# if numeric apply bounds
|
||||
if isinstance(val, (int, float)):
|
||||
# apply min / max if provided
|
||||
if "min" in opts:
|
||||
try:
|
||||
val = max(val, float(opts["min"]))
|
||||
except Exception:
|
||||
pass
|
||||
if "max" in opts:
|
||||
try:
|
||||
val = min(val, float(opts["max"]))
|
||||
except Exception:
|
||||
pass
|
||||
# round up numeric results (ceiling)
|
||||
return str(math.ceil(val))
|
||||
# non-numeric: string as before
|
||||
return str(val)
|
||||
except Exception:
|
||||
# if not evaluable, leave placeholder as-is
|
||||
# leave placeholder unchanged on error
|
||||
return m.group(0)
|
||||
|
||||
return _placeholder_re.sub(repl, name_template)
|
||||
|
|
|
|||
Loading…
Reference in a new issue