libsurfer/
util.rs

1//! Utility functions.
2use crate::displayed_item_tree::VisibleItemIndex;
3use camino::Utf8PathBuf;
4#[cfg(not(target_arch = "wasm32"))]
5use std::path::{Path, PathBuf};
6
7/// This function takes a number and converts it's digits into the range
8/// a-p. This is nice because it makes for some easily typed ids.
9/// The function first formats the number as a hex digit and then performs
10/// the mapping.
11pub fn uint_idx_to_alpha_idx(idx: VisibleItemIndex, nvariables: usize) -> String {
12    // this calculates how many hex digits we need to represent nvariables
13    // unwrap because the result should always fit into usize and because
14    // we are not going to display millions of character ids.
15    let width = usize::try_from(nvariables.ilog(16)).unwrap() + 1;
16    format!("{:0width$x}", idx.0)
17        .chars()
18        .map(|c| match c {
19            '0' => 'a',
20            '1' => 'b',
21            '2' => 'c',
22            '3' => 'd',
23            '4' => 'e',
24            '5' => 'f',
25            '6' => 'g',
26            '7' => 'h',
27            '8' => 'i',
28            '9' => 'j',
29            'a' => 'k',
30            'b' => 'l',
31            'c' => 'm',
32            'd' => 'n',
33            'e' => 'o',
34            'f' => 'p',
35            _ => '?',
36        })
37        .collect()
38}
39
40/// This is the reverse function to uint_idx_to_alpha_idx.
41pub fn alpha_idx_to_uint_idx(idx: String) -> Option<VisibleItemIndex> {
42    let mapped = idx
43        .chars()
44        .map(|c| match c {
45            'a' => '0',
46            'b' => '1',
47            'c' => '2',
48            'd' => '3',
49            'e' => '4',
50            'f' => '5',
51            'g' => '6',
52            'h' => '7',
53            'i' => '8',
54            'j' => '9',
55            'k' => 'a',
56            'l' => 'b',
57            'm' => 'c',
58            'n' => 'd',
59            'o' => 'e',
60            'p' => 'f',
61            _ => '?',
62        })
63        .collect::<String>();
64    usize::from_str_radix(&mapped, 16)
65        .ok()
66        .map(VisibleItemIndex)
67}
68
69/// This function searches upward from `start` for directories or files matching `item`. It returns
70/// a `Vec<PathBuf>` to all found instances in order of closest to furthest away. The function only
71/// searches up within subdirectories of `end`.
72#[cfg(not(target_arch = "wasm32"))]
73pub fn search_upward(
74    start: impl AsRef<Path>,
75    end: impl AsRef<Path>,
76    item: impl AsRef<Path>,
77) -> Vec<PathBuf> {
78    start
79        .as_ref()
80        .ancestors()
81        .take_while(|p| p.starts_with(end.as_ref()))
82        .map(|p| p.join(&item))
83        .filter(|p| p.try_exists().is_ok_and(std::convert::identity))
84        .collect()
85}
86
87pub fn get_multi_extension_from_filename(filename: &str) -> Option<String> {
88    filename
89        .as_bytes()
90        .iter()
91        .position(|&c| c == b'.')
92        .map(|pos| {
93            let iter = filename.chars().skip(pos + 1);
94            iter.collect::<String>()
95        })
96}
97
98/// Get the full extension of a path, including all extensions.
99/// For example, for "foo.tar.gz", this function returns "tar.gz", and not just "gz",
100/// like path.extension() would.
101pub fn get_multi_extension(path: &Utf8PathBuf) -> Option<String> {
102    // Find the first . in the path, if any. Return the rest of the path.
103    if let Some(filename) = path.file_name() {
104        return get_multi_extension_from_filename(filename);
105    }
106    None
107}