Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Upcoming (TBD)
Features
---------
* Add alternative interface `/favorite` for favorite queries.
* Add `/dsn edit` subcommand to edit an existing DSN.


Bug Fixes
Expand Down
8 changes: 4 additions & 4 deletions mycli/packages/completion_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,10 +879,10 @@ def suggest_special(text: str) -> list[dict[str, Any]]:

if cmd.lower() in (r'\dsn', '/dsn'):
dsn_arguments = _arg.split(maxsplit=1)
completing_delete_target = (len(dsn_arguments) == 1 and text[-1].isspace()) or (len(dsn_arguments) == 2 and not text[-1].isspace())
if dsn_arguments and dsn_arguments[0].lower() == 'delete' and completing_delete_target:
completing_alias_target = (len(dsn_arguments) == 1 and text[-1].isspace()) or (len(dsn_arguments) == 2 and not text[-1].isspace())
if dsn_arguments and dsn_arguments[0].lower() in ('edit', 'delete') and completing_alias_target:
return [{'type': 'dsn_alias'}]
if dsn_arguments and dsn_arguments[0].lower() == 'delete' and len(dsn_arguments) == 2:
if dsn_arguments and dsn_arguments[0].lower() in ('edit', 'delete') and len(dsn_arguments) == 2:
return []
if dsn_arguments and dsn_arguments[0].lower() == 'save':
completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
Expand All @@ -903,7 +903,7 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
if completing_option:
return [{'type': 'special_subcommand', 'subcommands': [option]}]
return []
if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'delete'}:
if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'edit', 'delete'}:
return []
return [{'type': 'special_subcommand', 'subcommands': list(DSN_SUBCOMMANDS)}]

Expand Down
5 changes: 4 additions & 1 deletion mycli/packages/special/dsn_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
if TYPE_CHECKING:
from mycli.client import MyCli

DSN_SUBCOMMANDS = {'help', 'list', 'show', 'save', 'delete'}
DSN_SUBCOMMANDS = {'help', 'list', 'show', 'save', 'edit', 'delete'}
INVALID_DSN_ALIAS_ERROR = 'Error: DSN aliases cannot start with a dash.'
MISSING = object()

Expand Down Expand Up @@ -84,6 +84,9 @@ class DsnAliases:
│ rocks │ mysql://mycli@localhost/mysql?prompt=%5Cd%3E%5C_ │
└───────┴──────────────────────────────────────────────────┘

# Edit a DSN saved alias in an external editor.
mysql> /dsn edit rocks

# Delete a DSN alias.
mysql> /dsn delete rocks
"""
Expand Down
29 changes: 27 additions & 2 deletions mycli/packages/special/iocommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,8 @@ def _delete_favorite_query(arg: str, usage: str) -> list[SQLResult]:

@special_command(
r'\dsn',
'/dsn <help|list|show|save|delete>',
'Manage saved DSNs.',
'/dsn <help|list|show|save|edit|delete>',
'Manage saved DSNs. See /dsn help.',
arg_type=ArgType.PARSED_QUERY,
case_sensitive=False,
)
Expand Down Expand Up @@ -685,6 +685,13 @@ def dsn(
dsn = DsnAliases.instance.dsn_more(dsn)
status = DsnAliases.instance.save(alias, dsn)
return [SQLResult(status=status)]
elif args and args[0].lower() == 'edit':
if len(args) != 2:
return [SQLResult(status='Error: a single alias-name argument is required to edit.')]
alias = args[1]
if not is_valid_dsn_alias(alias):
return [SQLResult(status=INVALID_DSN_ALIAS_ERROR)]
return _edit_dsn_alias(alias)
elif args and args[0].lower() == 'delete':
if len(args) != 2:
return [SQLResult(status='Error: a single alias-name argument is required to delete.')]
Expand All @@ -705,6 +712,24 @@ def dsn(
return [SQLResult(preamble=DsnAliases.instance.usage)]


def _edit_dsn_alias(alias: str) -> list[SQLResult]:
dsn = DsnAliases.instance.get(alias)
if dsn is None:
return [SQLResult(status=f'No DSN alias: {alias}')]

try:
edited_dsn = click.edit(dsn)
if edited_dsn is None:
return [SQLResult(status=f'{alias}: Not Changed.')]
DsnAliases.instance.save(alias, edited_dsn.strip())
except KeyboardInterrupt:
return [SQLResult(status=f'{alias}: Edit Cancelled.')]
except (click.ClickException, OSError) as error:
return [SQLResult(status=f'Unable to edit DSN alias "{alias}": {error}')]

return [SQLResult(status=f'{alias}: Edited.')]


@special_command(
"system",
"/system [-r] <command>",
Expand Down
84 changes: 42 additions & 42 deletions test/features/fixture_data/help_commands.txt
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
+-----------------+----------+--------------------------------------+-------------------------------------------------------------+
| Command | Shortcut | Usage | Description |
+-----------------+----------+--------------------------------------+-------------------------------------------------------------+
| /bug | <null> | /bug | File a bug on GitHub. |
| /clip | <null> | /clip | <query>\clip | Copy query to the system clipboard. |
| /config | <null> | /config <help|get|search|edit> [key] | Inspect settings from config files. |
| /connect | /r | /connect [database] | Reconnect to the server, optionally switching databases. |
| /delimiter | <null> | /delimiter <string> | Change end-of-statement delimiter. |
| /dsn | <null> | /dsn <help|list|show|save|delete> | Manage saved DSNs. |
| /dt | <null> | /dt[+] [table] | List or describe tables. |
| /edit | /e | /edit <filename> | <query>\edit | Edit query with editor (uses $VISUAL or $EDITOR). |
| /exit | /q | /exit | Exit. |
| /f | <null> | /f [name [args..] [--key=value]] | List or execute favorite queries. |
| /favorite | <null> | /favorite <command> | Alternative favorite query interface. See /favorite help. |
| /fd | <null> | /fd <name> | Delete a favorite query. |
| /fs | <null> | /fs <name> <query> | Save a favorite query. |
| \g | <null> | <query>\g | Display query results (mnemonic: go). |
| \G | <null> | <query>\G | Display query results vertically. |
| /help | /? | /help [term] | Show this table, or search for help on a term. |
| /l | <null> | /l | List databases. |
| /llm | /ai | /llm [arguments] | Interrogate an LLM. See "/llm help". |
| /nopager | /n | /nopager | Disable pager; print to stdout. |
| /notee | <null> | /notee | Stop writing results to an output file. |
| /nowarnings | /w | /nowarnings | Disable automatic warnings display. |
| /once | /o | /once [-o] <filename> | Append next result to an output file (overwrite using -o). |
| /pager | /P | /pager [command] | Set pager to [command]. Print query results via pager. |
| /pipe_once | /| | /pipe_once <command> | Send next result to a subprocess. |
| /prompt | /R | /prompt [string] | Show or change prompt format. |
| /quit | /q | /quit | Quit. |
| /redirectformat | /Tr | /redirectformat <format> | Change the table format used to output redirected results. |
| /rehash | /# | /rehash | Refresh auto-completions. |
| /source | /. | /source <filename> | Execute queries from a file. |
| /status | /s | /status | Get status information from the server. |
| /system | <null> | /system [-r] <command> | Execute a system shell command (raw mode with -r). |
| /tableformat | /T | /tableformat <format> | Change the table format used to output interactive results. |
| /tee | <null> | /tee [-o] <filename> | Append all results to an output file (overwrite using -o). |
| /timing | /t | /timing | Toggle timing of queries. |
| /use | /u | /use <database> | Change to a new database. |
| /warnings | /W | /warnings | Enable automatic warnings display. |
| /watch | <null> | /watch [seconds] [-c] <query> | Execute query every [seconds] seconds (5 by default). |
| \x | <null> | <query>\x | Display query results in an explorer rather than a pager. |
+-----------------+----------+--------------------------------------+-------------------------------------------------------------+
+-----------------+----------+----------------------------------------+-------------------------------------------------------------+
| Command | Shortcut | Usage | Description |
+-----------------+----------+----------------------------------------+-------------------------------------------------------------+
| /bug | <null> | /bug | File a bug on GitHub. |
| /clip | <null> | /clip | <query>\clip | Copy query to the system clipboard. |
| /config | <null> | /config <help|get|search|edit> [key] | Inspect settings from config files. |
| /connect | /r | /connect [database] | Reconnect to the server, optionally switching databases. |
| /delimiter | <null> | /delimiter <string> | Change end-of-statement delimiter. |
| /dsn | <null> | /dsn <help|list|show|save|edit|delete> | Manage saved DSNs. See /dsn help. |
| /dt | <null> | /dt[+] [table] | List or describe tables. |
| /edit | /e | /edit <filename> | <query>\edit | Edit query with editor (uses $VISUAL or $EDITOR). |
| /exit | /q | /exit | Exit. |
| /f | <null> | /f [name [args..] [--key=value]] | List or execute favorite queries. |
| /favorite | <null> | /favorite <command> | Alternative favorite query interface. See /favorite help. |
| /fd | <null> | /fd <name> | Delete a favorite query. |
| /fs | <null> | /fs <name> <query> | Save a favorite query. |
| \g | <null> | <query>\g | Display query results (mnemonic: go). |
| \G | <null> | <query>\G | Display query results vertically. |
| /help | /? | /help [term] | Show this table, or search for help on a term. |
| /l | <null> | /l | List databases. |
| /llm | /ai | /llm [arguments] | Interrogate an LLM. See "/llm help". |
| /nopager | /n | /nopager | Disable pager; print to stdout. |
| /notee | <null> | /notee | Stop writing results to an output file. |
| /nowarnings | /w | /nowarnings | Disable automatic warnings display. |
| /once | /o | /once [-o] <filename> | Append next result to an output file (overwrite using -o). |
| /pager | /P | /pager [command] | Set pager to [command]. Print query results via pager. |
| /pipe_once | /| | /pipe_once <command> | Send next result to a subprocess. |
| /prompt | /R | /prompt [string] | Show or change prompt format. |
| /quit | /q | /quit | Quit. |
| /redirectformat | /Tr | /redirectformat <format> | Change the table format used to output redirected results. |
| /rehash | /# | /rehash | Refresh auto-completions. |
| /source | /. | /source <filename> | Execute queries from a file. |
| /status | /s | /status | Get status information from the server. |
| /system | <null> | /system [-r] <command> | Execute a system shell command (raw mode with -r). |
| /tableformat | /T | /tableformat <format> | Change the table format used to output interactive results. |
| /tee | <null> | /tee [-o] <filename> | Append all results to an output file (overwrite using -o). |
| /timing | /t | /timing | Toggle timing of queries. |
| /use | /u | /use <database> | Change to a new database. |
| /warnings | /W | /warnings | Enable automatic warnings display. |
| /watch | <null> | /watch [seconds] [-c] <query> | Execute query every [seconds] seconds (5 by default). |
| \x | <null> | <query>\x | Display query results in an explorer rather than a pager. |
+-----------------+----------+----------------------------------------+-------------------------------------------------------------+
3 changes: 3 additions & 0 deletions test/pytests/test_completion_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,9 @@ def test_suggest_type_handles_parser_results_shorter_than_cursor(monkeypatch):
('/dsn delete ', [{'type': 'dsn_alias'}]),
('/dsn delete pro', [{'type': 'dsn_alias'}]),
('/dsn delete prod ', []),
('/dsn edit ', [{'type': 'dsn_alias'}]),
('/dsn edit pro', [{'type': 'dsn_alias'}]),
('/dsn edit prod ', []),
('/dsn show', []),
('/dsn show ', [{'type': 'special_subcommand', 'subcommands': ['--more']}]),
('/dsn show --m', [{'type': 'special_subcommand', 'subcommands': ['--more']}]),
Expand Down
7 changes: 4 additions & 3 deletions test/pytests/test_smart_completion_public_schema_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ def test_dsn_subcommand_completion(completer, complete_event):
text = '/dsn '
result = completer.get_completions(Document(text=text, cursor_position=len(text)), complete_event)

assert {completion.text for completion in result} == {'help', 'list', 'show', 'save', 'delete'}
assert {completion.text for completion in result} == {'help', 'list', 'show', 'save', 'edit', 'delete'}


def test_dsn_delete_alias_completion(completer, complete_event, monkeypatch):
@pytest.mark.parametrize('command', ['edit', 'delete'])
def test_dsn_alias_completion(completer, complete_event, monkeypatch, command):
import mycli.sqlcompleter as sqlcompleter

monkeypatch.setattr(
Expand All @@ -122,7 +123,7 @@ def test_dsn_delete_alias_completion(completer, complete_event, monkeypatch):
SimpleNamespace(list=lambda: ['prod', 'staging']),
raising=False,
)
text = '/dsn delete pro'
text = f'/dsn {command} pro'
result = completer.get_completions(Document(text=text, cursor_position=len(text)), complete_event)

assert list(result) == [Completion(text='prod', start_position=-3)]
Expand Down
Loading
Loading