summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-27 21:24:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-27 21:24:53 +0000
commit9042b07cb1884b4a9ab2958ffa425d15069127fb (patch)
tree4c5d5aa16b47bbe0ec150d47a8be8dca40d52c72
parentReleasing debian version 1.12.4-1. (diff)
downloadlitecli-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.md12
-rw-r--r--litecli/__init__.py4
-rw-r--r--litecli/main.py10
-rw-r--r--litecli/packages/special/iocommands.py74
-rw-r--r--pyproject.toml17
-rw-r--r--tests/test_special_iocommands.py59
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"