- Added field public - Added optional field available_on - Added optional field deadline - Updated tests and CLI to use these fields
247 lines
8.2 KiB
Rust
247 lines
8.2 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use clap::{Parser,Subcommand,Args,ValueEnum};
|
|
use serde::Deserialize;
|
|
use squad_quest::{config::Config,quest::{Quest,QuestDifficulty as LibQuestDifficulty}};
|
|
use toml::value::Date;
|
|
|
|
#[derive(Deserialize)]
|
|
struct DateWrapper {
|
|
date: Date
|
|
}
|
|
|
|
fn parse_date(arg: &str) -> Result<Date,toml::de::Error> {
|
|
let toml_str = format!("date = {arg}");
|
|
let wrapper: DateWrapper = toml::from_str(&toml_str)?;
|
|
Ok(wrapper.date)
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
#[command(version, about, long_about = None)]
|
|
#[command(propagate_version = true)]
|
|
struct Cli {
|
|
/// Path to config
|
|
#[arg(short, long)]
|
|
config: PathBuf,
|
|
/// Object to make operation on
|
|
#[command(subcommand)]
|
|
command: Objects,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Objects {
|
|
/// Operations on the quests
|
|
#[command(subcommand)]
|
|
Quest(QuestCommands)
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
|
enum QuestDifficulty {
|
|
/// Easy quest
|
|
Easy,
|
|
/// Normal quest
|
|
Normal,
|
|
/// Hard quest
|
|
Hard,
|
|
/// Special case of hard quests.
|
|
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)]
|
|
enum QuestCommands {
|
|
/// List available quests
|
|
List(QuestListArgs),
|
|
/// Create new quest and automatically assign it id
|
|
Create(QuestCreateArgs),
|
|
/// Update existing quest
|
|
Update(QuestUpdateArgs),
|
|
/// Delete quest
|
|
Delete(QuestDeleteArgs),
|
|
}
|
|
|
|
#[derive(Args)]
|
|
struct QuestListArgs {
|
|
/// Only list id and name of the quest
|
|
#[arg(short, long)]
|
|
short: bool
|
|
}
|
|
|
|
#[derive(Args)]
|
|
struct QuestCreateArgs {
|
|
/// Difficulty of the quest
|
|
#[arg(value_enum)]
|
|
difficulty: QuestDifficulty,
|
|
/// Reward for the quest
|
|
reward: u32,
|
|
/// Name of the quest
|
|
name: String,
|
|
/// Visible description of the quest
|
|
description: String,
|
|
/// Answer for the quest for admins
|
|
answer: String,
|
|
/// Create quest and make it public immediately
|
|
#[arg(short,long)]
|
|
public: bool,
|
|
/// Make quest available on date (format = YYYY-MM-DD, ex. 2025-12-24)
|
|
#[arg(short,long,value_parser = parse_date)]
|
|
available: Option<Date>,
|
|
/// Quest expiration date (format = YYYY-MM-DD, ex. 2025-12-24)
|
|
#[arg(short,long,value_parser = parse_date)]
|
|
deadline: Option<Date>,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
struct QuestUpdateArgs {
|
|
/// Id of the quest to update
|
|
id: u16,
|
|
/// Difficulty of the quest
|
|
#[arg(value_enum,long)]
|
|
difficulty: Option<QuestDifficulty>,
|
|
/// Reward for the quest
|
|
#[arg(long)]
|
|
reward: Option<u32>,
|
|
/// Name of the quest
|
|
#[arg(long)]
|
|
name: Option<String>,
|
|
/// Visible description of the quest
|
|
#[arg(long)]
|
|
description: Option<String>,
|
|
/// Answer for the quest for admins
|
|
#[arg(long)]
|
|
answer: Option<String>,
|
|
/// Create quest and make it public immediately
|
|
#[arg(long)]
|
|
public: Option<bool>,
|
|
/// Make quest available on date (format = YYYY-MM-DD, ex. 2025-12-24)
|
|
#[arg(long,value_parser = parse_date)]
|
|
available: Option<Date>,
|
|
/// Quest expiration date (format = YYYY-MM-DD, ex. 2025-12-24)
|
|
#[arg(long,value_parser = parse_date)]
|
|
deadline: Option<Date>,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
struct QuestDeleteArgs {
|
|
/// Id of the quest to delete
|
|
id: u16
|
|
}
|
|
|
|
fn print_quest_short(quest: &Quest) {
|
|
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(),
|
|
public: args.public,
|
|
available_on: args.available.clone(),
|
|
deadline: args.deadline.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()),
|
|
public: args.public.unwrap_or(quest.public),
|
|
available_on: args.available.clone().or(quest.available_on.clone()),
|
|
deadline: args.deadline.clone().or(quest.deadline.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),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|