use crate::langs::Language;
use crate::{genres::*, langs::*};
#[cfg(feature = "chrono")]
use chrono::{serde::ts_seconds, DateTime, Utc};
use itoa;
use serde::de::{MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{borrow::Cow, fmt, num::NonZeroU32};
#[derive(
Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord,
)]
pub struct Group<'a> {
pub id: u32,
pub name: Cow<'a, str>,
pub website: Option<Cow<'a, str>>,
}
impl<'a> From<(String, (&'a str, Option<&'a str>))> for Group<'a> {
fn from(g: (String, (&'a str, Option<&'a str>))) -> Self {
let website = (g.1).1.map(|s| Cow::Owned(s.to_owned()));
Group {
id: g.0.parse().unwrap_or_default(),
name: Cow::Owned((g.1).0.to_owned()),
website,
}
}
}
#[derive(
Serialize, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash,
)]
pub struct GroupID(u32);
#[derive(Default, PartialEq)]
pub struct Groups(Vec<Option<(String, Option<String>)>>);
impl<'a> Extend<Group<'a>> for Groups {
fn extend<T: IntoIterator<Item = Group<'a>>>(&mut self, iter: T) {
for grp in iter {
self.add_group(grp);
}
}
}
impl<'a> Extend<(String, (&'a str, Option<&'a str>))> for Groups {
fn extend<T: IntoIterator<Item = (String, (&'a str, Option<&'a str>))>>(
&mut self,
iter: T,
) {
self.extend(iter.into_iter().map(Group::from));
}
}
pub mod groups_seq {
use super::{Groups, GroupsSeqDeserializer};
use serde::{Deserializer, Serializer};
pub fn deserialize<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Groups, D::Error> {
let seq = d.deserialize_seq(GroupsSeqDeserializer)?;
let mut groups = Groups::default();
groups.extend(seq);
Ok(groups)
}
pub fn serialize<S: Serializer>(
groups: &Groups,
s: S,
) -> Result<S::Ok, S::Error> {
s.collect_seq(groups.compact_seq())
}
}
pub mod groups_map {
use super::{Groups, GroupsMapDeserializer};
use serde::{Deserializer, Serializer};
pub fn deserialize<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Groups, D::Error> {
let map = d.deserialize_map(GroupsMapDeserializer)?;
let mut groups = Groups::default();
groups.extend(map);
Ok(groups)
}
pub fn serialize<S: Serializer>(
groups: &Groups,
s: S,
) -> Result<S::Ok, S::Error> {
s.collect_map(groups.compact_map())
}
}
struct GroupsSeqDeserializer;
impl<'de> Visitor<'de> for GroupsSeqDeserializer {
type Value = GroupsSeq<'de>;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(
"sequence of groups (eg [{id: 1, name: \"Unknown\", website: null}])",
)
}
fn visit_seq<A: SeqAccess<'de>>(
self,
mut seq: A,
) -> Result<Self::Value, A::Error> {
let mut slf = GroupsSeq::default();
while let Some(value) = seq.next_element()? {
slf.0.push(value);
}
Ok(slf)
}
}
struct GroupsMapDeserializer;
impl<'de> Visitor<'de> for GroupsMapDeserializer {
type Value = GroupsMap<'de>;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(
"id-keyed map of group name-website pairs (eg {1: [\"Unknown\", null]})",
)
}
fn visit_map<A: MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut map_groups = GroupsMap::default();
while let Some(entry) = map.next_entry()? {
map_groups.0.push(entry);
}
Ok(map_groups)
}
}
impl fmt::Debug for Groups {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_set().entries(self.compact_seq()).finish()
}
}
impl From<u32> for GroupID {
fn from(group: u32) -> Self {
GroupID(group)
}
}
impl<'a> Group<'a> {
pub fn group_website(mut self, website: String) -> Self {
self.website = Some(website.into());
self
}
pub fn add_to_groups(self, groups: &mut Groups) {
groups.add_group(self);
}
}
impl GroupID {
pub fn group_name<'a>(self, name: String) -> Group<'a> {
Group {
id: self.0,
name: name.into(),
website: None,
}
}
pub fn group_name_and_website<'a>(
self,
name: String,
website: String,
) -> Group<'a> {
Group {
id: self.0,
name: name.into(),
website: Some(website.into()),
}
}
}
impl From<&GroupID> for usize {
fn from(v: &GroupID) -> usize {
v.0 as usize
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[repr(transparent)]
pub struct GroupsSeq<'a>(Vec<Group<'a>>);
impl<'a> GroupsSeq<'a> {
pub fn len(&self) -> u32 {
self.0.len() as u32
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'a> From<GroupsSeq<'a>> for Groups {
fn from(csg: GroupsSeq<'a>) -> Self {
let mut grps = Groups::default();
grps.extend(csg);
grps
}
}
impl<'a> IntoIterator for GroupsSeq<'a> {
type Item = Group<'a>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[derive(Debug, Default)]
#[repr(transparent)]
pub struct GroupsMap<'a>(Vec<(String, (&'a str, Option<&'a str>))>);
impl<'a> GroupsMap<'a> {
pub fn len(&self) -> u32 {
self.0.len() as u32
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'a> From<GroupsMap<'a>> for Groups {
fn from(cmg: GroupsMap<'a>) -> Self {
let mut grps = Groups::default();
grps.extend(cmg);
grps
}
}
impl<'a> Serialize for GroupsMap<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_map(self.0.iter().map(|(s, n)| (s.as_str(), n)))
}
}
impl<'de> Deserialize<'de> for GroupsMap<'de> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_map(GroupsMapDeserializer)
}
}
impl<'a> IntoIterator for GroupsMap<'a> {
type Item = (String, (&'a str, Option<&'a str>));
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Groups {
pub fn new() -> Self {
Groups(vec![None; 10_000])
}
pub fn compact_seq(&self) -> GroupsSeq {
GroupsSeq(
self
.0
.iter()
.zip(0..)
.skip(1)
.filter_map(|(ok, id)| {
ok.as_ref().map(|g| Group {
id,
name: Cow::Borrowed(&g.0),
website: g.1.as_ref().map(|s| s.into()),
})
})
.collect(),
)
}
pub fn compact_map(&self) -> GroupsMap {
GroupsMap(
self
.0
.iter()
.zip(0..)
.skip(1)
.filter_map(|(ok, id)| {
ok.as_ref().map(|g| {
(
id.to_string(),
(g.0.as_str(), g.1.as_ref().map(|w| w.as_str())),
)
})
})
.collect(),
)
}
fn filter_to_groups(&self, chs: &[Chapter]) -> Vec<bool> {
let mut ids: Vec<bool> = vec![true];
let mut ext = |n| {
if ids.len() < n {
let m = (ids.len()..=n).map(|_| false);
ids.extend(m);
}
ids[n] = true;
};
for Chapter { groups, .. } in chs {
let [a, b, c] = groups;
ext(a.into());
ext(b.into());
ext(c.into());
}
ids
}
pub fn compact_seq_with_ch(&self, chs: &[Chapter]) -> GroupsSeq {
let ids = self.filter_to_groups(chs);
GroupsSeq(
ids
.into_iter()
.zip(0..)
.skip(1)
.filter_map(|(b, id)| {
if b && id < self.len() {
let grp = unsafe { self.0.get_unchecked(id as usize) }.as_ref();
grp.map(|g| Group {
id,
name: Cow::Borrowed(&g.0),
website: g.1.as_ref().map(|s| s.into()),
})
} else {
None
}
})
.collect(),
)
}
pub fn compact_map_with_ch(&self, chs: &[Chapter]) -> GroupsMap {
let ids = self.filter_to_groups(chs);
GroupsMap(
ids
.into_iter()
.zip(0..)
.skip(1)
.filter_map(|(b, id)| {
if b && id < self.len() {
let g = unsafe { self.0.get_unchecked(id as usize) }.as_ref()?;
Some((
id.to_string(),
(g.0.as_str(), g.1.as_ref().map(|w| w.as_str())),
))
} else {
None
}
})
.collect(),
)
}
pub fn len(&self) -> u32 {
self.0.len() as u32
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn group_id(u: u32) -> GroupID {
GroupID(u)
}
fn add_group(&mut self, mut grp: Group) {
if self.len() < grp.id {
self.0.extend((self.len()..=grp.id).map(|_| None));
}
let entry = unsafe { self.0.get_unchecked_mut(grp.id as usize) };
if let Some(mut ent) = entry.as_mut() {
if ent.1.is_none() {
ent.1 = grp.website.take().map(|s| s.into());
}
} else {
let Group { name, website, .. } = grp;
*entry = Some((name.into(), website.map(|s| s.into())));
}
}
fn populate_groups_from(&mut self, ch: &crate::manga_api::Chapter) {
if !self.has(ch.group_id) {
GroupID(ch.group_id)
.group_name(ch.group_name.chars().collect())
.add_to_groups(self);
}
if let Some(group_name_2) = &ch.group_name_2 {
if !self.has(ch.group_id_2) {
GroupID(ch.group_id_2)
.group_name(group_name_2.chars().collect())
.add_to_groups(self);
}
}
if let Some(group_name_3) = &ch.group_name_3 {
if !self.has(ch.group_id_3) {
GroupID(ch.group_id_3)
.group_name(group_name_3.chars().collect())
.add_to_groups(self);
}
}
}
pub fn has(&self, gid: u32) -> bool {
if self.len() < gid {
false
} else {
unsafe { self.0.get_unchecked(gid as usize) }.is_some()
}
}
pub fn populate_website_from(&mut self, ch: &crate::chapter_api::Delayed) {
let id = ch.groups.group_id;
if self.len() < id {
self.0.extend((self.len()..=id).map(|_| None));
}
let group = unsafe { self.0.get_unchecked_mut(id as usize) };
if let Some(grp) = group.as_mut() {
let _ = grp
.1
.get_or_insert_with(|| ch.group_website.chars().collect());
}
}
pub fn get(&self, id: u32) -> Option<Group> {
if self.len() < id {
None
} else {
unsafe { self.0.get_unchecked(id as usize) }
.as_ref()
.map(|g| Group {
id,
name: Cow::Borrowed(&g.0),
website: g.1.as_ref().map(|s| s.into()),
})
}
}
}
#[derive(Default, Serialize, Clone, Debug)]
pub struct MangaData {
pub title: String,
pub status: crate::manga_api::Status,
pub genres: GenreSet,
pub desc: String,
pub authors: Vec<String>,
pub artists: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_chapter: Option<ChapterNumber>,
pub lang: Language,
pub hentai: bool,
pub links: Links,
pub chapters: Vec<Chapter>,
#[serde(skip)]
ch_langs: Vec<(u32, Language)>,
}
impl MangaData {
pub fn from_manga(
crate::manga_api::Data {
manga,
chapters: ch,
}: crate::manga_api::Data,
grps: &mut Groups,
) -> Self {
let comma_split = |v: &str| {
let s = v.trim();
if s.is_empty() {
None
} else {
Some(s.to_owned())
}
};
let (chapters, ch_langs) = if ch.is_empty() {
(vec![], vec![])
} else {
let mut chs: Vec<(ChapterNumber, Chapter)> = Vec::with_capacity(ch.len());
let ch_sortable = ch.into_iter().map(|(id, ch)| {
let chn = ch.chapter.parse().unwrap_or_default();
grps.populate_groups_from(&ch);
(chn, (id, ch).into())
});
chs.extend(ch_sortable);
chs.sort_unstable_by(chapter_sort);
chs.into_iter().fold(
(vec![], vec![]),
|(mut ch_l, mut ch_n), (chn, ch)| {
ch_n.push((chn.as_u32(), ch.lang));
ch_l.push(ch);
(ch_l, ch_n)
},
)
};
let last_chapter = if let Ok(ch) = manga.last_chapter.parse() {
if ch == ChapterNumber::Empty || ch == ChapterNumber::Simple(0) {
None
} else {
Some(ch)
}
} else {
None
};
MangaData {
title: manga.title,
status: manga.status,
desc: manga.description,
genres: manga.genres.into(),
authors: manga.author.split(',').filter_map(comma_split).collect(),
artists: manga.artist.split(',').filter_map(comma_split).collect(),
lang: manga.lang,
hentai: manga.hentai.into(),
links: manga.links.map_or_else(Default::default, |ln| ln.into()),
last_chapter,
chapters,
ch_langs,
}
}
pub fn filter_chap_by_langs<L: Into<LanguageSet>>(
&self,
l: L,
) -> Vec<&Chapter> {
let langs = l.into();
self
.chapters
.iter()
.filter(|ch| langs.has(ch.lang))
.collect()
}
}
pub type Hole = std::ops::Range<u32>;
#[derive(Default, Serialize, Clone, Debug, PartialEq)]
pub struct Links {
#[serde(skip_serializing_if = "Option::is_none")]
pub book_walker: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manga_updates: Option<NonZeroU32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub novel_updates: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amazon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cd_japan: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ebook_japan: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub my_anime_list: Option<NonZeroU32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub english_translation: Option<String>,
}
impl From<crate::manga_api::Links> for Links {
fn from(ln: crate::manga_api::Links) -> Self {
let manga_updates: u32 = if let Some(n) = ln.manga_updates {
n.parse().unwrap_or_default()
} else {
0
};
let my_anime_list: u32 = if let Some(n) = ln.my_anime_list {
n.parse().unwrap_or_default()
} else {
0
};
Links {
manga_updates: NonZeroU32::new(manga_updates),
my_anime_list: NonZeroU32::new(my_anime_list),
book_walker: ln.book_walker,
novel_updates: ln.novel_updates,
amazon: ln.amazon,
cd_japan: ln.cd_japan,
ebook_japan: ln.ebook_japan,
raw: ln.raw,
english_translation: ln.english_translation,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ChapterNumber {
Empty,
Simple(u32),
Dual(u32, u32),
Range(u32, u32),
FuckYou(u32, String),
SuperFuckYou(String),
}
impl ChapterNumber {
pub fn as_u32(&self) -> u32 {
use ChapterNumber::*;
match self {
Simple(n) => *n,
Dual(n, _) => *n,
Range(n, _) => *n,
FuckYou(n, _) => *n,
_ => 0,
}
}
}
impl Serialize for ChapterNumber {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use ChapterNumber::*;
match self {
Empty => s.serialize_none(),
Simple(n) => s.serialize_u32(*n),
Dual(a, b) => {
let mut buf = [0u8; 8];
let a_off = itoa::write(&mut buf[..], *a).unwrap();
buf[a_off] = b'.';
let b_off = itoa::write(&mut buf[(a_off + 1)..], *b).unwrap();
let len = a_off + b_off + 1;
let st = unsafe { std::str::from_utf8_unchecked(&buf[..len]) };
let flt = st.parse().unwrap();
s.serialize_f64(flt)
}
Range(a, b) => {
let mut buf = [0u8; 8];
let a_off = itoa::write(&mut buf[..], *a).unwrap();
buf[a_off] = b'-';
let b_off = itoa::write(&mut buf[(a_off + 1)..], *b).unwrap();
let len = a_off + b_off + 1;
let st = unsafe { std::str::from_utf8_unchecked(&buf[..len]) };
s.serialize_str(st)
}
FuckYou(n, st) => {
let mut buf = Vec::with_capacity(8);
let l = itoa::write(&mut buf, *n).unwrap();
buf[l] = b'.';
buf.extend_from_slice(&st.as_bytes());
s.serialize_str(unsafe { std::str::from_utf8_unchecked(&buf) })
}
SuperFuckYou(st) => s.serialize_str(&st),
}
}
}
impl Default for ChapterNumber {
fn default() -> Self {
ChapterNumber::Empty
}
}
use std::cmp::Ordering::{self, Equal, Greater, Less};
impl std::str::FromStr for ChapterNumber {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<ChapterNumber, Self::Err> {
use ChapterNumber::*;
let s = s.trim();
if s.is_empty() {
return Ok(Empty);
}
let mmkay = |v: &str| {
let v = v.trim();
if v.is_empty() {
None
} else {
Some(v.to_owned())
}
};
let mut alt = s.splitn(2, '.');
let a = alt.next().and_then(mmkay);
let b = alt.next().and_then(mmkay);
if let (Some(a_n), None) = (a, b) {
if let Ok(mhm) = a_n.parse() {
return Ok(Simple(mhm));
}
}
let mut alt = s.splitn(2, '.');
let a = alt.next().and_then(mmkay);
let b = alt.next().and_then(mmkay);
if let (Some(a_n), Some(b_n)) = (a, b) {
if let Ok(mhm) = a_n.parse() {
return if let Ok(owo) = b_n.parse() {
Ok(Dual(mhm, owo))
} else {
Ok(FuckYou(mhm, b_n.to_owned()))
};
}
}
let mut alt = s.splitn(2, '-');
let a = alt.next().and_then(mmkay);
let b = alt
.next()
.and_then(mmkay)
.map(|v: String| v.trim_start_matches('-').to_owned());
if let (Some(a_r), Some(b_r)) = (a, b) {
if let Ok(mhm) = a_r.parse() {
return if let Ok(owo) = b_r.parse() {
Ok(Range(mhm, owo))
} else {
Ok(FuckYou(mhm, b_r.to_owned()))
};
}
}
Ok(SuperFuckYou(s.to_owned()))
}
}
impl PartialOrd for ChapterNumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use ChapterNumber::*;
match (self, other) {
(Empty, Empty) => Some(Equal),
(Empty, _) => Some(Less),
(_, Empty) => Some(Greater),
(Simple(a), Simple(b)) => a.partial_cmp(b),
(Dual(a, b), Dual(c, d)) => a
.partial_cmp(c)
.map(|k| if k == Equal { b.cmp(d) } else { k }),
(Range(a, b), Range(c, d)) => a
.partial_cmp(c)
.map(|k| if k == Equal { b.cmp(d) } else { k }),
(FuckYou(a, b), FuckYou(c, d)) => a
.partial_cmp(c)
.map(|k| if k == Equal { b.cmp(d) } else { k }),
(Simple(a), Dual(b, c)) => Some(match a.cmp(b) {
Equal => c.cmp(&0),
d => d,
}),
(Simple(a), Range(b, c)) => Some(match a.cmp(b) {
Equal => c.cmp(a),
d => d,
}),
(Range(b, c), Simple(a)) => Some(match b.cmp(a) {
Equal => a.cmp(c),
d => d,
}),
(Dual(a, b), Simple(c)) => Some(match a.cmp(c) {
Equal => 0.cmp(b),
d => d,
}),
(Dual(a, b), FuckYou(c, _)) => a
.partial_cmp(c)
.map(|k| if k == Equal { b.cmp(&0) } else { k }),
(Dual(a, _), Range(b, c)) => Some(match a.cmp(b) {
Equal => c.cmp(a),
d => d,
}),
(Range(b, c), Dual(a, _)) => Some(match b.cmp(a) {
Equal => a.cmp(c),
d => d,
}),
(FuckYou(a, _), Dual(b, c)) => a
.partial_cmp(b)
.map(|k| if k == Equal { 0.cmp(c) } else { k }),
(FuckYou(a, _), Range(b, c)) => a
.partial_cmp(b)
.map(|k| if k == Equal { 0.cmp(c) } else { k }),
(Range(b, c), FuckYou(a, _)) => b
.partial_cmp(a)
.map(|k| if k == Equal { c.cmp(&0) } else { k }),
(FuckYou(a, _), Simple(b)) => a.partial_cmp(b),
(Simple(a), FuckYou(b, _)) => a.partial_cmp(b),
(SuperFuckYou(a), SuperFuckYou(b)) => a.partial_cmp(b),
(SuperFuckYou(_), _) => Some(Less),
(_, SuperFuckYou(_)) => Some(Greater),
}
}
}
#[derive(Serialize, Clone, Debug)]
pub struct Chapter {
pub vol: Option<u32>,
pub chapter: Option<String>,
pub title: Option<String>,
pub groups: [GroupID; 3],
pub id: u32,
pub lang: Language,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds", default = "Utc::now")]
pub timestamp: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub timestamp: u64,
}
fn chapter_sort(
(a_chn, a): &(ChapterNumber, Chapter),
(b_chn, b): &(ChapterNumber, Chapter),
) -> Ordering {
macro_rules! cmp {
($a:expr, $b:expr) => {
let cmp = $a.cmp(&$b);
if cmp != Equal {
return cmp;
}
};
}
match (a.vol, b.vol) {
(Some(a_v), Some(b_v)) => {
cmp!(a_v, b_v);
}
(Some(_), None) => return Less,
(None, Some(_)) => return Greater,
_ => {}
}
match a_chn.partial_cmp(&b_chn) {
None | Some(Equal) => {}
Some(c) => {
return c;
}
}
cmp!(a.groups, b.groups);
cmp!(a.lang, b.lang);
a.id.cmp(&b.id)
}
fn from_gids(a: u32, b: u32, c: u32) -> [GroupID; 3] {
[GroupID(a), GroupID(b), GroupID(c)]
}
impl From<(String, crate::manga_api::Chapter)> for Chapter {
fn from((id, ch): (String, crate::manga_api::Chapter)) -> Self {
Chapter {
vol: ch.volume.trim().parse().ok(),
chapter: if ch.chapter.is_empty() {
None
} else {
Some(ch.chapter)
},
lang: ch.lang,
title: if ch.title.is_empty() {
None
} else {
Some(ch.title)
},
groups: from_gids(ch.group_id, ch.group_id_2, ch.group_id_3),
id: id.parse().unwrap_or_default(),
timestamp: ch.timestamp,
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::manga_api::Manga;
use crate::test_data::*;
use serde_json::{from_str, to_string_pretty};
use std::mem::size_of as sizeof;
#[test]
fn size_of_group() {
println!(
"\
Group: {}; \
Option<Group>: {}; \
String: {}; \
Option<String>: {}; \
Option<Box<Group>>: {}; \
&Group: {}; \
Groups: {}; \
(usize, String, String): {}; \
MangaData: {}\
",
sizeof::<Group>(),
sizeof::<Option<Group>>(),
sizeof::<String>(),
sizeof::<Option<String>>(),
sizeof::<Option<Box<Group>>>(),
sizeof::<&Group>(),
sizeof::<Groups>(),
sizeof::<(usize, String, String)>(),
sizeof::<MangaData>()
);
}
fn reformats_manga(groups: &mut Groups) -> R {
for mng in &TEST_MANGA_ALL_TESTS {
if let Some(mn) = from_str::<Manga>(mng)?.ok() {
let manga = MangaData::from_manga(mn, groups);
eprintln!("{:?}", manga);
eprintln!("{}", to_string_pretty(&manga)?);
}
}
eprintln!("{:?}", groups);
Ok(())
}
#[test]
fn reformats_manga_with_default_groups() -> R {
reformats_manga(&mut Groups::default())
}
#[test]
fn reformats_manga_with_full_groups() -> R {
reformats_manga(&mut Groups::new())
}
#[test]
fn groups_listing() -> R {
let mut groups = Groups::default();
use std::collections::HashMap;
let mut mangas: HashMap<&str, MangaData> = HashMap::with_capacity(8);
for mng in &TEST_MANGA_ALL_TESTS {
if let Some(mn) = from_str::<Manga>(mng)?.ok() {
let manga: MangaData = MangaData::from_manga(mn, &mut groups);
assert!(mangas.insert(mng, manga).is_none());
}
}
let no_groups =
groups.compact_seq_with_ch(&mangas[TEST_MANGA_NO_META_OR_CH].chapters);
assert!(no_groups.is_empty());
eprintln!("{:?}", no_groups);
let includes_zeen =
groups.compact_seq_with_ch(&mangas[TEST_MANGA_OK].chapters);
let zeen = Group {
id: 2491,
name: "zeen3 typesetting".into(),
website: None,
};
assert!(includes_zeen.0.contains(&zeen));
let zeeno = groups.get(2491).unwrap();
assert_eq!(zeeno, zeen);
eprintln!("{:?}", includes_zeen);
let json = to_string_pretty(&groups.compact_seq())?;
eprintln!("{}", json);
let regroups_seq: GroupsSeq = from_str(&json)?;
eprintln!("{:?}", regroups_seq);
let mut regroups: Groups = Groups::default();
regroups.extend(regroups_seq);
let json = to_string_pretty(®roups.compact_map())?;
eprintln!("{}", json);
let regroups_map: GroupsMap = from_str(&json)?;
eprintln!("{:?}", regroups_map);
let regroups: Groups = Groups::from(regroups_map);
eprintln!("{:?}", regroups);
assert_eq!(regroups, groups);
Ok(())
}
}