Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pycommons/lang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .arrayutils import ArrayUtils
from .charutils import CharUtils
from .objectutils import ObjectUtils
from .stringutils import StringUtils

__all__ = ["ArrayUtils", "CharUtils", "ObjectUtils"]

Expand Down
10 changes: 7 additions & 3 deletions pycommons/lang/arrayutils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from typing import Sized
from typing import Sized, Optional


class ArrayUtils:
@classmethod
def get_length(cls, arr: Sized) -> int:
def get_length(cls, arr: Optional[Sized]) -> int:
return len(arr) if arr is not None else 0

@classmethod
def is_empty(cls, arr: Sized) -> bool:
def is_empty(cls, arr: Optional[Sized]) -> bool:
return cls.get_length(arr) == 0

@classmethod
def is_not_empty(cls, arr: Optional[Sized]) -> bool:
return not cls.is_empty(arr)
15 changes: 15 additions & 0 deletions pycommons/lang/charutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ def is_ascii_printable(cls, c: Optional[CharType]) -> bool:
char: Optional[Char] = cls.to_character(c)
return char is not None and cls.ASCII_SPACE < char < 127

@classmethod
def is_digit(cls, c: Optional[CharType]) -> bool:
char: Optional[Char] = cls.to_character(c)
return char is not None and char.isdigit()

@classmethod
def is_letter(cls, c: Optional[CharType]) -> bool:
char: Optional[Char] = cls.to_character(c)
Expand Down Expand Up @@ -53,6 +58,16 @@ def to_character(cls, c: Optional[CharType]) -> Optional[Char]:
return None
return Char(c)

@classmethod
def to_uppercase(cls, c: Optional[CharType]) -> Optional[Char]:
char: Optional[Char] = cls.to_character(c)
return char.upper() if char else None

@classmethod
def to_lowercase(cls, c: Optional[CharType]) -> Optional[Char]:
char: Optional[Char] = cls.to_character(c)
return char.lower() if char else None

@classmethod
def is_equal(cls, c: CharType, other: CharType) -> bool:
if c is None or other is None:
Expand Down
4 changes: 2 additions & 2 deletions pycommons/lang/function/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from .consumer import Consumer
from .consumer import Consumer, BiConsumer
from .function import Function
from .predicate import Predicate
from .runnable import Runnable
from .supplier import Supplier

__all__ = ["Consumer", "Function", "Predicate", "Runnable", "Supplier"]
__all__ = ["BiConsumer", "Consumer", "Function", "Predicate", "Runnable", "Supplier"]
24 changes: 24 additions & 0 deletions pycommons/lang/function/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TypeVar, Generic, Callable, Any

_T = TypeVar("_T")
_U = TypeVar("_U")


class Consumer(Generic[_T]):
Expand All @@ -26,3 +27,26 @@ def _impl(_t: _T) -> None:

def __call__(self, *args: _T, **kwargs: Any) -> None:
self.accept(args[0])


class BiConsumer(Generic[_T, _U]):
@classmethod
def of(cls, consumer: Callable[[_T, _U], None]) -> BiConsumer[_T, _U]:
class BasicBiConsumer(BiConsumer[_T, _U]):
def accept(self, t: _T, u: _U) -> None:
consumer(t, u)

return BasicBiConsumer()

def accept(self, t: _T, u: _U) -> None:
pass

def and_then(self, after: BiConsumer[_T, _U]) -> BiConsumer[_T, _U]:
def _impl(_t: _T, _u: _U) -> None:
self.accept(_t, _u)
after.accept(_t, _u)

return BiConsumer.of(_impl)

def __call__(self, t: _T, u: _U, *args: Any, **kwargs: Any) -> None:
self.accept(t, u)
7 changes: 4 additions & 3 deletions pycommons/lang/objectutils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
from typing import TypeVar, Optional

_T = TypeVar("_T")
Expand All @@ -6,13 +7,13 @@

class ObjectUtils:
@classmethod
def require_not_none(cls, t: _T, e: Optional[_E] = None) -> None:
def require_not_none(cls, t: Optional[_T], e: Optional[_E] = None) -> None:
if t is None:
if e is None:
raise ValueError("Object cannot be None")
raise e

@classmethod
def get_not_none(cls, t: _T, e: Optional[_E] = None) -> _T:
def get_not_none(cls, t: Optional[_T], e: Optional[_E] = None) -> _T:
cls.require_not_none(t, e)
return t
return typing.cast(_T, t)
168 changes: 152 additions & 16 deletions pycommons/lang/stringutils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,101 @@
from typing import Optional
import typing
from typing import Optional, List

from .arrayutils import ArrayUtils
from .bases.char import Char
from .charutils import CharUtils
from .function import Supplier


class StringUtils:
EMPTY: str = ""

@classmethod
def contains(cls, char_sequence: Optional[str], search_string: Optional[str]) -> bool:
if char_sequence is not None and search_string is not None:
search_length: int = len(search_string)
max_iterations: int = len(char_sequence) - search_length

for i in range(0, max_iterations + 1):
if cls.region_matches(char_sequence, False, i, search_string, 0, search_length):
return True
return False

@classmethod
def contains_any(cls, char_sequence: Optional[str], *search_strings: str) -> bool:
if char_sequence is not None and ArrayUtils.is_not_empty(search_strings):
for search_string in search_strings:
if cls.contains(char_sequence, search_string):
return True
return False

@classmethod
def contains_ignore_case(
cls, char_sequence: Optional[str], search_string: Optional[str]
) -> bool:
if char_sequence is not None and search_string is not None:
search_length: int = len(search_string)
max_iterations: int = len(char_sequence) - search_length

for i in range(0, max_iterations + 1):
if cls.region_matches(char_sequence, True, i, search_string, 0, search_length):
return True
return False

@classmethod
def get_first_non_blank(cls, *args: Optional[str]) -> Optional[str]:
if ArrayUtils.is_empty(args):
return None

for char_sequence in args:
if not cls.is_blank(char_sequence):
return char_sequence

return None

@classmethod
def is_all_lower_case(cls, char_sequence: str) -> bool:
def get_first_non_empty(cls, *args: Optional[str]) -> Optional[str]:
if ArrayUtils.is_empty(args):
return None

for char_sequence in args:
if not cls.is_empty(char_sequence):
return char_sequence

return None

@classmethod
def get_bytes(cls, char_sequence: Optional[str], encoding: str = "utf-8") -> Optional[bytes]:
if char_sequence is None:
return None
return char_sequence.encode(encoding)

@classmethod
def get_digits(cls, char_sequence: Optional[str]) -> str:
if cls.is_empty(char_sequence):
return cls.EMPTY

digits: List[str] = []
for c in typing.cast(str, char_sequence):
if CharUtils.is_digit(c):
digits.append(c)

return cls.EMPTY.join(digits)

@classmethod
def get_if_blank(cls, char_sequence: Optional[str], default_supplier: Supplier[str]) -> str:
if cls.is_not_blank(char_sequence):
return typing.cast(str, char_sequence)
return default_supplier.get()

@classmethod
def get_if_empty(cls, char_sequence: Optional[str], default_supplier: Supplier[str]) -> str:
if cls.is_not_empty(char_sequence):
return typing.cast(str, char_sequence)
return default_supplier.get()

@classmethod
def is_all_lower_case(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -17,7 +105,7 @@ def is_all_lower_case(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_all_upper_case(cls, char_sequence: str) -> bool:
def is_all_upper_case(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -27,7 +115,7 @@ def is_all_upper_case(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_alpha(cls, char_sequence: str) -> bool:
def is_alpha(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -37,7 +125,7 @@ def is_alpha(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_alphanumeric(cls, char_sequence: str) -> bool:
def is_alphanumeric(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -47,7 +135,7 @@ def is_alphanumeric(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_alphanumeric_space(cls, char_sequence: str) -> bool:
def is_alphanumeric_space(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -59,7 +147,7 @@ def is_alphanumeric_space(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_alpha_space(cls, char_sequence: str) -> bool:
def is_alpha_space(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -71,7 +159,7 @@ def is_alpha_space(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_any_blank(cls, *args: str) -> bool:
def is_any_blank(cls, *args: Optional[str]) -> bool:
if ArrayUtils.is_empty(args):
return False

Expand All @@ -81,7 +169,7 @@ def is_any_blank(cls, *args: str) -> bool:
return False

@classmethod
def is_any_empty(cls, *args: str) -> bool:
def is_any_empty(cls, *args: Optional[str]) -> bool:
if ArrayUtils.is_empty(args):
return False

Expand All @@ -91,7 +179,7 @@ def is_any_empty(cls, *args: str) -> bool:
return False

@classmethod
def is_ascii_printable(cls, char_sequence: str) -> bool:
def is_ascii_printable(cls, char_sequence: Optional[str]) -> bool:
if char_sequence is None:
return False

Expand All @@ -101,23 +189,71 @@ def is_ascii_printable(cls, char_sequence: str) -> bool:
return True

@classmethod
def is_blank(cls, char_sequence: str) -> bool:
def is_blank(cls, char_sequence: Optional[str]) -> bool:
length: int = cls.length(char_sequence)
if 0 == length:
return True

for char in char_sequence:
for char in typing.cast(str, char_sequence):
if not CharUtils.is_whitespace(cls.to_character(char)):
return False
return True

@classmethod
def is_empty(cls, cs: str) -> bool:
return cs is None or len(cs) == 0
def is_empty(cls, char_sequence: Optional[str]) -> bool:
return char_sequence is None or len(char_sequence) == 0

@classmethod
def length(cls, cs: str) -> int:
return 0 if cs is None else len(cs)
def is_not_blank(cls, char_sequence: Optional[str]) -> bool:
return not cls.is_blank(char_sequence)

@classmethod
def is_not_empty(cls, char_sequence: Optional[str]) -> bool:
return not cls.is_empty(char_sequence)

@classmethod
def length(cls, char_sequence: Optional[str]) -> int:
return 0 if char_sequence is None else len(char_sequence)

@classmethod
def region_matches(
cls,
char_sequence: str,
ignore_case: bool,
this_start: int,
sub_string: str,
start: int,
length: int,
) -> bool:
index1 = this_start
index2 = start
tmp_length = length
src_length = len(char_sequence) - this_start
other_length = len(sub_string) - start

if this_start >= 0 and start >= 0 and length >= 0:
if src_length >= length and other_length >= length:
while tmp_length > 0:

c1 = char_sequence[index1]
c2 = sub_string[index2]

index1 += 1
index2 += 1

if c1 != c2:
if not ignore_case:
return False

u1 = CharUtils.to_uppercase(c1)
u2 = CharUtils.to_uppercase(c2)
if u1 != u2 and CharUtils.to_lowercase(u1) != CharUtils.to_lowercase(u2):
return False

tmp_length -= 1
return True
return False
return False

@classmethod
def to_character(cls, c: str) -> Optional[Char]:
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ reports = "y"
include-ids = "yes"
msg-template = "{msg_id}:{line:3d},{column}: {obj}: {msg}"
disable = "C0103, C0116, C0114, C0115"
max-public-methods = 50
max-args = 8
max-locals = 20

[tool.pytest.ini_options]
addopts = "--cov=pycommons --cov-branch --cov-report term-missing -vv --color=yes --cov-fail-under 40"
Expand Down