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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""The ``init`` command: set up execsql in a project directory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import importlib.resources
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.markup import escape
|
|
12
|
+
|
|
13
|
+
from execsql import __version__
|
|
14
|
+
from execsql.cli.application import ExecsqlCommand, app
|
|
15
|
+
from execsql.cli.help import _console
|
|
16
|
+
|
|
17
|
+
__all__ = ["init_cmd", "pre_commit_snippet", "script_header"]
|
|
18
|
+
|
|
19
|
+
_HOOKS_REPO = "https://github.com/geocoug/execsql"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def script_header(name: str, today: datetime.date | None = None) -> str:
|
|
23
|
+
"""The header a new script starts with: purpose, notes, project, authors and history."""
|
|
24
|
+
today = today or datetime.date.today()
|
|
25
|
+
text = importlib.resources.files("execsql.data").joinpath("script_header.sql").read_text(encoding="utf-8")
|
|
26
|
+
return text.replace("{name}", name).replace("{date}", today.isoformat()).replace("{year}", str(today.year))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def pre_commit_snippet(indent: str = " ") -> str:
|
|
30
|
+
"""The ``repos:`` entry for the execsql-format and execsql-lint hooks, at *indent*."""
|
|
31
|
+
return (
|
|
32
|
+
f"{indent}- repo: {_HOOKS_REPO}\n"
|
|
33
|
+
f"{indent} rev: v{__version__}\n"
|
|
34
|
+
f"{indent} hooks:\n"
|
|
35
|
+
f"{indent} - id: execsql-format\n"
|
|
36
|
+
f"{indent} - id: execsql-lint\n"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _add_hooks(path: Path) -> tuple[str, str]:
|
|
41
|
+
"""Add the execsql hooks to a pre-commit config; return (action, detail).
|
|
42
|
+
|
|
43
|
+
A new file is written whole. An existing one is only appended to, and only
|
|
44
|
+
when ``repos:`` is its last top-level key, so its comments and layout are
|
|
45
|
+
untouched; anything else is reported with the snippet to add by hand.
|
|
46
|
+
"""
|
|
47
|
+
if not path.exists():
|
|
48
|
+
path.write_text("repos:\n" + pre_commit_snippet(), encoding="utf-8")
|
|
49
|
+
return "created", ""
|
|
50
|
+
text = path.read_text(encoding="utf-8")
|
|
51
|
+
if _HOOKS_REPO in text or "geocoug/execsql" in text:
|
|
52
|
+
return "skipped", "already has the execsql hooks"
|
|
53
|
+
top_level = re.findall(r"^([A-Za-z_][\w-]*)\s*:", text, flags=re.M)
|
|
54
|
+
if not top_level or top_level[-1] != "repos":
|
|
55
|
+
return "manual", "repos: is not the last key"
|
|
56
|
+
item = re.search(r"^(\s*)- repo:", text, flags=re.M)
|
|
57
|
+
indent = item.group(1) if item else " "
|
|
58
|
+
path.write_text(text.rstrip("\n") + "\n" + pre_commit_snippet(indent), encoding="utf-8")
|
|
59
|
+
return "added", "the execsql-format and execsql-lint hooks"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _write(path: Path, content: str, force: bool) -> str:
|
|
63
|
+
if path.exists() and not force:
|
|
64
|
+
return "skipped"
|
|
65
|
+
existed = path.exists()
|
|
66
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
path.write_text(content, encoding="utf-8")
|
|
68
|
+
return "overwrote" if existed else "created"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command(cls=ExecsqlCommand, name="init")
|
|
72
|
+
def init_cmd(
|
|
73
|
+
directory: Path = typer.Argument(
|
|
74
|
+
Path("."),
|
|
75
|
+
metavar="[DIR]",
|
|
76
|
+
help="Project directory; created if missing. Default: the current directory.",
|
|
77
|
+
show_default=False,
|
|
78
|
+
),
|
|
79
|
+
script: str = typer.Option(
|
|
80
|
+
"main.sql",
|
|
81
|
+
"--script",
|
|
82
|
+
metavar="NAME",
|
|
83
|
+
help="Name of the script to create, relative to DIR. .sql is added if missing.",
|
|
84
|
+
),
|
|
85
|
+
no_script: bool = typer.Option(False, "--no-script", help="Do not create a script."),
|
|
86
|
+
no_config: bool = typer.Option(False, "--no-config", help="Do not create execsql.conf."),
|
|
87
|
+
no_pre_commit: bool = typer.Option(
|
|
88
|
+
False,
|
|
89
|
+
"--no-pre-commit",
|
|
90
|
+
help="Do not create or change .pre-commit-config.yaml.",
|
|
91
|
+
),
|
|
92
|
+
force: bool = typer.Option(
|
|
93
|
+
False,
|
|
94
|
+
"--force",
|
|
95
|
+
help="Overwrite an existing execsql.conf or script. .pre-commit-config.yaml is never overwritten.",
|
|
96
|
+
),
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Set up execsql in a project: a config file, a script with a header, and pre-commit hooks.
|
|
99
|
+
|
|
100
|
+
Existing files are left alone unless --force is given. Run it again in an
|
|
101
|
+
existing project with --no-config --no-pre-commit --script NAME to add a
|
|
102
|
+
script.
|
|
103
|
+
"""
|
|
104
|
+
from execsql.cli.help import _init_config_text
|
|
105
|
+
|
|
106
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
report: list[tuple[str, str, str]] = []
|
|
108
|
+
|
|
109
|
+
if not no_config:
|
|
110
|
+
path = directory / "execsql.conf"
|
|
111
|
+
report.append((_write(path, _init_config_text(), force), str(path), ""))
|
|
112
|
+
|
|
113
|
+
if not no_script:
|
|
114
|
+
name = script if Path(script).suffix else f"{script}.sql"
|
|
115
|
+
path = directory / name
|
|
116
|
+
report.append((_write(path, script_header(Path(name).name), force), str(path), ""))
|
|
117
|
+
|
|
118
|
+
if not no_pre_commit:
|
|
119
|
+
path = directory / ".pre-commit-config.yaml"
|
|
120
|
+
action, detail = _add_hooks(path)
|
|
121
|
+
report.append((action, str(path), detail))
|
|
122
|
+
|
|
123
|
+
styles = {"created": "green", "added": "green", "overwrote": "yellow", "skipped": "dim", "manual": "yellow"}
|
|
124
|
+
for action, path_text, detail in report:
|
|
125
|
+
style = styles[action]
|
|
126
|
+
label = "add by hand" if action == "manual" else action
|
|
127
|
+
note = f" ({escape(detail)})" if detail else ""
|
|
128
|
+
if action == "skipped" and not detail:
|
|
129
|
+
note = " (exists; --force to overwrite)"
|
|
130
|
+
_console.print(f" [{style}]{label:<11}[/{style}] {escape(path_text)}{note}", highlight=False, soft_wrap=True)
|
|
131
|
+
if action == "manual":
|
|
132
|
+
_console.print("\n Add this under repos: in that file:\n", highlight=False)
|
|
133
|
+
_console.print(escape(pre_commit_snippet(" ")), highlight=False, soft_wrap=True)
|
|
134
|
+
if not report:
|
|
135
|
+
_console.print("Nothing to do: --no-config, --no-script and --no-pre-commit were all given.")
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""The ``lint`` command: check scripts for problems without connecting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from execsql.cli.application import ExecsqlCommand, app
|
|
12
|
+
from execsql.cli.help import _err_console
|
|
13
|
+
from execsql.cli.options import ConfigFileOpt, ScriptEncodingOpt
|
|
14
|
+
|
|
15
|
+
__all__ = ["LintFormat", "lint_cmd", "lint_paths", "sql_files"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def sql_files(targets: list[str]) -> list[Path]:
|
|
19
|
+
"""Every ``.sql`` file named by *targets*, directories walked recursively."""
|
|
20
|
+
found: list[Path] = []
|
|
21
|
+
for raw in targets:
|
|
22
|
+
p = Path(raw)
|
|
23
|
+
if p.is_dir():
|
|
24
|
+
found.extend(sorted(q for q in p.rglob("*.sql")))
|
|
25
|
+
else:
|
|
26
|
+
found.append(p)
|
|
27
|
+
return found
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def lint_paths(
|
|
31
|
+
targets: list[str],
|
|
32
|
+
*,
|
|
33
|
+
select: tuple[str, ...] = (),
|
|
34
|
+
ignore: tuple[str, ...] = (),
|
|
35
|
+
output_format: str = "text",
|
|
36
|
+
statistics: bool = False,
|
|
37
|
+
encoding: str = "utf-8",
|
|
38
|
+
strict: bool = False,
|
|
39
|
+
) -> int:
|
|
40
|
+
"""Lint every script named by *targets*; return the process exit code.
|
|
41
|
+
|
|
42
|
+
Directories are searched recursively, so one call checks a whole script
|
|
43
|
+
library.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
targets: Files and directories; directories are searched for ``*.sql``.
|
|
47
|
+
``-`` reads one script from stdin, reported as ``<stdin>``.
|
|
48
|
+
select: Rule-code prefixes to report (empty means all), already
|
|
49
|
+
validated by :func:`execsql.cli.lint.resolve_selectors`.
|
|
50
|
+
ignore: Rule-code prefixes to drop; wins over *select*.
|
|
51
|
+
output_format: ``"text"`` (grouped by file), ``"concise"`` (one line
|
|
52
|
+
per issue), ``"github"`` (GitHub Actions workflow commands) or ``"json"``. JSON writes one array to stdout and
|
|
53
|
+
nothing else.
|
|
54
|
+
statistics: Report a count per rule instead of each issue.
|
|
55
|
+
encoding: Character encoding every script is read with. A script that
|
|
56
|
+
does not decode is reported as a parse error (``P001``).
|
|
57
|
+
strict: Warnings fail too: exit ``1`` when any issue is reported.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
``1`` when any reported issue is an error or no ``.sql`` file was
|
|
61
|
+
found, otherwise ``0``.
|
|
62
|
+
"""
|
|
63
|
+
import json
|
|
64
|
+
|
|
65
|
+
from execsql.cli.help import _err_console
|
|
66
|
+
from execsql.cli.lint import (
|
|
67
|
+
Issue,
|
|
68
|
+
_issue,
|
|
69
|
+
exit_code,
|
|
70
|
+
filter_issues,
|
|
71
|
+
lint as _lint_script,
|
|
72
|
+
parse_error,
|
|
73
|
+
print_concise,
|
|
74
|
+
render_github,
|
|
75
|
+
print_statistics,
|
|
76
|
+
print_text,
|
|
77
|
+
render_json,
|
|
78
|
+
rule_counts,
|
|
79
|
+
)
|
|
80
|
+
from execsql.exceptions import ErrInfo
|
|
81
|
+
from execsql.script.parser import parse_string
|
|
82
|
+
|
|
83
|
+
paths = sql_files(targets)
|
|
84
|
+
if not paths:
|
|
85
|
+
_err_console.print("[bold red]Error:[/bold red] no .sql files found in the given paths.")
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
per_file: list[tuple[str, list[Issue]]] = []
|
|
89
|
+
for path in paths:
|
|
90
|
+
stdin = str(path) == "-"
|
|
91
|
+
label = "<stdin>" if stdin else str(path)
|
|
92
|
+
try:
|
|
93
|
+
source = sys.stdin.buffer.read().decode(encoding) if stdin else path.read_text(encoding=encoding)
|
|
94
|
+
except UnicodeDecodeError as exc:
|
|
95
|
+
found = [_issue("P001", label, 0, f"cannot decode as {encoding} ({exc.reason}); set -f/--script-encoding")]
|
|
96
|
+
except OSError as exc:
|
|
97
|
+
found = [_issue("P001", label, 0, f"cannot read: {exc.strerror or exc}")]
|
|
98
|
+
else:
|
|
99
|
+
try:
|
|
100
|
+
tree = parse_string(source, label)
|
|
101
|
+
except ErrInfo as exc:
|
|
102
|
+
found = [parse_error(label, exc)]
|
|
103
|
+
else:
|
|
104
|
+
found = _lint_script(tree, script_path=None if stdin else label)
|
|
105
|
+
per_file.append((label, sorted(filter_issues(found, select, ignore), key=lambda i: (i.line, i.code))))
|
|
106
|
+
|
|
107
|
+
reported = [issue for _, issues in per_file for issue in issues]
|
|
108
|
+
|
|
109
|
+
if output_format == "json":
|
|
110
|
+
if statistics:
|
|
111
|
+
rows = [
|
|
112
|
+
{"code": rule.code, "rule": rule.name, "severity": rule.severity, "count": n}
|
|
113
|
+
for rule, n in rule_counts(reported)
|
|
114
|
+
]
|
|
115
|
+
sys.stdout.write(json.dumps(rows, indent=2) + "\n")
|
|
116
|
+
else:
|
|
117
|
+
sys.stdout.write(render_json(reported) + "\n")
|
|
118
|
+
return exit_code(reported, strict=strict)
|
|
119
|
+
|
|
120
|
+
if statistics:
|
|
121
|
+
print_statistics(per_file, len(paths))
|
|
122
|
+
elif output_format == "concise":
|
|
123
|
+
print_concise(per_file, len(paths))
|
|
124
|
+
elif output_format == "github":
|
|
125
|
+
# Workflow commands only: any other line in the step's log is noise.
|
|
126
|
+
annotations = render_github(per_file)
|
|
127
|
+
if annotations:
|
|
128
|
+
sys.stdout.write(annotations + "\n")
|
|
129
|
+
else:
|
|
130
|
+
print_text(per_file, len(paths))
|
|
131
|
+
return exit_code(reported, strict=strict)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class LintFormat(str, Enum):
|
|
135
|
+
"""Output formats for ``execsql lint``."""
|
|
136
|
+
|
|
137
|
+
text = "text"
|
|
138
|
+
concise = "concise"
|
|
139
|
+
json = "json"
|
|
140
|
+
github = "github"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.command(
|
|
144
|
+
cls=ExecsqlCommand,
|
|
145
|
+
name="lint",
|
|
146
|
+
help=(
|
|
147
|
+
"Statically check scripts for problems. No database connection is made.\n\n"
|
|
148
|
+
"Every issue names its rule code, which --select and --ignore accept, as a full "
|
|
149
|
+
"code (V001) or a prefix (V). Parse errors (P001) are always reported. Exits 1 "
|
|
150
|
+
"when any error is found; warnings alone exit 0 unless --strict is given.\n\n"
|
|
151
|
+
"Rules: https://execsql2.readthedocs.io/en/latest/reference/lint/"
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
def lint_cmd(
|
|
155
|
+
targets: list[str] = typer.Argument(
|
|
156
|
+
...,
|
|
157
|
+
metavar="FILE_OR_DIR...",
|
|
158
|
+
help=(
|
|
159
|
+
"Files or directories to check. Directories are searched recursively for *.sql files. "
|
|
160
|
+
"- reads one script from stdin."
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
select: list[str] | None = typer.Option(
|
|
164
|
+
None,
|
|
165
|
+
"--select",
|
|
166
|
+
metavar="CODES",
|
|
167
|
+
help="Report only these rules: codes or prefixes, comma-separated (e.g. V001,F). Repeatable.",
|
|
168
|
+
),
|
|
169
|
+
ignore: list[str] | None = typer.Option(
|
|
170
|
+
None,
|
|
171
|
+
"--ignore",
|
|
172
|
+
metavar="CODES",
|
|
173
|
+
help="Do not report these rules: codes or prefixes, comma-separated. Wins over --select. Repeatable.",
|
|
174
|
+
),
|
|
175
|
+
output_format: LintFormat = typer.Option(
|
|
176
|
+
LintFormat.text,
|
|
177
|
+
"--output-format",
|
|
178
|
+
help=(
|
|
179
|
+
"text groups issues by file; concise is one path:line line per issue; json is for tools; "
|
|
180
|
+
"github prints GitHub Actions annotations."
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
statistics: bool = typer.Option(
|
|
184
|
+
False,
|
|
185
|
+
"--statistics",
|
|
186
|
+
help="Show how many times each rule fired instead of listing every issue.",
|
|
187
|
+
),
|
|
188
|
+
strict: bool | None = typer.Option(
|
|
189
|
+
None,
|
|
190
|
+
"--strict/--no-strict",
|
|
191
|
+
help="Exit 1 on warnings too, not only on errors. Default: [lint] strict, else off.",
|
|
192
|
+
show_default=False,
|
|
193
|
+
),
|
|
194
|
+
script_encoding: ScriptEncodingOpt = None,
|
|
195
|
+
config_file: ConfigFileOpt = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""The ``lint`` command; its user-facing help is the decorator's ``help``."""
|
|
198
|
+
from execsql.cli.lint import resolve_selectors
|
|
199
|
+
from execsql.cli.run import _configured_script_encoding, _tool_config
|
|
200
|
+
|
|
201
|
+
if "-" in targets and len(targets) > 1:
|
|
202
|
+
raise typer.BadParameter("- (stdin) cannot be combined with other paths.", param_hint="FILE_OR_DIR")
|
|
203
|
+
if config_file and not Path(config_file).is_file():
|
|
204
|
+
_err_console.print(f"[bold red]Error:[/bold red] Config file {config_file!r} does not exist.")
|
|
205
|
+
raise typer.Exit(code=2)
|
|
206
|
+
|
|
207
|
+
conf = _tool_config(config_file)
|
|
208
|
+
# A flag replaces the [lint] setting rather than adding to it, as in ruff.
|
|
209
|
+
try:
|
|
210
|
+
selected = resolve_selectors(select)
|
|
211
|
+
ignored = resolve_selectors(ignore)
|
|
212
|
+
except ValueError as exc:
|
|
213
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
214
|
+
try:
|
|
215
|
+
if not select and conf.lint_select:
|
|
216
|
+
selected = resolve_selectors([conf.lint_select])
|
|
217
|
+
if not ignore and conf.lint_ignore:
|
|
218
|
+
ignored = resolve_selectors([conf.lint_ignore])
|
|
219
|
+
except ValueError as exc:
|
|
220
|
+
_err_console.print(f"[bold red]Error:[/bold red] \\[lint] in config: {exc}", highlight=False)
|
|
221
|
+
raise typer.Exit(code=2) from exc
|
|
222
|
+
|
|
223
|
+
raise typer.Exit(
|
|
224
|
+
code=lint_paths(
|
|
225
|
+
targets,
|
|
226
|
+
select=selected,
|
|
227
|
+
ignore=ignored,
|
|
228
|
+
output_format=output_format.value,
|
|
229
|
+
statistics=statistics,
|
|
230
|
+
encoding=script_encoding or _configured_script_encoding(conf) or "utf-8",
|
|
231
|
+
strict=conf.lint_strict if strict is None else strict,
|
|
232
|
+
),
|
|
233
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""The ``list`` command: reference lists, one subcommand each.
|
|
2
|
+
|
|
3
|
+
``execsql list metacommands|encodings|plugins|keywords``. Each list is its own
|
|
4
|
+
command so it can take options of its own. The module is not named ``list``
|
|
5
|
+
so importing it never shadows the builtin.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from execsql.cli.application import ExecsqlCommand, ExecsqlSubGroup, app
|
|
16
|
+
from execsql.cli.help import (
|
|
17
|
+
_encoding_names,
|
|
18
|
+
_metacommand_rows,
|
|
19
|
+
_plugins_data,
|
|
20
|
+
_print_encodings,
|
|
21
|
+
_print_keywords_json,
|
|
22
|
+
_print_keywords_text,
|
|
23
|
+
_print_metacommands,
|
|
24
|
+
_print_plugins,
|
|
25
|
+
)
|
|
26
|
+
from execsql.cli.options import OutputFormat, OutputFormatOpt
|
|
27
|
+
|
|
28
|
+
__all__ = ["list_app"]
|
|
29
|
+
|
|
30
|
+
list_app = typer.Typer(
|
|
31
|
+
cls=ExecsqlSubGroup,
|
|
32
|
+
name="list",
|
|
33
|
+
help="List metacommands, encodings, plugins or keywords.",
|
|
34
|
+
rich_markup_mode=None,
|
|
35
|
+
add_completion=False,
|
|
36
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
37
|
+
)
|
|
38
|
+
app.add_typer(list_app, name="list")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _json(data: object) -> None:
|
|
42
|
+
sys.stdout.write(json.dumps(data, indent=2) + "\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@list_app.command(cls=ExecsqlCommand, name="metacommands")
|
|
46
|
+
def metacommands_cmd(output_format: OutputFormatOpt = OutputFormat.text) -> None:
|
|
47
|
+
"""Every metacommand and its syntax."""
|
|
48
|
+
if output_format is OutputFormat.json:
|
|
49
|
+
_json([{"name": name, "syntax": syntax} for name, syntax in _metacommand_rows()])
|
|
50
|
+
else:
|
|
51
|
+
_print_metacommands()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@list_app.command(cls=ExecsqlCommand, name="encodings")
|
|
55
|
+
def encodings_cmd(output_format: OutputFormatOpt = OutputFormat.text) -> None:
|
|
56
|
+
"""Every character encoding name -e, -f, -g and -i accept."""
|
|
57
|
+
if output_format is OutputFormat.json:
|
|
58
|
+
_json(_encoding_names())
|
|
59
|
+
else:
|
|
60
|
+
_print_encodings()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@list_app.command(cls=ExecsqlCommand, name="plugins")
|
|
64
|
+
def plugins_cmd(output_format: OutputFormatOpt = OutputFormat.text) -> None:
|
|
65
|
+
"""Installed plugins: metacommands, exporters, importers."""
|
|
66
|
+
if output_format is OutputFormat.json:
|
|
67
|
+
_json(_plugins_data())
|
|
68
|
+
else:
|
|
69
|
+
_print_plugins()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@list_app.command(cls=ExecsqlCommand, name="keywords")
|
|
73
|
+
def keywords_cmd(output_format: OutputFormatOpt = OutputFormat.text) -> None:
|
|
74
|
+
"""The full vocabulary: metacommands by category, conditions, CONFIG options,
|
|
75
|
+
export formats, database types, variable patterns. Its JSON is what editor
|
|
76
|
+
tooling reads.
|
|
77
|
+
"""
|
|
78
|
+
if output_format is OutputFormat.json:
|
|
79
|
+
_print_keywords_json()
|
|
80
|
+
else:
|
|
81
|
+
_print_keywords_text()
|