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
22pub fn get_workspace_dir() -> Result<PathBuf> {
25 #[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
42pub fn resolve_module_file_path(
50 source_path: &str,
51 css_module_path: &str,
52 is_windows_host: bool,
53) -> String {
54 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
66fn 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 assert_eq!(join_paths("a/./b/c", "./d"), "a/./b/./d");
108
109 assert_eq!(join_paths("a/../b/", "c/../d"), "a/../b/c/../d");
111
112 assert_eq!(join_paths("a\\b\\c", "d"), "d");
114 assert_eq!(join_paths("a/b/c", "..\\d"), "a/b/..\\d");
115 }
116}