execsql2 2.13.1__py3-none-any.whl → 2.13.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.
Files changed (22) hide show
  1. execsql/cli/lint.py +277 -92
  2. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/METADATA +1 -1
  3. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/RECORD +22 -22
  4. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/README.md +0 -0
  5. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  6. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  7. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/execsql.conf +0 -0
  8. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
  9. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/md_compare.sql +0 -0
  10. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
  11. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
  12. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
  13. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  14. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  15. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/script_template.sql +0 -0
  16. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
  17. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  18. {execsql2-2.13.1.data → execsql2-2.13.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  19. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/WHEEL +0 -0
  20. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/entry_points.txt +0 -0
  21. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/licenses/LICENSE.txt +0 -0
  22. {execsql2-2.13.1.dist-info → execsql2-2.13.2.dist-info}/licenses/NOTICE +0 -0
execsql/cli/lint.py CHANGED
@@ -10,11 +10,18 @@ Checks performed
10
10
  2. **Unmatched LOOP / END LOOP** — mismatched nesting depth (error).
11
11
  3. **Unmatched BEGIN BATCH / END BATCH** — mismatched nesting depth (error).
12
12
  4. **Potentially undefined variables** — ``!!$VAR!!`` tokens not preceded by a
13
- ``SUB`` metacommand in the same parsed command list and not in the set of
14
- built-in variables (warning).
15
- 5. **Missing INCLUDE files** INCLUDE target does not exist on disk relative
13
+ ``SUB`` (or ``SUB_EMPTY``, ``SUB_ADD``, ``SUB_APPEND``, ``SUBDATA``)
14
+ metacommand in the same parsed command list and not in the set of built-in
15
+ variables (warning). Note: ``SUB_INI`` and ``SELECT_SUB`` define variables
16
+ whose names are not statically knowable — those may produce false-positive
17
+ warnings.
18
+ 5. **EXECUTE SCRIPT flow analysis** — when an ``EXECUTE SCRIPT <name>``
19
+ metacommand is encountered, the linter descends into the named script
20
+ block (if found in ``_state.savedscripts``) and merges any variables it
21
+ defines back into the caller's scope.
22
+ 6. **Missing INCLUDE files** — INCLUDE target does not exist on disk relative
16
23
  to the script directory (warning).
17
- 6. **Empty script** — no commands found (warning).
24
+ 7. **Empty script** — no commands found (warning).
18
25
 
19
26
  The function walks ``CommandList.cmdlist`` and also descends into any
20
27
  ``CommandList`` objects stored in ``_state.savedscripts`` (i.e. named scripts
@@ -67,6 +74,31 @@ _RX_END_BATCH = re.compile(r"^\s*END\s+BATCH\s*$", re.I)
67
74
  # SUB <varname> <value> — defines a substitution variable
68
75
  _RX_SUB = re.compile(r"^\s*SUB\s+(?P<name>[+~]?\w+)\s+", re.I)
69
76
 
77
+ # SUB_EMPTY <varname> — defines a variable with empty string
78
+ _RX_SUB_EMPTY = re.compile(r"^\s*SUB_EMPTY\s+(?P<name>[+~]?\w+)\s*$", re.I)
79
+
80
+ # SUB_ADD <varname> <expr> — increments a variable (implies it exists)
81
+ _RX_SUB_ADD = re.compile(r"^\s*SUB_ADD\s+(?P<name>[+~]?\w+)\s+", re.I)
82
+
83
+ # SUB_APPEND <varname> <text> — appends to a variable (implies it exists)
84
+ _RX_SUB_APPEND = re.compile(r"^\s*SUB_APPEND\s+(?P<name>[+~]?\w+)\s", re.I)
85
+
86
+ # SUBDATA <varname> <datasource> — defines a variable from a query result
87
+ _RX_SUBDATA = re.compile(r"^\s*SUBDATA\s+(?P<name>[+~]?\w+)\s+", re.I)
88
+
89
+ # SUB_INI [FILE] <filename> [SECTION] <section> — bulk-defines variables from INI file
90
+ _RX_SUB_INI = re.compile(
91
+ r'^\s*SUB_INI\s+(?:FILE\s+)?(?:"(?P<qfile>[^"]+)"|(?P<file>\S+))'
92
+ r"(?:\s+SECTION)?\s+(?P<section>\w+)\s*$",
93
+ re.I,
94
+ )
95
+
96
+ # EXECUTE SCRIPT / EXEC SCRIPT / RUN SCRIPT
97
+ _RX_EXEC_SCRIPT = re.compile(
98
+ r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT(?:\s+IF\s+EXISTS)?\s+(?P<script_id>\w+)",
99
+ re.I,
100
+ )
101
+
70
102
  # INCLUDE <file>
71
103
  _RX_INCLUDE = re.compile(
72
104
  r"^\s*INCLUDE(?:\s+IF\s+EXISTS?)?\s+(?P<path>\S+.*?)\s*$",
@@ -76,67 +108,41 @@ _RX_INCLUDE = re.compile(
76
108
  # Variable reference — !!name!! where name may start with $, @, &, ~, #, +
77
109
  _RX_VAR_REF = re.compile(r"!!([$@&~#+]?\w+)!!", re.I)
78
110
 
79
- # Built-in system variables that are always defined (populated by _run before
80
- # any script commands execute). Variable names are stored without the leading
81
- # ``$`` for case-insensitive set membership tests.
82
- _BUILTIN_VARS: frozenset[str] = frozenset(
83
- {
84
- # Start-time / environment
85
- "SCRIPT_START_TIME",
86
- "SCRIPT_START_TIME_UTC",
87
- "DATE_TAG",
88
- "DATETIME_TAG",
89
- "DATETIME_UTC_TAG",
90
- "LAST_ROWCOUNT",
91
- "LAST_SQL",
92
- "LAST_ERROR",
93
- "ERROR_MESSAGE",
94
- "USER",
95
- "STARTING_PATH",
96
- "PATHSEP",
97
- "OS",
98
- "PYTHON_EXECUTABLE",
99
- "STARTING_SCRIPT",
100
- "STARTING_SCRIPT_NAME",
101
- "STARTING_SCRIPT_REVTIME",
102
- "RUN_ID",
103
- # Execution-time (set during runscripts — not available in --dry-run
104
- # but always defined before any script command can reference them)
105
- "CURRENT_TIME",
106
- "CURRENT_TIME_UTC",
107
- "CURRENT_SCRIPT",
108
- "CURRENT_SCRIPT_PATH",
109
- "CURRENT_SCRIPT_NAME",
110
- "CURRENT_SCRIPT_LINE",
111
- "SCRIPT_LINE",
112
- "CURRENT_DIR",
113
- "CURRENT_PATH",
114
- "CURRENT_ALIAS",
115
- "AUTOCOMMIT_STATE",
116
- "TIMER",
117
- "DB_USER",
118
- "DB_SERVER",
119
- "DB_NAME",
120
- "DB_NEED_PWD",
121
- "RANDOM",
122
- "UUID",
123
- "VERSION1",
124
- "VERSION2",
125
- "VERSION3",
126
- "CANCEL_HALT_STATE",
127
- "ERROR_HALT_STATE",
128
- "METACOMMAND_ERROR_HALT_STATE",
129
- "CONSOLE_WAIT_WHEN_ERROR_HALT_STATE",
130
- "CONSOLE_WAIT_WHEN_DONE_STATE",
131
- "CURRENT_DBMS",
132
- "CURRENT_DATABASE",
133
- "SYSTEM_CMD_EXIT_STATUS",
134
- # Connection-populated
135
- "DB_FILE",
136
- "DB_PORT",
137
- # Counter variables (@@name) are always valid — skip validation
138
- },
139
- )
111
+ # Built-in system variables extracted automatically from the installed
112
+ # ``execsql`` source by scanning for ``add_substitution("$NAME", ...)`` and
113
+ # ``register_lazy("$NAME", ...)`` calls. This avoids maintaining a hand-
114
+ # curated list that drifts out of sync when new system variables are added.
115
+ # Variable names are stored upper-case without the leading ``$``.
116
+
117
+
118
+ def _discover_builtin_vars() -> frozenset[str]:
119
+ """Scan the execsql package source for ``$VARNAME`` system variables."""
120
+ import importlib.util
121
+
122
+ _rx_add_sub = re.compile(r'(?:(?<!\w)add_substitution|(?<!\w)sv)\s*\(\s*["\'](\$\w+)["\']')
123
+ _rx_lazy = re.compile(r'register_lazy\s*\(\s*["\'](\$\w+)["\']')
124
+
125
+ names: set[str] = set()
126
+
127
+ spec = importlib.util.find_spec("execsql")
128
+ if spec is None or spec.submodule_search_locations is None:
129
+ return frozenset(names)
130
+
131
+ pkg_dir = Path(spec.submodule_search_locations[0])
132
+ for src_file in pkg_dir.rglob("*.py"):
133
+ try:
134
+ text = src_file.read_text(encoding="utf-8")
135
+ except OSError:
136
+ continue
137
+ for m in _rx_add_sub.finditer(text):
138
+ names.add(m.group(1).lstrip("$").upper())
139
+ for m in _rx_lazy.finditer(text):
140
+ names.add(m.group(1).lstrip("$").upper())
141
+
142
+ return frozenset(names)
143
+
144
+
145
+ _BUILTIN_VARS: frozenset[str] = _discover_builtin_vars()
140
146
 
141
147
 
142
148
  # ---------------------------------------------------------------------------
@@ -159,23 +165,90 @@ def _warning(source: str, line_no: int, message: str) -> _Issue:
159
165
  # ---------------------------------------------------------------------------
160
166
 
161
167
 
168
+ def _collect_defined_vars(
169
+ cmdlist: CommandList,
170
+ script_dir: Path | None,
171
+ defined_vars: set[str],
172
+ *,
173
+ _savedscripts: dict | None = None,
174
+ _visited_scripts: set[str] | None = None,
175
+ ) -> None:
176
+ """Pass 1: walk *cmdlist* and collect all variable definitions into *defined_vars*.
177
+
178
+ This populates the set with every variable name that could be defined at
179
+ runtime — ``SUB``, ``SUB_EMPTY``, ``SUB_ADD``, ``SUB_APPEND``,
180
+ ``SUBDATA``, and ``SUB_INI`` (by reading the INI file on disk). It also
181
+ descends into ``EXECUTE SCRIPT`` targets to collect their definitions.
182
+
183
+ No issues are reported; structural checks and variable-reference validation
184
+ happen in pass 2 (:func:`_lint_cmdlist`).
185
+ """
186
+ visited = _visited_scripts if _visited_scripts is not None else set()
187
+
188
+ for cmd in cmdlist.cmdlist:
189
+ if cmd.command_type == "sql":
190
+ continue
191
+ stmt = cmd.command.statement
192
+
193
+ # SUB <name> <value>
194
+ sub_m = _RX_SUB.match(stmt)
195
+ if sub_m:
196
+ defined_vars.add(sub_m.group("name").lstrip("+~").upper())
197
+
198
+ # SUB_EMPTY / SUB_ADD / SUB_APPEND / SUBDATA
199
+ for rx in (_RX_SUB_EMPTY, _RX_SUB_ADD, _RX_SUB_APPEND, _RX_SUBDATA):
200
+ m = rx.match(stmt)
201
+ if m:
202
+ defined_vars.add(m.group("name").lstrip("+~").upper())
203
+ break
204
+
205
+ # SUB_INI — read INI file keys
206
+ ini_m = _RX_SUB_INI.match(stmt)
207
+ if ini_m:
208
+ ini_file = ini_m.group("qfile") or ini_m.group("file")
209
+ ini_section = ini_m.group("section")
210
+ if ini_file and not _RX_VAR_REF.search(ini_file):
211
+ _read_ini_vars(ini_file, ini_section, script_dir, defined_vars)
212
+
213
+ # EXECUTE SCRIPT — descend into named script block
214
+ exec_m = _RX_EXEC_SCRIPT.match(stmt)
215
+ if exec_m and _savedscripts is not None:
216
+ script_id = exec_m.group("script_id").lower()
217
+ if script_id in _savedscripts and script_id not in visited:
218
+ visited.add(script_id)
219
+ _collect_defined_vars(
220
+ _savedscripts[script_id],
221
+ script_dir,
222
+ defined_vars,
223
+ _savedscripts=_savedscripts,
224
+ _visited_scripts=visited,
225
+ )
226
+
227
+
162
228
  def _lint_cmdlist(
163
229
  cmdlist: CommandList,
164
230
  script_dir: Path | None,
165
231
  defined_vars: set[str],
232
+ *,
233
+ _savedscripts: dict | None = None,
234
+ _visited_scripts: set[str] | None = None,
166
235
  ) -> list[_Issue]:
167
- """Lint a single :class:`CommandList` and return any issues found.
236
+ """Pass 2: lint a :class:`CommandList` for structural and variable issues.
168
237
 
169
238
  Args:
170
239
  cmdlist: The parsed command list to analyse.
171
240
  script_dir: Directory of the top-level script file, used for resolving
172
241
  relative INCLUDE paths. ``None`` for inline (``-c``) scripts.
173
- defined_vars: Mutable set of variable names (without sigil) that have
174
- been defined by preceding ``SUB`` metacommands. The caller passes
175
- in the set from the outer scope so that variables defined before an
176
- EXECUTE SCRIPT call are visible inside the script block when
177
- analysing top-level scripts. For named-script analysis the caller
178
- passes a *copy* so that local definitions don't leak.
242
+ defined_vars: Set of variable names (without sigil) that have been
243
+ pre-collected by :func:`_collect_defined_vars`. This includes
244
+ *all* top-level and script-block definitions so that ordering
245
+ does not matter.
246
+ _savedscripts: Dictionary of named script blocks (from
247
+ ``_state.savedscripts``). Passed explicitly so the function can
248
+ descend into EXECUTE SCRIPT targets.
249
+ _visited_scripts: Set of script IDs already descended into, shared
250
+ across recursive calls to prevent infinite recursion from circular
251
+ EXECUTE SCRIPT references.
179
252
 
180
253
  Returns:
181
254
  List of ``(severity, source, line_no, message)`` issue tuples.
@@ -191,6 +264,10 @@ def _lint_cmdlist(
191
264
  batch_depth = 0
192
265
  batch_open_locs: list[tuple[str, int]] = []
193
266
 
267
+ # Track which EXECUTE SCRIPT targets we've already descended into to
268
+ # prevent infinite recursion from circular script references.
269
+ visited_scripts: set[str] = _visited_scripts if _visited_scripts is not None else set()
270
+
194
271
  for cmd in cmdlist.cmdlist:
195
272
  src = cmd.source
196
273
  lno = cmd.line_no
@@ -202,7 +279,7 @@ def _lint_cmdlist(
202
279
  _check_var_ref(m.group(1), src, lno, defined_vars, issues)
203
280
  continue
204
281
 
205
- # Metacommand checks
282
+ # Metacommand checks — variable references
206
283
  for m in _RX_VAR_REF.finditer(stmt):
207
284
  _check_var_ref(m.group(1), src, lno, defined_vars, issues)
208
285
 
@@ -247,11 +324,27 @@ def _lint_cmdlist(
247
324
  batch_depth -= 1
248
325
  batch_open_locs.pop()
249
326
 
250
- # -- SUB variable definition --
251
- sub_m = _RX_SUB.match(stmt)
252
- if sub_m:
253
- varname = sub_m.group("name").lstrip("+~")
254
- defined_vars.add(varname.upper())
327
+ # -- EXECUTE SCRIPT descend into named script block --
328
+ exec_m = _RX_EXEC_SCRIPT.match(stmt)
329
+ if exec_m and _savedscripts is not None:
330
+ script_id = exec_m.group("script_id").lower()
331
+ if script_id not in _savedscripts:
332
+ # Warn unless it's EXECUTE SCRIPT IF EXISTS
333
+ if not re.search(r"\bIF\s+EXISTS\b", stmt, re.I):
334
+ issues.append(
335
+ _warning(src, lno, f"EXECUTE SCRIPT target not found: '{script_id}'"),
336
+ )
337
+ elif script_id not in visited_scripts:
338
+ visited_scripts.add(script_id)
339
+ sub_issues = _lint_cmdlist(
340
+ _savedscripts[script_id],
341
+ script_dir,
342
+ defined_vars,
343
+ _savedscripts=_savedscripts,
344
+ _visited_scripts=visited_scripts,
345
+ )
346
+ for sev, ssrc, slno, msg in sub_issues:
347
+ issues.append((sev, ssrc, slno, f"[script '{script_id}'] {msg}"))
255
348
 
256
349
  # -- INCLUDE file existence --
257
350
  inc_m = _RX_INCLUDE.match(stmt)
@@ -307,6 +400,10 @@ def _check_var_ref(
307
400
  if re.match(r"^ARG_\d+$", name, re.I):
308
401
  return
309
402
 
403
+ # $COUNTER_N is managed by CounterVars (@@counter metacommands)
404
+ if re.match(r"^COUNTER_\d+$", name, re.I):
405
+ return
406
+
310
407
  # Built-in system variables
311
408
  if name.upper() in _BUILTIN_VARS:
312
409
  return
@@ -325,6 +422,35 @@ def _check_var_ref(
325
422
  )
326
423
 
327
424
 
425
+ def _read_ini_vars(
426
+ ini_file: str,
427
+ section: str,
428
+ script_dir: Path | None,
429
+ defined_vars: set[str],
430
+ ) -> None:
431
+ """Read an INI file and register its section keys as defined variables.
432
+
433
+ Mirrors what ``SUB_INI`` does at runtime: reads a
434
+ :class:`~configparser.ConfigParser` section and defines each key as a
435
+ substitution variable. If the file does not exist or the section is
436
+ missing, silently does nothing (the runtime handler behaves the same way).
437
+ """
438
+ from configparser import ConfigParser
439
+
440
+ p = Path(ini_file)
441
+ if not p.is_absolute() and script_dir is not None:
442
+ p = script_dir / p
443
+
444
+ if not p.exists():
445
+ return
446
+
447
+ cp = ConfigParser()
448
+ cp.read(p)
449
+ if cp.has_section(section):
450
+ for key, _value in cp.items(section):
451
+ defined_vars.add(key.upper())
452
+
453
+
328
454
  def _check_include_path(
329
455
  raw_path: str,
330
456
  script_dir: Path | None,
@@ -393,19 +519,68 @@ def _lint_script(
393
519
  return issues
394
520
 
395
521
  script_dir = Path(script_path).resolve().parent if script_path else None
522
+ savedscripts: dict = getattr(_state, "savedscripts", {})
523
+
524
+ # ------------------------------------------------------------------
525
+ # Pass 1: collect all variable definitions from the top-level script
526
+ # and all reachable script blocks. This ensures definition order does
527
+ # not matter — a script block executed early can reference variables
528
+ # defined later in the top-level script.
529
+ # ------------------------------------------------------------------
530
+ all_defined: set[str] = set()
531
+ collect_visited: set[str] = set()
532
+ _collect_defined_vars(
533
+ cmdlist,
534
+ script_dir,
535
+ all_defined,
536
+ _savedscripts=savedscripts,
537
+ _visited_scripts=collect_visited,
538
+ )
539
+ # Also collect from every saved script block (they may define vars
540
+ # referenced by other blocks). Share the visited set so each block
541
+ # is only traversed once (O(N) instead of O(N²)).
542
+ for saved_cl in savedscripts.values():
543
+ _collect_defined_vars(
544
+ saved_cl,
545
+ script_dir,
546
+ all_defined,
547
+ _savedscripts=savedscripts,
548
+ _visited_scripts=collect_visited,
549
+ )
396
550
 
397
- # Shared set of variables defined in the top-level script via SUB.
398
- # Named scripts get a fresh copy so their internal definitions don't bleed
399
- # back into the top-level analysis.
400
- top_defined: set[str] = set()
401
-
402
- issues.extend(_lint_cmdlist(cmdlist, script_dir, top_defined))
551
+ # ------------------------------------------------------------------
552
+ # Pass 2: lint for structural issues and undefined-variable warnings
553
+ # using the complete variable set from pass 1.
554
+ # ------------------------------------------------------------------
555
+ # Shared visited-scripts tracker — prevents duplicate lint warnings
556
+ # when the same script block is reached via multiple paths.
557
+ visited: set[str] = set()
558
+
559
+ issues.extend(
560
+ _lint_cmdlist(
561
+ cmdlist,
562
+ script_dir,
563
+ all_defined,
564
+ _savedscripts=savedscripts,
565
+ _visited_scripts=visited,
566
+ ),
567
+ )
403
568
 
404
- # Analyse each named SCRIPT block collected during parsing
405
- for script_name, saved_cl in getattr(_state, "savedscripts", {}).items():
406
- saved_issues = _lint_cmdlist(saved_cl, script_dir, set(top_defined))
569
+ # Analyse each named SCRIPT block that was NOT already visited via
570
+ # EXECUTE SCRIPT (standalone analysis catches structural issues like
571
+ # unmatched IF/ENDIF in script blocks that are never executed).
572
+ for script_name, saved_cl in savedscripts.items():
573
+ if script_name in visited:
574
+ continue
575
+ visited.add(script_name)
576
+ saved_issues = _lint_cmdlist(
577
+ saved_cl,
578
+ script_dir,
579
+ set(all_defined),
580
+ _savedscripts=savedscripts,
581
+ _visited_scripts=visited,
582
+ )
407
583
  for sev, src, lno, msg in saved_issues:
408
- # Annotate with the script name if the source is the same file
409
584
  issues.append((sev, src, lno, f"[script '{script_name}'] {msg}"))
410
585
 
411
586
  return issues
@@ -440,12 +615,22 @@ def _print_lint_results(issues: list[_Issue], script_label: str) -> int:
440
615
  _console.print()
441
616
  return 0
442
617
 
443
- for severity, source, line_no, message in issues:
444
- loc = f"{source}:{line_no}" if line_no else source
618
+ # Sort: errors first, then warnings; within each group sort by line number.
619
+ _sev_order = {"error": 0, "warning": 1}
620
+ sorted_issues = sorted(issues, key=lambda i: (_sev_order.get(i[0], 9), i[2]))
621
+
622
+ # Compute the widest location string so columns align.
623
+ locs: list[str] = []
624
+ for _, source, line_no, _ in sorted_issues:
625
+ locs.append(f"{source}:{line_no}" if line_no else source)
626
+ loc_width = max(len(loc) for loc in locs) if locs else 0
627
+
628
+ for (severity, _source, _line_no, message), loc in zip(sorted_issues, locs):
629
+ pad = " " * (loc_width - len(loc))
445
630
  if severity == "error":
446
- _console.print(f" [bold red]ERROR [/bold red] [dim]{loc}[/dim] {message}")
631
+ _console.print(f" [bold red]ERROR [/bold red] [dim]{loc}[/dim]{pad} {message}")
447
632
  else:
448
- _console.print(f" [bold yellow]WARNING[/bold yellow] [dim]{loc}[/dim] {message}")
633
+ _console.print(f" [bold yellow]WARNING[/bold yellow] [dim]{loc}[/dim]{pad} {message}")
449
634
 
450
635
  _console.print()
451
636
  parts = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.13.1
3
+ Version: 2.13.2
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: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -12,7 +12,7 @@ execsql/types.py,sha256=HVWb4umIB9lpxCGgqk3xy1hoGYPfN39xci5mHF0Izq4,31882
12
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=XWuVcEsheZ8ql48VFWqICWEkAUezB2nIePX6SUiKSg8,16109
15
+ execsql/cli/lint.py,sha256=mVZu3Knp13-yQZkofiaeDmxLTWP7DkbaZoDeHPsfAEQ,24094
16
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
@@ -94,24 +94,24 @@ execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
94
94
  execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
95
95
  execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
96
96
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
97
- execsql2-2.13.1.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
98
- execsql2-2.13.1.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
99
- execsql2-2.13.1.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
100
- execsql2-2.13.1.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
101
- execsql2-2.13.1.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
102
- execsql2-2.13.1.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
103
- execsql2-2.13.1.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
104
- execsql2-2.13.1.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
105
- execsql2-2.13.1.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
106
- execsql2-2.13.1.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
107
- execsql2-2.13.1.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
108
- execsql2-2.13.1.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
109
- execsql2-2.13.1.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
110
- execsql2-2.13.1.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
111
- execsql2-2.13.1.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
112
- execsql2-2.13.1.dist-info/METADATA,sha256=l1roY9QFNY72_7eiyYogsqDXyxAcjeE_oo18sL9axHo,17566
113
- execsql2-2.13.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
114
- execsql2-2.13.1.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
115
- execsql2-2.13.1.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
116
- execsql2-2.13.1.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
117
- execsql2-2.13.1.dist-info/RECORD,,
97
+ execsql2-2.13.2.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
98
+ execsql2-2.13.2.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
99
+ execsql2-2.13.2.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
100
+ execsql2-2.13.2.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
101
+ execsql2-2.13.2.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
102
+ execsql2-2.13.2.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
103
+ execsql2-2.13.2.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
104
+ execsql2-2.13.2.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
105
+ execsql2-2.13.2.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
106
+ execsql2-2.13.2.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
107
+ execsql2-2.13.2.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
108
+ execsql2-2.13.2.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
109
+ execsql2-2.13.2.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
110
+ execsql2-2.13.2.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
111
+ execsql2-2.13.2.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
112
+ execsql2-2.13.2.dist-info/METADATA,sha256=R_jJi0OgB5NoPJ6g8mLse9YttgHU2EIZxlCZADPe9d0,17566
113
+ execsql2-2.13.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
114
+ execsql2-2.13.2.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
115
+ execsql2-2.13.2.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
116
+ execsql2-2.13.2.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
117
+ execsql2-2.13.2.dist-info/RECORD,,