arches_extensions.utils

  1import uuid
  2import textwrap
  3from typing import Union
  4from argparse import RawTextHelpFormatter
  5
  6from django.db.models.functions import Lower
  7
  8from arches.app.models.graph import Graph
  9
 10class ArchesCLIStyles():
 11    """
 12Styles for Arches CLI output. Borrowed heavily from https://stackoverflow.com/a/26445590/3873885.
 13
 14Usage::
 15
 16    s = ArchesCLIStyles()
 17
 18Add arbitrary style within strings::
 19
 20    print(f"{s.fg.red}Red text{s.reset} not red text")
 21
 22Some methods transform text in predetermined ways, like 
 23the following format for an optional CLI argument::
 24
 25    print(s.opt("--source"))
 26
 27Further notes
 28
 29    - Reset all colors with `s.reset`
 30    - Two subclasses `fg` for foreground and `bg` for background.
 31        - Use as `s.<subclass>.<colorname>`, e.g., `s.fg.red` or `s.bg.green`
 32    - Also, `bold`, `disable`, `underline`, `reverse`, `strikethrough`,
 33    and `invisible` work with the main class, e.g., `s.bold`
 34"""
 35    reset='\033[0m'
 36    bold='\033[01m'
 37    disable='\033[02m'
 38    underline='\033[04m'
 39    blink='\33[5m'
 40    noblink='\33[25m'
 41    reverse='\033[07m'
 42    strikethrough='\033[09m'
 43    invisible='\033[08m'
 44    class fg:
 45        black='\033[30m'
 46        red='\033[31m'
 47        green='\033[32m'
 48        orange='\033[33m'
 49        blue='\033[34m'
 50        purple='\033[35m'
 51        cyan='\033[36m'
 52        lightgrey='\033[37m'
 53        darkgrey='\033[90m'
 54        lightred='\033[91m'
 55        lightgreen='\033[92m'
 56        yellow='\033[93m'
 57        lightblue='\033[94m'
 58        pink='\033[95m'
 59        lightcyan='\033[96m'
 60    class bg:
 61        black='\033[40m'
 62        red='\033[41m'
 63        green='\033[42m'
 64        orange='\033[43m'
 65        blue='\033[44m'
 66        purple='\033[45m'
 67        cyan='\033[46m'
 68        lightgrey='\033[47m'
 69        salmon='\033[101m'
 70
 71    def print_ansi_codes(self):
 72        """
 73        This is a helper method that may be useful during style development.
 74
 75        https://stackoverflow.com/a/39452138/3873885
 76        """
 77        x  = 0
 78        for i in range(24):
 79            colors = ""
 80            for j in range(5):
 81                code = str(x+j)
 82                colors += "\33[" + code + "m\\33[" + code + "m\033[0m "
 83            print(colors)
 84            x += 5
 85
 86    def req(self, string):
 87        return f"{self.fg.lightgreen}{string}{self.reset}"
 88
 89    def opt(self, string):
 90        return f"{self.fg.yellow}{string}{self.reset}"
 91
 92    def invert(self, string):
 93        return f"{self.reverse}{string}{self.reset}"
 94
 95    def error(self, string):
 96        return f"{self.fg.lightred}{self.bold}{self.blink}{string}{self.reset}"
 97
 98    def warn(self, string):
 99        return f"{self.fg.cyan}{self.bold}{string}{self.reset}"
100
101class ArchesHelpTextFormatter(RawTextHelpFormatter):
102    """Help message formatter that applies optional text styling."""
103    def _split_lines(self, text, width):
104        ret = []
105        for line in text.splitlines():
106            ret += [i for i in textwrap.wrap(line.strip(), width)]
107        return ret
108
109def get_graph(name_or_uuid: Union[str, uuid.UUID]) -> Graph:
110    """ Utility function to get a graph by its name or UUID. """
111    graph = None
112    try:
113        uid = uuid.UUID(str(name_or_uuid))
114    except ValueError:
115        qs = Graph.objects.annotate(name_lower=Lower('name'))
116        graphs = qs.filter(name_lower=str(name_or_uuid).lower(), isresource=True)
117        if graphs.count() == 1:
118            return graphs[0]
119        elif graphs.count() > 1:
120            msg = "Choose one of the following (by number):"
121            lookup = {}
122            for n, g in enumerate(graphs, start=1):
123                msg += f"\n{n}. {g.name}, {g.uuid}"
124                lookup[n] = g
125            choice = input(msg)
126            if choice in lookup:
127                graph = lookup[choice]
128
129    return graph
130
131def user_confirms(message:str="Continue?", default:bool=True):
132
133    message = f"{message} Y/n " if default is True else f"{message} y/N "
134    response = input(message).lower()
135    if not response:
136        return default
137    elif response.startswith("y"):
138        return True
139    else:
140        return False
class ArchesCLIStyles:
 11class ArchesCLIStyles():
 12    """
 13Styles for Arches CLI output. Borrowed heavily from https://stackoverflow.com/a/26445590/3873885.
 14
 15Usage::
 16
 17    s = ArchesCLIStyles()
 18
 19Add arbitrary style within strings::
 20
 21    print(f"{s.fg.red}Red text{s.reset} not red text")
 22
 23Some methods transform text in predetermined ways, like 
 24the following format for an optional CLI argument::
 25
 26    print(s.opt("--source"))
 27
 28Further notes
 29
 30    - Reset all colors with `s.reset`
 31    - Two subclasses `fg` for foreground and `bg` for background.
 32        - Use as `s.<subclass>.<colorname>`, e.g., `s.fg.red` or `s.bg.green`
 33    - Also, `bold`, `disable`, `underline`, `reverse`, `strikethrough`,
 34    and `invisible` work with the main class, e.g., `s.bold`
 35"""
 36    reset='\033[0m'
 37    bold='\033[01m'
 38    disable='\033[02m'
 39    underline='\033[04m'
 40    blink='\33[5m'
 41    noblink='\33[25m'
 42    reverse='\033[07m'
 43    strikethrough='\033[09m'
 44    invisible='\033[08m'
 45    class fg:
 46        black='\033[30m'
 47        red='\033[31m'
 48        green='\033[32m'
 49        orange='\033[33m'
 50        blue='\033[34m'
 51        purple='\033[35m'
 52        cyan='\033[36m'
 53        lightgrey='\033[37m'
 54        darkgrey='\033[90m'
 55        lightred='\033[91m'
 56        lightgreen='\033[92m'
 57        yellow='\033[93m'
 58        lightblue='\033[94m'
 59        pink='\033[95m'
 60        lightcyan='\033[96m'
 61    class bg:
 62        black='\033[40m'
 63        red='\033[41m'
 64        green='\033[42m'
 65        orange='\033[43m'
 66        blue='\033[44m'
 67        purple='\033[45m'
 68        cyan='\033[46m'
 69        lightgrey='\033[47m'
 70        salmon='\033[101m'
 71
 72    def print_ansi_codes(self):
 73        """
 74        This is a helper method that may be useful during style development.
 75
 76        https://stackoverflow.com/a/39452138/3873885
 77        """
 78        x  = 0
 79        for i in range(24):
 80            colors = ""
 81            for j in range(5):
 82                code = str(x+j)
 83                colors += "\33[" + code + "m\\33[" + code + "m\033[0m "
 84            print(colors)
 85            x += 5
 86
 87    def req(self, string):
 88        return f"{self.fg.lightgreen}{string}{self.reset}"
 89
 90    def opt(self, string):
 91        return f"{self.fg.yellow}{string}{self.reset}"
 92
 93    def invert(self, string):
 94        return f"{self.reverse}{string}{self.reset}"
 95
 96    def error(self, string):
 97        return f"{self.fg.lightred}{self.bold}{self.blink}{string}{self.reset}"
 98
 99    def warn(self, string):
100        return f"{self.fg.cyan}{self.bold}{string}{self.reset}"

Styles for Arches CLI output. Borrowed heavily from https://stackoverflow.com/a/26445590/3873885.

Usage::

s = ArchesCLIStyles()

Add arbitrary style within strings::

print(f"{s.fg.red}Red text{s.reset} not red text")

Some methods transform text in predetermined ways, like the following format for an optional CLI argument::

print(s.opt("--source"))

Further notes

- Reset all colors with `s.reset`
- Two subclasses `fg` for foreground and `bg` for background.
    - Use as `s.<subclass>.<colorname>`, e.g., `s.fg.red` or `s.bg.green`
- Also, `bold`, `disable`, `underline`, `reverse`, `strikethrough`,
and `invisible` work with the main class, e.g., `s.bold`
reset = '\x1b[0m'
bold = '\x1b[01m'
disable = '\x1b[02m'
underline = '\x1b[04m'
reverse = '\x1b[07m'
strikethrough = '\x1b[09m'
invisible = '\x1b[08m'
def print_ansi_codes(self):
72    def print_ansi_codes(self):
73        """
74        This is a helper method that may be useful during style development.
75
76        https://stackoverflow.com/a/39452138/3873885
77        """
78        x  = 0
79        for i in range(24):
80            colors = ""
81            for j in range(5):
82                code = str(x+j)
83                colors += "\33[" + code + "m\\33[" + code + "m\033[0m "
84            print(colors)
85            x += 5

This is a helper method that may be useful during style development.

https://stackoverflow.com/a/39452138/3873885

def req(self, string):
87    def req(self, string):
88        return f"{self.fg.lightgreen}{string}{self.reset}"
def opt(self, string):
90    def opt(self, string):
91        return f"{self.fg.yellow}{string}{self.reset}"
def invert(self, string):
93    def invert(self, string):
94        return f"{self.reverse}{string}{self.reset}"
def error(self, string):
96    def error(self, string):
97        return f"{self.fg.lightred}{self.bold}{self.blink}{string}{self.reset}"
def warn(self, string):
 99    def warn(self, string):
100        return f"{self.fg.cyan}{self.bold}{string}{self.reset}"
class ArchesCLIStyles.fg:
45    class fg:
46        black='\033[30m'
47        red='\033[31m'
48        green='\033[32m'
49        orange='\033[33m'
50        blue='\033[34m'
51        purple='\033[35m'
52        cyan='\033[36m'
53        lightgrey='\033[37m'
54        darkgrey='\033[90m'
55        lightred='\033[91m'
56        lightgreen='\033[92m'
57        yellow='\033[93m'
58        lightblue='\033[94m'
59        pink='\033[95m'
60        lightcyan='\033[96m'
black = '\x1b[30m'
red = '\x1b[31m'
green = '\x1b[32m'
orange = '\x1b[33m'
blue = '\x1b[34m'
purple = '\x1b[35m'
cyan = '\x1b[36m'
lightgrey = '\x1b[37m'
darkgrey = '\x1b[90m'
lightred = '\x1b[91m'
lightgreen = '\x1b[92m'
yellow = '\x1b[93m'
lightblue = '\x1b[94m'
pink = '\x1b[95m'
lightcyan = '\x1b[96m'
class ArchesCLIStyles.bg:
61    class bg:
62        black='\033[40m'
63        red='\033[41m'
64        green='\033[42m'
65        orange='\033[43m'
66        blue='\033[44m'
67        purple='\033[45m'
68        cyan='\033[46m'
69        lightgrey='\033[47m'
70        salmon='\033[101m'
black = '\x1b[40m'
red = '\x1b[41m'
green = '\x1b[42m'
orange = '\x1b[43m'
blue = '\x1b[44m'
purple = '\x1b[45m'
cyan = '\x1b[46m'
lightgrey = '\x1b[47m'
salmon = '\x1b[101m'
class ArchesHelpTextFormatter(argparse.RawTextHelpFormatter):
102class ArchesHelpTextFormatter(RawTextHelpFormatter):
103    """Help message formatter that applies optional text styling."""
104    def _split_lines(self, text, width):
105        ret = []
106        for line in text.splitlines():
107            ret += [i for i in textwrap.wrap(line.strip(), width)]
108        return ret

Help message formatter that applies optional text styling.

def get_graph(name_or_uuid: Union[str, uuid.UUID]) -> arches.app.models.graph.Graph:
110def get_graph(name_or_uuid: Union[str, uuid.UUID]) -> Graph:
111    """ Utility function to get a graph by its name or UUID. """
112    graph = None
113    try:
114        uid = uuid.UUID(str(name_or_uuid))
115    except ValueError:
116        qs = Graph.objects.annotate(name_lower=Lower('name'))
117        graphs = qs.filter(name_lower=str(name_or_uuid).lower(), isresource=True)
118        if graphs.count() == 1:
119            return graphs[0]
120        elif graphs.count() > 1:
121            msg = "Choose one of the following (by number):"
122            lookup = {}
123            for n, g in enumerate(graphs, start=1):
124                msg += f"\n{n}. {g.name}, {g.uuid}"
125                lookup[n] = g
126            choice = input(msg)
127            if choice in lookup:
128                graph = lookup[choice]
129
130    return graph

Utility function to get a graph by its name or UUID.

def user_confirms(message: str = 'Continue?', default: bool = True):
132def user_confirms(message:str="Continue?", default:bool=True):
133
134    message = f"{message} Y/n " if default is True else f"{message} y/N "
135    response = input(message).lower()
136    if not response:
137        return default
138    elif response.startswith("y"):
139        return True
140    else:
141        return False