diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-27 21:24:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-27 21:24:53 +0000 |
commit | 9042b07cb1884b4a9ab2958ffa425d15069127fb (patch) | |
tree | 4c5d5aa16b47bbe0ec150d47a8be8dca40d52c72 | |
parent | Releasing debian version 1.12.4-1. (diff) | |
download | litecli-9042b07cb1884b4a9ab2958ffa425d15069127fb.tar.xz litecli-9042b07cb1884b4a9ab2958ffa425d15069127fb.zip |
Merging upstream version 1.13.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r-- | CHANGELOG.md | 12 | ||||
-rw-r--r-- | litecli/__init__.py | 4 | ||||
-rw-r--r-- | litecli/main.py | 10 | ||||
-rw-r--r-- | litecli/packages/special/iocommands.py | 74 | ||||
-rw-r--r-- | pyproject.toml | 17 | ||||
-rw-r--r-- | tests/test_special_iocommands.py | 59 |
6 files changed, 146 insertions, 30 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f8192..f1e8c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.13.2 - 2024-11-24 + +### Internal + +* Read the version from the git tag using setuptools-scm + +## 1.13.0 - 2024-11-23 + +### Features + +* Add `\pipe_once` / `\|` commands for sending output to a command + ## 1.12.4 - 2024-11-11 ### Bug Fixes diff --git a/litecli/__init__.py b/litecli/__init__.py index 19ee973..b3ce20e 100644 --- a/litecli/__init__.py +++ b/litecli/__init__.py @@ -1 +1,3 @@ -__version__ = "1.12.4" +import importlib.metadata + +__version__ = importlib.metadata.version("litecli") diff --git a/litecli/main.py b/litecli/main.py index d1d3713..a0607ab 100644 --- a/litecli/main.py +++ b/litecli/main.py @@ -472,6 +472,7 @@ class LiteCli(object): result_count += 1 mutating = mutating or is_mutating(status) special.unset_once_if_written() + special.unset_pipe_once_if_written() except EOFError as e: raise e except KeyboardInterrupt: @@ -658,6 +659,7 @@ class LiteCli(object): self.log_output(line) special.write_tee(line) special.write_once(line) + special.write_pipe_once(line) if fits or output_via_pager: # buffering @@ -824,7 +826,7 @@ class LiteCli(object): @click.command() -@click.option("-V", "--version", is_flag=True, help="Output litecli's version.") +@click.version_option(__version__, "-V", "--version") @click.option("-D", "--database", "dbname", help="Database to use.") @click.option( "-R", @@ -857,7 +859,6 @@ class LiteCli(object): def cli( database, dbname, - version, prompt, logfile, auto_vertical_output, @@ -874,11 +875,6 @@ def cli( - litecli lite_database """ - - if version: - print("Version:", __version__) - sys.exit(0) - litecli = LiteCli( prompt=prompt, logfile=logfile, diff --git a/litecli/packages/special/iocommands.py b/litecli/packages/special/iocommands.py index 78f8707..eeba814 100644 --- a/litecli/packages/special/iocommands.py +++ b/litecli/packages/special/iocommands.py @@ -21,7 +21,10 @@ from litecli.packages.prompt_utils import confirm_destructive_query use_expanded_output = False PAGER_ENABLED = True tee_file = None -once_file = once_file_args = written_to_once_file = None +once_file = None +written_to_once_file = None +pipe_once_process = None +written_to_pipe_once_process = False favoritequeries = FavoriteQueries(ConfigObj()) @@ -376,9 +379,12 @@ def write_tee(output): aliases=("\\o", "\\once"), ) def set_once(arg, **_): - global once_file_args - - once_file_args = parseargfile(arg) + global once_file, written_to_once_file + try: + once_file = open(**parseargfile(arg)) + except (IOError, OSError) as e: + raise OSError("Cannot write to file '{}': {}".format(e.filename, e.strerror)) + written_to_once_file = False return [(None, None, None, "")] @@ -386,26 +392,66 @@ def set_once(arg, **_): @export def write_once(output): global once_file, written_to_once_file - 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)) - + if output and once_file: click.echo(output, file=once_file, nl=False) click.echo("\n", file=once_file, nl=False) + once_file.flush() written_to_once_file = True @export def unset_once_if_written(): """Unset the once file, if it has been written to.""" - global once_file, once_file_args, written_to_once_file + global once_file, 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 + once_file = written_to_once_file = None + + +@special_command("\\pipe_once", "\\| command", "Send next result to a subprocess.", aliases=("\\|",)) +def set_pipe_once(arg, **_): + global pipe_once_process, written_to_pipe_once_process + pipe_once_cmd = shlex.split(arg) + if len(pipe_once_cmd) == 0: + raise OSError("pipe_once requires a command") + written_to_pipe_once_process = False + pipe_once_process = subprocess.Popen( + pipe_once_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=1, + encoding="UTF-8", + universal_newlines=True, + ) + return [(None, None, None, "")] + + +@export +def write_pipe_once(output): + global pipe_once_process, written_to_pipe_once_process + if output and pipe_once_process: + try: + click.echo(output, file=pipe_once_process.stdin, nl=False) + click.echo("\n", file=pipe_once_process.stdin, nl=False) + except (IOError, OSError) as e: + pipe_once_process.terminate() + raise OSError("Failed writing to pipe_once subprocess: {}".format(e.strerror)) + written_to_pipe_once_process = True + + +@export +def unset_pipe_once_if_written(): + """Unset the pipe_once cmd, if it has been written to.""" + global pipe_once_process, written_to_pipe_once_process + if written_to_pipe_once_process: + (stdout_data, stderr_data) = pipe_once_process.communicate() + if len(stdout_data) > 0: + print(stdout_data.rstrip("\n")) + if len(stderr_data) > 0: + print(stderr_data.rstrip("\n")) + pipe_once_process = None + written_to_pipe_once_process = False @special_command( diff --git a/pyproject.toml b/pyproject.toml index 55dc144..5caeb84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,8 @@ dynamic = ["version"] 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"} -] +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", @@ -19,9 +17,15 @@ dependencies = [ ] [build-system] -requires = ["setuptools >= 61.0"] +requires = [ + "setuptools>=64.0", + "setuptools-scm>=8;python_version>='3.8'", + "setuptools-scm<8;python_version<'3.8'", +] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] + [project.scripts] litecli = "litecli.main:cli" @@ -42,8 +46,5 @@ exclude = ["screenshots", "tests*"] [tool.setuptools.package-data] litecli = ["liteclirc", "AUTHORS"] -[tool.setuptools.dynamic] -version = {attr = "litecli.__version__"} - [tool.ruff] line-length = 140 diff --git a/tests/test_special_iocommands.py b/tests/test_special_iocommands.py new file mode 100644 index 0000000..ec60163 --- /dev/null +++ b/tests/test_special_iocommands.py @@ -0,0 +1,59 @@ +import os +import tempfile + +import pytest + +import litecli.packages.special + + +def test_once_command(): + with pytest.raises(TypeError): + litecli.packages.special.execute(None, ".once") + + with pytest.raises(OSError): + litecli.packages.special.execute(None, ".once /proc/access-denied") + + litecli.packages.special.write_once("hello world") # write without file set + # keep Windows from locking the file with delete=False + with tempfile.NamedTemporaryFile(delete=False) as f: + litecli.packages.special.execute(None, ".once " + f.name) + litecli.packages.special.write_once("hello world") + if os.name == "nt": + assert f.read() == b"hello world\r\n" + else: + assert f.read() == b"hello world\n" + + litecli.packages.special.execute(None, ".once -o " + f.name) + litecli.packages.special.write_once("hello world line 1") + litecli.packages.special.write_once("hello world line 2") + f.seek(0) + if os.name == "nt": + assert f.read() == b"hello world line 1\r\nhello world line 2\r\n" + else: + assert f.read() == b"hello world line 1\nhello world line 2\n" + # delete=False means we should try to clean up + try: + if os.path.exists(f.name): + os.remove(f.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") + + +def test_pipe_once_command(): + with pytest.raises(IOError): + litecli.packages.special.execute(None, "\\pipe_once") + + with pytest.raises(OSError): + litecli.packages.special.execute(None, "\\pipe_once /proc/access-denied") + + if os.name == "nt": + litecli.packages.special.execute(None, '\\pipe_once python -c "import sys; print(len(sys.stdin.read().strip()))"') + litecli.packages.special.write_pipe_once("hello world") + litecli.packages.special.unset_pipe_once_if_written() + else: + with tempfile.NamedTemporaryFile() as f: + litecli.packages.special.execute(None, "\\pipe_once tee " + f.name) + litecli.packages.special.write_pipe_once("hello world") + litecli.packages.special.unset_pipe_once_if_written() + f.seek(0) + assert f.read() == b"hello world\n" |