Module musar.misc

General purpose classes and functions.

Expand source code
"""General purpose classes and functions.
"""

# pylint: disable=E1101
import curses
import PIL


class TextEditor:
    """Text editor based on `curses`.

    Attributes
    ----------
    array : List[str]
        Text content model.
    cursor : [int, int]
        Position of the cursor within the text.
    stdscr : curses.window
        `curses` window for display the text.
    clipboard : str
        Text clipboard for copy-pasting.
    scroll : int
        Vertical line scrolling.
    rows : int
        Window number of rows.

    """

    def __init__(self):
        self.array = list()
        self.cursor = [0, 0]
        self.stdscr = None
        self.clipboard = None
        self.scroll = 0
        self.rows = 0

    def __call__(self, base_text=""):
        self.array = base_text.split("\n")
        self.cursor = [0, 0]
        self.stdscr = curses.initscr()
        self.rows = self.stdscr.getmaxyx()[0]
        curses.noecho()
        curses.cbreak()
        self.stdscr.keypad(True)
        while True:
            self._draw()
            key = self.stdscr.getch()
            break_loop = self._handle_key(key)
            if break_loop:
                break
        curses.nocbreak()
        self.stdscr.keypad(False)
        curses.echo()
        curses.endwin()
        return "\n".join(self.array)

    def _move_cursor_left(self):
        self.cursor[1] = max(0, self.cursor[1] - 1)

    def _move_cursor_right(self):
        self.cursor[1] = min(
            len(self.array[self.cursor[0]]),
            self.cursor[1] + 1
        )

    def _move_cursor_up(self):
        self.cursor[0] = max(0, self.cursor[0] - 1)
        self.cursor[1] = min(self.cursor[1], len(self.array[self.cursor[0]]))
        if self.cursor[0] < self.scroll:
            self.scroll -= 1

    def _move_cursor_down(self):
        self.cursor[0] = min(len(self.array) - 1, self.cursor[0] + 1)
        self.cursor[1] = min(self.cursor[1], len(self.array[self.cursor[0]]))
        if self.cursor[0] >= self.rows + self.scroll:
            self.scroll += 1

    def _handle_key(self, key):  # pylint: disable=R0912,R0915
        if key in {
                3,  # ^C
                4,  # ^D
                17,  # ^Q
                23,  # ^W
                24,  # ^X
                27  # ESC
            }:
            return True
        if key == 260:  # Left
            self._move_cursor_left()
        elif key == 261:  # Right
            self._move_cursor_right()
        elif key == 259:  # Up
            self._move_cursor_up()
        elif key == 258:  # Down
            self._move_cursor_down()
        elif key == 338:  # Page Down
            for _ in range(self.rows):
                self._move_cursor_down()
        elif key == 339:  # Page Up
            for _ in range(self.rows):
                self._move_cursor_up()
        elif key == 443:  # Ctrl+Left
            for i in range(1, self.cursor[1] + 1):
                target = self.cursor[1] - i
                if target == 0 or self.array[self.cursor[0]][target - 1] == " ":
                    self.cursor[1] = target
                    break
        elif key == 444:  # Ctrl+Right
            for target in range(self.cursor[1] + 1, len(self.array[self.cursor[0]]) + 1):
                if target == len(self.array[self.cursor[0]])\
                    or self.array[self.cursor[0]][target - 1] == " ":
                    self.cursor[1] = target
                    break
        elif key == 1:  # ^A
            self.cursor[1] = 0
        elif key == 5:  # ^E
            self.cursor[1] = len(self.array[self.cursor[0]])
        elif key == 10:  # ENTER
            prev_line = self.array[self.cursor[0]][:self.cursor[1]]
            next_line = self.array[self.cursor[0]][self.cursor[1]:]
            self.array[self.cursor[0]] = prev_line
            self.array.insert(self.cursor[0] + 1, next_line)
            self._move_cursor_down()
            self.cursor[1] = 0
        elif key == 8:  # BACKSPACE
            if self.cursor[1] == 0:
                if self.cursor[0] > 0:
                    self.cursor[1] = len(self.array[self.cursor[0] - 1])
                    self.array[self.cursor[0] - 1] =\
                        self.array[self.cursor[0] - 1]\
                        + self.array[self.cursor[0]]
                    self.array.pop(self.cursor[0])
                    self.cursor[0] = self.cursor[0] - 1
            else:
                self.array[self.cursor[0]] =\
                    self.array[self.cursor[0]][:self.cursor[1] - 1]\
                    + self.array[self.cursor[0]][self.cursor[1]:]
                self.cursor[1] = max(0, self.cursor[1] - 1)
        elif key == 330:  # DELETE:
            if self.cursor[1] == len(self.array[self.cursor[0]]):
                if self.cursor[0] < len(self.array) - 1:
                    self.array[self.cursor[0]] += self.array[self.cursor[0] + 1]
                    self.array.pop(self.cursor[0] + 1)
            else:
                self.array[self.cursor[0]] =\
                    self.array[self.cursor[0]][:self.cursor[1]]\
                    + self.array[self.cursor[0]][self.cursor[1] + 1:]
        elif key == 11:  # ^K
            self.clipboard = self.array[self.cursor[0]]
            self.array.pop(self.cursor[0])
            self._move_cursor_up()
            self._move_cursor_down()
        elif key == 21:  # ^U
            self.array.insert(self.cursor[0], self.clipboard)
            self.cursor[1] = len(self.clipboard)
        elif key < 256:
            self.array[self.cursor[0]] =\
                self.array[self.cursor[0]][:self.cursor[1]]\
                + chr(key)\
                + self.array[self.cursor[0]][self.cursor[1]:]
            self._move_cursor_right()
        return False

    def _draw(self):
        self.stdscr.clear()
        for i, line in enumerate(self.array[self.scroll:]):
            if i >= self.rows:
                break
            self.stdscr.addstr(i, 0, line)
        self.stdscr.addstr(self.cursor[0] - self.scroll, self.cursor[1], "")
        self.stdscr.refresh()


def most_common_list_value(values):
    """Select the most common value from a list.

    Parameters
    ----------
    values : List[T]
        Input list.

    Returns
    -------
    T
        Most common value.

    """
    occurrences = dict()
    for value in values:
        occurrences.setdefault(value, 0)
        occurrences[value] += 1
    return max(occurrences.items(), key=lambda x: x[1])[0]


def most_common_key_value(key, items):
    """Select the most common value for a key from a list of dictionnaries.

    Parameters
    ----------
    key : T
        Input dictionnary key.
    items : List[Dict[T, U]]
        Input dictionnaries.

    Returns
    -------
    U
        Most common value.

    """
    values = [item[key] for item in items]
    return most_common_list_value(values)


class HashableImage:
    """Pillow image that can be hashed and quickly compared to another.

    Parameters
    ----------
    image : PIL.Image.Image
        Pillow image.

    Attributes
    ----------
    hash : int
        Image hash digest.
    image : PIL.Image.Image

    """

    def __init__(self, image):
        self.image: PIL.Image.Image = image
        self.hash: int = hash(self.image.tobytes())

    def __hash__(self):
        return self.hash

    def __eq__(self, other):
        return self.hash == other.hash

Functions

def most_common_key_value(key, items)

Select the most common value for a key from a list of dictionnaries.

Parameters

key : T
Input dictionnary key.
items : List[Dict[T, U]]
Input dictionnaries.

Returns

U
Most common value.
Expand source code
def most_common_key_value(key, items):
    """Select the most common value for a key from a list of dictionnaries.

    Parameters
    ----------
    key : T
        Input dictionnary key.
    items : List[Dict[T, U]]
        Input dictionnaries.

    Returns
    -------
    U
        Most common value.

    """
    values = [item[key] for item in items]
    return most_common_list_value(values)
def most_common_list_value(values)

Select the most common value from a list.

Parameters

values : List[T]
Input list.

Returns

T
Most common value.
Expand source code
def most_common_list_value(values):
    """Select the most common value from a list.

    Parameters
    ----------
    values : List[T]
        Input list.

    Returns
    -------
    T
        Most common value.

    """
    occurrences = dict()
    for value in values:
        occurrences.setdefault(value, 0)
        occurrences[value] += 1
    return max(occurrences.items(), key=lambda x: x[1])[0]

Classes

class HashableImage (image)

Pillow image that can be hashed and quickly compared to another.

Parameters

image : PIL.Image.Image
Pillow image.

Attributes

hash : int
Image hash digest.
image : PIL.Image.Image
 
Expand source code
class HashableImage:
    """Pillow image that can be hashed and quickly compared to another.

    Parameters
    ----------
    image : PIL.Image.Image
        Pillow image.

    Attributes
    ----------
    hash : int
        Image hash digest.
    image : PIL.Image.Image

    """

    def __init__(self, image):
        self.image: PIL.Image.Image = image
        self.hash: int = hash(self.image.tobytes())

    def __hash__(self):
        return self.hash

    def __eq__(self, other):
        return self.hash == other.hash
class TextEditor

Text editor based on curses.

Attributes

array : List[str]
Text content model.
cursor : [int, int]
Position of the cursor within the text.
stdscr : curses.window
curses window for display the text.
clipboard : str
Text clipboard for copy-pasting.
scroll : int
Vertical line scrolling.
rows : int
Window number of rows.
Expand source code
class TextEditor:
    """Text editor based on `curses`.

    Attributes
    ----------
    array : List[str]
        Text content model.
    cursor : [int, int]
        Position of the cursor within the text.
    stdscr : curses.window
        `curses` window for display the text.
    clipboard : str
        Text clipboard for copy-pasting.
    scroll : int
        Vertical line scrolling.
    rows : int
        Window number of rows.

    """

    def __init__(self):
        self.array = list()
        self.cursor = [0, 0]
        self.stdscr = None
        self.clipboard = None
        self.scroll = 0
        self.rows = 0

    def __call__(self, base_text=""):
        self.array = base_text.split("\n")
        self.cursor = [0, 0]
        self.stdscr = curses.initscr()
        self.rows = self.stdscr.getmaxyx()[0]
        curses.noecho()
        curses.cbreak()
        self.stdscr.keypad(True)
        while True:
            self._draw()
            key = self.stdscr.getch()
            break_loop = self._handle_key(key)
            if break_loop:
                break
        curses.nocbreak()
        self.stdscr.keypad(False)
        curses.echo()
        curses.endwin()
        return "\n".join(self.array)

    def _move_cursor_left(self):
        self.cursor[1] = max(0, self.cursor[1] - 1)

    def _move_cursor_right(self):
        self.cursor[1] = min(
            len(self.array[self.cursor[0]]),
            self.cursor[1] + 1
        )

    def _move_cursor_up(self):
        self.cursor[0] = max(0, self.cursor[0] - 1)
        self.cursor[1] = min(self.cursor[1], len(self.array[self.cursor[0]]))
        if self.cursor[0] < self.scroll:
            self.scroll -= 1

    def _move_cursor_down(self):
        self.cursor[0] = min(len(self.array) - 1, self.cursor[0] + 1)
        self.cursor[1] = min(self.cursor[1], len(self.array[self.cursor[0]]))
        if self.cursor[0] >= self.rows + self.scroll:
            self.scroll += 1

    def _handle_key(self, key):  # pylint: disable=R0912,R0915
        if key in {
                3,  # ^C
                4,  # ^D
                17,  # ^Q
                23,  # ^W
                24,  # ^X
                27  # ESC
            }:
            return True
        if key == 260:  # Left
            self._move_cursor_left()
        elif key == 261:  # Right
            self._move_cursor_right()
        elif key == 259:  # Up
            self._move_cursor_up()
        elif key == 258:  # Down
            self._move_cursor_down()
        elif key == 338:  # Page Down
            for _ in range(self.rows):
                self._move_cursor_down()
        elif key == 339:  # Page Up
            for _ in range(self.rows):
                self._move_cursor_up()
        elif key == 443:  # Ctrl+Left
            for i in range(1, self.cursor[1] + 1):
                target = self.cursor[1] - i
                if target == 0 or self.array[self.cursor[0]][target - 1] == " ":
                    self.cursor[1] = target
                    break
        elif key == 444:  # Ctrl+Right
            for target in range(self.cursor[1] + 1, len(self.array[self.cursor[0]]) + 1):
                if target == len(self.array[self.cursor[0]])\
                    or self.array[self.cursor[0]][target - 1] == " ":
                    self.cursor[1] = target
                    break
        elif key == 1:  # ^A
            self.cursor[1] = 0
        elif key == 5:  # ^E
            self.cursor[1] = len(self.array[self.cursor[0]])
        elif key == 10:  # ENTER
            prev_line = self.array[self.cursor[0]][:self.cursor[1]]
            next_line = self.array[self.cursor[0]][self.cursor[1]:]
            self.array[self.cursor[0]] = prev_line
            self.array.insert(self.cursor[0] + 1, next_line)
            self._move_cursor_down()
            self.cursor[1] = 0
        elif key == 8:  # BACKSPACE
            if self.cursor[1] == 0:
                if self.cursor[0] > 0:
                    self.cursor[1] = len(self.array[self.cursor[0] - 1])
                    self.array[self.cursor[0] - 1] =\
                        self.array[self.cursor[0] - 1]\
                        + self.array[self.cursor[0]]
                    self.array.pop(self.cursor[0])
                    self.cursor[0] = self.cursor[0] - 1
            else:
                self.array[self.cursor[0]] =\
                    self.array[self.cursor[0]][:self.cursor[1] - 1]\
                    + self.array[self.cursor[0]][self.cursor[1]:]
                self.cursor[1] = max(0, self.cursor[1] - 1)
        elif key == 330:  # DELETE:
            if self.cursor[1] == len(self.array[self.cursor[0]]):
                if self.cursor[0] < len(self.array) - 1:
                    self.array[self.cursor[0]] += self.array[self.cursor[0] + 1]
                    self.array.pop(self.cursor[0] + 1)
            else:
                self.array[self.cursor[0]] =\
                    self.array[self.cursor[0]][:self.cursor[1]]\
                    + self.array[self.cursor[0]][self.cursor[1] + 1:]
        elif key == 11:  # ^K
            self.clipboard = self.array[self.cursor[0]]
            self.array.pop(self.cursor[0])
            self._move_cursor_up()
            self._move_cursor_down()
        elif key == 21:  # ^U
            self.array.insert(self.cursor[0], self.clipboard)
            self.cursor[1] = len(self.clipboard)
        elif key < 256:
            self.array[self.cursor[0]] =\
                self.array[self.cursor[0]][:self.cursor[1]]\
                + chr(key)\
                + self.array[self.cursor[0]][self.cursor[1]:]
            self._move_cursor_right()
        return False

    def _draw(self):
        self.stdscr.clear()
        for i, line in enumerate(self.array[self.scroll:]):
            if i >= self.rows:
                break
            self.stdscr.addstr(i, 0, line)
        self.stdscr.addstr(self.cursor[0] - self.scroll, self.cursor[1], "")
        self.stdscr.refresh()