Added some more gamenight-cli Flows.

This commit is contained in:
2025-05-02 22:58:45 +02:00
parent db25dc0aed
commit 6699dcf392
11 changed files with 309 additions and 66 deletions

135
gamenight-cli/Cargo.lock generated
View File

@@ -17,6 +17,21 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "async-trait"
version = "0.1.88"
@@ -93,9 +108,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.19"
version = "1.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
dependencies = [
"shlex",
]
@@ -106,6 +121,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -298,6 +327,8 @@ name = "gamenight-cli"
version = "0.1.0"
dependencies = [
"async-trait",
"chrono",
"dyn-clone",
"gamenight-api-client-rs",
"inquire",
"tokio",
@@ -353,9 +384,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]]
name = "http"
@@ -470,6 +501,30 @@ dependencies = [
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
@@ -626,6 +681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
dependencies = [
"bitflags 2.9.0",
"chrono",
"crossterm",
"dyn-clone",
"fuzzy-matcher",
@@ -772,6 +828,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
@@ -821,9 +886,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.107"
version = "0.9.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
dependencies = [
"cc",
"libc",
@@ -978,9 +1043,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "1.0.5"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.0",
"errno",
@@ -1208,9 +1273,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.100"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@@ -1228,9 +1293,9 @@ dependencies = [
[[package]]
name = "synstructure"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
@@ -1591,6 +1656,41 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings 0.4.0",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
@@ -1604,7 +1704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
"windows-result",
"windows-strings",
"windows-strings 0.3.1",
"windows-targets 0.53.0",
]
@@ -1626,6 +1726,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View File

@@ -6,5 +6,7 @@ edition = "2024"
[dependencies]
gamenight-api-client-rs = { path = "../gamenight-api-client-rs" }
tokio = { version = "1", features = ["full"] }
inquire = "0.7.5"
async-trait = "0.1"
inquire = { version = "0.7.5", features = ["date"] }
async-trait = "0.1"
dyn-clone = "1.0"
chrono = "0.4"

View File

@@ -1,5 +1,6 @@
use super::*;
#[derive(Clone)]
pub struct Abort {
}

View File

@@ -0,0 +1,44 @@
use gamenight_api_client_rs::{apis::default_api::post_gamenight, models};
use inquire::{Text, DateSelect};
use chrono::{self, Local, NaiveTime};
use super::*;
#[derive(Clone)]
pub struct AddGamenight {
}
impl AddGamenight {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl<'a> Flow<'a> for AddGamenight {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let mut add_gamenight = models::AddGamenightRequestBody::new();
add_gamenight.name = Some(Text::new("What should we call your gamenight").prompt()?);
add_gamenight.datetime = Some(DateSelect::new("When is your gamenight").prompt()?
.and_time(NaiveTime::default())
.and_local_timezone(Local)
.earliest()
.unwrap()
.to_utc()
.to_rfc3339());
post_gamenight(&state.configuration, Some(add_gamenight)).await?;
Ok((FlowOutcome::Successful, state))
}
}
impl Display for AddGamenight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Add Gamenight.")
}
}

View File

@@ -1,8 +1,12 @@
use gamenight_api_client_rs::apis::default_api::get_gamenights;
use inquire::Select;
use super::*;
use crate::flows::view_gamenight::ViewGamenight;
use super::{abort::Abort, *};
#[derive(Clone)]
pub struct ListGamenights {
}
@@ -17,8 +21,16 @@ impl ListGamenights {
impl<'a> Flow<'a> for ListGamenights {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let response = get_gamenights(&state.configuration).await?;
println!("{:?}", response);
Ok((FlowOutcome::Successful, state))
let mut view_flows = response.into_iter().map(|gamenight| -> Box<dyn Flow<'a> + Send> {
Box::new(ViewGamenight::new(gamenight))
}).collect::<Vec<Box<dyn Flow<'a> + Send>>>();
view_flows.push(Box::new(Abort::new()));
let choice = Select::new("What gamenight would you like to view?", view_flows)
.prompt_skippable()?;
handle_choice_option(&choice, self, state).await
}
}

View File

@@ -4,6 +4,7 @@ use inquire::{Password, Text};
use super::*;
#[derive(Clone)]
pub struct Login {
}

View File

@@ -1,7 +1,7 @@
use super::{abort::Abort, list_gamenights::ListGamenights, login::Login, main_menu::MainMenu, *};
use super::{abort::Abort, add_gamenight::AddGamenight, list_gamenights::ListGamenights, login::Login, main_menu::MainMenu, *};
#[derive(Clone)]
pub struct Main {
login: Box<dyn for<'a> Flow<'a>>,
main_menu: MainMenu
@@ -11,6 +11,7 @@ impl Main {
pub fn new() -> Self {
let mut main_menu = MainMenu::new();
main_menu.menu.push(Box::new(ListGamenights::new()));
main_menu.menu.push(Box::new(AddGamenight::new()));
main_menu.menu.push(Box::new(Abort::new()));
Self {
login: Box::new(Login::new()),

View File

@@ -3,6 +3,7 @@ use inquire::{ui::RenderConfig, Select};
use super::*;
#[derive(Clone)]
pub struct MainMenu {
pub menu: Vec<Box<dyn for<'a> Flow<'a> + Send>>
}
@@ -22,7 +23,7 @@ unsafe impl Send for MainMenu {
#[async_trait]
impl<'a> Flow<'a> for MainMenu {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let choice = Select::new("What would you like to do?", self.menu)
let choice = Select::new("What would you like to do?", self.menu.clone())
.with_help_message("Select the action you want to take or quit the program")
.with_render_config(RenderConfig {
option_index_prefix: inquire::ui::IndexPrefix::Simple,
@@ -30,19 +31,7 @@ impl<'a> Flow<'a> for MainMenu {
})
.prompt_skippable()?;
if let Some(choice) = choice {
let (outcome, new_state) = choice.run(state).await?;
if outcome == FlowOutcome::Abort {
Ok((outcome, new_state))
}
else {
self.run(new_state).await
}
}
else {
return Ok((FlowOutcome::Abort, state))
}
handle_choice_option(&choice, self, state).await
}
}

View File

@@ -1,14 +1,18 @@
use std::fmt::Display;
use async_trait::async_trait;
use chrono::ParseError;
use gamenight_api_client_rs::apis::configuration::Configuration;
use inquire::InquireError;
use dyn_clone::DynClone;
pub mod main;
pub mod login;
mod login;
mod main_menu;
mod abort;
mod list_gamenights;
mod add_gamenight;
mod view_gamenight;
pub struct GamenightState {
configuration: Configuration,
@@ -43,6 +47,14 @@ impl<T> From<gamenight_api_client_rs::apis::Error<T>> for FlowError {
}
}
impl From<ParseError> for FlowError {
fn from(value: ParseError) -> Self {
Self {
error: value.to_string()
}
}
}
#[derive(PartialEq)]
pub enum FlowOutcome {
Successful,
@@ -53,7 +65,29 @@ pub enum FlowOutcome {
type FlowResult<'a> = Result<(FlowOutcome, &'a mut GamenightState), FlowError>;
dyn_clone::clone_trait_object!(for<'a> Flow<'a>);
#[async_trait]
pub trait Flow<'a>: Sync + Display {
pub trait Flow<'a>: Sync + Display + DynClone + Send {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a>;
}
async fn handle_choice<'a>(choice: &Box<dyn Flow<'a> + Send>, flow: &dyn Flow<'a>, state: &'a mut GamenightState) -> FlowResult<'a> {
let (outcome, new_state) = choice.run(state).await?;
if outcome == FlowOutcome::Abort {
Ok((FlowOutcome::Successful, new_state))
}
else {
flow.run(new_state).await
}
}
async fn handle_choice_option<'a>(choice: &Option<Box<dyn Flow<'a> + Send>>, flow: &dyn Flow<'a>, state: &'a mut GamenightState) -> FlowResult<'a> {
if let Some(choice) = choice {
handle_choice(&choice, flow, state).await
}
else {
Ok((FlowOutcome::Abort, state))
}
}

View File

@@ -0,0 +1,43 @@
use chrono::DateTime;
use gamenight_api_client_rs::models::Gamenight;
use super::*;
#[derive(Clone)]
pub struct ViewGamenight {
gamenight: Gamenight
}
impl ViewGamenight {
pub fn new(gamenight: Gamenight) -> Self {
Self {
gamenight
}
}
pub fn gamenight_localtime(&self) -> Result<String, FlowError> {
let datetime = DateTime::parse_from_rfc3339(&self.gamenight.datetime)?;
let offset = *chrono::offset::Local::now().offset();
Ok(format!("{}", datetime.naive_local().checked_add_offset(offset).unwrap().format("%d-%m-%Y %H:%M")))
}
}
#[async_trait]
impl<'a> Flow<'a> for ViewGamenight {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
println!("Name: {}", self.gamenight.name);
println!("When: {}", self.gamenight_localtime()?);
Ok((FlowOutcome::Successful, state))
}
}
impl Display for ViewGamenight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.gamenight.name, self.gamenight.datetime)
}
}