execsql2 2.17.0__py3-none-any.whl → 2.17.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execsql/__init__.py +6 -2
- execsql/api.py +25 -6
- execsql/cli/__init__.py +5 -3
- execsql/cli/lint.py +30 -34
- execsql/cli/run.py +10 -0
- execsql/config.py +145 -92
- execsql/db/access.py +54 -40
- execsql/db/base.py +33 -6
- execsql/db/firebird.py +3 -1
- execsql/db/mysql.py +4 -3
- execsql/db/oracle.py +36 -14
- execsql/db/postgres.py +8 -6
- execsql/db/sqlite.py +5 -2
- execsql/db/sqlserver.py +8 -6
- execsql/debug/repl.py +59 -21
- execsql/exceptions.py +19 -4
- execsql/exporters/base.py +3 -2
- execsql/exporters/delimited.py +2 -3
- execsql/exporters/feather.py +3 -3
- execsql/exporters/ods.py +1 -1
- execsql/exporters/xls.py +12 -4
- execsql/exporters/xlsx.py +1 -1
- execsql/gui/desktop.py +129 -15
- execsql/importers/__init__.py +1 -1
- execsql/importers/ods.py +1 -1
- execsql/importers/xls.py +1 -1
- execsql/metacommands/__init__.py +34 -5
- execsql/metacommands/conditions.py +26 -14
- execsql/metacommands/connect.py +21 -14
- execsql/metacommands/control.py +55 -68
- execsql/metacommands/data.py +25 -9
- execsql/metacommands/debug.py +132 -77
- execsql/metacommands/io_export.py +14 -2
- execsql/metacommands/io_import.py +11 -2
- execsql/metacommands/io_write.py +113 -11
- execsql/metacommands/prompt.py +46 -32
- execsql/metacommands/script_ext.py +63 -34
- execsql/metacommands/system.py +4 -3
- execsql/script/__init__.py +28 -37
- execsql/script/ast.py +7 -7
- execsql/script/control.py +4 -101
- execsql/script/engine.py +37 -251
- execsql/script/executor.py +181 -222
- execsql/script/parser.py +1 -3
- execsql/script/variables.py +8 -3
- execsql/state.py +125 -37
- execsql/utils/errors.py +0 -2
- execsql/utils/fileio.py +47 -3
- execsql/utils/mail.py +3 -2
- execsql/utils/strings.py +5 -5
- {execsql2-2.17.0.dist-info → execsql2-2.17.2.dist-info}/METADATA +42 -36
- execsql2-2.17.2.dist-info/RECORD +124 -0
- execsql2-2.17.2.dist-info/licenses/NOTICE +11 -0
- execsql2-2.17.0.dist-info/RECORD +0 -124
- execsql2-2.17.0.dist-info/licenses/NOTICE +0 -10
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.17.0.data → execsql2-2.17.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.17.0.dist-info → execsql2-2.17.2.dist-info}/WHEEL +0 -0
- {execsql2-2.17.0.dist-info → execsql2-2.17.2.dist-info}/entry_points.txt +0 -0
- {execsql2-2.17.0.dist-info → execsql2-2.17.2.dist-info}/licenses/LICENSE.txt +0 -0
execsql/script/engine.py
CHANGED
|
@@ -2,28 +2,25 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
"""Script execution engine for execsql.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Holds the metacommand dispatch primitives, the statement data types
|
|
6
|
+
consumed by the AST executor, and the substitution-variable helpers
|
|
7
|
+
shared by the parser and executor. Script parsing and tree-walking
|
|
8
|
+
live in :mod:`execsql.script.parser` and
|
|
9
|
+
:mod:`execsql.script.executor` respectively.
|
|
7
10
|
|
|
8
11
|
Classes:
|
|
9
12
|
- :class:`MetaCommand` — one entry in the metacommand dispatch table.
|
|
10
|
-
- :class:`MetaCommandList` — ordered list of :class:`MetaCommand` entries.
|
|
13
|
+
- :class:`MetaCommandList` — ordered list of :class:`MetaCommand` entries with a keyword index.
|
|
11
14
|
- :class:`SqlStmt` — wraps a single SQL string for execution.
|
|
12
15
|
- :class:`MetacommandStmt` — wraps a metacommand line for dispatch.
|
|
13
16
|
- :class:`ScriptCmd` — pairs a statement with its source-file location.
|
|
14
|
-
- :class:`CommandList` — ordered list of :class:`ScriptCmd` objects.
|
|
15
|
-
- :class:`CommandListWhileLoop` — loop variant that repeats while a condition is true.
|
|
16
|
-
- :class:`CommandListUntilLoop` — loop variant that repeats until a condition is true.
|
|
17
|
-
- :class:`ScriptFile` — reads and tokenises a ``.sql`` file.
|
|
18
17
|
- :class:`ScriptExecSpec` — specification for deferred script execution.
|
|
19
18
|
|
|
20
19
|
Functions:
|
|
21
|
-
- :func:`set_system_vars` — populates built-in ``$VARNAME`` system variables.
|
|
22
|
-
- :func:`
|
|
23
|
-
- :func:`
|
|
24
|
-
- :func:`current_script_line` — returns the
|
|
25
|
-
- :func:`read_sqlfile` — parses a SQL script file into a new :class:`CommandList`.
|
|
26
|
-
- :func:`read_sqlstring` — parses an inline script string into a new :class:`CommandList`.
|
|
20
|
+
- :func:`set_system_vars` — populates built-in ``$VARNAME`` system variables (calls the static + dynamic helpers).
|
|
21
|
+
- :func:`set_static_system_vars` / :func:`set_dynamic_system_vars` — refresh the half-static / per-statement system variables independently.
|
|
22
|
+
- :func:`substitute_vars` — performs ``!!$VAR!!`` / ``!'!var!'!`` / ``!"!var!"!`` / ``!{$var}!`` expansion.
|
|
23
|
+
- :func:`current_script_line` — returns the ``(file, line_no)`` of the currently executing command.
|
|
27
24
|
"""
|
|
28
25
|
|
|
29
26
|
import datetime
|
|
@@ -35,7 +32,7 @@ from typing import Any
|
|
|
35
32
|
|
|
36
33
|
import execsql.state as _state
|
|
37
34
|
from execsql.exceptions import ErrInfo
|
|
38
|
-
from execsql.script.variables import
|
|
35
|
+
from execsql.script.variables import SubVarSet
|
|
39
36
|
from execsql.utils.errors import exception_desc
|
|
40
37
|
|
|
41
38
|
__all__ = [
|
|
@@ -44,7 +41,6 @@ __all__ = [
|
|
|
44
41
|
"SqlStmt",
|
|
45
42
|
"MetacommandStmt",
|
|
46
43
|
"ScriptCmd",
|
|
47
|
-
"CommandList",
|
|
48
44
|
"ScriptExecSpec",
|
|
49
45
|
"set_system_vars",
|
|
50
46
|
"substitute_vars",
|
|
@@ -237,10 +233,9 @@ class MetaCommandList:
|
|
|
237
233
|
run, ``(False, None)`` if no command matched.
|
|
238
234
|
"""
|
|
239
235
|
for cmd in self._candidates(cmd_str):
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return True, value
|
|
236
|
+
success, value = cmd.run(cmd_str)
|
|
237
|
+
if success:
|
|
238
|
+
return True, value
|
|
244
239
|
return False, None
|
|
245
240
|
|
|
246
241
|
def get_match(self, cmd: str) -> tuple | None:
|
|
@@ -261,109 +256,38 @@ class MetaCommandList:
|
|
|
261
256
|
|
|
262
257
|
|
|
263
258
|
class SqlStmt:
|
|
264
|
-
"""A single SQL statement ready to be executed against the active database.
|
|
259
|
+
"""A single SQL statement ready to be executed against the active database.
|
|
260
|
+
|
|
261
|
+
Data class only — the legacy ``.run()`` method was removed when the AST
|
|
262
|
+
executor became the sole engine. SQL execution now goes through
|
|
263
|
+
:func:`execsql.script.executor._exec_sql`.
|
|
264
|
+
"""
|
|
265
265
|
|
|
266
|
-
# A SQL statement to be passed to a database to execute.
|
|
267
266
|
def __init__(self, sql_statement: str) -> None:
|
|
268
267
|
self.statement = re.sub(r"\s*;(\s*;\s*)+$", ";", sql_statement)
|
|
269
268
|
|
|
270
269
|
def __repr__(self) -> str:
|
|
271
270
|
return f"SqlStmt({self.statement})"
|
|
272
271
|
|
|
273
|
-
def run(self, localvars: SubVarSet | None = None, commit: bool = True) -> None:
|
|
274
|
-
"""Execute the statement on the current database, committing unless in a batch."""
|
|
275
|
-
# Run the SQL statement on the current database.
|
|
276
|
-
if _state.if_stack.all_true():
|
|
277
|
-
e = None
|
|
278
|
-
_state.status.sql_error = False
|
|
279
|
-
cmd = substitute_vars(self.statement, localvars)
|
|
280
|
-
if _state.varlike.search(cmd):
|
|
281
|
-
_state.output.write(
|
|
282
|
-
f"Warning: There is a potential un-substituted variable in the command\n {cmd}\n",
|
|
283
|
-
)
|
|
284
|
-
try:
|
|
285
|
-
db = _state.dbs.current()
|
|
286
|
-
if _state.conf.log_sql and _state.exec_log:
|
|
287
|
-
lno = getattr(_state, "last_command", None)
|
|
288
|
-
lno = lno.line_no if lno and hasattr(lno, "line_no") else None
|
|
289
|
-
_state.exec_log.log_sql_query(cmd, db.name(), lno)
|
|
290
|
-
db.execute(cmd)
|
|
291
|
-
if commit:
|
|
292
|
-
db.commit()
|
|
293
|
-
except ErrInfo as errinfo:
|
|
294
|
-
e = errinfo
|
|
295
|
-
except SystemExit:
|
|
296
|
-
raise
|
|
297
|
-
except Exception:
|
|
298
|
-
e = ErrInfo(type="exception", exception_msg=exception_desc())
|
|
299
|
-
if e:
|
|
300
|
-
from execsql.utils.errors import stamp_errinfo
|
|
301
|
-
|
|
302
|
-
stamp_errinfo(e)
|
|
303
|
-
_state.subvars.add_substitution("$LAST_ERROR", cmd)
|
|
304
|
-
_state.subvars.add_substitution("$ERROR_MESSAGE", e.errmsg())
|
|
305
|
-
_state.status.sql_error = True
|
|
306
|
-
if _state.exec_log is not None:
|
|
307
|
-
_state.exec_log.log_status_info(f"SQL error: {e.errmsg()}")
|
|
308
|
-
if _state.status.halt_on_err:
|
|
309
|
-
from execsql.utils.errors import exit_now
|
|
310
|
-
|
|
311
|
-
exit_now(1, e)
|
|
312
|
-
return
|
|
313
|
-
_state.subvars.add_substitution("$LAST_SQL", cmd)
|
|
314
|
-
|
|
315
272
|
def commandline(self) -> str:
|
|
316
273
|
"""Return the raw SQL statement text."""
|
|
317
274
|
return self.statement
|
|
318
275
|
|
|
319
276
|
|
|
320
277
|
class MetacommandStmt:
|
|
321
|
-
"""A single execsql metacommand line
|
|
278
|
+
"""A single execsql metacommand line.
|
|
279
|
+
|
|
280
|
+
Data class only — the legacy ``.run()`` method was removed when the AST
|
|
281
|
+
executor became the sole engine. Metacommand dispatch now goes through
|
|
282
|
+
:func:`execsql.script.executor._exec_metacommand`.
|
|
283
|
+
"""
|
|
322
284
|
|
|
323
|
-
# A metacommand to be handled by execsql.
|
|
324
285
|
def __init__(self, metacommand_statement: str) -> None:
|
|
325
286
|
self.statement = metacommand_statement
|
|
326
287
|
|
|
327
288
|
def __repr__(self) -> str:
|
|
328
289
|
return f"MetacommandStmt({self.statement})"
|
|
329
290
|
|
|
330
|
-
def run(self, localvars: SubVarSet | None = None, commit: bool = False) -> Any:
|
|
331
|
-
"""Expand substitution variables then dispatch through the metacommand table."""
|
|
332
|
-
# Tries all metacommands in the dispatch table until one runs.
|
|
333
|
-
errmsg = "Unknown metacommand"
|
|
334
|
-
cmd = substitute_vars(self.statement, localvars)
|
|
335
|
-
if _state.if_stack.all_true() and _state.varlike.search(cmd):
|
|
336
|
-
_state.output.write(f"Warning: There is a potential un-substituted variable in the command\n {cmd}\n")
|
|
337
|
-
e = None
|
|
338
|
-
try:
|
|
339
|
-
applies, result = _state.metacommandlist.eval(cmd)
|
|
340
|
-
if applies:
|
|
341
|
-
return result
|
|
342
|
-
except ErrInfo as errinfo:
|
|
343
|
-
e = errinfo
|
|
344
|
-
except SystemExit:
|
|
345
|
-
raise
|
|
346
|
-
except Exception:
|
|
347
|
-
e = ErrInfo(type="exception", exception_msg=exception_desc())
|
|
348
|
-
if e:
|
|
349
|
-
from execsql.utils.errors import stamp_errinfo
|
|
350
|
-
|
|
351
|
-
stamp_errinfo(e)
|
|
352
|
-
_state.status.metacommand_error = True
|
|
353
|
-
_state.subvars.add_substitution("$LAST_ERROR", cmd)
|
|
354
|
-
_state.subvars.add_substitution("$ERROR_MESSAGE", e.errmsg())
|
|
355
|
-
if _state.exec_log is not None:
|
|
356
|
-
_state.exec_log.log_status_info(f"Metacommand error: {e.errmsg()}")
|
|
357
|
-
if _state.status.halt_on_metacommand_err:
|
|
358
|
-
# Re-raise the original ErrInfo so its message is preserved, not
|
|
359
|
-
# replaced with the generic "Unknown metacommand" text.
|
|
360
|
-
raise e
|
|
361
|
-
if _state.if_stack.all_true():
|
|
362
|
-
# but nothing applies, because we got here.
|
|
363
|
-
_state.status.metacommand_error = True
|
|
364
|
-
raise ErrInfo(type="cmd", command_text=cmd, other_msg=errmsg)
|
|
365
|
-
return None
|
|
366
|
-
|
|
367
291
|
def commandline(self) -> str:
|
|
368
292
|
"""Return the metacommand line in its canonical ``-- !x! ...`` form."""
|
|
369
293
|
return "-- !x! " + self.statement
|
|
@@ -409,145 +333,6 @@ class ScriptCmd:
|
|
|
409
333
|
return self.command.statement if self.command_type == "sql" else "-- !x! " + self.command.statement
|
|
410
334
|
|
|
411
335
|
|
|
412
|
-
# ---------------------------------------------------------------------------
|
|
413
|
-
# CommandList / CommandListWhileLoop / CommandListUntilLoop
|
|
414
|
-
# ---------------------------------------------------------------------------
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
class CommandList:
|
|
418
|
-
"""Ordered sequence of :class:`ScriptCmd` objects with a forward-only execution cursor.
|
|
419
|
-
|
|
420
|
-
Push onto ``_state.commandliststack`` and call :meth:`run_next` in a loop
|
|
421
|
-
(or let :func:`runscripts` drive it) to execute each command in turn.
|
|
422
|
-
"""
|
|
423
|
-
|
|
424
|
-
# A list of ScriptCmd objects including execution state.
|
|
425
|
-
def __init__(
|
|
426
|
-
self,
|
|
427
|
-
cmdlist: list[ScriptCmd],
|
|
428
|
-
listname: str,
|
|
429
|
-
paramnames: list[str] | None = None,
|
|
430
|
-
) -> None:
|
|
431
|
-
if cmdlist is None:
|
|
432
|
-
raise ErrInfo("error", other_msg="Initiating a command list without any commands.")
|
|
433
|
-
self.listname = listname
|
|
434
|
-
self.cmdlist = cmdlist
|
|
435
|
-
self.cmdptr = 0
|
|
436
|
-
self.paramnames = paramnames
|
|
437
|
-
self.paramvals: SubVarSet | None = None
|
|
438
|
-
self.localvars = LocalSubVarSet()
|
|
439
|
-
self.init_if_level: int | None = None
|
|
440
|
-
|
|
441
|
-
def add(self, script_command: ScriptCmd) -> None:
|
|
442
|
-
"""Append *script_command* to the end of this command list."""
|
|
443
|
-
self.cmdlist.append(script_command)
|
|
444
|
-
|
|
445
|
-
def set_paramvals(self, paramvals: SubVarSet) -> None:
|
|
446
|
-
self.paramvals = paramvals
|
|
447
|
-
if self.paramnames is not None:
|
|
448
|
-
passed_paramnames = [p[0].lstrip("#") for p in paramvals.substitutions]
|
|
449
|
-
if not all(p in passed_paramnames for p in self.paramnames):
|
|
450
|
-
raise ErrInfo(
|
|
451
|
-
"error",
|
|
452
|
-
other_msg=f"Formal and actual parameter name mismatch in call to {self.listname}.",
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
def current_command(self) -> ScriptCmd | None:
|
|
456
|
-
"""Return the :class:`ScriptCmd` at the current cursor position, or ``None`` if exhausted."""
|
|
457
|
-
if self.cmdptr > len(self.cmdlist) - 1:
|
|
458
|
-
return None
|
|
459
|
-
return self.cmdlist[self.cmdptr]
|
|
460
|
-
|
|
461
|
-
def check_iflevels(self) -> None:
|
|
462
|
-
"""Warn if the IF-stack depth changed during execution of this command list."""
|
|
463
|
-
if_excess = len(_state.if_stack.if_levels) - self.init_if_level
|
|
464
|
-
if if_excess > 0:
|
|
465
|
-
sources = _state.if_stack.script_lines(if_excess)
|
|
466
|
-
src_msg = ", ".join([f"{src[0]} line {src[1]}" for src in sources])
|
|
467
|
-
from execsql.utils.errors import write_warning
|
|
468
|
-
|
|
469
|
-
write_warning(f"IF level mismatch at beginning and end of script; origin at or after: {src_msg}.")
|
|
470
|
-
|
|
471
|
-
def run_and_increment(self) -> None:
|
|
472
|
-
cmditem = self.cmdlist[self.cmdptr]
|
|
473
|
-
if _state.compiling_loop:
|
|
474
|
-
# Don't run this command, but save it or complete the loop.
|
|
475
|
-
if cmditem.command_type == "cmd" and _state.loop_rx.match(cmditem.command.statement):
|
|
476
|
-
_state.loop_nest_level += 1
|
|
477
|
-
# Substitute any deferred substitution variables with regular substitution var flags.
|
|
478
|
-
m = _state.defer_rx.findall(cmditem.command.statement)
|
|
479
|
-
if m is not None:
|
|
480
|
-
for dv in m:
|
|
481
|
-
rep = "!!" + dv[1] + "!!"
|
|
482
|
-
cmditem.command.statement = cmditem.command.statement.replace(dv[0], rep)
|
|
483
|
-
_state.loopcommandstack[-1].add(cmditem)
|
|
484
|
-
elif cmditem.command_type == "cmd" and _state.endloop_rx.match(cmditem.command.statement):
|
|
485
|
-
if _state.loop_nest_level == 0:
|
|
486
|
-
_state.endloop()
|
|
487
|
-
else:
|
|
488
|
-
_state.loop_nest_level -= 1
|
|
489
|
-
_state.loopcommandstack[-1].add(cmditem)
|
|
490
|
-
else:
|
|
491
|
-
_state.loopcommandstack[-1].add(cmditem)
|
|
492
|
-
else:
|
|
493
|
-
_state.last_command = cmditem
|
|
494
|
-
if cmditem.command_type == "sql" and _state.status.batch.in_batch():
|
|
495
|
-
_state.status.batch.using_db(_state.dbs.current())
|
|
496
|
-
_state.subvars.add_substitution("$CURRENT_TIME", datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
|
|
497
|
-
utcnow = datetime.datetime.now(tz=datetime.timezone.utc)
|
|
498
|
-
_state.subvars.add_substitution("$CURRENT_TIME_UTC", utcnow.strftime("%Y-%m-%d %H:%M"))
|
|
499
|
-
_state.subvars.add_substitution("$CURRENT_SCRIPT", cmditem.source)
|
|
500
|
-
_state.subvars.add_substitution(
|
|
501
|
-
"$CURRENT_SCRIPT_PATH",
|
|
502
|
-
cmditem.source_dir,
|
|
503
|
-
)
|
|
504
|
-
_state.subvars.add_substitution("$CURRENT_SCRIPT_NAME", cmditem.source_name)
|
|
505
|
-
_state.subvars.add_substitution("$CURRENT_SCRIPT_LINE", str(cmditem.line_no))
|
|
506
|
-
_state.subvars.add_substitution("$SCRIPT_LINE", str(cmditem.line_no))
|
|
507
|
-
if _state.step_mode:
|
|
508
|
-
_state.step_mode = False
|
|
509
|
-
from execsql.debug.repl import _debug_repl
|
|
510
|
-
|
|
511
|
-
_debug_repl(step=True)
|
|
512
|
-
_profiling = _state.profile_data is not None
|
|
513
|
-
if _profiling:
|
|
514
|
-
import time as _time
|
|
515
|
-
|
|
516
|
-
_t0 = _time.perf_counter()
|
|
517
|
-
cmditem.command.run(self.localvars.merge(self.paramvals), not _state.status.batch.in_batch())
|
|
518
|
-
if _profiling:
|
|
519
|
-
_elapsed = _time.perf_counter() - _t0
|
|
520
|
-
_state.profile_data.append(
|
|
521
|
-
(
|
|
522
|
-
cmditem.source,
|
|
523
|
-
cmditem.line_no,
|
|
524
|
-
cmditem.command_type,
|
|
525
|
-
_elapsed,
|
|
526
|
-
cmditem.command.commandline()[:100],
|
|
527
|
-
),
|
|
528
|
-
)
|
|
529
|
-
self.cmdptr += 1
|
|
530
|
-
|
|
531
|
-
def run_next(self) -> None:
|
|
532
|
-
"""Execute the command at the current cursor and advance; raise StopIteration when done."""
|
|
533
|
-
if self.cmdptr == 0:
|
|
534
|
-
self.init_if_level = len(_state.if_stack.if_levels)
|
|
535
|
-
if self.cmdptr > len(self.cmdlist) - 1:
|
|
536
|
-
self.check_iflevels()
|
|
537
|
-
raise StopIteration
|
|
538
|
-
self.run_and_increment()
|
|
539
|
-
|
|
540
|
-
def __iter__(self) -> Any:
|
|
541
|
-
return self
|
|
542
|
-
|
|
543
|
-
def __next__(self) -> ScriptCmd:
|
|
544
|
-
if self.cmdptr > len(self.cmdlist) - 1:
|
|
545
|
-
raise StopIteration
|
|
546
|
-
scriptcmd = self.cmdlist[self.cmdptr]
|
|
547
|
-
self.cmdptr += 1
|
|
548
|
-
return scriptcmd
|
|
549
|
-
|
|
550
|
-
|
|
551
336
|
# ---------------------------------------------------------------------------
|
|
552
337
|
# ScriptExecSpec
|
|
553
338
|
# ---------------------------------------------------------------------------
|
|
@@ -564,7 +349,7 @@ class ScriptExecSpec:
|
|
|
564
349
|
|
|
565
350
|
def __init__(self, **kwargs: Any) -> None:
|
|
566
351
|
self.script_id = kwargs["script_id"].lower()
|
|
567
|
-
if self.script_id not in _state.
|
|
352
|
+
if self.script_id not in _state.ast_scripts:
|
|
568
353
|
raise ErrInfo("cmd", other_msg=f"There is no SCRIPT named {self.script_id}.")
|
|
569
354
|
self.arg_exp = kwargs["argexp"]
|
|
570
355
|
self.looptype = kwargs["looptype"].upper() if "looptype" in kwargs and kwargs["looptype"] is not None else None
|
|
@@ -640,7 +425,7 @@ def set_dynamic_system_vars(ctx: Any = None) -> None:
|
|
|
640
425
|
_s.subvars.add_substitution("$CONSOLE_WAIT_WHEN_DONE_STATE", "ON" if _s.conf.gui_wait_on_exit else "OFF")
|
|
641
426
|
db = _s.dbs.current()
|
|
642
427
|
_s.subvars.add_substitution("$AUTOCOMMIT_STATE", "ON" if db.autocommit else "OFF")
|
|
643
|
-
# $CURRENT_TIME is set per-statement in
|
|
428
|
+
# $CURRENT_TIME is set per-statement in executor._set_command_vars() for accuracy.
|
|
644
429
|
_s.subvars.add_substitution("$TIMER", str(datetime.timedelta(seconds=_s.timer.elapsed())))
|
|
645
430
|
_s.subvars.clear_lazy_cache()
|
|
646
431
|
|
|
@@ -700,12 +485,13 @@ def substitute_vars(command_str: str, localvars: SubVarSet | None = None, ctx: A
|
|
|
700
485
|
|
|
701
486
|
|
|
702
487
|
def current_script_line() -> tuple:
|
|
703
|
-
"""Return ``(source_name, line_number)`` for the command currently executing.
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
488
|
+
"""Return ``(source_name, line_number)`` for the command currently executing.
|
|
489
|
+
|
|
490
|
+
Reads from ``_state.last_command``, which the AST executor updates on
|
|
491
|
+
every statement via the ``_FakeScriptCmd`` shim. Returns ``("", 0)``
|
|
492
|
+
when nothing has executed yet (e.g. during early initialization errors).
|
|
493
|
+
"""
|
|
494
|
+
last = getattr(_state, "last_command", None)
|
|
495
|
+
if last is None:
|
|
711
496
|
return ("", 0)
|
|
497
|
+
return (getattr(last, "source", ""), getattr(last, "line_no", 0))
|