codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import dataclasses4import enum5import typing6 7if typing.TYPE_CHECKING:8 from typing_extensions import Literal9 10 11# define some names for these constants to avoid misspellings in the source12# and to document the constant strings we are using13 14 15class Sizing(str, enum.Enum):16 """Widget sizing methods."""17 18 FLOW = "flow"19 BOX = "box"20 FIXED = "fixed"21 22 23class Align(str, enum.Enum):24 """Text alignment modes"""25 26 LEFT = "left"27 RIGHT = "right"28 CENTER = "center"29 30 31class VAlign(str, enum.Enum):32 """Filler alignment"""33 34 TOP = "top"35 MIDDLE = "middle"36 BOTTOM = "bottom"37 38 39class WrapMode(str, enum.Enum):40 """Text wrapping modes"""41 42 SPACE = "space"43 ANY = "any"44 CLIP = "clip"45 ELLIPSIS = "ellipsis"46 47 48class WHSettings(str, enum.Enum):49 """Width and Height settings"""50 51 PACK = "pack"52 GIVEN = "given"53 RELATIVE = "relative"54 WEIGHT = "weight"55 CLIP = "clip" # Used as "given" for widgets with fixed width (with clipping part of it)56 FLOW = "flow" # Used as pack for flow widgets57 58 59RELATIVE_100 = (WHSettings.RELATIVE, 100)60 61 62@typing.overload63def normalize_align(64 align: Literal["left", "center", "right"] | Align,65 err: type[BaseException],66) -> tuple[Align, None]: ...67 68 69@typing.overload70def normalize_align(71 align: tuple[Literal["relative", WHSettings.RELATIVE], int],72 err: type[BaseException],73) -> tuple[Literal[WHSettings.RELATIVE], int]: ...74 75 76def normalize_align(77 align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int],78 err: type[BaseException],79) -> tuple[Align, None] | tuple[Literal[WHSettings.RELATIVE], int]:80 """81 Split align into (align_type, align_amount). Raise exception err82 if align doesn't match a valid alignment.83 """84 if align in {Align.LEFT, Align.CENTER, Align.RIGHT}:85 return (Align(align), None)86 87 if isinstance(align, tuple) and len(align) == 2 and align[0] == WHSettings.RELATIVE:88 _align_type, align_amount = align89 return (WHSettings.RELATIVE, align_amount)90 91 raise err(92 f"align value {align!r} is not one of 'left', 'center', 'right', ('relative', percentage 0=left 100=right)"93 )94 95 96@typing.overload97def simplify_align(98 align_type: Literal["relative", WHSettings.RELATIVE],99 align_amount: int,100) -> tuple[Literal[WHSettings.RELATIVE], int]: ...101 102 103@typing.overload104def simplify_align(105 align_type: Literal["relative", WHSettings.RELATIVE],106 align_amount: None,107) -> typing.NoReturn: ...108 109 110@typing.overload111def simplify_align(112 align_type: Literal["left", "center", "right"] | Align,113 align_amount: int | None,114) -> Align: ...115 116 117def simplify_align(118 align_type: Literal["left", "center", "right", "relative", WHSettings.RELATIVE] | Align,119 align_amount: int | None,120) -> Align | tuple[Literal[WHSettings.RELATIVE], int]:121 """122 Recombine (align_type, align_amount) into an align value.123 Inverse of normalize_align.124 """125 if align_type == WHSettings.RELATIVE:126 if not isinstance(align_amount, int):127 raise TypeError(align_amount)128 129 return (WHSettings.RELATIVE, align_amount)130 return Align(align_type)131 132 133@typing.overload134def normalize_valign(135 valign: Literal["top", "middle", "bottom"] | VAlign,136 err: type[BaseException],137) -> tuple[VAlign, None]: ...138 139 140@typing.overload141def normalize_valign(142 valign: tuple[Literal["relative", WHSettings.RELATIVE], int],143 err: type[BaseException],144) -> tuple[Literal[WHSettings.RELATIVE], int]: ...145 146 147def normalize_valign(148 valign: Literal["top", "middle", "bottom"] | VAlign | tuple[Literal["relative", WHSettings.RELATIVE], int],149 err: type[BaseException],150) -> tuple[VAlign, None] | tuple[Literal[WHSettings.RELATIVE], int]:151 """152 Split align into (valign_type, valign_amount). Raise exception err153 if align doesn't match a valid alignment.154 """155 if valign in {VAlign.TOP, VAlign.MIDDLE, VAlign.BOTTOM}:156 return (VAlign(valign), None)157 158 if isinstance(valign, tuple) and len(valign) == 2 and valign[0] == WHSettings.RELATIVE:159 _valign_type, valign_amount = valign160 return (WHSettings.RELATIVE, valign_amount)161 162 raise err(163 f"valign value {valign!r} is not one of 'top', 'middle', 'bottom', ('relative', percentage 0=left 100=right)"164 )165 166 167@typing.overload168def simplify_valign(169 valign_type: Literal["top", "middle", "bottom"] | VAlign,170 valign_amount: int | None,171) -> VAlign: ...172 173 174@typing.overload175def simplify_valign(176 valign_type: Literal["relative", WHSettings.RELATIVE],177 valign_amount: int,178) -> tuple[Literal[WHSettings.RELATIVE], int]: ...179 180 181@typing.overload182def simplify_valign(183 valign_type: Literal["relative", WHSettings.RELATIVE],184 valign_amount: None,185) -> typing.NoReturn: ...186 187 188def simplify_valign(189 valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,190 valign_amount: int | None,191) -> VAlign | tuple[Literal[WHSettings.RELATIVE], int]:192 """193 Recombine (valign_type, valign_amount) into an valign value.194 Inverse of normalize_valign.195 """196 if valign_type == WHSettings.RELATIVE:197 if not isinstance(valign_amount, int):198 raise TypeError(valign_amount)199 return (WHSettings.RELATIVE, valign_amount)200 return VAlign(valign_type)201 202 203@typing.overload204def normalize_width(205 width: Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK],206 err: type[BaseException],207) -> tuple[Literal[WHSettings.CLIP, WHSettings.PACK], None]: ...208 209 210@typing.overload211def normalize_width(212 width: int,213 err: type[BaseException],214) -> tuple[Literal[WHSettings.GIVEN], int]: ...215 216 217@typing.overload218def normalize_width(219 width: tuple[Literal["relative", WHSettings.RELATIVE], int],220 err: type[BaseException],221) -> tuple[Literal[WHSettings.RELATIVE], int]: ...222 223 224@typing.overload225def normalize_width(226 width: tuple[Literal["weight", WHSettings.WEIGHT], int | float],227 err: type[BaseException],228) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...229 230 231def normalize_width(232 width: (233 Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]234 | int235 | tuple[Literal["relative", WHSettings.RELATIVE], int]236 | tuple[Literal["weight", WHSettings.WEIGHT], int | float]237 ),238 err: type[BaseException],239) -> (240 tuple[Literal[WHSettings.CLIP, WHSettings.PACK], None]241 | tuple[Literal[WHSettings.GIVEN, WHSettings.RELATIVE], int]242 | tuple[Literal[WHSettings.WEIGHT], int | float]243):244 """245 Split width into (width_type, width_amount). Raise exception err246 if width doesn't match a valid alignment.247 """248 if width in {WHSettings.CLIP, WHSettings.PACK}:249 return (WHSettings(width), None)250 251 if isinstance(width, int):252 return (WHSettings.GIVEN, width)253 254 if isinstance(width, tuple) and len(width) == 2 and width[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:255 width_type, width_amount = width256 return (WHSettings(width_type), width_amount)257 258 raise err(259 f"width value {width!r} is not one of"260 f"fixed number of columns, 'pack', ('relative', percentage of total width), 'clip'"261 )262 263 264@typing.overload265def simplify_width(266 width_type: Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK],267 width_amount: int | None,268) -> Literal[WHSettings.CLIP, WHSettings.PACK]: ...269 270 271@typing.overload272def simplify_width(273 width_type: Literal["given", WHSettings.GIVEN],274 width_amount: int,275) -> int: ...276 277 278@typing.overload279def simplify_width(280 width_type: Literal["relative", WHSettings.RELATIVE],281 width_amount: int,282) -> tuple[Literal[WHSettings.RELATIVE], int]: ...283 284 285@typing.overload286def simplify_width(287 width_type: Literal["weight", WHSettings.WEIGHT],288 width_amount: int | float, # noqa: PYI041 # provide explicit for IDEs289) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...290 291 292@typing.overload293def simplify_width(294 width_type: Literal["given", "relative", "weight", WHSettings.GIVEN, WHSettings.RELATIVE, WHSettings.WEIGHT],295 width_amount: None,296) -> typing.NoReturn: ...297 298 299def simplify_width(300 width_type: Literal["clip", "pack", "given", "relative", "weight"] | WHSettings,301 width_amount: int | float | None, # noqa: PYI041 # provide explicit for IDEs302) -> (303 Literal[WHSettings.CLIP, WHSettings.PACK]304 | int305 | tuple[Literal[WHSettings.RELATIVE], int]306 | tuple[Literal[WHSettings.WEIGHT], int | float]307):308 """309 Recombine (width_type, width_amount) into an width value.310 Inverse of normalize_width.311 """312 if width_type in {WHSettings.CLIP, WHSettings.PACK}:313 return WHSettings(width_type)314 315 if not isinstance(width_amount, int):316 raise TypeError(width_amount)317 318 if width_type == WHSettings.GIVEN:319 return width_amount320 321 return (WHSettings(width_type), width_amount)322 323 324@typing.overload325def normalize_height(326 height: int,327 err: type[BaseException],328) -> tuple[Literal[WHSettings.GIVEN], int]: ...329 330 331@typing.overload332def normalize_height(333 height: Literal["flow", "pack", Sizing.FLOW, WHSettings.PACK],334 err: type[BaseException],335) -> tuple[Literal[Sizing.FLOW, WHSettings.PACK], None]: ...336 337 338@typing.overload339def normalize_height(340 height: tuple[Literal["relative", WHSettings.RELATIVE], int],341 err: type[BaseException],342) -> tuple[Literal[WHSettings.RELATIVE], int]: ...343 344 345@typing.overload346def normalize_height(347 height: tuple[Literal["weight", WHSettings.WEIGHT], int | float],348 err: type[BaseException],349) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...350 351 352def normalize_height(353 height: (354 int355 | Literal["flow", "pack", Sizing.FLOW, WHSettings.PACK]356 | tuple[Literal["relative", WHSettings.RELATIVE], int]357 | tuple[Literal["weight", WHSettings.WEIGHT], int | float]358 ),359 err: type[BaseException],360) -> (361 tuple[Literal[Sizing.FLOW, WHSettings.PACK], None]362 | tuple[Literal[WHSettings.RELATIVE, WHSettings.GIVEN], int]363 | tuple[Literal[WHSettings.WEIGHT], int | float]364):365 """366 Split height into (height_type, height_amount). Raise exception err367 if height isn't valid.368 """369 if height == Sizing.FLOW:370 return (Sizing.FLOW, None)371 372 if height == WHSettings.PACK:373 return (WHSettings.PACK, None)374 375 if isinstance(height, tuple) and len(height) == 2 and height[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:376 return (WHSettings(height[0]), height[1])377 378 if isinstance(height, int):379 return (WHSettings.GIVEN, height)380 381 raise err(382 f"height value {height!r} is not one of "383 f"fixed number of columns, 'pack', ('relative', percentage of total height)"384 )385 386 387@typing.overload388def simplify_height(389 height_type: Literal["flow", "pack", WHSettings.FLOW, WHSettings.PACK],390 height_amount: int | None,391) -> Literal[WHSettings.FLOW, WHSettings.PACK]: ...392 393 394@typing.overload395def simplify_height(396 height_type: Literal["given", WHSettings.GIVEN],397 height_amount: int,398) -> int: ...399 400 401@typing.overload402def simplify_height(403 height_type: Literal["relative", WHSettings.RELATIVE],404 height_amount: int | None,405) -> tuple[Literal[WHSettings.RELATIVE], int]: ...406 407 408@typing.overload409def simplify_height(410 height_type: Literal["weight", WHSettings.WEIGHT],411 height_amount: int | float | None, # noqa: PYI041 # provide explicit for IDEs412) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...413 414 415@typing.overload416def simplify_height(417 height_type: Literal["relative", "given", "weight", WHSettings.RELATIVE, WHSettings.GIVEN, WHSettings.WEIGHT],418 height_amount: None,419) -> typing.NoReturn: ...420 421 422def simplify_height(423 height_type: Literal[424 "flow",425 "pack",426 "relative",427 "given",428 "weight",429 WHSettings.FLOW,430 WHSettings.PACK,431 WHSettings.RELATIVE,432 WHSettings.GIVEN,433 WHSettings.WEIGHT,434 ],435 height_amount: int | float | None, # noqa: PYI041 # provide explicit for IDEs436) -> (437 int438 | Literal[WHSettings.FLOW, WHSettings.PACK]439 | tuple[Literal[WHSettings.RELATIVE], int]440 | tuple[Literal[WHSettings.WEIGHT], int | float]441):442 """443 Recombine (height_type, height_amount) into a height value.444 Inverse of normalize_height.445 """446 if height_type in {WHSettings.FLOW, WHSettings.PACK}:447 return WHSettings(height_type)448 449 if not isinstance(height_amount, int):450 raise TypeError(height_amount)451 452 if height_type == WHSettings.GIVEN:453 return height_amount454 455 return (WHSettings(height_type), height_amount)456 457 458@dataclasses.dataclass(frozen=True)459class _BoxSymbols:460 """Box symbols for drawing."""461 462 HORIZONTAL: str463 VERTICAL: str464 TOP_LEFT: str465 TOP_RIGHT: str466 BOTTOM_LEFT: str467 BOTTOM_RIGHT: str468 # Joints for tables making469 LEFT_T: str470 RIGHT_T: str471 TOP_T: str472 BOTTOM_T: str473 CROSS: str474 475 476@dataclasses.dataclass(frozen=True)477class _BoxSymbolsWithDashes(_BoxSymbols):478 """Box symbols for drawing.479 480 Extra dashes symbols.481 """482 483 HORIZONTAL_4_DASHES: str484 HORIZONTAL_3_DASHES: str485 HORIZONTAL_2_DASHES: str486 VERTICAL_2_DASH: str487 VERTICAL_3_DASH: str488 VERTICAL_4_DASH: str489 490 491@dataclasses.dataclass(frozen=True)492class _LightBoxSymbols(_BoxSymbolsWithDashes):493 """Box symbols for drawing.494 495 The Thin version includes extra symbols.496 Symbols are ordered as in Unicode except dashes.497 """498 499 TOP_LEFT_ROUNDED: str500 TOP_RIGHT_ROUNDED: str501 BOTTOM_LEFT_ROUNDED: str502 BOTTOM_RIGHT_ROUNDED: str503 504 505class _BoxSymbolsCollection(typing.NamedTuple):506 """Standard Unicode box symbols for basic tables drawing.507 508 .. note::509 Transitions are not included: depends on line types, different kinds of transitions are available.510 Please check Unicode table for transitions symbols if required.511 """512 513 # fmt: off514 515 LIGHT: _LightBoxSymbols = _LightBoxSymbols(516 "─", "│", "┌", "┐", "└", "┘", "├", "┤", "┬", "┴", "┼", "┈", "┄", "╌", "╎", "┆", "┊", "╭", "╮", "╰", "╯"517 )518 HEAVY: _BoxSymbolsWithDashes = _BoxSymbolsWithDashes(519 "━", "┃", "┏", "┓", "┗", "┛", "┣", "┫", "┳", "┻", "╋", "┉", "┅", "╍", "╏", "┇", "┋"520 )521 DOUBLE: _BoxSymbols = _BoxSymbols(522 "═", "║", "╔", "╗", "╚", "╝", "╠", "╣", "╦", "╩", "╬"523 )524 525 526BOX_SYMBOLS = _BoxSymbolsCollection()527 528 529class BAR_SYMBOLS(str, enum.Enum):530 """Standard Unicode bar symbols excluding empty space.531 532 Start from space (0), then 1/8 till full block (1/1).533 Typically used only 8 from this symbol collection depends on use-case:534 * empty - 7/8 and styles for BG different on both sides (like standard `ProgressBar` and `BarGraph`)535 * 1/8 - full block and single style for BG on the right side536 """537 538 # fmt: off539 540 HORISONTAL = " ▏▎▍▌▋▊▉█"541 VERTICAL = " ▁▂▃▄▅▆▇█"542 543 544class _SHADE_SYMBOLS(typing.NamedTuple):545 """Standard shade symbols excluding empty space."""546 547 # fmt: off548 549 FULL_BLOCK: str = "█"550 DARK_SHADE: str = "▓"551 MEDIUM_SHADE: str = "▒"552 LITE_SHADE: str = "░"553 554 555SHADE_SYMBOLS = _SHADE_SYMBOLS()556 