From 1a0ea9da91886f2e05eee4f8d4ade40d34860e23 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 25 Oct 2019 22:04:54 -0700 Subject: [PATCH 001/302] Update changelog. --- changelog.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index c3f54fe..ea739aa 100644 --- a/changelog.md +++ b/changelog.md @@ -1,16 +1,12 @@ -TBD -=== - -Bug Fixes: ----------- - -* +1.2.0 +===== Features: --------- -* - +* Enhance the describe command. (Thanks: [Amjith]) +* Autocomplete table names for special commands. (Thanks: [Amjith]) +* Add .read command. (Thanks: [Shawn Chapla]) 1.1.0 ===== @@ -39,3 +35,4 @@ Features: [Amjith]: https://blog.amjith.com [Zhiming Wang]: https://github.com/zmwangx [Irina Truong]: https://github.com/j-bennet +[Shawn Chapla]: https://github.com/shwnchpl From 408e930a83c9cd36e2609db51fc9a448300fe51e Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 25 Oct 2019 22:05:27 -0700 Subject: [PATCH 002/302] Releasing version 1.2.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 6849410..c68196d 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.2.0" From cd5d4e0cf9164a147c0c59f6f559347c851db5d6 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 25 Oct 2019 22:11:07 -0700 Subject: [PATCH 003/302] Update changelog. --- changelog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.md b/changelog.md index ea739aa..7ef61d6 100644 --- a/changelog.md +++ b/changelog.md @@ -6,7 +6,6 @@ Features: * Enhance the describe command. (Thanks: [Amjith]) * Autocomplete table names for special commands. (Thanks: [Amjith]) -* Add .read command. (Thanks: [Shawn Chapla]) 1.1.0 ===== From 8425188be868f061c43321fceb7eebe3957cbe25 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Mon, 23 Dec 2019 23:40:32 -0500 Subject: [PATCH 004/302] Add .import command --- .gitignore | 1 + changelog.md | 10 +++++- litecli/packages/completion_engine.py | 2 +- litecli/packages/special/dbcommands.py | 46 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ab7d02e..63c3eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /src /test/behave.ini /litecli_env +/.venv /.eggs .vagrant diff --git a/changelog.md b/changelog.md index 7ef61d6..da703e3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +Upcomming: +========== + +Features: +--------- +* Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu]) + 1.2.0 ===== @@ -34,4 +41,5 @@ Features: [Amjith]: https://blog.amjith.com [Zhiming Wang]: https://github.com/zmwangx [Irina Truong]: https://github.com/j-bennet -[Shawn Chapla]: https://github.com/shwnchpl +[Shawn Chapla]: https://github.com/shwnchpl +[Zhaolong Zhu]: https://github.com/zzl0 diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 21ca81d..7eb2abd 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -110,7 +110,7 @@ def suggest_special(text): if cmd in ["\\f", "\\fs", "\\fd"]: return [{"type": "favoritequery"}] - if cmd in ["\\d", "\\dt", "\\dt+", ".schema"]: + if cmd in ["\\d", "\\dt", "\\dt+", ".schema", ".import"]: return [ {"type": "table", "schema": []}, {"type": "view", "schema": []}, diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 9d5d84e..25b731f 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals +import csv import logging import os +import sys import platform import shlex from sqlite3 import ProgrammingError @@ -216,3 +218,47 @@ def read_script(cur, arg, **_): script = f.read() cur.executescript(script) return [(None, None, None, "")] + + +@special_command( + ".import", + ".import filename table", + "Import data from filename into table", + arg_type=PARSED_QUERY, + case_sensitive=True, +) +def import_file(cur, arg=None, **_): + args = shlex.split(arg) + if len(args) != 2: + raise TypeError("Usage: .import filename table") + + filename, table = args + cur.execute("PRAGMA table_info(%s)" % table) + ncols = len(cur.fetchall()) + insert_tmpl = 'INSERT INTO "%s" VALUES (?%s)' % (table, ',?' * (ncols - 1)) + + with open(filename, "r") as csvfile: + dialect = csv.Sniffer().sniff(csvfile.read(1024)) + csvfile.seek(0) + reader = csv.reader(csvfile, dialect) + + cur.execute("BEGIN") + ninserted, nignored = 0, 0 + for i, row in enumerate(reader): + if len(row) != ncols: + print( + "%s:%d expected %d columns but found %d - ignored" % ( + filename, i, ncols, len(row) + ), + file=sys.stderr + ) + nignored += 1 + continue + cur.execute(insert_tmpl, row) + ninserted += 1 + cur.execute("COMMIT") + + status = "Inserted %d rows into %s" % (ninserted, table) + if nignored > 0: + status += " (%d rows are ignored)" % nignored + return [(None, None, None, status)] From d6c51210ee049cab86437f8b6175505bc0e38197 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Tue, 24 Dec 2019 00:11:04 -0500 Subject: [PATCH 005/302] Fix style issue --- litecli/packages/special/dbcommands.py | 11 +++++------ requirements-dev.txt | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 25b731f..14141bd 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import unicode_literals, print_function import csv import logging import os @@ -235,7 +235,7 @@ def import_file(cur, arg=None, **_): filename, table = args cur.execute("PRAGMA table_info(%s)" % table) ncols = len(cur.fetchall()) - insert_tmpl = 'INSERT INTO "%s" VALUES (?%s)' % (table, ',?' * (ncols - 1)) + insert_tmpl = 'INSERT INTO "%s" VALUES (?%s)' % (table, ",?" * (ncols - 1)) with open(filename, "r") as csvfile: dialect = csv.Sniffer().sniff(csvfile.read(1024)) @@ -247,10 +247,9 @@ def import_file(cur, arg=None, **_): for i, row in enumerate(reader): if len(row) != ncols: print( - "%s:%d expected %d columns but found %d - ignored" % ( - filename, i, ncols, len(row) - ), - file=sys.stderr + "%s:%d expected %d columns but found %d - ignored" + % (filename, i, ncols, len(row)), + file=sys.stderr, ) nignored += 1 continue diff --git a/requirements-dev.txt b/requirements-dev.txt index b95211a..681882e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ pexpect coverage codecov click +black From a3f260c869b3b062a3f55e19bdc646ea3343cb20 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Tue, 24 Dec 2019 00:19:46 -0500 Subject: [PATCH 006/302] remove black --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 681882e..b95211a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,4 +7,3 @@ pexpect coverage codecov click -black From 8f2dca4808128aad080fa32c0e133f1d70376e34 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Wed, 1 Jan 2020 14:29:06 -0500 Subject: [PATCH 007/302] update the suggestion type for .import cmd --- litecli/packages/completion_engine.py | 29 +++++++++++++++++++++++++-- tests/test_dbspecial.py | 16 +++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 7eb2abd..aa24349 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -1,6 +1,7 @@ from __future__ import print_function import os import sys +import shlex import sqlparse from sqlparse.sql import Comparison, Identifier, Where from sqlparse.compat import text_type @@ -110,18 +111,42 @@ def suggest_special(text): if cmd in ["\\f", "\\fs", "\\fd"]: return [{"type": "favoritequery"}] - if cmd in ["\\d", "\\dt", "\\dt+", ".schema", ".import"]: + if cmd in ["\\d", "\\dt", "\\dt+", ".schema"]: return [ {"type": "table", "schema": []}, {"type": "view", "schema": []}, {"type": "schema"}, ] - elif cmd in ["\\.", "source", ".open"]: + + if cmd in ["\\.", "source", ".open"]: return [{"type": "file_name"}] + if cmd in [".import"]: + # Usage: .import filename table + if _expecting_arg_idx(arg, text) == 1: + return [{"type": "file_name"}] + else: + return [{"type": "table", "schema": []}] + return [{"type": "keyword"}, {"type": "special"}] +def _expecting_arg_idx(arg, text): + """Return the index of expecting argument. + + >>> _expecting_arg_idx("./da", ".import ./da") + 1 + >>> _expecting_arg_idx("./data.csv", ".import ./data.csv") + 1 + >>> _expecting_arg_idx("./data.csv", ".import ./data.csv ") + 2 + >>> _expecting_arg_idx("./data.csv t", ".import ./data.csv t") + 2 + """ + args = shlex.split(arg) + return len(args) + int(text[-1].isspace()) + + def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier): if isinstance(token, string_types): token_v = token.lower() diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py index b0ca402..18e7f1d 100644 --- a/tests/test_dbspecial.py +++ b/tests/test_dbspecial.py @@ -3,6 +3,22 @@ from litecli.packages.special.utils import format_uptime +def test_import_first_argument(): + test_cases = [ + # text, expecting_arg_idx + [".import ", 1], + [".import ./da", 1], + [".import ./data.csv ", 2], + [".import ./data.csv t", 2], + ] + for text, expecting_arg_idx in test_cases: + suggestions = suggest_type(text, text) + if expecting_arg_idx == 1: + assert suggestions == [{"type": "file_name"}] + else: + assert suggestions == [{"type": "table", "schema": []}] + + def test_u_suggests_databases(): suggestions = suggest_type("\\u ", "\\u ") assert sorted_dicts(suggestions) == sorted_dicts([{"type": "database"}]) From 6f450ff52a4c5655647f6e434b00f4d35bb5fee6 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Wed, 1 Jan 2020 15:04:19 -0500 Subject: [PATCH 008/302] Add test for .import command --- litecli/packages/special/dbcommands.py | 2 +- tests/data/import_data.csv | 2 ++ tests/test_main.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/data/import_data.csv diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 14141bd..ce09b13 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -223,7 +223,7 @@ def read_script(cur, arg, **_): @special_command( ".import", ".import filename table", - "Import data from filename into table", + "Import data from filename into an existing table", arg_type=PARSED_QUERY, case_sensitive=True, ) diff --git a/tests/data/import_data.csv b/tests/data/import_data.csv new file mode 100644 index 0000000..d68d655 --- /dev/null +++ b/tests/data/import_data.csv @@ -0,0 +1,2 @@ +t1,11 +t2,22 diff --git a/tests/test_main.py b/tests/test_main.py index 0b926f4..90132f1 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -238,3 +238,24 @@ def stub_terminal_size(): lc = LiteCli() assert isinstance(lc.get_reserved_space(), int) click.get_terminal_size = old_func + + +@dbtest +def test_import_command(executor): + data_file = os.path.join(project_dir, "tests", "data", "import_data.csv") + run(executor, """create table tbl1(one varchar(10), two smallint)""") + + # execute + run(executor, """.import %s tbl1""" % data_file) + + # verify + sql = "select * from tbl1;" + runner = CliRunner() + result = runner.invoke(cli, args=CLI_ARGS + ["--csv"], input=sql) + + expected = """one","two" +"t1","11" +"t2","22" +""" + assert result.exit_code == 0 + assert expected in "".join(result.output) From 5e8fcf316108111b4aa34d5b8d7c6d0186946240 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Fri, 3 Jan 2020 00:39:11 -0500 Subject: [PATCH 009/302] Fix `key word` and "key word" issue for .import command --- litecli/packages/completion_engine.py | 3 +-- litecli/packages/special/dbcommands.py | 14 ++++++++++++-- tests/test_dbspecial.py | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index aa24349..96334ce 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -1,7 +1,6 @@ from __future__ import print_function import os import sys -import shlex import sqlparse from sqlparse.sql import Comparison, Identifier, Where from sqlparse.compat import text_type @@ -143,7 +142,7 @@ def _expecting_arg_idx(arg, text): >>> _expecting_arg_idx("./data.csv t", ".import ./data.csv t") 2 """ - args = shlex.split(arg) + args = arg.split() return len(args) + int(text[-1].isspace()) diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index ce09b13..a7eaa0c 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -228,12 +228,22 @@ def read_script(cur, arg, **_): case_sensitive=True, ) def import_file(cur, arg=None, **_): - args = shlex.split(arg) + def split(s): + # this is a modification of shlex.split function, just to make it support '`', + # because table name might contain '`' character. + lex = shlex.shlex(s, posix=True) + lex.whitespace_split = True + lex.commenters = "" + lex.quotes += "`" + return list(lex) + + args = split(arg) + log.debug("[arg = %r], [args = %r]", arg, args) if len(args) != 2: raise TypeError("Usage: .import filename table") filename, table = args - cur.execute("PRAGMA table_info(%s)" % table) + cur.execute('PRAGMA table_info("%s")' % table) ncols = len(cur.fetchall()) insert_tmpl = 'INSERT INTO "%s" VALUES (?%s)' % (table, ",?" * (ncols - 1)) diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py index 18e7f1d..c7065a9 100644 --- a/tests/test_dbspecial.py +++ b/tests/test_dbspecial.py @@ -10,6 +10,8 @@ def test_import_first_argument(): [".import ./da", 1], [".import ./data.csv ", 2], [".import ./data.csv t", 2], + [".import ./data.csv `t", 2], + ['.import ./data.csv "t', 2], ] for text, expecting_arg_idx in test_cases: suggestions = suggest_type(text, text) From 3c10de66fcbfbbbf812eb3a8ea73205e1d709803 Mon Sep 17 00:00:00 2001 From: Leon Jacobs Date: Thu, 6 Feb 2020 09:22:32 +0200 Subject: [PATCH 010/302] Upgrade python-prompt-toolkit. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 056b99a..6e4e3a8 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def open_file(filename): install_requirements = [ "click >= 4.1", "Pygments >= 1.6", - "prompt_toolkit>=2.0.0,<2.1.0", + "prompt_toolkit>=3.0.3,<4.0.0", "sqlparse>=0.2.2,<0.3.0", "configobj >= 5.0.5", "cli_helpers[styles] >= 1.0.1", From c8ab4a6ebc5b9f35678a21e54ddb5fc70c9b55cd Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 10 Feb 2020 21:27:58 -0800 Subject: [PATCH 011/302] Remove older python versions from testing. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01a5df0..896e7f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: python python: - - "2.7" - - "3.4" - - "3.5" - "3.6" matrix: From 4527101a3658489b048a5c094fe49dc9b2735998 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 10 Feb 2020 21:37:11 -0800 Subject: [PATCH 012/302] Releasing version 1.3.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index c68196d..67bc602 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.2.0" +__version__ = "1.3.0" From 39d7dad9d63f445e288f476f9e4b76cdd7dc8e9b Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 10 Feb 2020 21:38:47 -0800 Subject: [PATCH 013/302] Update changelog. --- changelog.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index da703e3..6db94a7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,16 @@ -Upcomming: -========== +Upcoming: +========= + +Features: +--------- + +1.3.0: +====== Features: --------- * Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu]) +* Upgraded to prompt-toolkit 3.x. 1.2.0 ===== From a0d4adb94534c383e419d2fa526d9a4aa24246fb Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Wed, 11 Mar 2020 10:07:51 -0700 Subject: [PATCH 014/302] Remove the version pinning of sqlparse. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6e4e3a8..4d01d2b 100755 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def open_file(filename): "click >= 4.1", "Pygments >= 1.6", "prompt_toolkit>=3.0.3,<4.0.0", - "sqlparse>=0.2.2,<0.3.0", + "sqlparse", "configobj >= 5.0.5", "cli_helpers[styles] >= 1.0.1", ] From 4f30a5c9a8954fd830020c288213449843c930bd Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Wed, 11 Mar 2020 10:09:16 -0700 Subject: [PATCH 015/302] Releasing version 1.3.1 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 67bc602..9c73af2 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.3.0" +__version__ = "1.3.1" From c92e27266cb8ff8b64e81a63c29cecffaa0b98da Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Wed, 11 Mar 2020 10:10:09 -0700 Subject: [PATCH 016/302] Bump changelog. --- changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog.md b/changelog.md index 6db94a7..7fb199c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,12 @@ Upcoming: ========= +1.3.1: +====== + +* Remove the version pinning of sqlparse package. + + Features: --------- From 9de8cd576f4b2ba3c8a5e09795f5b0a29fa32616 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Wed, 11 Mar 2020 10:52:45 -0700 Subject: [PATCH 017/302] Fix the completion engine to work with newer sqlparse. --- changelog.md | 5 ++ litecli/packages/completion_engine.py | 7 +- tests/liteclirc | 100 +++++++++++++++----------- 3 files changed, 68 insertions(+), 44 deletions(-) diff --git a/changelog.md b/changelog.md index 7fb199c..f19d4a6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,11 @@ Upcoming: ========= +1.3.2: +====== + +* Fix the completion engine to work with newer sqlparse. + 1.3.1: ====== diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 96334ce..2a43511 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -1,5 +1,4 @@ from __future__ import print_function -import os import sys import sqlparse from sqlparse.sql import Comparison, Identifier, Where @@ -81,7 +80,11 @@ def suggest_type(full_text, text_before_cursor): # Be careful here because trivial whitespace is parsed as a statement, # but the statement won't have a first token tok1 = statement.token_first() - if tok1 and tok1.value in [".", "\\", "source"]: + if tok1 and tok1.value.startswith("."): + return suggest_special(text_before_cursor) + elif tok1 and tok1.value.startswith("\\"): + return suggest_special(text_before_cursor) + elif tok1 and tok1.value.startswith("source"): return suggest_special(text_before_cursor) elif text_before_cursor and text_before_cursor.startswith(".open "): return suggest_special(text_before_cursor) diff --git a/tests/liteclirc b/tests/liteclirc index d380345..e31942f 100644 --- a/tests/liteclirc +++ b/tests/liteclirc @@ -1,4 +1,3 @@ -# vi: ft=dosini [main] # Multi-line mode allows breaking up the sql statements into multiple lines. If @@ -26,9 +25,14 @@ log_level = INFO # line below. # audit_log = ~/.litecli-audit.log -# Table format. Possible values: ascii, double, github, -# psql, plain, simple, grid, fancy_grid, pipe, orgtbl, rst, mediawiki, html, -# latex, latex_booktabs, textile, moinmoin, jira, vertical, tsv, csv. +# Default pager. +# By default '$PAGER' environment variable is used +# pager = less -SRXF + +# Table format. Possible values: +# ascii, double, github, psql, plain, simple, grid, fancy_grid, pipe, orgtbl, +# rst, mediawiki, html, latex, latex_booktabs, textile, moinmoin, jira, +# vertical, tsv, csv. # Recommended: ascii table_format = ascii @@ -53,11 +57,11 @@ wider_completion_menu = False # \m - Minutes of the current time # \n - Newline # \P - AM/PM -# \R - The current time, in 24-hour military time (0–23) -# \r - The current time, standard 12-hour time (1–12) +# \R - The current time, in 24-hour military time (0-23) +# \r - The current time, standard 12-hour time (1-12) # \s - Seconds of the current time -prompt = '\t :\d> ' -prompt_continuation = '-> ' +prompt = "\t :\d> " +prompt_continuation = "-> " # Skip intro info on startup and outro info on exit less_chatty = False @@ -74,39 +78,51 @@ keyword_casing = auto # disabled pager on startup enable_pager = True - -# Custom colors for the completion menu, toolbar, etc. [colors] -# Completion menus. -Token.Menu.Completions.Completion.Current = 'bg:#00aaaa #000000' -Token.Menu.Completions.Completion = 'bg:#008888 #ffffff' -Token.Menu.Completions.MultiColumnMeta = 'bg:#aaffff #000000' -Token.Menu.Completions.ProgressButton = 'bg:#003333' -Token.Menu.Completions.ProgressBar = 'bg:#00aaaa' - -# Query results -Token.Output.Header = 'bold' -Token.Output.OddRow = '' -Token.Output.EvenRow = '' - -# Selected text. -Token.SelectedText = '#ffffff bg:#6666aa' - -# Search matches. (reverse-i-search) -Token.SearchMatch = '#ffffff bg:#4444aa' -Token.SearchMatch.Current = '#ffffff bg:#44aa44' - -# The bottom toolbar. -Token.Toolbar = 'bg:#222222 #aaaaaa' -Token.Toolbar.Off = 'bg:#222222 #888888' -Token.Toolbar.On = 'bg:#222222 #ffffff' - -# Search/arg/system toolbars. -Token.Toolbar.Search = 'noinherit bold' -Token.Toolbar.Search.Text = 'nobold' -Token.Toolbar.System = 'noinherit bold' -Token.Toolbar.Arg = 'noinherit bold' -Token.Toolbar.Arg.Text = 'nobold' - -# Favorite queries. +completion-menu.completion.current = "bg:#ffffff #000000" +completion-menu.completion = "bg:#008888 #ffffff" +completion-menu.meta.completion.current = "bg:#44aaaa #000000" +completion-menu.meta.completion = "bg:#448888 #ffffff" +completion-menu.multi-column-meta = "bg:#aaffff #000000" +scrollbar.arrow = "bg:#003333" +scrollbar = "bg:#00aaaa" +selected = "#ffffff bg:#6666aa" +search = "#ffffff bg:#4444aa" +search.current = "#ffffff bg:#44aa44" +bottom-toolbar = "bg:#222222 #aaaaaa" +bottom-toolbar.off = "bg:#222222 #888888" +bottom-toolbar.on = "bg:#222222 #ffffff" +search-toolbar = noinherit bold +search-toolbar.text = nobold +system-toolbar = noinherit bold +arg-toolbar = noinherit bold +arg-toolbar.text = nobold +bottom-toolbar.transaction.valid = "bg:#222222 #00ff5f bold" +bottom-toolbar.transaction.failed = "bg:#222222 #ff005f bold" + +# style classes for colored table output +output.header = "#00ff5f bold" +output.odd-row = "" +output.even-row = "" +Token.Menu.Completions.Completion.Current = "bg:#00aaaa #000000" +Token.Menu.Completions.Completion = "bg:#008888 #ffffff" +Token.Menu.Completions.MultiColumnMeta = "bg:#aaffff #000000" +Token.Menu.Completions.ProgressButton = "bg:#003333" +Token.Menu.Completions.ProgressBar = "bg:#00aaaa" +Token.Output.Header = bold +Token.Output.OddRow = "" +Token.Output.EvenRow = "" +Token.SelectedText = "#ffffff bg:#6666aa" +Token.SearchMatch = "#ffffff bg:#4444aa" +Token.SearchMatch.Current = "#ffffff bg:#44aa44" +Token.Toolbar = "bg:#222222 #aaaaaa" +Token.Toolbar.Off = "bg:#222222 #888888" +Token.Toolbar.On = "bg:#222222 #ffffff" +Token.Toolbar.Search = noinherit bold +Token.Toolbar.Search.Text = nobold +Token.Toolbar.System = noinherit bold +Token.Toolbar.Arg = noinherit bold +Token.Toolbar.Arg.Text = nobold [favorite_queries] +q_param = select * from test where name=? +sh_param = select * from test where id=$1 From 2c9abde4ee6ebbe42bd8ab4c5a6ba13aa459aa91 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Wed, 11 Mar 2020 10:55:56 -0700 Subject: [PATCH 018/302] Releasing version 1.3.2 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 9c73af2..f708a9b 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.3.1" +__version__ = "1.3.2" From e8990666642ec7c861952a36999066eb8390ba62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B5=D0=BE=D1=80=D0=B3=D0=B8=D0=B9=20=D0=A4=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B2?= Date: Wed, 11 Mar 2020 22:00:39 +0300 Subject: [PATCH 019/302] removed is_dropping_database check (you cannot drop database in SQLite) --- litecli/main.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index 23f5f79..5768851 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -529,10 +529,6 @@ def one_iteration(text=None): logger.error("traceback: %r", traceback.format_exc()) self.echo(str(e), err=True, fg="red") else: - if is_dropping_database(text, self.sqlexecute.dbname): - self.sqlexecute.dbname = None - self.sqlexecute.connect() - # Refresh the table names and column names if necessary. if need_completion_refresh(text): self.refresh_completions(reset=need_completion_reset(text)) @@ -966,31 +962,6 @@ def need_completion_refresh(queries): return False -def is_dropping_database(queries, dbname): - """Determine if the query is dropping a specific database.""" - if dbname is None: - return False - - def normalize_db_name(db): - return db.lower().strip('`"') - - dbname = normalize_db_name(dbname) - - for query in sqlparse.parse(queries): - if query.get_name() is None: - continue - - first_token = query.token_first(skip_cm=True) - _, second_token = query.token_next(0, skip_cm=True) - database_name = normalize_db_name(query.get_name()) - if ( - first_token.value.lower() == "drop" - and second_token.value.lower() in ("database", "schema") - and database_name == dbname - ): - return True - - def need_completion_reset(queries): """Determines if the statement is a database switch such as 'use' or '\\u'. When a database is changed the existing completions must be reset before we From 158f1f53b7a7d13f53eaeb6013068a688fd9d193 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 30 Mar 2020 11:18:36 -0700 Subject: [PATCH 020/302] Include csv files in the tests folder to package. --- MANIFEST.in | 2 ++ setup.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index c7e08e7..beca86f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,4 +3,6 @@ include LICENSE changelog.md include tox.ini recursive-include tests *.py recursive-include tests *.txt +recursive-include tests *.csv +recursive-include tests liteclirc recursive-include litecli AUTHORS diff --git a/setup.py b/setup.py index 4d01d2b..66e5178 100755 --- a/setup.py +++ b/setup.py @@ -4,9 +4,7 @@ import ast from io import open import re -import sys -import subprocess -from setuptools import Command, setup, find_packages +from setuptools import setup, find_packages _version_re = re.compile(r"__version__\s+=\s+(.*)") From 47ed6069748e7db0454eb048e5de6ffee6970a33 Mon Sep 17 00:00:00 2001 From: Dario Vladovic Date: Mon, 13 Jul 2020 01:40:22 +0200 Subject: [PATCH 021/302] Use official Homebrew tap --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 468615f..dfc995a 100644 --- a/README.md +++ b/README.md @@ -33,18 +33,19 @@ $ yay -S litecli-git For MacOS users, you can also use Homebrew to install it: ``` -$ brew tap dbcli/tap $ brew install litecli ``` ## Usage - $ litecli --help - - Usage: litecli [OPTIONS] [DATABASE] +``` +$ litecli --help + +Usage: litecli [OPTIONS] [DATABASE] - Examples: - - litecli sqlite_db_name +Examples: + - litecli sqlite_db_name +``` A config file is automatically created at `~/.config/litecli/config` at first launch. See the file itself for a description of all available options. From 1128ee7753496ce42c514fd51fd4470af1f0db71 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 08:51:35 -0600 Subject: [PATCH 022/302] Add NULLS FIRST and NULLS LAST to keywords. --- changelog.md | 13 +++++++++++-- litecli/sqlcompleter.py | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index f19d4a6..eabdb99 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ -Upcoming: -========= +1.3.3: +====== + +Features: +--------- + +* Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith]) + +Bug Fixes: +---------- + 1.3.2: ====== diff --git a/litecli/sqlcompleter.py b/litecli/sqlcompleter.py index 6950a3c..64ca352 100644 --- a/litecli/sqlcompleter.py +++ b/litecli/sqlcompleter.py @@ -117,13 +117,15 @@ class SQLCompleter(Completer): "NOT", "NOTHING", "NULL", + "NULLS FIRST", + "NULLS LAST", "NUMERIC", "NVARCHAR", "OF", "OFFSET", "ON", "OR", - "ORDER", + "ORDER BY", "OUTER", "OVER", "PARTITION", From 536683da2511044da984ef304ecf80fec74e08be Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 08:57:14 -0600 Subject: [PATCH 023/302] Fix the completion for order by. --- litecli/packages/completion_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 2a43511..8725b63 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -227,7 +227,7 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier # We're probably in a function argument list return [{"type": "column", "tables": extract_tables(full_text)}] - elif token_v in ("set", "by", "distinct"): + elif token_v in ("set", "order by", "distinct"): return [{"type": "column", "tables": extract_tables(full_text)}] elif token_v == "as": # Don't suggest anything for an alias From 2286ca3f4e12ace58df7e595614e6ec7eb30a279 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 09:01:46 -0600 Subject: [PATCH 024/302] Add a test for order by. --- tests/test_completion_engine.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_completion_engine.py b/tests/test_completion_engine.py index 4e0a39d..84d5536 100644 --- a/tests/test_completion_engine.py +++ b/tests/test_completion_engine.py @@ -31,6 +31,15 @@ def test_select_suggests_cols_with_qualified_table_scope(): ) +def test_order_by_suggests_cols_with_qualified_table_scope(): + suggestions = suggest_type( + "SELECT * FROM sch.tabl ORDER BY ", "SELECT * FROM sch.tabl ORDER BY " + ) + assert sorted_dicts(suggestions) == sorted_dicts( + [{"type": "column", "tables": [("sch", "tabl", None)]},] + ) + + @pytest.mark.parametrize( "expression", [ From 88ad28b955cb807b28266776726a99da78abac6f Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 21:30:13 -0600 Subject: [PATCH 025/302] Changelog for release. --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index eabdb99..187ead2 100644 --- a/changelog.md +++ b/changelog.md @@ -10,7 +10,7 @@ Bug Fixes: ---------- -1.3.2: +1.4.0: ====== * Fix the completion engine to work with newer sqlparse. From 4ac97def2b3a8f27f8690f8ab2e75426bb18499a Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 21:30:31 -0600 Subject: [PATCH 026/302] Releasing version 1.4.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index f708a9b..3e8d9f9 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.3.2" +__version__ = "1.4.0" From a3837f8baf6f415818d59fc504b32e21053e968d Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 21:36:52 -0600 Subject: [PATCH 027/302] set markdown as long description format. --- changelog.md | 16 +++++++++++----- setup.py | 3 +-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 187ead2..08e56d0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,13 @@ -1.3.3: +1.4.1: +====== + +Bug Fixes: +---------- + +* Fix setup.py to set long_description_content_type as markdown. + + +1.4.0: ====== Features: @@ -6,11 +15,8 @@ Features: * Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith]) -Bug Fixes: ----------- - -1.4.0: +1.3.2: ====== * Fix the completion engine to work with newer sqlparse. diff --git a/setup.py b/setup.py index 66e5178..acbb0d9 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ def open_file(filename): description="CLI for SQLite Databases with auto-completion and syntax " "highlighting.", long_description=readme, + long_description_content_type="text/markdown", install_requires=install_requirements, # cmdclass={"test": test, "lint": lint}, entry_points={ @@ -55,8 +56,6 @@ def open_file(filename): "License :: OSI Approved :: BSD License", "Operating System :: Unix", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", From 11c87dbf7b13f5e9b8f713ff9f0efe39062ccc17 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 26 Jul 2020 21:37:16 -0600 Subject: [PATCH 028/302] Releasing version 1.4.1 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 3e8d9f9..bf25615 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.4.0" +__version__ = "1.4.1" From bb0f9c4e85ee7d6e8562a0667a1dda084be00c57 Mon Sep 17 00:00:00 2001 From: zzl0 Date: Thu, 8 Oct 2020 21:47:13 -0400 Subject: [PATCH 029/302] Add verbose feature to favorite_query command --- changelog.md | 10 ++++++ litecli/packages/special/iocommands.py | 6 ++-- tests/test_sqlexecute.py | 47 ++++++++++++++++++++------ 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/changelog.md b/changelog.md index 08e56d0..dbbcdb4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ + +(Unreleased; add upcoming change notes here) +============================================= + +Features: +--------- +* Add verbose feature to favorite_query command. (Thanks: [Zhaolong Zhu]) + * > `\f query` will not show the full SQL. + * > `\f+ query` will show the full SQL. + 1.4.1: ====== diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index a9036aa..8940057 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -175,7 +175,7 @@ def open_external_editor(filename=None, sql=None): arg_type=PARSED_QUERY, case_sensitive=True, ) -def execute_favorite_query(cur, arg, **_): +def execute_favorite_query(cur, arg, verbose=False, **_): """Returns (title, rows, headers, status)""" if arg == "": for result in list_favorite_queries(): @@ -192,7 +192,7 @@ def execute_favorite_query(cur, arg, **_): elif "?" in query: for sql in sqlparse.split(query): sql = sql.rstrip(";") - title = "> %s" % (sql) + title = "> %s" % (sql) if verbose else None cur.execute(sql, args) if cur.description: headers = [x[0] for x in cur.description] @@ -206,7 +206,7 @@ def execute_favorite_query(cur, arg, **_): else: for sql in sqlparse.split(query): sql = sql.rstrip(";") - title = "> %s" % (sql) + title = "> %s" % (sql) if verbose else None cur.execute(sql) if cur.description: headers = [x[0] for x in cur.description] diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 098b26e..2dde4d4 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -139,7 +139,7 @@ def test_favorite_query(executor): results = run(executor, "\\fs test-a select * from test where a like 'a%'") assert_result_equal(results, status="Saved.") - results = run(executor, "\\f test-a") + results = run(executor, "\\f+ test-a") assert_result_equal( results, title="> select * from test where a like 'a%'", @@ -162,7 +162,7 @@ def test_bind_parameterized_favorite_query(executor): results = run(executor, "\\fs q_param select * from test where name=?") assert_result_equal(results, status="Saved.") - results = run(executor, "\\f q_param def") + results = run(executor, "\\f+ q_param def") assert_result_equal( results, title="> select * from test where name=?", @@ -171,7 +171,7 @@ def test_bind_parameterized_favorite_query(executor): auto_status=False, ) - results = run(executor, "\\f q_param 'two words'") + results = run(executor, "\\f+ q_param 'two words'") assert_result_equal( results, title="> select * from test where name=?", @@ -181,11 +181,38 @@ def test_bind_parameterized_favorite_query(executor): ) with pytest.raises(ProgrammingError): - results = run(executor, "\\f q_param") + results = run(executor, "\\f+ q_param") with pytest.raises(ProgrammingError): - results = run(executor, "\\f q_param 1 2") + results = run(executor, "\\f+ q_param 1 2") +@dbtest +def test_verbose_feature_of_favorite_query(executor): + set_expanded_output(False) + run(executor, "create table test(a text, id integer)") + run(executor, "insert into test values('abc', 1)") + run(executor, "insert into test values('def', 2)") + + results = run(executor, "\\fs sh_param select * from test where id=$1") + assert_result_equal(results, status="Saved.") + + results = run(executor, "\\f sh_param 1") + assert_result_equal( + results, + title=None, + headers=["a", "id"], + rows=[("abc", 1)], + auto_status=False, + ) + + results = run(executor, "\\f+ sh_param 1") + assert_result_equal( + results, + title="> select * from test where id=1", + headers=["a", "id"], + rows=[("abc", 1)], + auto_status=False, + ) @dbtest def test_shell_parameterized_favorite_query(executor): @@ -197,7 +224,7 @@ def test_shell_parameterized_favorite_query(executor): results = run(executor, "\\fs sh_param select * from test where id=$1") assert_result_equal(results, status="Saved.") - results = run(executor, "\\f sh_param 1") + results = run(executor, "\\f+ sh_param 1") assert_result_equal( results, title="> select * from test where id=1", @@ -206,7 +233,7 @@ def test_shell_parameterized_favorite_query(executor): auto_status=False, ) - results = run(executor, "\\f sh_param") + results = run(executor, "\\f+ sh_param") assert_result_equal( results, title=None, @@ -215,7 +242,7 @@ def test_shell_parameterized_favorite_query(executor): status="missing substitution for $1 in query:\n select * from test where id=$1", ) - results = run(executor, "\\f sh_param 1 2") + results = run(executor, "\\f+ sh_param 1 2") assert_result_equal( results, title=None, @@ -239,7 +266,7 @@ def test_favorite_query_multiple_statement(executor): ) assert_result_equal(results, status="Saved.") - results = run(executor, "\\f test-ad") + results = run(executor, "\\f+ test-ad") expected = [ { "title": "> select * from test where a like 'a%'", @@ -269,7 +296,7 @@ def test_favorite_query_expanded_output(executor): results = run(executor, "\\fs test-ae select * from test") assert_result_equal(results, status="Saved.") - results = run(executor, "\\f test-ae \G") + results = run(executor, "\\f+ test-ae \G") assert is_expanded_output() is True assert_result_equal( results, From 37957e401d22f88800bbdec2c690e731f2cc13bd Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Sat, 17 Oct 2020 18:49:25 +0100 Subject: [PATCH 030/302] Fix compatibility with sqlparse >= 0.4.0 --- litecli/encodingutils.py | 6 ++++-- litecli/packages/completion_engine.py | 10 +--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/litecli/encodingutils.py b/litecli/encodingutils.py index bd23820..6caf14d 100644 --- a/litecli/encodingutils.py +++ b/litecli/encodingutils.py @@ -5,11 +5,13 @@ if PY2: - text_type = unicode binary_type = str + string_types = basestring + text_type = unicode else: - text_type = str binary_type = bytes + string_types = str + text_type = str def unicode2utf8(arg): diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 8725b63..0397857 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -2,18 +2,10 @@ import sys import sqlparse from sqlparse.sql import Comparison, Identifier, Where -from sqlparse.compat import text_type +from litecli.encodingutils import string_types, text_type from .parseutils import last_word, extract_tables, find_prev_keyword from .special import parse_special_command -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -if PY3: - string_types = str -else: - string_types = basestring - def suggest_type(full_text, text_before_cursor): """Takes the full_text that is typed so far and also the text before the From 4926c5e85442c73590a69d037d96ec3ede18e9ae Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Sat, 17 Oct 2020 19:43:19 +0100 Subject: [PATCH 031/302] Update changelog --- changelog.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index dbbcdb4..9887151 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ Bug Fixes: ---------- * Fix setup.py to set long_description_content_type as markdown. +* Fix compatibility with sqlparse >= 0.4.0. 1.4.0: @@ -29,12 +30,12 @@ Features: 1.3.2: ====== -* Fix the completion engine to work with newer sqlparse. +* Fix the completion engine to work with newer sqlparse. 1.3.1: ====== -* Remove the version pinning of sqlparse package. +* Remove the version pinning of sqlparse package. Features: From f4bb9d50fdbc3166c873a6ac5c6cf4c7e6752464 Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Sun, 18 Oct 2020 14:28:14 +0100 Subject: [PATCH 032/302] Update changelog Use standard changelog conventions as per https://keepachangelog.com/ - add release dates - use standard file name: changelog.md -> CHANGELOG.md - move latest change to the Unreleased section - use standard Markdown formatting --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CHANGELOG.md | 72 ++++++++++++++++++++++++++ MANIFEST.in | 2 +- changelog.md | 89 -------------------------------- 4 files changed, 74 insertions(+), 91 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 changelog.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e995453..3e14cc7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,4 +5,4 @@ ## Checklist -- [ ] I've added this contribution to the `changelog.md` file. +- [ ] I've added this contribution to the `CHANGELOG.md` file. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ec98b08 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,72 @@ +## Unreleased - TBD + + + +### Features + +- Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu]) + - `\f query` does not show the full SQL. + - `\f+ query` shows the full SQL. + +### Bug Fixes + +- Fix compatibility with sqlparse >= 0.4.0. (Thanks: [chocolateboy]) + +## 1.4.1 - 2020-07-27 + +### Bug Fixes + +- Fix setup.py to set `long_description_content_type` as markdown. + +## 1.4.0 - 2020-07-27 + +### Features + +- Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith]) + +## 1.3.2 - 2020-03-11 + +- Fix the completion engine to work with newer sqlparse. + +## 1.3.1 - 2020-03-11 + +- Remove the version pinning of sqlparse package. + +## 1.3.0 - 2020-02-11 + +### Features + +- Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu]) +- Upgraded to prompt-toolkit 3.x. + +## 1.2.0 - 2019-10-26 + +### Features + +- Enhance the `describe` command. (Thanks: [Amjith]) +- Autocomplete table names for special commands. (Thanks: [Amjith]) + +## 1.1.0 - 2019-07-14 + +### Features + +- Added `.read` command for reading scripts. +- Added `.load` command for loading extension libraries. (Thanks: [Zhiming Wang]) +- Add support for using `?` as a placeholder in the favorite queries. (Thanks: [Amjith]) +- Added shift-tab to select the previous entry in the completion menu. [Amjith] +- Added `describe` and `desc` keywords. + +### Bug Fixes + +- Clear error message when directory does not exist. (Thanks: [Irina Truong]) + +## 1.0.0 - 2019-01-04 + +- To new beginnings. :tada: + +[Amjith]: https://blog.amjith.com +[chocolateboy]: https://github.com/chocolateboy +[Irina Truong]: https://github.com/j-bennet +[Shawn Chapla]: https://github.com/shwnchpl +[Zhaolong Zhu]: https://github.com/zzl0 +[Zhiming Wang]: https://github.com/zmwangx diff --git a/MANIFEST.in b/MANIFEST.in index beca86f..f1ff0f6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include *.txt *.py -include LICENSE changelog.md +include LICENSE CHANGELOG.md include tox.ini recursive-include tests *.py recursive-include tests *.txt diff --git a/changelog.md b/changelog.md deleted file mode 100644 index 9887151..0000000 --- a/changelog.md +++ /dev/null @@ -1,89 +0,0 @@ - -(Unreleased; add upcoming change notes here) -============================================= - -Features: ---------- -* Add verbose feature to favorite_query command. (Thanks: [Zhaolong Zhu]) - * > `\f query` will not show the full SQL. - * > `\f+ query` will show the full SQL. - -1.4.1: -====== - -Bug Fixes: ----------- - -* Fix setup.py to set long_description_content_type as markdown. -* Fix compatibility with sqlparse >= 0.4.0. - - -1.4.0: -====== - -Features: ---------- - -* Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith]) - - -1.3.2: -====== - -* Fix the completion engine to work with newer sqlparse. - -1.3.1: -====== - -* Remove the version pinning of sqlparse package. - - -Features: ---------- - -1.3.0: -====== - -Features: ---------- -* Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu]) -* Upgraded to prompt-toolkit 3.x. - -1.2.0 -===== - -Features: ---------- - -* Enhance the describe command. (Thanks: [Amjith]) -* Autocomplete table names for special commands. (Thanks: [Amjith]) - -1.1.0 -===== - -Bug Fixes: ----------- - -* Clear error message when directory does not exist. (Thanks: [Irina Truong]) - -Features: ---------- - -* Added `.read` command for reading scripts. -* Added `.load` command for loading extension libraries. (Thanks: [Zhiming Wang]) -* Add support for using `?` as a placeholder in the favorite queries. (Thanks: [Amjith]) -* Added shift-tab to select the previous entry in the completion menu. [Amjith] -* Added `describe` and `desc` keywords. - -1.0.0 -===== - -* To new beginnings. :tada: - - - -[Amjith]: https://blog.amjith.com -[Zhiming Wang]: https://github.com/zmwangx -[Irina Truong]: https://github.com/j-bennet -[Shawn Chapla]: https://github.com/shwnchpl -[Zhaolong Zhu]: https://github.com/zzl0 From 93c7a3b265ca5d312ceb17ec81e6712453aa5eab Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 11 Dec 2020 16:47:31 -0800 Subject: [PATCH 033/302] Releasing version 1.5.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index bf25615..5b60188 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.4.1" +__version__ = "1.5.0" From 0baeadf06893ce26e3f3947581dd6656f3fa44c0 Mon Sep 17 00:00:00 2001 From: Eliran Gonen <31196036+elig0n@users.noreply.github.com> Date: Sun, 3 Jan 2021 04:34:54 +0200 Subject: [PATCH 034/302] \f prompt format string for database basename (#107) \f prompt format string for showing only the basename of a database --- CHANGELOG.md | 1 + litecli/liteclirc | 1 + litecli/main.py | 1 + tests/liteclirc | 1 + 4 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec98b08..b8c8122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu]) - `\f query` does not show the full SQL. - `\f+ query` shows the full SQL. +- Add prompt format of file's basename. (Thanks: [elig0n]) ### Bug Fixes diff --git a/litecli/liteclirc b/litecli/liteclirc index e3331d1..5bfffbd 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -55,6 +55,7 @@ wider_completion_menu = False # litecli prompt # \D - The full current date # \d - Database name +# \f - File basename of the "main" database # \m - Minutes of the current time # \n - Newline # \P - AM/PM diff --git a/litecli/main.py b/litecli/main.py index 5768851..6d9adfd 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -725,6 +725,7 @@ def get_prompt(self, string): sqlexecute = self.sqlexecute now = datetime.now() string = string.replace("\\d", sqlexecute.dbname or "(none)") + string = string.replace("\\f", os.path.basename(sqlexecute.dbname or "(none)")) string = string.replace("\\n", "\n") string = string.replace("\\D", now.strftime("%a %b %d %H:%M:%S %Y")) string = string.replace("\\m", now.strftime("%M")) diff --git a/tests/liteclirc b/tests/liteclirc index e31942f..979b409 100644 --- a/tests/liteclirc +++ b/tests/liteclirc @@ -54,6 +54,7 @@ wider_completion_menu = False # litecli prompt # \D - The full current date # \d - Database name +# \f - File basename of the "main" database # \m - Minutes of the current time # \n - Newline # \P - AM/PM From d4561a1ca0456a045a22a2a7dd3680d44d47c21c Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Fri, 22 Jan 2021 15:09:09 -0800 Subject: [PATCH 035/302] Switch to Github Actions. --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 29 -------------------------- requirements-dev.txt | 1 + tasks.py | 2 +- 4 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..372fbbb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: litecli + +on: + pull_request: + paths-ignore: + - '**.md' + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install requirements + run: | + python -m pip install -U pip setuptools + pip install --no-cache-dir -e . + pip install -r requirements-dev.txt -U --upgrade-strategy=only-if-needed + + - name: Run unit tests + env: + PYTEST_PASSWORD: root + run: | + ./setup.py test --pytest-args="--cov-report= --cov=litecli" + + - name: Run Black + run: | + ./setup.py lint + if: matrix.python-version == '3.6' + + - name: Coverage + run: | + coverage report + codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 896e7f7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: python -python: - - "3.6" - -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true - -install: - - pip install -r requirements-dev.txt - - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then pip install black; fi - - pip install -e . - -script: - - ./setup.py test --pytest-args="--cov-report= --cov=litecli" - - coverage report - - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then ./setup.py lint; fi - -after_success: - - codecov - -notifications: - webhooks: - urls: - - YOUR_WEBHOOK_URL - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always diff --git a/requirements-dev.txt b/requirements-dev.txt index b95211a..c517d59 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ pexpect coverage codecov click +black \ No newline at end of file diff --git a/tasks.py b/tasks.py index 5e68107..589f748 100644 --- a/tasks.py +++ b/tasks.py @@ -83,7 +83,7 @@ def initialize_options(self): self.pytest_args = "" def run_tests(self): - unit_test_errno = subprocess.call("pytest " + self.pytest_args, shell=True) + unit_test_errno = subprocess.call("pytest tests " + self.pytest_args, shell=True) # cli_errno = subprocess.call('behave test/features', shell=True) # sys.exit(unit_test_errno or cli_errno) sys.exit(unit_test_errno) From f767afc80bd5bcc8f1b1cc1a134babc2dec4d239 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Fri, 22 Jan 2021 15:34:02 -0800 Subject: [PATCH 036/302] black the code. --- litecli/main.py | 3 +-- litecli/packages/parseutils.py | 2 +- litecli/packages/special/iocommands.py | 3 +-- tasks.py | 4 +++- tests/test_completion_engine.py | 4 +++- ...est_smart_completion_public_schema_only.py | 20 ++++++++++--------- tests/test_sqlexecute.py | 2 ++ 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index 6d9adfd..24b1bb4 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -704,8 +704,7 @@ def refresh_completions(self, reset=False): ] def _on_completions_refreshed(self, new_completer): - """Swap the completer object in cli with the newly created completer. - """ + """Swap the completer object in cli with the newly created completer.""" with self._completer_lock: self.completer = new_completer diff --git a/litecli/packages/parseutils.py b/litecli/packages/parseutils.py index 92fe365..3f5ca61 100644 --- a/litecli/packages/parseutils.py +++ b/litecli/packages/parseutils.py @@ -166,7 +166,7 @@ def extract_tables(sql): def find_prev_keyword(sql): - """ Find the last sql keyword in an SQL statement + """Find the last sql keyword in an SQL statement Returns the value of the last keyword, and the text of the query with everything after the last keyword stripped diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 8940057..43c3577 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -276,8 +276,7 @@ def save_favorite_query(arg, **_): @special_command("\\fd", "\\fd [name]", "Delete a favorite query.") def delete_favorite_query(arg, **_): - """Delete an existing favorite query. - """ + """Delete an existing favorite query.""" usage = "Syntax: \\fd name.\n\n" + favoritequeries.usage if not arg: return [(None, None, None, usage)] diff --git a/tasks.py b/tasks.py index 589f748..1cd4b69 100644 --- a/tasks.py +++ b/tasks.py @@ -83,7 +83,9 @@ def initialize_options(self): self.pytest_args = "" def run_tests(self): - unit_test_errno = subprocess.call("pytest tests " + self.pytest_args, shell=True) + unit_test_errno = subprocess.call( + "pytest tests " + self.pytest_args, shell=True + ) # cli_errno = subprocess.call('behave test/features', shell=True) # sys.exit(unit_test_errno or cli_errno) sys.exit(unit_test_errno) diff --git a/tests/test_completion_engine.py b/tests/test_completion_engine.py index 84d5536..760f275 100644 --- a/tests/test_completion_engine.py +++ b/tests/test_completion_engine.py @@ -36,7 +36,9 @@ def test_order_by_suggests_cols_with_qualified_table_scope(): "SELECT * FROM sch.tabl ORDER BY ", "SELECT * FROM sch.tabl ORDER BY " ) assert sorted_dicts(suggestions) == sorted_dicts( - [{"type": "column", "tables": [("sch", "tabl", None)]},] + [ + {"type": "column", "tables": [("sch", "tabl", None)]}, + ] ) diff --git a/tests/test_smart_completion_public_schema_only.py b/tests/test_smart_completion_public_schema_only.py index ea5c580..e532118 100644 --- a/tests/test_smart_completion_public_schema_only.py +++ b/tests/test_smart_completion_public_schema_only.py @@ -367,15 +367,17 @@ def test_auto_escaped_col_names(completer, complete_event): Document(text=text, cursor_position=position), complete_event ) ) - assert result == [ - Completion(text="*", start_position=0), - Completion(text="`ABC`", start_position=0), - Completion(text="`insert`", start_position=0), - Completion(text="id", start_position=0), - ] + list(map(Completion, completer.functions)) + [ - Completion(text="`select`", start_position=0) - ] + list( - map(Completion, sorted(completer.keywords)) + assert ( + result + == [ + Completion(text="*", start_position=0), + Completion(text="`ABC`", start_position=0), + Completion(text="`insert`", start_position=0), + Completion(text="id", start_position=0), + ] + + list(map(Completion, completer.functions)) + + [Completion(text="`select`", start_position=0)] + + list(map(Completion, sorted(completer.keywords))) ) diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 2dde4d4..03e7f19 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -186,6 +186,7 @@ def test_bind_parameterized_favorite_query(executor): with pytest.raises(ProgrammingError): results = run(executor, "\\f+ q_param 1 2") + @dbtest def test_verbose_feature_of_favorite_query(executor): set_expanded_output(False) @@ -214,6 +215,7 @@ def test_verbose_feature_of_favorite_query(executor): auto_status=False, ) + @dbtest def test_shell_parameterized_favorite_query(executor): set_expanded_output(False) From af93ba10061c4099c0a4c98a701a2b00061c3d1f Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Fri, 22 Jan 2021 15:34:53 -0800 Subject: [PATCH 037/302] Add black commits to ignore revs file. --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..c433202 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Black +f767afc80bd5bcc8f1b1cc1a134babc2dec4d239 \ No newline at end of file From c2f914a79161f2b160295ca4ae6777321cb0648f Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 14 Mar 2021 12:44:37 -0700 Subject: [PATCH 038/302] Add a test to reproduce the invalid utf-8 error. --- tests/test_sqlexecute.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 03e7f19..0ddfb8f 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -101,6 +101,17 @@ def test_unicode_support_in_output(executor): assert_result_equal(results, headers=["t"], rows=[(u"é",)]) +@dbtest +def test_invalid_unicode_values_dont_choke(executor): + run(executor, "create table unicodechars(t text)") + # \xc3 is not a valid utf-8 char. But we can insert it into the database + # which can break querying if not handled correctly. + run(executor, u"insert into unicodechars (t) values (cast(x'c3' as text))") + + results = run(executor, u"select * from unicodechars") + assert_result_equal(results, headers=["t"], rows=[(u"Ã",)]) + + @dbtest def test_multiple_queries_same_line(executor): results = run(executor, "select 'foo'; select 'bar'") @@ -199,11 +210,7 @@ def test_verbose_feature_of_favorite_query(executor): results = run(executor, "\\f sh_param 1") assert_result_equal( - results, - title=None, - headers=["a", "id"], - rows=[("abc", 1)], - auto_status=False, + results, title=None, headers=["a", "id"], rows=[("abc", 1)], auto_status=False, ) results = run(executor, "\\f+ sh_param 1") From 5b86afa53160551e536718f75124c6a45cd19757 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 14 Mar 2021 13:10:27 -0700 Subject: [PATCH 039/302] Fix the utf-8 decoding error. --- litecli/sqlexecute.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 7ef103c..93acd91 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -17,6 +17,13 @@ # }) +def utf8_resilient_decoder(s): + try: + return s.decode("utf-8") + except UnicodeDecodeError: + return s.decode("latin-1") + + class SQLExecute(object): databases_query = """ @@ -61,6 +68,7 @@ def connect(self, database=None): raise Exception("Path does not exist: {}".format(db_dir_name)) conn = sqlite3.connect(database=db_name, isolation_level=None) + conn.text_factory = utf8_resilient_decoder if self.conn: self.conn.close() From b71fb3d6d5596a711d8a2a8cb9ada3808f8d3059 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 14 Mar 2021 13:12:40 -0700 Subject: [PATCH 040/302] Update changelog. --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c8122..0ccb53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ ## Unreleased - TBD - - ### Features - Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu]) @@ -12,6 +10,7 @@ ### Bug Fixes - Fix compatibility with sqlparse >= 0.4.0. (Thanks: [chocolateboy]) +- Fix invalid utf-8 exception. (Thanks: [Amjith]) ## 1.4.1 - 2020-07-27 From 04c15ed2661b7bc16d35b5ffc04c31efbe405796 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 14 Mar 2021 19:43:33 -0700 Subject: [PATCH 041/302] UTF-8 Decoding Error (#113) Fix the utf-8 decoding error. --- CHANGELOG.md | 3 +-- litecli/sqlexecute.py | 8 ++++++++ tests/test_sqlexecute.py | 17 ++++++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c8122..0ccb53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ ## Unreleased - TBD - - ### Features - Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu]) @@ -12,6 +10,7 @@ ### Bug Fixes - Fix compatibility with sqlparse >= 0.4.0. (Thanks: [chocolateboy]) +- Fix invalid utf-8 exception. (Thanks: [Amjith]) ## 1.4.1 - 2020-07-27 diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 7ef103c..93acd91 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -17,6 +17,13 @@ # }) +def utf8_resilient_decoder(s): + try: + return s.decode("utf-8") + except UnicodeDecodeError: + return s.decode("latin-1") + + class SQLExecute(object): databases_query = """ @@ -61,6 +68,7 @@ def connect(self, database=None): raise Exception("Path does not exist: {}".format(db_dir_name)) conn = sqlite3.connect(database=db_name, isolation_level=None) + conn.text_factory = utf8_resilient_decoder if self.conn: self.conn.close() diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 03e7f19..0ddfb8f 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -101,6 +101,17 @@ def test_unicode_support_in_output(executor): assert_result_equal(results, headers=["t"], rows=[(u"é",)]) +@dbtest +def test_invalid_unicode_values_dont_choke(executor): + run(executor, "create table unicodechars(t text)") + # \xc3 is not a valid utf-8 char. But we can insert it into the database + # which can break querying if not handled correctly. + run(executor, u"insert into unicodechars (t) values (cast(x'c3' as text))") + + results = run(executor, u"select * from unicodechars") + assert_result_equal(results, headers=["t"], rows=[(u"Ã",)]) + + @dbtest def test_multiple_queries_same_line(executor): results = run(executor, "select 'foo'; select 'bar'") @@ -199,11 +210,7 @@ def test_verbose_feature_of_favorite_query(executor): results = run(executor, "\\f sh_param 1") assert_result_equal( - results, - title=None, - headers=["a", "id"], - rows=[("abc", 1)], - auto_status=False, + results, title=None, headers=["a", "id"], rows=[("abc", 1)], auto_status=False, ) results = run(executor, "\\f+ sh_param 1") From c0e9feb375ea413f94ed26c9db1e896bbe4aa6ec Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 15 Mar 2021 06:49:47 -0700 Subject: [PATCH 042/302] Use backslash decode when utf-8 chokes. --- litecli/sqlexecute.py | 9 +-------- tests/test_sqlexecute.py | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 93acd91..2b8fce3 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -17,13 +17,6 @@ # }) -def utf8_resilient_decoder(s): - try: - return s.decode("utf-8") - except UnicodeDecodeError: - return s.decode("latin-1") - - class SQLExecute(object): databases_query = """ @@ -68,7 +61,7 @@ def connect(self, database=None): raise Exception("Path does not exist: {}".format(db_dir_name)) conn = sqlite3.connect(database=db_name, isolation_level=None) - conn.text_factory = utf8_resilient_decoder + conn.text_factory = lambda x: x.decode("utf-8", "backslashreplace") if self.conn: self.conn.close() diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 0ddfb8f..029c64b 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -109,7 +109,7 @@ def test_invalid_unicode_values_dont_choke(executor): run(executor, u"insert into unicodechars (t) values (cast(x'c3' as text))") results = run(executor, u"select * from unicodechars") - assert_result_equal(results, headers=["t"], rows=[(u"Ã",)]) + assert_result_equal(results, headers=["t"], rows=[("\\xc3",)]) @dbtest From 06cafa646af77a8068ac0868905bb07ae13528f9 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 15 Mar 2021 12:21:02 -0700 Subject: [PATCH 043/302] Update changelog for 1.6.0 release. --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b268b62..be3886f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/ambv/black - rev: stable + rev: 20.8b1 hooks: - id: black language_version: python3.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ccb53a..d170c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ### Features +### Bug Fixes + + +## 1.6.0 - 2021-03-15 + +### Features + - Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu]) - `\f query` does not show the full SQL. - `\f+ query` shows the full SQL. From 22ff7eaf1775ecd272a111bbb344cecc8d1f7c5a Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 15 Mar 2021 12:21:34 -0700 Subject: [PATCH 044/302] Releasing version 1.6.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 5b60188..e4adfb8 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.5.0" +__version__ = "1.6.0" From bef9cce5b587e537e2ea3add96fa216130971230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sacawa?= Date: Sun, 16 May 2021 03:25:59 -0400 Subject: [PATCH 045/302] Add: show_bottom_toolbar config option --- litecli/AUTHORS | 1 + litecli/liteclirc | 3 +++ litecli/main.py | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/litecli/AUTHORS b/litecli/AUTHORS index d5265de..194cdc7 100644 --- a/litecli/AUTHORS +++ b/litecli/AUTHORS @@ -18,3 +18,4 @@ Contributors: * Zhaolong Zhu * Zhiming Wang * Shawn M. Chapla + * Paweł Sacawa diff --git a/litecli/liteclirc b/litecli/liteclirc index 5bfffbd..f0a4285 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -65,6 +65,9 @@ wider_completion_menu = False prompt = '\d> ' prompt_continuation = '-> ' +# Show/hide the informational toolbar with function keymap at the footer. +show_bottom_toolbar = True + # Skip intro info on startup and outro info on exit less_chatty = False diff --git a/litecli/main.py b/litecli/main.py index 24b1bb4..faa7322 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -83,6 +83,7 @@ def __init__( self.formatter.litecli = self self.syntax_style = c["main"]["syntax_style"] self.less_chatty = c["main"].as_bool("less_chatty") + self.show_bottom_toolbar = c["main"].as_bool("show_bottom_toolbar") self.cli_style = c["colors"] self.output_style = style_factory_output(self.syntax_style, self.cli_style) self.wider_completion_menu = c["main"].as_bool("wider_completion_menu") @@ -557,7 +558,7 @@ def one_iteration(text=None): reserve_space_for_menu=self.get_reserved_space(), message=get_message, prompt_continuation=get_continuation, - bottom_toolbar=get_toolbar_tokens, + bottom_toolbar=get_toolbar_tokens if self.show_bottom_toolbar else None, complete_style=complete_style, input_processors=[ ConditionalProcessor( From 083ffa292e1df28281750e01cf64f9e77a7e96e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sacawa?= Date: Sun, 16 May 2021 03:29:51 -0400 Subject: [PATCH 046/302] Add to CHANGELOG.MD --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d170c9a..13839f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +* Add config option show_bottom_toolbar. + ### Bug Fixes From 31491344ce0513ad439e0dbb19679cef7e925d6c Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 16 May 2021 09:40:04 -0700 Subject: [PATCH 047/302] Blacken. --- tests/test_sqlexecute.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 029c64b..e559bc6 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -210,7 +210,11 @@ def test_verbose_feature_of_favorite_query(executor): results = run(executor, "\\f sh_param 1") assert_result_equal( - results, title=None, headers=["a", "id"], rows=[("abc", 1)], auto_status=False, + results, + title=None, + headers=["a", "id"], + rows=[("abc", 1)], + auto_status=False, ) results = run(executor, "\\f+ sh_param 1") From cacdd8ed1a12d5f5cc710275ced5dd8122f117f7 Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 11 Sep 2021 00:23:44 -0400 Subject: [PATCH 048/302] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dfc995a..649481d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Examples: - litecli sqlite_db_name ``` -A config file is automatically created at `~/.config/litecli/config` at first launch. See the file itself for a description of all available options. +A config file is automatically created at `~/.config/litecli/config` at first launch. For Windows machines a config file is created at `~\AppData\Local\dbcli\litecli\config` at first launch. See the file itself for a description of all available options. ## Docs From 14c5c84ec4064ceb69ec14bcc035633d688b9313 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 10 Jan 2022 13:46:45 -0800 Subject: [PATCH 049/302] Workaround pygments issue. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index acbb0d9..808b165 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def open_file(filename): install_requirements = [ "click >= 4.1", - "Pygments >= 1.6", + "Pygments>=1.6,<=2.11.1", "prompt_toolkit>=3.0.3,<4.0.0", "sqlparse", "configobj >= 5.0.5", From 877964de158670cba958b5ffbacde33742b6fddf Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 11 Jan 2022 08:30:15 -0800 Subject: [PATCH 050/302] Update changelog for 1.7.0 --- .pre-commit-config.yaml | 5 ++--- CHANGELOG.md | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be3886f..4f53bac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,5 @@ repos: -- repo: https://github.com/ambv/black - rev: 20.8b1 +- repo: https://github.com/psf/black + rev: 21.12b0 hooks: - id: black - language_version: python3.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 13839f4..58c9926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased - TBD +## 1.7.0 - 2022-01-11 ### Features @@ -6,6 +6,7 @@ ### Bug Fixes +* Pin pygments version to prevent breaking change. ## 1.6.0 - 2021-03-15 From f291030d0ce6312eeee3e4585f17e11388df3876 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 11 Jan 2022 08:30:38 -0800 Subject: [PATCH 051/302] Releasing version 1.7.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index e4adfb8..14d9d2f 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.6.0" +__version__ = "1.7.0" From d3fe089253ed663b4ea2ea0e53f366b6f8a6a16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20B=C5=82a=C5=BCewicz?= Date: Mon, 17 Jan 2022 12:12:33 +0100 Subject: [PATCH 052/302] Update compatible Python versions --- CHANGELOG.md | 6 ++++++ CONTRIBUTING.md | 2 +- setup.py | 4 ++-- tox.ini | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c9926..5d1404b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased - TBD + +### Features + +- Update compatible Python versions. (Thanks: [blazewicz]) + ## 1.7.0 - 2022-01-11 ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad22cdf..decb224 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,7 @@ hasn't broken any existing functionality. To run the tests, just type in: $ ./setup.py test ``` -litecli supports Python 2.7 and 3.4+. You can test against multiple versions of +litecli supports Python 3.6+. You can test against multiple versions of Python by running tox: ```bash diff --git a/setup.py b/setup.py index 808b165..310a89b 100755 --- a/setup.py +++ b/setup.py @@ -57,10 +57,10 @@ def open_file(filename): "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: SQL", "Topic :: Database", "Topic :: Database :: Front-Ends", diff --git a/tox.ini b/tox.ini index 559538b..d11c4dd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py34, py35, py36, py37 +envlist = py36, py37, py38, py39 [testenv] deps = pytest From 9f5e8969abbfe81e7ca7c9c03ff7404c07cb75ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20B=C5=82a=C5=BCewicz?= Date: Mon, 17 Jan 2022 12:12:44 +0100 Subject: [PATCH 053/302] Add support for Python 3.10 --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + setup.py | 1 + tox.ini | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 372fbbb..cdb0ff3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1404b..9c78e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features - Update compatible Python versions. (Thanks: [blazewicz]) +- Add support for Python 3.10. (Thanks: [blazewicz]) ## 1.7.0 - 2022-01-11 diff --git a/setup.py b/setup.py index 310a89b..7eea59a 100755 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def open_file(filename): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: SQL", "Topic :: Database", "Topic :: Database :: Front-Ends", diff --git a/tox.ini b/tox.ini index d11c4dd..62b300e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36, py37, py38, py39 +envlist = py36, py37, py38, py39, py310 [testenv] deps = pytest From 9e9fe936c6971ed0eb1c10ec7a92c0660d34fd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20B=C5=82a=C5=BCewicz?= Date: Mon, 17 Jan 2022 12:00:43 +0100 Subject: [PATCH 054/302] Drop support for Python 3.6 Python 3.6 reached its end-of-life at 2021-12-23 --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 1 + CONTRIBUTING.md | 4 ++-- setup.py | 1 - tox.ini | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdb0ff3..7cead94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: [3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 @@ -36,7 +36,7 @@ jobs: - name: Run Black run: | ./setup.py lint - if: matrix.python-version == '3.6' + if: matrix.python-version == '3.7' - name: Coverage run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c78e07..5ff841f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Update compatible Python versions. (Thanks: [blazewicz]) - Add support for Python 3.10. (Thanks: [blazewicz]) +- Drop support for Python 3.6. (Thanks: [blazewicz]) ## 1.7.0 - 2022-01-11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index decb224..9cd868a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Development Guide -This is a guide for developers who would like to contribute to this project. It is recommended to use Python 3.6 and above for development. +This is a guide for developers who would like to contribute to this project. It is recommended to use Python 3.7 and above for development. If you're interested in contributing to litecli, thank you. We'd love your help! You'll always get credit for your work. @@ -79,7 +79,7 @@ hasn't broken any existing functionality. To run the tests, just type in: $ ./setup.py test ``` -litecli supports Python 3.6+. You can test against multiple versions of +litecli supports Python 3.7+. You can test against multiple versions of Python by running tox: ```bash diff --git a/setup.py b/setup.py index 7eea59a..0720078 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,6 @@ def open_file(filename): "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tox.ini b/tox.ini index 62b300e..c1c81ea 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36, py37, py38, py39, py310 +envlist = py37, py38, py39, py310 [testenv] deps = pytest From 35b069d74b55579eb8eaded9808f87daac65a2ea Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 18 Jan 2022 03:33:03 -0800 Subject: [PATCH 055/302] Upgrade cli_helpers to workaround the Pygments regression. (#129) --- CHANGELOG.md | 7 +++++++ setup.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c9926..d4550fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## TBD + +### Bug Fixes + +* Upgrade cli_helpers to workaround Pygments regression. + + ## 1.7.0 - 2022-01-11 ### Features diff --git a/setup.py b/setup.py index 808b165..48dbefd 100755 --- a/setup.py +++ b/setup.py @@ -24,11 +24,11 @@ def open_file(filename): install_requirements = [ "click >= 4.1", - "Pygments>=1.6,<=2.11.1", + "Pygments>=1.6", "prompt_toolkit>=3.0.3,<4.0.0", "sqlparse", "configobj >= 5.0.5", - "cli_helpers[styles] >= 1.0.1", + "cli_helpers[styles] >= 2.2.1", ] From d866ff414467bb0b0fe7e3be66386da55805f318 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 20 Jan 2022 17:37:32 -0800 Subject: [PATCH 056/302] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cead94..9ee36cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 From e15bbe3feceecf9122879e7e6489a68154fff18b Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:34:13 -0700 Subject: [PATCH 057/302] Fix the get_terminal_size to use shutil. --- litecli/main.py | 81 +++++++++++----------------------------------- tests/test_main.py | 11 +++---- 2 files changed, 23 insertions(+), 69 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index faa7322..1853a50 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -11,6 +11,7 @@ from io import open from collections import namedtuple from sqlite3 import OperationalError +import shutil from cli_helpers.tabular_output import TabularOutputFormatter from cli_helpers.tabular_output import preprocessors @@ -92,9 +93,7 @@ def __init__( self.login_path_as_host = c["main"].as_bool("login_path_as_host") # read from cli argument or user config file - self.auto_vertical_output = auto_vertical_output or c["main"].as_bool( - "auto_vertical_output" - ) + self.auto_vertical_output = auto_vertical_output or c["main"].as_bool("auto_vertical_output") # audit log if self.logfile is None and "audit_log" in c["main"]: @@ -114,9 +113,7 @@ def __init__( self.initialize_logging() prompt_cnf = self.read_my_cnf_files(["prompt"])["prompt"] - self.prompt_format = ( - prompt or prompt_cnf or c["main"]["prompt"] or self.default_prompt - ) + self.prompt_format = prompt or prompt_cnf or c["main"]["prompt"] or self.default_prompt self.prompt_continuation_format = c["main"]["prompt_continuation"] keyword_casing = c["main"].get("keyword_casing", "auto") @@ -257,10 +254,7 @@ def initialize_logging(self): ) return - formatter = logging.Formatter( - "%(asctime)s (%(process)d/%(threadName)s) " - "%(name)s %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s (%(process)d/%(threadName)s) " "%(name)s %(levelname)s - %(message)s") handler.setFormatter(formatter) @@ -359,8 +353,7 @@ def run_cli(self): else: history = None self.echo( - 'Error: Unable to open the history file "{}". ' - "Your query history will not be saved.".format(history_file), + 'Error: Unable to open the history file "{}". ' "Your query history will not be saved.".format(history_file), err=True, fg="red", ) @@ -375,10 +368,7 @@ def run_cli(self): def get_message(): prompt = self.get_prompt(self.prompt_format) - if ( - self.prompt_format == self.default_prompt - and len(prompt) > self.max_len_prompt - ): + if self.prompt_format == self.default_prompt and len(prompt) > self.max_len_prompt: prompt = self.get_prompt("\\d> ") return [("class:prompt", prompt)] @@ -458,9 +448,7 @@ def one_iteration(text=None): else: max_width = None - formatted = self.format_output( - title, cur, headers, special.is_expanded_output(), max_width - ) + formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width) t = time() - start try: @@ -487,9 +475,7 @@ def one_iteration(text=None): # Restart connection to the database sqlexecute.connect() try: - for title, cur, headers, status in sqlexecute.run( - "kill %s" % connection_id_to_kill - ): + 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( @@ -610,11 +596,7 @@ def echo(self, s, **kwargs): def get_output_margin(self, status=None): """Get the output margin (number of rows for the prompt, footer and timing message.""" - margin = ( - self.get_reserved_space() - + self.get_prompt(self.prompt_format).count("\n") - + 2 - ) + margin = self.get_reserved_space() + self.get_prompt(self.prompt_format).count("\n") + 2 if status: margin += 1 + status.count("\n") @@ -700,9 +682,7 @@ def refresh_completions(self, reset=False): }, ) - return [ - (None, None, None, "Auto-completion refresh started in the background.") - ] + return [(None, None, None, "Auto-completion refresh started in the background.")] def _on_completions_refreshed(self, new_completer): """Swap the completer object in cli with the newly created completer.""" @@ -716,9 +696,7 @@ def _on_completions_refreshed(self, new_completer): def get_completions(self, text, cursor_positition): with self._completer_lock: - return self.completer.get_completions( - Document(text=text, cursor_position=cursor_positition), None - ) + return self.completer.get_completions(Document(text=text, cursor_position=cursor_positition), None) def get_prompt(self, string): self.logger.debug("Getting prompt") @@ -776,11 +754,7 @@ def get_col_type(col): cur = list(cur) formatted = self.formatter.format_output( - cur, - headers, - format_name="vertical" if expanded else None, - column_types=column_types, - **output_kwargs + cur, headers, format_name="vertical" if expanded else None, column_types=column_types, **output_kwargs ) if isinstance(formatted, (text_type)): @@ -790,20 +764,8 @@ def get_col_type(col): first_line = next(formatted) formatted = itertools.chain([first_line], formatted) - if ( - not expanded - and max_width - and headers - and cur - and len(first_line) > max_width - ): - formatted = self.formatter.format_output( - cur, - headers, - format_name="vertical", - column_types=column_types, - **output_kwargs - ) + if not expanded and max_width and headers and cur and len(first_line) > max_width: + formatted = self.formatter.format_output(cur, headers, format_name="vertical", column_types=column_types, **output_kwargs) if isinstance(formatted, (text_type)): formatted = iter(formatted.splitlines()) @@ -815,7 +777,7 @@ def get_reserved_space(self): """Get the number of lines to reserve for the completion menu.""" reserved_space_ratio = 0.45 max_reserved_space = 8 - _, height = click.get_terminal_size() + _, height = shutil.get_terminal_size() return min(int(round(height * reserved_space_ratio)), max_reserved_space) def get_last_query(self): @@ -849,13 +811,9 @@ def get_last_query(self): is_flag=True, help="Automatically switch to vertical output mode if the result is wider than the terminal width.", ) -@click.option( - "-t", "--table", is_flag=True, help="Display batch output in table format." -) +@click.option("-t", "--table", is_flag=True, help="Display batch output in table format.") @click.option("--csv", is_flag=True, help="Display batch output in CSV format.") -@click.option( - "--warn/--no-warn", default=None, help="Warn before running a destructive query." -) +@click.option("--warn/--no-warn", default=None, help="Warn before running a destructive query.") @click.option("-e", "--execute", type=str, help="Execute command and quit.") @click.argument("database", default="", nargs=1) def cli( @@ -923,10 +881,7 @@ def cli( except (FileNotFoundError, OSError): litecli.logger.warning("Unable to open TTY as stdin.") - if ( - litecli.destructive_warning - and confirm_destructive_query(stdin_text) is False - ): + if litecli.destructive_warning and confirm_destructive_query(stdin_text) is False: exit(0) try: new_line = True diff --git a/tests/test_main.py b/tests/test_main.py index 90132f1..091600f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2,6 +2,7 @@ from collections import namedtuple from textwrap import dedent from tempfile import NamedTemporaryFile +import shutil import click from click.testing import CliRunner @@ -179,9 +180,7 @@ def secho(s): def test_conditional_pager(monkeypatch): - testdata = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do".split( - " " - ) + testdata = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do".split(" ") # User didn't set pager, output doesn't fit screen -> pager output( monkeypatch, @@ -232,12 +231,12 @@ def test_reserved_space_is_integer(): def stub_terminal_size(): return (5, 5) - old_func = click.get_terminal_size + old_func = shutil.get_terminal_size - click.get_terminal_size = stub_terminal_size + shutil.get_terminal_size = stub_terminal_size lc = LiteCli() assert isinstance(lc.get_reserved_space(), int) - click.get_terminal_size = old_func + shutil.get_terminal_size = old_func @dbtest From 1b33842149c5fb8c0e547f54a3e6b89e6dfe4756 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:36:20 -0700 Subject: [PATCH 058/302] Blacken --- litecli/main.py | 78 ++++++++++++++++++++++++++++++++++++---------- tests/test_main.py | 4 ++- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index 1853a50..dce3c1f 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -93,7 +93,9 @@ def __init__( self.login_path_as_host = c["main"].as_bool("login_path_as_host") # read from cli argument or user config file - self.auto_vertical_output = auto_vertical_output or c["main"].as_bool("auto_vertical_output") + self.auto_vertical_output = auto_vertical_output or c["main"].as_bool( + "auto_vertical_output" + ) # audit log if self.logfile is None and "audit_log" in c["main"]: @@ -113,7 +115,9 @@ def __init__( self.initialize_logging() prompt_cnf = self.read_my_cnf_files(["prompt"])["prompt"] - self.prompt_format = prompt or prompt_cnf or c["main"]["prompt"] or self.default_prompt + self.prompt_format = ( + prompt or prompt_cnf or c["main"]["prompt"] or self.default_prompt + ) self.prompt_continuation_format = c["main"]["prompt_continuation"] keyword_casing = c["main"].get("keyword_casing", "auto") @@ -254,7 +258,10 @@ def initialize_logging(self): ) return - formatter = logging.Formatter("%(asctime)s (%(process)d/%(threadName)s) " "%(name)s %(levelname)s - %(message)s") + formatter = logging.Formatter( + "%(asctime)s (%(process)d/%(threadName)s) " + "%(name)s %(levelname)s - %(message)s" + ) handler.setFormatter(formatter) @@ -353,7 +360,8 @@ def run_cli(self): else: history = None self.echo( - 'Error: Unable to open the history file "{}". ' "Your query history will not be saved.".format(history_file), + 'Error: Unable to open the history file "{}". ' + "Your query history will not be saved.".format(history_file), err=True, fg="red", ) @@ -368,7 +376,10 @@ def run_cli(self): def get_message(): prompt = self.get_prompt(self.prompt_format) - if self.prompt_format == self.default_prompt and len(prompt) > self.max_len_prompt: + if ( + self.prompt_format == self.default_prompt + and len(prompt) > self.max_len_prompt + ): prompt = self.get_prompt("\\d> ") return [("class:prompt", prompt)] @@ -448,7 +459,9 @@ def one_iteration(text=None): else: max_width = None - formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width) + formatted = self.format_output( + title, cur, headers, special.is_expanded_output(), max_width + ) t = time() - start try: @@ -475,7 +488,9 @@ def one_iteration(text=None): # Restart connection to the database sqlexecute.connect() try: - for title, cur, headers, status in sqlexecute.run("kill %s" % connection_id_to_kill): + 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( @@ -596,7 +611,11 @@ def echo(self, s, **kwargs): def get_output_margin(self, status=None): """Get the output margin (number of rows for the prompt, footer and timing message.""" - margin = self.get_reserved_space() + self.get_prompt(self.prompt_format).count("\n") + 2 + margin = ( + self.get_reserved_space() + + self.get_prompt(self.prompt_format).count("\n") + + 2 + ) if status: margin += 1 + status.count("\n") @@ -682,7 +701,9 @@ def refresh_completions(self, reset=False): }, ) - return [(None, None, None, "Auto-completion refresh started in the background.")] + return [ + (None, None, None, "Auto-completion refresh started in the background.") + ] def _on_completions_refreshed(self, new_completer): """Swap the completer object in cli with the newly created completer.""" @@ -696,7 +717,9 @@ def _on_completions_refreshed(self, new_completer): def get_completions(self, text, cursor_positition): with self._completer_lock: - return self.completer.get_completions(Document(text=text, cursor_position=cursor_positition), None) + return self.completer.get_completions( + Document(text=text, cursor_position=cursor_positition), None + ) def get_prompt(self, string): self.logger.debug("Getting prompt") @@ -754,7 +777,11 @@ def get_col_type(col): cur = list(cur) formatted = self.formatter.format_output( - cur, headers, format_name="vertical" if expanded else None, column_types=column_types, **output_kwargs + cur, + headers, + format_name="vertical" if expanded else None, + column_types=column_types, + **output_kwargs ) if isinstance(formatted, (text_type)): @@ -764,8 +791,20 @@ def get_col_type(col): first_line = next(formatted) formatted = itertools.chain([first_line], formatted) - if not expanded and max_width and headers and cur and len(first_line) > max_width: - formatted = self.formatter.format_output(cur, headers, format_name="vertical", column_types=column_types, **output_kwargs) + if ( + not expanded + and max_width + and headers + and cur + and len(first_line) > max_width + ): + formatted = self.formatter.format_output( + cur, + headers, + format_name="vertical", + column_types=column_types, + **output_kwargs + ) if isinstance(formatted, (text_type)): formatted = iter(formatted.splitlines()) @@ -811,9 +850,13 @@ def get_last_query(self): is_flag=True, help="Automatically switch to vertical output mode if the result is wider than the terminal width.", ) -@click.option("-t", "--table", is_flag=True, help="Display batch output in table format.") +@click.option( + "-t", "--table", is_flag=True, help="Display batch output in table format." +) @click.option("--csv", is_flag=True, help="Display batch output in CSV format.") -@click.option("--warn/--no-warn", default=None, help="Warn before running a destructive query.") +@click.option( + "--warn/--no-warn", default=None, help="Warn before running a destructive query." +) @click.option("-e", "--execute", type=str, help="Execute command and quit.") @click.argument("database", default="", nargs=1) def cli( @@ -881,7 +924,10 @@ def cli( except (FileNotFoundError, OSError): litecli.logger.warning("Unable to open TTY as stdin.") - if litecli.destructive_warning and confirm_destructive_query(stdin_text) is False: + if ( + litecli.destructive_warning + and confirm_destructive_query(stdin_text) is False + ): exit(0) try: new_line = True diff --git a/tests/test_main.py b/tests/test_main.py index 091600f..d4d52af 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -180,7 +180,9 @@ def secho(s): def test_conditional_pager(monkeypatch): - testdata = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do".split(" ") + testdata = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do".split( + " " + ) # User didn't set pager, output doesn't fit screen -> pager output( monkeypatch, From 78c84ba912e4ab2d3104029bf5ef55e0ae6ca76e Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:41:52 -0700 Subject: [PATCH 059/302] Update changelog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 780c97c..6fad747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased - TBD +## 1.8.0 - 2022-03-29 ### Features @@ -9,6 +9,7 @@ ### Bug Fixes * Upgrade cli_helpers to workaround Pygments regression. +* Use get_terminal_size from shutil instead of click. ## 1.7.0 - 2022-01-11 From 59fa76ac9b7dbdacfc982233721fc20bec5120f0 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:48:05 -0700 Subject: [PATCH 060/302] Update pre-commit. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f53bac..67ba03d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.3.0 hooks: - id: black From f3d19212f2a3c0ff9df45c2f3d3149c33240d042 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:48:18 -0700 Subject: [PATCH 061/302] Releasing version 1.8.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 14d9d2f..29654ee 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.7.0" +__version__ = "1.8.0" From 7c35bf3aa0e5868f46d171c44475090c0b6c5867 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Tue, 29 Mar 2022 17:50:01 -0700 Subject: [PATCH 062/302] Update release script. --- release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.py b/release.py index 264a4c3..f6beb88 100644 --- a/release.py +++ b/release.py @@ -75,7 +75,7 @@ def upload_distribution_files(): def push_to_github(): - run_step("git", "push", "origin", "master") + run_step("git", "push", "origin", "main") def push_tags_to_github(): From e07fed962271d0f2ba60cff36f57fb7765d41aab Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 28 Apr 2022 06:33:56 -0700 Subject: [PATCH 063/302] Add ansi support in prompts. --- CHANGELOG.md | 7 +++++++ litecli/main.py | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fad747..e33b107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## TBD - + +### Features + +* Add support for ANSI escape sequences for coloring the prompt. + + ## 1.8.0 - 2022-03-29 ### Features diff --git a/litecli/main.py b/litecli/main.py index dce3c1f..986bf65 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -23,6 +23,7 @@ from prompt_toolkit.styles.pygments import style_from_pygments_cls from prompt_toolkit.document import Document from prompt_toolkit.filters import HasFocus, IsDone +from prompt_toolkit.formatted_text import ANSI from prompt_toolkit.layout.processors import ( HighlightMatchingBracketProcessor, ConditionalProcessor, @@ -381,7 +382,8 @@ def get_message(): and len(prompt) > self.max_len_prompt ): prompt = self.get_prompt("\\d> ") - return [("class:prompt", prompt)] + prompt = prompt.replace("\\x1b", "\x1b") + return ANSI(prompt) def get_continuation(width, line_number, is_soft_wrap): continuation = " " * (width - 1) + " " From 7ca8ff02969d30fc301102a5aa8f96b2b4bf86bd Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 28 Apr 2022 06:35:37 -0700 Subject: [PATCH 064/302] Include documentation in the config file. --- litecli/liteclirc | 1 + 1 file changed, 1 insertion(+) diff --git a/litecli/liteclirc b/litecli/liteclirc index f0a4285..5a42e89 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -62,6 +62,7 @@ wider_completion_menu = False # \R - The current time, in 24-hour military time (0-23) # \r - The current time, standard 12-hour time (1-12) # \s - Seconds of the current time +# \x1b[...m - insert ANSI escape sequence prompt = '\d> ' prompt_continuation = '-> ' From 48301679178bcaed98575485e663d368298b9ac6 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 4 Jun 2022 20:21:24 -0700 Subject: [PATCH 065/302] Add .indexes command. --- litecli/packages/completion_engine.py | 4 +-- litecli/packages/special/dbcommands.py | 35 ++++++++++++++++++++++++++ litecli/sqlexecute.py | 7 ++++++ tests/test_dbspecial.py | 11 ++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/litecli/packages/completion_engine.py b/litecli/packages/completion_engine.py index 0397857..0e2a30f 100644 --- a/litecli/packages/completion_engine.py +++ b/litecli/packages/completion_engine.py @@ -105,14 +105,14 @@ def suggest_special(text): if cmd in ["\\f", "\\fs", "\\fd"]: return [{"type": "favoritequery"}] - if cmd in ["\\d", "\\dt", "\\dt+", ".schema"]: + if cmd in ["\\d", "\\dt", "\\dt+", ".schema", ".indexes"]: return [ {"type": "table", "schema": []}, {"type": "view", "schema": []}, {"type": "schema"}, ] - if cmd in ["\\.", "source", ".open"]: + if cmd in ["\\.", "source", ".open", ".read"]: return [{"type": "file_name"}] if cmd in [".import"]: diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index a7eaa0c..f7bf415 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -110,6 +110,41 @@ def list_databases(cur, **_): return [(None, None, None, "")] +@special_command( + ".indexes", + ".indexes [tablename]", + "List indexes.", + arg_type=PARSED_QUERY, + case_sensitive=True, + aliases=("\\di",), +) +def list_indexes(cur, arg=None, arg_type=PARSED_QUERY, verbose=False): + if arg: + args = ("{0}%".format(arg),) + query = """ + SELECT name FROM sqlite_master + WHERE type = 'index' AND tbl_name LIKE ? AND name NOT LIKE 'sqlite_%' + ORDER BY 1 + """ + else: + args = tuple() + query = """ + SELECT name FROM sqlite_master + WHERE type = 'index' AND name NOT LIKE 'sqlite_%' + ORDER BY 1 + """ + + log.debug(query) + cur.execute(query, args) + indexes = cur.fetchall() + status = "" + if cur.description: + headers = [x[0] for x in cur.description] + else: + return [(None, None, None, "")] + return [(None, indexes, headers, status)] + + @special_command( ".status", "\\s", diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 2b8fce3..3f78d49 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -38,6 +38,13 @@ class SQLExecute(object): ORDER BY tableName, columnName """ + indexes_query = """ + SELECT name + FROM sqlite_master + WHERE type = 'index' AND name NOT LIKE 'sqlite_%' + ORDER BY 1 + """ + functions_query = '''SELECT ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE="FUNCTION" AND ROUTINE_SCHEMA = "%s"''' diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py index c7065a9..5128b5b 100644 --- a/tests/test_dbspecial.py +++ b/tests/test_dbspecial.py @@ -63,3 +63,14 @@ def test_format_uptime(): seconds = 522600 assert "6 days 1 hour 10 min 0 sec" == format_uptime(seconds) + + +def test_indexes(): + suggestions = suggest_type(".indexes", ".indexes ") + assert sorted_dicts(suggestions) == sorted_dicts( + [ + {"type": "table", "schema": []}, + {"type": "view", "schema": []}, + {"type": "schema"}, + ] + ) From f2b147dc9e6c96f46edd22999ca2dade916102da Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 4 Jun 2022 20:23:12 -0700 Subject: [PATCH 066/302] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e33b107..1883f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features * Add support for ANSI escape sequences for coloring the prompt. +* Add support for `.indexes` command. ## 1.8.0 - 2022-03-29 From 637ddc7b7d2d8a71557bcfa348eaa0ebe9b4b3ad Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 4 Jun 2022 20:42:37 -0700 Subject: [PATCH 067/302] Handle the vi mode crashing for replace single char. --- litecli/clitoolbar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litecli/clitoolbar.py b/litecli/clitoolbar.py index 05d0bfd..1e28784 100644 --- a/litecli/clitoolbar.py +++ b/litecli/clitoolbar.py @@ -48,4 +48,5 @@ def _get_vi_mode(): InputMode.NAVIGATION: "N", InputMode.REPLACE: "R", InputMode.INSERT_MULTIPLE: "M", + InputMode.REPLACE_SINGLE: "R", }[get_app().vi_state.input_mode] From deb5686182186ac122d98a7755992fe28c81e3ac Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 4 Jun 2022 20:55:05 -0700 Subject: [PATCH 068/302] Fix the .read command. (#140) * Fix the .read command. --- CHANGELOG.md | 4 ++++ litecli/main.py | 5 +++-- litecli/packages/special/dbcommands.py | 18 ------------------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e33b107..c4414d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Add support for ANSI escape sequences for coloring the prompt. +### Bug Fixes + +* Fix #120. Make the `.read` command actually read and execute the commands from a file. + ## 1.8.0 - 2022-03-29 diff --git a/litecli/main.py b/litecli/main.py index 986bf65..05cfc30 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -162,10 +162,11 @@ def register_special_commands(self): ) special.register_special_command( self.execute_from_file, - "source", + ".read", "\\. filename", "Execute commands from file.", - aliases=("\\.",), + case_sensitive=True, + aliases=("\\.", "source"), ) special.register_special_command( self.change_prompt_format, diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index a7eaa0c..15db578 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -202,24 +202,6 @@ def describe(cur, arg, **_): return [(None, tables, headers, status)] -@special_command( - ".read", - ".read path", - "Read input from path", - arg_type=PARSED_QUERY, - case_sensitive=True, -) -def read_script(cur, arg, **_): - args = shlex.split(arg) - if len(args) != 1: - raise TypeError(".read accepts exactly one path") - path = args[0] - with open(path, "r") as f: - script = f.read() - cur.executescript(script) - return [(None, None, None, "")] - - @special_command( ".import", ".import filename table", From 05a6d1bbf8d6c4455fc73fe01b86c5120238d2a4 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 4 Jun 2022 21:01:18 -0700 Subject: [PATCH 069/302] Update changelog. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4414d2..4eb5067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ ### Bug Fixes -* Fix #120. Make the `.read` command actually read and execute the commands from a file. - +* Fix [#120](https://github.com/dbcli/litecli/issues/120). Make the `.read` command actually read and execute the commands from a file. +* Fix [#96](https://github.com/dbcli/litecli/issues/96) the crash in VI mode when pressing `r`. ## 1.8.0 - 2022-03-29 From 3bbdd5ebf0b2cf078a6b50a4ef60d2754a76d511 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 5 Jun 2022 14:44:01 -0700 Subject: [PATCH 070/302] Option to turn off autocompletion. --- litecli/liteclirc | 4 ++++ litecli/main.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/litecli/liteclirc b/litecli/liteclirc index 5a42e89..4db6f3a 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -52,6 +52,10 @@ key_bindings = emacs # Enabling this option will show the suggestions in a wider menu. Thus more items are suggested. wider_completion_menu = False +# Autocompletion is on by default. This can be truned off by setting this +# option to False. Pressing tab will still trigger completion. +autocompletion = True + # litecli prompt # \D - The full current date # \d - Database name diff --git a/litecli/main.py b/litecli/main.py index 05cfc30..de279f6 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -89,6 +89,7 @@ def __init__( self.cli_style = c["colors"] self.output_style = style_factory_output(self.syntax_style, self.cli_style) self.wider_completion_menu = c["main"].as_bool("wider_completion_menu") + self.autocompletion = c["main"].as_bool("autocompletion") c_dest_warning = c["main"].as_bool("destructive_warning") self.destructive_warning = c_dest_warning if warn is None else warn self.login_path_as_host = c["main"].as_bool("login_path_as_host") @@ -550,6 +551,9 @@ def one_iteration(text=None): else: complete_style = CompleteStyle.COLUMN + if not self.autocompletion: + complete_style = CompleteStyle.READLINE_LIKE + with self._completer_lock: if self.key_bindings == "vi": From 57d9e7cfb764e89f6d6d1bbc9c83905fcfda288b Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 5 Jun 2022 14:45:09 -0700 Subject: [PATCH 071/302] Update the config file in tests. --- tests/liteclirc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/liteclirc b/tests/liteclirc index 979b409..da9b061 100644 --- a/tests/liteclirc +++ b/tests/liteclirc @@ -51,6 +51,10 @@ key_bindings = emacs # Enabling this option will show the suggestions in a wider menu. Thus more items are suggested. wider_completion_menu = False +# Autocompletion is on by default. This can be truned off by setting this +# option to False. Pressing tab will still trigger completion. +autocompletion = True + # litecli prompt # \D - The full current date # \d - Database name @@ -61,9 +65,13 @@ wider_completion_menu = False # \R - The current time, in 24-hour military time (0-23) # \r - The current time, standard 12-hour time (1-12) # \s - Seconds of the current time +# \x1b[...m - insert ANSI escape sequence prompt = "\t :\d> " prompt_continuation = "-> " +# Show/hide the informational toolbar with function keymap at the footer. +show_bottom_toolbar = True + # Skip intro info on startup and outro info on exit less_chatty = False From 974d061627bb9ab371d6d0b8348b59543110f184 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 5 Jun 2022 14:47:11 -0700 Subject: [PATCH 072/302] Update changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eb5067..06a4a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ### Features * Add support for ANSI escape sequences for coloring the prompt. +* Add an option to turn off the auto-completion menu. Completion menu can be + triggered by pressed the `` key when this option is set to False. Fixes + [#105](https://github.com/dbcli/litecli/issues/105). ### Bug Fixes From ef9400b94c2bf5dde715a460bd07a6d829bda858 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 6 Jun 2022 20:10:54 -0700 Subject: [PATCH 073/302] Changelog for 1.9.0 release. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b2a70..afeebd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## TBD - +## 1.9.0 - 2022-06-06 ### Features From 7b2190dbd043b961e224eabe729322684e6dd1c0 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 6 Jun 2022 20:11:13 -0700 Subject: [PATCH 074/302] Releasing version 1.9.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index 29654ee..0a0a43a 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.8.0" +__version__ = "1.9.0" From 0eaf72eed8c98f2c0a2bb4b6d1f6601d18b66bee Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Mon, 20 Jun 2022 20:10:25 +0800 Subject: [PATCH 075/302] Fix typos --- litecli/packages/completion_engine.py | 2 +- litecli/packages/parseutils.py | 2 +- litecli/packages/special/iocommands.py | 2 +- tasks.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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/iocommands.py b/litecli/packages/special/iocommands.py index 43c3577..92ea444 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -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" diff --git a/tasks.py b/tasks.py index 1cd4b69..1714646 100644 --- a/tasks.py +++ b/tasks.py @@ -34,7 +34,7 @@ def call_and_exit(self, cmd, shell=True): sys.exit(subprocess.call(cmd, shell=shell)) def call_in_sequence(self, cmds, shell=True): - """Run multiple commmands in a row, exiting if one fails.""" + """Run multiple commands in a row, exiting if one fails.""" for cmd in cmds: if subprocess.call(cmd, shell=shell) == 1: sys.exit(1) From 5b46fc8d86b2b3e3ae394b8790a8b8fad671dca2 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 3 Jul 2022 19:55:37 +1000 Subject: [PATCH 076/302] docs: Fix a few typos There are small typos in: - litecli/completion_refresher.py Fixes: - Should read `populate` rather than `popoulate`. - Should read `naturally` rather than `natually`. --- litecli/completion_refresher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litecli/completion_refresher.py b/litecli/completion_refresher.py index 9602070..91ea515 100644 --- a/litecli/completion_refresher.py +++ b/litecli/completion_refresher.py @@ -65,7 +65,7 @@ def _bg_refresh(self, sqlexecute, callbacks, completer_options): # 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 +79,7 @@ def _bg_refresh(self, sqlexecute, callbacks, completer_options): 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 From 3f6013f007c8f01956e07faf0f3d044c7c43b35c Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Sun, 10 Jul 2022 15:22:12 +0100 Subject: [PATCH 077/302] Make it possible to use .once more than once When .once has been used, reset `written_to_once_file` as otherwise `once_file` is cleared after every subsequent command, including additional `.once` commands. --- CHANGELOG.md | 7 +++++++ litecli/packages/special/iocommands.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afeebd1..6c15b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## TBD + +### Bug Fixes + +* Fix [[#146](https://github.com/dbcli/litecli/issues/146)], making sure `.once` + can be used more than once in a session. + ## 1.9.0 - 2022-06-06 ### Features diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 92ea444..0429430 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -405,9 +405,9 @@ def write_once(output): @export def unset_once_if_written(): """Unset the once file, if it has been written to.""" - global once_file + global once_file, written_to_once_file if written_to_once_file: - once_file = None + once_file = written_to_once_file = None @special_command( From c8417196af9e3eea81c90acb34acecad63d2342c Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Thu, 10 Nov 2022 14:02:01 +0000 Subject: [PATCH 078/302] Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..384b12f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: "13 4 * * 0" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From 7bf06230f9a72cc27f5490feb13d683b1f7eb9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Brende=20Smestad?= Date: Wed, 3 May 2023 10:47:00 +0200 Subject: [PATCH 079/302] Update CONTRIBUTING.md Changed git pull upstream master to git pull upstream main to reflect changes to github naming. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cd868a..e4f4456 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,7 @@ You'll always get credit for your work. 7. While you work on your bugfix or feature, be sure to pull the latest changes from `upstream`. This ensures that your local codebase is up-to-date: ```bash - $ git pull upstream master + $ git pull upstream main ``` 8. When your work is ready for the litecli team to review it, push your branch to your fork: From 8cac55323d9879fdf795c5a66957524aba2e416f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Brende=20Smestad?= Date: Wed, 3 May 2023 10:51:02 +0200 Subject: [PATCH 080/302] Update CONTRIBUTING.md Found another master, substituted with main. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4f4456..a38d8d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ You'll always get credit for your work. $ pip install --editable . ``` -6. Create a branch for your bugfix or feature based off the `master` branch: +6. Create a branch for your bugfix or feature based off the `main` branch: ```bash $ git checkout -b From 550eceeb2189d522a1206f1b7190d4e65b0579b4 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Wed, 3 May 2023 21:28:51 +0200 Subject: [PATCH 081/302] Fixed missing implementation of successful --- litecli/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/litecli/main.py b/litecli/main.py index de279f6..34db568 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -442,7 +442,6 @@ def one_iteration(text=None): start = time() res = sqlexecute.run(text) self.formatter.query = text - successful = True result_count = 0 for title, cur, headers, status in res: logger.debug("headers: %r", headers) @@ -457,6 +456,8 @@ def one_iteration(text=None): if not confirm("Do you want to continue?"): self.echo("Aborted!", err=True, fg="red") break + else: + successful = True if self.auto_vertical_output: max_width = self.prompt_app.output.get_size().columns @@ -475,6 +476,8 @@ def one_iteration(text=None): self.output(formatted, status) except KeyboardInterrupt: pass + else: + successful = True self.echo("Time: %0.03fs" % t) except KeyboardInterrupt: pass From c27c2f25ef617fecc46d4fe65fc798f7bc877622 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Wed, 3 May 2023 22:58:48 +0200 Subject: [PATCH 082/302] Added changes done in changelog. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c15b38..3df0efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Fix [[#146](https://github.com/dbcli/litecli/issues/146)], making sure `.once` can be used more than once in a session. +* Fixed setting `successful = True` only when query is executed without exceptions so + failing queries get `successful = False` in `query_history`. +* Changed `master` to `main` in CONTRIBUTING.md to reflect GitHubs new default branch + naming. ## 1.9.0 - 2022-06-06 @@ -115,3 +119,4 @@ [Shawn Chapla]: https://github.com/shwnchpl [Zhaolong Zhu]: https://github.com/zzl0 [Zhiming Wang]: https://github.com/zmwangx +[Bjørnar Smestad]: https://brendesmestad.no From d48571b7696970a39f676682922cd6f9084d95c4 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Thu, 4 May 2023 13:05:10 +0200 Subject: [PATCH 083/302] Minimal implementation of startup commands --- litecli/liteclirc | 8 +++++++- litecli/main.py | 39 ++++++++++++++++++++++++++++++++++----- litecli/sqlexecute.py | 17 ++++++++++++----- 3 files changed, 53 insertions(+), 11 deletions(-) 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 34db568..25c15ea 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -110,7 +110,12 @@ def __init__( 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() self.logger = logging.getLogger(__name__) @@ -442,6 +447,7 @@ def one_iteration(text=None): start = time() res = sqlexecute.run(text) self.formatter.query = text + successful = True result_count = 0 for title, cur, headers, status in res: logger.debug("headers: %r", headers) @@ -456,8 +462,6 @@ def one_iteration(text=None): if not confirm("Do you want to continue?"): self.echo("Aborted!", err=True, fg="red") break - else: - successful = True if self.auto_vertical_output: max_width = self.prompt_app.output.get_size().columns @@ -476,8 +480,6 @@ def one_iteration(text=None): self.output(formatted, status) except KeyboardInterrupt: pass - else: - successful = True self.echo("Time: %0.03fs" % t) except KeyboardInterrupt: pass @@ -592,6 +594,33 @@ def one_iteration(text=None): editing_mode=editing_mode, search_ignore_case=True, ) + def startup_commands(): + #TODO: Wait for attach db + 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: diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 3f78d49..b721654 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -8,7 +8,6 @@ import os.path from .packages import special - _logger = logging.getLogger(__name__) # FIELD_TYPES = decoders.copy() @@ -16,6 +15,9 @@ # FIELD_TYPE.NULL: type(None) # }) +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'] + + class SQLExecute(object): @@ -79,6 +81,7 @@ def connect(self, database=None): # retrieve connection id self.reset_connection_id() + def run(self, statement): """Execute the sql in the database and return the results. The results are a list of tuples. Each tuple has 4 values @@ -129,10 +132,14 @@ def run(self, statement): 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) - + basecommand = sql.split(' ', 1)[0] + if basecommand.lower() in sqlite3dotcommands: + 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.""" title = headers = None From cc87ad095999a6a608255370b34ef4378a25f800 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Wed, 10 May 2023 23:17:38 +0200 Subject: [PATCH 084/302] Refactored the code and updated test/liteclirc --- litecli/main.py | 4 +--- litecli/packages/special/utils.py | 11 +++++++++++ litecli/sqlexecute.py | 9 +++------ tests/liteclirc | 7 +++++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index 25c15ea..46e3c88 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -115,7 +115,7 @@ def __init__( 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() self.logger = logging.getLogger(__name__) @@ -595,7 +595,6 @@ def one_iteration(text=None): search_ignore_case=True, ) def startup_commands(): - #TODO: Wait for attach db if self.startup_commands: if "commands" in self.startup_commands: for command in self.startup_commands['commands']: @@ -613,7 +612,6 @@ def startup_commands(): 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") diff --git a/litecli/packages/special/utils.py b/litecli/packages/special/utils.py index eed9306..2a872d6 100644 --- a/litecli/packages/special/utils.py +++ b/litecli/packages/special/utils.py @@ -46,3 +46,14 @@ 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 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'] + return (command.lower() in sqlite3dotcommands) diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index b721654..d22de33 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -3,6 +3,7 @@ import uuid from contextlib import closing from sqlite3 import OperationalError +from .packages.special.utils import check_if_sqlitedotcommand import sqlparse import os.path @@ -15,10 +16,6 @@ # FIELD_TYPE.NULL: type(None) # }) -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'] - - - class SQLExecute(object): databases_query = """ @@ -133,13 +130,13 @@ def run(self, statement): yield result except special.CommandNotFound: # Regular SQL basecommand = sql.split(' ', 1)[0] - if basecommand.lower() in sqlite3dotcommands: + if check_if_sqlitedotcommand(basecommand): 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.""" title = headers = None diff --git a/tests/liteclirc b/tests/liteclirc index da9b061..f7af8ff 100644 --- a/tests/liteclirc +++ b/tests/liteclirc @@ -135,3 +135,10 @@ Token.Toolbar.Arg.Text = nobold [favorite_queries] q_param = select * from test where name=? sh_param = select * from test where id=$1 + +# 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 From 1f1ea97f0afa36844b913b4df089e06dc8f2c739 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Thu, 11 May 2023 00:45:17 +0200 Subject: [PATCH 085/302] Bugfix + cosmetic --- litecli/packages/special/utils.py | 3 +-- litecli/sqlexecute.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/litecli/packages/special/utils.py b/litecli/packages/special/utils.py index 2a872d6..ae45597 100644 --- a/litecli/packages/special/utils.py +++ b/litecli/packages/special/utils.py @@ -47,13 +47,12 @@ 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 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'] return (command.lower() in sqlite3dotcommands) diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index d22de33..dd05240 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -3,7 +3,7 @@ import uuid from contextlib import closing from sqlite3 import OperationalError -from .packages.special.utils import check_if_sqlitedotcommand +from litecli.packages.special.utils import check_if_sqlitedotcommand import sqlparse import os.path From 6c204e06dc789dc62341f31c931f5ac32d095f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Brende=20Smestad?= Date: Thu, 11 May 2023 11:14:14 +0200 Subject: [PATCH 086/302] Update liteclirc Corrected the startupcommands section in tests/liteclirc --- tests/liteclirc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/liteclirc b/tests/liteclirc index f7af8ff..21a3ef9 100644 --- a/tests/liteclirc +++ b/tests/liteclirc @@ -140,5 +140,5 @@ sh_param = select * from test where id=$1 # 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 +[startup_commands] +commands = "create table startupcommands(a text)", "insert into startupcommands values('abc')" From c32f424571fad361b1681d3aa19faae16d255c05 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Thu, 11 May 2023 12:53:17 +0200 Subject: [PATCH 087/302] Added simple tests and refactored sqlitedotcommand --- litecli/packages/special/utils.py | 10 ++++++++-- litecli/sqlexecute.py | 3 +-- tests/test_dbspecial.py | 13 +++++++++++++ tests/test_main.py | 7 +++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/litecli/packages/special/utils.py b/litecli/packages/special/utils.py index ae45597..d9e45b5 100644 --- a/litecli/packages/special/utils.py +++ b/litecli/packages/special/utils.py @@ -51,8 +51,14 @@ def format_uptime(uptime_in_seconds): def check_if_sqlitedotcommand(command): """Does a check if the command supplied is in the list of SQLite dot commands. - :param command: A command supplied from the user + :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'] - return (command.lower() in sqlite3dotcommands) + + if isinstance(command, str): + command = command.split(' ', 1)[0].lower() + return (command in sqlite3dotcommands) + else: + return False \ No newline at end of file diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index dd05240..348ee17 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -129,8 +129,7 @@ def run(self, statement): for result in special.execute(cur, sql): yield result except special.CommandNotFound: # Regular SQL - basecommand = sql.split(' ', 1)[0] - if check_if_sqlitedotcommand(basecommand): + if check_if_sqlitedotcommand(sql): yield ('dot command not implemented', None, None, None) else: _logger.debug("Regular sql statement. sql: %r", sql) diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py index 5128b5b..7c561b2 100644 --- a/tests/test_dbspecial.py +++ b/tests/test_dbspecial.py @@ -1,6 +1,7 @@ from litecli.packages.completion_engine import suggest_type from test_completion_engine import sorted_dicts from litecli.packages.special.utils import format_uptime +from litecli.packages.special.utils import check_if_sqlitedotcommand def test_import_first_argument(): @@ -74,3 +75,15 @@ def test_indexes(): {"type": "schema"}, ] ) + + +def test_check_if_sqlitedotcommand(): + test_cases = [ + [".tables", True], + [".BiNarY", True], + ["binary", False], + [234, False], + [".changes test! test", True], + ["NotDotcommand", False]] + for command, expected_result in test_cases: + assert check_if_sqlitedotcommand(command) == expected_result \ No newline at end of file diff --git a/tests/test_main.py b/tests/test_main.py index d4d52af..1f9fe85 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -260,3 +260,10 @@ def test_import_command(executor): """ assert result.exit_code == 0 assert expected in "".join(result.output) + + +def test_startup_commands(executor): + m = LiteCli(liteclirc=default_config_file) + assert m.startup_commands['commands'] == ['create table startupcommands(a text)', "insert into startupcommands values('abc')"] + + # implement tests on executions of the startupcommands \ No newline at end of file From 59f074bbb0343aefd009be06795cd7fb1559e745 Mon Sep 17 00:00:00 2001 From: bjornasm Date: Thu, 11 May 2023 21:00:14 +0200 Subject: [PATCH 088/302] Updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df0efb..82e2609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## TBD +### Features + +* Adding support for startup commands being set in liteclirc and executed on startup. Limited to commands already implemented in litecli. ([[#56](https://github.com/dbcli/litecli/issues/56)]) + ### Bug Fixes * Fix [[#146](https://github.com/dbcli/litecli/issues/146)], making sure `.once` From 11a823130eca236300dc3dfe8307d33476614273 Mon Sep 17 00:00:00 2001 From: Kevin-Prichard Date: Fri, 25 Aug 2023 00:07:57 -0700 Subject: [PATCH 089/302] - litecli/packages/special/dbcommands.py- describe(): the 'describe' command does not appear when user requests 'help'. This happens because the command string 'describe' also appears in describe()'s aliases , which causes show_help() to skip it altogether (because aliases are added to COMMANDS with 'hidden=True', and this overwrites the original 'describe' entry.) Fix is to remove 'describe' from describe()'s aliases. --- litecli/packages/special/dbcommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 203e1a8..3bba548 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -212,7 +212,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: From 020a1019b6b4bd33851de7886e76e30e179f36c6 Mon Sep 17 00:00:00 2001 From: Liam Hennebury Date: Wed, 27 Sep 2023 12:20:56 -0300 Subject: [PATCH 090/302] Add key binding to accept completion with the right-arrow key --- litecli/key_bindings.py | 8 ++++++++ tests/test_smart_completion_public_schema_only.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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 _(event): 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/tests/test_smart_completion_public_schema_only.py b/tests/test_smart_completion_public_schema_only.py index e532118..c4a076a 100644 --- a/tests/test_smart_completion_public_schema_only.py +++ b/tests/test_smart_completion_public_schema_only.py @@ -376,7 +376,7 @@ def test_auto_escaped_col_names(completer, complete_event): Completion(text="id", start_position=0), ] + list(map(Completion, completer.functions)) - + [Completion(text="`select`", start_position=0)] + + [Completion(text="select", start_position=0)] + list(map(Completion, sorted(completer.keywords))) ) From ce16d63a1781ff495f4ec6b25e0df1a52e080a16 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Tue, 3 Oct 2023 15:48:04 +0100 Subject: [PATCH 091/302] Apply black This is necessary to pass the CI lint tests on all PRs. --- litecli/completion_refresher.py | 1 - litecli/main.py | 29 ++++--- litecli/packages/special/favoritequeries.py | 1 - litecli/packages/special/iocommands.py | 2 +- litecli/packages/special/utils.py | 75 ++++++++++++++++++- litecli/sqlcompleter.py | 1 - litecli/sqlexecute.py | 6 +- tasks.py | 1 - tests/test_dbspecial.py | 5 +- tests/test_main.py | 7 +- ...est_smart_completion_public_schema_only.py | 21 +++--- tests/test_sqlexecute.py | 18 ++--- 12 files changed, 120 insertions(+), 47 deletions(-) diff --git a/litecli/completion_refresher.py b/litecli/completion_refresher.py index 91ea515..bf36d03 100644 --- a/litecli/completion_refresher.py +++ b/litecli/completion_refresher.py @@ -7,7 +7,6 @@ class CompletionRefresher(object): - refreshers = OrderedDict() def __init__(self): diff --git a/litecli/main.py b/litecli/main.py index 46e3c88..e532d9d 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -59,7 +59,6 @@ class LiteCli(object): - default_prompt = "\\d> " max_len_prompt = 45 @@ -113,7 +112,9 @@ def __init__( # 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. + 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() @@ -235,7 +236,6 @@ def change_prompt_format(self, arg, **_): 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" @@ -303,7 +303,6 @@ def get(key): return {x: get(x) for x in keys} def connect(self, database=""): - cnf = {"database": None} cnf = self.read_my_cnf_files(cnf.keys()) @@ -560,7 +559,6 @@ def one_iteration(text=None): complete_style = CompleteStyle.READLINE_LIKE with self._completer_lock: - if self.key_bindings == "vi": editing_mode = EditingMode.VI else: @@ -594,10 +592,11 @@ def one_iteration(text=None): editing_mode=editing_mode, search_ignore_case=True, ) + def startup_commands(): if self.startup_commands: if "commands" in self.startup_commands: - for command in self.startup_commands['commands']: + for command in self.startup_commands["commands"]: try: res = sqlexecute.run(command) except Exception as e: @@ -606,19 +605,29 @@ def startup_commands(): 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") + 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") + 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") + self.echo( + "Could not execute all startup commands: \n" + str(e), fg="yellow" + ) try: while True: 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 @@ class FavoriteQueries(object): - section_name = "favorite_queries" usage = """ diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 0429430..1f77885 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -461,7 +461,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 d9e45b5..4d3ad91 100644 --- a/litecli/packages/special/utils.py +++ b/litecli/packages/special/utils.py @@ -55,10 +55,77 @@ def check_if_sqlitedotcommand(command): :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'] + 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) + command = command.split(" ", 1)[0].lower() + return command in sqlite3dotcommands else: - return False \ No newline at end of file + 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 @@ def get_completions(self, document, complete_event): 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 348ee17..5d00277 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -9,6 +9,7 @@ import os.path from .packages import special + _logger = logging.getLogger(__name__) # FIELD_TYPES = decoders.copy() @@ -16,8 +17,8 @@ # FIELD_TYPE.NULL: type(None) # }) -class SQLExecute(object): +class SQLExecute(object): databases_query = """ PRAGMA database_list """ @@ -78,7 +79,6 @@ def connect(self, database=None): # retrieve connection id self.reset_connection_id() - def run(self, statement): """Execute the sql in the database and return the results. The results are a list of tuples. Each tuple has 4 values @@ -130,7 +130,7 @@ def run(self, statement): yield result except special.CommandNotFound: # Regular SQL if check_if_sqlitedotcommand(sql): - yield ('dot command not implemented', None, None, None) + yield ("dot command not implemented", None, None, None) else: _logger.debug("Regular sql statement. sql: %r", sql) cur.execute(sql) diff --git a/tasks.py b/tasks.py index 1714646..fb632ae 100644 --- a/tasks.py +++ b/tasks.py @@ -75,7 +75,6 @@ def run(self): class test(TestCommand): - user_options = [("pytest-args=", "a", "Arguments to pass to pytest")] def initialize_options(self): diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py index 7c561b2..d3c7c07 100644 --- a/tests/test_dbspecial.py +++ b/tests/test_dbspecial.py @@ -84,6 +84,7 @@ def test_check_if_sqlitedotcommand(): ["binary", False], [234, False], [".changes test! test", True], - ["NotDotcommand", False]] + ["NotDotcommand", False], + ] for command, expected_result in test_cases: - assert check_if_sqlitedotcommand(command) == expected_result \ No newline at end of file + assert check_if_sqlitedotcommand(command) == expected_result diff --git a/tests/test_main.py b/tests/test_main.py index 1f9fe85..e2f183a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -264,6 +264,9 @@ def test_import_command(executor): def test_startup_commands(executor): m = LiteCli(liteclirc=default_config_file) - assert m.startup_commands['commands'] == ['create table startupcommands(a text)', "insert into startupcommands values('abc')"] + assert m.startup_commands["commands"] == [ + "create table startupcommands(a text)", + "insert into startupcommands values('abc')", + ] - # implement tests on executions of the startupcommands \ No newline at end of file + # implement tests on executions of the startupcommands diff --git a/tests/test_smart_completion_public_schema_only.py b/tests/test_smart_completion_public_schema_only.py index c4a076a..c8e1be4 100644 --- a/tests/test_smart_completion_public_schema_only.py +++ b/tests/test_smart_completion_public_schema_only.py @@ -15,7 +15,6 @@ @pytest.fixture def completer(): - import litecli.sqlcompleter as sqlcompleter comp = sqlcompleter.SQLCompleter() @@ -367,17 +366,15 @@ def test_auto_escaped_col_names(completer, complete_event): Document(text=text, cursor_position=position), complete_event ) ) - assert ( - result - == [ - Completion(text="*", start_position=0), - Completion(text="`ABC`", start_position=0), - Completion(text="`insert`", start_position=0), - Completion(text="id", start_position=0), - ] - + list(map(Completion, completer.functions)) - + [Completion(text="select", start_position=0)] - + list(map(Completion, sorted(completer.keywords))) + assert result == [ + Completion(text="*", start_position=0), + Completion(text="`ABC`", start_position=0), + Completion(text="`insert`", start_position=0), + Completion(text="id", start_position=0), + ] + list(map(Completion, completer.functions)) + [ + Completion(text="select", start_position=0) + ] + list( + map(Completion, sorted(completer.keywords)) ) diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index e559bc6..16bad74 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -94,11 +94,11 @@ def test_invalid_column_name(executor): @dbtest def test_unicode_support_in_output(executor): run(executor, "create table unicodechars(t text)") - run(executor, u"insert into unicodechars (t) values ('é')") + run(executor, "insert into unicodechars (t) values ('é')") # See issue #24, this raises an exception without proper handling - results = run(executor, u"select * from unicodechars") - assert_result_equal(results, headers=["t"], rows=[(u"é",)]) + results = run(executor, "select * from unicodechars") + assert_result_equal(results, headers=["t"], rows=[("é",)]) @dbtest @@ -106,9 +106,9 @@ def test_invalid_unicode_values_dont_choke(executor): run(executor, "create table unicodechars(t text)") # \xc3 is not a valid utf-8 char. But we can insert it into the database # which can break querying if not handled correctly. - run(executor, u"insert into unicodechars (t) values (cast(x'c3' as text))") + run(executor, "insert into unicodechars (t) values (cast(x'c3' as text))") - results = run(executor, u"select * from unicodechars") + results = run(executor, "select * from unicodechars") assert_result_equal(results, headers=["t"], rows=[("\\xc3",)]) @@ -120,13 +120,13 @@ def test_multiple_queries_same_line(executor): { "title": None, "headers": ["'foo'"], - "rows": [(u"foo",)], + "rows": [("foo",)], "status": "1 row in set", }, { "title": None, "headers": ["'bar'"], - "rows": [(u"bar",)], + "rows": [("bar",)], "status": "1 row in set", }, ] @@ -369,8 +369,8 @@ def test_cd_command_current_dir(executor): @dbtest def test_unicode_support(executor): - results = run(executor, u"SELECT '日本語' AS japanese;") - assert_result_equal(results, headers=["japanese"], rows=[(u"日本語",)]) + results = run(executor, "SELECT '日本語' AS japanese;") + assert_result_equal(results, headers=["japanese"], rows=[("日本語",)]) @dbtest From cc9689a2bac4b102f38a1c0ea32ab8d27029251b Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Tue, 3 Oct 2023 15:37:34 +0100 Subject: [PATCH 092/302] Use sqlite3 API to cancel running queries Since the interrupt handler was the only code that referenced `sqlexecute.connection_id`, and the connection ids is merely a random UUID without any meaning, the code generating that UUID can be removed as well. --- CHANGELOG.md | 2 ++ litecli/main.py | 20 ++++---------------- litecli/sqlexecute.py | 18 ------------------ 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e2609..4bd0f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ failing queries get `successful = False` in `query_history`. * Changed `master` to `main` in CONTRIBUTING.md to reflect GitHubs new default branch naming. +* Use the sqlite3 API to cancel a running query on interrupt + ([#164](https://github.com/dbcli/litecli/issues/164)). ## 1.9.0 - 2022-06-06 diff --git a/litecli/main.py b/litecli/main.py index e532d9d..e608da7 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -490,29 +490,17 @@ def one_iteration(text=None): 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: diff --git a/litecli/sqlexecute.py b/litecli/sqlexecute.py index 5d00277..2392472 100644 --- a/litecli/sqlexecute.py +++ b/litecli/sqlexecute.py @@ -1,6 +1,5 @@ import logging import sqlite3 -import uuid from contextlib import closing from sqlite3 import OperationalError from litecli.packages.special.utils import check_if_sqlitedotcommand @@ -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 @@ def connect(self, database=None): # 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 @@ -207,17 +203,3 @@ def show_candidates(self): 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) From e18bdb2b2222881d5367aace7f51513d30c05c53 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Tue, 3 Oct 2023 16:19:29 +0100 Subject: [PATCH 093/302] Open once_file once per use It was being re-opened for every single line of output, which prevented `.once -o ...` from working, plus this is a lot more efficient for long result sets. --- CHANGELOG.md | 2 ++ litecli/packages/special/iocommands.py | 37 +++++++++++++------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e2609..535f7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ failing queries get `successful = False` in `query_history`. * Changed `master` to `main` in CONTRIBUTING.md to reflect GitHubs new default branch naming. +* Fixed `.once -o ` by opening the output file once per statement instead + of for every line of output ([#148](https://github.com/dbcli/litecli/issues/148)). ## 1.9.0 - 2022-06-06 diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 1f77885..ee254f0 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -21,7 +21,7 @@ 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()) @@ -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, written_to_once_file - if written_to_once_file: - once_file = written_to_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( From 76a6017e8597dfe31a935e2dd79a275c80b5335a Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 10 Nov 2023 13:07:29 +0000 Subject: [PATCH 094/302] Skip internal indexes in .schema output See the [schema table `sql` column documentation][schema]: > The `sqlite_schema.sql` is NULL for the internal indexes that are automatically > created by UNIQUE or PRIMARY KEY constraints. [schema]: https://www.sqlite.org/schematab.html#interpretation_of_the_schema_table Fixes #170 --- CHANGELOG.md | 2 ++ litecli/packages/special/dbcommands.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d0ba2..4b62259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ of for every line of output ([#148](https://github.com/dbcli/litecli/issues/148)). * Use the sqlite3 API to cancel a running query on interrupt ([#164](https://github.com/dbcli/litecli/issues/164)). +* Skip internal indexes in the .schema output + ([#170](https://github.com/dbcli/litecli/issues/170)). ## 1.9.0 - 2022-06-06 diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index 3bba548..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 """ From 9c0d58c45d4563600fea9b9c035dfda5887c875f Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 19 Nov 2023 13:30:17 -0800 Subject: [PATCH 095/302] Prep for release. --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 67ba03d..17fd047 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.11.0 hooks: - id: black diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b62259..808b8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## TBD +## 1.10.0 - 2022-11-19 ### Features @@ -8,9 +8,9 @@ * Fix [[#146](https://github.com/dbcli/litecli/issues/146)], making sure `.once` can be used more than once in a session. -* Fixed setting `successful = True` only when query is executed without exceptions so +* Fixed setting `successful = True` only when query is executed without exceptions so failing queries get `successful = False` in `query_history`. -* Changed `master` to `main` in CONTRIBUTING.md to reflect GitHubs new default branch +* Changed `master` to `main` in CONTRIBUTING.md to reflect GitHubs new default branch naming. * Fixed `.once -o ` by opening the output file once per statement instead of for every line of output ([#148](https://github.com/dbcli/litecli/issues/148)). @@ -46,7 +46,7 @@ ### Bug Fixes * Upgrade cli_helpers to workaround Pygments regression. -* Use get_terminal_size from shutil instead of click. +* Use get_terminal_size from shutil instead of click. ## 1.7.0 - 2022-01-11 From b183f86e45dc8b69797e09227769d99245ab1d0a Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 19 Nov 2023 13:30:55 -0800 Subject: [PATCH 096/302] Releasing version 1.10.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 8296335dbb951bae27f21fda96d8d498546e6702 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 18 Feb 2024 20:23:13 -0800 Subject: [PATCH 097/302] Do not crash if ~/.config/litecli is not writeable. --- litecli/config.py | 6 +++++- litecli/main.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/litecli/config.py b/litecli/config.py index 1c7fb25..55d3e32 100644 --- a/litecli/config.py +++ b/litecli/config.py @@ -57,6 +57,10 @@ def get_config(liteclirc_file=None): liteclirc_file = liteclirc_file or "%sconfig" % config_location() default_config = os.path.join(package_root, "liteclirc") - write_default_config(default_config, liteclirc_file) + try: + write_default_config(default_config, liteclirc_file) + except OSError: + # If we can't write to the config file, just use the default config + return load_config(default_config) return load_config(liteclirc_file, default_config) diff --git a/litecli/main.py b/litecli/main.py index e608da7..268ede2 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -239,7 +239,11 @@ def initialize_logging(self): log_file = self.config["main"]["log_file"] if log_file == "default": log_file = config_location() + "log" - ensure_dir_exists(log_file) + try: + ensure_dir_exists(log_file) + except OSError: + # Unable to create log file, log to temp directory instead. + log_file = "/tmp/litecli.log" log_level = self.config["main"]["log_level"] From e2a58c05a917d72ba70e647968b0096e6612e12d Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 18 Feb 2024 20:25:50 -0800 Subject: [PATCH 098/302] Update changelog. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808b8df..692d113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## Next Release - [TBD] + +### Bug Fixes + +* Do not crash at start up if ~/.config/litecli is not writeable. [#172](https://github.com/dbcli/litecli/issues/172) + + ## 1.10.0 - 2022-11-19 ### Features From 227de507d2da6daf641a8383efae410daa1d5dc0 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 07:53:45 -0700 Subject: [PATCH 099/302] Prep for release. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 692d113..edee1e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Next Release - [TBD] +## 1.10.1 - 2024-3-23 ### Bug Fixes From 9d5bcf0ba17fd0562309164175d7db1a54f85464 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 07:54:46 -0700 Subject: [PATCH 100/302] Releasing version 1.10.1 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index fcfdf38..a0865bb 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.10.0" +__version__ = "1.10.1" From 0725b88358cee9ac6ab219bab498edb6bcf70bd3 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 19:22:06 -0700 Subject: [PATCH 101/302] Improve \d to show list of tables. --- CHANGELOG.md | 8 ++++++++ TODO | 3 --- TODO.md | 6 ++++++ litecli/packages/special/dbcommands.py | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) delete mode 100644 TODO create mode 100644 TODO.md diff --git a/CHANGELOG.md b/CHANGELOG.md index edee1e9..00f3b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.11.0 - TBD + +### Improvements + +* When an empty `\d` is invoked the list of tables are returned instead of an error. + + + ## 1.10.1 - 2024-3-23 ### Bug Fixes diff --git a/TODO b/TODO deleted file mode 100644 index 7c854dc..0000000 --- a/TODO +++ /dev/null @@ -1,3 +0,0 @@ -* [] Sort by frecency. -* [] Add completions when an attach database command is run. -* [] Add behave tests. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..58e2ebc --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +* [ ] Change to use ruff +* [ ] Automate the release process via GH actions. [Article](https://simonwillison.net/2024/Jan/16/python-lib-pypi/) + +* [] Sort by frecency. +* [] Add completions when an attach database command is run. +* [] Add behave tests. diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py index dec3507..687c9a4 100644 --- a/litecli/packages/special/dbcommands.py +++ b/litecli/packages/special/dbcommands.py @@ -224,7 +224,7 @@ def describe(cur, arg, **_): arg ) else: - raise ArgumentMissing("Table name required.") + return list_tables(cur) log.debug(query) cur.execute(query) From 11a68b4e72f73cd498b2dafe05932a6be0868985 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 19:25:17 -0700 Subject: [PATCH 102/302] Drop Python 3.7 and add 3.11 and 3.12 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ee36cf..d4de9d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 From 6c26b4243496eefa869d46b96ac480a4da857c8a Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 21:09:14 -0700 Subject: [PATCH 103/302] Print the sqlite version at startup. --- litecli/main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index 268ede2..d82f7cd 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -10,7 +10,7 @@ from datetime import datetime from io import open from collections import namedtuple -from sqlite3 import OperationalError +from sqlite3 import OperationalError, sqlite_version import shutil from cli_helpers.tabular_output import TabularOutputFormatter @@ -380,10 +380,8 @@ def run_cli(self): key_bindings = cli_bindings(self) if not self.less_chatty: - print("Version:", __version__) - print("Mail: https://groups.google.com/forum/#!forum/litecli-users") + print(f"LiteCli: {__version__} (SQLite: {sqlite_version})") print("GitHub: https://github.com/dbcli/litecli") - # print("Home: https://litecli.com") def get_message(): prompt = self.get_prompt(self.prompt_format) @@ -819,7 +817,7 @@ def get_col_type(col): headers, format_name="vertical" if expanded else None, column_types=column_types, - **output_kwargs + **output_kwargs, ) if isinstance(formatted, (text_type)): @@ -841,7 +839,7 @@ def get_col_type(col): headers, format_name="vertical", column_types=column_types, - **output_kwargs + **output_kwargs, ) if isinstance(formatted, (text_type)): formatted = iter(formatted.splitlines()) From 4c545ae54fe813bd69727d88c1c48dd452ca8301 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 23 Mar 2024 21:28:14 -0700 Subject: [PATCH 104/302] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f3b65..14222a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Improvements * When an empty `\d` is invoked the list of tables are returned instead of an error. +* Show SQLite version at startup. From c03632adb5e3563867fe2976ad628c3ebb7fe3bf Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 3 May 2024 08:58:43 -0700 Subject: [PATCH 105/302] Check if startup commands are a list. --- litecli/liteclirc | 4 ++-- litecli/main.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/litecli/liteclirc b/litecli/liteclirc index 924b585..1184278 100644 --- a/litecli/liteclirc +++ b/litecli/liteclirc @@ -122,7 +122,7 @@ output.even-row = "" # Startup commands # litecli commands or sqlite commands to be executed on startup. -# some of them will require you to have a database attached. +# 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 +#commands = ".tables", "pragma foreign_keys = ON;" diff --git a/litecli/main.py b/litecli/main.py index 268ede2..35638bd 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -588,7 +588,11 @@ def one_iteration(text=None): def startup_commands(): if self.startup_commands: if "commands" in self.startup_commands: - for command in self.startup_commands["commands"]: + if isinstance(self.startup_commands["commands"], str): + commands = [self.startup_commands["commands"]] + else: + commands = self.startup_commands["commands"] + for command in commands: try: res = sqlexecute.run(command) except Exception as e: From fab06ca7a9ce90e725e550eb69200789408eebb8 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 3 May 2024 09:02:30 -0700 Subject: [PATCH 106/302] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edee1e9..21b4121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## Upcoming - TBD + +### Features + + +### Bug Fixes + +* Support a single item in the startup commands in the config. (bug #176) + + ## 1.10.1 - 2024-3-23 ### Bug Fixes From a65574ab878a6870feeb7c40ada901c14096db3e Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 3 May 2024 10:14:17 -0700 Subject: [PATCH 107/302] Update changelog to release 1.11.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fce91e3..9530cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming - TBD +## 1.11.0 - 2024-05-03 ### Improvements From 557ac472187b647f1363a37e46639edf76fdb3fc Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 3 May 2024 10:14:30 -0700 Subject: [PATCH 108/302] Releasing version 1.11.0 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index a0865bb..f84c53b 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.10.1" +__version__ = "1.11.0" From 13de27ded33268624e39893382475aaee2f6815b Mon Sep 17 00:00:00 2001 From: James Morris Date: Fri, 14 Jun 2024 14:18:16 -0400 Subject: [PATCH 109/302] Fix warnings about invalid escape sequences --- litecli/main.py | 2 +- litecli/packages/parseutils.py | 8 ++++---- litecli/packages/special/iocommands.py | 4 ++-- litecli/sqlcompleter.py | 2 +- tests/test_sqlexecute.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/litecli/main.py b/litecli/main.py index ebbc5ba..6dc9dff 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -329,7 +329,7 @@ def _connect(): exit(1) def handle_editor_command(self, text): - """Editor command is any query that is prefixed or suffixed by a '\e'. + R"""Editor command is any query that is prefixed or suffixed by a '\e'. The reason for a while loop is because a user might edit a query multiple times. For eg: diff --git a/litecli/packages/parseutils.py b/litecli/packages/parseutils.py index f5fdc1d..d5fd197 100644 --- a/litecli/packages/parseutils.py +++ b/litecli/packages/parseutils.py @@ -12,12 +12,12 @@ # This matches everything except spaces, parens, colon, comma, and period "most_punctuations": re.compile(r"([^\.():,\s]+)$"), # This matches everything except a space. - "all_punctuations": re.compile("([^\s]+)$"), + "all_punctuations": re.compile(r"([^\s]+)$"), } def last_word(text, include="alphanum_underscore"): - """ + R""" Find the last word in a sentence. >>> last_word('abc') @@ -41,9 +41,9 @@ def last_word(text, include="alphanum_underscore"): >>> last_word('bac $def', include='most_punctuations') '$def' >>> last_word('bac \def', include='most_punctuations') - '\\\\def' + '\\def' >>> last_word('bac \def;', include='most_punctuations') - '\\\\def;' + '\\def;' >>> last_word('bac::def', include='most_punctuations') 'def' """ diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index ee254f0..c1596eb 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -121,7 +121,7 @@ def get_editor_query(sql): # The reason we can't simply do .strip('\e') is that it strips characters, # not a substring. So it'll strip "e" in the end of the sql also! # Ex: "select * from style\e" -> "select * from styl". - pattern = re.compile("(^\\\e|\\\e$)") + pattern = re.compile(r"(^\\e|\\e$)") while pattern.search(sql): sql = pattern.sub("", sql) @@ -245,7 +245,7 @@ def subst_favorite_query_args(query, args): + query, ] - match = re.search("\\?|\\$\d+", query) + match = re.search(r"\?|\$\d+", query) if match: return [ None, diff --git a/litecli/sqlcompleter.py b/litecli/sqlcompleter.py index 82b8d87..68252ea 100644 --- a/litecli/sqlcompleter.py +++ b/litecli/sqlcompleter.py @@ -257,7 +257,7 @@ def __init__(self, supported_formats=(), keyword_casing="auto"): self.reserved_words = set() for x in self.keywords: self.reserved_words.update(x.split()) - self.name_pattern = compile("^[_a-z][_a-z0-9\$]*$") + self.name_pattern = compile(r"^[_a-z][_a-z0-9\$]*$") self.special_commands = [] self.table_formats = supported_formats diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py index 16bad74..cd56683 100644 --- a/tests/test_sqlexecute.py +++ b/tests/test_sqlexecute.py @@ -309,7 +309,7 @@ def test_favorite_query_expanded_output(executor): results = run(executor, "\\fs test-ae select * from test") assert_result_equal(results, status="Saved.") - results = run(executor, "\\f+ test-ae \G") + results = run(executor, R"\f+ test-ae \G") assert is_expanded_output() is True assert_result_equal( results, From 0977ff8036cd95e87d42d7ef7aa61a0dee3584ec Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 4 Jul 2024 14:20:47 -0700 Subject: [PATCH 110/302] Update changelog to release 1.11.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9530cd7..ee3d972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.11.1 - 2024-07-04 + +### Bug Fixes + +* Fix the escape sequence warning. + + ## 1.11.0 - 2024-05-03 ### Improvements From 54f96506b12e5c71c5d09acb37e3bc4b63aa03e3 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 4 Jul 2024 14:21:01 -0700 Subject: [PATCH 111/302] Releasing version 1.11.1 --- litecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litecli/__init__.py b/litecli/__init__.py index f84c53b..c3fa782 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1 @@ -__version__ = "1.11.0" +__version__ = "1.11.1" From fdfd552f520640984d8d0bf44783b0600e8341de Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sat, 7 Sep 2024 11:29:06 -0700 Subject: [PATCH 112/302] Move to using pyproject.toml --- .pre-commit-config.yaml | 13 +- pyproject.toml | 39 ++++++ requirements-dev.txt | 10 -- setup.cfg | 18 --- setup.py | 70 ---------- tasks.py | 129 ------------------ tests/test_completion_refresher.py | 2 +- ...est_smart_completion_public_schema_only.py | 124 ++++------------- tests/utils.py | 20 +-- tox.ini | 23 ++-- 10 files changed, 95 insertions(+), 353 deletions(-) create mode 100644 pyproject.toml delete mode 100644 requirements-dev.txt delete mode 100644 setup.cfg delete mode 100755 setup.py delete mode 100644 tasks.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17fd047..159c2b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,10 @@ repos: -- repo: https://github.com/psf/black - rev: 23.11.0 - hooks: - - id: black +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.4 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a6805c0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[project] +name = "litecli" +version = "1.11.1" +description = "CLI for SQLite Databases with auto-completion and syntax highlighting." +readme = "README.md" +requires-python = ">=3.7" +license = {text = "BSD"} +authors = [ + {name = "dbcli", email = "litecli-users@googlegroups.com"} +] +urls = { "homepage" = "https://github.com/dbcli/litecli" } +dependencies = [ + "cli-helpers[styles]>=2.2.1", + "click>=4.1", + "configobj>=5.0.5", + "prompt-toolkit>=3.0.3,<4.0.0", + "pygments>=1.6", + "sqlparse>=0.4.4", +] + + +[project.scripts] +litecli = "litecli.main:cli" + +[project.optional-dependencies] +dev = [ + "behave>=1.2.6", + "coverage>=7.2.7", + "pexpect>=4.9.0", + "pytest>=7.4.4", + "pytest-cov>=4.1.0", + "tox>=4.8.0", +] + +[tool.setuptools.package-data] +litecli = ["liteclirc", "AUTHORS"] + +[tool.ruff] +line-length = 140 diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index c517d59..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,10 +0,0 @@ -mock -pytest>=3.6 -pytest-cov -tox -behave -pexpect -coverage -codecov -click -black \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 40eab0a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[bdist_wheel] -universal = 1 - -[tool:pytest] -addopts = --capture=sys - --showlocals - --doctest-modules - --doctest-ignore-import-errors - --ignore=setup.py - --ignore=litecli/magic.py - --ignore=litecli/packages/parseutils.py - --ignore=test/features - -[pep8] -rev = master -docformatter = True -diff = True -error-status = True diff --git a/setup.py b/setup.py deleted file mode 100755 index 0ff4eeb..0000000 --- a/setup.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import ast -from io import open -import re -from setuptools import setup, find_packages - -_version_re = re.compile(r"__version__\s+=\s+(.*)") - -with open("litecli/__init__.py", "rb") as f: - version = str( - ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)) - ) - - -def open_file(filename): - """Open and read the file *filename*.""" - with open(filename) as f: - return f.read() - - -readme = open_file("README.md") - -install_requirements = [ - "click >= 4.1", - "Pygments>=1.6", - "prompt_toolkit>=3.0.3,<4.0.0", - "sqlparse", - "configobj >= 5.0.5", - "cli_helpers[styles] >= 2.2.1", -] - - -setup( - name="litecli", - author="dbcli", - author_email="litecli-users@googlegroups.com", - license="BSD", - version=version, - url="https://github.com/dbcli/litecli", - packages=find_packages(), - package_data={"litecli": ["liteclirc", "AUTHORS"]}, - description="CLI for SQLite Databases with auto-completion and syntax " - "highlighting.", - long_description=readme, - long_description_content_type="text/markdown", - install_requires=install_requirements, - # cmdclass={"test": test, "lint": lint}, - entry_points={ - "console_scripts": ["litecli = litecli.main:cli"], - "distutils.commands": ["lint = tasks:lint", "test = tasks:test"], - }, - classifiers=[ - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: Unix", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: SQL", - "Topic :: Database", - "Topic :: Database :: Front-Ends", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) diff --git a/tasks.py b/tasks.py deleted file mode 100644 index fb632ae..0000000 --- a/tasks.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -"""Common development tasks for setup.py to use.""" - -import re -import subprocess -import sys - -from setuptools import Command -from setuptools.command.test import test as TestCommand - - -class BaseCommand(Command, object): - """The base command for project tasks.""" - - user_options = [] - - default_cmd_options = ("verbose", "quiet", "dry_run") - - def __init__(self, *args, **kwargs): - super(BaseCommand, self).__init__(*args, **kwargs) - self.verbose = False - - def initialize_options(self): - """Override the distutils abstract method.""" - pass - - def finalize_options(self): - """Override the distutils abstract method.""" - # Distutils uses incrementing integers for verbosity. - self.verbose = bool(self.verbose) - - def call_and_exit(self, cmd, shell=True): - """Run the *cmd* and exit with the proper exit code.""" - sys.exit(subprocess.call(cmd, shell=shell)) - - def call_in_sequence(self, cmds, shell=True): - """Run multiple commands in a row, exiting if one fails.""" - for cmd in cmds: - if subprocess.call(cmd, shell=shell) == 1: - sys.exit(1) - - def apply_options(self, cmd, options=()): - """Apply command-line options.""" - for option in self.default_cmd_options + options: - cmd = self.apply_option(cmd, option, active=getattr(self, option, False)) - return cmd - - def apply_option(self, cmd, option, active=True): - """Apply a command-line option.""" - return re.sub( - r"{{{}\:(?P