feat: add errout for interactive for script support and atty for clean output
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Kasper Juul Hermansen 2024-09-23 21:35:10 +02:00
parent c9aacf0ecd
commit f0f81f8a0b
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
10 changed files with 137 additions and 21 deletions

23
Cargo.lock generated
View File

@ -137,6 +137,17 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.3.0" version = "1.3.0"
@ -569,6 +580,7 @@ version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"atty",
"bytes", "bytes",
"clap", "clap",
"crossterm", "crossterm",
@ -626,6 +638,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@ -990,7 +1011,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi 0.3.9",
"libc", "libc",
"log", "log",
"wasi", "wasi",

View File

@ -1 +0,0 @@
/target

View File

@ -28,6 +28,7 @@ nucleo-matcher = "0.3.1"
ratatui = "0.28.1" ratatui = "0.28.1"
crossterm = { version = "0.28.0", features = ["event-stream"] } crossterm = { version = "0.28.0", features = ["event-stream"] }
futures = "0.3.30" futures = "0.3.30"
atty = "0.2.14"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"

View File

@ -0,0 +1,15 @@
function git-now {
choice=$(gitnow "$@")
if [[ $? -ne 0 ]]; then
return $?
fi
cd "$(echo "$choice" | tail --lines 1)"
}
function gn {
git-now "$@"
if [[ $? -ne 0 ]]; then
return $?
fi
}

View File

@ -88,16 +88,21 @@ impl RootCommand {
if clone { if clone {
let git_clone = self.app.git_clone(); let git_clone = self.app.git_clone();
let mut wrap_cmd = if atty::is(atty::Stream::Stdout) && shell {
InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display())); let mut wrap_cmd =
let repo = repo.clone(); InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display()));
wrap_cmd let repo = repo.clone();
.execute(move || async move { wrap_cmd
git_clone.clone_repo(&repo, force_refresh).await?; .execute(move || async move {
git_clone.clone_repo(&repo, force_refresh).await?;
Ok(()) Ok(())
}) })
.await?; .await?;
} else {
eprintln!("cloning repository...");
git_clone.clone_repo(&repo, force_refresh).await?;
}
} else { } else {
tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display()); tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display());
} }

View File

@ -0,0 +1,33 @@
use zsh::ZshShell;
pub mod zsh;
#[derive(clap::Parser)]
pub struct Shell {
#[command(subcommand)]
shell: ShellSubcommands,
}
impl Shell {
pub async fn execute(&mut self) -> anyhow::Result<()> {
self.shell.execute().await?;
Ok(())
}
}
#[derive(clap::Subcommand)]
pub enum ShellSubcommands {
#[command()]
Zsh(ZshShell),
}
impl ShellSubcommands {
pub async fn execute(&mut self) -> anyhow::Result<()> {
match self {
ShellSubcommands::Zsh(zsh) => zsh.execute().await?,
}
Ok(())
}
}

View File

@ -0,0 +1,12 @@
#[derive(clap::Parser)]
pub struct ZshShell {}
const SCRIPT: &str = include_str!("../../../include/shell/zsh.sh");
impl ZshShell {
pub async fn execute(&mut self) -> anyhow::Result<()> {
println!("{}", SCRIPT);
Ok(())
}
}

View File

@ -1,7 +1,14 @@
use std::{io::Write, time::Duration}; use std::{
io::{stderr, Write},
time::Duration,
};
use anyhow::Context; use anyhow::Context;
use crossterm::event::{EventStream, KeyCode, KeyEventKind}; use crossterm::{
event::{EventStream, KeyCode, KeyEventKind},
terminal::{enable_raw_mode, EnterAlternateScreen},
ExecutableCommand,
};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ratatui::{ use ratatui::{
crossterm, crossterm,
@ -73,6 +80,8 @@ impl InlineCommand {
drop(guard); drop(guard);
println!();
Ok(()) Ok(())
} }

View File

@ -1,4 +1,11 @@
use std::io::stderr;
use app::App; use app::App;
use crossterm::{
terminal::{enable_raw_mode, EnterAlternateScreen},
ExecutableCommand,
};
use ratatui::{prelude::CrosstermBackend, Terminal};
use crate::git_provider::Repository; use crate::git_provider::Repository;
@ -15,8 +22,14 @@ impl Interactive {
&mut self, &mut self,
repositories: &[Repository], repositories: &[Repository],
) -> anyhow::Result<Option<Repository>> { ) -> anyhow::Result<Option<Repository>> {
let terminal = ratatui::init(); let backend = CrosstermBackend::new(std::io::stderr());
let terminal = Terminal::new(backend)?;
enable_raw_mode()?;
stderr().execute(EnterAlternateScreen)?;
let app_result = App::new(self.app, repositories).run(terminal); let app_result = App::new(self.app, repositories).run(terminal);
ratatui::restore(); ratatui::restore();
app_result app_result
@ -37,10 +50,11 @@ mod app {
use ratatui::{ use ratatui::{
crossterm::event::{self, Event, KeyCode}, crossterm::event::{self, Event, KeyCode},
layout::{Constraint, Layout}, layout::{Constraint, Layout},
prelude::CrosstermBackend,
style::{Style, Stylize}, style::{Style, Stylize},
text::{Line, Span}, text::{Line, Span},
widgets::{ListItem, ListState, Paragraph, StatefulWidget}, widgets::{ListItem, ListState, Paragraph, StatefulWidget},
DefaultTerminal, Frame, Frame, Terminal,
}; };
use crate::{ use crate::{
@ -81,7 +95,10 @@ mod app {
} }
} }
pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<Option<Repository>> { pub fn run<T: std::io::Write>(
mut self,
mut terminal: Terminal<CrosstermBackend<T>>,
) -> anyhow::Result<Option<Repository>> {
self.update_matched_repos(); self.update_matched_repos();
loop { loop {
@ -99,14 +116,19 @@ mod app {
self.update_matched_repos(); self.update_matched_repos();
} }
} }
KeyCode::Esc => return Ok(None), KeyCode::Esc => {
terminal.clear()?;
return Ok(None);
}
KeyCode::Enter => { KeyCode::Enter => {
if let Some(selected) = self.list.selected() { if let Some(selected) = self.list.selected() {
if let Some(repo) = self.matched_repos.get(selected).cloned() { if let Some(repo) = self.matched_repos.get(selected).cloned() {
terminal.clear()?;
return Ok(Some(repo)); return Ok(Some(repo));
} }
} }
terminal.clear()?;
return Ok(None); return Ok(None);
} }
KeyCode::Up => self.list.select_next(), KeyCode::Up => self.list.select_next(),

View File

@ -4,8 +4,7 @@ use std::path::PathBuf;
use anyhow::Context; use anyhow::Context;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use commands::root::RootCommand; use commands::{root::RootCommand, shell::Shell};
use components::inline_command::InlineCommand;
use config::Config; use config::Config;
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
@ -50,7 +49,7 @@ struct Command {
#[derive(Subcommand)] #[derive(Subcommand)]
enum Commands { enum Commands {
Hello {}, Init(Shell),
} }
const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml"; const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml";
@ -81,7 +80,7 @@ async fn main() -> anyhow::Result<()> {
tracing::debug!("Starting cli"); tracing::debug!("Starting cli");
match cli.command { match cli.command {
Some(_) => todo!(), Some(Commands::Init(mut shell)) => shell.execute().await?,
None => { None => {
RootCommand::new(app) RootCommand::new(app)
.execute( .execute(