#!/usr/bin/python3

"""
Wrap up a bash command or other bash string in n levels of bash quoting.

We make no attempt to deal with here documents, including quoted here documents

We do attempt to handle single quotes, double quotes, dollar signs and backslashes
"""

from __future__ import print_function

import sys

VERBOSE = 0


class BashquoteString(object):
    """The main worker - string (old) version."""

    def reset(self) -> None:
        """Reset back to just a single apostrophe."""
        self.result_so_far = "'"

    def __init__(self) -> None:
        """Initialize."""
        self.reset()
        # these just simplify the quoting of this class, not the bash quoting we're operating on :)
        self.single_quote = "'"
        self.double_quote = '"'
        self.backslash = '\\'
        self.dollar = '$'

    def result(self) -> str:
        """Return the result so far."""
        to_return = self.result_so_far + "'"
        self.reset()
        return to_return

    def __str__(self) -> str:
        """Return result as a string."""
        return self.result()

    def add(self, string: str) -> None:
        """Add string to the quoted string."""
        if not isinstance(string, str):
            raise TypeError('BashquoteString.add requires a str argument, but got {}'.format(type(string)))
        # this outer if is just a performance thing - don't make strings that don't contain
        # quoting pay a (as much of a) performance penalty for quoting
        if self.single_quote in string or self.double_quote in string or self.backslash in string:
            for char in string:
                if VERBOSE:
                    sys.stderr.write('considering %c\n' % char)
                if char == self.single_quote:
                    # this ends the single quoting
                    # leave the single quoting - so we're unquoted
                    # A single quote inside double quotes
                    # reenter single quoting
                    if VERBOSE:
                        sys.stderr.write('Thick single quote\n')
                    self.result_so_far += '%s%s%s%s%s' % \
                        (
                            self.single_quote,
                            self.double_quote, self.single_quote, self.double_quote,
                            self.single_quote,
                        )
                else:
                    self.result_so_far += char
        else:
            self.result_so_far += string


class BashquoteBytes(object):
    """The main worker - bytes() (new) version."""

    def reset(self) -> None:
        """Reset to just an apostrophe."""
        self.result_so_far = b"'"

    def __init__(self) -> None:
        """Initialize."""
        self.reset()
        # these just simplify the quoting of this class, not the bash quoting we're operating on :)
        self.single_quote = b"'"
        self.double_quote = b'"'
        self.backslash = b'\\'
        self.dollar = b'$'

    def result(self) -> bytes:
        """Return our result."""
        result = self.result_so_far + b"'"
        self.reset()
        return result

    # I don't think this was used - it has a type conflict.
    # def __str__(self) -> str:
    #     """Return result as a string."""
    #     return self.result()

    def add(self, byte_string: bytes) -> None:
        """Add (another) part of a command."""
        if not isinstance(byte_string, bytes):
            raise TypeError('BashquoteBytes.add requires a bytes argument, but got {}'.format(type(byte_string)))
        # this outer if is just a performance thing - don't make strings that don't contain
        # quoting pay a (as much of a) performance penalty for quoting
        if self.single_quote in byte_string or self.double_quote in byte_string or self.backslash in byte_string:
            # We do this weird for loop and slice so the same code will work on 2.x and 3.x.
            for byteno in range(len(byte_string)):
                one_byte = byte_string[byteno:byteno + 1]
                if VERBOSE:
                    sys.stderr.write('considering %r\n' % one_byte)
                if one_byte == self.single_quote:
                    # this ends the single quoting
                    # leave the single quoting - so we're unquoted
                    # A single quote inside double quotes
                    # reenter single quoting
                    if VERBOSE:
                        sys.stderr.write('Thick single quote\n')
                    self.result_so_far += b'%s%s%s%s%s' % \
                        (
                            self.single_quote,
                            self.double_quote,
                            self.single_quote,
                            self.double_quote,
                            self.single_quote,
                        )
                else:
                    self.result_so_far += one_byte
        else:
            self.result_so_far += byte_string
