Skip to content

Commit 1edd0e1

Browse files
committed
utils/cli: Introduce class to build CLI commands
Introduce a class allowing to programmatically generate CLI commands in a clear, reliable and robust way for any calling code manipulating raw command strings.
1 parent ad350c9 commit 1edd0e1

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed

devlib/utils/cli.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright 2019 ARM Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
import collections
17+
import itertools
18+
import shlex
19+
20+
class Command:
21+
"""Provides an abstraction for manipulating CLI commands
22+
"""
23+
# pylint: disable=too-many-instance-attributes
24+
# pylint: disable=too-few-public-methods
25+
def __init__(self, command, flags=None, kwflags=None, kwflags_sep=' ',
26+
kwflags_join=',', options=None, end_of_options=None, args=None,
27+
stdout=None, stderr=None):
28+
"""
29+
NB: if ``None`` in ``flags``, ``kwflags``, ``options``, replace with ``''``
30+
empty flags are ignored
31+
empty kwflag values are kept but striped
32+
NB: caller responsible for escaping args as a single string
33+
""" #TODO
34+
# pylint: disable=too-many-arguments
35+
these = lambda x: (x if isinstance(x, collections.abc.Iterable)
36+
and not isinstance(x, str) else [x])
37+
38+
self.command = shlex.split(command)
39+
self.flags = map(str, filter(None, these(flags)))
40+
self.kwflags_sep = kwflags_sep
41+
self.kwflags_join = kwflags_join
42+
self.kwflags = {} if kwflags is None else {
43+
key: ['' if x is None else str(x) for x in these(values)]
44+
for key, values in kwflags.items()
45+
}
46+
self.options = [] if options is None else [
47+
'' if x is None else str(x) for x in these(options)]
48+
if end_of_options:
49+
self.options.append(str(end_of_options).strip())
50+
if isinstance(args, collections.Mapping):
51+
self.args = Command(**args)
52+
else:
53+
self.args = None if args is None else str(args)
54+
self.stdout = stdout
55+
self.stderr = stderr
56+
57+
def __str__(self):
58+
filepipe = lambda f: (f if isinstance(f, str) and f.startswith('&')
59+
else shlex.quote(f))
60+
quoted = itertools.chain(
61+
self.command,
62+
map(self._flagged, self.flags),
63+
('{}{}{}'.format(self._flagged(k),
64+
self.kwflags_sep,
65+
self.kwflags_join.join(v))
66+
for k, v in self.kwflags.items()),
67+
self.options
68+
)
69+
words = [shlex.quote(word) for word in quoted]
70+
if self.args:
71+
words.append(str(self.args))
72+
if self.stdout:
73+
words.append('1>{}'.format(filepipe(self.stdout)))
74+
if self.stderr:
75+
words.append('2>{}'.format(filepipe(self.stderr)))
76+
return ' '.join(words)
77+
78+
@classmethod
79+
def _flagged(cls, flag):
80+
return '{}{}'.format('--' if len(flag) > 1 else '-', str(flag).strip())

0 commit comments

Comments
 (0)