use std::{fmt::Display, str::FromStr};
use anyhow::Result;
use dynasty_api::directory::DirectoryKind;
use lazy_regex::regex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsedArgumentValue {
Chapter(String),
#[cfg(feature = "search")]
Search(SearchValue),
Tag(TagValue),
}
#[cfg(feature = "search")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchValue {
pub query: String,
pub sort: Option<dynasty_api::search::SearchSort>,
pub categories: Vec<dynasty_api::search::SearchCategory>,
}
#[cfg(feature = "search")]
impl TryFrom<crate::search::SearchCommand> for SearchValue {
type Error = anyhow::Error;
fn try_from(value: crate::search::SearchCommand) -> Result<Self, Self::Error> {
let query = if let Some(query) = value.query {
query
} else {
dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default())
.with_prompt("query")
.report(false)
.interact_text()?
};
let sort = value.sort.map(|i| i.0);
let categories = value.categories.iter().map(|i| i.0).collect();
Ok(SearchValue {
query,
sort,
categories,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TagValue {
pub kind: DirectoryKind,
pub permalink: String,
}
impl Display for TagValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.kind, self.permalink)
}
}
impl Display for ParsedArgumentValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
ParsedArgumentValue::Chapter(value) => value.fmt(f),
ParsedArgumentValue::Tag(value) => value.fmt(f),
#[cfg(feature = "search")]
ParsedArgumentValue::Search(value) => value.query.fmt(f),
}
}
}
impl FromStr for ParsedArgumentValue {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let re = regex!(
r"(?x)
^
(?:https?://dynasty-scans.com)
/?
(?P<directory>\w+)
/?
(?P<permalink>\w+)?
/?
$"
);
if let Some(captures) = re.captures(s) {
let directory = captures.name("directory").unwrap().as_str();
if let Some(permalink_match) = captures.name("permalink") {
if directory == "chapters" {
return Ok(ParsedArgumentValue::Chapter(
permalink_match.as_str().to_string(),
));
} else if let Ok(kind) = directory.parse::<DirectoryKind>() {
return Ok(ParsedArgumentValue::Tag(TagValue {
kind,
permalink: permalink_match.as_str().to_string(),
}));
}
}
}
Err(anyhow::anyhow!("not a valid Dynasty Reader URL"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_parse_dynasty_api_argument() -> Result<()> {
let predicates = [
(
"https://dynasty-scans.com/series/liar_satsuki_can_see_death",
ParsedArgumentValue::Tag(TagValue {
kind: DirectoryKind::Series,
permalink: "liar_satsuki_can_see_death".to_string(),
}),
),
(
"https://dynasty-scans.com/chapters/kiss_distance",
ParsedArgumentValue::Chapter("kiss_distance".to_string()),
),
];
for (left, right) in predicates {
assert_eq!(left.parse::<ParsedArgumentValue>()?, right);
}
Ok(())
}
#[test]
fn should_fail_parse_dynasty_api_argument() -> Result<()> {
let predicates = [
"https://google.com/a/b", "anytextorwhateverthisisn", "https://dynasty-scans.com/tags", ];
for predicate in predicates {
assert!(predicate.parse::<ParsedArgumentValue>().is_err())
}
Ok(())
}
}