Skip to content

Commit 3143963

Browse files
committed
add pipe_once special command
Mostly copied from mycli. Add tests for once as part of getting that going. Updates #192
1 parent da590d8 commit 3143963

4 files changed

Lines changed: 131 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## Upcoming - TBD
2+
3+
### Features
4+
5+
* Add `\pipe_once` / `\|` commands for sending output to a command
6+
7+
### Bug Fixes
8+
9+
### Internal Changes
10+
111
## 1.12.4 - 2024-11-11
212

313
### Bug Fixes

litecli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ def one_iteration(text=None):
472472
result_count += 1
473473
mutating = mutating or is_mutating(status)
474474
special.unset_once_if_written()
475+
special.unset_pipe_once_if_written()
475476
except EOFError as e:
476477
raise e
477478
except KeyboardInterrupt:
@@ -658,6 +659,7 @@ def output(self, output, status=None):
658659
self.log_output(line)
659660
special.write_tee(line)
660661
special.write_once(line)
662+
special.write_pipe_once(line)
661663

662664
if fits or output_via_pager:
663665
# buffering

litecli/packages/special/iocommands.py

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
use_expanded_output = False
2222
PAGER_ENABLED = True
2323
tee_file = None
24-
once_file = once_file_args = written_to_once_file = None
24+
once_file = None
25+
written_to_once_file = None
26+
pipe_once_process = None
27+
written_to_pipe_once_process = False
2528
favoritequeries = FavoriteQueries(ConfigObj())
2629

2730

@@ -376,36 +379,79 @@ def write_tee(output):
376379
aliases=("\\o", "\\once"),
377380
)
378381
def set_once(arg, **_):
379-
global once_file_args
380-
381-
once_file_args = parseargfile(arg)
382+
global once_file, written_to_once_file
383+
try:
384+
once_file = open(**parseargfile(arg))
385+
except (IOError, OSError) as e:
386+
raise OSError("Cannot write to file '{}': {}".format(e.filename, e.strerror))
387+
written_to_once_file = False
382388

383389
return [(None, None, None, "")]
384390

385391

386392
@export
387393
def write_once(output):
388394
global once_file, written_to_once_file
389-
if output and once_file_args:
390-
if once_file is None:
391-
try:
392-
once_file = open(**once_file_args)
393-
except (IOError, OSError) as e:
394-
once_file = None
395-
raise OSError("Cannot write to file '{}': {}".format(e.filename, e.strerror))
396-
395+
if output and once_file:
397396
click.echo(output, file=once_file, nl=False)
398397
click.echo("\n", file=once_file, nl=False)
398+
once_file.flush()
399399
written_to_once_file = True
400400

401401

402402
@export
403403
def unset_once_if_written():
404404
"""Unset the once file, if it has been written to."""
405-
global once_file, once_file_args, written_to_once_file
405+
global once_file, written_to_once_file
406406
if once_file and written_to_once_file:
407407
once_file.close()
408-
once_file = once_file_args = written_to_once_file = None
408+
once_file = written_to_once_file = None
409+
410+
411+
@special_command("\\pipe_once", "\\| command", "Send next result to a subprocess.", aliases=("\\|",))
412+
def set_pipe_once(arg, **_):
413+
global pipe_once_process, written_to_pipe_once_process
414+
pipe_once_cmd = shlex.split(arg)
415+
if len(pipe_once_cmd) == 0:
416+
raise OSError("pipe_once requires a command")
417+
written_to_pipe_once_process = False
418+
pipe_once_process = subprocess.Popen(
419+
pipe_once_cmd,
420+
stdin=subprocess.PIPE,
421+
stdout=subprocess.PIPE,
422+
stderr=subprocess.PIPE,
423+
bufsize=1,
424+
encoding="UTF-8",
425+
universal_newlines=True,
426+
)
427+
return [(None, None, None, "")]
428+
429+
430+
@export
431+
def write_pipe_once(output):
432+
global pipe_once_process, written_to_pipe_once_process
433+
if output and pipe_once_process:
434+
try:
435+
click.echo(output, file=pipe_once_process.stdin, nl=False)
436+
click.echo("\n", file=pipe_once_process.stdin, nl=False)
437+
except (IOError, OSError) as e:
438+
pipe_once_process.terminate()
439+
raise OSError("Failed writing to pipe_once subprocess: {}".format(e.strerror))
440+
written_to_pipe_once_process = True
441+
442+
443+
@export
444+
def unset_pipe_once_if_written():
445+
"""Unset the pipe_once cmd, if it has been written to."""
446+
global pipe_once_process, written_to_pipe_once_process
447+
if written_to_pipe_once_process:
448+
(stdout_data, stderr_data) = pipe_once_process.communicate()
449+
if len(stdout_data) > 0:
450+
print(stdout_data.rstrip("\n"))
451+
if len(stderr_data) > 0:
452+
print(stderr_data.rstrip("\n"))
453+
pipe_once_process = None
454+
written_to_pipe_once_process = False
409455

410456

411457
@special_command(

tests/test_special_iocommands.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import os
2+
import tempfile
3+
4+
import pytest
5+
6+
import litecli.packages.special
7+
8+
9+
def test_once_command():
10+
with pytest.raises(TypeError):
11+
litecli.packages.special.execute(None, ".once")
12+
13+
with pytest.raises(OSError):
14+
litecli.packages.special.execute(None, ".once /proc/access-denied")
15+
16+
litecli.packages.special.write_once("hello world") # write without file set
17+
# keep Windows from locking the file with delete=False
18+
with tempfile.NamedTemporaryFile(delete=False) as f:
19+
litecli.packages.special.execute(None, ".once " + f.name)
20+
litecli.packages.special.write_once("hello world")
21+
if os.name == "nt":
22+
assert f.read() == b"hello world\r\n"
23+
else:
24+
assert f.read() == b"hello world\n"
25+
26+
litecli.packages.special.execute(None, ".once -o " + f.name)
27+
litecli.packages.special.write_once("hello world line 1")
28+
litecli.packages.special.write_once("hello world line 2")
29+
f.seek(0)
30+
if os.name == "nt":
31+
assert f.read() == b"hello world line 1\r\nhello world line 2\r\n"
32+
else:
33+
assert f.read() == b"hello world line 1\nhello world line 2\n"
34+
# delete=False means we should try to clean up
35+
try:
36+
if os.path.exists(f.name):
37+
os.remove(f.name)
38+
except Exception as e:
39+
print(f"An error occurred while attempting to delete the file: {e}")
40+
41+
42+
def test_pipe_once_command():
43+
with pytest.raises(IOError):
44+
litecli.packages.special.execute(None, "\\pipe_once")
45+
46+
with pytest.raises(OSError):
47+
litecli.packages.special.execute(None, "\\pipe_once /proc/access-denied")
48+
49+
if os.name == "nt":
50+
litecli.packages.special.execute(None, '\\pipe_once python -c "import sys; print(len(sys.stdin.read().strip()))"')
51+
litecli.packages.special.write_pipe_once("hello world")
52+
litecli.packages.special.unset_pipe_once_if_written()
53+
else:
54+
with tempfile.NamedTemporaryFile() as f:
55+
litecli.packages.special.execute(None, "\\pipe_once tee " + f.name)
56+
litecli.packages.special.write_pipe_once("hello world")
57+
litecli.packages.special.unset_pipe_once_if_written()
58+
f.seek(0)
59+
assert f.read() == b"hello world\n"

0 commit comments

Comments
 (0)