mirror of
https://github.com/denoland/deno
synced 2024-08-28 20:39:51 +00:00
209 lines
5.7 KiB
Rust
209 lines
5.7 KiB
Rust
// 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<Item = &'a ModuleSpecifier>,
|
|
) -> BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> {
|
|
let mut root_specifiers: BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> =
|
|
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<String>,
|
|
) -> 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<String, AnyError> {
|
|
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::<Vec<_>>();
|
|
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::<Vec<_>>(),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let expected = expected
|
|
.into_iter()
|
|
.map(|(s, vec)| {
|
|
(
|
|
s.to_string(),
|
|
vec.into_iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
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")
|
|
);
|
|
}
|
|
}
|