use chrono::{DateTime, Local, Utc};
use chrono_english::{parse_date_string, Dialect};
use color_eyre::Section;
use dialoguer::{theme, Editor, Input};
use hypothesis::annotations::Selector;
use std::time::Duration;
use url::Url;
use crate::errors::Apologize;
pub const SEMICOLON: u8 = 59;
pub fn parse_datetime(datetime_string: &str) -> color_eyre::Result<DateTime<Utc>> {
if datetime_string.to_ascii_lowercase() == "today" {
Ok(Utc::now())
} else {
Ok(parse_date_string(datetime_string, Local::now(), Dialect::Uk)?.with_timezone(&Utc))
}
}
pub fn split_ids(index_list: &[u8]) -> color_eyre::Result<Vec<String>> {
let index_list_string = std::str::from_utf8(index_list)?;
Ok(index_list_string
.split(';')
.map(|x| x.to_string())
.collect())
}
pub fn join_ids(index_list: &[String]) -> color_eyre::Result<Vec<u8>> {
Ok(index_list.join(";").as_bytes().to_vec())
}
pub fn user_input(
message: &str,
default: Option<&str>,
show_default: bool,
allow_empty: bool,
) -> color_eyre::Result<String> {
match default {
Some(default) => Ok(Input::with_theme(&theme::ColorfulTheme::default())
.with_prompt(message)
.default(default.to_owned())
.show_default(show_default)
.allow_empty(allow_empty)
.interact()?
.trim()
.to_owned()),
None => Ok(
Input::<String>::with_theme(&theme::ColorfulTheme::default())
.with_prompt(message)
.allow_empty(allow_empty)
.interact()?
.trim()
.to_owned(),
),
}
}
pub fn external_editor_input(default: Option<&str>, extension: &str) -> color_eyre::Result<String> {
Editor::new()
.trim_newlines(false)
.extension(extension)
.edit(default.unwrap_or(""))
.suggestion("Set your default editor using the $EDITOR or $VISUAL environment variables")?
.ok_or(Apologize::EditorError)
.suggestion("Make sure to save next time!")
}
pub fn get_spinner(message: &str) -> color_eyre::Result<indicatif::ProgressBar> {
let spinner = indicatif::ProgressBar::new_spinner();
spinner.enable_steady_tick(Duration::from_millis(200));
spinner.set_style(
indicatif::ProgressStyle::default_spinner()
.tick_chars("/|\\- ")
.template("{spinner:.dim.bold.blue} {wide_msg}")?,
);
spinner.set_message(message.to_owned());
Ok(spinner)
}
pub fn get_quotes(annotation: &hypothesis::annotations::Annotation) -> Vec<&str> {
annotation
.target
.iter()
.filter_map(|target| {
let quotes = target
.selector
.iter()
.filter_map(|selector| match selector {
Selector::TextQuoteSelector(selector) => Some(selector.exact.as_str()),
_ => None,
})
.collect::<Vec<_>>();
if quotes.is_empty() {
None
} else {
Some(quotes)
}
})
.flat_map(|v| v.into_iter())
.collect::<Vec<_>>()
}
pub fn clean_uri(uri: &str) -> String {
match Url::parse(uri) {
Ok(parsed_uri) => {
if parsed_uri.scheme() == "urn" {
uri.to_owned()
} else {
parsed_uri[url::Position::AfterScheme..]
.trim_start_matches("://")
.trim_end_matches('/')
.to_owned()
}
}
Err(_) => uri.to_owned(),
}
}
pub fn uri_to_filename(uri: &str) -> String {
clean_uri(uri)
.replace("://", "_")
.replace(['.', '/', ':'], "_")
}