scripts/release/util.py
author Lennard de Rijk <ljvderijk@gmail.com>
Fri, 13 Mar 2009 13:49:07 +0000
changeset 1830 b2b638076e90
parent 1827 c03995a6a88e
child 1833 9df2e9a67081
permissions -rw-r--r--
Started with the view for showing students who have been assigned to multiple slots. The basics that are in this patch will be used by Mario Ferraro to write the actual view. Patch by: Lennard de Rijk Reviewed by: to-be-reviewed

# Copyright 2009 the Melange authors.
#
# 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.

"""Various utilities.

Current contents:
 - Text colorization using ANSI color codes
 - A class to construct and manage paths under a root path.
 - A helper to manage running subprocesses, wrapping the subprocess module.
"""

__authors__ = [
    # alphabetical order by last name, please
    '"David Anderson" <dave@natulte.net>',
    ]

import os.path
import subprocess

import error


class Error(error.Error):
    pass


class SubprocessFailed(Error):
    """A subprocess returned a non-zero error code."""


# The magic escape sequence understood by modern terminal emulators to
# configure fore/background colors and other basic text display
# settings.
_ANSI_ESCAPE = '\x1b[%dm'


# Some internal non-color settings that we use.
_RESET = 0  # Reset to terminal defaults.
_BOLD = 1   # Brighter colors.


# ANSI color codes.
RED = 31
GREEN = 32
WHITE = 37


def _ansi_escape(code):
    return _ANSI_ESCAPE % code


def colorize(text, color, bold=False):
    """Colorize some text using ANSI color codes.

    Note that while ANSI color codes look good in a terminal they look
    like noise in log files unless viewed in an ANSI color capable
    viewer (such as 'less -R').

    Args:
      text: The text to colorize.
      color: One of the color symbols from this module.
      bold: If True, make the color brighter.

    Returns:
      The input text string, appropriately sprinkled with color
      codes. Colors are reset to terminal defaults after the input
      text.
    """
    bold = _ansi_escape(_BOLD) if bold else ''
    return '%s%s%s%s' % (bold, _ansi_escape(color),
                         text, _ansi_escape(_RESET))


class Paths(object):
    """A helper to construct and check paths under a given root."""

    def __init__(self, root):
        """Initializer.

        Args:
          root: The root of all paths this instance will consider.
        """
        self._root = os.path.abspath(
            os.path.expandvars(os.path.expanduser(root)))

    def path(self, path=''):
        """Construct and return a path under the path root.

        Args:
          path: The desired path string relative to the root.

        Returns:
          The absolute path corresponding to the relative input path.
        """
        assert not os.path.isabs(path)
        return os.path.abspath(os.path.join(self._root, path))

    def exists(self, path=''):
        """Check for the existence of a path under the path root.

        Does not discriminate on the path type (ie. it could be a
        directory, a file, a symbolic link...), just checks for the
        existence of the path.

        Args:
          path: The path string relative to the root.

        Returns:
          True if the path exists, False otherwise.
        """
        return os.path.exists(self.path(path))


def run(argv, cwd=None, capture=False, split_capture=True, stdin=''):
    """Run the given command and optionally return its output.

    Note that if you set capture=True, the command's output is
    buffered in memory. Output capture should only be used with
    commands that output small amounts of data. O(kB) is fine, O(MB)
    is starting to push it a little.

    Args:
      argv: A list containing the name of the program to run, followed
            by its argument vector.
      cwd: Run the program from this directory.
      capture: If True, capture the program's stdout stream. If False,
               stdout will output to sys.stdout.
      split_capture: If True, return the captured output as a list of
                     lines. Else, return as a single unaltered string.
      stdin: The string to feed to the program's stdin stream.

    Returns:
      If capture is True, a string containing the combined
      stdout/stderr output of the program. If capture is False,
      nothing is returned.

    Raises:
      SubprocessFailed: The subprocess exited with a non-zero exit
                        code.
    """
    print colorize('# ' + ' '.join(argv), WHITE, bold=True)

    process = subprocess.Popen(argv,
                               shell=False,
                               cwd=cwd,
                               stdin=subprocess.PIPE,
                               stdout=(subprocess.PIPE if capture else None),
                               stderr=None)
    output, _ = process.communicate(input=stdin)
    if process.returncode != 0:
        raise SubprocessFailed('Process %s failed with output: %s' %
                               (argv[0], output))
    if output is not None and split_capture:
        return output.strip().split('\n')
    else:
        return output