Development Artifact Cleanup: ✅ BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location - dev/test-nodes/brother_node/: Moved from root directory for better organization - Contains development configuration, test logs, and test chain data - No impact on production systems - purely development/testing artifact ✅ DEVELOPMENT ARTIFACTS IDENTIFIED: - Chain ID: aitbc-brother-chain (test/development chain) - Ports: 8010 (P2P) and 8011 (RPC) - different from production - Environment: .env file with test configuration - Logs: rpc.log and node.log from development testing session (March 15, 2026) ✅ ROOT DIRECTORY CLEANUP: Removed development clutter from production directory - brother_node/ moved to dev/test-nodes/brother_node/ - Root directory now contains only production-ready components - Development artifacts properly organized in dev/ subdirectory DIRECTORY STRUCTURE IMPROVEMENT: 📁 dev/test-nodes/: Development and testing node configurations 🏗️ Root Directory: Clean production structure with only essential components 🧪 Development Isolation: Test environments separated from production BENEFITS: ✅ Clean Production Directory: No development artifacts in root ✅ Better Organization: Development nodes grouped in dev/ subdirectory ✅ Clear Separation: Production vs development environments clearly distinguished ✅ Maintainability: Easier to identify and manage development components RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
318 lines
11 KiB
Python
Executable File
318 lines
11 KiB
Python
Executable File
from typing import TYPE_CHECKING, Optional
|
|
|
|
from .align import AlignMethod
|
|
from .box import ROUNDED, Box
|
|
from .cells import cell_len
|
|
from .jupyter import JupyterMixin
|
|
from .measure import Measurement, measure_renderables
|
|
from .padding import Padding, PaddingDimensions
|
|
from .segment import Segment
|
|
from .style import Style, StyleType
|
|
from .text import Text, TextType
|
|
|
|
if TYPE_CHECKING:
|
|
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
|
|
|
|
class Panel(JupyterMixin):
|
|
"""A console renderable that draws a border around its contents.
|
|
|
|
Example:
|
|
>>> console.print(Panel("Hello, World!"))
|
|
|
|
Args:
|
|
renderable (RenderableType): A console renderable object.
|
|
box (Box): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
|
|
title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None.
|
|
title_align (AlignMethod, optional): Alignment of title. Defaults to "center".
|
|
subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None.
|
|
subtitle_align (AlignMethod, optional): Alignment of subtitle. Defaults to "center".
|
|
safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
|
|
expand (bool, optional): If True the panel will stretch to fill the console width, otherwise it will be sized to fit the contents. Defaults to True.
|
|
style (str, optional): The style of the panel (border and contents). Defaults to "none".
|
|
border_style (str, optional): The style of the border. Defaults to "none".
|
|
width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
|
|
height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
|
|
padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
|
|
highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
renderable: "RenderableType",
|
|
box: Box = ROUNDED,
|
|
*,
|
|
title: Optional[TextType] = None,
|
|
title_align: AlignMethod = "center",
|
|
subtitle: Optional[TextType] = None,
|
|
subtitle_align: AlignMethod = "center",
|
|
safe_box: Optional[bool] = None,
|
|
expand: bool = True,
|
|
style: StyleType = "none",
|
|
border_style: StyleType = "none",
|
|
width: Optional[int] = None,
|
|
height: Optional[int] = None,
|
|
padding: PaddingDimensions = (0, 1),
|
|
highlight: bool = False,
|
|
) -> None:
|
|
self.renderable = renderable
|
|
self.box = box
|
|
self.title = title
|
|
self.title_align: AlignMethod = title_align
|
|
self.subtitle = subtitle
|
|
self.subtitle_align = subtitle_align
|
|
self.safe_box = safe_box
|
|
self.expand = expand
|
|
self.style = style
|
|
self.border_style = border_style
|
|
self.width = width
|
|
self.height = height
|
|
self.padding = padding
|
|
self.highlight = highlight
|
|
|
|
@classmethod
|
|
def fit(
|
|
cls,
|
|
renderable: "RenderableType",
|
|
box: Box = ROUNDED,
|
|
*,
|
|
title: Optional[TextType] = None,
|
|
title_align: AlignMethod = "center",
|
|
subtitle: Optional[TextType] = None,
|
|
subtitle_align: AlignMethod = "center",
|
|
safe_box: Optional[bool] = None,
|
|
style: StyleType = "none",
|
|
border_style: StyleType = "none",
|
|
width: Optional[int] = None,
|
|
height: Optional[int] = None,
|
|
padding: PaddingDimensions = (0, 1),
|
|
highlight: bool = False,
|
|
) -> "Panel":
|
|
"""An alternative constructor that sets expand=False."""
|
|
return cls(
|
|
renderable,
|
|
box,
|
|
title=title,
|
|
title_align=title_align,
|
|
subtitle=subtitle,
|
|
subtitle_align=subtitle_align,
|
|
safe_box=safe_box,
|
|
style=style,
|
|
border_style=border_style,
|
|
width=width,
|
|
height=height,
|
|
padding=padding,
|
|
highlight=highlight,
|
|
expand=False,
|
|
)
|
|
|
|
@property
|
|
def _title(self) -> Optional[Text]:
|
|
if self.title:
|
|
title_text = (
|
|
Text.from_markup(self.title)
|
|
if isinstance(self.title, str)
|
|
else self.title.copy()
|
|
)
|
|
title_text.end = ""
|
|
title_text.plain = title_text.plain.replace("\n", " ")
|
|
title_text.no_wrap = True
|
|
title_text.expand_tabs()
|
|
title_text.pad(1)
|
|
return title_text
|
|
return None
|
|
|
|
@property
|
|
def _subtitle(self) -> Optional[Text]:
|
|
if self.subtitle:
|
|
subtitle_text = (
|
|
Text.from_markup(self.subtitle)
|
|
if isinstance(self.subtitle, str)
|
|
else self.subtitle.copy()
|
|
)
|
|
subtitle_text.end = ""
|
|
subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
|
|
subtitle_text.no_wrap = True
|
|
subtitle_text.expand_tabs()
|
|
subtitle_text.pad(1)
|
|
return subtitle_text
|
|
return None
|
|
|
|
def __rich_console__(
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
) -> "RenderResult":
|
|
_padding = Padding.unpack(self.padding)
|
|
renderable = (
|
|
Padding(self.renderable, _padding) if any(_padding) else self.renderable
|
|
)
|
|
style = console.get_style(self.style)
|
|
border_style = style + console.get_style(self.border_style)
|
|
width = (
|
|
options.max_width
|
|
if self.width is None
|
|
else min(options.max_width, self.width)
|
|
)
|
|
|
|
safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
|
|
box = self.box.substitute(options, safe=safe_box)
|
|
|
|
def align_text(
|
|
text: Text, width: int, align: str, character: str, style: Style
|
|
) -> Text:
|
|
"""Gets new aligned text.
|
|
|
|
Args:
|
|
text (Text): Title or subtitle text.
|
|
width (int): Desired width.
|
|
align (str): Alignment.
|
|
character (str): Character for alignment.
|
|
style (Style): Border style
|
|
|
|
Returns:
|
|
Text: New text instance
|
|
"""
|
|
text = text.copy()
|
|
text.truncate(width)
|
|
excess_space = width - cell_len(text.plain)
|
|
if text.style:
|
|
text.stylize(console.get_style(text.style))
|
|
|
|
if excess_space:
|
|
if align == "left":
|
|
return Text.assemble(
|
|
text,
|
|
(character * excess_space, style),
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
elif align == "center":
|
|
left = excess_space // 2
|
|
return Text.assemble(
|
|
(character * left, style),
|
|
text,
|
|
(character * (excess_space - left), style),
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
else:
|
|
return Text.assemble(
|
|
(character * excess_space, style),
|
|
text,
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
return text
|
|
|
|
title_text = self._title
|
|
if title_text is not None:
|
|
title_text.stylize_before(border_style)
|
|
|
|
child_width = (
|
|
width - 2
|
|
if self.expand
|
|
else console.measure(
|
|
renderable, options=options.update_width(width - 2)
|
|
).maximum
|
|
)
|
|
child_height = self.height or options.height or None
|
|
if child_height:
|
|
child_height -= 2
|
|
if title_text is not None:
|
|
child_width = min(
|
|
options.max_width - 2, max(child_width, title_text.cell_len + 2)
|
|
)
|
|
|
|
width = child_width + 2
|
|
child_options = options.update(
|
|
width=child_width, height=child_height, highlight=self.highlight
|
|
)
|
|
lines = console.render_lines(renderable, child_options, style=style)
|
|
|
|
line_start = Segment(box.mid_left, border_style)
|
|
line_end = Segment(f"{box.mid_right}", border_style)
|
|
new_line = Segment.line()
|
|
if title_text is None or width <= 4:
|
|
yield Segment(box.get_top([width - 2]), border_style)
|
|
else:
|
|
title_text = align_text(
|
|
title_text,
|
|
width - 4,
|
|
self.title_align,
|
|
box.top,
|
|
border_style,
|
|
)
|
|
yield Segment(box.top_left + box.top, border_style)
|
|
yield from console.render(title_text, child_options.update_width(width - 4))
|
|
yield Segment(box.top + box.top_right, border_style)
|
|
|
|
yield new_line
|
|
for line in lines:
|
|
yield line_start
|
|
yield from line
|
|
yield line_end
|
|
yield new_line
|
|
|
|
subtitle_text = self._subtitle
|
|
if subtitle_text is not None:
|
|
subtitle_text.stylize_before(border_style)
|
|
|
|
if subtitle_text is None or width <= 4:
|
|
yield Segment(box.get_bottom([width - 2]), border_style)
|
|
else:
|
|
subtitle_text = align_text(
|
|
subtitle_text,
|
|
width - 4,
|
|
self.subtitle_align,
|
|
box.bottom,
|
|
border_style,
|
|
)
|
|
yield Segment(box.bottom_left + box.bottom, border_style)
|
|
yield from console.render(
|
|
subtitle_text, child_options.update_width(width - 4)
|
|
)
|
|
yield Segment(box.bottom + box.bottom_right, border_style)
|
|
|
|
yield new_line
|
|
|
|
def __rich_measure__(
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
) -> "Measurement":
|
|
_title = self._title
|
|
_, right, _, left = Padding.unpack(self.padding)
|
|
padding = left + right
|
|
renderables = [self.renderable, _title] if _title else [self.renderable]
|
|
|
|
if self.width is None:
|
|
width = (
|
|
measure_renderables(
|
|
console,
|
|
options.update_width(options.max_width - padding - 2),
|
|
renderables,
|
|
).maximum
|
|
+ padding
|
|
+ 2
|
|
)
|
|
else:
|
|
width = self.width
|
|
return Measurement(width, width)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
from .console import Console
|
|
|
|
c = Console()
|
|
|
|
from .box import DOUBLE, ROUNDED
|
|
from .padding import Padding
|
|
|
|
p = Panel(
|
|
"Hello, World!",
|
|
title="rich.Panel",
|
|
style="white on blue",
|
|
box=DOUBLE,
|
|
padding=1,
|
|
)
|
|
|
|
c.print()
|
|
c.print(p)
|