// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::collections::BTreeMap; use std::collections::HashSet; use std::path::PathBuf; use deno_ast::ModuleSpecifier; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use crate::util::path::is_banned_path_char; use crate::util::path::path_with_stem_suffix; use crate::util::path::root_url_to_safe_local_dirname; /// Partitions the provided specifiers by the non-path and non-query parts of a specifier. pub fn partition_by_root_specifiers<'a>( specifiers: impl Iterator, ) -> BTreeMap> { let mut root_specifiers: BTreeMap> = Default::default(); for remote_specifier in specifiers { let mut root_specifier = remote_specifier.clone(); root_specifier.set_query(None); root_specifier.set_path("/"); let specifiers = root_specifiers.entry(root_specifier).or_default(); specifiers.push(remote_specifier.clone()); } root_specifiers } /// Gets the directory name to use for the provided root. pub fn dir_name_for_root(root: &ModuleSpecifier) -> PathBuf { root_url_to_safe_local_dirname(root) } /// Gets a unique file path given the provided file path /// and the set of existing file paths. Inserts to the /// set when finding a unique path. pub fn get_unique_path( mut path: PathBuf, unique_set: &mut HashSet, ) -> PathBuf { let original_path = path.clone(); let mut count = 2; // case insensitive comparison so the output works on case insensitive file systems while !unique_set.insert(path.to_string_lossy().to_lowercase()) { path = path_with_stem_suffix(&original_path, &format!("_{count}")); count += 1; } path } pub fn make_url_relative( root: &ModuleSpecifier, url: &ModuleSpecifier, ) -> Result { root.make_relative(url).ok_or_else(|| { anyhow!( "Error making url ({}) relative to root: {}", url.to_string(), root.to_string() ) }) } pub fn is_remote_specifier(specifier: &ModuleSpecifier) -> bool { matches!(specifier.scheme().to_lowercase().as_str(), "http" | "https") } pub fn is_remote_specifier_text(text: &str) -> bool { let text = text.trim_start().to_lowercase(); text.starts_with("http:") || text.starts_with("https:") } pub fn sanitize_filepath(text: &str) -> String { text .chars() .map(|c| if is_banned_path_char(c) { '_' } else { c }) .collect() } #[cfg(test)] mod test { use super::*; use pretty_assertions::assert_eq; #[test] fn partition_by_root_specifiers_same_sub_folder() { run_partition_by_root_specifiers_test( vec![ "https://deno.land/x/mod/A.ts", "https://deno.land/x/mod/other/A.ts", ], vec![( "https://deno.land/", vec![ "https://deno.land/x/mod/A.ts", "https://deno.land/x/mod/other/A.ts", ], )], ); } #[test] fn partition_by_root_specifiers_different_sub_folder() { run_partition_by_root_specifiers_test( vec![ "https://deno.land/x/mod/A.ts", "https://deno.land/x/other/A.ts", ], vec![( "https://deno.land/", vec![ "https://deno.land/x/mod/A.ts", "https://deno.land/x/other/A.ts", ], )], ); } #[test] fn partition_by_root_specifiers_different_hosts() { run_partition_by_root_specifiers_test( vec![ "https://deno.land/mod/A.ts", "http://deno.land/B.ts", "https://deno.land:8080/C.ts", "https://localhost/mod/A.ts", "https://other/A.ts", ], vec![ ("http://deno.land/", vec!["http://deno.land/B.ts"]), ("https://deno.land/", vec!["https://deno.land/mod/A.ts"]), ( "https://deno.land:8080/", vec!["https://deno.land:8080/C.ts"], ), ("https://localhost/", vec!["https://localhost/mod/A.ts"]), ("https://other/", vec!["https://other/A.ts"]), ], ); } fn run_partition_by_root_specifiers_test( input: Vec<&str>, expected: Vec<(&str, Vec<&str>)>, ) { let input = input .iter() .map(|s| ModuleSpecifier::parse(s).unwrap()) .collect::>(); let output = partition_by_root_specifiers(input.iter()); // the assertion is much easier to compare when everything is strings let output = output .into_iter() .map(|(s, vec)| { ( s.to_string(), vec.into_iter().map(|s| s.to_string()).collect::>(), ) }) .collect::>(); let expected = expected .into_iter() .map(|(s, vec)| { ( s.to_string(), vec.into_iter().map(|s| s.to_string()).collect::>(), ) }) .collect::>(); assert_eq!(output, expected); } #[test] fn test_unique_path() { let mut paths = HashSet::new(); assert_eq!( get_unique_path(PathBuf::from("/test"), &mut paths), PathBuf::from("/test") ); assert_eq!( get_unique_path(PathBuf::from("/test"), &mut paths), PathBuf::from("/test_2") ); assert_eq!( get_unique_path(PathBuf::from("/test"), &mut paths), PathBuf::from("/test_3") ); assert_eq!( get_unique_path(PathBuf::from("/TEST"), &mut paths), PathBuf::from("/TEST_4") ); assert_eq!( get_unique_path(PathBuf::from("/test.txt"), &mut paths), PathBuf::from("/test.txt") ); assert_eq!( get_unique_path(PathBuf::from("/test.txt"), &mut paths), PathBuf::from("/test_2.txt") ); assert_eq!( get_unique_path(PathBuf::from("/TEST.TXT"), &mut paths), PathBuf::from("/TEST_3.TXT") ); } }