1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00
denoland-deno/cli/tools/vendor/specifiers.rs
2022-02-16 13:14:19 -05:00

251 lines
6.7 KiB
Rust

// Copyright 2018-2022 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::fs_util::path_with_stem_suffix;
/// 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 {
let mut result = String::new();
if let Some(domain) = root.domain() {
result.push_str(&sanitize_segment(domain));
}
if let Some(port) = root.port() {
if !result.is_empty() {
result.push('_');
}
result.push_str(&port.to_string());
}
let mut result = PathBuf::from(result);
if let Some(segments) = root.path_segments() {
for segment in segments.filter(|s| !s.is_empty()) {
result = result.join(sanitize_segment(segment));
}
}
result
}
/// 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 {
specifier.scheme().to_lowercase().starts_with("http")
}
pub fn is_remote_specifier_text(text: &str) -> bool {
text.trim_start().to_lowercase().starts_with("http")
}
pub fn sanitize_filepath(text: &str) -> String {
text
.chars()
.map(|c| if is_banned_path_char(c) { '_' } else { c })
.collect()
}
fn is_banned_path_char(c: char) -> bool {
matches!(c, '<' | '>' | ':' | '"' | '|' | '?' | '*')
}
fn sanitize_segment(text: &str) -> String {
text
.chars()
.map(|c| if is_banned_segment_char(c) { '_' } else { c })
.collect()
}
fn is_banned_segment_char(c: char) -> bool {
matches!(c, '/' | '\\') || is_banned_path_char(c)
}
#[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 should_get_dir_name_root() {
run_test("http://deno.land/x/test", "deno.land/x/test");
run_test("http://localhost", "localhost");
run_test("http://localhost/test%20:test", "localhost/test%20_test");
fn run_test(specifier: &str, expected: &str) {
assert_eq!(
dir_name_for_root(&ModuleSpecifier::parse(specifier).unwrap()),
PathBuf::from(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")
);
}
}