Source code for idmtools.entities.command_line
"""
Defines the CommandLine class that represents our remote command line to be executed.
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
"""
import re
import shlex
from typing import TypeVar, Dict, Any, List
from dataclasses import dataclass, field
[docs]@dataclass(init=False)
class CommandLine:
"""
A class to construct command-line strings from executable, options, and params.
"""
#: The executable portion of the command
_executable: str = None
#: Options for the command
_options: Dict[str, Any] = field(default_factory=dict)
#: Arguments for the command
_args: List[Any] = field(default_factory=list)
#: Raw Arguments. These arguments are not quoted, so if you need quotes, you have to do that manually
_raw_args: List[Any] = field(default_factory=list)
#: Is this a command line for a windows system
is_windows: bool = field(default=False)
[docs] def __init__(self, executable=None, *args, is_windows: bool = False, raw_args: List[Any] = None, **kwargs):
"""
Initialize CommandLine.
Args:
executable: Executable
*args: Additional Arguments
is_windows: is the command for windows
raw_args: Any raw arguments
**kwargs: Keyword arguments
"""
# If there is a space in executable, we probably need to split it
self._executable = executable
self._options = kwargs or {}
self._args = list(args) if args else []
self.is_windows = is_windows
self._raw_args = list(raw_args) if raw_args else []
# check if user is providing a full command line
if executable and " " in executable:
other = self.from_string(executable)
self._executable = other._executable
if other._args:
self._args += other._args
if other.options:
self._options += other._options
@property
def executable(self) -> str:
"""
Return executable as string.
Returns:
Executable
"""
return self._executable.replace('/', "\\") if self.is_windows else self._executable
@executable.setter
def executable(self, executable):
"""
Set the executable portion of the command line.
Args:
executable: Executable
Returns:
None
"""
self._executable = executable
[docs] def add_argument(self, arg):
"""
Add argument.
Args:
arg: Argument string
Returns:
None
"""
self._args.append(str(arg))
[docs] def add_raw_argument(self, arg):
"""
Add an argument that won't be quote on format.
Args:
arg:arg
Returns:
None
"""
self._raw_args.append(str(arg))
[docs] def add_option(self, option, value):
"""
Add a command-line option.
Args:
option: Option to add
value: Value of option
Returns:
None
"""
self._options[option] = str(value)
@property
def options(self):
"""
Options as a string.
Returns:
Options string
"""
options = []
for k, v in self._options.items():
# Handles spaces
value = '"%s"' % v if ' ' in str(v) else str(v)
if k[-1] == ':':
options.append(k + value) # if the option ends in ':', don't insert a space
else:
options.extend([k, value]) # otherwise let join (below) add a space
if self.is_windows:
return ' '.join([self.__quote_windows(s) for s in options if s])
else:
return ' '.join([shlex.quote(s) for s in options if s])
@staticmethod
def __quote_windows(s):
"""
Quote a parameter for windows command line.
Args:
s: String to quote
Returns:
Quoted string
"""
n = s.replace('"', '\\"')
if re.search(r'(["\s])', s):
return f'"{n}"'
return n
@property
def arguments(self):
"""
The CommandLine arguments.
Returns:
Arguments as string
"""
quote_fn = self.__quote_windows if self.is_windows else self.__quote_linux
qargs = ' '.join([quote_fn(s) for s in self._args if s])
qargs += ' ' + self.raw_arguments
return qargs
def __quote_linux(self, s):
"""
Quote linux.
Args:
s: String to quote
Returns:
Quotes string
"""
return shlex.quote(s)
@property
def raw_arguments(self):
"""
Raw arguments(arguments not to be parsed).
Returns:
Raw arguments as a string
"""
return ' '.join(self._raw_args)
@property
def cmd(self):
"""
Converts command to string.
Returns:
Command as string
"""
return ' '.join(filter(None, [self._executable.strip() if self._executable else None, self.options.strip(), self.arguments.strip()]))
def __str__(self):
"""
String representation of command.
Returns:
String of command
"""
return self.cmd
[docs] @staticmethod
def from_string(command: str, as_raw_args: bool = False) -> 'CommandLine':
"""
Creates a command line object from string.
Args:
command: Command
as_raw_args: When set to true, arguments will preserve the quoting provided
Returns:
CommandLine object from string
"""
parts = shlex.split(command.replace("\\", "/"))
arguments = parts[1:] if len(parts) > 1 else []
cl = CommandLine(parts[0], *arguments if not as_raw_args else [])
if as_raw_args:
# replace executable
remaining = command.replace(parts[0], "", 1)
cl.add_raw_argument(remaining)
return cl
TCommandLine = TypeVar("TCommandLine", bound=CommandLine)