css_mod/
utils.rs

1use anyhow::{Context, Result};
2use serde::Deserialize;
3use std::env;
4use std::fs::{create_dir_all, File};
5use std::io::Write;
6use std::path::{Path, PathBuf};
7
8pub fn write_file(file_path: &Path, content: String) -> Result<()> {
9    let dir_path = file_path
10        .parent()
11        .context("Failed to get parent directory")?;
12
13    create_dir_all(&dir_path)?;
14
15    let mut file = File::create(&file_path)
16        .with_context(|| format!("Failed to create file: {:?}", file_path))?;
17    file.write_all(content.as_bytes())?;
18
19    Ok(())
20}
21
22/// Gets path to workspace root directory of currently built package, or package root directory
23/// if it is not part of workspace.
24pub fn get_workspace_dir() -> Result<PathBuf> {
25    // this is ugly but the only way to get workspace directory path right now
26    // TODO: replace with environment variable when cargo supports it
27    // https://github.com/rust-lang/cargo/issues/3946
28    #[derive(Deserialize)]
29    struct Manifest {
30        workspace_root: String,
31    }
32    let package_dir = env::var("CARGO_MANIFEST_DIR").context("CARGO_MANIFEST_DIR")?;
33    let output = std::process::Command::new(env!("CARGO"))
34        .arg("metadata")
35        .arg("--format-version=1")
36        .current_dir(&package_dir)
37        .output()?;
38    let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
39    Ok(PathBuf::from(manifest.workspace_root))
40}
41
42// Resolves CSS module file path.
43//
44// * `source_path`: Source code file path from which CSS module is requested.
45//      In host-os-style (ie. on windows - with backward, otherwise - forward slash separators).
46//      Expected to be result of `file!()` macro.
47// * `css_module_path`: CSS module file path relative to source file.
48//      In posix-style (ie. with forward slash separators).
49pub fn resolve_module_file_path(
50    source_path: &str,
51    css_module_path: &str,
52    is_windows_host: bool,
53) -> String {
54    // normalize source path separators to posix-style, since `file!()` returns host-os-style paths.
55    // not using cfg!(windows) here because it corresponds to target (which is 'wasm' when
56    // been built for browser), not host os on which building is happening
57    let source_path_normalized = if is_windows_host {
58        source_path.replace('\\', "/")
59    } else {
60        source_path.to_owned()
61    };
62
63    join_paths(&source_path_normalized, css_module_path)
64}
65
66/// Joins file paths
67///
68/// Intention is to perform path joining with basic two-dot normalization as fast as possible.
69/// Two-dot normalization will only happen for two-dots at the beginning of rhs path.
70/// Both paths expected to be in posix-style (ie. with forward slash separators).
71fn join_paths(lhs: &str, rhs: &str) -> String {
72    let mut lhs = lhs.trim_end_matches(|c| c != '/');
73    let mut rhs = rhs;
74
75    while rhs.starts_with("../") {
76        lhs = lhs.trim_end_matches(|c| c != '/');
77        lhs = lhs.strip_suffix('/').unwrap_or("");
78        lhs = lhs.trim_end_matches(|c| c != '/');
79        rhs = rhs.strip_prefix("../").unwrap_or("");
80    }
81
82    lhs.to_owned() + rhs
83}
84
85#[cfg(test)]
86mod join_paths {
87    use super::*;
88
89    #[test]
90    fn basic() {
91        assert_eq!(join_paths("a/b/c", "d"), "a/b/d");
92        assert_eq!(join_paths("a/b/", "d"), "a/b/d");
93        assert_eq!(join_paths("a/b/", "c/d"), "a/b/c/d");
94    }
95
96    #[test]
97    fn two_dot_normalization() {
98        assert_eq!(join_paths("a/b/", "../d"), "a/d");
99        assert_eq!(join_paths("a/b/c", "../d"), "a/d");
100        assert_eq!(join_paths("a/b/c", "../../d"), "d");
101        assert_eq!(join_paths("a/b/c", "../../../d"), "d");
102    }
103
104    #[test]
105    fn exceptions() {
106        // doesn't normalize one dot
107        assert_eq!(join_paths("a/./b/c", "./d"), "a/./b/./d");
108
109        // doesn't normalize two dots not at the beginning of rhs
110        assert_eq!(join_paths("a/../b/", "c/../d"), "a/../b/c/../d");
111
112        // doesn't work with back slash separators
113        assert_eq!(join_paths("a\\b\\c", "d"), "d");
114        assert_eq!(join_paths("a/b/c", "..\\d"), "a/b/..\\d");
115    }
116}