Completed timeago table
This commit is contained in:
parent
17094d121b
commit
2bba9064fc
3 changed files with 4392 additions and 136 deletions
|
|
@ -7,22 +7,19 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use futures::{stream, StreamExt};
|
use futures::{stream, StreamExt};
|
||||||
use intl_pluralrules::{PluralCategory, PluralRuleType, PluralRules};
|
use intl_pluralrules::{PluralCategory, PluralRuleType, PluralRules};
|
||||||
|
use log::{error, info};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use unic_langid::LanguageIdentifier;
|
use unic_langid::LanguageIdentifier;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{
|
client::{response, ClientType, ContextYT, RustyTube},
|
||||||
response::{self, video::CommentListItem},
|
|
||||||
ClientType, ContextYT, RustyTube,
|
|
||||||
},
|
|
||||||
model::{Country, Language},
|
model::{Country, Language},
|
||||||
timeago::{self, TimeAgo, TimeUnit, LANGUAGES},
|
timeago::{self, TimeUnit, TimeagoPattern, LANGUAGES},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
|
@ -77,49 +74,48 @@ async fn get_channel_datestrings(rp: &RustyTube, channel_id: &str) -> Vec<String
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_comment_initial_ctoken(rp: &RustyTube, video_id: &str) -> (String, String) {
|
async fn get_comment_initial_ctoken(rp: &RustyTube, video_id: &str, latest: bool) -> String {
|
||||||
let video_response = rp.get_video_response(video_id).await.unwrap();
|
let video_response = rp.get_video_response(video_id).await.unwrap();
|
||||||
|
|
||||||
let top = video_response
|
match latest {
|
||||||
.contents
|
true => video_response
|
||||||
.two_column_watch_next_results
|
.engagement_panels
|
||||||
.results
|
.iter()
|
||||||
.results
|
.find_map(|p| {
|
||||||
.contents
|
p.engagement_panel_section_list_renderer
|
||||||
.iter()
|
.header
|
||||||
.find_map(|c| match c {
|
.engagement_panel_title_header_renderer
|
||||||
response::video::VideoResultsItem::ItemSectionRenderer {
|
.menu
|
||||||
contents,
|
.sort_filter_sub_menu_renderer
|
||||||
section_identifier,
|
.sub_menu_items
|
||||||
} => match section_identifier == "comment-item-section" {
|
.get(1)
|
||||||
true => match &contents[0] {
|
.map(|i| i.service_endpoint.continuation_command.token.to_owned())
|
||||||
response::video::ItemSection::ContinuationItemRenderer {
|
})
|
||||||
continuation_endpoint,
|
.unwrap(),
|
||||||
} => Some(continuation_endpoint.continuation_command.token.to_owned()),
|
false => video_response
|
||||||
_ => None,
|
.contents
|
||||||
|
.two_column_watch_next_results
|
||||||
|
.results
|
||||||
|
.results
|
||||||
|
.contents
|
||||||
|
.iter()
|
||||||
|
.find_map(|c| match c {
|
||||||
|
response::video::VideoResultsItem::ItemSectionRenderer {
|
||||||
|
contents,
|
||||||
|
section_identifier,
|
||||||
|
} => match section_identifier == "comment-item-section" {
|
||||||
|
true => match &contents[0] {
|
||||||
|
response::video::ItemSection::ContinuationItemRenderer {
|
||||||
|
continuation_endpoint,
|
||||||
|
} => Some(continuation_endpoint.continuation_command.token.to_owned()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
false => None,
|
||||||
},
|
},
|
||||||
false => None,
|
_ => None,
|
||||||
},
|
})
|
||||||
_ => None,
|
.unwrap(),
|
||||||
})
|
}
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let latest = video_response
|
|
||||||
.engagement_panels
|
|
||||||
.iter()
|
|
||||||
.find_map(|p| {
|
|
||||||
p.engagement_panel_section_list_renderer
|
|
||||||
.header
|
|
||||||
.engagement_panel_title_header_renderer
|
|
||||||
.menu
|
|
||||||
.sort_filter_sub_menu_renderer
|
|
||||||
.sub_menu_items
|
|
||||||
.get(1)
|
|
||||||
.map(|i| i.service_endpoint.continuation_command.token.to_owned())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(top, latest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_comment_datestrings(rp: &RustyTube, ctoken: &str) -> (Vec<String>, Option<String>) {
|
async fn get_comment_datestrings(rp: &RustyTube, ctoken: &str) -> (Vec<String>, Option<String>) {
|
||||||
|
|
@ -253,6 +249,7 @@ struct Ruleset {
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
enum PluralCat {
|
enum PluralCat {
|
||||||
|
Zero,
|
||||||
One,
|
One,
|
||||||
Two,
|
Two,
|
||||||
Few,
|
Few,
|
||||||
|
|
@ -260,17 +257,15 @@ enum PluralCat {
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PluralCategory> for PluralCat {
|
impl From<PluralCategory> for PluralCat {
|
||||||
type Error = anyhow::Error;
|
fn from(value: PluralCategory) -> Self {
|
||||||
|
|
||||||
fn try_from(value: PluralCategory) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
match value {
|
||||||
PluralCategory::ZERO => Err(anyhow!("zero is not supported")),
|
PluralCategory::ZERO => Self::Zero,
|
||||||
PluralCategory::ONE => Ok(Self::One),
|
PluralCategory::ONE => Self::One,
|
||||||
PluralCategory::TWO => Ok(Self::Two),
|
PluralCategory::TWO => Self::Two,
|
||||||
PluralCategory::FEW => Ok(Self::Few),
|
PluralCategory::FEW => Self::Few,
|
||||||
PluralCategory::MANY => Ok(Self::Many),
|
PluralCategory::MANY => Self::Many,
|
||||||
PluralCategory::OTHER => Ok(Self::Other),
|
PluralCategory::OTHER => Self::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -308,11 +303,15 @@ static PLURAL_RULES: Lazy<BTreeMap<String, HashSet<PluralCat>>> = Lazy::new(|| {
|
||||||
.collect::<BTreeMap<_, _>>()
|
.collect::<BTreeMap<_, _>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
type TimeagoTable = BTreeMap<Language, BTreeMap<TimeUnit, TimeagoTableEntry>>;
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct TimeagoTable {
|
||||||
|
entries: BTreeMap<Language, BTreeMap<TimeUnit, TimeagoTableEntry>>,
|
||||||
|
errors: BTreeMap<Language, HashSet<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct TimeagoTableEntry {
|
struct TimeagoTableEntry {
|
||||||
cases: BTreeMap<String, TimeAgo>,
|
cases: BTreeMap<String, u32>,
|
||||||
missing_plurals: HashSet<PluralCat>,
|
missing_plurals: HashSet<PluralCat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,85 +326,153 @@ const TIME_UNITS: [TimeUnit; 7] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
fn new_timeago_table() -> TimeagoTable {
|
fn new_timeago_table() -> TimeagoTable {
|
||||||
LANGUAGES
|
TimeagoTable {
|
||||||
.iter()
|
entries: LANGUAGES
|
||||||
.filter_map(|lang| {
|
.iter()
|
||||||
// Check if language is redundant
|
.filter_map(|lang| {
|
||||||
match lang {
|
// Check if language is redundant
|
||||||
Language::EnGb
|
match lang {
|
||||||
| Language::EnIn
|
Language::EnGb
|
||||||
| Language::FrCa
|
| Language::EnIn
|
||||||
| Language::EsUs
|
| Language::FrCa
|
||||||
| Language::Es419 => None,
|
| Language::EsUs
|
||||||
_ => {
|
| Language::Es419 => None,
|
||||||
let cldr_lang_str = match lang {
|
_ => {
|
||||||
Language::SrLatn => "sr".to_owned(),
|
let cldr_lang_str = match lang {
|
||||||
Language::ZhCn | Language::ZhHk | Language::ZhTw => "zh".to_owned(),
|
Language::SrLatn => "sr".to_owned(),
|
||||||
_ => lang.to_string(),
|
Language::ZhCn | Language::ZhHk | Language::ZhTw => "zh".to_owned(),
|
||||||
};
|
_ => lang.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let m = TIME_UNITS
|
let m = TIME_UNITS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let missing_plurals = if t == &TimeUnit::Week {
|
let missing_plurals = if t == &TimeUnit::Week {
|
||||||
// Week only has 3 valid values (1-3)
|
// Week only has 3 valid values (2-4)
|
||||||
let mut mp = HashSet::new();
|
let mut mp = HashSet::new();
|
||||||
|
|
||||||
let l_id = cldr_lang_str.parse::<LanguageIdentifier>().unwrap();
|
let l_id = cldr_lang_str.parse::<LanguageIdentifier>().unwrap();
|
||||||
let pr =
|
let pr = PluralRules::create(l_id, PluralRuleType::CARDINAL)
|
||||||
PluralRules::create(l_id, PluralRuleType::CARDINAL).unwrap();
|
.unwrap();
|
||||||
|
|
||||||
mp.insert(PluralCat::try_from(pr.select(1).unwrap()).unwrap());
|
mp.insert(PluralCat::from(pr.select(2).unwrap()));
|
||||||
mp.insert(PluralCat::try_from(pr.select(2).unwrap()).unwrap());
|
mp.insert(PluralCat::from(pr.select(3).unwrap()));
|
||||||
mp.insert(PluralCat::try_from(pr.select(3).unwrap()).unwrap());
|
mp.insert(PluralCat::from(pr.select(4).unwrap()));
|
||||||
|
|
||||||
mp
|
mp
|
||||||
} else {
|
} else {
|
||||||
PLURAL_RULES.get(&cldr_lang_str).unwrap().clone()
|
PLURAL_RULES.get(&cldr_lang_str).unwrap().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
t.to_owned(),
|
t.to_owned(),
|
||||||
TimeagoTableEntry {
|
TimeagoTableEntry {
|
||||||
cases: BTreeMap::new(),
|
cases: BTreeMap::new(),
|
||||||
missing_plurals,
|
missing_plurals,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Some((lang.to_owned(), m))
|
Some((lang.to_owned(), m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.collect(),
|
||||||
.collect()
|
errors: BTreeMap::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn read_timeago_table() -> TimeagoTable {
|
||||||
fn t_new_timeago_table() {
|
|
||||||
let json_path = Path::new("testfiles/date/timeago_table.json").to_path_buf();
|
let json_path = Path::new("testfiles/date/timeago_table.json").to_path_buf();
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
let file = File::open(json_path).unwrap();
|
||||||
|
serde_json::from_reader(BufReader::new(file)).unwrap()
|
||||||
|
} else {
|
||||||
|
new_timeago_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = File::create(json_path).unwrap();
|
|
||||||
serde_json::to_writer_pretty(file, &new_timeago_table()).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
fn write_timeago_table(timeago_table: &TimeagoTable) {
|
||||||
async fn t_tmp() {
|
let json_path = Path::new("testfiles/date/timeago_table.json").to_path_buf();
|
||||||
let rp = RustyTube::new();
|
let file = File::create(json_path).unwrap();
|
||||||
let (top, latest) = get_comment_initial_ctoken(&rp, "gQlMMD8auMs").await;
|
serde_json::to_writer_pretty(file, timeago_table).unwrap();
|
||||||
// let (top, latest) = get_comment_initial_ctoken(&rp, "9bZkp7q19f0").await;
|
}
|
||||||
let mut ctoken = latest;
|
|
||||||
|
|
||||||
|
fn insert_timeago_table(
|
||||||
|
timeago_table: &mut TimeagoTable,
|
||||||
|
lang: &Language,
|
||||||
|
date_str: &str,
|
||||||
|
limit: Option<TimeUnit>,
|
||||||
|
ignore_1s: bool,
|
||||||
|
) -> bool {
|
||||||
|
let pattern = TimeagoPattern::from(lang.to_owned());
|
||||||
|
match pattern.parse(date_str) {
|
||||||
|
Some(timeago) => {
|
||||||
|
let entry = timeago_table
|
||||||
|
.entries
|
||||||
|
.get_mut(lang)
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&timeago.unit)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let cldr_lang_str = &lang.to_string()[0..2];
|
||||||
|
let l_id: LanguageIdentifier = cldr_lang_str.parse().unwrap();
|
||||||
|
let pl_pat = PluralRules::create(l_id, PluralRuleType::CARDINAL).unwrap();
|
||||||
|
let pl = PluralCat::from(pl_pat.select(timeago.n).unwrap());
|
||||||
|
|
||||||
|
// Collect the case if its plural type is missing
|
||||||
|
if entry.missing_plurals.remove(&pl) {
|
||||||
|
entry.cases.insert(date_str.to_owned(), timeago.n);
|
||||||
|
info!(
|
||||||
|
"Collected `{}` ({} {:?})",
|
||||||
|
date_str, timeago.n, timeago.unit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeago_table
|
||||||
|
.entries
|
||||||
|
.get(lang)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.all(|(t, entry)| {
|
||||||
|
(limit.is_some() && t > &limit.unwrap())
|
||||||
|
|| entry.missing_plurals.is_empty()
|
||||||
|
|| (ignore_1s
|
||||||
|
&& t == &TimeUnit::Second
|
||||||
|
&& entry.missing_plurals.len() == 1
|
||||||
|
&& entry.missing_plurals.contains(&PluralCat::One))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Could not parse `{}`", date_str);
|
||||||
|
let errors = timeago_table
|
||||||
|
.errors
|
||||||
|
.entry(*lang)
|
||||||
|
.or_insert_with(|| HashSet::new());
|
||||||
|
errors.insert(date_str.to_owned());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_timeago_table_datestrings(
|
||||||
|
rp: &RustyTube,
|
||||||
|
timeago_table: &mut TimeagoTable,
|
||||||
|
video_id: &str,
|
||||||
|
latest: bool,
|
||||||
|
limit: Option<TimeUnit>,
|
||||||
|
ignore_1s: bool,
|
||||||
|
) {
|
||||||
|
let mut ctoken = get_comment_initial_ctoken(&rp, video_id, latest).await;
|
||||||
let brace_pattern = Regex::new(r"\(.+\)").unwrap();
|
let brace_pattern = Regex::new(r"\(.+\)").unwrap();
|
||||||
|
let lang = &rp.localization.language;
|
||||||
|
let err_baseline = timeago_table.errors.len();
|
||||||
|
|
||||||
for _ in 0..100 {
|
for _ in 0..40 {
|
||||||
let (strings, new_ctoken) = get_comment_datestrings(&rp, &ctoken).await;
|
let (strings, new_ctoken) = get_comment_datestrings(&rp, &ctoken).await;
|
||||||
|
|
||||||
/*
|
let res = strings
|
||||||
strings
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
// Remove zero-width space characters
|
// Remove zero-width space characters
|
||||||
|
|
@ -417,14 +484,186 @@ async fn t_tmp() {
|
||||||
let s = s.trim();
|
let s = s.trim();
|
||||||
s.to_owned()
|
s.to_owned()
|
||||||
})
|
})
|
||||||
.for_each(|s| println!("{}", s));
|
.find(|s| insert_timeago_table(timeago_table, lang, &s, limit, ignore_1s));
|
||||||
*/
|
|
||||||
println!("n: {}", strings.len());
|
if res.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeago_table.errors.len() > err_baseline {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(new_ctoken) = new_ctoken {
|
if let Some(new_ctoken) = new_ctoken {
|
||||||
ctoken = new_ctoken.to_owned();
|
ctoken = new_ctoken.to_owned();
|
||||||
} else {
|
} else {
|
||||||
|
error!("end of comments");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn insert_timeago_table_datestrings_channel(
|
||||||
|
rp: &RustyTube,
|
||||||
|
timeago_table: &mut TimeagoTable,
|
||||||
|
channel_id: &str,
|
||||||
|
) {
|
||||||
|
let lang = &rp.localization.language;
|
||||||
|
|
||||||
|
let strings = get_channel_datestrings(rp, channel_id).await;
|
||||||
|
|
||||||
|
strings
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
// Remove zero-width space characters
|
||||||
|
let s = s.replace('\u{200b}', "");
|
||||||
|
|
||||||
|
let s = s.trim();
|
||||||
|
s.to_owned()
|
||||||
|
})
|
||||||
|
.for_each(|s| {
|
||||||
|
insert_timeago_table(timeago_table, lang, &s, None, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_log::test(tokio::test)]
|
||||||
|
async fn t_build_timeago_table() {
|
||||||
|
let mut timeago_table = read_timeago_table();
|
||||||
|
let ignore_1s = false;
|
||||||
|
let langs = timeago_table
|
||||||
|
.entries
|
||||||
|
.keys()
|
||||||
|
.map(|k| k.to_owned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for lang in langs {
|
||||||
|
if timeago_table
|
||||||
|
.entries
|
||||||
|
.get(&lang)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.all(|(t, entry)| {
|
||||||
|
entry.missing_plurals.is_empty()
|
||||||
|
|| (ignore_1s
|
||||||
|
&& t == &TimeUnit::Second
|
||||||
|
&& entry.missing_plurals.len() == 1
|
||||||
|
&& entry.missing_plurals.contains(&PluralCat::One))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rp = RustyTube::new_with_ua(lang, Country::Us, None);
|
||||||
|
|
||||||
|
println!("{}: 1s!", lang);
|
||||||
|
{
|
||||||
|
let ctoken = get_comment_initial_ctoken(&rp, "gQlMMD8auMs", true).await;
|
||||||
|
// let ctoken = get_comment_initial_ctoken(&rp, "k6jqx9kZgPM", true).await;
|
||||||
|
let brace_pattern = Regex::new(r"\(.+\)").unwrap();
|
||||||
|
let lang = &rp.localization.language;
|
||||||
|
let err_baseline = timeago_table.errors.len();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (strings, _) = get_comment_datestrings(&rp, &ctoken).await;
|
||||||
|
println!("{}", strings[0]);
|
||||||
|
|
||||||
|
let res = strings
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
// Remove zero-width space characters
|
||||||
|
let s = s.replace('\u{200b}', "");
|
||||||
|
|
||||||
|
// Remove braces
|
||||||
|
let s = brace_pattern.replace(&s, "");
|
||||||
|
|
||||||
|
let s = s.trim();
|
||||||
|
s.to_owned()
|
||||||
|
})
|
||||||
|
.find(|s| {
|
||||||
|
insert_timeago_table(
|
||||||
|
&mut timeago_table,
|
||||||
|
lang,
|
||||||
|
&s,
|
||||||
|
Some(TimeUnit::Second),
|
||||||
|
ignore_1s,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if res.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeago_table.errors.len() > err_baseline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}: 2s - n min", lang);
|
||||||
|
insert_timeago_table_datestrings(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"gQlMMD8auMs",
|
||||||
|
true,
|
||||||
|
Some(TimeUnit::Minute),
|
||||||
|
ignore_1s,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("{}: x hr", lang);
|
||||||
|
insert_timeago_table_datestrings(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"TohrPm3ICJE",
|
||||||
|
true,
|
||||||
|
Some(TimeUnit::Hour),
|
||||||
|
ignore_1s,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("{}: 1 hr - n day", lang);
|
||||||
|
insert_timeago_table_datestrings(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"J9NQFACZYEU",
|
||||||
|
true,
|
||||||
|
Some(TimeUnit::Day),
|
||||||
|
ignore_1s,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("{}: week", lang);
|
||||||
|
insert_timeago_table_datestrings(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"-zPDx6HQ_9w",
|
||||||
|
true,
|
||||||
|
Some(TimeUnit::Week),
|
||||||
|
ignore_1s,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
println!("{}: 1 yr - n yr", lang);
|
||||||
|
insert_timeago_table_datestrings_channel(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"UCEOXxzW2vU0P-0THehuIIeg",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
println!("{}: 11 mon", lang);
|
||||||
|
insert_timeago_table_datestrings_channel(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"UCY1kMZp36IQSyNx_9h4mpCg",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
println!("{}: 13 yr", lang);
|
||||||
|
insert_timeago_table_datestrings_channel(
|
||||||
|
&rp,
|
||||||
|
&mut timeago_table,
|
||||||
|
"UCfw6qEAJMDbmgqQbuoB5moA",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
write_timeago_table(&timeago_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{borrow::Cow, str::FromStr, vec};
|
use std::{borrow::Cow, str::FromStr, vec, cmp::Ordering};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
|
|
@ -92,7 +92,7 @@ pub const LANGUAGES: [Language; 83] = [
|
||||||
Language::Zu,
|
Language::Zu,
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq)]
|
||||||
pub struct TimeAgo {
|
pub struct TimeAgo {
|
||||||
pub n: u32,
|
pub n: u32,
|
||||||
pub unit: TimeUnit,
|
pub unit: TimeUnit,
|
||||||
|
|
@ -139,7 +139,7 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
},
|
},
|
||||||
Language::Am => TimeagoPattern {
|
Language::Am => TimeagoPattern {
|
||||||
word_separator: " ",
|
word_separator: " ",
|
||||||
seconds: vec!["ሰኮንዶች", "ሴኮንድ"],
|
seconds: vec!["ሰኮንዶች", "ሴኮንድ", "ሰከንድ", "ሰከንዶች"],
|
||||||
minutes: vec!["ደቂቃ", "ደቂቃዎች"],
|
minutes: vec!["ደቂቃ", "ደቂቃዎች"],
|
||||||
hours: vec!["ሰዓት", "ሰዓቶች"],
|
hours: vec!["ሰዓት", "ሰዓቶች"],
|
||||||
// INFO: add days[0]
|
// INFO: add days[0]
|
||||||
|
|
@ -151,14 +151,28 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
},
|
},
|
||||||
Language::Ar => TimeagoPattern {
|
Language::Ar => TimeagoPattern {
|
||||||
word_separator: " ",
|
word_separator: " ",
|
||||||
seconds: vec!["ثانية", "ثانيتين", "ثوانٍ"],
|
seconds: vec!["ثانية", "ثوانٍ", "ثوانِ"],
|
||||||
minutes: vec!["دقائق", "دقيقة", "دقيقتين"],
|
minutes: vec!["دقائق", "دقيقة", "دقيقتين"],
|
||||||
hours: vec!["ساعات", "ساعة", "ساعتين"],
|
hours: vec!["ساعات", "ساعة"],
|
||||||
days: vec!["أيام", "يوم", "يومين", "يومًا"],
|
days: vec!["أيام", "يوم", "يومًا"],
|
||||||
weeks: vec!["أسابيع", "أسبوع", "أسبوعين"],
|
weeks: vec!["أسابيع", "أسبوع"],
|
||||||
months: vec!["أشهر", "شهر", "شهرين", "شهرًا"],
|
months: vec!["أشهر", "شهر", "شهرًا"],
|
||||||
years: vec!["سنة", "سنتين", "سنوات"],
|
years: vec!["سنة", "سنوات"],
|
||||||
special_cases: vec![
|
special_cases: vec![
|
||||||
|
(
|
||||||
|
"ثانيتين",
|
||||||
|
TimeAgo {
|
||||||
|
n: 2,
|
||||||
|
unit: TimeUnit::Second,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"دقيقتين",
|
||||||
|
TimeAgo {
|
||||||
|
n: 2,
|
||||||
|
unit: TimeUnit::Minute,
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"ساعتين",
|
"ساعتين",
|
||||||
TimeAgo {
|
TimeAgo {
|
||||||
|
|
@ -281,7 +295,7 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
seconds: vec!["sekundami", "sekundou"],
|
seconds: vec!["sekundami", "sekundou"],
|
||||||
minutes: vec!["minutami", "minutou"],
|
minutes: vec!["minutami", "minutou"],
|
||||||
hours: vec!["hodinami", "hodinou"],
|
hours: vec!["hodinami", "hodinou"],
|
||||||
days: vec!["dny", "včera"],
|
days: vec!["dny", "dnem"],
|
||||||
weeks: vec!["týdnem", "týdny"],
|
weeks: vec!["týdnem", "týdny"],
|
||||||
months: vec!["měsícem", "měsíci"],
|
months: vec!["měsícem", "měsíci"],
|
||||||
years: vec!["rokem", "roky", "lety"],
|
years: vec!["rokem", "roky", "lety"],
|
||||||
|
|
@ -521,6 +535,20 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
months: vec!["חודש", "חודשים"],
|
months: vec!["חודש", "חודשים"],
|
||||||
years: vec!["שנה", "שנים"],
|
years: vec!["שנה", "שנים"],
|
||||||
special_cases: vec![
|
special_cases: vec![
|
||||||
|
(
|
||||||
|
"שתי שניות",
|
||||||
|
TimeAgo {
|
||||||
|
n: 2,
|
||||||
|
unit: TimeUnit::Second,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"שתי דקות",
|
||||||
|
TimeAgo {
|
||||||
|
n: 2,
|
||||||
|
unit: TimeUnit::Minute,
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"שעתיים",
|
"שעתיים",
|
||||||
TimeAgo {
|
TimeAgo {
|
||||||
|
|
@ -662,7 +690,7 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
Language::Lv => TimeagoPattern {
|
Language::Lv => TimeagoPattern {
|
||||||
word_separator: " ",
|
word_separator: " ",
|
||||||
seconds: vec!["sekundes", "sekundēm"],
|
seconds: vec!["sekundes", "sekundēm"],
|
||||||
minutes: vec!["minūtes", "minūtēm", "minūtes"],
|
minutes: vec!["minūtes", "minūtēm"],
|
||||||
hours: vec!["stundas", "stundām"],
|
hours: vec!["stundas", "stundām"],
|
||||||
days: vec!["dienas", "dienām"],
|
days: vec!["dienas", "dienām"],
|
||||||
weeks: vec!["nedēļas", "nedēļām"],
|
weeks: vec!["nedēļas", "nedēļām"],
|
||||||
|
|
@ -867,7 +895,7 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
seconds: vec!["sekundama", "sekundami", "sekundo"],
|
seconds: vec!["sekundama", "sekundami", "sekundo"],
|
||||||
minutes: vec!["minutama", "minutami", "minuto"],
|
minutes: vec!["minutama", "minutami", "minuto"],
|
||||||
hours: vec!["urama", "urami", "uro"],
|
hours: vec!["urama", "urami", "uro"],
|
||||||
days: vec!["dnem", "dnevi", "dnevoma"],
|
days: vec!["dnem", "dnevi", "dnevoma", "dnevom"],
|
||||||
weeks: vec!["tedni", "tednom", "tednoma"],
|
weeks: vec!["tedni", "tednom", "tednoma"],
|
||||||
months: vec!["mesecem", "mesecema", "meseci"],
|
months: vec!["mesecem", "mesecema", "meseci"],
|
||||||
years: vec!["leti", "letom", "letoma"],
|
years: vec!["leti", "letom", "letoma"],
|
||||||
|
|
@ -932,9 +960,9 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
},
|
},
|
||||||
Language::Ta => TimeagoPattern {
|
Language::Ta => TimeagoPattern {
|
||||||
word_separator: " ",
|
word_separator: " ",
|
||||||
// INFO: fixed minutes hours months, TODO: 1 second
|
// INFO: fixed minutes hours months
|
||||||
// 2 விநாடிகளுக்கு முன்
|
// 2 விநாடிகளுக்கு முன்
|
||||||
seconds: vec!["வினாடி", "வினாடிகளுக்கு"],
|
seconds: vec!["வினாடி", "வினாடிகளுக்கு", "விநாடிகளுக்கு", "விநாடிக்கு"],
|
||||||
// 1 நிமிடத்திற்கு முன் 2 நிமிடங்களுக்கு முன்
|
// 1 நிமிடத்திற்கு முன் 2 நிமிடங்களுக்கு முன்
|
||||||
minutes: vec!["நிமிடங்களுக்கு", "நிமிடத்திற்கு", "நிமிடங்கள்", "நிமிடம்"],
|
minutes: vec!["நிமிடங்களுக்கு", "நிமிடத்திற்கு", "நிமிடங்கள்", "நிமிடம்"],
|
||||||
hours: vec!["மணிநேரம்"],
|
hours: vec!["மணிநேரம்"],
|
||||||
|
|
@ -1065,7 +1093,7 @@ impl From<Language> for TimeagoPattern<'_> {
|
||||||
seconds: vec!["amasekhondi", "isekhondi"],
|
seconds: vec!["amasekhondi", "isekhondi"],
|
||||||
minutes: vec!["amaminithi", "iminithi"],
|
minutes: vec!["amaminithi", "iminithi"],
|
||||||
hours: vec!["emahoreni", "amahora", "ihora"],
|
hours: vec!["emahoreni", "amahora", "ihora"],
|
||||||
days: vec!["ezinsukwini", "izinsuku", "usuku"],
|
days: vec!["ezinsukwini", "izinsuku", "usuku", "osukwini"],
|
||||||
weeks: vec!["amaviki", "iviki"],
|
weeks: vec!["amaviki", "iviki"],
|
||||||
months: vec!["inyanga", "izinyanga"],
|
months: vec!["inyanga", "izinyanga"],
|
||||||
years: vec!["iminyaka", "unyaka"],
|
years: vec!["iminyaka", "unyaka"],
|
||||||
|
|
@ -1163,6 +1191,44 @@ impl TimeagoPattern<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TimeUnit {
|
||||||
|
fn seconds(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
TimeUnit::Second => 1,
|
||||||
|
TimeUnit::Minute => 60,
|
||||||
|
TimeUnit::Hour => 3600,
|
||||||
|
TimeUnit::Day => 24 * 3600,
|
||||||
|
TimeUnit::Week => 7 * 24 * 3600,
|
||||||
|
TimeUnit::Month => 30 * 24 * 3600,
|
||||||
|
TimeUnit::Year => 365 * 24 * 3600,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeAgo {
|
||||||
|
fn seconds(&self) -> u64 {
|
||||||
|
self.n as u64 * self.unit.seconds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TimeAgo {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.seconds() == other.seconds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for TimeAgo {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.seconds().cmp(&other.seconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for TimeAgo {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path};
|
use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path};
|
||||||
|
|
|
||||||
3951
testfiles/date/timeago_table.json
Normal file
3951
testfiles/date/timeago_table.json
Normal file
File diff suppressed because it is too large
Load diff
Reference in a new issue