Compare commits

..

3 commits

Author SHA1 Message Date
96235086d7 test: fixed crud test 2025-11-29 14:46:42 +03:00
d61011f5ea feat: CLI quest CRUD
- Quest creation
- Quest list retrieving
- Quest update
- Quest deletion
2025-11-29 14:40:23 +03:00
2e14614bdf feat: full_quests_path
- Added Config::full_quests_path
- Fixed quests saving in parent folder
2025-11-29 14:12:07 +03:00
4 changed files with 147 additions and 14 deletions

View file

@ -1,6 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Parser,Subcommand,Args,ValueEnum}; use clap::{Parser,Subcommand,Args,ValueEnum};
use squad_quest::{config::Config,quest::{Quest,QuestDifficulty as LibQuestDifficulty}};
#[derive(Parser)] #[derive(Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -33,6 +34,17 @@ enum QuestDifficulty {
Secret Secret
} }
impl From<QuestDifficulty> for LibQuestDifficulty {
fn from(value: QuestDifficulty) -> Self {
match value {
QuestDifficulty::Easy => LibQuestDifficulty::Easy,
QuestDifficulty::Normal => LibQuestDifficulty::Normal,
QuestDifficulty::Hard => LibQuestDifficulty::Hard,
QuestDifficulty::Secret => LibQuestDifficulty::Secret,
}
}
}
#[derive(Subcommand)] #[derive(Subcommand)]
enum QuestCommands { enum QuestCommands {
/// List available quests /// List available quests
@ -72,16 +84,20 @@ struct QuestUpdateArgs {
/// Id of the quest to update /// Id of the quest to update
id: u16, id: u16,
/// Difficulty of the quest /// Difficulty of the quest
#[arg(value_enum)] #[arg(value_enum,long)]
difficulty: QuestDifficulty, difficulty: Option<QuestDifficulty>,
/// Reward for the quest /// Reward for the quest
reward: u32, #[arg(long)]
reward: Option<u32>,
/// Name of the quest /// Name of the quest
name: String, #[arg(long)]
name: Option<String>,
/// Visible description of the quest /// Visible description of the quest
description: String, #[arg(long)]
description: Option<String>,
/// Answer for the quest for admins /// Answer for the quest for admins
answer: String #[arg(long)]
answer: Option<String>
} }
#[derive(Args)] #[derive(Args)]
@ -90,6 +106,105 @@ struct QuestDeleteArgs {
id: u16 id: u16
} }
fn main() { fn print_quest_short(quest: &Quest) {
let _cli = Cli::parse(); println!("Quest #{}: {}", quest.id, quest.name);
}
fn print_quest_long(quest: &Quest) {
print_quest_short(quest);
println!("Difficulty: {:?}", quest.difficulty);
println!("Description:\n{}", quest.description);
println!("Answer:\n{}", quest.answer);
}
fn main() {
let cli = Cli::parse();
let config = Config::load(cli.config.clone());
match &cli.command {
Objects::Quest(commands) => {
match commands {
QuestCommands::List(args) => {
let quests = config.load_quests();
for quest in quests {
if args.short {
print_quest_short(&quest);
} else {
print_quest_long(&quest);
}
}
},
QuestCommands::Create(args) => {
let mut quests = config.load_quests();
quests.sort_by(|a,b| a.id.cmp(&b.id));
let next_id = match quests.last() {
Some(quest) if quest.id == u16::MAX => {
panic!("Error: quest list contains quest with u16::MAX id.");
}
Some(quest) => quest.id + 1u16,
None => 0u16
};
let path = config.full_quests_path();
let mut quest_path = path.clone();
quest_path.push(format!("{next_id}.toml"));
match std::fs::exists(&quest_path) {
Ok(exists) => {
if exists {
panic!("Error: {:?} is not empty.", quest_path);
}
},
Err(error) => {
panic!("Error while retrieving {:?}: {}.", quest_path, error);
}
}
let quest = Quest {
id: next_id,
difficulty: args.difficulty.into(),
reward: args.reward,
name: args.name.clone(),
description: args.description.clone(),
answer: args.answer.clone(),
};
if let Err(error) = quest.save(path) {
eprintln!("Error while saving quest: {error}.");
} else {
println!("Successfully saved quest #{}.", quest.id);
}
},
QuestCommands::Update(args) => {
let quests = config.load_quests();
let Some(quest) = quests.iter().find(|q| q.id == args.id) else {
panic!("Error: Quest #{} not found.", args.id);
};
let quest = Quest {
id: args.id,
difficulty: match args.difficulty {
Some(diff) => diff.into(),
None => quest.difficulty
},
reward: args.reward.unwrap_or(quest.reward),
name: args.name.clone().unwrap_or(quest.name.clone()),
description: args.description.clone().unwrap_or(quest.description.clone()),
answer: args.answer.clone().unwrap_or(quest.answer.clone())
};
let path = config.full_quests_path();
match quest.save(path) {
Ok(_) => println!("Updated quest #{}", quest.id),
Err(error) => eprintln!("Error while updating quest: {error}")
}
},
QuestCommands::Delete(args) => {
let mut path = config.full_quests_path();
path.push(format!("{}.toml", args.id));
match Quest::delete(path) {
Ok(_) => println!("Successfully deleted quest #{}", args.id),
Err(error) => eprintln!("Error deleting quest #{}: {}", args.id, error),
}
},
}
}
}
} }

View file

@ -88,6 +88,24 @@ impl Config {
} }
} }
/// Returns full path to quests folder
/// This path will be relative to $PWD, not to config.
///
/// # Examples
/// ```rust
/// use squad_quest::config::Config;
///
/// let path = "cfg/config.toml".into();
/// let config = Config::load(path);
///
/// let quests_path = config.full_quests_path();
/// ```
pub fn full_quests_path(&self) -> PathBuf {
let mut path = self.path.clone();
path.push(self.quests_path.clone());
path
}
/// Load [Vec]<[Quest]> from quests folder. /// Load [Vec]<[Quest]> from quests folder.
/// Also logs errors and counts successfully loaded quests. /// Also logs errors and counts successfully loaded quests.
/// ///
@ -107,8 +125,7 @@ impl Config {
pub fn load_quests(&self) -> Vec<Quest> { pub fn load_quests(&self) -> Vec<Quest> {
let mut out_vec = Vec::new(); let mut out_vec = Vec::new();
let mut path = self.path.clone(); let path = self.full_quests_path();
path.push(self.quests_path.clone());
match fs::read_dir(path) { match fs::read_dir(path) {
Ok(iter) => { Ok(iter) => {

View file

@ -8,7 +8,7 @@ use serde::{ Serialize, Deserialize };
use error::QuestError; use error::QuestError;
/// Difficulty of the quest /// Difficulty of the quest
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub enum QuestDifficulty { pub enum QuestDifficulty {
/// Easy quest /// Easy quest
Easy, Easy,
@ -154,7 +154,8 @@ impl Quest {
/// ``` /// ```
pub fn save(&self, path: PathBuf) -> Result<(), QuestError> { pub fn save(&self, path: PathBuf) -> Result<(), QuestError> {
let filename = format!("{}.toml", self.id); let filename = format!("{}.toml", self.id);
let full_path = path.with_file_name(filename); let mut full_path = path;
full_path.push(filename);
let str = match toml::to_string_pretty(&self) { let str = match toml::to_string_pretty(&self) {
Ok(string) => string, Ok(string) => string,

View file

@ -21,9 +21,9 @@ fn quest_crud() -> Result<(), QuestError> {
let filename = format!("{}.toml", quest.id); let filename = format!("{}.toml", quest.id);
let delete_path = quests_path.with_file_name(filename); quests_path.push(filename);
Quest::delete(delete_path)?; Quest::delete(quests_path)?;
Ok(()) Ok(())
} }