summaryrefslogtreecommitdiff
path: root/cli/tools/vendor/specifiers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/vendor/specifiers.rs')
-rw-r--r--cli/tools/vendor/specifiers.rs251
1 files changed, 251 insertions, 0 deletions
diff --git a/cli/tools/vendor/specifiers.rs b/cli/tools/vendor/specifiers.rs
new file mode 100644
index 000000000..b869e989c
--- /dev/null
+++ b/cli/tools/vendor/specifiers.rs
@@ -0,0 +1,251 @@
+// 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")
+ );
+ }
+}