Compare commits

..

No commits in common. "master" and "ci" have entirely different histories.
master ... ci

5 changed files with 13 additions and 95 deletions

View File

@ -7,7 +7,8 @@ Why?
In Python we now have async subprocesses which allows to execute several In Python we now have async subprocesses which allows to execute several
subprocesses at the same time. The purpose of this library is to: subprocesses at the same time. The purpose of this library is to:
- stream stderr and stdout in real time while capturing it, - provide an acceptable asyncio subprocess wrapper for my syntaxic taste,
- can stream stderr and stdout in real time while capturing it,
- real time output must be prefixed for when you execute several commands at - real time output must be prefixed for when you execute several commands at
the time so that you know which line is for which process, like with the time so that you know which line is for which process, like with
docker-compose logs, docker-compose logs,
@ -15,13 +16,6 @@ subprocesses at the same time. The purpose of this library is to:
This code was copy/pasted between projects and finally extracted on its own. This code was copy/pasted between projects and finally extracted on its own.
Demo
====
.. image:: https://yourlabs.io/oss/shlax/-/raw/master/demo.png
You will find the demo script in demo.py in this repository.
Usage Usage
===== =====
@ -102,9 +96,8 @@ will be applied line by line:
Where is the rest? Where is the rest?
================== ==================
Shlax used to be the name of a much more ambitious poc-project, that you can Shlax used to be the name of a much more ambitious poc-project that has been
still find in the ``OLD`` branch of this repository. It has been extracted in extracted in two projects with clear boundaries, namely `sysplan
two projects with clear boundaries, namely `sysplan
<https://yourlabs.io/oss/sysplan>`_ and `podplan <https://yourlabs.io/oss/sysplan>`_ and `podplan
<https://yourlabs.io/oss/podplan>`_ which are still in alpha state, although <https://yourlabs.io/oss/podplan>`_ which are still in alpha state, but Shlax
Shlax as it now, is feature complete and stable. as it is feature complete and stable.

BIN
demo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 KiB

24
demo.py
View File

@ -1,24 +0,0 @@
import asyncio
from shlax import Subprocess
async def main():
colors = {
'^(.*).txt$': '{green}\\1.txt',
'^(.*).py$': '{bred}\\1.py',
}
await asyncio.gather(
Subprocess(
'for i in $(find .. | head); do echo $i; sleep .2; done',
regexps=colors,
prefix='parent',
).wait(),
Subprocess(
'for i in $(find . | head); do echo $i; sleep .3; done',
regexps=colors,
prefix='cwd',
).wait(),
)
asyncio.run(main())

View File

@ -1,7 +1,6 @@
import asyncio import asyncio
import functools import functools
import re import re
import shlex
import sys import sys
from .colors import colors from .colors import colors
@ -55,6 +54,7 @@ class Subprocess:
if len(args) == 1 and ' ' in args[0]: if len(args) == 1 and ' ' in args[0]:
args = ['sh', '-euc', args[0]] args = ['sh', '-euc', args[0]]
self.cmd = ' '.join(args)
self.args = args self.args = args
self.quiet = quiet if quiet is not None else False self.quiet = quiet if quiet is not None else False
self.prefix = prefix self.prefix = prefix
@ -75,18 +75,6 @@ class Subprocess:
self.regexps[search] = replace self.regexps[search] = replace
async def start(self, wait=True): async def start(self, wait=True):
if not self.quiet:
self.output(
self.colors.bgray.encode()
+ b'+ '
+ shlex.join([
arg.replace('\n', '\\n')
for arg in self.args
]).encode()
+ self.colors.reset.encode(),
highlight=False
)
# Get a reference to the event loop as we plan to use # Get a reference to the event loop as we plan to use
# low-level APIs. # low-level APIs.
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()

View File

@ -37,10 +37,12 @@ async def test_wait_unbound():
async def test_rc_1(): async def test_rc_1():
proc = await Proc( proc = await Proc(
'NON EXISTING COMMAND', 'NON EXISTING COMMAND',
quiet=True, write=Mock(),
).wait() ).wait()
assert proc.rc != 0 assert proc.rc != 0
assert proc.err == 'sh: line 1: NON: command not found' proc.write.assert_called_once_with(
b'sh: line 1: NON: command not found\x1b[0m\n'
)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -68,16 +70,6 @@ async def test_prefix():
).wait() ).wait()
assert write.mock_calls == [ assert write.mock_calls == [
call(
Proc.prefix_colors[0].encode()
+ b'test_prefix '
+ Proc.colors.reset.encode()
+ b'| '
+ Proc.colors.bgray.encode()
+ b'+ sh -euc \'echo hi\''
+ Proc.colors.reset.encode()
+ b'\n'
),
call( call(
Proc.prefix_colors[0].encode() Proc.prefix_colors[0].encode()
+ b'test_prefix ' + b'test_prefix '
@ -86,16 +78,6 @@ async def test_prefix():
+ Proc.colors.reset.encode() + Proc.colors.reset.encode()
+ b'\n' + b'\n'
), ),
call(
Proc.prefix_colors[1].encode()
+ b'test_prefix_1 '
+ Proc.colors.reset.encode()
+ b'| '
+ Proc.colors.bgray.encode()
+ b'+ sh -euc \'echo hi\''
+ Proc.colors.reset.encode()
+ b'\n'
),
call( call(
Proc.prefix_colors[1].encode() Proc.prefix_colors[1].encode()
# padding has been added because of output1 # padding has been added because of output1
@ -105,17 +87,6 @@ async def test_prefix():
+ Proc.colors.reset.encode() + Proc.colors.reset.encode()
+ b'\n' + b'\n'
), ),
call(
Proc.prefix_colors[0].encode()
# padding has been added because of output1
+ b' test_prefix '
+ Proc.colors.reset.encode()
+ b'| '
+ Proc.colors.bgray.encode()
+ b'+ sh -euc \'echo hi\''
+ Proc.colors.reset.encode()
+ b'\n'
),
call( call(
Proc.prefix_colors[0].encode() Proc.prefix_colors[0].encode()
# padding has been added because of output1 # padding has been added because of output1
@ -137,16 +108,6 @@ async def test_prefix_multiline():
prefix='test_prefix', prefix='test_prefix',
).wait() ).wait()
assert proc.write.mock_calls == [ assert proc.write.mock_calls == [
call(
Proc.prefix_colors[0].encode()
+ b'test_prefix '
+ Proc.colors.reset.encode()
+ b'| '
+ Proc.colors.bgray.encode()
+ b'+ sh -euc \'echo -e "a\\nb"\''
+ Proc.colors.reset.encode()
+ b'\n'
),
call( call(
Proc.prefix_colors[0].encode() Proc.prefix_colors[0].encode()
+ b'test_prefix ' + b'test_prefix '
@ -179,7 +140,7 @@ async def test_highlight():
r'h([\w\d-]+)': 'h{cyan}\\1', r'h([\w\d-]+)': 'h{cyan}\\1',
} }
).wait() ).wait()
proc.write.assert_called_with(b'h\x1b[38;5;51mi\x1b[0m\n') proc.write.assert_called_once_with(b'h\x1b[38;5;51mi\x1b[0m\n')
@pytest.mark.asyncio @pytest.mark.asyncio
@ -194,4 +155,4 @@ async def test_highlight_if_not_colored():
r'h([\w\d-]+)': 'h{cyan}\\1', r'h([\w\d-]+)': 'h{cyan}\\1',
} }
).wait() ).wait()
proc.write.assert_called_with(b'h\x1b[31mi\n') proc.write.assert_called_once_with(b'h\x1b[31mi\n')