execsql2 2.23.0__py3-none-any.whl → 2.24.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.
- execsql/cli/__init__.py +17 -586
- execsql/cli/application.py +309 -0
- execsql/cli/commands/__init__.py +13 -0
- execsql/cli/commands/config.py +205 -0
- execsql/cli/commands/format.py +100 -0
- execsql/cli/commands/init.py +135 -0
- execsql/cli/commands/lint.py +233 -0
- execsql/cli/commands/listing.py +81 -0
- execsql/cli/commands/run.py +451 -0
- execsql/cli/dispatch.py +66 -0
- execsql/cli/help.py +175 -24
- execsql/cli/lint.py +513 -76
- execsql/cli/options.py +108 -0
- execsql/cli/run.py +41 -14
- execsql/config.py +251 -154
- execsql/config_validation.py +282 -0
- execsql/data/execsql.conf.template +24 -1
- execsql/data/script_header.sql +23 -0
- execsql/db/access.py +22 -6
- execsql/db/base.py +105 -1
- execsql/db/dsn.py +2 -0
- execsql/db/duckdb.py +95 -2
- execsql/db/factory.py +2 -2
- execsql/db/mysql.py +11 -0
- execsql/db/oracle.py +12 -4
- execsql/db/sqlite.py +16 -0
- execsql/debug/repl.py +51 -193
- execsql/exceptions.py +9 -0
- execsql/format.py +87 -75
- execsql/interactive.py +232 -0
- execsql/manifest.py +189 -0
- execsql/metacommands/conditions.py +1 -1
- execsql/metacommands/control.py +2 -3
- execsql/metacommands/effects.py +124 -0
- execsql/script/__init__.py +0 -9
- execsql/script/ast.py +1 -1
- execsql/script/engine.py +6 -93
- execsql/script/executor.py +153 -34
- execsql/state.py +11 -2
- execsql/state.pyi +3 -0
- execsql/utils/color.py +18 -0
- execsql/utils/errors.py +11 -1
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/README.md +1 -1
- {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/METADATA +111 -58
- {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/RECORD +62 -46
- execsql2-2.24.0.dist-info/entry_points.txt +2 -0
- execsql2-2.23.0.dist-info/entry_points.txt +0 -3
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/WHEEL +0 -0
- {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/help.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Rich
|
|
1
|
+
"""Rich tables and console objects for the execsql CLI.
|
|
2
2
|
|
|
3
3
|
Contains the metacommand reference table, encoding list, and the shared
|
|
4
4
|
``Console`` instances used by the other CLI submodules.
|
|
@@ -7,11 +7,26 @@ Contains the metacommand reference table, encoding list, and the shared
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
from encodings.aliases import aliases as codec_dict
|
|
10
|
+
from typing import Any
|
|
10
11
|
|
|
11
12
|
from rich.console import Console
|
|
12
13
|
from rich.table import Table
|
|
13
14
|
|
|
14
|
-
__all__ = [
|
|
15
|
+
__all__ = [
|
|
16
|
+
"_console",
|
|
17
|
+
"_encoding_names",
|
|
18
|
+
"_err_console",
|
|
19
|
+
"_init_config",
|
|
20
|
+
"_init_config_text",
|
|
21
|
+
"_keywords_data",
|
|
22
|
+
"_metacommand_rows",
|
|
23
|
+
"_plugins_data",
|
|
24
|
+
"_print_encodings",
|
|
25
|
+
"_print_keywords_json",
|
|
26
|
+
"_print_keywords_text",
|
|
27
|
+
"_print_metacommands",
|
|
28
|
+
"_print_plugins",
|
|
29
|
+
]
|
|
15
30
|
|
|
16
31
|
_console = Console()
|
|
17
32
|
_err_console = Console(stderr=True)
|
|
@@ -77,35 +92,29 @@ _SKIP_FROM_DISPATCH = {
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
|
|
95
|
+
def _init_config_text() -> str:
|
|
96
|
+
"""The default execsql.conf template, every option commented out and documented."""
|
|
97
|
+
import importlib.resources
|
|
98
|
+
|
|
99
|
+
return importlib.resources.files("execsql.data").joinpath("execsql.conf.template").read_text(encoding="utf-8")
|
|
100
|
+
|
|
101
|
+
|
|
80
102
|
def _init_config() -> None:
|
|
81
103
|
"""Print the default execsql.conf template to stdout."""
|
|
82
|
-
import importlib.resources
|
|
83
104
|
import sys
|
|
84
105
|
|
|
85
|
-
|
|
86
|
-
sys.stdout.write(template)
|
|
106
|
+
sys.stdout.write(_init_config_text())
|
|
87
107
|
|
|
88
108
|
|
|
89
|
-
def
|
|
90
|
-
"""
|
|
109
|
+
def _metacommand_rows() -> list[tuple[str, str]]:
|
|
110
|
+
"""``(name, syntax)`` for every metacommand, in the order they are listed.
|
|
91
111
|
|
|
92
112
|
Keyword list is derived from the dispatch table; syntax hints come from
|
|
93
|
-
the ``_SYNTAX`` dict above. Keywords not in ``_SYNTAX``
|
|
94
|
-
|
|
113
|
+
the ``_SYNTAX`` dict above. Keywords not in ``_SYNTAX`` get an empty
|
|
114
|
+
syntax hint.
|
|
95
115
|
"""
|
|
96
116
|
from execsql.metacommands import DISPATCH_TABLE
|
|
97
117
|
|
|
98
|
-
table = Table(
|
|
99
|
-
title="execsql Metacommands",
|
|
100
|
-
caption="Embed in SQL comment lines following the [bold]!x![/bold] token.",
|
|
101
|
-
show_header=True,
|
|
102
|
-
header_style="bold cyan",
|
|
103
|
-
border_style="dim",
|
|
104
|
-
expand=False,
|
|
105
|
-
)
|
|
106
|
-
table.add_column("Metacommand", style="bold green", no_wrap=True)
|
|
107
|
-
table.add_column("Syntax", style="white")
|
|
108
|
-
|
|
109
118
|
# Collect unique keyword names from the dispatch table.
|
|
110
119
|
seen: set[str] = set()
|
|
111
120
|
keywords: list[str] = []
|
|
@@ -119,21 +128,42 @@ def _print_metacommands() -> None:
|
|
|
119
128
|
seen.add(extra)
|
|
120
129
|
keywords.append(extra)
|
|
121
130
|
|
|
131
|
+
rows: list[tuple[str, str]] = []
|
|
122
132
|
for kw in sorted(keywords):
|
|
123
133
|
if kw in _SYNTAX:
|
|
124
|
-
|
|
125
|
-
table.add_row(name, syntax)
|
|
134
|
+
rows.append(_SYNTAX[kw])
|
|
126
135
|
elif kw.startswith("CONFIG ") or kw.startswith("CONSOLE_") or "_" in kw:
|
|
127
136
|
continue # skip config options / internal entries
|
|
128
137
|
else:
|
|
129
|
-
|
|
138
|
+
rows.append((kw, ""))
|
|
139
|
+
return rows
|
|
130
140
|
|
|
141
|
+
|
|
142
|
+
def _print_metacommands() -> None:
|
|
143
|
+
"""Print the metacommands table using Rich."""
|
|
144
|
+
table = Table(
|
|
145
|
+
title="execsql Metacommands",
|
|
146
|
+
caption="Embed in SQL comment lines following the [bold]!x![/bold] token.",
|
|
147
|
+
show_header=True,
|
|
148
|
+
header_style="bold cyan",
|
|
149
|
+
border_style="dim",
|
|
150
|
+
expand=False,
|
|
151
|
+
)
|
|
152
|
+
table.add_column("Metacommand", style="bold green", no_wrap=True)
|
|
153
|
+
table.add_column("Syntax", style="white")
|
|
154
|
+
for name, syntax in _metacommand_rows():
|
|
155
|
+
table.add_row(name, syntax)
|
|
131
156
|
_console.print(table)
|
|
132
157
|
|
|
133
158
|
|
|
159
|
+
def _encoding_names() -> list[str]:
|
|
160
|
+
"""Every encoding name Python accepts, sorted."""
|
|
161
|
+
return sorted(codec_dict.keys())
|
|
162
|
+
|
|
163
|
+
|
|
134
164
|
def _print_encodings() -> None:
|
|
135
165
|
"""Print available encodings using Rich."""
|
|
136
|
-
enc =
|
|
166
|
+
enc = _encoding_names()
|
|
137
167
|
table = Table(
|
|
138
168
|
title="Available Encodings",
|
|
139
169
|
show_header=False,
|
|
@@ -149,3 +179,124 @@ def _print_encodings() -> None:
|
|
|
149
179
|
row.append("")
|
|
150
180
|
table.add_row(*row)
|
|
151
181
|
_console.print(table)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _keywords_data() -> dict[str, Any]:
|
|
185
|
+
"""The full keyword vocabulary: the data behind ``execsql list keywords``.
|
|
186
|
+
|
|
187
|
+
The VS Code grammar build and ``tests/test_registry.py`` read its JSON
|
|
188
|
+
form, so the shape of this dict is a contract.
|
|
189
|
+
"""
|
|
190
|
+
from execsql.metacommands import (
|
|
191
|
+
ALL_EXPORT_FORMATS,
|
|
192
|
+
DATABASE_TYPES,
|
|
193
|
+
DISPATCH_TABLE,
|
|
194
|
+
JSON_VARIANT_FORMATS,
|
|
195
|
+
METADATA_FORMATS,
|
|
196
|
+
QUERY_EXPORT_FORMATS,
|
|
197
|
+
SERVE_FORMATS,
|
|
198
|
+
TABLE_EXPORT_FORMATS,
|
|
199
|
+
)
|
|
200
|
+
from execsql.metacommands.conditions import CONDITIONAL_TABLE
|
|
201
|
+
|
|
202
|
+
mc_kw = DISPATCH_TABLE.keywords_by_category()
|
|
203
|
+
cond_kw = CONDITIONAL_TABLE.keywords_by_category()
|
|
204
|
+
|
|
205
|
+
data = {
|
|
206
|
+
"metacommands": {
|
|
207
|
+
"control": sorted(mc_kw.get("control", [])),
|
|
208
|
+
"block": sorted(
|
|
209
|
+
mc_kw.get("block", []) + ["BEGIN SCRIPT", "END SCRIPT", "BEGIN SQL", "END SQL"],
|
|
210
|
+
),
|
|
211
|
+
"action": sorted(mc_kw.get("action", [])),
|
|
212
|
+
"config": sorted(mc_kw.get("config", [])),
|
|
213
|
+
"prompt": sorted(mc_kw.get("prompt", [])),
|
|
214
|
+
},
|
|
215
|
+
"conditions": sorted(cond_kw.get("condition", []) + ["IS_FALSE", "NOT", "OR"]),
|
|
216
|
+
"config_options": sorted(mc_kw.get("config_option", [])),
|
|
217
|
+
"export_formats": {
|
|
218
|
+
"query": sorted(QUERY_EXPORT_FORMATS),
|
|
219
|
+
"table": sorted(TABLE_EXPORT_FORMATS),
|
|
220
|
+
"serve": sorted(SERVE_FORMATS),
|
|
221
|
+
"metadata": sorted(METADATA_FORMATS),
|
|
222
|
+
"json_variants": sorted(JSON_VARIANT_FORMATS),
|
|
223
|
+
"all": sorted(ALL_EXPORT_FORMATS),
|
|
224
|
+
},
|
|
225
|
+
"database_types": sorted(DATABASE_TYPES),
|
|
226
|
+
"variable_patterns": {
|
|
227
|
+
"system": "!!$name!!",
|
|
228
|
+
"environment": "!!&name!!",
|
|
229
|
+
"parameter": "!!#name!!",
|
|
230
|
+
"column": "!!@name!!",
|
|
231
|
+
"local": "!!~name!!",
|
|
232
|
+
"local_alt": "!!+name!!",
|
|
233
|
+
"regular": "!!name!!",
|
|
234
|
+
"deferred": "!{name}!",
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
return data
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _print_keywords_json() -> None:
|
|
241
|
+
"""Print :func:`_keywords_data` as JSON: ``list keywords --output-format json``, and ``--dump-keywords`` as it always has."""
|
|
242
|
+
import json
|
|
243
|
+
|
|
244
|
+
_console.print_json(json.dumps(_keywords_data(), indent=2))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _print_keywords_text() -> None:
|
|
248
|
+
"""Print :func:`_keywords_data` grouped under headings, for reading."""
|
|
249
|
+
import textwrap
|
|
250
|
+
|
|
251
|
+
data = _keywords_data()
|
|
252
|
+
|
|
253
|
+
def group(title: str, words: list[str]) -> None:
|
|
254
|
+
_console.print(f"[bold]{title}[/bold] ({len(words)})")
|
|
255
|
+
_console.print(textwrap.fill(", ".join(words), width=88, initial_indent=" ", subsequent_indent=" "))
|
|
256
|
+
_console.print()
|
|
257
|
+
|
|
258
|
+
for category, words in data["metacommands"].items():
|
|
259
|
+
group(f"Metacommands: {category}", words)
|
|
260
|
+
group("Conditions", data["conditions"])
|
|
261
|
+
group("CONFIG options", data["config_options"])
|
|
262
|
+
group("Export formats", data["export_formats"]["all"])
|
|
263
|
+
group("Database types", data["database_types"])
|
|
264
|
+
_console.print("[bold]Variable patterns[/bold]")
|
|
265
|
+
for name, pattern in data["variable_patterns"].items():
|
|
266
|
+
_console.print(f" {pattern:<12} {name}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _plugins_data() -> dict[str, list[str]]:
|
|
270
|
+
"""Names of the installed plugins, by kind."""
|
|
271
|
+
from execsql.plugins import (
|
|
272
|
+
EXPORTER_GROUP,
|
|
273
|
+
IMPORTER_GROUP,
|
|
274
|
+
METACOMMAND_GROUP,
|
|
275
|
+
_load_entry_points,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
"metacommands": [name for name, _ in _load_entry_points(METACOMMAND_GROUP)],
|
|
280
|
+
"exporters": [name for name, _ in _load_entry_points(EXPORTER_GROUP)],
|
|
281
|
+
"importers": [name for name, _ in _load_entry_points(IMPORTER_GROUP)],
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _print_plugins() -> None:
|
|
286
|
+
"""Print the installed plugins, or how to write one when there are none."""
|
|
287
|
+
plugins = _plugins_data()
|
|
288
|
+
_console.print("\n[bold cyan]Installed plugins:[/bold cyan]\n")
|
|
289
|
+
if not any(plugins.values()):
|
|
290
|
+
_console.print(" [dim]No plugins found.[/dim]")
|
|
291
|
+
_console.print()
|
|
292
|
+
_console.print(
|
|
293
|
+
" Plugins are discovered via Python entry points.\n"
|
|
294
|
+
" See the execsql documentation for how to create plugins.",
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
for kind, names in plugins.items():
|
|
298
|
+
if names:
|
|
299
|
+
_console.print(f" [bold]{kind.capitalize()}[/bold] ({len(names)}):")
|
|
300
|
+
for name in names:
|
|
301
|
+
_console.print(f" - {name}")
|
|
302
|
+
_console.print()
|