execsql2 2.11.0__py3-none-any.whl → 2.12.0__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.
Files changed (37) hide show
  1. execsql/cli/__init__.py +6 -0
  2. execsql/cli/lint.py +1 -1
  3. execsql/cli/run.py +18 -12
  4. execsql/debug/__init__.py +6 -0
  5. execsql/debug/repl.py +472 -0
  6. execsql/exporters/xlsx.py +5 -0
  7. execsql/exporters/yaml.py +2 -0
  8. execsql/metacommands/__init__.py +1 -1
  9. execsql/metacommands/conditions.py +1 -1
  10. execsql/metacommands/control.py +4 -10
  11. execsql/metacommands/debug.py +1 -1
  12. execsql/metacommands/dispatch.py +1 -1
  13. execsql/script/engine.py +15 -5
  14. execsql/state.py +2 -2
  15. execsql/utils/gui.py +26 -4
  16. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/METADATA +5 -2
  17. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/RECORD +36 -35
  18. execsql/metacommands/debug_repl.py +0 -288
  19. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/README.md +0 -0
  20. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  21. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  22. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/execsql.conf +0 -0
  23. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  24. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  25. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  26. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  27. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  28. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  29. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  30. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/script_template.sql +0 -0
  31. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  32. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  33. {execsql2-2.11.0.data → execsql2-2.12.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  34. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/WHEEL +0 -0
  35. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/entry_points.txt +0 -0
  36. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/licenses/LICENSE.txt +0 -0
  37. {execsql2-2.11.0.dist-info → execsql2-2.12.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,5 +1,4 @@
1
1
  from __future__ import annotations
2
- from execsql.exceptions import ErrInfo
3
2
 
4
3
  """
5
4
  Control-flow metacommand handlers for execsql.
@@ -18,6 +17,8 @@ Implements the imperative ``x_*`` functions for script flow control:
18
17
  """
19
18
 
20
19
  import time
20
+
21
+ from execsql.exceptions import ErrInfo
21
22
  from typing import Any
22
23
 
23
24
  import execsql.state as _state
@@ -62,7 +63,8 @@ def x_assert(**kwargs: Any) -> None:
62
63
 
63
64
  result = _state.xcmd_test(condition)
64
65
  if result:
65
- _state.exec_log.log_user_msg(f"ASSERT passed: {condition}")
66
+ if _state.exec_log is not None:
67
+ _state.exec_log.log_user_msg(f"ASSERT passed: {condition}")
66
68
  else:
67
69
  raise ErrInfo(type="cmd", other_msg=message)
68
70
 
@@ -134,14 +136,6 @@ def x_loop(**kwargs: Any) -> None:
134
136
  )
135
137
 
136
138
 
137
- def endloop() -> None:
138
- if len(_state.loopcommandstack) == 0:
139
- raise ErrInfo("error", other_msg="END LOOP metacommand without a matching preceding LOOP metacommand.")
140
- _state.compiling_loop = False
141
- _state.commandliststack.append(_state.loopcommandstack[-1])
142
- _state.loopcommandstack.pop()
143
-
144
-
145
139
  def x_halt(**kwargs: Any) -> None:
146
140
  errmsg = kwargs["errmsg"]
147
141
  tee = kwargs["tee"]
@@ -74,7 +74,7 @@ def x_debug_log_subvars(**kwargs: Any) -> None:
74
74
  local = kwargs["local"]
75
75
  user = kwargs["user"]
76
76
  for s in _state.commandliststack[-1].localvars.substitutions:
77
- _state.exec_log.log_status_info(f"Substitution [{s}] = [{s}]")
77
+ _state.exec_log.log_status_info(f"Substitution [{s[0]}] = [{s[1]}]")
78
78
  if local is None:
79
79
  for s in _state.subvars.substitutions:
80
80
  if user is None or s[0][0].isalnum() or s[0][0] == "_":
@@ -100,7 +100,7 @@ from execsql.metacommands.debug import (
100
100
  x_debug_write_odbc_drivers,
101
101
  x_debug_write_subvars,
102
102
  )
103
- from execsql.metacommands.debug_repl import x_breakpoint
103
+ from execsql.debug.repl import x_breakpoint
104
104
  from execsql.metacommands.io import (
105
105
  x_cd,
106
106
  x_copy,
execsql/script/engine.py CHANGED
@@ -194,7 +194,7 @@ class MetaCommandList:
194
194
  patterns; each compiles into a separate :class:`MetaCommand` prepended to
195
195
  the dispatch list so that later registrations take priority.
196
196
  """
197
- if type(matching_regexes) in (tuple, list):
197
+ if isinstance(matching_regexes, (tuple, list)):
198
198
  raw_patterns = list(matching_regexes)
199
199
  regexes = [re.compile(rx, re.I) for rx in raw_patterns]
200
200
  else:
@@ -305,9 +305,14 @@ class SqlStmt:
305
305
  except Exception:
306
306
  e = ErrInfo(type="exception", exception_msg=exception_desc())
307
307
  if e:
308
+ from execsql.utils.errors import stamp_errinfo
309
+
310
+ stamp_errinfo(e)
308
311
  _state.subvars.add_substitution("$LAST_ERROR", cmd)
309
- _state.subvars.add_substitution("$ERROR_MESSAGE", str(e))
312
+ _state.subvars.add_substitution("$ERROR_MESSAGE", e.errmsg())
310
313
  _state.status.sql_error = True
314
+ if _state.exec_log is not None:
315
+ _state.exec_log.log_status_info(f"SQL error: {e.errmsg()}")
311
316
  if _state.status.halt_on_err:
312
317
  from execsql.utils.errors import exit_now
313
318
 
@@ -349,9 +354,14 @@ class MetacommandStmt:
349
354
  except Exception:
350
355
  e = ErrInfo(type="exception", exception_msg=exception_desc())
351
356
  if e:
357
+ from execsql.utils.errors import stamp_errinfo
358
+
359
+ stamp_errinfo(e)
352
360
  _state.status.metacommand_error = True
353
361
  _state.subvars.add_substitution("$LAST_ERROR", cmd)
354
- _state.subvars.add_substitution("$ERROR_MESSAGE", str(e))
362
+ _state.subvars.add_substitution("$ERROR_MESSAGE", e.errmsg())
363
+ if _state.exec_log is not None:
364
+ _state.exec_log.log_status_info(f"Metacommand error: {e.errmsg()}")
355
365
  if _state.status.halt_on_metacommand_err:
356
366
  # Re-raise the original ErrInfo so its message is preserved, not
357
367
  # replaced with the generic "Unknown metacommand" text.
@@ -495,9 +505,9 @@ class CommandList:
495
505
  _state.subvars.add_substitution("$SCRIPT_LINE", str(cmditem.line_no))
496
506
  if _state.step_mode:
497
507
  _state.step_mode = False
498
- from execsql.metacommands.debug_repl import _debug_repl
508
+ from execsql.debug.repl import _debug_repl
499
509
 
500
- _debug_repl()
510
+ _debug_repl(step=True)
501
511
  _profiling = _state.profile_data is not None
502
512
  if _profiling:
503
513
  import time as _time
execsql/state.py CHANGED
@@ -336,8 +336,7 @@ class _StateModule(types.ModuleType):
336
336
  # via setattr (local) or delattr (non-local). Since context
337
337
  # attrs live on _ctx, not __dict__, patch takes the delattr
338
338
  # path. We reset to the default rather than truly deleting.
339
- _defaults = RuntimeContext()
340
- setattr(self.__dict__["_ctx"], name, getattr(_defaults, name))
339
+ setattr(self.__dict__["_ctx"], name, getattr(_DEFAULT_CTX, name))
341
340
  else:
342
341
  super().__delattr__(name)
343
342
 
@@ -468,4 +467,5 @@ def initialize(
468
467
  # ---------------------------------------------------------------------------
469
468
 
470
469
  _ctx = RuntimeContext()
470
+ _DEFAULT_CTX = RuntimeContext() # Cached defaults for __delattr__ reset
471
471
  sys.modules[__name__].__class__ = _StateModule
execsql/utils/gui.py CHANGED
@@ -358,13 +358,35 @@ def gui_console_wait_user(message: str = "") -> None:
358
358
  print(message, file=sys.stderr)
359
359
 
360
360
 
361
- def gui_console_width() -> int:
362
- """Return the current console width in characters."""
361
+ def gui_console_width(width: int | None = None) -> int:
362
+ """Get or set the console width in characters.
363
+
364
+ When *width* is provided, updates the active GUI console (if any).
365
+ Always returns the current width.
366
+ """
367
+ global _console_width
368
+ if width is not None:
369
+ import execsql.state as _state
370
+
371
+ _console_width = int(width)
372
+ if _state.gui_console is not None and hasattr(_state.gui_console, "set_width"):
373
+ _state.gui_console.set_width(_console_width)
363
374
  return _console_width
364
375
 
365
376
 
366
- def gui_console_height() -> int:
367
- """Return the current console height in lines."""
377
+ def gui_console_height(height: int | None = None) -> int:
378
+ """Get or set the console height in lines.
379
+
380
+ When *height* is provided, updates the active GUI console (if any).
381
+ Always returns the current height.
382
+ """
383
+ global _console_height
384
+ if height is not None:
385
+ import execsql.state as _state
386
+
387
+ _console_height = int(height)
388
+ if _state.gui_console is not None and hasattr(_state.gui_console, "set_height"):
389
+ _state.gui_console.set_height(_console_height)
368
390
  return _console_height
369
391
 
370
392
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.11.0
3
+ Version: 2.12.0
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Repository, https://github.com/geocoug/execsql
6
6
  Project-URL: Issues, https://github.com/geocoug/execsql/issues
@@ -220,8 +220,11 @@ execsql script.sql # read connection from config file
220
220
  | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
221
221
  | `-w` | Skip password prompt when a username is supplied |
222
222
  | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
223
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
223
224
  | `--dry-run` | Parse the script and report commands without executing |
224
225
  | `--lint` | Static analysis: check structure and warn on issues (no DB) |
226
+ | `--ping` | Test database connectivity and exit |
227
+ | `--profile` | Show per-statement timing summary after execution |
225
228
  | `--progress` | Show a progress bar for long-running IMPORT operations |
226
229
  | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
227
230
  | `--dump-keywords` | Print metacommand keywords as JSON and exit |
@@ -298,7 +301,7 @@ execsql-format --check scripts/
298
301
  ```yaml
299
302
  repos:
300
303
  - repo: https://github.com/geocoug/execsql
301
- rev: v2.4.4
304
+ rev: v2.11.0
302
305
  hooks:
303
306
  - id: execsql-format
304
307
  args: [--in-place]
@@ -7,13 +7,13 @@ execsql/format.py,sha256=-6iknDddqbkapMo4NKmT5LAynDLqMW5kHgDWRg0KSws,11990
7
7
  execsql/models.py,sha256=DxkGp9iWbuZDWPGmnxZp9mvEeyOwxEJNx94fxQQiLfQ,13538
8
8
  execsql/parser.py,sha256=mbNSMiAMR1NvNvFtQAZq6nxBOupMGJZXSimLWLtZeNs,15537
9
9
  execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- execsql/state.py,sha256=ovUQOr78R3LzsDUzBLL9Bq9ZdHiiFVlBvCNzilQ-K-s,15055
10
+ execsql/state.py,sha256=29b3SwG4GirED2KVQc-cCC7Z_-FsGN3fEt9xQNN-Puo,15090
11
11
  execsql/types.py,sha256=HVWb4umIB9lpxCGgqk3xy1hoGYPfN39xci5mHF0Izq4,31882
12
- execsql/cli/__init__.py,sha256=dPKq7KOY7soD1GfBEztoRWcKuDLY5QyhzgC6PuyeyII,16270
12
+ execsql/cli/__init__.py,sha256=YXxOVF2lNkCkifXyjoC7yWrhHJFT9PzI7cnCzsLJwT8,16488
13
13
  execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
14
14
  execsql/cli/help.py,sha256=Sn_TgSJiQeBx-xZH0fuP5OvR_wasSTumjWF9UHfIX5k,5414
15
- execsql/cli/lint.py,sha256=KluYROdjGJNUrVbO3cJym-H296zbih4no-F10HF0P4U,16165
16
- execsql/cli/run.py,sha256=p4QWuBO7WvMPAJDyLtwiVZAdpwjgq1H3wz__WgUiRKA,30218
15
+ execsql/cli/lint.py,sha256=XWuVcEsheZ8ql48VFWqICWEkAUezB2nIePX6SUiKSg8,16109
16
+ execsql/cli/run.py,sha256=JGfndnBnJMkEqbz26pflhEdXDScZNIdGu6b6jTRLYl8,30681
17
17
  execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
18
18
  execsql/db/access.py,sha256=L79gUnAnnM9EJ_f4k42jr7DI0qGcKtLOnJTlBC7uPm0,17879
19
19
  execsql/db/base.py,sha256=hfMFj8fXY0T1aXLvWJHqb0aU4EQUDFOc-YrS29HH8U4,30405
@@ -26,6 +26,8 @@ execsql/db/oracle.py,sha256=AFVHhGlCzBuU7JgrAqeUG6e8TUUkk1Y80XVJQnGOqLM,10547
26
26
  execsql/db/postgres.py,sha256=oXR7ODzQhR3yO6q-aNa9_il_rO3SpOX9yYGsfIqHwLI,20139
27
27
  execsql/db/sqlite.py,sha256=2fD3AfckIGWN1oHcOaqQlQnbig26top1IlW-ejPHttI,10204
28
28
  execsql/db/sqlserver.py,sha256=mNwmIIxTzqXU-cOjpNpeFi568HjQHsAk8Xnn-tR6F_E,7563
29
+ execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
30
+ execsql/debug/repl.py,sha256=HeQ9emFKUjo7UTouxuTcmpGCTJIR1nOLxKkRJ5mvd2c,16669
29
31
  execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
30
32
  execsql/exporters/base.py,sha256=W9USFyk_2eztjJ51X6CJh7-chE1i3cSx-STOtbHXCNI,6373
31
33
  execsql/exporters/delimited.py,sha256=zMvurTRVl5W-6N8DuYtn_xILUkYLMlfflwWMfvdeaF0,30304
@@ -44,9 +46,9 @@ execsql/exporters/sqlite.py,sha256=XA0ALLvy-r6Pz1lpOFkWWbvpSP9Hm1tHHiuo_BvPVDk,2
44
46
  execsql/exporters/templates.py,sha256=T9nk7vJrlxiPGfOWGc79xqqDxK3TCYu0wXq48U02npw,5564
45
47
  execsql/exporters/values.py,sha256=HIyud31aux_dbCphfKHEGeZB9fkIPE5PoGXQz817XIE,2520
46
48
  execsql/exporters/xls.py,sha256=nPROgxL8XK2oiBVoqN2L-o0j_jynRIMokwB8NpvOBt0,10623
47
- execsql/exporters/xlsx.py,sha256=xXTFIKkvJnNOsFdnhSYEkJa4ulTrtq9tIRk6SSchqA0,11299
49
+ execsql/exporters/xlsx.py,sha256=Gm8ns_KeqSMu2DONSSJ1DcwPBEjYwpbU7frmX0g5L7c,11487
48
50
  execsql/exporters/xml.py,sha256=lqcOM8uKDoCayU42BPSLNH1_2DIHU5D3LtQItREU90c,2564
49
- execsql/exporters/yaml.py,sha256=0bwLDU3Fy00yMryBOSBSptbjV8Re6Ks-b62DObFNP4o,3062
51
+ execsql/exporters/yaml.py,sha256=1Vuc6uMDuLTkCuXCfXWKz4gLkkAVdEXkLs4gEB_67Xo,3110
50
52
  execsql/exporters/zip.py,sha256=9-hExltQorONNThiMfxPDYHqHsbTeq9zM9zmtG4oFb8,4410
51
53
  execsql/gui/__init__.py,sha256=oCb-cyhLZzVpWJ4WU5HbqEDBrV-lm0ytEwxemrOZyqs,2048
52
54
  execsql/gui/base.py,sha256=sfNRkDrf7FhIgMRUOdyZpRLS1Xk9RqNhrV0A1RP6PXU,6068
@@ -59,14 +61,13 @@ execsql/importers/csv.py,sha256=Mu848WNzuhVO1ade-WurPyxqGOuVNRO8UwRF3-bav_I,4845
59
61
  execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
60
62
  execsql/importers/ods.py,sha256=MJsdsjropzCvxAA3DDZfAL_AnmZ4yij7DnrjGyDJqHQ,2843
61
63
  execsql/importers/xls.py,sha256=e0Zfe47ZiCpA1Ae3XDJ1ko3sCiH3-8U6XLKi6NvD0jQ,3683
62
- execsql/metacommands/__init__.py,sha256=TT1ARHgHltHqZ7qx4Y62o1h_GOPvUztZKCem-wAE560,11215
63
- execsql/metacommands/conditions.py,sha256=QUpevCHce9kbKpt6XpHkc73q3bYAIfaBin0b6eaJnYQ,29259
64
+ execsql/metacommands/__init__.py,sha256=EmYUZZq1oaubbSQ26-8F9jJI_JnOJ2R697NeossXF1Q,11202
65
+ execsql/metacommands/conditions.py,sha256=Fzrk83-pWbFOoKahYdQW7CZjQeh3zByDUbfgpTM_bjQ,29259
64
66
  execsql/metacommands/connect.py,sha256=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
65
- execsql/metacommands/control.py,sha256=CBCg0ZKSR-BGejBW5cXwk6aJ9VrYBzCg9C40ofi8qi8,8776
67
+ execsql/metacommands/control.py,sha256=xNHyTrYUM042OgDarNq7HxslY7JuQs-KOKcb-fHUngM,8510
66
68
  execsql/metacommands/data.py,sha256=tRQBGTAuW-eJ2tBNWaoZI9OjTyNNyHJISo7gOdL-sm8,11370
67
- execsql/metacommands/debug.py,sha256=nmfQ2ijUbTQO3drnyV9EzFueGSTfMl-CddP_NlQyI14,8178
68
- execsql/metacommands/debug_repl.py,sha256=TA4aDWgl_wvR63R9ddBIF-vUB2wRamsxdxzeM8F3WZo,9805
69
- execsql/metacommands/dispatch.py,sha256=1Mae6yqrea6wViFLBsvVt33Zgx4xP8tnhOuB_aQC89c,84054
69
+ execsql/metacommands/debug.py,sha256=pnT24dfvfOx8xFu86mO5czfVCGKbcvgBLyXnqaMWO4w,8184
70
+ execsql/metacommands/dispatch.py,sha256=OQwLOo9XT3N9C96wsRt0zmu1Nn4HL-7cSBOsGCfp5V4,84041
70
71
  execsql/metacommands/io.py,sha256=Duh60caM4go9JczbGYNMKKYpcMimwPzF6EQ_tshKxdE,2971
71
72
  execsql/metacommands/io_export.py,sha256=7lkCSnPhXy9FVau9_hT1u68NOVdG2DsWmvUh9hM1QWI,18359
72
73
  execsql/metacommands/io_fileops.py,sha256=RKqbWPTYiwiqCZYG-lpih0w1JVOY4RBFdWr3BJb_pnY,9669
@@ -77,7 +78,7 @@ execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHN
77
78
  execsql/metacommands/system.py,sha256=sUR5kLL7idTVg8WXIMdd-Kv7nkERIiaeL0beWsz8NyY,7293
78
79
  execsql/script/__init__.py,sha256=pIo0EJ7-vg67rSMbOvbri_BOUgLoGoSEUfJgxUN7ZS0,3380
79
80
  execsql/script/control.py,sha256=s-1eZdGARM6H1FwZ6VDdO_f50j7bvvRtTHesfUm9tbc,6144
80
- execsql/script/engine.py,sha256=PqR8_5CUoHu1Wm8uLfudGN08h_vWD_3diYzlKhuqdgU,40633
81
+ execsql/script/engine.py,sha256=NJk4Is7Lp2Ov6zO8NNOOU8ACfmIFs2dD7PdhvJn6nNQ,41083
81
82
  execsql/script/variables.py,sha256=MOT9XEHucpuuuHQZM5bklxGMBQcwHzwTBxd0q3aO0XY,11641
82
83
  execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
83
84
  execsql/utils/auth.py,sha256=onXzNkNZQZxGC5w7eey06sjvAIAX_Lf9g7nUJtcsel0,7009
@@ -85,30 +86,30 @@ execsql/utils/crypto.py,sha256=2OnBWwn9bCBGc1ZkyRv16TvhottoCNYtXqgbE3mG3Sg,2960
85
86
  execsql/utils/datetime.py,sha256=V_itd5vVvUPjT86P_z_hh4mlerMDGhDzI5MwPMDBaI4,7715
86
87
  execsql/utils/errors.py,sha256=YKhYD27-3timuZavc2vIrRIfHa71vzih-KVPsAKgvkU,8163
87
88
  execsql/utils/fileio.py,sha256=F6M4osE0Mb2ycTcvwwcYnhBXH1L36v6d7Oxdab6J16s,24110
88
- execsql/utils/gui.py,sha256=kCfHvqY60jdoJa1k5HlxhrmzM4HPp7uajV6nxz3_Qh4,17583
89
+ execsql/utils/gui.py,sha256=UFtwrXPNqNvZCJFCbumZ1aG2d9B-vyaJXIG83fDHteo,18409
89
90
  execsql/utils/mail.py,sha256=75A1cMnEfyP0_QMMWuSLv8hrcIjc9cGBCrttLpr2TXQ,5372
90
91
  execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
91
92
  execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
92
93
  execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
93
94
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
94
- execsql2-2.11.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
95
- execsql2-2.11.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
96
- execsql2-2.11.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
97
- execsql2-2.11.0.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
98
- execsql2-2.11.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
99
- execsql2-2.11.0.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
100
- execsql2-2.11.0.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
101
- execsql2-2.11.0.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
102
- execsql2-2.11.0.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
103
- execsql2-2.11.0.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
104
- execsql2-2.11.0.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
105
- execsql2-2.11.0.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
106
- execsql2-2.11.0.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
107
- execsql2-2.11.0.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
108
- execsql2-2.11.0.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
109
- execsql2-2.11.0.dist-info/METADATA,sha256=Cmn0y2Xx2ZC-S3lsADP1gSyoCCfAsUGj8wlbyVJ6e8c,17062
110
- execsql2-2.11.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
111
- execsql2-2.11.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
112
- execsql2-2.11.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
113
- execsql2-2.11.0.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
114
- execsql2-2.11.0.dist-info/RECORD,,
95
+ execsql2-2.12.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
96
+ execsql2-2.12.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
97
+ execsql2-2.12.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
98
+ execsql2-2.12.0.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
99
+ execsql2-2.12.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
100
+ execsql2-2.12.0.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
101
+ execsql2-2.12.0.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
102
+ execsql2-2.12.0.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
103
+ execsql2-2.12.0.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
104
+ execsql2-2.12.0.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
105
+ execsql2-2.12.0.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
106
+ execsql2-2.12.0.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
107
+ execsql2-2.12.0.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
108
+ execsql2-2.12.0.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
109
+ execsql2-2.12.0.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
110
+ execsql2-2.12.0.dist-info/METADATA,sha256=l6mKQU4tPA9vkX6ZlUWNFRbou5rw5KPLML8V3urrV10,17381
111
+ execsql2-2.12.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
112
+ execsql2-2.12.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
113
+ execsql2-2.12.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
114
+ execsql2-2.12.0.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
115
+ execsql2-2.12.0.dist-info/RECORD,,
@@ -1,288 +0,0 @@
1
- from __future__ import annotations
2
-
3
- """
4
- Interactive debug REPL metacommand handler for execsql.
5
-
6
- Implements ``x_breakpoint`` — the ``BREAKPOINT`` metacommand — which pauses
7
- script execution and drops into an interactive read-eval-print loop.
8
-
9
- The REPL allows the user to:
10
-
11
- - Inspect and print substitution variables.
12
- - Run ad-hoc SQL queries against the current database.
13
- - Step through the script one statement at a time.
14
- - Resume or abort execution.
15
-
16
- All REPL commands are dot-prefixed (``.continue``, ``.vars``, ``.next``)
17
- to avoid ambiguity with variable names and SQL. Anything not starting
18
- with ``.`` is treated as either a variable lookup (if it matches a known
19
- variable) or SQL (if it ends with ``;``).
20
-
21
- In non-interactive environments (CI, piped input, ``sys.stdin.isatty()`` is
22
- ``False``) the metacommand is silently skipped so automated pipelines are not
23
- blocked.
24
- """
25
-
26
- import sys
27
- from typing import Any
28
-
29
- import execsql.state as _state
30
-
31
- __all__ = ["x_breakpoint"]
32
-
33
- # ---------------------------------------------------------------------------
34
- # Public handler
35
- # ---------------------------------------------------------------------------
36
-
37
- _HELP_TEXT = """\
38
- execsql debug REPL — all commands start with '.'
39
-
40
- .continue .c Resume script execution
41
- .abort .q Halt the script (exit 1)
42
- .vars List user, system, local, and counter variables
43
- .vars all Include environment variables (&) in the listing
44
- .next .n Execute the next statement then pause again (step mode)
45
- .stack Show the command-list stack (script name, line, depth)
46
- .help Show this help text
47
-
48
- Everything else:
49
- varname Print a variable's value (e.g. logfile, $ARG_1, &HOME)
50
- SELECT ...; Run ad-hoc SQL against the current database
51
- """
52
-
53
-
54
- def x_breakpoint(**kwargs: Any) -> None:
55
- """Pause execution and enter the interactive debug REPL.
56
-
57
- If ``sys.stdin`` is not a TTY (CI, piped input), the metacommand is
58
- silently skipped — scripts will not hang in automation.
59
-
60
- Args:
61
- **kwargs: Keyword arguments injected by the dispatch table (unused).
62
- """
63
- if not sys.stdin.isatty():
64
- return
65
- _debug_repl()
66
-
67
-
68
- # ---------------------------------------------------------------------------
69
- # REPL core
70
- # ---------------------------------------------------------------------------
71
-
72
-
73
- def _debug_repl() -> None:
74
- """Interactive read-eval-print loop for script debugging.
75
-
76
- Reads commands from stdin until the user types ``.continue`` or ``.abort``,
77
- or until EOF / KeyboardInterrupt.
78
- """
79
- try:
80
- import readline as _readline # noqa: F401 — side-effect: enables history/arrow keys
81
- except ImportError:
82
- pass # readline not available on Windows; continue without it
83
-
84
- _write("\n[Breakpoint] Script paused. Type '.help' for commands, '.c' to resume.\n")
85
-
86
- while True:
87
- try:
88
- line = input("execsql debug> ").strip()
89
- except EOFError:
90
- _write("\n")
91
- return # Ctrl-D → continue
92
- except KeyboardInterrupt:
93
- _write("\n")
94
- return # Ctrl-C → continue
95
-
96
- if not line:
97
- continue
98
-
99
- # Dot-prefixed → REPL command
100
- if line.startswith("."):
101
- _handle_dot_command(line)
102
- if line.lower().lstrip(".") in ("continue", "c"):
103
- return
104
- if line.lower().lstrip(".") in ("abort", "q", "quit"):
105
- # _handle_dot_command already raised SystemExit, but guard anyway
106
- return
107
- if line.lower().lstrip(".") in ("next", "n"):
108
- return
109
- continue
110
-
111
- # SQL (ends with semicolon)
112
- if line.rstrip().endswith(";"):
113
- _run_sql(line)
114
- continue
115
-
116
- # Everything else → variable lookup
117
- _print_var(line)
118
-
119
-
120
- def _handle_dot_command(line: str) -> None:
121
- """Dispatch a dot-prefixed REPL command."""
122
- # Strip the leading dot and normalize
123
- cmd = line[1:].strip().lower()
124
-
125
- if cmd in ("continue", "c"):
126
- return # caller checks and returns from _debug_repl
127
- elif cmd in ("abort", "q", "quit"):
128
- raise SystemExit(1)
129
- elif cmd == "help":
130
- _write(_HELP_TEXT)
131
- elif cmd == "vars all":
132
- _print_all_vars(include_env=True)
133
- elif cmd == "vars":
134
- _print_all_vars()
135
- elif cmd == "stack":
136
- _print_stack()
137
- elif cmd in ("next", "n"):
138
- _enable_step_mode()
139
- else:
140
- _write(f" Unknown command: {line!r}. Type '.help' for available commands.\n")
141
-
142
-
143
- # ---------------------------------------------------------------------------
144
- # REPL command implementations
145
- # ---------------------------------------------------------------------------
146
-
147
-
148
- def _write(text: str) -> None:
149
- """Write *text* to the execsql output stream (falls back to stdout)."""
150
- output = _state.output
151
- if output is not None:
152
- output.write(text)
153
- else:
154
- sys.stdout.write(text)
155
- sys.stdout.flush()
156
-
157
-
158
- def _print_all_vars(*, include_env: bool = False) -> None:
159
- """Print substitution variables grouped by type."""
160
- subvars = _state.subvars
161
- if subvars is None:
162
- _write(" (no substitution variables defined)\n")
163
- return
164
- items = subvars.substitutions # list of (name, value) tuples
165
- if not items:
166
- _write(" (no substitution variables defined)\n")
167
- return
168
-
169
- # Group by prefix.
170
- user_vars: list[tuple[str, str]] = []
171
- system_vars: list[tuple[str, str]] = []
172
- counter_vars: list[tuple[str, str]] = []
173
- local_vars: list[tuple[str, str]] = []
174
- env_vars: list[tuple[str, str]] = []
175
-
176
- for name, value in sorted(items):
177
- if name.startswith("&"):
178
- env_vars.append((name, value))
179
- elif name.startswith("~"):
180
- local_vars.append((name, value))
181
- elif name.startswith("@"):
182
- counter_vars.append((name, value))
183
- elif name.startswith("$"):
184
- system_vars.append((name, value))
185
- else:
186
- user_vars.append((name, value))
187
-
188
- def _print_group(label: str, group: list[tuple[str, str]]) -> None:
189
- if not group:
190
- return
191
- _write(f" {label}:\n")
192
- max_name = max(len(n) for n, _ in group)
193
- for name, value in group:
194
- _write(f" {name:<{max_name}} = {value}\n")
195
-
196
- _print_group("User variables", user_vars)
197
- _print_group("System variables ($)", system_vars)
198
- _print_group("Local variables (~)", local_vars)
199
- _print_group("Counter variables (@)", counter_vars)
200
- if include_env:
201
- _print_group("Environment variables (&)", env_vars)
202
-
203
- if not any([user_vars, system_vars, local_vars, counter_vars]):
204
- if env_vars:
205
- _write(" (no script variables defined — use '.vars all' to see environment variables)\n")
206
- else:
207
- _write(" (no variables defined)\n")
208
-
209
-
210
- def _print_var(varname: str) -> None:
211
- """Print the value of a single substitution variable.
212
-
213
- Tries the name as typed, then with the sigil prefix stripped.
214
- """
215
- subvars = _state.subvars
216
- if subvars is None:
217
- _write(f" {varname}: (substitution variables not initialised)\n")
218
- return
219
- # Try the name as typed first, then without the sigil prefix ($, &, @, #, ~).
220
- # SUB creates variables without a prefix (e.g., "logfile"), but users
221
- # may type "$logfile" at the prompt.
222
- value = subvars.varvalue(varname)
223
- if value is None and len(varname) > 1 and varname[0] in "$&@#~":
224
- value = subvars.varvalue(varname[1:])
225
- if value is None:
226
- _write(f" {varname}: (undefined)\n")
227
- else:
228
- _write(f" {varname} = {value}\n")
229
-
230
-
231
- def _print_stack() -> None:
232
- """Print the current command-list stack (script name, line number, depth)."""
233
- stack = _state.commandliststack
234
- if not stack:
235
- _write(" (command list stack is empty)\n")
236
- return
237
- _write(f" Stack depth: {len(stack)}\n")
238
- for depth, cmdlist in enumerate(stack):
239
- listname = getattr(cmdlist, "listname", "<unknown>")
240
- cmdptr = getattr(cmdlist, "cmdptr", 0)
241
- _write(f" [{depth}] {listname} (cursor at index {cmdptr})\n")
242
-
243
-
244
- def _run_sql(sql: str) -> None:
245
- """Execute ad-hoc SQL against the current database and pretty-print the results."""
246
- dbs = _state.dbs
247
- if dbs is None:
248
- _write(" (no database connection is active)\n")
249
- return
250
- db = dbs.current()
251
- if db is None:
252
- _write(" (no database connection is active)\n")
253
- return
254
- try:
255
- colnames, rows = db.select_data(sql)
256
- except Exception as exc:
257
- _write(f" SQL error: {exc}\n")
258
- return
259
-
260
- if not colnames:
261
- _write(" (query returned no columns)\n")
262
- return
263
-
264
- # Build a simple text table.
265
- col_widths = [len(c) for c in colnames]
266
- str_rows: list[list[str]] = []
267
- for row in rows:
268
- str_row = [str(v) if v is not None else "NULL" for v in row]
269
- str_rows.append(str_row)
270
- for i, cell in enumerate(str_row):
271
- col_widths[i] = max(col_widths[i], len(cell))
272
-
273
- sep = "+-" + "-+-".join("-" * w for w in col_widths) + "-+"
274
- header = "| " + " | ".join(c.ljust(col_widths[i]) for i, c in enumerate(colnames)) + " |"
275
- _write(sep + "\n")
276
- _write(header + "\n")
277
- _write(sep + "\n")
278
- for str_row in str_rows:
279
- data_line = "| " + " | ".join(cell.ljust(col_widths[i]) for i, cell in enumerate(str_row)) + " |"
280
- _write(data_line + "\n")
281
- _write(sep + "\n")
282
- row_word = "row" if len(str_rows) == 1 else "rows"
283
- _write(f" ({len(str_rows)} {row_word})\n")
284
-
285
-
286
- def _enable_step_mode() -> None:
287
- """Activate step mode so the engine re-enters the REPL after the next statement."""
288
- _state.step_mode = True