1use std::{
4 borrow::Cow,
5 cmp::{self, Ordering},
6 fmt::{self, Display},
7};
8
9use sway_types::{SourceEngine, SourceId, Span};
10
11use crate::diagnostic::Hint;
12
13pub fn get_file_name(source_engine: &SourceEngine, source_id: Option<&SourceId>) -> Option<String> {
17 match source_id {
18 Some(source_id) => source_engine.get_file_name(source_id),
19 None => None,
20 }
21}
22
23pub fn num_to_str(num: usize) -> String {
26 match num {
27 0 => "zero".to_string(),
28 1 => "one".to_string(),
29 2 => "two".to_string(),
30 3 => "three".to_string(),
31 4 => "four".to_string(),
32 5 => "five".to_string(),
33 6 => "six".to_string(),
34 7 => "seven".to_string(),
35 8 => "eight".to_string(),
36 9 => "nine".to_string(),
37 10 => "ten".to_string(),
38 _ => format!("{num}"),
39 }
40}
41
42pub fn num_to_str_or_none(num: usize) -> String {
47 if num == 0 {
48 "none".to_string()
49 } else {
50 num_to_str(num)
51 }
52}
53
54pub enum Enclosing {
55 #[allow(dead_code)]
56 None,
57 DoubleQuote,
58}
59
60impl Display for Enclosing {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(
63 f,
64 "{}",
65 match self {
66 Self::None => "",
67 Self::DoubleQuote => "\"",
68 },
69 )
70 }
71}
72
73pub enum Indent {
74 #[allow(dead_code)]
75 None,
76 Single,
77 Double,
78}
79
80impl Display for Indent {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(
83 f,
84 "{}",
85 match self {
86 Self::None => "",
87 Self::Single => " ",
88 Self::Double => " ",
89 },
90 )
91 }
92}
93
94pub fn sequence_to_str<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
108where
109 T: Display,
110{
111 sequence_to_str_impl(sequence, enclosing, max_items, "and")
112}
113
114pub fn sequence_to_str_or<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
128where
129 T: Display,
130{
131 sequence_to_str_impl(sequence, enclosing, max_items, "or")
132}
133
134fn sequence_to_str_impl<T>(
135 sequence: &[T],
136 enclosing: Enclosing,
137 max_items: usize,
138 and_or: &str,
139) -> String
140where
141 T: Display,
142{
143 assert!(
144 !sequence.is_empty(),
145 "Sequence to display must not be empty."
146 );
147 assert!(
148 max_items > 0,
149 "Maximum number of items to display must be greater than zero."
150 );
151
152 let max_items = cmp::min(max_items, sequence.len());
153
154 let (to_display, remaining) = sequence.split_at(max_items);
155
156 let fmt_item = |item: &T| format!("{enclosing}{item}{enclosing}");
157
158 if !remaining.is_empty() {
159 format!(
160 "{}, {} {} more",
161 to_display
162 .iter()
163 .map(fmt_item)
164 .collect::<Vec<_>>()
165 .join(", "),
166 and_or,
167 num_to_str(remaining.len())
168 )
169 } else {
170 match to_display {
171 [] => unreachable!("There must be at least one item in the sequence."),
172 [item] => fmt_item(item),
173 [first_item, second_item] => {
174 format!(
175 "{} {} {}",
176 fmt_item(first_item),
177 and_or,
178 fmt_item(second_item)
179 )
180 }
181 _ => format!(
182 "{}, {} {}",
183 to_display
184 .split_last()
185 .unwrap()
186 .1
187 .iter()
188 .map(fmt_item)
189 .collect::<Vec::<_>>()
190 .join(", "),
191 and_or,
192 fmt_item(to_display.last().unwrap())
193 ),
194 }
195 }
196}
197
198pub fn sequence_to_list<T>(sequence: &[T], indent: Indent, max_items: usize) -> Vec<String>
216where
217 T: Display,
218{
219 assert!(
220 !sequence.is_empty(),
221 "Sequence to display must not be empty."
222 );
223 assert!(
224 max_items > 0,
225 "Maximum number of items to display must be greater than zero."
226 );
227
228 let mut result = vec![];
229
230 let max_items = cmp::min(max_items, sequence.len());
231 let (to_display, remaining) = sequence.split_at(max_items);
232 for item in to_display {
233 result.push(format!("{indent}- {item}"));
234 }
235 if !remaining.is_empty() {
236 result.push(format!(
237 "{indent}- and {} more",
238 num_to_str(remaining.len())
239 ));
240 }
241
242 result
243}
244
245pub fn plural_s(count: usize) -> &'static str {
248 if count == 1 {
249 ""
250 } else {
251 "s"
252 }
253}
254
255pub fn is_are(count: usize) -> &'static str {
257 if count == 1 {
258 "is"
259 } else {
260 "are"
261 }
262}
263
264pub fn singular_plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
266 if count == 1 {
267 singular
268 } else {
269 plural
270 }
271}
272
273pub fn call_path_suffix_with_args(call_path: &String) -> Cow<String> {
284 match call_path.rfind(':') {
285 Some(index) if index < call_path.len() - 1 => {
286 Cow::Owned(call_path.split_at(index + 1).1.to_string())
287 }
288 _ => Cow::Borrowed(call_path),
289 }
290}
291
292pub fn a_or_an<S: AsRef<str> + ?Sized>(word: &S) -> &'static str {
300 let is_a = in_definite::is_an(word.as_ref());
301 match is_a {
302 in_definite::Is::An => "an ",
303 in_definite::Is::A => "a ",
304 in_definite::Is::None => "",
305 }
306}
307
308pub fn ascii_sentence_case(text: &String) -> Cow<String> {
310 if text.is_empty() || text.chars().next().unwrap().is_uppercase() {
311 Cow::Borrowed(text)
312 } else {
313 let mut result = text.clone();
314 result[0..1].make_ascii_uppercase();
315 Cow::Owned(result.to_owned())
316 }
317}
318
319pub fn first_line(text: &str, with_ellipses: bool) -> Cow<str> {
335 if !text.contains('\n') {
336 Cow::Borrowed(text)
337 } else {
338 let index_of_new_line = text.find('\n').unwrap();
339 Cow::Owned(text[..index_of_new_line].to_string() + if with_ellipses { "..." } else { "" })
340 }
341}
342
343pub fn did_you_mean<T, I>(v: &str, possible_values: I, max_num_of_suggestions: usize) -> Vec<String>
350where
351 T: AsRef<str>,
352 I: IntoIterator<Item = T>,
353{
354 let mut candidates: Vec<_> = possible_values
355 .into_iter()
356 .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
357 .filter(|(confidence, _)| *confidence > 0.7)
359 .collect();
360 candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
361 candidates
362 .into_iter()
363 .take(max_num_of_suggestions)
364 .map(|(_, pv)| pv)
365 .collect()
366}
367
368pub fn did_you_mean_help<T, I>(
373 source_engine: &SourceEngine,
374 span: Span,
375 possible_values: I,
376 max_num_of_suggestions: usize,
377 enclosing: Enclosing,
378) -> Hint
379where
380 T: AsRef<str>,
381 I: IntoIterator<Item = T>,
382{
383 let suggestions = &did_you_mean(span.as_str(), possible_values, max_num_of_suggestions);
384 if suggestions.is_empty() {
385 Hint::none()
386 } else {
387 Hint::help(
388 source_engine,
389 span,
390 format!(
391 "Did you mean {}?",
392 sequence_to_str_or(suggestions, enclosing, max_num_of_suggestions)
393 ),
394 )
395 }
396}