command/
main.rs

1use clap::{load_yaml, App, ArgMatches};
2use std::io;
3use crossterm::event::{Event, KeyCode};
4use tui::backend::{Backend, CrosstermBackend};
5use tui::layout::{Constraint, Direction, Layout, Rect};
6use tui::widgets::{Block, Borders};
7use tui::Terminal;
8use tui_clap::{Events, TuiClap};
9
10fn main() -> Result<(), io::Error> {
11    let yaml = load_yaml!("cli.yaml");
12    let app = App::from(yaml);
13
14    let stdout = io::stdout();
15    let backend = CrosstermBackend::new(stdout);
16    let mut terminal = Terminal::new(backend)?;
17
18    let mut tui = TuiClap::from_app(app);
19    tui.input_widget().prompt("prompt > ");
20
21    terminal.clear().expect("Could not clear terminal");
22
23    let events = Events::default();
24
25    loop {
26        draw(&mut terminal, &mut tui)?;
27        handle_input(&mut tui, &events)
28    }
29}
30
31fn handle_input(tui: &mut TuiClap, events: &Events) {
32    if let Ok(Some(Event::Key(key_event))) = events.next() {
33        match key_event.code {
34            KeyCode::Backspace => {
35                tui.state().del_char()
36            }
37            KeyCode::Enter => {
38                if let Ok(matches) = tui.parse() {
39                    match handle_matches(matches) {
40                        Ok(output) => {
41                            for message in output {
42                                tui.write_to_output(message)
43                            }
44                        }
45                        Err(err) => tui.write_to_output(err)
46                    }
47                }
48            }
49            KeyCode::Char(char) => {
50                tui.state().add_char(char)
51            },
52            _ => {}
53        }
54    }
55}
56
57fn draw<B: Backend>(terminal: &mut Terminal<B>, tui: &mut TuiClap) -> io::Result<()> {
58    terminal.draw(|f| {
59        let chunks = Layout::default()
60            .direction(Direction::Vertical)
61            .margin(1)
62            .constraints(
63                [
64                    Constraint::Percentage(10),
65                    Constraint::Percentage(80),
66                    Constraint::Percentage(10),
67                ]
68                .as_ref(),
69            )
70            .split(f.size());
71        let block = Block::default().title("Block").borders(Borders::ALL);
72        f.render_widget(block, chunks[0]);
73        let chunks_output = Layout::default()
74            .direction(Direction::Horizontal)
75            .margin(1)
76            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
77            .split(chunks[1]);
78        let block = Block::default().title("Block 2").borders(Borders::ALL);
79        f.render_widget(block, chunks_output[0]);
80        let inset_area = edge_inset(&chunks_output[0], 1);
81        tui.render_output(f, inset_area);
82        let block = Block::default().title("Command").borders(Borders::ALL);
83        f.render_widget(block, chunks[2]);
84
85        let inset_area = edge_inset(&chunks[2], 1);
86        tui.render_input(f, inset_area);
87    })?;
88    Ok(())
89}
90
91fn edge_inset(area: &Rect, margin: u16) -> Rect {
92    let mut inset_area = *area;
93    inset_area.x += margin;
94    inset_area.y += margin;
95    inset_area.height -= margin;
96    inset_area.width -= margin;
97
98    inset_area
99}
100
101fn handle_matches(matches: ArgMatches) -> Result<Vec<String>, String> {
102    let mut output = vec![];
103
104    let config = matches.value_of("config").unwrap_or("default.conf");
105    let out = format!("Value for config: {}", config);
106    output.push(out);
107
108    // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't
109    // required we could have used an 'if let' to conditionally get the value)
110    let out = format!("Using input file: {}", matches.value_of("INPUT").unwrap());
111    output.push(out);
112
113    // Vary the output based on how many times the user used the "verbose" flag
114    // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v'
115    let out = match matches.occurrences_of("v") {
116        0 => "No verbose info".to_string(),
117        1 => "Some verbose info".to_string(),
118        2 => "Tons of verbose info".to_string(),
119        _ => "Don't be crazy".to_string(),
120    };
121    output.push(out);
122
123    // You can handle information about subcommands by requesting their matches by name
124    // (as below), requesting just the name used, or both at the same time
125    if let Some(matches) = matches.subcommand_matches("test") {
126        if matches.is_present("debug") {
127            let out = "Printing debug info...".to_string();
128            output.push(out);
129        } else {
130            let out = "Printing normally...".to_string();
131            output.push(out);
132        }
133    };
134
135    Ok(output)
136}