# Provides methods for manipulating text styling in specific terminals.
# Uses a basic chaining method where text properties are added by calling
# methods with related names.
#
# For example, to print "Hello, world!" in red:
#   print pyfancy().red("Hello, world!").get()
#
# Styles can be changed for different text components. Example:
#   print pyfancy().red("Hello").raw(", ").blue("world!").get()
#
# No output text is necessary when calling a styling method. This allows
# styles to be stacked:
#   print pyfancy().red().bold("Hello, world!").get()
#
# There are two provided ways to access the modified text. The first is
# direct access to the string object called "out". However, accessing this
# object will not reset the style, so any text outputted after will have
# the same style as whatever the text was at the end of the chain.
#
# The get() method is better for accessing text because it resets the text
# style so no new text will have unwanted styling.


class pyfancy:

    def __str__(self):
        return self.get()

    def __init__(self, parseText='', obj=''):
        # Stores output text, for reset use get()
        self.out = str(obj)
        self.parseText = str(parseText)
        if (self.parseText != ''):
            self.parse(self.parseText)

    codes = {  # The different escape codes
        'raw': 0,
        'bold': 1,
        'dim': 2,
        'underlined': 4,
        'blinking': 5,
        'inverted': 7,
        'hidden': 8,
        'black': 30,
        'red': 31,
        'green': 32,
        'yellow': 33,
        'blue': 34,
        'magenta': 35,
        'cyan': 36,
        'light_gray': 37,
        'black_bg': 40,
        'red_bg': 41,
        'green_bg': 42,
        'yellow_bg': 43,
        'blue_bg': 44,
        'purple_bg': 45,
        'cyan_bg': 46,
        'gray_bg': 47,
        'dark_gray': 90,
        'light_red': 91,
        'light_green': 92,
        'light_yellow': 93,
        'light_blue': 94,
        'light_magenta': 95,
        'light_cyan': 96,
        'white': 97,
        'dark_gray_bg': 100,
        'light_red_bg': 101,
        'light_green_bg': 102,
        'light_yellow_bg': 103,
        'light_blue_bg': 104,
        'light_purple_bg': 105,
        'light_cyan_bg': 106,
        'white_bg': 107
    }

    # Stores output text, for reset use get()
    out = ''

    # Returns output text and resets properties
    def get(self):
        return self.out + '\033[0m'

    # Outputs text using print (should work in Python 2 and 3)
    def output(self):
        print(self.get())

    # Adds new text without changing the styling
    def add(self, addition):
        self.out += addition
        return self

    def read(self, file):
        f = open(file, 'r')
        self.parse(f.read())
        f.close()
        return self

    def reset(self):
        self.out = ''
        return self

    # Alternate between all the colours of the rainbow
    # No orange, replaced with lightRed
    # No purple/violet so I ignored it
    def rainbow(self, addition=''):
        x = 0
        for i in range(len(addition)):
            if (addition[i] in [' ', '\t', '\n', '\r']):
                x += 1
            [self.red, self.light_red, self.yellow, self.green,
                self.light_blue, self.blue][(i - x) % 6](addition[i])
        return self

    def strip(self):
        text = ''
        i = 0
        while i < len(self.out):
            if self.out[i] == '\033':
                if i + 1 >= len(self.out):
                    return text + '\033'
                if self.out[i + 1] == '[':
                    i += 1
                    if 'm' in self.out[i:]:
                        while self.out[i] != 'm':
                            i += 1
                        i += 1
                    else:
                        text += '\033'
            text += self.out[i]
            i += 1
        return text

    # Simply apply the attribute with the given name
    def attr(self, name):
        if name in self.codes:
            self.out += '\033[%dm' % self.codes[name]

    # Parses text and automatically assigns attributes
    # Attributes are specified through brackets
    # For example, .parse("{red Hello}") is the same as .red("Hello")
    # Multiple attributes can be specified by commas, eg {red,bold Hello}
    # Brackets can be nested, eg {red Hello, {bold world}!}
    # Brackets can be escaped with backslashes
    def parse(self, text):
        i = 0  # Current index
        props = []  # Property stack; required for nested brackets
        while i < len(text):
            c = text[i]
            if c == '\\':  # Escape character
                i += 1
                if i < len(text):
                    self.out += text[i]
            elif c == '{':  # New property list
                prop = ''  # Current property
                i += 1
                curprops = []  # Properties that are part of this bracket
                while text[i] != ' ':
                    if i + 1 == len(text):
                        return self
                    if text[i] == ',':
                        # Properties separated by commas
                        self.attr(prop)
                        curprops.append(prop)
                        prop = ''
                        i += 1
                    prop += text[i]
                    i += 1
                self.attr(prop)
                curprops.append(prop)
                # Add properties to property stack
                props.append(curprops)
            elif c == '}':
                # Reset styling
                self.raw()
                # Remove last entry from property stack
                if len(props) >= 1:
                    props.pop()
                # Apply all properties from any surrounding brackets
                for plist in props:
                    for p in plist:
                        self.attr(p)
            else:
                self.out += c
            i += 1
        return self

    # Multicolored text
    def multi(self, string):
        i = 31  # ID of escape code; starts at 31 (red) and goes to 36 (cyan)
        for c in string:  # Iterate through string
            self.out += '\033[' + str(i) + 'm' + c
            i += 1  # Why u no have ++i? >:(
            if(i > 36):
                i = 31
        return self

# Adds a formatting function to pyfancy with the specified name
# and formatting code
# This shouldn't be exported


def _add(name, number):
    def inner(self, addition=''):
        self.out += '\033[%dm%s' % (number, addition)
        return self
    setattr(pyfancy, name, inner)


# Generate all default color / format codes
for item in pyfancy.codes.items():
    if len(item) > 1:  # Just in case
        _add(item[0], item[1])

# Provides methods for manipulating text styling in specific terminals.
# Uses a basic chaining method where text properties are added by calling
# methods with related names.
#
# For example, to print "Hello, world!" in red:
#   print pyfancy().red("Hello, world!").get()
#
# Styles can be changed for different text components. Example:
#   print pyfancy().red("Hello").raw(", ").blue("world!").get()
#
# No output text is necessary when calling a styling method. This allows
# styles to be stacked:
#   print pyfancy().red().bold("Hello, world!").get()
#
# There are two provided ways to access the modified text. The first is
# direct access to the string object called "out". However, accessing this
# object will not reset the style, so any text outputted after will have
# the same style as whatever the text was at the end of the chain.
#
# The get() method is better for accessing text because it resets the text
# style so no new text will have unwanted styling.


class pyfancy:

    def __str__(self):
        return self.get()

    def __init__(self, parseText='', obj=''):
        # Stores output text, for reset use get()
        self.out = str(obj)
        self.parseText = str(parseText)
        if (self.parseText != ''):
            self.parse(self.parseText)

    codes = {  # The different escape codes
        'raw': 0,
        'bold': 1,
        'dim': 2,
        'underlined': 4,
        'blinking': 5,
        'inverted': 7,
        'hidden': 8,
        'black': 30,
        'red': 31,
        'green': 32,
        'yellow': 33,
        'blue': 34,
        'magenta': 35,
        'cyan': 36,
        'light_gray': 37,
        'black_bg': 40,
        'red_bg': 41,
        'green_bg': 42,
        'yellow_bg': 43,
        'blue_bg': 44,
        'purple_bg': 45,
        'cyan_bg': 46,
        'gray_bg': 47,
        'dark_gray': 90,
        'light_red': 91,
        'light_green': 92,
        'light_yellow': 93,
        'light_blue': 94,
        'light_magenta': 95,
        'light_cyan': 96,
        'white': 97,
        'dark_gray_bg': 100,
        'light_red_bg': 101,
        'light_green_bg': 102,
        'light_yellow_bg': 103,
        'light_blue_bg': 104,
        'light_purple_bg': 105,
        'light_cyan_bg': 106,
        'white_bg': 107
    }

    # Stores output text, for reset use get()
    out = ''

    # Returns output text and resets properties
    def get(self):
        return self.out + '\033[0m'

    # Outputs text using print (should work in Python 2 and 3)
    def output(self):
        print(self.get())

    # Adds new text without changing the styling
    def add(self, addition):
        self.out += addition
        return self

    def read(self, file):
        f = open(file, 'r')
        self.parse(f.read())
        f.close()
        return self

    def reset(self):
        self.out = ''
        return self

    # Alternate between all the colours of the rainbow
    # No orange, replaced with lightRed
    # No purple/violet so I ignored it
    def rainbow(self, addition=''):
        x = 0
        for i in range(len(addition)):
            if (addition[i] in [' ', '\t', '\n', '\r']):
                x += 1
            [self.red, self.light_red, self.yellow, self.green,
                self.light_blue, self.blue][(i - x) % 6](addition[i])
        return self

    def strip(self):
        text = ''
        i = 0
        while i < len(self.out):
            if self.out[i] == '\033':
                if i + 1 >= len(self.out):
                    return text + '\033'
                if self.out[i + 1] == '[':
                    i += 1
                    if 'm' in self.out[i:]:
                        while self.out[i] != 'm':
                            i += 1
                        i += 1
                    else:
                        text += '\033'
            text += self.out[i]
            i += 1
        return text

    # Simply apply the attribute with the given name
    def attr(self, name):
        if name in self.codes:
            self.out += '\033[%dm' % self.codes[name]

    # Parses text and automatically assigns attributes
    # Attributes are specified through brackets
    # For example, .parse("{red Hello}") is the same as .red("Hello")
    # Multiple attributes can be specified by commas, eg {red,bold Hello}
    # Brackets can be nested, eg {red Hello, {bold world}!}
    # Brackets can be escaped with backslashes
    def parse(self, text):
        i = 0  # Current index
        props = []  # Property stack; required for nested brackets
        while i < len(text):
            c = text[i]
            if c == '\\':  # Escape character
                i += 1
                if i < len(text):
                    self.out += text[i]
            elif c == '{':  # New property list
                prop = ''  # Current property
                i += 1
                curprops = []  # Properties that are part of this bracket
                while text[i] != ' ':
                    if i + 1 == len(text):
                        return self
                    if text[i] == ',':
                        # Properties separated by commas
                        self.attr(prop)
                        curprops.append(prop)
                        prop = ''
                        i += 1
                    prop += text[i]
                    i += 1
                self.attr(prop)
                curprops.append(prop)
                # Add properties to property stack
                props.append(curprops)
            elif c == '}':
                # Reset styling
                self.raw()
                # Remove last entry from property stack
                if len(props) >= 1:
                    props.pop()
                # Apply all properties from any surrounding brackets
                for plist in props:
                    for p in plist:
                        self.attr(p)
            else:
                self.out += c
            i += 1
        return self

    # Multicolored text
    def multi(self, string):
        i = 31  # ID of escape code; starts at 31 (red) and goes to 36 (cyan)
        for c in string:  # Iterate through string
            self.out += '\033[' + str(i) + 'm' + c
            i += 1  # Why u no have ++i? >:(
            if(i > 36):
                i = 31
        return self

# Adds a formatting function to pyfancy with the specified name
# and formatting code
# This shouldn't be exported


def _add(name, number):
    def inner(self, addition=''):
        self.out += '\033[%dm%s' % (number, addition)
        return self
    setattr(pyfancy, name, inner)


# Generate all default color / format codes
for item in pyfancy.codes.items():
    if len(item) > 1:  # Just in case
        _add(item[0], item[1])

# Provides methods for manipulating text styling in specific terminals. # Uses a basic chaining method where text properties are added by calling # methods with related names. # # For example, to print "Hello, world!" in red: #   print pyfancy().red("Hello, world!").get() # # Styles can be changed for different text components. Example: #   print pyfancy().red("Hello").raw(", ").blue("world!").get() # # No output text is necessary when calling a styling method. This allows # styles to be stacked: #   print pyfancy().red().bold("Hello, world!").get() # # There are two provided ways to access the modified text. The first is # direct access to the string object called "out". However, accessing this # object will not reset the style, so any text outputted after will have # the same style as whatever the text was at the end of the chain. # # The get() method is better for accessing text because it resets the text # style so no new text will have unwanted styling. # Stores output text, for reset use get() # The different escape codes # Stores output text, for reset use get() # Returns output text and resets properties # Outputs text using print (should work in Python 2 and 3) # Adds new text without changing the styling # Alternate between all the colours of the rainbow # No orange, replaced with lightRed # No purple/violet so I ignored it # Simply apply the attribute with the given name # Parses text and automatically assigns attributes # Attributes are specified through brackets # For example, .parse("{red Hello}") is the same as .red("Hello") # Multiple attributes can be specified by commas, eg {red,bold Hello} # Brackets can be nested, eg {red Hello, {bold world}!} # Brackets can be escaped with backslashes # Current index # Property stack; required for nested brackets # Escape character # New property list # Current property # Properties that are part of this bracket # Properties separated by commas # Add properties to property stack # Reset styling # Remove last entry from property stack # Apply all properties from any surrounding brackets # Multicolored text # ID of escape code; starts at 31 (red) and goes to 36 (cyan) # Iterate through string # Why u no have ++i? >:( # Adds a formatting function to pyfancy with the specified name # and formatting code # This shouldn't be exported # Generate all default color / format codes # Just in case