Skip to main content

web_static_pack_packer/
pack_path.rs

1//! Pack path helpers. Contains [from_file_base_relative_path] that creates pack
2//! paths from fs paths.
3
4use crate::common::pack_path::PackPath;
5use anyhow::{Error, anyhow, ensure};
6use std::{
7    iter,
8    path::{Component, Path},
9};
10
11/// Creates pack path (eg. "/dir1/dir2/file.html") from relative fs path (eg.
12/// "workdir\\dir1\\dir2\\file.html").
13///
14/// # Examples
15///
16/// ```
17/// # use anyhow::Error;
18/// # use std::path::PathBuf;
19/// # use web_static_pack_packer::{
20/// #    common::pack_path::PackPath, pack_path::from_file_base_relative_path,
21/// # };
22/// #
23/// # fn main() -> Result<(), Error> {
24/// #
25/// assert_eq!(
26///     from_file_base_relative_path(&PathBuf::from("path\\to\\file.txt"))?,
27///     PackPath::from_string("/path/to/file.txt".to_owned()),
28/// );
29/// #
30/// # Ok(())
31/// # }
32/// ```
33pub fn from_file_base_relative_path(file_base_relative_path: &Path) -> Result<PackPath, Error> {
34    assert!(file_base_relative_path.is_relative());
35
36    // list of path components, eg. ["dir1", "dir2", "file.bin"]
37    let file_base_relative_path_components = file_base_relative_path
38        .components()
39        .map(|component| {
40            // we cannot handle things like '/' or '.' or '..' here
41            ensure!(
42                matches!(component, Component::Normal(_)),
43                "relative path must not contain only standard path items, got {:?}",
44                component
45            );
46
47            component
48                .as_os_str()
49                .to_str()
50                .ok_or_else(|| anyhow!("cannot convert path component to string"))
51        })
52        .collect::<Result<Vec<_>, Error>>()?;
53
54    // we add empty element at the beginning to have path starting with /
55    let pack_path_string = itertools::join(
56        iter::once("").chain(file_base_relative_path_components),
57        "/",
58    );
59
60    // convert into pack path
61    let pack_path = PackPath::from_string(pack_path_string);
62
63    Ok(pack_path)
64}
65
66#[cfg(test)]
67mod test {
68    use super::from_file_base_relative_path;
69    use crate::common::pack_path::PackPath;
70    use std::path::{Path, PathBuf};
71    use test_case::test_case;
72
73    #[test_case(
74        &PathBuf::from("somefile"),
75        &PackPath::from_string("/somefile".to_owned());
76        "base file path without prefix"
77    )]
78    #[test_case(
79        &PathBuf::from("linux/like/relative/path.html"),
80        &PackPath::from_string("/linux/like/relative/path.html".to_owned());
81        "linux like relative path"
82    )]
83    #[test_case(
84        &PathBuf::from("Project\\MyApp\\Application.js"),
85        &PackPath::from_string("/Project/MyApp/Application.js".to_owned());
86        "windows relative path"
87    )]
88    fn from_file_base_relative_path_returns_expected(
89        path: &Path,
90        expected: &PackPath,
91    ) {
92        assert_eq!(&from_file_base_relative_path(path).unwrap(), expected);
93    }
94}