Add commands and parsers modules
This commit is contained in:
186
.termflow/commands/visualize.py
Normal file
186
.termflow/commands/visualize.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""Visualize command for CLI."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from ..core.database import SessionDatabase
|
||||
from ..core.session import Session
|
||||
from ..parsers.git_parser import GitLogParser
|
||||
from ..parsers.pattern_analyzer import PatternDetector, SequenceAnalyzer
|
||||
from ..exporters.ascii_generator import ASCIIGenerator, GitGraphGenerator
|
||||
from ..exporters.mermaid_generator import MermaidGenerator
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@click.command(name="visualize")
|
||||
@click.argument("source", type=str)
|
||||
@click.option(
|
||||
"--session-id",
|
||||
"-s",
|
||||
type=int,
|
||||
help="Session ID to visualize",
|
||||
)
|
||||
@click.option(
|
||||
"--style",
|
||||
"visual_style",
|
||||
type=click.Choice(["compact", "detailed", "minimal"]),
|
||||
default="detailed",
|
||||
help="Visualization style",
|
||||
)
|
||||
@click.option(
|
||||
"--format",
|
||||
"-f",
|
||||
type=click.Choice(["ascii", "mermaid"]),
|
||||
default="ascii",
|
||||
help="Output format",
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
type=click.Path(path_type=Path),
|
||||
help="Output file",
|
||||
)
|
||||
@click.option(
|
||||
"--analyze/--no-analyze",
|
||||
default=True,
|
||||
help="Include pattern analysis",
|
||||
)
|
||||
@click.pass_context
|
||||
def visualize_cmd(
|
||||
ctx: click.Context,
|
||||
source: str,
|
||||
session_id: Optional[int],
|
||||
visual_style: str,
|
||||
format: str,
|
||||
output: Optional[Path],
|
||||
analyze: bool,
|
||||
) -> None:
|
||||
"""Visualize a session or git workflow."""
|
||||
db_path = ctx.obj.get("db", Path.home() / ".termflow" / "sessions.db")
|
||||
|
||||
if source == "session":
|
||||
_visualize_session(
|
||||
db_path,
|
||||
session_id,
|
||||
visual_style,
|
||||
format,
|
||||
output,
|
||||
analyze,
|
||||
)
|
||||
elif source == "git":
|
||||
_visualize_git(
|
||||
Path.cwd(),
|
||||
visual_style,
|
||||
format,
|
||||
output,
|
||||
)
|
||||
else:
|
||||
console.print(f"[red]Unknown source: {source}[/red]")
|
||||
console.print("Use 'session' or 'git' as source")
|
||||
|
||||
|
||||
def _visualize_session(
|
||||
db_path: Path,
|
||||
session_id: Optional[int],
|
||||
style: str,
|
||||
format: str,
|
||||
output: Optional[Path],
|
||||
analyze: bool,
|
||||
) -> None:
|
||||
"""Visualize a terminal session."""
|
||||
db = SessionDatabase(db_path)
|
||||
|
||||
if session_id is None:
|
||||
sessions = db.get_all_sessions()
|
||||
if not sessions:
|
||||
console.print("[yellow]No sessions found[/yellow]")
|
||||
return
|
||||
session_id = sessions[0]["id"]
|
||||
|
||||
session = db.get_session(session_id)
|
||||
if not session:
|
||||
console.print(f"[red]Session {session_id} not found[/red]")
|
||||
return
|
||||
|
||||
commands = db.get_session_commands(session_id)
|
||||
command_list = [cmd.get("command", "") for cmd in commands]
|
||||
|
||||
if analyze:
|
||||
detector = PatternDetector()
|
||||
patterns = detector.analyze_commands(command_list)
|
||||
|
||||
if patterns:
|
||||
text = Text()
|
||||
text.append(f"Found {len(patterns)} patterns:\n", style="bold")
|
||||
for i, pattern in enumerate(patterns[:5], 1):
|
||||
text.append(f" {i}. {pattern.commands} (x{pattern.frequency})\n")
|
||||
console.print(Panel(text, title="Pattern Analysis"))
|
||||
|
||||
metadata = {
|
||||
"title": f"Session: {session.get('name', 'Unknown')}",
|
||||
"command_count": len(command_list),
|
||||
"session_name": session.get("name", ""),
|
||||
}
|
||||
|
||||
if format == "ascii":
|
||||
ascii_gen = ASCIIGenerator(style=style)
|
||||
result = ascii_gen.generate_from_commands(command_list, metadata)
|
||||
else:
|
||||
mermaid_gen = MermaidGenerator()
|
||||
result = mermaid_gen.generate_flowchart(
|
||||
command_list,
|
||||
title=f"Session: {session.get('name', 'Unknown')}",
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
if output:
|
||||
with open(output, "w") as f:
|
||||
f.write(result)
|
||||
console.print(f"[cyan]Visualization saved to {output}[/cyan]")
|
||||
else:
|
||||
console.print(Panel(result, title=f"Session Flow: {session.get('name', 'Unknown')}"))
|
||||
|
||||
|
||||
def _visualize_git(
|
||||
repo_path: Path,
|
||||
style: str,
|
||||
format: str,
|
||||
output: Optional[Path],
|
||||
) -> None:
|
||||
"""Visualize git workflow."""
|
||||
parser = GitLogParser(repo_path)
|
||||
|
||||
if not parser.is_git_repo():
|
||||
console.print("[red]Not a git repository[/red]")
|
||||
return
|
||||
|
||||
commits = parser.parse_log(max_count=50)
|
||||
parser.get_commit_parents()
|
||||
workflow = parser.analyze_workflow()
|
||||
|
||||
if format == "ascii":
|
||||
graph_gen = GitGraphGenerator()
|
||||
result = graph_gen.generate_from_parser(parser)
|
||||
else:
|
||||
mermaid_gen = MermaidGenerator()
|
||||
result = mermaid_gen.generate_git_graph_from_parser(parser)
|
||||
|
||||
stats_text = Text()
|
||||
stats_text.append(f"Commits: {len(commits)}\n", style="bold")
|
||||
stats_text.append(f"Merges: {workflow.merge_count}\n")
|
||||
stats_text.append(f"Features: {workflow.feature_count}\n")
|
||||
stats_text.append(f"Branches: {len(workflow.branches)}")
|
||||
|
||||
if output:
|
||||
with open(output, "w") as f:
|
||||
f.write(result)
|
||||
console.print(f"[cyan]Visualization saved to {output}[/cyan]")
|
||||
else:
|
||||
console.print(Panel(result, title="Git Workflow"))
|
||||
console.print(Panel(stats_text, title="Statistics"))
|
||||
Reference in New Issue
Block a user