<gh_stars>10-100
"""Utilities for analyzing and manipulating shell commands."""
import shlex
import logging
import re
import os
try:
        from shlex import quote  # python 3.3 and above
except ImportError:
        from pipes import quote  # python 3.2 and earlier


class ShellCommand:
    """This class represents a parsed shell command (a subset of posix shell
    commands; see parse()). The parsed form allows direct access to information
    like the executable name, working directory or the file to which
    stdin/stdout/stderr is redirected."""
    def __init__(self, executable=None, arguments=None):
        self.stdin = None
        self.stdout = None
        self.stderr = None
        self.executable = executable
        if arguments is None:
            arguments = []
        self.arguments = arguments
        self.workdir = None
        self.envvars = {}

    def toCommandline(self):
        """Transforms ShellCommand object to a posix shell commandline."""
        result = ""

        if self.workdir is not None:
            result += "cd %s && " % quote(self.workdir)

        res_list = [self.executable] + self.arguments
        result += " ".join(map(quote, res_list))

        for key, value in self.envvars.items():
            result += "%s=%s " % (key, quote(value))

        if self.stdin is not None:
            result += " < %s" % quote(self.stdin)
        if self.stdout is not None:
            result += " > %s" % quote(self.stdout)
        if self.stderr is not None:
            result += " 2> %s" % quote(self.stderr)
        return result

    def wrap(self, new_executable, args):
        """Adds a prefix to the exeutable. Example:
        Prefixing `SOMVAR=42 cd mydir && mycmd -v > /dev/null` with `lldb --`
        becomes `SOMEVAR=42 cd mydir && lldb -- mycmd -v > /dev/null`."""
        self.arguments = args + [self.executable] + self.arguments
        self.executable = new_executable


# Tokens/keywords for shell command parsing, loosely modeled after posix
# specification for sh.
reserved_words = ['!', '{', '}', 'case', 'do', 'done', 'elif', 'else', 'esac',
                  'fi', 'for', 'if', 'in', 'then', 'until', 'while']

chaining_tokens = set([';', '&&', '||', '|', '&'])
unhandled_tokens = set([';;', '<<', '>>', '<&', '>&', '<>', '<<-', '>|', '(',
                        ')'])


def parse(commandline):
    """Parses a posix shell commandline to a ShellCommand object. This supports
    typical commandline with environment variables, input/output redirection
    and switching directories upfront. It does not support full posix shell
    and will throw an exception if the commandline uses unsupported features.
    """
    result = ShellCommand()
    tokens = shlex.split(commandline)
    i = 0
    first_word = True
    while i < len(tokens):
        token = tokens[i]
        i += 1
        if token == '<' and i < len(tokens):
            result.stdin = tokens[i]
            i += 1
            continue
        if token == '>' and i < len(tokens):
            result.stdout = tokens[i]
            i += 1
            continue
        if token == '2>' and i < len(tokens):
            result.stderr = tokens[i]
            i += 1
            continue

        if first_word:
            # Is it an environment variable assignment?
            assignment = re.match('([A-Za-z_][A-Za-z_0-9]*)=(.*)', token)
            if assignment:
                result.envvars[assignment.group(1)] = assignment.group(2)
                continue
            # A reserved word?
            if token in reserved_words or token in unhandled_tokens:
                raise Exception("Reserved word '%s' not supported" % token)
            result.executable = token
            first_word = False
        else:
            if token in chaining_tokens:
                # Currently we only allow the special case of 'cd DIR ;'
                if (token == ';' or token == '&&') and \
                        result.executable == 'cd' and \
                        len(result.arguments) == 1:
                    newresult = ShellCommand()
                    newresult.workdir = result.arguments[0]
                    result = newresult
                    first_word = True
                    continue
                raise Exception("Command chaining not supported yet")
            if token in unhandled_tokens or token in reserved_words:
                raise Exception("Commandline with '%s' not supported yet" %
                                token)
            result.arguments.append(token)
    return result


# Some executables are just used to cleanup/prepare for a test run, ignore them
# here. This is from a time when there was no concept of a prepare script,
# it should not be necessary anymore for new test files.
_ignore_executables = set(['cd', 'rm', 'cp'])


def getMainExecutable(context):
    """Returns the main executable of the current run script. This skips over
    some commands typically used to setup a benchmark (see _ignore_executables)
    and returns the first executable found in the run script and prints a
    warning if more than one executable is found."""

    # Executable name already determined in a previous run?
    if hasattr(context, 'executable'):
        return context.executable

    executable = None
    cwd = '.'
    for line in context.parsed_runscript:
        cmd = parse(line)
        if cmd.workdir is not None:
            cwd = os.path.join(cwd, cmd.workdir)
        if cmd.executable in _ignore_executables:
            continue
        new_executable = os.path.join(cwd, cmd.executable)
        # We only support one executable yet for collecting md5sums
        if new_executable != executable and executable is not None:
            logging.warning("More than one executable used in test %s",
                            context.test.getFullName())
        executable = new_executable
    if executable is None:
        logging.warning("No executable found for test %s",
                        context.test.getFullName())
    context.executable = executable
    return executable

"""Utilities for analyzing and manipulating shell commands."""
import shlex
import logging
import re
import os
try:
        from shlex import quote  # python 3.3 and above
except ImportError:
        from pipes import quote  # python 3.2 and earlier


class ShellCommand:
    """This class represents a parsed shell command (a subset of posix shell
    commands; see parse()). The parsed form allows direct access to information
    like the executable name, working directory or the file to which
    stdin/stdout/stderr is redirected."""
    def __init__(self, executable=None, arguments=None):
        self.stdin = None
        self.stdout = None
        self.stderr = None
        self.executable = executable
        if arguments is None:
            arguments = []
        self.arguments = arguments
        self.workdir = None
        self.envvars = {}

    def toCommandline(self):
        """Transforms ShellCommand object to a posix shell commandline."""
        result = ""

        if self.workdir is not None:
            result += "cd %s && " % quote(self.workdir)

        res_list = [self.executable] + self.arguments
        result += " ".join(map(quote, res_list))

        for key, value in self.envvars.items():
            result += "%s=%s " % (key, quote(value))

        if self.stdin is not None:
            result += " < %s" % quote(self.stdin)
        if self.stdout is not None:
            result += " > %s" % quote(self.stdout)
        if self.stderr is not None:
            result += " 2> %s" % quote(self.stderr)
        return result

    def wrap(self, new_executable, args):
        """Adds a prefix to the exeutable. Example:
        Prefixing `SOMVAR=42 cd mydir && mycmd -v > /dev/null` with `lldb --`
        becomes `SOMEVAR=42 cd mydir && lldb -- mycmd -v > /dev/null`."""
        self.arguments = args + [self.executable] + self.arguments
        self.executable = new_executable


# Tokens/keywords for shell command parsing, loosely modeled after posix
# specification for sh.
reserved_words = ['!', '{', '}', 'case', 'do', 'done', 'elif', 'else', 'esac',
                  'fi', 'for', 'if', 'in', 'then', 'until', 'while']

chaining_tokens = set([';', '&&', '||', '|', '&'])
unhandled_tokens = set([';;', '<<', '>>', '<&', '>&', '<>', '<<-', '>|', '(',
                        ')'])


def parse(commandline):
    """Parses a posix shell commandline to a ShellCommand object. This supports
    typical commandline with environment variables, input/output redirection
    and switching directories upfront. It does not support full posix shell
    and will throw an exception if the commandline uses unsupported features.
    """
    result = ShellCommand()
    tokens = shlex.split(commandline)
    i = 0
    first_word = True
    while i < len(tokens):
        token = tokens[i]
        i += 1
        if token == '<' and i < len(tokens):
            result.stdin = tokens[i]
            i += 1
            continue
        if token == '>' and i < len(tokens):
            result.stdout = tokens[i]
            i += 1
            continue
        if token == '2>' and i < len(tokens):
            result.stderr = tokens[i]
            i += 1
            continue

        if first_word:
            # Is it an environment variable assignment?
            assignment = re.match('([A-Za-z_][A-Za-z_0-9]*)=(.*)', token)
            if assignment:
                result.envvars[assignment.group(1)] = assignment.group(2)
                continue
            # A reserved word?
            if token in reserved_words or token in unhandled_tokens:
                raise Exception("Reserved word '%s' not supported" % token)
            result.executable = token
            first_word = False
        else:
            if token in chaining_tokens:
                # Currently we only allow the special case of 'cd DIR ;'
                if (token == ';' or token == '&&') and \
                        result.executable == 'cd' and \
                        len(result.arguments) == 1:
                    newresult = ShellCommand()
                    newresult.workdir = result.arguments[0]
                    result = newresult
                    first_word = True
                    continue
                raise Exception("Command chaining not supported yet")
            if token in unhandled_tokens or token in reserved_words:
                raise Exception("Commandline with '%s' not supported yet" %
                                token)
            result.arguments.append(token)
    return result


# Some executables are just used to cleanup/prepare for a test run, ignore them
# here. This is from a time when there was no concept of a prepare script,
# it should not be necessary anymore for new test files.
_ignore_executables = set(['cd', 'rm', 'cp'])


def getMainExecutable(context):
    """Returns the main executable of the current run script. This skips over
    some commands typically used to setup a benchmark (see _ignore_executables)
    and returns the first executable found in the run script and prints a
    warning if more than one executable is found."""

    # Executable name already determined in a previous run?
    if hasattr(context, 'executable'):
        return context.executable

    executable = None
    cwd = '.'
    for line in context.parsed_runscript:
        cmd = parse(line)
        if cmd.workdir is not None:
            cwd = os.path.join(cwd, cmd.workdir)
        if cmd.executable in _ignore_executables:
            continue
        new_executable = os.path.join(cwd, cmd.executable)
        # We only support one executable yet for collecting md5sums
        if new_executable != executable and executable is not None:
            logging.warning("More than one executable used in test %s",
                            context.test.getFullName())
        executable = new_executable
    if executable is None:
        logging.warning("No executable found for test %s",
                        context.test.getFullName())
    context.executable = executable
    return executable
Utilities for analyzing and manipulating shell commands. # python 3.3 and above # python 3.2 and earlier This class represents a parsed shell command (a subset of posix shell     commands; see parse()). The parsed form allows direct access to information     like the executable name, working directory or the file to which     stdin/stdout/stderr is redirected. Transforms ShellCommand object to a posix shell commandline. Adds a prefix to the exeutable. Example:         Prefixing `SOMVAR=42 cd mydir && mycmd -v > /dev/null` with `lldb --`         becomes `SOMEVAR=42 cd mydir && lldb -- mycmd -v > /dev/null`. # Tokens/keywords for shell command parsing, loosely modeled after posix # specification for sh. Parses a posix shell commandline to a ShellCommand object. This supports     typical commandline with environment variables, input/output redirection     and switching directories upfront. It does not support full posix shell     and will throw an exception if the commandline uses unsupported features. # Is it an environment variable assignment? # A reserved word? # Currently we only allow the special case of 'cd DIR ;' # Some executables are just used to cleanup/prepare for a test run, ignore them # here. This is from a time when there was no concept of a prepare script, # it should not be necessary anymore for new test files. Returns the main executable of the current run script. This skips over     some commands typically used to setup a benchmark (see _ignore_executables)     and returns the first executable found in the run script and prints a     warning if more than one executable is found. # Executable name already determined in a previous run? # We only support one executable yet for collecting md5sums