1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use chrono::{DateTime, Utc};
use chrono_english::{parse_date_string, Dialect};
use color_eyre::Section;
use dialoguer::{theme, Editor, Input};
use hypothesis::annotations::Selector;
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().date().and_hms(0, 0, 0))
} else {
Ok(parse_date_string(datetime_string, Utc::now(), Dialect::Uk)?)
}
}
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> {
Ok(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) -> indicatif::ProgressBar {
let spinner = indicatif::ProgressBar::new_spinner();
spinner.enable_steady_tick(200);
spinner.set_style(
indicatif::ProgressStyle::default_spinner()
.tick_chars("/|\\- ")
.template("{spinner:.dim.bold.blue} {wide_msg}"),
);
spinner.set_message(message);
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 uri_to_filename(uri: &str) -> String {
uri.replace("://", "_")
.replace(".", "_")
.replace("/", "_")
.replace(":", "_")
}