aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs63
-rw-r--r--src/output.rs9
2 files changed, 57 insertions, 15 deletions
diff --git a/src/main.rs b/src/main.rs
index 6194ef0..d3839b2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,27 +29,43 @@ use clap::{App, Arg, ArgMatches};
use api::{Api, Profession, Skill};
use bt::{BuildTemplate, ExtraData, Legend, TraitChoice, Traitline};
+/// The name of this application.
+///
+/// This is used for example in the cache path.
const APP_NAME: &str = "kondou";
+/// Return value indicating that a requested resource could not be found.
#[derive(Debug, Clone)]
-enum Error {
- ProfessionNotFound(String),
- SkillIdNotFound(u32),
- SkillNotFound(String),
- SpecializationNotFound(String),
+enum NotFound {
+ /// Used when the requested profession can not be found.
+ ///
+ /// The argument is the requested profession.
+ Profession(String),
+ /// Used when a skill given by its ID could not be found.
+ ///
+ /// The argument is the requested skill id.
+ SkillId(u32),
+ /// Used when a skill given by its name could not be found.
+ ///
+ /// The argument is the requested skill name.
+ SkillName(String),
+ /// Used when a specialization could not be found.
+ ///
+ /// The argument is the requested specialization.
+ Specialization(String),
}
-impl fmt::Display for Error {
+impl fmt::Display for NotFound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
- Error::ProfessionNotFound(ref name) => {
+ NotFound::Profession(ref name) => {
write!(f, "the profession '{}' could not be found", name)
}
- Error::SkillIdNotFound(id) => write!(f, "the skill with the ID '{}' was not found", id),
- Error::SkillNotFound(ref name) => {
+ NotFound::SkillId(id) => write!(f, "the skill with the ID '{}' was not found", id),
+ NotFound::SkillName(ref name) => {
write!(f, "the skill with the name '{}' was not found", name)
}
- Error::SpecializationNotFound(ref name) => write!(
+ NotFound::Specialization(ref name) => write!(
f,
"the specialization with the name '{}' was not found",
name
@@ -58,22 +74,32 @@ impl fmt::Display for Error {
}
}
-impl StdError for Error {}
+impl StdError for NotFound {}
+/// A top-level result.
+///
+/// We use a dynamic error dispatch here for two reasons:
+/// 1. The only thing that we really do here is displaying the error to the user.
+/// 2. We don't want yet another error kind with a lot of kinds.
type MainResult<T> = Result<T, Box<dyn StdError>>;
+/// Find the profession by the given name.
fn find_profession(api: &mut Api, name: &str) -> MainResult<Profession> {
let profession_ids = api.get_profession_ids()?;
let lower_name = name.to_lowercase();
let profession_id = profession_ids
.iter()
.find(|id| id.to_lowercase() == lower_name)
- .ok_or_else(|| Error::ProfessionNotFound(name.to_owned()))?
+ .ok_or_else(|| NotFound::Profession(name.to_owned()))?
.clone();
Ok(api.get_professions(&[profession_id])?.remove(0))
}
+/// Resolve a skill.
+///
+/// `text` can either be a skill name, in which case all skills of the profession will be searched.
+/// Alternatively, it can also be a numeric ID, in which case it will be requested directly.
fn resolve_skill(api: &mut Api, profession: &Profession, text: &str) -> MainResult<Skill> {
// Try it as an ID first
let numeric = text.parse::<u32>();
@@ -82,7 +108,7 @@ fn resolve_skill(api: &mut Api, profession: &Profession, text: &str) -> MainResu
if exists {
return Ok(api.get_skills(&[num_id])?.remove(0));
} else {
- return Err(Error::SkillIdNotFound(num_id).into());
+ return Err(NotFound::SkillId(num_id).into());
}
}
@@ -93,9 +119,12 @@ fn resolve_skill(api: &mut Api, profession: &Profession, text: &str) -> MainResu
all_skills
.into_iter()
.find(|s| s.name.to_lowercase().contains(&lower_text))
- .ok_or_else(|| Error::SkillNotFound(text.to_owned()).into())
+ .ok_or_else(|| NotFound::SkillName(text.to_owned()).into())
}
+/// Resolve a traitline.
+///
+/// `text` must be in the `"name:choice1:choice2:choice3"` format.
fn resolve_traitline(api: &mut Api, profession: &Profession, text: &str) -> MainResult<Traitline> {
let parts = text.split(':').collect::<Vec<_>>();
assert_eq!(
@@ -110,7 +139,7 @@ fn resolve_traitline(api: &mut Api, profession: &Profession, text: &str) -> Main
.get_specializations(&profession.specializations)?
.into_iter()
.find(|s| s.name.to_lowercase() == lower_name)
- .ok_or_else(|| Error::SpecializationNotFound(name.to_owned()))?;
+ .ok_or_else(|| NotFound::Specialization(name.to_owned()))?;
let mut choices = [TraitChoice::None; 3];
for (i, text_choice) in parts.iter().skip(1).enumerate() {
@@ -122,6 +151,7 @@ fn resolve_traitline(api: &mut Api, profession: &Profession, text: &str) -> Main
Ok((spec, choices))
}
+/// Create the build template by manually combining the given skills/traitlines from the CLI.
fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplate> {
let requested_profession = matches
.value_of("profession")
@@ -192,11 +222,13 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplat
Ok(build)
}
+/// Create the build template by parsing a chat link.
fn run_chatlink(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplate> {
let link = matches.value_of("chatlink").unwrap();
Ok(BuildTemplate::from_chatlink(api, link)?)
}
+/// Make sure a traitline is in the `"traitline:choice1:choice2:choice3"` format.
fn validate_traitline_format(input: String) -> Result<(), String> {
let parts = input.split(':').collect::<Vec<_>>();
if parts.len() != 4 {
@@ -216,6 +248,7 @@ fn validate_traitline_format(input: String) -> Result<(), String> {
Ok(())
}
+/// Make sure a legend is valid.
fn validate_legend(input: String) -> Result<(), String> {
input
.parse::<Legend>()
diff --git a/src/output.rs b/src/output.rs
index 2a2546f..f6e9bc4 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -1,3 +1,4 @@
+//! Functions for console output.
use super::{
api,
bt::{BuildTemplate, Traitline},
@@ -7,6 +8,9 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
const HEADER_COLOR: Color = Color::Cyan;
+/// Format a skill for displaying.
+///
+/// This is just the skill name (if given).
fn format_skill(skill: &Option<api::Skill>) -> String {
match *skill {
None => "none".to_owned(),
@@ -14,6 +18,7 @@ fn format_skill(skill: &Option<api::Skill>) -> String {
}
}
+/// Format a traitline for displaying.
fn format_traitline(traitline: &Option<Traitline>) -> String {
match *traitline {
None => "none".to_owned(),
@@ -24,6 +29,7 @@ fn format_traitline(traitline: &Option<Traitline>) -> String {
}
}
+/// Show a build template to the standard output stream.
pub fn show_build_template(build: &BuildTemplate) -> io::Result<()> {
let mut stdout = StandardStream::stdout(ColorChoice::Auto);
let mut color_spec = ColorSpec::new();
@@ -58,6 +64,9 @@ pub fn show_build_template(build: &BuildTemplate) -> io::Result<()> {
Ok(())
}
+/// Show an error to the standard error stream.
+///
+/// This will also show the chain of errors that lead up to this error, if available.
pub fn show_error<E: Error + ?Sized>(error: &E) -> io::Result<()> {
let mut error_color = ColorSpec::new();
error_color.set_fg(Some(Color::Red));