Update shell command tool

Improve typing hints for shell command tool
Add unit test for ShellCommand class

Change-Id: Ibe3d2cfeddd13fb96831c6343c1a7585fc39536e
This commit is contained in:
Federico Ressi 2021-06-04 11:31:39 +02:00
parent ba4ef88e01
commit e6dc88201b
2 changed files with 118 additions and 15 deletions

View File

@ -19,35 +19,37 @@ import shlex
import typing # noqa
ShellCommandType = typing.Union['ShellCommand', str, typing.Iterable[str]]
class ShellCommand(tuple):
def __repr__(self):
return "ShellCommand([{!s}])".format(', '.join(self))
def __repr__(self) -> str:
return f"ShellCommand({str(self)!r})"
def __str__(self):
return list_to_command_line(self)
def __str__(self) -> str:
return join_command(self)
def __add__(self, other):
other = shell_command(other)
return shell_command(tuple(self) + other)
ShellCommandType = typing.Union[ShellCommand, str, typing.Iterable]
def __add__(self, other: ShellCommandType) -> 'ShellCommand':
return shell_command(tuple(self) + shell_command(other))
def shell_command(command: ShellCommandType) -> ShellCommand:
if isinstance(command, ShellCommand):
return command
elif isinstance(command, str):
return ShellCommand(shlex.split(command))
return ShellCommand(split_command(command))
else:
return ShellCommand(str(a) for a in command)
def list_to_command_line(seq):
result = []
for arg in seq:
bs_buf = []
NEED_QUOTE_CHARS = {' ', '\t', '\n', '\r', "'", '"'}
def join_command(sequence: typing.Iterable[str]) -> str:
result: typing.List[str] = []
for arg in sequence:
bs_buf: typing.List[str] = []
# Add a space to separate this argument from the others
if result:
@ -82,3 +84,7 @@ def list_to_command_line(seq):
result.append("'")
return ''.join(result)
def split_command(command: str) -> typing.Sequence[str]:
return shlex.split(command)

View File

@ -0,0 +1,97 @@
# Copyright (c) 2021 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from tobiko.shell import sh
from tobiko.tests import unit
class ShellCommandTest(unit.TobikoUnitTest):
def test_from_str(self):
result = sh.shell_command('ls -lh *.py')
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('ls', '-lh', '*.py'), result)
self.assertEqual("ls -lh *.py", str(result))
def test_from_str_with_quotes(self):
result = sh.shell_command('ls -lh "with quotes"')
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('ls', '-lh', "with quotes"), result)
self.assertEqual("ls -lh 'with quotes'", str(result))
def test_from_sequence(self):
result = sh.shell_command(['ls', '-lh', '*.py'])
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('ls', '-lh', '*.py'), result)
self.assertEqual("ls -lh *.py", str(result))
def test_from_sequence_with_quotes(self):
result = sh.shell_command(['ls', '-lh', "with quotes"])
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('ls', '-lh', "with quotes"), result)
self.assertEqual("ls -lh 'with quotes'", str(result))
def test_from_shell_command(self):
other = sh.shell_command(['ls', '-lh', '*.py'])
result = sh.shell_command(other)
self.assertIs(other, result)
def test_add_str(self):
base = sh.shell_command('ssh pippo@clubhouse.mouse')
result = base + 'ls -lh *.py'
self.assertEqual(('ssh', 'pippo@clubhouse.mouse', 'ls', '-lh', '*.py'),
result)
self.assertEqual("ssh pippo@clubhouse.mouse ls -lh *.py",
str(result))
def test_add_str_with_quotes(self):
base = sh.shell_command('sh -c')
result = base + "'echo Hello!'"
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('sh', '-c', "echo Hello!"), result)
self.assertEqual("sh -c 'echo Hello!'", str(result))
def test_add_sequence(self):
base = sh.shell_command('ssh pippo@clubhouse.mouse')
result = base + ['ls', '-lh', '*.py']
self.assertEqual(('ssh', 'pippo@clubhouse.mouse', 'ls', '-lh', '*.py'),
result)
self.assertEqual("ssh pippo@clubhouse.mouse ls -lh *.py",
str(result))
def test_add_sequence_with_quotes(self):
base = sh.shell_command('sh -c')
result = base + ['echo Hello!']
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('sh', '-c', "echo Hello!"), result)
self.assertEqual("sh -c 'echo Hello!'", str(result))
def test_add_shell_command(self):
base = sh.shell_command('ssh pippo@clubhouse.mouse')
result = base + sh.shell_command(['ls', '-lh', '*.py'])
self.assertEqual(('ssh', 'pippo@clubhouse.mouse', 'ls', '-lh', '*.py'),
result)
self.assertEqual("ssh pippo@clubhouse.mouse ls -lh *.py",
str(result))
def test_add_shell_command_with_quotes(self):
base = sh.shell_command('sh -c')
result = base + sh.shell_command(['echo Hello!'])
self.assertIsInstance(result, sh.ShellCommand)
self.assertEqual(('sh', '-c', "echo Hello!"), result)
self.assertEqual("sh -c 'echo Hello!'", str(result))