titan-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- titan_cli/__init__.py +3 -0
- titan_cli/__main__.py +4 -0
- titan_cli/ai/__init__.py +0 -0
- titan_cli/ai/agents/__init__.py +15 -0
- titan_cli/ai/agents/base.py +152 -0
- titan_cli/ai/client.py +170 -0
- titan_cli/ai/constants.py +56 -0
- titan_cli/ai/exceptions.py +48 -0
- titan_cli/ai/models.py +34 -0
- titan_cli/ai/oauth_helper.py +120 -0
- titan_cli/ai/providers/__init__.py +9 -0
- titan_cli/ai/providers/anthropic.py +117 -0
- titan_cli/ai/providers/base.py +75 -0
- titan_cli/ai/providers/gemini.py +278 -0
- titan_cli/cli.py +59 -0
- titan_cli/clients/__init__.py +1 -0
- titan_cli/clients/gcloud_client.py +52 -0
- titan_cli/core/__init__.py +3 -0
- titan_cli/core/config.py +274 -0
- titan_cli/core/discovery.py +51 -0
- titan_cli/core/errors.py +81 -0
- titan_cli/core/models.py +52 -0
- titan_cli/core/plugins/available.py +36 -0
- titan_cli/core/plugins/models.py +67 -0
- titan_cli/core/plugins/plugin_base.py +108 -0
- titan_cli/core/plugins/plugin_registry.py +163 -0
- titan_cli/core/secrets.py +141 -0
- titan_cli/core/workflows/__init__.py +22 -0
- titan_cli/core/workflows/models.py +88 -0
- titan_cli/core/workflows/project_step_source.py +86 -0
- titan_cli/core/workflows/workflow_exceptions.py +17 -0
- titan_cli/core/workflows/workflow_filter_service.py +137 -0
- titan_cli/core/workflows/workflow_registry.py +419 -0
- titan_cli/core/workflows/workflow_sources.py +307 -0
- titan_cli/engine/__init__.py +39 -0
- titan_cli/engine/builder.py +159 -0
- titan_cli/engine/context.py +82 -0
- titan_cli/engine/mock_context.py +176 -0
- titan_cli/engine/results.py +91 -0
- titan_cli/engine/steps/ai_assistant_step.py +185 -0
- titan_cli/engine/steps/command_step.py +93 -0
- titan_cli/engine/utils/__init__.py +3 -0
- titan_cli/engine/utils/venv.py +31 -0
- titan_cli/engine/workflow_executor.py +187 -0
- titan_cli/external_cli/__init__.py +0 -0
- titan_cli/external_cli/configs.py +17 -0
- titan_cli/external_cli/launcher.py +65 -0
- titan_cli/messages.py +121 -0
- titan_cli/ui/tui/__init__.py +205 -0
- titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
- titan_cli/ui/tui/app.py +113 -0
- titan_cli/ui/tui/icons.py +70 -0
- titan_cli/ui/tui/screens/__init__.py +24 -0
- titan_cli/ui/tui/screens/ai_config.py +498 -0
- titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
- titan_cli/ui/tui/screens/base.py +110 -0
- titan_cli/ui/tui/screens/cli_launcher.py +151 -0
- titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
- titan_cli/ui/tui/screens/main_menu.py +162 -0
- titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
- titan_cli/ui/tui/screens/plugin_management.py +377 -0
- titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
- titan_cli/ui/tui/screens/workflow_execution.py +592 -0
- titan_cli/ui/tui/screens/workflows.py +249 -0
- titan_cli/ui/tui/textual_components.py +537 -0
- titan_cli/ui/tui/textual_workflow_executor.py +405 -0
- titan_cli/ui/tui/theme.py +102 -0
- titan_cli/ui/tui/widgets/__init__.py +40 -0
- titan_cli/ui/tui/widgets/button.py +108 -0
- titan_cli/ui/tui/widgets/header.py +116 -0
- titan_cli/ui/tui/widgets/panel.py +81 -0
- titan_cli/ui/tui/widgets/status_bar.py +115 -0
- titan_cli/ui/tui/widgets/table.py +77 -0
- titan_cli/ui/tui/widgets/text.py +177 -0
- titan_cli/utils/__init__.py +0 -0
- titan_cli/utils/autoupdate.py +155 -0
- titan_cli-0.1.0.dist-info/METADATA +149 -0
- titan_cli-0.1.0.dist-info/RECORD +146 -0
- titan_cli-0.1.0.dist-info/WHEEL +4 -0
- titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
- titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- titan_plugin_git/__init__.py +1 -0
- titan_plugin_git/clients/__init__.py +8 -0
- titan_plugin_git/clients/git_client.py +772 -0
- titan_plugin_git/exceptions.py +40 -0
- titan_plugin_git/messages.py +112 -0
- titan_plugin_git/models.py +39 -0
- titan_plugin_git/plugin.py +118 -0
- titan_plugin_git/steps/__init__.py +1 -0
- titan_plugin_git/steps/ai_commit_message_step.py +171 -0
- titan_plugin_git/steps/branch_steps.py +104 -0
- titan_plugin_git/steps/commit_step.py +80 -0
- titan_plugin_git/steps/push_step.py +63 -0
- titan_plugin_git/steps/status_step.py +59 -0
- titan_plugin_git/workflows/__previews__/__init__.py +1 -0
- titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
- titan_plugin_git/workflows/commit-ai.yaml +28 -0
- titan_plugin_github/__init__.py +11 -0
- titan_plugin_github/agents/__init__.py +6 -0
- titan_plugin_github/agents/config_loader.py +130 -0
- titan_plugin_github/agents/issue_generator.py +353 -0
- titan_plugin_github/agents/pr_agent.py +528 -0
- titan_plugin_github/clients/__init__.py +8 -0
- titan_plugin_github/clients/github_client.py +1105 -0
- titan_plugin_github/config/__init__.py +0 -0
- titan_plugin_github/config/pr_agent.toml +85 -0
- titan_plugin_github/exceptions.py +28 -0
- titan_plugin_github/messages.py +88 -0
- titan_plugin_github/models.py +330 -0
- titan_plugin_github/plugin.py +131 -0
- titan_plugin_github/steps/__init__.py +12 -0
- titan_plugin_github/steps/ai_pr_step.py +172 -0
- titan_plugin_github/steps/create_pr_step.py +86 -0
- titan_plugin_github/steps/github_prompt_steps.py +171 -0
- titan_plugin_github/steps/issue_steps.py +143 -0
- titan_plugin_github/steps/preview_step.py +40 -0
- titan_plugin_github/utils.py +82 -0
- titan_plugin_github/workflows/__previews__/__init__.py +1 -0
- titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
- titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
- titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
- titan_plugin_jira/__init__.py +8 -0
- titan_plugin_jira/agents/__init__.py +6 -0
- titan_plugin_jira/agents/config_loader.py +154 -0
- titan_plugin_jira/agents/jira_agent.py +553 -0
- titan_plugin_jira/agents/prompts.py +364 -0
- titan_plugin_jira/agents/response_parser.py +435 -0
- titan_plugin_jira/agents/token_tracker.py +223 -0
- titan_plugin_jira/agents/validators.py +246 -0
- titan_plugin_jira/clients/jira_client.py +745 -0
- titan_plugin_jira/config/jira_agent.toml +92 -0
- titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
- titan_plugin_jira/exceptions.py +37 -0
- titan_plugin_jira/formatters/__init__.py +6 -0
- titan_plugin_jira/formatters/markdown_formatter.py +245 -0
- titan_plugin_jira/messages.py +115 -0
- titan_plugin_jira/models.py +89 -0
- titan_plugin_jira/plugin.py +264 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
- titan_plugin_jira/steps/get_issue_step.py +82 -0
- titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
- titan_plugin_jira/steps/search_saved_query_step.py +238 -0
- titan_plugin_jira/utils/__init__.py +13 -0
- titan_plugin_jira/utils/issue_sorter.py +140 -0
- titan_plugin_jira/utils/saved_queries.py +150 -0
- titan_plugin_jira/workflows/analyze-jira-issues.yaml +34 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Header Widget
|
|
3
|
+
|
|
4
|
+
Custom header widget with title and back button.
|
|
5
|
+
"""
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.widget import Widget
|
|
8
|
+
from textual.widgets import Static
|
|
9
|
+
from textual.containers import Horizontal
|
|
10
|
+
from textual.reactive import reactive
|
|
11
|
+
from textual.message import Message
|
|
12
|
+
|
|
13
|
+
from titan_cli.ui.tui.icons import Icons
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HeaderWidget(Widget):
|
|
17
|
+
"""
|
|
18
|
+
Header widget that displays screen title and back button.
|
|
19
|
+
|
|
20
|
+
Shows:
|
|
21
|
+
- Left: Back button (← Back)
|
|
22
|
+
- Center: Screen title
|
|
23
|
+
- Right: Empty (for symmetry)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Reactive property for title
|
|
27
|
+
title: reactive[str] = reactive("Titan CLI")
|
|
28
|
+
|
|
29
|
+
DEFAULT_CSS = """
|
|
30
|
+
HeaderWidget {
|
|
31
|
+
background: $surface-lighten-1;
|
|
32
|
+
height: 3;
|
|
33
|
+
width: 100%;
|
|
34
|
+
dock: top;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
HeaderWidget Horizontal {
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
align: center middle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
HeaderWidget #header-back {
|
|
44
|
+
width: auto;
|
|
45
|
+
min-width: 15;
|
|
46
|
+
height: 100%;
|
|
47
|
+
background: transparent;
|
|
48
|
+
color: $primary;
|
|
49
|
+
text-align: left;
|
|
50
|
+
padding: 0 2;
|
|
51
|
+
content-align: left middle;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
HeaderWidget #header-back:hover {
|
|
55
|
+
background: $surface-lighten-2;
|
|
56
|
+
text-style: bold;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
HeaderWidget #header-title {
|
|
60
|
+
width: 1fr;
|
|
61
|
+
height: 100%;
|
|
62
|
+
content-align: center middle;
|
|
63
|
+
text-align: center;
|
|
64
|
+
color: $primary;
|
|
65
|
+
text-style: bold;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
HeaderWidget .header-left,
|
|
69
|
+
HeaderWidget .header-right {
|
|
70
|
+
width: auto;
|
|
71
|
+
min-width: 15;
|
|
72
|
+
height: 100%;
|
|
73
|
+
}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, title: str = "Titan CLI", show_back: bool = True, **kwargs):
|
|
77
|
+
"""
|
|
78
|
+
Initialize header widget.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
title: Title to display in header
|
|
82
|
+
show_back: Whether to show back button
|
|
83
|
+
"""
|
|
84
|
+
super().__init__(**kwargs)
|
|
85
|
+
self.title = title
|
|
86
|
+
self.show_back = show_back
|
|
87
|
+
|
|
88
|
+
def compose(self) -> ComposeResult:
|
|
89
|
+
"""Compose the header with back button and title."""
|
|
90
|
+
with Horizontal():
|
|
91
|
+
if self.show_back:
|
|
92
|
+
yield Static(f"{Icons.BACK} Back", id="header-back", classes="header-left")
|
|
93
|
+
else:
|
|
94
|
+
yield Static("", classes="header-left")
|
|
95
|
+
|
|
96
|
+
yield Static(self.title, id="header-title")
|
|
97
|
+
yield Static("", classes="header-right")
|
|
98
|
+
|
|
99
|
+
def on_click(self, event) -> None:
|
|
100
|
+
"""Handle click on header elements."""
|
|
101
|
+
if event.widget.id == "header-back":
|
|
102
|
+
# Post a message to the screen to go back
|
|
103
|
+
self.post_message(self.BackPressed())
|
|
104
|
+
|
|
105
|
+
def watch_title(self, new_value: str) -> None:
|
|
106
|
+
"""Update title display when title changes."""
|
|
107
|
+
if self.is_mounted:
|
|
108
|
+
try:
|
|
109
|
+
title_widget = self.query_one("#header-title", Static)
|
|
110
|
+
title_widget.update(new_value)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
class BackPressed(Message):
|
|
115
|
+
"""Message sent when back button is pressed."""
|
|
116
|
+
pass
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Panel Widget
|
|
3
|
+
|
|
4
|
+
A bordered container for displaying important messages with different types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from textual.app import ComposeResult
|
|
8
|
+
from textual.widget import Widget
|
|
9
|
+
from textual.widgets import Label
|
|
10
|
+
from textual.containers import Container
|
|
11
|
+
|
|
12
|
+
from titan_cli.ui.tui.icons import Icons
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Panel(Widget):
|
|
16
|
+
"""Panel widget with border and type-based styling."""
|
|
17
|
+
|
|
18
|
+
DEFAULT_CSS = """
|
|
19
|
+
Panel {
|
|
20
|
+
width: auto;
|
|
21
|
+
height: auto;
|
|
22
|
+
margin: 0 0 1 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Panel > Container {
|
|
26
|
+
width: auto;
|
|
27
|
+
height: auto;
|
|
28
|
+
border: round $primary;
|
|
29
|
+
padding: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Panel.info > Container {
|
|
33
|
+
border: round $accent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Panel.success > Container {
|
|
37
|
+
border: round $success;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Panel.warning > Container {
|
|
41
|
+
border: round $warning;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Panel.error > Container {
|
|
45
|
+
border: round $error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Panel Label {
|
|
49
|
+
width: auto;
|
|
50
|
+
height: auto;
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, text: str, panel_type: str = "info", **kwargs):
|
|
55
|
+
"""
|
|
56
|
+
Initialize panel.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
text: Text to display
|
|
60
|
+
panel_type: Type of panel (info, success, warning, error)
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(**kwargs)
|
|
63
|
+
self.text = text
|
|
64
|
+
self.panel_type = panel_type
|
|
65
|
+
|
|
66
|
+
# Add CSS class based on type
|
|
67
|
+
self.add_class(panel_type)
|
|
68
|
+
|
|
69
|
+
def compose(self) -> ComposeResult:
|
|
70
|
+
"""Compose the panel with bordered container."""
|
|
71
|
+
# Map panel types to icons from Icons class
|
|
72
|
+
icons = {
|
|
73
|
+
"info": Icons.INFO,
|
|
74
|
+
"success": Icons.SUCCESS,
|
|
75
|
+
"warning": Icons.WARNING,
|
|
76
|
+
"error": Icons.ERROR,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
icon = icons.get(self.panel_type, Icons.INFO)
|
|
80
|
+
with Container():
|
|
81
|
+
yield Label(f"{icon} {self.text}")
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Status Bar Widget
|
|
3
|
+
|
|
4
|
+
Fixed status bar showing git branch, AI info, and active project.
|
|
5
|
+
"""
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.widget import Widget
|
|
8
|
+
from textual.widgets import Static
|
|
9
|
+
from textual.containers import Horizontal
|
|
10
|
+
from textual.reactive import reactive
|
|
11
|
+
|
|
12
|
+
class StatusBarWidget(Widget):
|
|
13
|
+
"""
|
|
14
|
+
Status bar widget that displays project information.
|
|
15
|
+
|
|
16
|
+
Shows:
|
|
17
|
+
- Left: Git branch
|
|
18
|
+
- Center: AI provider and model
|
|
19
|
+
- Right: Active project name
|
|
20
|
+
|
|
21
|
+
This widget is designed to be docked at the bottom of the screen.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Reactive properties - automatically update the widget when changed
|
|
25
|
+
git_branch: reactive[str] = reactive("N/A")
|
|
26
|
+
ai_info: reactive[str] = reactive("N/A")
|
|
27
|
+
project_name: reactive[str] = reactive("N/A")
|
|
28
|
+
|
|
29
|
+
DEFAULT_CSS = """
|
|
30
|
+
StatusBarWidget {
|
|
31
|
+
background: $surface-lighten-1;
|
|
32
|
+
color: white;
|
|
33
|
+
height: 1;
|
|
34
|
+
width: 100%;
|
|
35
|
+
dock: bottom;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
StatusBarWidget Horizontal {
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
StatusBarWidget Static {
|
|
43
|
+
width: 1fr;
|
|
44
|
+
height: 100%;
|
|
45
|
+
content-align: center middle;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
StatusBarWidget #branch-info {
|
|
49
|
+
text-align: left;
|
|
50
|
+
color: blue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
StatusBarWidget #ai-info {
|
|
54
|
+
text-align: center;
|
|
55
|
+
color: green;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
StatusBarWidget #project-info {
|
|
59
|
+
text-align: right;
|
|
60
|
+
color: orange;
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def compose(self) -> ComposeResult:
|
|
65
|
+
"""Compose the status bar with three columns."""
|
|
66
|
+
with Horizontal():
|
|
67
|
+
yield Static(f"{self.git_branch}", id="branch-info")
|
|
68
|
+
yield Static(f"{self.ai_info}", id="ai-info")
|
|
69
|
+
yield Static(f"{self.project_name}", id="project-info")
|
|
70
|
+
|
|
71
|
+
def _update_branch(self, value: str) -> None:
|
|
72
|
+
"""Update branch display."""
|
|
73
|
+
branch_widget = self.query_one("#branch-info", Static)
|
|
74
|
+
branch_widget.update(value)
|
|
75
|
+
|
|
76
|
+
def _update_ai(self, value: str) -> None:
|
|
77
|
+
"""Update AI display."""
|
|
78
|
+
ai_widget = self.query_one("#ai-info", Static)
|
|
79
|
+
ai_widget.update(value)
|
|
80
|
+
|
|
81
|
+
def _update_project(self, value: str) -> None:
|
|
82
|
+
"""Update project display."""
|
|
83
|
+
project_widget = self.query_one("#project-info", Static)
|
|
84
|
+
project_widget.update(value)
|
|
85
|
+
|
|
86
|
+
def watch_git_branch(self, new_value: str) -> None:
|
|
87
|
+
"""Update branch display when git_branch changes."""
|
|
88
|
+
if self.is_mounted:
|
|
89
|
+
self._update_branch(new_value)
|
|
90
|
+
|
|
91
|
+
def watch_ai_info(self, new_value: str) -> None:
|
|
92
|
+
"""Update AI display when ai_info changes."""
|
|
93
|
+
if self.is_mounted:
|
|
94
|
+
self._update_ai(new_value)
|
|
95
|
+
|
|
96
|
+
def watch_project_name(self, new_value: str) -> None:
|
|
97
|
+
"""Update project display when project_name changes."""
|
|
98
|
+
if self.is_mounted:
|
|
99
|
+
self._update_project(new_value)
|
|
100
|
+
|
|
101
|
+
def update_status(self, git_branch: str = None, ai_info: str = None, project_name: str = None):
|
|
102
|
+
"""
|
|
103
|
+
Update status bar information.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
git_branch: Git branch name
|
|
107
|
+
ai_info: AI provider/model info
|
|
108
|
+
project_name: Active project name
|
|
109
|
+
"""
|
|
110
|
+
if git_branch is not None:
|
|
111
|
+
self.git_branch = git_branch
|
|
112
|
+
if ai_info is not None:
|
|
113
|
+
self.ai_info = ai_info
|
|
114
|
+
if project_name is not None:
|
|
115
|
+
self.project_name = project_name
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Table Widget
|
|
3
|
+
|
|
4
|
+
A simple table widget for displaying tabular data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from textual.app import ComposeResult
|
|
9
|
+
from textual.widget import Widget
|
|
10
|
+
from textual.widgets import DataTable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Table(Widget):
|
|
14
|
+
"""Table widget for displaying rows and columns."""
|
|
15
|
+
|
|
16
|
+
DEFAULT_CSS = """
|
|
17
|
+
Table {
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: auto;
|
|
20
|
+
margin: 0 0 1 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Table.compact {
|
|
24
|
+
width: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Table > DataTable {
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: auto;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Table.compact > DataTable {
|
|
33
|
+
width: auto;
|
|
34
|
+
}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
headers: List[str],
|
|
40
|
+
rows: List[List[str]],
|
|
41
|
+
title: str = "",
|
|
42
|
+
full_width: bool = True,
|
|
43
|
+
**kwargs
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Initialize table.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
headers: List of column headers
|
|
50
|
+
rows: List of rows (each row is a list of cell values)
|
|
51
|
+
title: Optional title for the table
|
|
52
|
+
full_width: If False, table uses auto width (compact mode)
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(**kwargs)
|
|
55
|
+
self.headers = headers
|
|
56
|
+
self.rows = rows
|
|
57
|
+
self.title_text = title
|
|
58
|
+
|
|
59
|
+
# Add compact class if not full width
|
|
60
|
+
if not full_width:
|
|
61
|
+
self.add_class("compact")
|
|
62
|
+
|
|
63
|
+
def compose(self) -> ComposeResult:
|
|
64
|
+
"""Compose the table."""
|
|
65
|
+
table = DataTable()
|
|
66
|
+
if self.title_text:
|
|
67
|
+
table.border_title = self.title_text
|
|
68
|
+
|
|
69
|
+
# Add columns
|
|
70
|
+
for header in self.headers:
|
|
71
|
+
table.add_column(header)
|
|
72
|
+
|
|
73
|
+
# Add rows
|
|
74
|
+
for row in self.rows:
|
|
75
|
+
table.add_row(*row)
|
|
76
|
+
|
|
77
|
+
yield table
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Text Widgets
|
|
3
|
+
|
|
4
|
+
Reusable text widgets with theme-based styling.
|
|
5
|
+
"""
|
|
6
|
+
from textual.widgets import Static
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Shared CSS for all text styling - DRY principle
|
|
10
|
+
SHARED_TEXT_CSS = """
|
|
11
|
+
.dim, DimText, DimItalicText {
|
|
12
|
+
color: $text-muted;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.bold, BoldText, BoldPrimaryText {
|
|
16
|
+
text-style: bold;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.italic, ItalicText, DimItalicText {
|
|
20
|
+
text-style: italic;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.primary, PrimaryText, BoldPrimaryText {
|
|
24
|
+
color: $primary;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.success, SuccessText {
|
|
28
|
+
color: $success;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.error, ErrorText {
|
|
32
|
+
color: $error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.warning, WarningText {
|
|
36
|
+
color: $warning;
|
|
37
|
+
}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Text(Static):
|
|
42
|
+
"""
|
|
43
|
+
Reusable text widget with dynamic styling via CSS classes.
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
# Create with initial style
|
|
47
|
+
text = Text("Hello", style="bold")
|
|
48
|
+
|
|
49
|
+
# Change style dynamically
|
|
50
|
+
text.set_style("error")
|
|
51
|
+
|
|
52
|
+
# Combine multiple styles
|
|
53
|
+
text = Text("Hello", style="bold primary")
|
|
54
|
+
|
|
55
|
+
Available styles:
|
|
56
|
+
- dim: Muted/dimmed text
|
|
57
|
+
- bold: Bold text
|
|
58
|
+
- italic: Italic text
|
|
59
|
+
- primary: Primary color
|
|
60
|
+
- success: Success/green color
|
|
61
|
+
- error: Error/red color
|
|
62
|
+
- warning: Warning/yellow color
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
66
|
+
|
|
67
|
+
def __init__(self, renderable="", *, style: str = "", **kwargs):
|
|
68
|
+
"""
|
|
69
|
+
Initialize text widget.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
renderable: Text content
|
|
73
|
+
style: Space-separated style classes (e.g., "bold primary")
|
|
74
|
+
**kwargs: Additional Static arguments
|
|
75
|
+
"""
|
|
76
|
+
# Add style classes to existing classes
|
|
77
|
+
if style:
|
|
78
|
+
existing_classes = kwargs.get("classes", "")
|
|
79
|
+
if existing_classes:
|
|
80
|
+
kwargs["classes"] = f"{existing_classes} {style}"
|
|
81
|
+
else:
|
|
82
|
+
kwargs["classes"] = style
|
|
83
|
+
|
|
84
|
+
super().__init__(renderable, **kwargs)
|
|
85
|
+
|
|
86
|
+
def set_style(self, *styles: str) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Set the text style(s), removing previous styles.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
*styles: One or more style names to apply
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
text.set_style("bold", "error")
|
|
95
|
+
"""
|
|
96
|
+
# Remove all style classes
|
|
97
|
+
for cls in ["dim", "bold", "italic", "primary", "success", "error", "warning"]:
|
|
98
|
+
self.remove_class(cls)
|
|
99
|
+
|
|
100
|
+
# Add new styles
|
|
101
|
+
for style in styles:
|
|
102
|
+
if style:
|
|
103
|
+
self.add_class(style)
|
|
104
|
+
|
|
105
|
+
def add_style(self, *styles: str) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Add style(s) to the text without removing existing ones.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
*styles: One or more style names to add
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
text.add_style("italic")
|
|
114
|
+
"""
|
|
115
|
+
for style in styles:
|
|
116
|
+
if style:
|
|
117
|
+
self.add_class(style)
|
|
118
|
+
|
|
119
|
+
def remove_style(self, *styles: str) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Remove style(s) from the text.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
*styles: One or more style names to remove
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
text.remove_style("bold")
|
|
128
|
+
"""
|
|
129
|
+
for style in styles:
|
|
130
|
+
if style:
|
|
131
|
+
self.remove_class(style)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Convenience widgets - use shared CSS
|
|
135
|
+
class DimText(Static):
|
|
136
|
+
"""Text widget with dim/muted styling."""
|
|
137
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class BoldText(Static):
|
|
141
|
+
"""Text widget with bold styling."""
|
|
142
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class PrimaryText(Static):
|
|
146
|
+
"""Text widget with primary color."""
|
|
147
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class BoldPrimaryText(Static):
|
|
151
|
+
"""Text widget with bold primary color."""
|
|
152
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class SuccessText(Static):
|
|
156
|
+
"""Text widget with success/green color."""
|
|
157
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ErrorText(Static):
|
|
161
|
+
"""Text widget with error/red color."""
|
|
162
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class WarningText(Static):
|
|
166
|
+
"""Text widget with warning/yellow color."""
|
|
167
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ItalicText(Static):
|
|
171
|
+
"""Text widget with italic styling."""
|
|
172
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class DimItalicText(Static):
|
|
176
|
+
"""Text widget with dim italic styling."""
|
|
177
|
+
DEFAULT_CSS = SHARED_TEXT_CSS
|
|
File without changes
|