Delete unneeded source file
This commit is contained in:
parent
131f0b77d8
commit
496d3786e7
1 changed files with 0 additions and 464 deletions
|
@ -1,464 +0,0 @@
|
|||
use std::io;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use application::UseCase;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use domain::{CreateProduct, CreateUser, Product, User};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use textwrap;
|
||||
|
||||
const MAX_HISTORY: usize = 100;
|
||||
|
||||
pub struct App {
|
||||
input: String,
|
||||
messages: Vec<String>,
|
||||
should_quit: bool,
|
||||
command_history: VecDeque<String>,
|
||||
history_index: Option<usize>,
|
||||
cursor_position: usize,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: String::new(),
|
||||
messages: vec!["Welcome to Sharenet CLI!".to_string()],
|
||||
should_quit: false,
|
||||
command_history: VecDeque::with_capacity(MAX_HISTORY),
|
||||
history_index: None,
|
||||
cursor_position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message(&mut self, message: String) {
|
||||
self.messages.push(message);
|
||||
}
|
||||
|
||||
pub fn clear_input(&mut self) {
|
||||
self.input.clear();
|
||||
self.cursor_position = 0;
|
||||
}
|
||||
|
||||
pub fn add_to_history(&mut self, command: String) {
|
||||
if !command.trim().is_empty() {
|
||||
self.command_history.push_front(command);
|
||||
if self.command_history.len() > MAX_HISTORY {
|
||||
self.command_history.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor(&mut self, delta: isize) {
|
||||
let new_pos = self.cursor_position as isize + delta;
|
||||
if new_pos >= 0 && new_pos <= self.input.len() as isize {
|
||||
self.cursor_position = new_pos as usize;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_char(&mut self, c: char) {
|
||||
self.input.insert(self.cursor_position, c);
|
||||
self.cursor_position += 1;
|
||||
}
|
||||
|
||||
pub fn delete_char(&mut self) {
|
||||
if self.cursor_position > 0 {
|
||||
self.input.remove(self.cursor_position - 1);
|
||||
self.cursor_position -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_tui<U, P>(user_service: U, product_service: P) -> anyhow::Result<()>
|
||||
where
|
||||
U: UseCase<User> + Clone + Send + 'static,
|
||||
P: UseCase<Product> + Clone + Send + 'static,
|
||||
{
|
||||
// Setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// Create app and run it
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app, user_service, product_service).await;
|
||||
|
||||
// Restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{:?}", err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app<B: Backend, U, P>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
user_service: U,
|
||||
product_service: P,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
U: UseCase<User> + Clone + Send + 'static,
|
||||
P: UseCase<Product> + Clone + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let user_service_clone = user_service.clone();
|
||||
let product_service_clone = product_service.clone();
|
||||
|
||||
// Spawn a thread to handle user input
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
if event::poll(Duration::from_millis(50)).unwrap() {
|
||||
if let Event::Key(key) = event::read().unwrap() {
|
||||
tx.send(key).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
if let Ok(key) = rx.try_recv() {
|
||||
match key.code {
|
||||
KeyCode::Char(c) => {
|
||||
app.insert_char(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.delete_char();
|
||||
}
|
||||
KeyCode::Left => {
|
||||
app.move_cursor(-1);
|
||||
}
|
||||
KeyCode::Right => {
|
||||
app.move_cursor(1);
|
||||
}
|
||||
KeyCode::Up => {
|
||||
if let Some(index) = app.history_index {
|
||||
if index + 1 < app.command_history.len() {
|
||||
app.history_index = Some(index + 1);
|
||||
app.input = app.command_history[index + 1].clone();
|
||||
app.cursor_position = app.input.len();
|
||||
}
|
||||
} else if !app.command_history.is_empty() {
|
||||
app.history_index = Some(0);
|
||||
app.input = app.command_history[0].clone();
|
||||
app.cursor_position = app.input.len();
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if let Some(index) = app.history_index {
|
||||
if index > 0 {
|
||||
app.history_index = Some(index - 1);
|
||||
app.input = app.command_history[index - 1].clone();
|
||||
app.cursor_position = app.input.len();
|
||||
} else {
|
||||
app.history_index = None;
|
||||
app.clear_input();
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let input = app.input.clone();
|
||||
app.add_to_history(input.clone());
|
||||
app.clear_input();
|
||||
app.history_index = None;
|
||||
|
||||
// Display the command in a distinct color
|
||||
app.add_message(format!("> {}", input));
|
||||
|
||||
// Handle commands
|
||||
match input.trim() {
|
||||
"exit" => {
|
||||
app.add_message("Goodbye!".to_string());
|
||||
app.should_quit = true;
|
||||
}
|
||||
"help" => {
|
||||
print_help(&mut app);
|
||||
}
|
||||
cmd if cmd.starts_with("user create") => {
|
||||
match parse_user_create(cmd) {
|
||||
Ok((username, email)) => {
|
||||
match user_service_clone
|
||||
.create(CreateUser { username, email })
|
||||
.await
|
||||
{
|
||||
Ok(user) => app.add_message(format!("Created user: {:?}", user)),
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
"user list" => {
|
||||
match user_service_clone.list().await {
|
||||
Ok(users) => app.add_message(format!("Users: {:?}", users)),
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
cmd if cmd.starts_with("product create") => {
|
||||
match parse_product_create(cmd) {
|
||||
Ok((name, description)) => {
|
||||
match product_service_clone
|
||||
.create(CreateProduct { name, description })
|
||||
.await
|
||||
{
|
||||
Ok(product) => app.add_message(format!("Created product: {:?}", product)),
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
"product list" => {
|
||||
match product_service_clone.list().await {
|
||||
Ok(products) => app.add_message(format!("Products: {:?}", products)),
|
||||
Err(e) => app.add_message(format!("Error: {}", e)),
|
||||
}
|
||||
}
|
||||
"" => {}
|
||||
_ => {
|
||||
app.add_message("Unknown command. Type 'help' for available commands.".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.should_quit = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help(app: &mut App) {
|
||||
app.add_message("\nAvailable commands:".to_string());
|
||||
app.add_message(" user create -u <username> -e <email>".to_string());
|
||||
app.add_message(" Example: user create -u \"john doe\" -e \"john@example.com\"".to_string());
|
||||
app.add_message(" user list".to_string());
|
||||
app.add_message(" product create -n <name> -d <description>".to_string());
|
||||
app.add_message(" Example: product create -n \"My Product\" -d \"A great product description\"".to_string());
|
||||
app.add_message(" product list".to_string());
|
||||
app.add_message("\nTips:".to_string());
|
||||
app.add_message(" - Use quotes for values with spaces".to_string());
|
||||
app.add_message(" - Use Up/Down arrows to navigate command history".to_string());
|
||||
app.add_message(" - Press Esc to exit".to_string());
|
||||
app.add_message(" - Type 'help' to show this message".to_string());
|
||||
}
|
||||
|
||||
fn ui(f: &mut Frame, app: &App) {
|
||||
// Create the layout
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints([
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(f.size());
|
||||
|
||||
// Create the messages list with styling and wrapping
|
||||
let messages: Vec<ListItem> = app
|
||||
.messages
|
||||
.iter()
|
||||
.flat_map(|msg| {
|
||||
let style = if msg.starts_with("Error:") {
|
||||
Style::default().fg(Color::Red)
|
||||
} else if msg.starts_with("Created") {
|
||||
Style::default().fg(Color::Green)
|
||||
} else if msg.starts_with(">") {
|
||||
Style::default().fg(Color::Cyan)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
// Calculate available width for text (accounting for borders and margins)
|
||||
let available_width = chunks[0].width.saturating_sub(2) as usize;
|
||||
|
||||
// Split message into wrapped lines
|
||||
let wrapped_lines = textwrap::wrap(msg, available_width);
|
||||
|
||||
// Convert each wrapped line into a ListItem
|
||||
wrapped_lines.into_iter().map(move |line| {
|
||||
ListItem::new(line.to_string()).style(style)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let messages = List::new(messages)
|
||||
.block(Block::default().title("Messages").borders(Borders::ALL));
|
||||
f.render_widget(messages, chunks[0]);
|
||||
|
||||
// Create the input box with cursor and wrapping
|
||||
let input = Paragraph::new(app.input.as_str())
|
||||
.style(Style::default().fg(Color::Yellow))
|
||||
.block(Block::default().title("Input (Press 'Esc' to exit)").borders(Borders::ALL))
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(input, chunks[1]);
|
||||
|
||||
// Show cursor
|
||||
f.set_cursor(
|
||||
chunks[1].x + app.cursor_position as u16 + 1,
|
||||
chunks[1].y + 1,
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_user_create(cmd: &str) -> anyhow::Result<(String, String)> {
|
||||
let parts: Vec<&str> = cmd.split_whitespace().collect();
|
||||
if parts.len() < 6 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid command format. Use: user create -u <username> -e <email>\nExample: user create -u \"john doe\" -e \"john@example.com\""
|
||||
));
|
||||
}
|
||||
|
||||
let mut username = None;
|
||||
let mut email = None;
|
||||
let mut current_arg = None;
|
||||
let mut current_value = Vec::new();
|
||||
|
||||
// Skip "user create" command
|
||||
let mut i = 2;
|
||||
while i < parts.len() {
|
||||
match parts[i] {
|
||||
"-u" => {
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"username" => username = Some(current_value.join(" ")),
|
||||
"email" => email = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
current_arg = Some("username");
|
||||
current_value.clear();
|
||||
i += 1;
|
||||
}
|
||||
"-e" => {
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"username" => username = Some(current_value.join(" ")),
|
||||
"email" => email = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
current_arg = Some("email");
|
||||
current_value.clear();
|
||||
i += 1;
|
||||
}
|
||||
_ => {
|
||||
if current_arg.is_some() {
|
||||
current_value.push(parts[i].trim_matches('"'));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last argument
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"username" => username = Some(current_value.join(" ")),
|
||||
"email" => email = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match (username, email) {
|
||||
(Some(u), Some(e)) if !u.is_empty() && !e.is_empty() => Ok((u, e)),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Invalid command format. Use: user create -u <username> -e <email>\nExample: user create -u \"john doe\" -e \"john@example.com\""
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_product_create(cmd: &str) -> anyhow::Result<(String, String)> {
|
||||
let parts: Vec<&str> = cmd.split_whitespace().collect();
|
||||
if parts.len() < 6 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid command format. Use: product create -n <name> -d <description>\nExample: product create -n \"My Product\" -d \"A great product description\""
|
||||
));
|
||||
}
|
||||
|
||||
let mut name = None;
|
||||
let mut description = None;
|
||||
let mut current_arg = None;
|
||||
let mut current_value = Vec::new();
|
||||
|
||||
// Skip "product create" command
|
||||
let mut i = 2;
|
||||
while i < parts.len() {
|
||||
match parts[i] {
|
||||
"-n" => {
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"name" => name = Some(current_value.join(" ")),
|
||||
"description" => description = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
current_arg = Some("name");
|
||||
current_value.clear();
|
||||
i += 1;
|
||||
}
|
||||
"-d" => {
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"name" => name = Some(current_value.join(" ")),
|
||||
"description" => description = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
current_arg = Some("description");
|
||||
current_value.clear();
|
||||
i += 1;
|
||||
}
|
||||
_ => {
|
||||
if current_arg.is_some() {
|
||||
current_value.push(parts[i].trim_matches('"'));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last argument
|
||||
if let Some(arg_type) = current_arg {
|
||||
match arg_type {
|
||||
"name" => name = Some(current_value.join(" ")),
|
||||
"description" => description = Some(current_value.join(" ")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match (name, description) {
|
||||
(Some(n), Some(d)) if !n.is_empty() && !d.is_empty() => Ok((n, d)),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Invalid command format. Use: product create -n <name> -d <description>\nExample: product create -n \"My Product\" -d \"A great product description\""
|
||||
)),
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue