diff options
Diffstat (limited to 'litecli')
-rw-r--r-- | litecli/__init__.py | 2 | ||||
-rw-r--r-- | litecli/completion_refresher.py | 5 | ||||
-rw-r--r-- | litecli/key_bindings.py | 8 | ||||
-rw-r--r-- | litecli/liteclirc | 8 | ||||
-rw-r--r-- | litecli/main.py | 67 | ||||
-rw-r--r-- | litecli/packages/completion_engine.py | 2 | ||||
-rw-r--r-- | litecli/packages/parseutils.py | 2 | ||||
-rw-r--r-- | litecli/packages/special/dbcommands.py | 5 | ||||
-rw-r--r-- | litecli/packages/special/favoritequeries.py | 1 | ||||
-rw-r--r-- | litecli/packages/special/iocommands.py | 41 | ||||
-rw-r--r-- | litecli/packages/special/utils.py | 83 | ||||
-rw-r--r-- | litecli/sqlcompleter.py | 1 | ||||
-rw-r--r-- | litecli/sqlexecute.py | 29 |
13 files changed, 181 insertions, 73 deletions
diff --git a/litecli/__init__.py b/litecli/__init__.py index 0a0a43a..fcfdf38 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.9.0" +__version__ = "1.10.0" diff --git a/litecli/completion_refresher.py b/litecli/completion_refresher.py index 9602070..bf36d03 100644 --- a/litecli/completion_refresher.py +++ b/litecli/completion_refresher.py @@ -7,7 +7,6 @@ from .sqlexecute import SQLExecute class CompletionRefresher(object): - refreshers = OrderedDict() def __init__(self): @@ -65,7 +64,7 @@ class CompletionRefresher(object): # if DB is memory, needed to use same connection executor = sqlexecute else: - # Create a new sqlexecute method to popoulate the completions. + # Create a new sqlexecute method to populate the completions. executor = SQLExecute(e.dbname) # If callbacks is a single function then push it into a list. @@ -79,7 +78,7 @@ class CompletionRefresher(object): self._restart_refresh.clear() break else: - # Break out of while loop if the for loop finishes natually + # Break out of while loop if the for loop finishes naturally # without hitting the break statement. break diff --git a/litecli/key_bindings.py b/litecli/key_bindings.py index 44d59d2..96eed50 100644 --- a/litecli/key_bindings.py +++ b/litecli/key_bindings.py @@ -81,4 +81,12 @@ def cli_bindings(cli): b = event.app.current_buffer b.complete_state = None + @kb.add("right", filter=completion_is_selected) + def _(event): + """Accept the completion that is selected in the dropdown menu.""" + _logger.debug("Detected right-arrow key.") + + b = event.app.current_buffer + b.complete_state = None + return kb diff --git a/litecli/liteclirc b/litecli/liteclirc index 4db6f3a..924b585 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -117,6 +117,12 @@ output.header = "#00ff5f bold" output.odd-row = "" output.even-row = "" - # Favorite queries. [favorite_queries] + +# Startup commands +# litecli commands or sqlite commands to be executed on startup. +# some of them will require you to have a database attached. +# they will be executed in the same order as they appear in the list. +[startup_commands] +#commands = ".tables", "pragma foreign_keys = ON;"
\ No newline at end of file diff --git a/litecli/main.py b/litecli/main.py index de279f6..e608da7 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -59,7 +59,6 @@ PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) class LiteCli(object): - default_prompt = "\\d> " max_len_prompt = 45 @@ -110,6 +109,13 @@ class LiteCli(object): fg="red", ) self.logfile = False + # Load startup commands. + try: + self.startup_commands = c["startup_commands"] + except ( + KeyError + ): # Redundant given the load_config() function that merges in the standard config, but put here to avoid fail if user do not have updated config file. + self.startup_commands = None self.completion_refresher = CompletionRefresher() @@ -230,7 +236,6 @@ class LiteCli(object): return [(None, None, None, "Changed prompt format to %s" % arg)] def initialize_logging(self): - log_file = self.config["main"]["log_file"] if log_file == "default": log_file = config_location() + "log" @@ -298,7 +303,6 @@ class LiteCli(object): return {x: get(x) for x in keys} def connect(self, database=""): - cnf = {"database": None} cnf = self.read_my_cnf_files(cnf.keys()) @@ -486,29 +490,17 @@ class LiteCli(object): except EOFError as e: raise e except KeyboardInterrupt: - # get last connection id - connection_id_to_kill = sqlexecute.connection_id - logger.debug("connection id to kill: %r", connection_id_to_kill) - # Restart connection to the database - sqlexecute.connect() try: - for title, cur, headers, status in sqlexecute.run( - "kill %s" % connection_id_to_kill - ): - status_str = str(status).lower() - if status_str.find("ok") > -1: - logger.debug( - "cancelled query, connection id: %r, sql: %r", - connection_id_to_kill, - text, - ) - self.echo("cancelled query", err=True, fg="red") + sqlexecute.conn.interrupt() except Exception as e: self.echo( "Encountered error while cancelling query: {}".format(e), err=True, fg="red", ) + else: + logger.debug("cancelled query") + self.echo("cancelled query", err=True, fg="red") except NotImplementedError: self.echo("Not Yet Implemented.", fg="yellow") except OperationalError as e: @@ -555,7 +547,6 @@ class LiteCli(object): complete_style = CompleteStyle.READLINE_LIKE with self._completer_lock: - if self.key_bindings == "vi": editing_mode = EditingMode.VI else: @@ -590,6 +581,42 @@ class LiteCli(object): search_ignore_case=True, ) + def startup_commands(): + if self.startup_commands: + if "commands" in self.startup_commands: + for command in self.startup_commands["commands"]: + try: + res = sqlexecute.run(command) + except Exception as e: + click.echo(command) + self.echo(str(e), err=True, fg="red") + else: + click.echo(command) + for title, cur, headers, status in res: + if title == "dot command not implemented": + self.echo( + "The SQLite dot command '" + + command.split(" ", 1)[0] + + "' is not yet implemented.", + fg="yellow", + ) + else: + output = self.format_output(title, cur, headers) + for line in output: + self.echo(line) + else: + self.echo( + "Could not read commands. The startup commands needs to be formatted as: \n commands = 'command1', 'command2', ...", + fg="yellow", + ) + + try: + startup_commands() + except Exception as e: + self.echo( + "Could not execute all startup commands: \n" + str(e), fg="yellow" + ) + try: while True: one_iteration() diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 0e2a30f..31a32b7 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -210,7 +210,7 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier # suggest columns that are present in more than one table return [{"type": "column", "tables": tables, "drop_unique": True}] elif p.token_first().value.lower() == "select": - # If the lparen is preceeded by a space chances are we're about to + # If the lparen is preceded by a space chances are we're about to # do a sub-select. if last_word(text_before_cursor, "all_punctuations").startswith("("): return [{"type": "keyword"}] diff --git a/litecli/packages/parseutils.py b/litecli/packages/parseutils.py index 3f5ca61..f5fdc1d 100644 --- a/litecli/packages/parseutils.py +++ b/litecli/packages/parseutils.py @@ -147,7 +147,7 @@ def extract_table_identifiers(token_stream): # extract_tables is inspired from examples in the sqlparse lib. def extract_tables(sql): - """Extract the table names from an SQL statment. + """Extract the table names from an SQL statement. Returns a list of (schema, table, alias) tuples diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 203e1a8..dec3507 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -69,13 +69,14 @@ def show_schema(cur, arg=None, **_): args = (arg,) query = """ SELECT sql FROM sqlite_master - WHERE name==? + WHERE name==? AND sql IS NOT NULL ORDER BY tbl_name, type DESC, name """ else: args = tuple() query = """ SELECT sql FROM sqlite_master + WHERE sql IS NOT NULL ORDER BY tbl_name, type DESC, name """ @@ -212,7 +213,7 @@ def load_extension(cur, arg, **_): "Description of a table", arg_type=PARSED_QUERY, case_sensitive=True, - aliases=("\\d", "describe", "desc"), + aliases=("\\d", "desc"), ) def describe(cur, arg, **_): if arg: diff --git a/litecli/packages/special/favoritequeries.py b/litecli/packages/special/favoritequeries.py index 7da6fbf..8eab521 100644 --- a/litecli/packages/special/favoritequeries.py +++ b/litecli/packages/special/favoritequeries.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals class FavoriteQueries(object): - section_name = "favorite_queries" usage = """ diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 43c3577..ee254f0 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -21,7 +21,7 @@ from litecli.packages.prompt_utils import confirm_destructive_query use_expanded_output = False PAGER_ENABLED = True tee_file = None -once_file = written_to_once_file = None +once_file = once_file_args = written_to_once_file = None favoritequeries = FavoriteQueries(ConfigObj()) @@ -286,7 +286,7 @@ def delete_favorite_query(arg, **_): return [(None, None, None, status)] -@special_command("system", "system [command]", "Execute a system shell commmand.") +@special_command("system", "system [command]", "Execute a system shell command.") def execute_system_command(arg, **_): """Execute a system shell command.""" usage = "Syntax: system [command].\n" @@ -377,9 +377,9 @@ def write_tee(output): aliases=("\\o", "\\once"), ) def set_once(arg, **_): - global once_file + global once_file_args - once_file = parseargfile(arg) + once_file_args = parseargfile(arg) return [(None, None, None, "")] @@ -387,27 +387,28 @@ def set_once(arg, **_): @export def write_once(output): global once_file, written_to_once_file - if output and once_file: - try: - f = open(**once_file) - except (IOError, OSError) as e: - once_file = None - raise OSError( - "Cannot write to file '{}': {}".format(e.filename, e.strerror) - ) - - with f: - click.echo(output, file=f, nl=False) - click.echo("\n", file=f, nl=False) + if output and once_file_args: + if once_file is None: + try: + once_file = open(**once_file_args) + except (IOError, OSError) as e: + once_file = None + raise OSError( + "Cannot write to file '{}': {}".format(e.filename, e.strerror) + ) + + click.echo(output, file=once_file, nl=False) + click.echo("\n", file=once_file, nl=False) written_to_once_file = True @export def unset_once_if_written(): """Unset the once file, if it has been written to.""" - global once_file - if written_to_once_file: - once_file = None + global once_file, once_file_args, written_to_once_file + if once_file and written_to_once_file: + once_file.close() + once_file = once_file_args = written_to_once_file = None @special_command( @@ -461,7 +462,7 @@ def watch_query(arg, **kwargs): # Somewhere in the code the pager its activated after every yield, # so we disable it in every iteration set_pager_enabled(False) - for (sql, title) in sql_list: + for sql, title in sql_list: cur.execute(sql) if cur.description: headers = [x[0] for x in cur.description] diff --git a/litecli/packages/special/utils.py b/litecli/packages/special/utils.py index eed9306..4d3ad91 100644 --- a/litecli/packages/special/utils.py +++ b/litecli/packages/special/utils.py @@ -46,3 +46,86 @@ def format_uptime(uptime_in_seconds): uptime = " ".join(uptime_values) return uptime + + +def check_if_sqlitedotcommand(command): + """Does a check if the command supplied is in the list of SQLite dot commands. + + :param command: A command (str) supplied from the user + :returns: True/False + """ + + sqlite3dotcommands = [ + ".archive", + ".auth", + ".backup", + ".bail", + ".binary", + ".cd", + ".changes", + ".check", + ".clone", + ".connection", + ".databases", + ".dbconfig", + ".dbinfo", + ".dump", + ".echo", + ".eqp", + ".excel", + ".exit", + ".expert", + ".explain", + ".filectrl", + ".fullschema", + ".headers", + ".help", + ".import", + ".imposter", + ".indexes", + ".limit", + ".lint", + ".load", + ".log", + ".mode", + ".nonce", + ".nullvalue", + ".once", + ".open", + ".output", + ".parameter", + ".print", + ".progress", + ".prompt", + ".quit", + ".read", + ".recover", + ".restore", + ".save", + ".scanstats", + ".schema", + ".selftest", + ".separator", + ".session", + ".sha3sum", + ".shell", + ".show", + ".stats", + ".system", + ".tables", + ".testcase", + ".testctrl", + ".timeout", + ".timer", + ".trace", + ".vfsinfo", + ".vfslist", + ".vfsname", + ".width", + ] + + if isinstance(command, str): + command = command.split(" ", 1)[0].lower() + return command in sqlite3dotcommands + else: + return False diff --git a/litecli/sqlcompleter.py b/litecli/sqlcompleter.py index 64ca352..82b8d87 100644 --- a/litecli/sqlcompleter.py +++ b/litecli/sqlcompleter.py @@ -448,7 +448,6 @@ class SQLCompleter(Completer): suggestions = suggest_type(document.text, document.text_before_cursor) for suggestion in suggestions: - _logger.debug("Suggestion type: %r", suggestion["type"]) if suggestion["type"] == "column": diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 3f78d49..2392472 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -1,8 +1,8 @@ import logging import sqlite3 -import uuid from contextlib import closing from sqlite3 import OperationalError +from litecli.packages.special.utils import check_if_sqlitedotcommand import sqlparse import os.path @@ -18,7 +18,6 @@ _logger = logging.getLogger(__name__) class SQLExecute(object): - databases_query = """ PRAGMA database_list """ @@ -51,7 +50,6 @@ class SQLExecute(object): def __init__(self, database): self.dbname = database self._server_type = None - self.connection_id = None self.conn = None if not database: _logger.debug("Database is not specified. Skip connection.") @@ -76,8 +74,6 @@ class SQLExecute(object): # Update them after the connection is made to ensure that it was a # successful connection. self.dbname = db - # retrieve connection id - self.reset_connection_id() def run(self, statement): """Execute the sql in the database and return the results. The results @@ -129,9 +125,12 @@ class SQLExecute(object): for result in special.execute(cur, sql): yield result except special.CommandNotFound: # Regular SQL - _logger.debug("Regular sql statement. sql: %r", sql) - cur.execute(sql) - yield self.get_result(cur) + if check_if_sqlitedotcommand(sql): + yield ("dot command not implemented", None, None, None) + else: + _logger.debug("Regular sql statement. sql: %r", sql) + cur.execute(sql) + yield self.get_result(cur) def get_result(self, cursor): """Get the current result's data from the cursor.""" @@ -204,17 +203,3 @@ class SQLExecute(object): def server_type(self): self._server_type = ("sqlite3", "3") return self._server_type - - def get_connection_id(self): - if not self.connection_id: - self.reset_connection_id() - return self.connection_id - - def reset_connection_id(self): - # Remember current connection id - _logger.debug("Get current connection id") - # res = self.run('select connection_id()') - self.connection_id = uuid.uuid4() - # for title, cur, headers, status in res: - # self.connection_id = cur.fetchone()[0] - _logger.debug("Current connection id: %s", self.connection_id) |