-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.rs
168 lines (147 loc) · 6.08 KB
/
utils.rs
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/// Given a duration, return a tuple of (scalar, time-unit).
/// This function attempts to round far away times to the nearest large
/// unit (naively implemented so it doesn't exactly behave that way).
pub fn pretty_duration<'a>(time_difference: chrono::Duration) -> (i64, &'a str) {
// Pretty print how long ago a note was taken.
let weeks_ago = time_difference.num_weeks();
let days_ago = time_difference.num_days();
let hours_ago = time_difference.num_hours();
let minutes_ago = time_difference.num_minutes();
let seconds_ago = time_difference.num_seconds();
let (amount, amount_unit) = if weeks_ago > 0 {
(weeks_ago, "week")
} else if days_ago > 0 {
(days_ago, "day")
} else if hours_ago > 0 {
(hours_ago, "hour")
} else if minutes_ago > 0 {
(minutes_ago, "minute")
} else {
(seconds_ago, "second")
};
(amount, amount_unit)
}
#[test]
fn test_pretty_duration() {
assert_eq!(pretty_duration(chrono::Duration::seconds(1)), (1, "second"));
assert_eq!(
pretty_duration(chrono::Duration::seconds(124)),
(2, "minute")
);
assert_eq!(pretty_duration(chrono::Duration::minutes(64)), (1, "hour"));
assert_eq!(pretty_duration(chrono::Duration::hours(54)), (2, "day"));
assert_eq!(pretty_duration(chrono::Duration::days(10)), (1, "week"));
assert_eq!(pretty_duration(chrono::Duration::days(365)), (52, "week"));
}
/// Pluralize words e.g. Hour => Hours, etc.
pub fn pluralize_time_unit(amount: i64, time_unit: &str) -> String {
if amount == 1 {
return time_unit.to_string();
}
return format!("{}s", time_unit);
}
#[test]
fn test_pluralize_time_unit() {
assert_eq!(pluralize_time_unit(1, "day"), "day");
assert_eq!(pluralize_time_unit(2, "day"), "days");
assert_eq!(pluralize_time_unit(-2, "minute"), "minutes");
}
/// Remove ANSI escape codes and get the real terminal width of the text.
pub fn count_real_chars(input: &str) -> Option<usize> {
Some(console::measure_text_width(input))
}
#[test]
fn test_count_real_chars() {
assert_eq!(count_real_chars("hello"), Some(5));
assert_eq!(count_real_chars("hello"), Some(5));
assert_eq!(count_real_chars(" "), Some(5));
assert_eq!(count_real_chars("👩"), Some(2));
assert_eq!(count_real_chars("何"), Some(2));
assert_eq!(count_real_chars("🖋️"), Some(1)); // TODO: This is incorrect I think?
}
/// We use this function to attempt to format messages into smaller terminals.
/// We will also render newlines similarly to how markdown does it.
pub fn break_apart_long_string(st: &str) -> String {
let term = console::Term::stdout();
let (_height, width) = term.size();
let ideal_split_point = width - 4;
format!("{}", textwrap::fill(st, ideal_split_point as usize))
}
const BASE: u32 = 21;
const BASE_2: u32 = BASE * BASE;
const BASE_3: u32 = BASE * BASE * BASE;
const BASE_4: u32 = BASE * BASE * BASE * BASE;
const LETTERS: [char; BASE as usize] = [
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x',
'y', 'z',
];
/// Generate a new uuid that is unique favoring short and easy to type ids.
pub fn generate_new_uuid(previous_uuids: &std::collections::HashSet<String>) -> String {
// Algorithm is look at the size of existing uuids, lock them to powers of 26 since
// our uuids that are random are all lowercase letters. Pick a random number
// inside that range of available numbers (if there are slots available) and
// then increase that id by 1 every repeatedly till we find one that is available.
// NOTE: we add 26 to len because that's where we start counting to ensure
// every uuid has at least 2 digits.
let len = previous_uuids.len() + BASE as usize;
// If previous_uuids is sufficiently long we increase the bit space.
// This means that if the user has a lot of custom ones we might artificially
// hit these sooner.
// For each of these checks we subtract away the previous base. I'm no mathemagician
// but since we're drawing from a mostly random, evenly distributed pool we want to
// on average hit an open spot relatively quick. If we let the entire space fill up
// the worst case is we might have to iterate the entire darn space. If we never entirely
// fill up a slot this seems safe.
let pool_to_draw_from = if len < (BASE_2 - BASE) as usize {
// 26^2, 2 letters should be available.
BASE_2
} else if len < (BASE_3 - BASE_2) as usize {
// 26^3, 3 letters should be available.
BASE_3
} else if len < (BASE_4 - BASE_3) as usize {
// 26^4, 4 letters should be available.
// TODO: at this point we should parallelize the walk. :P
BASE_4
} else {
// Wow you have a lot of notes, have the entire bitspace
std::u32::MAX
};
let mut n = rand::random::<u32>() % pool_to_draw_from;
loop {
let attempt = num_to_string_id(n);
if !previous_uuids.contains(&attempt) {
return attempt;
}
// Increase us but keep us inside this pool of candidates.
n = n + 1 % pool_to_draw_from;
}
}
/// Encode a decimal number to a lowercase string comprised of letters.
fn num_to_string_id(num: u32) -> String {
let mut out = String::new();
let mut rem = num + BASE; // Allow 0 to have a code and start us off into the two digit zone.
loop {
let base_26_digit = rem % BASE;
out.push(LETTERS[base_26_digit as usize]);
rem = rem / BASE;
if rem == 0 {
break;
}
}
return out;
}
#[test]
fn test_num_to_string_id() {
// Make sure everything is unique.
let mut all = std::collections::HashSet::new();
let size = 50000;
for i in 0..size {
all.insert(num_to_string_id(i));
}
assert_eq!(size as usize, all.len());
assert_eq!(num_to_string_id(0).len(), 2);
assert_eq!(num_to_string_id(BASE).len(), 2);
assert_eq!(num_to_string_id(BASE_2).len(), 3);
assert_eq!(num_to_string_id(BASE_3).len(), 4);
assert_eq!(num_to_string_id(BASE_4).len(), 5);
}