Skip to content

Commit

Permalink
Format.wrap() now accepts any type of argument, not only str.
Browse files Browse the repository at this point in the history
Rebuilt Sequence inheritance tree.
Added equality methods for Sequence and Format classes/subclasses.
Added some tests for fmt.* and seq.* classes.
  • Loading branch information
delameter committed Apr 16, 2022
1 parent bf5be84 commit 510a9db
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .env.dist
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VERSION=1.3.2
VERSION=1.4.0
PYPI_USERNAME=__token__
PYPI_PASSWORD= #api token
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ cleanup:
prepare:
python3 -m pip install --upgrade build twine

set-version: ## set new package version
test: ## Run tests
PYTHONPATH=src python3 -m unittest

set-version: ## Set new package version
@echo "Current version: ${YELLOW}${VERSION}${RESET}"
read -p "New version (press enter to keep current): " VERSION
if [ -z $$VERSION ] ; then echo "No changes" && return 0 ; fi
Expand All @@ -34,23 +37,23 @@ set-version: ## set new package version
sed -E -i "s/^version.+/version = $$VERSION/" setup.cfg
echo "Updated version: ${GREEN}$$VERSION${RESET}"

build: ## build module
build: ## Build module
build: cleanup
sed -E -i "s/^VERSION.+/VERSION=$$VERSION/" .env.dist
python3 -m build

## Test repository

upload-dev: ## upload module to test repository
upload-dev: ## Upload module to test repository
python3 -m twine upload --repository testpypi dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD}

install-dev: ## install module from test repository
install-dev: ## Install module from test repository
pip install -i https://test.pypi.org/simple/ pytermor-delameter==${VERSION}

## Primary repository

upload: ## upload module
upload: ## Upload module
python3 -m twine upload dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD} --verbose

install: ## install module
install: ## Install module
pip install pytermor==${VERSION}
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ As you can see, the update went well — we kept all the previously applied

### autof

Signature: `autof(*params str|int|SequenceSGR) -> Format`
Signature: `autof(*params str|int|AbstractSequenceSGR) -> Format`

Create new _Format_ with specified control sequence(s) as a opening/starter sequence and **automatically compose** closing sequence that will terminate attributes defined in opening sequence while keeping the others (soft reset).

Expand All @@ -153,13 +153,16 @@ Each sequence param can be specified as:
- string key (see [API: Registries](#api-registries))
- integer param value
- existing _SequenceSGR_ instance (params will be extracted)
- another inheritor of _AbstractSequenceSGR_: _EmptySequenceSGR_

### build

Signature: `build(*params str|int|SequenceSGR) -> SequenceSGR`
Signature: `build(*params str|int|AbstractSequenceSGR) -> AbstractSequenceSGR`

Create new _SequenceSGR_ with specified params. Resulting sequence params order is the same as argument order. Parameter specification is the same as for `autof`.

Will create _EmptySequenceSGR_ when called without arguments. The purpose of this class is to serve as an empty placeholder when no SGR params are specified; without it method would have created `SequenceSGR()`, which translates to `\e[m`, hard reset sequence, and that's highly counter-intuitive.

### build_c256

Signature:`build_c256(color: int, bg: bool = False) -> SequenceSGR`
Expand Down Expand Up @@ -830,29 +833,36 @@ You can of course create your own sequences and formats, but with one limitation

## Changelog

### v1.4.0

- `Format.wrap()` now accepts any type of argument, not only _str_.
- Rebuilt _Sequence_ inheritance tree.
- Added equality methods for _Sequence_ and _Format_ classes/subclasses.
- Added some tests for `fmt.*` and `seq.*` classes.

### v1.3.2

Added `gray` and `bg_gray` format presets.
- Added `gray` and `bg_gray` format presets.

### v1.3.1

Interface revisioning.
- Interface revisioning.

### v1.2.1

`opening_seq` and `closing_seq` properties for `Format` class.
- `opening_seq` and `closing_seq` properties for _Format_ class.

### v1.2.0

`EmptySequenceSGR` and `EmptyFormat` classes.
- _EmptySequenceSGR_ and _EmptyFormat_ classes.

### v1.1.0

Autoformat feature.
- Autoformat feature.

### v1.0.0

First public version.
- First public version.

## References

Expand Down
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pytermor
version = 1.3.2
version = 1.4.0
author = Aleksandr Shavykin
author_email = 0.delameter@gmail.com
description = ANSI formatted terminal output library
Expand All @@ -9,9 +9,12 @@ long_description_content_type = text/markdown
url = https://github.com/delameter/pytermor
project_urls =
classifiers =
Environment :: Console
Intended Audience :: Developers
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Topic :: Terminals

[options]
package_dir =
Expand Down
81 changes: 51 additions & 30 deletions src/pytermor/fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,50 @@
from __future__ import annotations

from abc import ABCMeta, abstractmethod
from typing import Any

from . import build, code
from .registry import sgr_parity_registry
from .seq import SequenceSGR
from .seq import AbstractSequenceSGR, SequenceSGR


class AbstractFormat(metaclass=ABCMeta):
def __repr__(self):
return f'{self.__class__.__name__}'

def __call__(self, text: str = None) -> str:
def __call__(self, text: Any = None) -> str:
return self.wrap(text)

@abstractmethod
def wrap(self, text: str) -> str: raise NotImplementedError
def wrap(self, text: Any) -> str:
raise NotImplementedError

@property
@abstractmethod
def opening_str(self) -> str: raise NotImplementedError
def opening_str(self) -> str:
raise NotImplementedError

@property
@abstractmethod
def closing_str(self) -> str: raise NotImplementedError
def closing_str(self) -> str:
raise NotImplementedError

@property
@abstractmethod
def opening_seq(self) -> SequenceSGR|None: raise NotImplementedError
def opening_seq(self) -> AbstractSequenceSGR|None:
raise NotImplementedError

@property
@abstractmethod
def closing_seq(self) -> SequenceSGR|None: raise NotImplementedError
def closing_seq(self) -> AbstractSequenceSGR|None:
raise NotImplementedError

def __repr__(self):
return f'{self.__class__.__name__}'


class EmptyFormat(AbstractFormat):
def wrap(self, text: str = None) -> str:
def wrap(self, text: Any = None) -> str:
if text is None:
return ''
return text
return str(text)

@property
def opening_str(self) -> str:
Expand All @@ -49,48 +59,59 @@ def closing_str(self) -> str:
return ''

@property
def opening_seq(self) -> SequenceSGR|None:
def opening_seq(self) -> AbstractSequenceSGR|None:
return None

@property
def closing_seq(self) -> SequenceSGR|None:
def closing_seq(self) -> AbstractSequenceSGR|None:
return None


class Format(AbstractFormat):
def __init__(self, opening_seq: SequenceSGR, closing_seq: SequenceSGR = None, hard_reset_after: bool = False):
self._opening_seq: SequenceSGR = opening_seq
self._closing_seq: SequenceSGR|None = SequenceSGR(0) if hard_reset_after else closing_seq

def __repr__(self):
return super().__repr__() + '[{!r}, {!r}]'.format(self._opening_seq, self._closing_seq)

def wrap(self, text: str = None) -> str:
result = str(self._opening_seq)
def __init__(self, opening_seq: AbstractSequenceSGR, closing_seq: AbstractSequenceSGR = None, hard_reset_after: bool = False):
self._opening_seq: AbstractSequenceSGR = opening_seq
self._closing_seq: AbstractSequenceSGR|None = closing_seq
if hard_reset_after:
self._closing_seq = SequenceSGR(0)

def wrap(self, text: Any = None) -> str:
result = self._opening_seq.print()
if text is not None:
result += text
result += str(text)
if self._closing_seq is not None:
result += str(self._closing_seq)
result += self._closing_seq.print()
return result

@property
def opening_str(self) -> str:
return str(self._opening_seq)
return self._opening_seq.print()

@property
def opening_seq(self) -> SequenceSGR:
def opening_seq(self) -> AbstractSequenceSGR:
return self._opening_seq

@property
def closing_str(self) -> str:
return str(self._closing_seq) if self._closing_seq else ''
if self._closing_seq is not None:
return self._closing_seq.print()
return ''

@property
def closing_seq(self) -> SequenceSGR|None:
def closing_seq(self) -> AbstractSequenceSGR|None:
return self._closing_seq

def __eq__(self, other: Format) -> bool:
if not isinstance(other, Format):
return False

return self._opening_seq == other._opening_seq \
and self._closing_seq == other._closing_seq

def __repr__(self):
return super().__repr__() + '[{!r}, {!r}]'.format(self._opening_seq, self._closing_seq)


def autof(*args: str|int|SequenceSGR) -> Format:
def autof(*args: str|int|AbstractSequenceSGR) -> Format:
opening_seq = build(*args)
closing_seq = sgr_parity_registry.get_closing_seq(opening_seq)
return Format(opening_seq, closing_seq)
Expand Down
14 changes: 8 additions & 6 deletions src/pytermor/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from __future__ import annotations

from copy import copy
from typing import Dict, Tuple
from typing import Dict, Tuple, List

from pytermor import build

from . import code
from .seq import SequenceSGR
from .seq import SequenceSGR, AbstractSequenceSGR


class Registry:
Expand All @@ -30,8 +32,8 @@ def register_complex(self, starter_codes: Tuple[int, ...], param_len: int, break
self._complex_code_def[starter_codes] = param_len
self._complex_code_max_len = max(self._complex_code_max_len, len(starter_codes) + param_len)

def get_closing_seq(self, opening_seq: SequenceSGR) -> SequenceSGR:
closing_seq = SequenceSGR()
def get_closing_seq(self, opening_seq: AbstractSequenceSGR) -> AbstractSequenceSGR:
closing_seq_params: List[int] = []
opening_params = copy(opening_seq.params)
while len(opening_params):
key_params: int|Tuple[int, ...]|None = None
Expand All @@ -46,9 +48,9 @@ def get_closing_seq(self, opening_seq: SequenceSGR) -> SequenceSGR:
key_params = opening_params.pop(0)
if key_params not in self._code_to_breaker_map:
continue
closing_seq += self._code_to_breaker_map[key_params]
closing_seq_params.extend(self._code_to_breaker_map[key_params].params)

return closing_seq
return build(*closing_seq_params)


sgr_parity_registry = Registry()
Expand Down
Loading

0 comments on commit 510a9db

Please sign in to comment.