mycli 2.7.0__tar.gz → 2.9.0__tar.gz
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.
- {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/publish.yml +1 -1
- {mycli-2.7.0 → mycli-2.9.0}/AGENTS.md +1 -0
- {mycli-2.7.0 → mycli-2.9.0}/MANIFEST.in +1 -0
- {mycli-2.7.0/mycli.egg-info → mycli-2.9.0}/PKG-INFO +21 -3
- {mycli-2.7.0 → mycli-2.9.0}/README.md +5 -2
- {mycli-2.7.0 → mycli-2.9.0}/changelog.md +45 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/TIPS +1 -1
- {mycli-2.7.0 → mycli-2.9.0}/mycli/cli_runner.py +114 -40
- {mycli-2.7.0 → mycli-2.9.0}/mycli/client.py +1 -1
- {mycli-2.7.0 → mycli-2.9.0}/mycli/client_commands.py +8 -6
- {mycli-2.7.0 → mycli-2.9.0}/mycli/constants.py +20 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main.py +24 -5
- mycli-2.9.0/mycli/main_modes/completions.py +16 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/list_dsn.py +4 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/repl.py +10 -2
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/completion_engine.py +19 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/__init__.py +7 -0
- mycli-2.9.0/mycli/packages/special/dsn_aliases.py +173 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/iocommands.py +15 -6
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/llm.py +4 -0
- mycli-2.9.0/mycli/resources/completions/bash/mycli +169 -0
- mycli-2.9.0/mycli/resources/completions/fish/mycli.fish +132 -0
- mycli-2.9.0/mycli/resources/completions/zsh/_mycli +161 -0
- {mycli-2.7.0 → mycli-2.9.0/mycli.egg-info}/PKG-INFO +21 -3
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/SOURCES.txt +8 -1
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/scm_file_list.json +8 -1
- mycli-2.9.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.7.0 → mycli-2.9.0}/pyproject.toml +17 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/environment.py +4 -18
- {mycli-2.7.0 → mycli-2.9.0}/test/features/fixture_data/help_commands.txt +1 -1
- mycli-2.9.0/test/pytests/test_bash_completion.py +151 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_cli_runner.py +367 -18
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_commands.py +53 -3
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completion_engine.py +16 -0
- mycli-2.9.0/test/pytests/test_dsn_aliases.py +283 -0
- mycli-2.9.0/test/pytests/test_fish_completion.py +123 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main.py +41 -4
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_list_dsn.py +8 -2
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_smart_completion_public_schema_only.py +37 -2
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_iocommands.py +62 -2
- mycli-2.9.0/test/pytests/test_zsh_completion.py +156 -0
- mycli-2.7.0/mycli/packages/special/dsn_aliases.py +0 -66
- mycli-2.7.0/mycli.egg-info/scm_version.json +0 -8
- mycli-2.7.0/test/pytests/test_dsn_aliases.py +0 -102
- {mycli-2.7.0 → mycli-2.9.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/dependabot.yml +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/ci.yml +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/typecheck.yml +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/.gitignore +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/AUTHORS.rst +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/CONTRIBUTING.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/LICENSE.txt +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/SPONSORS.rst +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/key_bindings.rst +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/llm.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/total_histogram.png +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/doc/transforms.md +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/AUTHORS +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/SPONSORS +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/app_state.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/clibuffer.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/client_connection.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/client_query.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/clistyle.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/compat.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/config.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/key_bindings.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/lexer.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/myclirc +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/output.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/filepaths.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/polars_transform.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/favoritequeries.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/main.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/password_sources.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/sqlcompleter.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/sqlexecute.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/ssh_tunnel.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/types.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli/vault.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/requires.txt +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/setup.cfg +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/basic_commands.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/connection.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/crud_database.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/crud_table.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/db_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/iocommands.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/named_queries.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/specials.feature +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/connection.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/specials.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/features/wrappager.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/myclirc +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/mylogin.cnf +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/conftest.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_connection.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_query.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_config.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_lexer.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_repl.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_output.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_password_sources.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_polars_transform.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_main.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlexecute.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_vault.py +0 -0
- {mycli-2.7.0 → mycli-2.9.0}/test/utils.py +0 -0
|
@@ -49,6 +49,7 @@ A command line client for MySQL with auto-completion and syntax highlighting.
|
|
|
49
49
|
├── mycli/packages/string_utils.py # generic string utilities
|
|
50
50
|
├── mycli/packages/tabular_output/ # extends cli_helper with additional output formats
|
|
51
51
|
├── mycli/password_sources.py # password sources and precedence
|
|
52
|
+
├── mycli/resources/completions/ # shell completions for CLI interface
|
|
52
53
|
├── mycli/ssh_tunnel.py # connecting over a tunnel with `--ssh-jump`
|
|
53
54
|
├── mycli/schema_prefetcher.py # background prefetcher for multi-schema auto-completion
|
|
54
55
|
├── mycli/sqlcompleter.py # offers SQL completions
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: CLI for MySQL Database. With auto-completion and syntax highlighting.
|
|
5
5
|
Author: Mycli Core Team
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -9,6 +9,21 @@ Project-URL: Documentation, https://mycli.net/docs
|
|
|
9
9
|
Project-URL: Source, https://github.com/dbcli/mycli
|
|
10
10
|
Project-URL: Issues, https://github.com/dbcli/mycli/issues
|
|
11
11
|
Project-URL: Changelog, https://github.com/dbcli/mycli/blob/main/changelog.md
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: SQL
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
26
|
+
Classifier: Topic :: Software Development
|
|
12
27
|
Requires-Python: >=3.10
|
|
13
28
|
Description-Content-Type: text/markdown
|
|
14
29
|
License-File: LICENSE.txt
|
|
@@ -57,6 +72,7 @@ Dynamic: license-file
|
|
|
57
72
|
# mycli
|
|
58
73
|
|
|
59
74
|
[](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
|
|
75
|
+
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
60
76
|
|
|
61
77
|
A command line client for MySQL with auto-completion and syntax highlighting.
|
|
62
78
|
|
|
@@ -64,8 +80,10 @@ Homepage: [https://mycli.net](https://mycli.net)
|
|
|
64
80
|
|
|
65
81
|
Documentation: [https://mycli.net/docs](https://mycli.net/docs)
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
<div align="center">
|
|
84
|
+
<img alt="Completion" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png">
|
|
85
|
+
<img alt="CompletionGif" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif">
|
|
86
|
+
</div>
|
|
69
87
|
|
|
70
88
|
Mycli is compatible with MySQL, MariaDB, Percona, TiDB, and Apache Doris.
|
|
71
89
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# mycli
|
|
2
2
|
|
|
3
3
|
[](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
|
|
4
|
+
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
4
5
|
|
|
5
6
|
A command line client for MySQL with auto-completion and syntax highlighting.
|
|
6
7
|
|
|
@@ -8,8 +9,10 @@ Homepage: [https://mycli.net](https://mycli.net)
|
|
|
8
9
|
|
|
9
10
|
Documentation: [https://mycli.net/docs](https://mycli.net/docs)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
<div align="center">
|
|
13
|
+
<img alt="Completion" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png">
|
|
14
|
+
<img alt="CompletionGif" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif">
|
|
15
|
+
</div>
|
|
13
16
|
|
|
14
17
|
Mycli is compatible with MySQL, MariaDB, Percona, TiDB, and Apache Doris.
|
|
15
18
|
|
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
2.9.0 (2026/08/01)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
---------
|
|
6
|
+
* Improve filename completions for zsh.
|
|
7
|
+
* Improve acceptance of a literal DSN or alias as a positional argument.
|
|
8
|
+
* Improve acceptance of a literal DSN or alias as a `--database` argument.
|
|
9
|
+
* Add bash completions which can complete DSN aliases.
|
|
10
|
+
* Add fish completions which can complete DSN aliases.
|
|
11
|
+
* Support Python 3.15 lazy imports for startup performance.
|
|
12
|
+
* Add a `--completions` argument to emit a shell completion script.
|
|
13
|
+
* Ability to set the `prompt` value as a DSN query parameter.
|
|
14
|
+
* Add `--more` option to some `/dsn` subcommands, showing more DSN query parameters.
|
|
15
|
+
* Let `/prompt` with no arguments display the current prompt format string.
|
|
16
|
+
* Allow the `/prompt` argument to be quoted.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Bugfixes
|
|
20
|
+
---------
|
|
21
|
+
* Disallow DSN aliases starting with `-`, because of ambiguity.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Documentation
|
|
25
|
+
---------
|
|
26
|
+
* Add license badge to `README.md`.
|
|
27
|
+
* Center images in `README.md`.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
2.8.0 (2026/07/31)
|
|
31
|
+
==============
|
|
32
|
+
|
|
33
|
+
Features
|
|
34
|
+
---------
|
|
35
|
+
* Render tab literals as four spaces in the REPL.
|
|
36
|
+
* Let cursor shape respond to vi modes: _eg_ bar for insert mode.
|
|
37
|
+
* Add zsh completions which can complete DSN aliases.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Internal
|
|
41
|
+
---------
|
|
42
|
+
* Add Trove classifiers for PyPi.
|
|
43
|
+
* Don't let behave tests refer to real home-directory resources.
|
|
44
|
+
|
|
45
|
+
|
|
1
46
|
2.7.0 (2026/07/25)
|
|
2
47
|
==============
|
|
3
48
|
|
|
@@ -90,7 +90,7 @@ use /tee or /notee to write/stop-writing results to a output file!
|
|
|
90
90
|
|
|
91
91
|
/P or /pager sets the pager. Try "/pager less"!
|
|
92
92
|
|
|
93
|
-
/R or /prompt changes the prompt format!
|
|
93
|
+
/R or /prompt shows or changes the prompt format!
|
|
94
94
|
|
|
95
95
|
/Tr or /redirectformat changes the table format for redirects!
|
|
96
96
|
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
__lazy_modules__ = [
|
|
4
|
+
'io',
|
|
5
|
+
're',
|
|
6
|
+
'sys',
|
|
7
|
+
'typing',
|
|
8
|
+
'urllib.parse',
|
|
9
|
+
'click',
|
|
10
|
+
'mycli.config',
|
|
11
|
+
'mycli.constants',
|
|
12
|
+
'mycli.main_modes.batch',
|
|
13
|
+
'mycli.main_modes.checkup',
|
|
14
|
+
'mycli.main_modes.completions',
|
|
15
|
+
'mycli.main_modes.execute',
|
|
16
|
+
'mycli.main_modes.list_dsn',
|
|
17
|
+
'mycli.packages.cli_utils',
|
|
18
|
+
'mycli.packages.special.dsn_aliases',
|
|
19
|
+
'mycli.password_sources',
|
|
20
|
+
'mycli.vault',
|
|
21
|
+
]
|
|
22
|
+
|
|
3
23
|
import os
|
|
4
24
|
import re
|
|
5
25
|
import sys
|
|
@@ -9,12 +29,14 @@ from urllib.parse import parse_qs, unquote, urlparse
|
|
|
9
29
|
import click
|
|
10
30
|
|
|
11
31
|
from mycli.config import str_to_bool
|
|
12
|
-
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
|
|
32
|
+
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL, KNOWN_DSN_QUERY_PARAMS
|
|
13
33
|
from mycli.main_modes.batch import main_batch_from_stdin, main_batch_with_progress_bar, main_batch_without_progress_bar
|
|
14
34
|
from mycli.main_modes.checkup import main_checkup
|
|
35
|
+
from mycli.main_modes.completions import main_completions
|
|
15
36
|
from mycli.main_modes.execute import main_execute_from_cli
|
|
16
37
|
from mycli.main_modes.list_dsn import main_list_dsn
|
|
17
38
|
from mycli.packages.cli_utils import is_valid_connection_scheme
|
|
39
|
+
from mycli.packages.special.dsn_aliases import INVALID_DSN_ALIAS_ERROR, is_valid_dsn_alias
|
|
18
40
|
from mycli.password_sources import PasswordCandidates
|
|
19
41
|
from mycli.vault import (
|
|
20
42
|
DEFAULT_VAULT_EXECUTABLE,
|
|
@@ -29,25 +51,6 @@ if TYPE_CHECKING:
|
|
|
29
51
|
|
|
30
52
|
ClientFactory = Callable[..., Any]
|
|
31
53
|
ENV_VAR_PATTERN = re.compile(r'^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$')
|
|
32
|
-
KNOWN_DSN_QUERY_PARAMS = {
|
|
33
|
-
'character_set',
|
|
34
|
-
'keepalive_ticks',
|
|
35
|
-
'socket',
|
|
36
|
-
'ssh_jump',
|
|
37
|
-
'ssl_ca',
|
|
38
|
-
'ssl_capath',
|
|
39
|
-
'ssl_cert',
|
|
40
|
-
'ssl_cipher',
|
|
41
|
-
'ssl_key',
|
|
42
|
-
'ssl_mode',
|
|
43
|
-
'ssl_verify_server_cert',
|
|
44
|
-
'tls_version',
|
|
45
|
-
'vault_address',
|
|
46
|
-
'vault_mount',
|
|
47
|
-
'vault_secret',
|
|
48
|
-
'vault_password_field',
|
|
49
|
-
'vault_username_field',
|
|
50
|
-
}
|
|
51
54
|
|
|
52
55
|
|
|
53
56
|
class DsnAliasEnvVarError(ValueError):
|
|
@@ -124,6 +127,10 @@ def expand_dsn_alias_env_vars(
|
|
|
124
127
|
def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> None:
|
|
125
128
|
from mycli import main as main_module
|
|
126
129
|
|
|
130
|
+
if cli_args.completions:
|
|
131
|
+
main_completions(cli_args.completions)
|
|
132
|
+
return
|
|
133
|
+
|
|
127
134
|
cli_verbosity = main_module.preprocess_cli_args(cli_args, is_valid_connection_scheme)
|
|
128
135
|
|
|
129
136
|
mycli = client_factory(
|
|
@@ -162,43 +169,108 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
162
169
|
if cli_args.list_dsn:
|
|
163
170
|
sys.exit(main_list_dsn(mycli))
|
|
164
171
|
|
|
165
|
-
# Choose which ever one has a valid value.
|
|
166
|
-
database = cli_args.dbname or cli_args.database
|
|
167
|
-
|
|
168
172
|
dsn_uri = None
|
|
169
173
|
dsn_password: str | None = None
|
|
174
|
+
database: str | None = ''
|
|
175
|
+
explicit_dsn = bool(cli_args.dsn)
|
|
170
176
|
|
|
171
|
-
#
|
|
172
|
-
# todo why is port tested but not socket?
|
|
173
|
-
truthy_password = (
|
|
174
|
-
cli_args.password not in (None, EMPTY_PASSWORD_FLAG_SENTINEL)
|
|
175
|
-
or cli_args.password_file is not None
|
|
176
|
-
or os.environ.get('MYSQL_PWD') is not None
|
|
177
|
-
)
|
|
177
|
+
# This could be better written, but it is all made harder by the fact that a DSN is recognized after --database
|
|
178
178
|
if (
|
|
179
|
-
|
|
180
|
-
and "://" not in
|
|
181
|
-
and
|
|
179
|
+
cli_args.positional_database
|
|
180
|
+
and "://" not in cli_args.positional_database
|
|
181
|
+
and cli_args.positional_database in mycli.config.get("alias_dsn", {})
|
|
182
|
+
):
|
|
183
|
+
if any([
|
|
182
184
|
cli_args.user,
|
|
183
|
-
truthy_password,
|
|
184
185
|
cli_args.host,
|
|
185
186
|
cli_args.port,
|
|
187
|
+
cli_args.socket,
|
|
186
188
|
cli_args.login_path,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
cli_args.dsn,
|
|
190
|
+
]):
|
|
191
|
+
if cli_verbosity:
|
|
192
|
+
click.secho(
|
|
193
|
+
f'Interpreting ambiguous positional database/DSN-alias argument "{cli_args.positional_database}" as a database name.',
|
|
194
|
+
err=True,
|
|
195
|
+
fg='yellow',
|
|
196
|
+
)
|
|
197
|
+
database = cli_args.database or cli_args.positional_database
|
|
198
|
+
else:
|
|
199
|
+
if not is_valid_dsn_alias(cli_args.positional_database):
|
|
200
|
+
click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
cli_args.dsn, database = cli_args.positional_database, cli_args.database or ''
|
|
203
|
+
elif cli_args.positional_database and '://' in cli_args.positional_database:
|
|
204
|
+
if cli_args.dsn:
|
|
205
|
+
click.secho(
|
|
206
|
+
f'Ignoring duplicate positional DSN argument "{cli_args.positional_database}".',
|
|
207
|
+
err=True,
|
|
208
|
+
fg='yellow',
|
|
209
|
+
)
|
|
210
|
+
database = cli_args.database or ''
|
|
211
|
+
else:
|
|
212
|
+
cli_args.dsn, database = cli_args.positional_database, cli_args.database or ''
|
|
213
|
+
elif cli_args.positional_database:
|
|
214
|
+
if cli_args.database:
|
|
215
|
+
click.secho(
|
|
216
|
+
f'Ignoring ambiguous positional database argument "{cli_args.positional_database}" since --database was given.',
|
|
217
|
+
err=True,
|
|
218
|
+
fg='yellow',
|
|
219
|
+
)
|
|
220
|
+
database = cli_args.database
|
|
221
|
+
else:
|
|
222
|
+
database = cli_args.positional_database
|
|
223
|
+
elif cli_args.database:
|
|
224
|
+
database = cli_args.database
|
|
191
225
|
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
database_from_option = bool(cli_args.database and database == cli_args.database)
|
|
227
|
+
|
|
228
|
+
if database and '://' not in database and database in mycli.config.get('alias_dsn', {}):
|
|
229
|
+
if any([
|
|
230
|
+
cli_args.user,
|
|
231
|
+
cli_args.host,
|
|
232
|
+
cli_args.port,
|
|
233
|
+
cli_args.socket,
|
|
234
|
+
cli_args.login_path,
|
|
235
|
+
cli_args.dsn,
|
|
236
|
+
]):
|
|
237
|
+
if database_from_option and (cli_args.verbose or 0) >= 2:
|
|
238
|
+
click.secho(
|
|
239
|
+
f'Interpreting ambiguous --database argument "{cli_args.database}" as a database name, not a DSN alias, '
|
|
240
|
+
' since other connection coordinates were given.',
|
|
241
|
+
err=True,
|
|
242
|
+
fg='yellow',
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
if not is_valid_dsn_alias(database):
|
|
246
|
+
click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
cli_args.dsn, database = database, ''
|
|
249
|
+
elif database and '://' in database:
|
|
250
|
+
if explicit_dsn:
|
|
251
|
+
click.secho(
|
|
252
|
+
f'Ignoring duplicate DSN argument in --database "{database}".',
|
|
253
|
+
err=True,
|
|
254
|
+
fg='yellow',
|
|
255
|
+
)
|
|
256
|
+
database = ''
|
|
257
|
+
else:
|
|
258
|
+
cli_args.dsn = ''
|
|
259
|
+
dsn_uri, database = database, ''
|
|
194
260
|
|
|
195
261
|
if cli_args.dsn:
|
|
262
|
+
if not is_valid_dsn_alias(cli_args.dsn):
|
|
263
|
+
click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
|
|
264
|
+
sys.exit(1)
|
|
196
265
|
try:
|
|
197
266
|
dsn_uri = mycli.config["alias_dsn"][cli_args.dsn]
|
|
198
267
|
except KeyError:
|
|
199
268
|
is_valid_scheme, scheme = is_valid_connection_scheme(cli_args.dsn)
|
|
200
269
|
if is_valid_scheme:
|
|
201
270
|
dsn_uri = cli_args.dsn
|
|
271
|
+
elif '://' in cli_args.dsn:
|
|
272
|
+
click.secho(f'Error: Unknown connection scheme provided for DSN URI ({scheme}://)', err=True, fg='red')
|
|
273
|
+
sys.exit(1)
|
|
202
274
|
else:
|
|
203
275
|
click.secho(
|
|
204
276
|
"Could not find the specified DSN in the config file. Please check the \"[alias_dsn]\" section in your myclirc.",
|
|
@@ -283,6 +355,8 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
283
355
|
cli_args.keepalive_ticks = int(params[0])
|
|
284
356
|
if params := dsn_params.get('character_set'):
|
|
285
357
|
cli_args.character_set = cli_args.character_set or params[0]
|
|
358
|
+
if params := dsn_params.get('prompt'):
|
|
359
|
+
mycli.prompt_format = cli_args.prompt or params[0] or mycli.prompt_format
|
|
286
360
|
if params := dsn_params.get('ssh_jump'):
|
|
287
361
|
cli_args.ssh_jump = cli_args.ssh_jump or params[0]
|
|
288
362
|
if params := dsn_params.get('vault_address'):
|
|
@@ -116,7 +116,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
116
116
|
self.default_keepalive_ticks = c['connection'].as_int('default_keepalive_ticks')
|
|
117
117
|
|
|
118
118
|
FavoriteQueries.instance = FavoriteQueries.from_config(self.config)
|
|
119
|
-
DsnAliases.instance = DsnAliases.from_config(self.config)
|
|
119
|
+
DsnAliases.instance = DsnAliases.from_config(self.config, self)
|
|
120
120
|
|
|
121
121
|
self.dsn_alias: str | None = None
|
|
122
122
|
self.main_formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
|
|
@@ -81,8 +81,8 @@ class ClientCommandsMixin:
|
|
|
81
81
|
special.register_special_command(
|
|
82
82
|
self.change_prompt_format,
|
|
83
83
|
"prompt",
|
|
84
|
-
"/prompt
|
|
85
|
-
"
|
|
84
|
+
"/prompt [string]",
|
|
85
|
+
"Show or change prompt format.",
|
|
86
86
|
case_sensitive=True,
|
|
87
87
|
aliases=[SpecialCommandAlias("\\R", case_sensitive=True)],
|
|
88
88
|
)
|
|
@@ -161,14 +161,16 @@ class ClientCommandsMixin:
|
|
|
161
161
|
|
|
162
162
|
def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
|
|
163
163
|
"""
|
|
164
|
-
|
|
164
|
+
Show or change the prompt format.
|
|
165
165
|
"""
|
|
166
166
|
if not arg:
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
return [SQLResult(status=f'Prompt format: "{self.prompt_format}"')]
|
|
168
|
+
|
|
169
|
+
if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] in {'\'', '"'}:
|
|
170
|
+
arg = arg[1:-1]
|
|
169
171
|
|
|
170
172
|
self.prompt_format = arg
|
|
171
|
-
return [SQLResult(status=f
|
|
173
|
+
return [SQLResult(status=f'Changed prompt format to: "{arg}"')]
|
|
172
174
|
|
|
173
175
|
def initialize_logging(self) -> None:
|
|
174
176
|
log_file = os.path.expanduser(self.config["main"]["log_file"])
|
|
@@ -4,6 +4,26 @@ DOCS_URL = f'{HOME_URL}/docs'
|
|
|
4
4
|
ISSUES_URL = f'{REPO_URL}/issues'
|
|
5
5
|
|
|
6
6
|
DEFAULT_CHARSET = 'utf8mb4'
|
|
7
|
+
KNOWN_DSN_QUERY_PARAMS = {
|
|
8
|
+
'character_set',
|
|
9
|
+
'keepalive_ticks',
|
|
10
|
+
'prompt',
|
|
11
|
+
'socket',
|
|
12
|
+
'ssh_jump',
|
|
13
|
+
'ssl_ca',
|
|
14
|
+
'ssl_capath',
|
|
15
|
+
'ssl_cert',
|
|
16
|
+
'ssl_cipher',
|
|
17
|
+
'ssl_key',
|
|
18
|
+
'ssl_mode',
|
|
19
|
+
'ssl_verify_server_cert',
|
|
20
|
+
'tls_version',
|
|
21
|
+
'vault_address',
|
|
22
|
+
'vault_mount',
|
|
23
|
+
'vault_password_field',
|
|
24
|
+
'vault_secret',
|
|
25
|
+
'vault_username_field',
|
|
26
|
+
}
|
|
7
27
|
DEFAULT_DATABASE = 'mysql'
|
|
8
28
|
DEFAULT_HOST = 'localhost'
|
|
9
29
|
DEFAULT_PORT = 3306
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
__lazy_modules__ = [
|
|
4
|
+
'dataclasses',
|
|
5
|
+
'io',
|
|
6
|
+
'sys',
|
|
7
|
+
'textwrap',
|
|
8
|
+
'typing',
|
|
9
|
+
'click',
|
|
10
|
+
'clickdc',
|
|
11
|
+
'mycli',
|
|
12
|
+
'mycli.cli_runner',
|
|
13
|
+
'mycli.client',
|
|
14
|
+
'mycli.constants',
|
|
15
|
+
'mycli.packages.cli_utils',
|
|
16
|
+
]
|
|
17
|
+
|
|
3
18
|
from dataclasses import dataclass
|
|
4
19
|
from io import TextIOWrapper
|
|
5
20
|
import os
|
|
@@ -39,8 +54,9 @@ INT_OR_STRING_CLICK_TYPE = IntOrStringClickParamType()
|
|
|
39
54
|
|
|
40
55
|
@dataclass(slots=True)
|
|
41
56
|
class CliArgs:
|
|
42
|
-
|
|
57
|
+
positional_database: str | None = clickdc.argument(
|
|
43
58
|
type=str,
|
|
59
|
+
metavar='DATABASE',
|
|
44
60
|
default=None,
|
|
45
61
|
nargs=1,
|
|
46
62
|
)
|
|
@@ -151,10 +167,9 @@ class CliArgs:
|
|
|
151
167
|
is_flag=True,
|
|
152
168
|
help='Less verbose output and feedback.',
|
|
153
169
|
)
|
|
154
|
-
|
|
170
|
+
database: str | None = clickdc.option(
|
|
155
171
|
'-D',
|
|
156
172
|
'--database',
|
|
157
|
-
'dbname',
|
|
158
173
|
type=str,
|
|
159
174
|
clickdc=None,
|
|
160
175
|
help='Database or DSN to use for the connection.',
|
|
@@ -170,6 +185,10 @@ class CliArgs:
|
|
|
170
185
|
is_flag=True,
|
|
171
186
|
help='Show list of DSN aliases configured in the [alias_dsn] section of ~/.myclirc.',
|
|
172
187
|
)
|
|
188
|
+
completions: str | None = clickdc.option(
|
|
189
|
+
type=click.Choice(['bash', 'zsh', 'fish']),
|
|
190
|
+
help='Print a completion script for the selected shell.',
|
|
191
|
+
)
|
|
173
192
|
prompt: str | None = clickdc.option(
|
|
174
193
|
'-R',
|
|
175
194
|
type=str,
|
|
@@ -382,12 +401,12 @@ def preprocess_cli_args(
|
|
|
382
401
|
cli_args: CliArgs,
|
|
383
402
|
is_valid_connection_scheme: Callable[[str], tuple[bool, str | None]],
|
|
384
403
|
) -> int:
|
|
385
|
-
if cli_args.
|
|
404
|
+
if cli_args.positional_database is None and isinstance(cli_args.password, str) and '://' in cli_args.password:
|
|
386
405
|
is_valid_scheme, scheme = is_valid_connection_scheme(cli_args.password)
|
|
387
406
|
if not is_valid_scheme:
|
|
388
407
|
click.secho(f'Error: Unknown connection scheme provided for DSN URI ({scheme}://)', err=True, fg='red')
|
|
389
408
|
sys.exit(1)
|
|
390
|
-
cli_args.
|
|
409
|
+
cli_args.positional_database = cli_args.password
|
|
391
410
|
cli_args.password = EMPTY_PASSWORD_FLAG_SENTINEL
|
|
392
411
|
|
|
393
412
|
if cli_args.resume and not cli_args.checkpoint:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import resources
|
|
4
|
+
|
|
5
|
+
import mycli as mycli_package
|
|
6
|
+
|
|
7
|
+
COMPLETION_PATHS = {
|
|
8
|
+
'bash': ('resources', 'completions', 'bash', 'mycli'),
|
|
9
|
+
'zsh': ('resources', 'completions', 'zsh', '_mycli'),
|
|
10
|
+
'fish': ('resources', 'completions', 'fish', 'mycli.fish'),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main_completions(shell: str) -> None:
|
|
15
|
+
completion_path = resources.files(mycli_package).joinpath(*COMPLETION_PATHS[shell])
|
|
16
|
+
print(completion_path.read_text(), end='')
|
|
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
+
from mycli.packages.special.dsn_aliases import is_valid_dsn_alias
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from mycli.client import MyCli
|
|
9
11
|
|
|
@@ -18,6 +20,8 @@ def main_list_dsn(mycli: 'MyCli') -> int:
|
|
|
18
20
|
click.secho(str(e), err=True, fg='red')
|
|
19
21
|
return 1
|
|
20
22
|
for alias, value in alias_dsn.items():
|
|
23
|
+
if not is_valid_dsn_alias(alias):
|
|
24
|
+
continue
|
|
21
25
|
if mycli.verbosity >= 1:
|
|
22
26
|
click.secho(f'{alias} : {value}')
|
|
23
27
|
else:
|
|
@@ -23,6 +23,7 @@ import prompt_toolkit
|
|
|
23
23
|
from prompt_toolkit.application.current import get_app
|
|
24
24
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, ThreadedAutoSuggest
|
|
25
25
|
from prompt_toolkit.completion import DynamicCompleter
|
|
26
|
+
from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig
|
|
26
27
|
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
|
|
27
28
|
from prompt_toolkit.filters import Condition, has_focus, is_done
|
|
28
29
|
from prompt_toolkit.formatted_text import (
|
|
@@ -33,7 +34,11 @@ from prompt_toolkit.formatted_text import (
|
|
|
33
34
|
to_plain_text,
|
|
34
35
|
)
|
|
35
36
|
from prompt_toolkit.key_binding import KeyBindings
|
|
36
|
-
from prompt_toolkit.layout.processors import
|
|
37
|
+
from prompt_toolkit.layout.processors import (
|
|
38
|
+
ConditionalProcessor,
|
|
39
|
+
HighlightMatchingBracketProcessor,
|
|
40
|
+
TabsProcessor,
|
|
41
|
+
)
|
|
37
42
|
from prompt_toolkit.lexers import PygmentsLexer
|
|
38
43
|
from prompt_toolkit.output import ColorDepth
|
|
39
44
|
from prompt_toolkit.shortcuts import CompleteStyle, PromptSession
|
|
@@ -583,7 +588,9 @@ def _build_prompt_session(
|
|
|
583
588
|
ConditionalProcessor(
|
|
584
589
|
processor=HighlightMatchingBracketProcessor(chars='[](){}'),
|
|
585
590
|
filter=has_focus(DEFAULT_BUFFER) & ~is_done,
|
|
586
|
-
)
|
|
591
|
+
),
|
|
592
|
+
# renders tab as four spaces rather than ^I control character
|
|
593
|
+
TabsProcessor(char1=' ', char2=' '),
|
|
587
594
|
],
|
|
588
595
|
tempfile_suffix='.sql',
|
|
589
596
|
completer=DynamicCompleter(lambda: mycli.completer),
|
|
@@ -599,6 +606,7 @@ def _build_prompt_session(
|
|
|
599
606
|
enable_system_prompt=True,
|
|
600
607
|
enable_suspend=True,
|
|
601
608
|
editing_mode=editing_mode,
|
|
609
|
+
cursor=ModalCursorShapeConfig() if editing_mode == EditingMode.VI else CursorShape.BLOCK,
|
|
602
610
|
search_ignore_case=True,
|
|
603
611
|
)
|
|
604
612
|
|
|
@@ -834,6 +834,25 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
|
|
|
834
834
|
return [{'type': 'dsn_alias'}]
|
|
835
835
|
if dsn_arguments and dsn_arguments[0].lower() == 'delete' and len(dsn_arguments) == 2:
|
|
836
836
|
return []
|
|
837
|
+
if dsn_arguments and dsn_arguments[0].lower() == 'save':
|
|
838
|
+
completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
|
|
839
|
+
len(dsn_arguments) == 2
|
|
840
|
+
and dsn_arguments[1].startswith('-')
|
|
841
|
+
and dsn_arguments[1] != '--more'
|
|
842
|
+
and ' ' not in dsn_arguments[1]
|
|
843
|
+
and not text[-1].isspace()
|
|
844
|
+
)
|
|
845
|
+
if completing_option:
|
|
846
|
+
return [{'type': 'special_subcommand', 'subcommands': ['--more']}]
|
|
847
|
+
return []
|
|
848
|
+
if dsn_arguments and dsn_arguments[0].lower() == 'show':
|
|
849
|
+
option = '--more'
|
|
850
|
+
completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
|
|
851
|
+
len(dsn_arguments) == 2 and dsn_arguments[1] != option and not text[-1].isspace()
|
|
852
|
+
)
|
|
853
|
+
if completing_option:
|
|
854
|
+
return [{'type': 'special_subcommand', 'subcommands': [option]}]
|
|
855
|
+
return []
|
|
837
856
|
if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'delete'}:
|
|
838
857
|
return []
|
|
839
858
|
return [{'type': 'special_subcommand', 'subcommands': list(DSN_SUBCOMMANDS)}]
|