xacli_core/application/
context.rs

1//! Unified context for all callbacks and components
2//!
3//! This module provides:
4//! - `Input` trait for argument access and interactive input
5//! - `Output` trait for output, styling, logging, and terminal control
6//! - `Context` trait combining Input + Output + metadata
7//! - `StdContext` as the standard implementation
8//! - `FromArgValue` for type-safe argument extraction
9
10use std::io::{IsTerminal, Write};
11
12use super::{app::AppInfo, parser::Parsed};
13use crate::{
14    io::{InputEvent, OutputCommand, ReadEvent},
15    CommandInfo, Result,
16};
17
18pub struct Output {
19    out: Box<dyn Write>,
20}
21
22impl Output {
23    pub fn new(out: Box<dyn Write>) -> Self {
24        Self { out }
25    }
26}
27
28impl Write for Output {
29    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
30        self.out.write(buf)
31    }
32
33    fn flush(&mut self) -> std::io::Result<()> {
34        self.out.flush()
35    }
36}
37
38pub trait Context: ReadEvent {
39    /// Get context metadata
40    fn info(&self) -> &ContextInfo;
41
42    fn stdout(&mut self) -> Output;
43
44    // fn stdout(&mut self) -> Box<dyn Write> {
45    //     Box::new(std::io::stdout())
46    // }
47
48    fn stderr(&mut self) -> Output;
49}
50
51/// Context metadata
52#[derive(Debug, Clone)]
53pub struct ContextInfo {
54    /// Application info
55    pub app: AppInfo,
56    /// Current command info
57    pub command: CommandInfo,
58    /// Parsed arguments
59    pub args: Parsed,
60}
61
62impl Default for ContextInfo {
63    fn default() -> Self {
64        Self {
65            app: AppInfo {
66                name: "default".to_string(),
67                version: "0.1.0".to_string(),
68                title: "Default Application".to_string(),
69                description: "A default application".to_string(),
70            },
71            command: CommandInfo {
72                name: "default".to_string(),
73                title: "Default Command".to_string(),
74                description: "A default command".to_string(),
75                aliases: Vec::new(),
76            },
77            args: Parsed::default(),
78        }
79    }
80}
81
82/// Standard context implementation using stdin/stdout/stderr
83pub struct AppContext {
84    pub is_tty: bool,
85    pub info: ContextInfo,
86    pub queued_commands: Vec<OutputCommand>,
87}
88
89impl AppContext {
90    /// Create a new AppContext
91    ///
92    /// Automatically detects whether stdout is a TTY.
93    /// Note: Raw mode is NOT enabled here - interactive components
94    /// should manage raw mode themselves when needed.
95    pub fn new(info: ContextInfo) -> Self {
96        let is_tty = std::io::stdout().is_terminal();
97        Self {
98            is_tty,
99            info,
100            queued_commands: Vec::new(),
101        }
102    }
103}
104
105impl Context for AppContext {
106    fn info(&self) -> &ContextInfo {
107        &self.info
108    }
109
110    fn stdout(&mut self) -> Output {
111        {
112            Output {
113                out: Box::new(std::io::stdout()),
114            }
115        }
116    }
117
118    fn stderr(&mut self) -> Output {
119        Output {
120            out: Box::new(std::io::stderr()),
121        }
122    }
123}
124
125impl ReadEvent for AppContext {
126    fn read_event(&mut self) -> Result<InputEvent> {
127        crossterm::event::read().map_err(|e| e.into())
128    }
129}