⭐️ A friendly language for building type-safe, scalable systems!

use UTF8 paths from camino (#2277)



Co-authored-by: BENJAMIN PEINHARDT <ben.peinhardt@siteimpact.com>

authored by Benjamin Peinhardt BENJAMIN PEINHARDT and committed by GitHub 228048ec 02320566

+13
Cargo.lock
··· 211 211 checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 212 212 213 213 [[package]] 214 + name = "camino" 215 + version = "1.1.6" 216 + source = "registry+https://github.com/rust-lang/crates.io-index" 217 + checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" 218 + dependencies = [ 219 + "serde", 220 + ] 221 + 222 + [[package]] 214 223 name = "capnp" 215 224 version = "0.14.11" 216 225 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 708 717 "atty", 709 718 "base16", 710 719 "bytes", 720 + "camino", 711 721 "clap", 712 722 "ctrlc", 713 723 "debug-ignore", ··· 757 767 "base16", 758 768 "bincode", 759 769 "bytes", 770 + "camino", 760 771 "capnp", 761 772 "capnpc", 762 773 "codespan-reporting", ··· 801 812 name = "gleam-wasm" 802 813 version = "0.30.4" 803 814 dependencies = [ 815 + "camino", 804 816 "console_error_panic_hook", 805 817 "gleam-core", 806 818 "hexpm", ··· 2107 2119 name = "test-package-compiler" 2108 2120 version = "0.30.4" 2109 2121 dependencies = [ 2122 + "camino", 2110 2123 "gleam-core", 2111 2124 "im", 2112 2125 "insta",
+1
compiler-cli/Cargo.toml
··· 82 82 same-file = "1.0.6" 83 83 # Open generated docs in browser 84 84 opener = "0.6" 85 + camino = { version = "1.1.6", features = ["serde1"] } 85 86 86 87 [dev-dependencies] 87 88 # Test assertion errors with diffs
+4
compiler-cli/clippy.toml
··· 1 + disallowed-methods = [ 2 + { path = "std::path::Path::new", reason = "Manually constructed paths should use camino::Utf8Path" }, 3 + { path = "std::path::PathBuf::new", reason = "Manually constructed pathbufs should use camino::Utf8Path" }, 4 + ]
+3 -3
compiler-cli/src/add.rs
··· 1 - use std::path::{Path, PathBuf}; 1 + use camino::{Utf8Path, Utf8PathBuf}; 2 2 3 3 use gleam_core::{ 4 4 error::{FileIoAction, FileKind}, ··· 25 25 .map_err(|e| Error::FileIo { 26 26 kind: FileKind::File, 27 27 action: FileIoAction::Parse, 28 - path: PathBuf::from("gleam.toml"), 28 + path: Utf8PathBuf::from("gleam.toml"), 29 29 err: Some(e.to_string()), 30 30 })?; 31 31 ··· 56 56 } 57 57 58 58 // Write the updated config 59 - fs::write(Path::new("gleam.toml"), &toml.to_string())?; 59 + fs::write(Utf8Path::new("gleam.toml"), &toml.to_string())?; 60 60 61 61 Ok(()) 62 62 }
+2 -2
compiler-cli/src/build.rs
··· 11 11 build_lock::BuildLock, 12 12 cli, 13 13 dependencies::UseManifest, 14 - fs::{self, ConsoleWarningEmitter}, 14 + fs::{self, get_current_directory, ConsoleWarningEmitter}, 15 15 }; 16 16 17 17 pub fn download_dependencies() -> Result<Manifest> { ··· 31 31 options.mode, 32 32 options.target.unwrap_or(root_config.target), 33 33 )?; 34 - let current_dir = std::env::current_dir().expect("Failed to get current directory"); 34 + let current_dir = get_current_directory().expect("Failed to get current directory"); 35 35 36 36 tracing::info!("Compiling packages"); 37 37 let compiled = {
+3 -3
compiler-cli/src/build_lock.rs
··· 1 + use camino::Utf8PathBuf; 1 2 use gleam_core::{ 2 3 build::{Mode, Target, Telemetry}, 3 4 paths::ProjectPaths, 4 5 Result, 5 6 }; 6 - use std::path::PathBuf; 7 7 use strum::IntoEnumIterator; 8 8 9 9 #[derive(Debug)] 10 10 pub(crate) struct BuildLock { 11 - directory: PathBuf, 11 + directory: Utf8PathBuf, 12 12 } 13 13 14 14 impl BuildLock { ··· 36 36 crate::fs::mkdir(&self.directory).expect("Could not create lock directory"); 37 37 38 38 let lock_path = self.directory.join("gleam.lock"); 39 - let mut file = fslock::LockFile::open(&lock_path).expect("LockFile creation"); 39 + let mut file = fslock::LockFile::open(lock_path.as_str()).expect("LockFile creation"); 40 40 41 41 if !file.try_lock_with_pid().expect("Trying build locking") { 42 42 telemetry.waiting_for_build_directory_lock();
+4 -2
compiler-cli/src/compile_package.rs
··· 3 3 fs::{self, ConsoleWarningEmitter, ProjectIO}, 4 4 CompilePackage, 5 5 }; 6 + use camino::Utf8Path; 6 7 use gleam_core::{ 7 8 build::{Mode, PackageCompiler, StaleTracker, Target, TargetCodegenConfiguration}, 8 9 metadata, ··· 13 14 Result, 14 15 }; 15 16 use smol_str::SmolStr; 16 - use std::{path::Path, sync::Arc}; 17 + use std::sync::Arc; 17 18 18 19 pub fn command(options: CompilePackage) -> Result<()> { 19 20 let ids = UniqueIdGenerator::new(); ··· 56 57 57 58 fn load_libraries( 58 59 ids: &UniqueIdGenerator, 59 - lib: &Path, 60 + lib: &Utf8Path, 60 61 ) -> Result<im::HashMap<SmolStr, ModuleInterface>> { 61 62 tracing::info!("Reading precompiled module metadata files"); 62 63 let mut manifests = im::HashMap::new(); ··· 71 72 let _ = manifests.insert(module.name.clone(), module); 72 73 } 73 74 } 75 + 74 76 Ok(manifests) 75 77 }
+5 -3
compiler-cli/src/config.rs
··· 1 - use std::path::PathBuf; 1 + use camino::Utf8PathBuf; 2 2 3 3 use gleam_core::{ 4 4 config::PackageConfig, ··· 7 7 paths::ProjectPaths, 8 8 }; 9 9 10 + use crate::fs::get_current_directory; 11 + 10 12 pub fn root_config() -> Result<PackageConfig, Error> { 11 - let current_dir = std::env::current_dir().expect("Could not get current directory"); 13 + let current_dir = get_current_directory().expect("Failed to get current directory"); 12 14 let paths = ProjectPaths::new(current_dir); 13 15 read(paths.root_config()) 14 16 } ··· 53 55 } 54 56 } 55 57 56 - pub fn read(config_path: PathBuf) -> Result<PackageConfig, Error> { 58 + pub fn read(config_path: Utf8PathBuf) -> Result<PackageConfig, Error> { 57 59 let toml = crate::fs::read(&config_path)?; 58 60 let config: PackageConfig = toml::from_str(&toml).map_err(|e| Error::FileIo { 59 61 action: FileIoAction::Parse,
+21 -21
compiler-cli/src/dependencies.rs
··· 1 1 use std::{ 2 2 collections::{HashMap, HashSet}, 3 - path::{Path, PathBuf}, 4 3 time::Instant, 5 4 }; 6 5 6 + use camino::{Utf8Path, Utf8PathBuf}; 7 7 use flate2::read::GzDecoder; 8 8 use futures::future; 9 9 use gleam_core::{ ··· 522 522 #[derive(Clone, Eq, Debug)] 523 523 enum ProvidedPackageSource { 524 524 Git { repo: SmolStr, commit: SmolStr }, 525 - Local { path: PathBuf }, 525 + Local { path: Utf8PathBuf }, 526 526 } 527 527 528 528 impl ProvidedPackage { ··· 587 587 format!(r#"{{ repo: "{}", commit: "{}" }}"#, repo, commit) 588 588 } 589 589 Self::Local { path } => { 590 - format!(r#"{{ path: "{}" }}"#, path.to_string_lossy()) 590 + format!(r#"{{ path: "{}" }}"#, path) 591 591 } 592 592 } 593 593 } ··· 689 689 /// Provide a package from a local project 690 690 fn provide_local_package( 691 691 package_name: SmolStr, 692 - package_path: &Path, 693 - parent_path: &Path, 692 + package_path: &Utf8Path, 693 + parent_path: &Utf8Path, 694 694 project_paths: &ProjectPaths, 695 695 provided: &mut HashMap<SmolStr, ProvidedPackage>, 696 696 parents: &mut Vec<SmolStr>, ··· 730 730 /// Adds a gleam project located at a specific path to the list of "provided packages" 731 731 fn provide_package( 732 732 package_name: SmolStr, 733 - package_path: PathBuf, 733 + package_path: Utf8PathBuf, 734 734 package_source: ProvidedPackageSource, 735 735 project_paths: &ProjectPaths, 736 736 provided: &mut HashMap<SmolStr, ProvidedPackage>, ··· 820 820 let project_paths = crate::project_paths_at_current_directory(); 821 821 let result = provide_local_package( 822 822 "wrong_name".into(), 823 - Path::new("./test/hello_world"), 824 - Path::new("./"), 823 + Utf8Path::new("./test/hello_world"), 824 + Utf8Path::new("./"), 825 825 &project_paths, 826 826 &mut provided, 827 827 &mut vec!["root".into(), "subpackage".into()], ··· 844 844 845 845 let result = provide_local_package( 846 846 "hello_world".into(), 847 - Path::new("./test/hello_world"), 848 - Path::new("./"), 847 + Utf8Path::new("./test/hello_world"), 848 + Utf8Path::new("./"), 849 849 &project_paths, 850 850 &mut provided, 851 851 &mut vec!["root".into(), "subpackage".into()], ··· 857 857 858 858 let result = provide_local_package( 859 859 "hello_world".into(), 860 - Path::new("./test/hello_world"), 861 - Path::new("./"), 860 + Utf8Path::new("./test/hello_world"), 861 + Utf8Path::new("./"), 862 862 &project_paths, 863 863 &mut provided, 864 864 &mut vec!["root".into(), "subpackage".into()], ··· 875 875 let project_paths = crate::project_paths_at_current_directory(); 876 876 let result = provide_local_package( 877 877 "hello_world".into(), 878 - Path::new("./test/hello_world"), 879 - Path::new("./"), 878 + Utf8Path::new("./test/hello_world"), 879 + Utf8Path::new("./"), 880 880 &project_paths, 881 881 &mut provided, 882 882 &mut vec!["root".into(), "subpackage".into()], ··· 888 888 889 889 let result = provide_package( 890 890 "hello_world".into(), 891 - PathBuf::from("./test/other"), 891 + Utf8PathBuf::from("./test/other"), 892 892 ProvidedPackageSource::Local { 893 - path: Path::new("./test/other").to_path_buf(), 893 + path: Utf8Path::new("./test/other").to_path_buf(), 894 894 }, 895 895 &project_paths, 896 896 &mut provided, ··· 909 909 let project_paths = crate::project_paths_at_current_directory(); 910 910 let result = provide_local_package( 911 911 "hello_world".into(), 912 - Path::new("./test/hello_world"), 913 - Path::new("./"), 912 + Utf8Path::new("./test/hello_world"), 913 + Utf8Path::new("./"), 914 914 &project_paths, 915 915 &mut provided, 916 916 &mut vec!["root".into(), "subpackage".into()], ··· 933 933 let project_paths = crate::project_paths_at_current_directory(); 934 934 let result = provide_local_package( 935 935 "hello_world".into(), 936 - Path::new("./test/hello_world"), 937 - Path::new("./"), 936 + Utf8Path::new("./test/hello_world"), 937 + Utf8Path::new("./"), 938 938 &project_paths, 939 939 &mut provided, 940 940 &mut vec!["root".into(), "hello_world".into(), "subpackage".into()], ··· 1006 1006 1007 1007 fn io_result_unpack( 1008 1008 &self, 1009 - path: &Path, 1009 + path: &Utf8Path, 1010 1010 mut archive: tar::Archive<GzDecoder<tar::Entry<'_, WrappedReader>>>, 1011 1011 ) -> std::io::Result<()> { 1012 1012 archive.unpack(path)
+4 -3
compiler-cli/src/docs.rs
··· 1 - use std::path::Path; 2 1 use std::time::Instant; 2 + 3 + use camino::Utf8Path; 3 4 4 5 use crate::{cli, hex::ApiKeyCommand, http::HttpClient}; 5 6 use gleam_core::{ ··· 84 85 println!( 85 86 "\nThe documentation for {package} has been rendered to \n{index_html}", 86 87 package = config.name, 87 - index_html = index_html.to_string_lossy() 88 + index_html = index_html 88 89 ); 89 90 90 91 if options.open { ··· 99 100 /// 100 101 /// For the docs this will generally be a browser (unless some other program is 101 102 /// configured as the default for `.html` files). 102 - fn open_docs(path: &Path) -> Result<()> { 103 + fn open_docs(path: &Utf8Path) -> Result<()> { 103 104 opener::open(path).map_err(|error| Error::FailedToOpenDocs { 104 105 path: path.to_path_buf(), 105 106 error: error.to_string(),
+6 -6
compiler-cli/src/export.rs
··· 46 46 continue; 47 47 } 48 48 49 - let name = path.file_name().expect("Directory name").to_string_lossy(); 50 - let build = build.join(name.as_ref()); 51 - let out = out.join(name.as_ref()); 49 + let name = path.file_name().expect("Directory name"); 50 + let build = build.join(name); 51 + let out = out.join(name); 52 52 crate::fs::mkdir(&out)?; 53 53 54 54 // Copy desired package subdirectories ··· 79 79 80 80 {entrypoint} 81 81 ", 82 - path = out.to_string_lossy(), 83 - entrypoint = entrypoint.to_string_lossy(), 82 + path = out, 83 + entrypoint = entrypoint, 84 84 ); 85 85 86 86 Ok(()) ··· 97 97 " 98 98 Your hex tarball has been generated in {}. 99 99 ", 100 - &path.display() 100 + &path 101 101 ); 102 102 Ok(()) 103 103 }
+5 -4
compiler-cli/src/fix.rs
··· 1 + use camino::Utf8PathBuf; 1 2 use gleam_core::{ 2 3 build::Target, 3 4 error::{FileIoAction, FileKind}, 4 5 Error, Result, 5 6 }; 6 - use std::{path::PathBuf, str::FromStr}; 7 + use std::str::FromStr; 7 8 8 9 pub fn run(target: Option<Target>, files: Vec<String>) -> Result<()> { 9 10 let mut complete = true; 10 11 11 12 for file_path in files { 12 - let path = PathBuf::from_str(&file_path).map_err(|e| Error::FileIo { 13 + let path = Utf8PathBuf::from_str(&file_path).map_err(|e| Error::FileIo { 13 14 action: FileIoAction::Open, 14 15 kind: FileKind::File, 15 - path: PathBuf::from(file_path), 16 + path: Utf8PathBuf::from(file_path), 16 17 err: Some(e.to_string()), 17 18 })?; 18 19 ··· 35 36 Ok(()) 36 37 } 37 38 38 - fn fix_file(target: Option<Target>, path: PathBuf) -> Result<bool> { 39 + fn fix_file(target: Option<Target>, path: Utf8PathBuf) -> Result<bool> { 39 40 let src = crate::fs::read(&path)?; 40 41 let (out, complete) = gleam_core::fix::parse_fix_and_format(target, &src.into(), &path)?; 41 42 crate::fs::write(&path, &out)?;
+9 -11
compiler-cli/src/format.rs
··· 3 3 io::Content, 4 4 io::OutputFile, 5 5 }; 6 - use std::{ 7 - io::Read, 8 - path::{Path, PathBuf}, 9 - str::FromStr, 10 - }; 6 + use std::{io::Read, str::FromStr}; 7 + 8 + use camino::{Utf8Path, Utf8PathBuf}; 11 9 12 10 pub fn run(stdin: bool, check: bool, files: Vec<String>) -> Result<()> { 13 11 if stdin { ··· 20 18 fn process_stdin(check: bool) -> Result<()> { 21 19 let src = read_stdin()?.into(); 22 20 let mut out = String::new(); 23 - gleam_core::format::pretty(&mut out, &src, Path::new("<stdin>"))?; 21 + gleam_core::format::pretty(&mut out, &src, Utf8Path::new("<stdin>"))?; 24 22 25 23 if !check { 26 24 print!("{out}"); ··· 30 28 if src != out { 31 29 return Err(Error::Format { 32 30 problem_files: vec![Unformatted { 33 - source: PathBuf::from("<standard input>"), 34 - destination: PathBuf::from("<standard output>"), 31 + source: Utf8PathBuf::from("<standard input>"), 32 + destination: Utf8PathBuf::from("<standard output>"), 35 33 input: src, 36 34 output: out, 37 35 }], ··· 73 71 let mut problem_files = Vec::with_capacity(files.len()); 74 72 75 73 for file_path in files { 76 - let path = PathBuf::from_str(&file_path).map_err(|e| Error::FileIo { 74 + let path = Utf8PathBuf::from_str(&file_path).map_err(|e| Error::FileIo { 77 75 action: FileIoAction::Open, 78 76 kind: FileKind::File, 79 - path: PathBuf::from(file_path), 77 + path: Utf8PathBuf::from(file_path), 80 78 err: Some(e.to_string()), 81 79 })?; 82 80 ··· 92 90 Ok(problem_files) 93 91 } 94 92 95 - fn format_file(problem_files: &mut Vec<Unformatted>, path: PathBuf) -> Result<()> { 93 + fn format_file(problem_files: &mut Vec<Unformatted>, path: Utf8PathBuf) -> Result<()> { 96 94 let src = crate::fs::read(&path)?.into(); 97 95 let mut output = String::new(); 98 96 gleam_core::format::pretty(&mut output, &src, &path)?;
+117 -101
compiler-cli/src/fs.rs
··· 17 17 fmt::Debug, 18 18 fs::File, 19 19 io::{self, BufRead, BufReader, Write}, 20 - path::{Path, PathBuf}, 21 20 time::SystemTime, 22 21 }; 22 + 23 + use camino::{ReadDirUtf8, Utf8Path, Utf8PathBuf}; 23 24 24 25 use crate::{dependencies::UseManifest, lsp::LspLocker}; 25 26 26 27 #[cfg(test)] 27 28 mod tests; 28 29 30 + pub fn get_current_directory() -> Result<Utf8PathBuf, &'static str> { 31 + let curr_dir = std::env::current_dir().map_err(|_| "Failed to get current directory")?; 32 + Utf8PathBuf::from_path_buf(curr_dir).map_err(|_| "Non Utf8 Path") 33 + } 34 + 29 35 /// A `FileWriter` implementation that writes to the file system. 30 36 #[derive(Debug, Clone, Copy)] 31 37 pub struct ProjectIO; ··· 41 47 } 42 48 43 49 impl FileSystemReader for ProjectIO { 44 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf> { 50 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 45 51 if !dir.is_dir() { 46 52 return vec![]; 47 53 } ··· 53 59 .filter(|e| e.file_type().is_file()) 54 60 .map(|d| d.into_path()) 55 61 .filter(move |d| d.extension() == Some(OsStr::new("gleam"))) 62 + .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf-8 Path")) 56 63 .collect() 57 64 } 58 65 59 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf> { 66 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 60 67 if !dir.is_dir() { 61 68 return vec![]; 62 69 } ··· 68 75 .filter(|e| e.file_type().is_file()) 69 76 .map(|d| d.into_path()) 70 77 .filter(|p| p.extension().and_then(OsStr::to_str) == Some("cache")) 78 + .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf-8 Path")) 71 79 .collect() 72 80 } 73 81 74 - fn read(&self, path: &Path) -> Result<String, Error> { 82 + fn read(&self, path: &Utf8Path) -> Result<String, Error> { 75 83 read(path) 76 84 } 77 85 78 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, Error> { 86 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error> { 79 87 read_bytes(path) 80 88 } 81 89 82 - fn is_file(&self, path: &Path) -> bool { 90 + fn is_file(&self, path: &Utf8Path) -> bool { 83 91 path.is_file() 84 92 } 85 93 86 - fn is_directory(&self, path: &Path) -> bool { 94 + fn is_directory(&self, path: &Utf8Path) -> bool { 87 95 path.is_dir() 88 96 } 89 97 90 - fn reader(&self, path: &Path) -> Result<WrappedReader, Error> { 98 + fn reader(&self, path: &Utf8Path) -> Result<WrappedReader, Error> { 91 99 reader(path) 92 100 } 93 101 94 - fn read_dir(&self, path: &Path) -> Result<ReadDir> { 102 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir> { 95 103 read_dir(path).map(|entries| { 96 104 entries 97 105 .map(|result| result.map(|entry| DirEntry::from_path(entry.path()))) ··· 99 107 }) 100 108 } 101 109 102 - fn modification_time(&self, path: &Path) -> Result<SystemTime, Error> { 110 + fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime, Error> { 103 111 path.metadata() 104 112 .map(|m| m.modified().unwrap_or_else(|_| SystemTime::now())) 105 113 .map_err(|e| Error::FileIo { ··· 110 118 }) 111 119 } 112 120 113 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, Error> { 121 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error> { 114 122 canonicalise(path) 115 123 } 116 124 } 117 125 118 126 impl FileSystemWriter for ProjectIO { 119 - fn delete(&self, path: &Path) -> Result<()> { 127 + fn delete(&self, path: &Utf8Path) -> Result<()> { 120 128 delete_dir(path) 121 129 } 122 130 123 - fn copy(&self, from: &Path, to: &Path) -> Result<()> { 131 + fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 124 132 copy(from, to) 125 133 } 126 134 127 - fn copy_dir(&self, from: &Path, to: &Path) -> Result<()> { 135 + fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 128 136 copy_dir(from, to) 129 137 } 130 138 131 - fn mkdir(&self, path: &Path) -> Result<(), Error> { 139 + fn mkdir(&self, path: &Utf8Path) -> Result<(), Error> { 132 140 mkdir(path) 133 141 } 134 142 135 - fn hardlink(&self, from: &Path, to: &Path) -> Result<(), Error> { 143 + fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { 136 144 hardlink(from, to) 137 145 } 138 146 139 - fn symlink_dir(&self, from: &Path, to: &Path) -> Result<(), Error> { 147 + fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { 140 148 symlink_dir(from, to) 141 149 } 142 150 143 - fn delete_file(&self, path: &Path) -> Result<()> { 151 + fn delete_file(&self, path: &Utf8Path) -> Result<()> { 144 152 delete_file(path) 145 153 } 146 154 147 - fn write(&self, path: &Path, content: &str) -> Result<(), Error> { 155 + fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { 148 156 write(path, content) 149 157 } 150 158 151 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<(), Error> { 159 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { 152 160 write_bytes(path, content) 153 161 } 154 162 } ··· 159 167 program: &str, 160 168 args: &[String], 161 169 env: &[(&str, String)], 162 - cwd: Option<&Path>, 170 + cwd: Option<&Utf8Path>, 163 171 stdio: Stdio, 164 172 ) -> Result<i32, Error> { 165 173 tracing::trace!(program=program, args=?args.join(" "), env=?env, cwd=?cwd, "command_exec"); ··· 168 176 .stdin(stdio.get_process_stdio()) 169 177 .stdout(stdio.get_process_stdio()) 170 178 .envs(env.iter().map(|(a, b)| (a, b))) 171 - .current_dir(cwd.unwrap_or_else(|| Path::new("./"))) 179 + .current_dir(cwd.unwrap_or_else(|| Utf8Path::new("./"))) 172 180 .status(); 173 181 174 182 match result { ··· 201 209 } 202 210 } 203 211 204 - pub fn delete_dir(dir: &Path) -> Result<(), Error> { 212 + pub fn delete_dir(dir: &Utf8Path) -> Result<(), Error> { 205 213 tracing::trace!(path=?dir, "deleting_directory"); 206 214 if dir.exists() { 207 215 std::fs::remove_dir_all(dir).map_err(|e| Error::FileIo { ··· 216 224 Ok(()) 217 225 } 218 226 219 - pub fn delete_file(file: &Path) -> Result<(), Error> { 227 + pub fn delete_file(file: &Utf8Path) -> Result<(), Error> { 220 228 tracing::trace!("Deleting file {:?}", file); 221 229 if file.exists() { 222 230 std::fs::remove_file(file).map_err(|e| Error::FileIo { ··· 231 239 Ok(()) 232 240 } 233 241 234 - pub fn write_outputs_under(outputs: &[OutputFile], base: &Path) -> Result<(), Error> { 242 + pub fn write_outputs_under(outputs: &[OutputFile], base: &Utf8Path) -> Result<(), Error> { 235 243 for file in outputs { 236 244 let path = base.join(&file.path); 237 245 match &file.content { ··· 250 258 } 251 259 } 252 260 253 - pub fn write(path: &Path, text: &str) -> Result<(), Error> { 261 + pub fn write(path: &Utf8Path, text: &str) -> Result<(), Error> { 254 262 write_bytes(path, text.as_bytes()) 255 263 } 256 264 257 265 #[cfg(target_family = "unix")] 258 - pub fn make_executable(path: impl AsRef<Path>) -> Result<(), Error> { 266 + pub fn make_executable(path: impl AsRef<Utf8Path>) -> Result<(), Error> { 259 267 use std::os::unix::fs::PermissionsExt; 260 268 tracing::trace!(path = ?path.as_ref(), "setting_permissions"); 261 269 ··· 271 279 } 272 280 273 281 #[cfg(not(target_family = "unix"))] 274 - pub fn make_executable(_path: impl AsRef<Path>) -> Result<(), Error> { 282 + pub fn make_executable(_path: impl AsRef<Utf8Path>) -> Result<(), Error> { 275 283 Ok(()) 276 284 } 277 285 278 - pub fn write_bytes(path: &Path, bytes: &[u8]) -> Result<(), Error> { 286 + pub fn write_bytes(path: &Utf8Path, bytes: &[u8]) -> Result<(), Error> { 279 287 tracing::trace!(path=?path, "writing_file"); 280 288 281 289 let dir_path = path.parent().ok_or_else(|| Error::FileIo { ··· 308 316 Ok(()) 309 317 } 310 318 311 - fn is_gleam_path(path: &Path, dir: impl AsRef<Path>) -> bool { 319 + fn is_gleam_path(path: &Utf8Path, dir: impl AsRef<Utf8Path>) -> bool { 312 320 use regex::Regex; 313 321 lazy_static! { 314 322 static ref RE: Regex = Regex::new(&format!( ··· 320 328 } 321 329 322 330 RE.is_match( 323 - path.strip_prefix(dir) 331 + path.strip_prefix(dir.as_ref()) 324 332 .expect("is_gleam_path(): strip_prefix") 325 - .to_str() 326 - .expect("is_gleam_path(): to_str"), 333 + .as_str(), 327 334 ) 328 335 } 329 336 330 - pub fn gleam_files_excluding_gitignore(dir: &Path) -> impl Iterator<Item = PathBuf> + '_ { 337 + pub fn gleam_files_excluding_gitignore(dir: &Utf8Path) -> impl Iterator<Item = Utf8PathBuf> + '_ { 331 338 ignore::WalkBuilder::new(dir) 332 339 .follow_links(true) 333 340 .require_git(false) ··· 335 342 .filter_map(Result::ok) 336 343 .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false)) 337 344 .map(ignore::DirEntry::into_path) 345 + .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf-8 Path")) 338 346 .filter(move |d| is_gleam_path(d, dir)) 339 347 } 340 348 341 - pub fn native_files(dir: &Path) -> Result<impl Iterator<Item = PathBuf> + '_> { 349 + pub fn native_files(dir: &Utf8Path) -> Result<impl Iterator<Item = Utf8PathBuf> + '_> { 342 350 Ok(read_dir(dir)? 343 351 .flat_map(Result::ok) 344 - .map(|e| e.path()) 352 + .map(|e| e.into_path()) 345 353 .filter(|path| { 346 - let extension = path 347 - .extension() 348 - .unwrap_or_default() 349 - .to_str() 350 - .unwrap_or_default(); 354 + let extension = path.extension().unwrap_or_default(); 351 355 matches!(extension, "erl" | "hrl" | "ex" | "js" | "mjs" | "ts") 352 356 })) 353 357 } 354 358 355 - pub fn private_files_excluding_gitignore(dir: &Path) -> impl Iterator<Item = PathBuf> + '_ { 359 + pub fn private_files_excluding_gitignore(dir: &Utf8Path) -> impl Iterator<Item = Utf8PathBuf> + '_ { 356 360 ignore::WalkBuilder::new(dir) 357 361 .follow_links(true) 358 362 .require_git(false) ··· 360 364 .filter_map(Result::ok) 361 365 .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false)) 362 366 .map(ignore::DirEntry::into_path) 367 + .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf-8 Path")) 363 368 } 364 369 365 - pub fn erlang_files(dir: &Path) -> Result<impl Iterator<Item = PathBuf> + '_> { 370 + pub fn erlang_files(dir: &Utf8Path) -> Result<impl Iterator<Item = Utf8PathBuf> + '_> { 366 371 Ok(read_dir(dir)? 367 372 .flat_map(Result::ok) 368 - .map(|e| e.path()) 373 + .map(|e| e.into_path()) 369 374 .filter(|path| { 370 - let extension = path 371 - .extension() 372 - .unwrap_or_default() 373 - .to_str() 374 - .unwrap_or_default(); 375 + let extension = path.extension().unwrap_or_default(); 375 376 extension == "erl" || extension == "hrl" 376 377 })) 377 378 } ··· 405 406 .map_err(|e| Error::Gzip(e.to_string())) 406 407 } 407 408 408 - pub fn mkdir(path: impl AsRef<Path> + Debug) -> Result<(), Error> { 409 + pub fn mkdir(path: impl AsRef<Utf8Path> + Debug) -> Result<(), Error> { 409 410 if path.as_ref().exists() { 410 411 return Ok(()); 411 412 } 412 413 413 414 tracing::trace!(path=?path, "creating_directory"); 414 415 415 - std::fs::create_dir_all(&path).map_err(|err| Error::FileIo { 416 + std::fs::create_dir_all(path.as_ref()).map_err(|err| Error::FileIo { 416 417 kind: FileKind::Directory, 417 - path: PathBuf::from(path.as_ref()), 418 + path: Utf8PathBuf::from(path.as_ref()), 418 419 action: FileIoAction::Create, 419 420 err: Some(err.to_string()), 420 421 }) 421 422 } 422 423 423 - pub fn read_dir(path: impl AsRef<Path> + Debug) -> Result<std::fs::ReadDir, Error> { 424 + pub fn read_dir(path: impl AsRef<Utf8Path> + Debug) -> Result<ReadDirUtf8, Error> { 424 425 tracing::trace!(path=?path,"reading_directory"); 425 426 426 - std::fs::read_dir(&path).map_err(|e| Error::FileIo { 427 + Utf8Path::read_dir_utf8(path.as_ref()).map_err(|e| Error::FileIo { 427 428 action: FileIoAction::Read, 428 429 kind: FileKind::Directory, 429 - path: PathBuf::from(path.as_ref()), 430 + path: Utf8PathBuf::from(path.as_ref()), 430 431 err: Some(e.to_string()), 431 432 }) 432 433 } 433 434 434 435 pub fn module_caches_paths( 435 - path: impl AsRef<Path> + Debug, 436 - ) -> Result<impl Iterator<Item = PathBuf>, Error> { 436 + path: impl AsRef<Utf8Path> + Debug, 437 + ) -> Result<impl Iterator<Item = Utf8PathBuf>, Error> { 437 438 Ok(read_dir(path)? 438 439 .filter_map(Result::ok) 439 - .map(|f| f.path()) 440 - .filter(|p| p.extension().and_then(OsStr::to_str) == Some("cache"))) 440 + .map(|f| f.into_path()) 441 + .filter(|p| p.extension() == Some("cache"))) 441 442 } 442 443 443 - pub fn read(path: impl AsRef<Path> + Debug) -> Result<String, Error> { 444 + pub fn read(path: impl AsRef<Utf8Path> + Debug) -> Result<String, Error> { 444 445 tracing::trace!(path=?path,"reading_file"); 445 446 446 - std::fs::read_to_string(&path).map_err(|err| Error::FileIo { 447 + std::fs::read_to_string(path.as_ref()).map_err(|err| Error::FileIo { 447 448 action: FileIoAction::Read, 448 449 kind: FileKind::File, 449 - path: PathBuf::from(path.as_ref()), 450 + path: Utf8PathBuf::from(path.as_ref()), 450 451 err: Some(err.to_string()), 451 452 }) 452 453 } 453 454 454 - pub fn read_bytes(path: impl AsRef<Path> + Debug) -> Result<Vec<u8>, Error> { 455 + pub fn read_bytes(path: impl AsRef<Utf8Path> + Debug) -> Result<Vec<u8>, Error> { 455 456 tracing::trace!(path=?path,"reading_file"); 456 457 457 - std::fs::read(&path).map_err(|err| Error::FileIo { 458 + std::fs::read(path.as_ref()).map_err(|err| Error::FileIo { 458 459 action: FileIoAction::Read, 459 460 kind: FileKind::File, 460 - path: PathBuf::from(path.as_ref()), 461 + path: Utf8PathBuf::from(path.as_ref()), 461 462 err: Some(err.to_string()), 462 463 }) 463 464 } 464 465 465 - pub fn reader(path: impl AsRef<Path> + Debug) -> Result<WrappedReader, Error> { 466 + pub fn reader(path: impl AsRef<Utf8Path> + Debug) -> Result<WrappedReader, Error> { 466 467 tracing::trace!(path=?path,"opening_file_reader"); 467 468 468 - let reader = File::open(&path).map_err(|err| Error::FileIo { 469 + let reader = File::open(path.as_ref()).map_err(|err| Error::FileIo { 469 470 action: FileIoAction::Open, 470 471 kind: FileKind::File, 471 - path: PathBuf::from(path.as_ref()), 472 + path: Utf8PathBuf::from(path.as_ref()), 472 473 err: Some(err.to_string()), 473 474 })?; 474 475 475 476 Ok(WrappedReader::new(path.as_ref(), Box::new(reader))) 476 477 } 477 478 478 - pub fn buffered_reader<P: AsRef<Path> + Debug>(path: P) -> Result<impl BufRead, Error> { 479 + pub fn buffered_reader<P: AsRef<Utf8Path> + Debug>(path: P) -> Result<impl BufRead, Error> { 479 480 tracing::trace!(path=?path,"opening_file_buffered_reader"); 480 - let reader = File::open(&path).map_err(|err| Error::FileIo { 481 + let reader = File::open(path.as_ref()).map_err(|err| Error::FileIo { 481 482 action: FileIoAction::Open, 482 483 kind: FileKind::File, 483 - path: PathBuf::from(path.as_ref()), 484 + path: Utf8PathBuf::from(path.as_ref()), 484 485 err: Some(err.to_string()), 485 486 })?; 486 487 Ok(BufReader::new(reader)) 487 488 } 488 489 489 - pub fn copy(path: impl AsRef<Path> + Debug, to: impl AsRef<Path> + Debug) -> Result<(), Error> { 490 + pub fn copy( 491 + path: impl AsRef<Utf8Path> + Debug, 492 + to: impl AsRef<Utf8Path> + Debug, 493 + ) -> Result<(), Error> { 490 494 tracing::trace!(from=?path, to=?to, "copying_file"); 491 495 492 496 // TODO: include the destination in the error message 493 - std::fs::copy(&path, &to) 497 + std::fs::copy(path.as_ref(), to.as_ref()) 494 498 .map_err(|err| Error::FileIo { 495 499 action: FileIoAction::Copy, 496 500 kind: FileKind::File, 497 - path: PathBuf::from(path.as_ref()), 501 + path: Utf8PathBuf::from(path.as_ref()), 498 502 err: Some(err.to_string()), 499 503 }) 500 504 .map(|_| ()) 501 505 } 502 506 503 - // pub fn rename(path: impl AsRef<Path> + Debug, to: impl AsRef<Path> + Debug) -> Result<(), Error> { 507 + // pub fn rename(path: impl AsRef<Utf8Path> + Debug, to: impl AsRef<Utf8Path> + Debug) -> Result<(), Error> { 504 508 // tracing::trace!(from=?path, to=?to, "renaming_file"); 505 509 506 510 // // TODO: include the destination in the error message ··· 508 512 // .map_err(|err| Error::FileIo { 509 513 // action: FileIoAction::Rename, 510 514 // kind: FileKind::File, 511 - // path: PathBuf::from(path.as_ref()), 515 + // path: Utf8PathBuf::from(path.as_ref()), 512 516 // err: Some(err.to_string()), 513 517 // }) 514 518 // .map(|_| ()) 515 519 // } 516 520 517 - pub fn copy_dir(path: impl AsRef<Path> + Debug, to: impl AsRef<Path> + Debug) -> Result<(), Error> { 521 + pub fn copy_dir( 522 + path: impl AsRef<Utf8Path> + Debug, 523 + to: impl AsRef<Utf8Path> + Debug, 524 + ) -> Result<(), Error> { 518 525 tracing::trace!(from=?path, to=?to, "copying_directory"); 519 526 520 527 // TODO: include the destination in the error message 521 - fs_extra::dir::copy(&path, &to, &fs_extra::dir::CopyOptions::new()) 522 - .map_err(|err| Error::FileIo { 523 - action: FileIoAction::Copy, 524 - kind: FileKind::Directory, 525 - path: PathBuf::from(path.as_ref()), 526 - err: Some(err.to_string()), 527 - }) 528 - .map(|_| ()) 528 + fs_extra::dir::copy( 529 + path.as_ref(), 530 + to.as_ref(), 531 + &fs_extra::dir::CopyOptions::new(), 532 + ) 533 + .map_err(|err| Error::FileIo { 534 + action: FileIoAction::Copy, 535 + kind: FileKind::Directory, 536 + path: Utf8PathBuf::from(path.as_ref()), 537 + err: Some(err.to_string()), 538 + }) 539 + .map(|_| ()) 529 540 } 530 541 531 542 pub fn symlink_dir( 532 - src: impl AsRef<Path> + Debug, 533 - dest: impl AsRef<Path> + Debug, 543 + src: impl AsRef<Utf8Path> + Debug, 544 + dest: impl AsRef<Utf8Path> + Debug, 534 545 ) -> Result<(), Error> { 535 546 tracing::trace!(src=?src, dest=?dest, "symlinking"); 536 547 symlink::symlink_dir(canonicalise(src.as_ref())?, dest.as_ref()).map_err(|err| { 537 548 Error::FileIo { 538 549 action: FileIoAction::Link, 539 550 kind: FileKind::File, 540 - path: PathBuf::from(dest.as_ref()), 551 + path: Utf8PathBuf::from(dest.as_ref()), 541 552 err: Some(err.to_string()), 542 553 } 543 554 })?; 544 555 Ok(()) 545 556 } 546 557 547 - pub fn hardlink(from: impl AsRef<Path> + Debug, to: impl AsRef<Path> + Debug) -> Result<(), Error> { 558 + pub fn hardlink( 559 + from: impl AsRef<Utf8Path> + Debug, 560 + to: impl AsRef<Utf8Path> + Debug, 561 + ) -> Result<(), Error> { 548 562 tracing::trace!(from=?from, to=?to, "hardlinking"); 549 - std::fs::hard_link(&from, &to) 563 + std::fs::hard_link(from.as_ref(), to.as_ref()) 550 564 .map_err(|err| Error::FileIo { 551 565 action: FileIoAction::Link, 552 566 kind: FileKind::File, 553 - path: PathBuf::from(from.as_ref()), 567 + path: Utf8PathBuf::from(from.as_ref()), 554 568 err: Some(err.to_string()), 555 569 }) 556 570 .map(|_| ()) ··· 561 575 /// given path. If git is not installed then we assume we're not in a git work 562 576 /// tree. 563 577 /// 564 - pub fn is_inside_git_work_tree(path: &Path) -> Result<bool, Error> { 578 + pub fn is_inside_git_work_tree(path: &Utf8Path) -> Result<bool, Error> { 565 579 tracing::trace!(path=?path, "checking_for_git_repo"); 566 580 567 581 let args: Vec<&str> = vec!["rev-parse", "--is-inside-work-tree", "--quiet"]; ··· 592 606 593 607 /// Run `git init` in the given path. 594 608 /// If git is not installed then we do nothing. 595 - pub fn git_init(path: &Path) -> Result<(), Error> { 609 + pub fn git_init(path: &Utf8Path) -> Result<(), Error> { 596 610 tracing::trace!(path=?path, "initializing git"); 597 611 598 612 if is_inside_git_work_tree(path)? { ··· 600 614 return Ok(()); 601 615 } 602 616 603 - let args = vec!["init".into(), "--quiet".into(), path.display().to_string()]; 617 + let args = vec!["init".into(), "--quiet".into(), path.to_string()]; 604 618 605 619 match ProjectIO::new().exec("git", &args, &[], None, Stdio::Inherit) { 606 620 Ok(_) => Ok(()), ··· 613 627 } 614 628 } 615 629 616 - pub fn canonicalise(path: &Path) -> Result<PathBuf, Error> { 617 - std::fs::canonicalize(path).map_err(|err| Error::FileIo { 618 - action: FileIoAction::Canonicalise, 619 - kind: FileKind::File, 620 - path: PathBuf::from(path), 621 - err: Some(err.to_string()), 622 - }) 630 + pub fn canonicalise(path: &Utf8Path) -> Result<Utf8PathBuf, Error> { 631 + std::fs::canonicalize(path) 632 + .map_err(|err| Error::FileIo { 633 + action: FileIoAction::Canonicalise, 634 + kind: FileKind::File, 635 + path: Utf8PathBuf::from(path), 636 + err: Some(err.to_string()), 637 + }) 638 + .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf8 Path")) 623 639 } 624 640 625 641 #[derive(Debug, Clone, Copy)]
+21 -14
compiler-cli/src/fs/tests.rs
··· 1 - use std::path::Path; 1 + use camino::Utf8Path; 2 2 3 3 #[test] 4 4 fn is_inside_git_work_tree_ok() { 5 5 let tmp_dir = tempfile::tempdir().unwrap(); 6 - let path = tmp_dir.path(); 6 + let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); 7 7 8 8 assert!(!super::is_inside_git_work_tree(path).unwrap()); 9 9 assert_eq!(super::git_init(path), Ok(())); ··· 13 13 #[test] 14 14 fn git_init_success() { 15 15 let tmp_dir = tempfile::tempdir().unwrap(); 16 - let path = tmp_dir.path(); 16 + let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); 17 17 let git = path.join(".git"); 18 18 19 19 assert!(!git.exists()); ··· 24 24 #[test] 25 25 fn git_init_already_in_git() { 26 26 let tmp_dir = tempfile::tempdir().unwrap(); 27 - let git = tmp_dir.path().join(".git"); 27 + let git = Utf8Path::from_path(tmp_dir.path()) 28 + .expect("Non Utf-8 Path") 29 + .join(".git"); 28 30 assert!(!git.exists()); 29 - assert_eq!(super::git_init(tmp_dir.path()), Ok(())); 31 + assert_eq!( 32 + super::git_init(Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path")), 33 + Ok(()) 34 + ); 30 35 assert!(git.exists()); 31 36 32 - let sub = tmp_dir.path().join("subproject"); 37 + let sub = Utf8Path::from_path(tmp_dir.path()) 38 + .expect("Non Utf-8 Path") 39 + .join("subproject"); 33 40 let git = sub.join(".git"); 34 41 crate::fs::mkdir(&sub).unwrap(); 35 42 assert!(!git.exists()); ··· 40 47 #[test] 41 48 fn is_gleam_path_test() { 42 49 assert!(super::is_gleam_path( 43 - Path::new("/some-prefix/a.gleam"), 44 - Path::new("/some-prefix/") 50 + Utf8Path::new("/some-prefix/a.gleam"), 51 + Utf8Path::new("/some-prefix/") 45 52 )); 46 53 47 54 assert!(super::is_gleam_path( 48 - Path::new("/some-prefix/one_two/a.gleam"), 49 - Path::new("/some-prefix/") 55 + Utf8Path::new("/some-prefix/one_two/a.gleam"), 56 + Utf8Path::new("/some-prefix/") 50 57 )); 51 58 52 59 assert!(super::is_gleam_path( 53 - Path::new("/some-prefix/one_two/a123.gleam"), 54 - Path::new("/some-prefix/") 60 + Utf8Path::new("/some-prefix/one_two/a123.gleam"), 61 + Utf8Path::new("/some-prefix/") 55 62 )); 56 63 57 64 assert!(super::is_gleam_path( 58 - Path::new("/some-prefix/one_2/a123.gleam"), 59 - Path::new("/some-prefix/") 65 + Utf8Path::new("/some-prefix/one_2/a123.gleam"), 66 + Utf8Path::new("/some-prefix/") 60 67 )); 61 68 }
+6 -5
compiler-cli/src/main.rs
··· 74 74 75 75 use config::root_config; 76 76 use dependencies::UseManifest; 77 + use fs::get_current_directory; 77 78 pub use gleam_core::{ 78 79 error::{Error, Result}, 79 80 warning::Warning, ··· 87 88 }; 88 89 use hex::ApiKeyCommand as _; 89 90 90 - use std::path::PathBuf; 91 + use camino::Utf8PathBuf; 91 92 92 93 use clap::{Args, Parser, Subcommand}; 93 94 use strum::VariantNames; ··· 283 284 284 285 /// The directory of the Gleam package 285 286 #[clap(long = "package")] 286 - package_directory: PathBuf, 287 + package_directory: Utf8PathBuf, 287 288 288 289 /// A directory to write compiled package to 289 290 #[clap(long = "out")] 290 - output_directory: PathBuf, 291 + output_directory: Utf8PathBuf, 291 292 292 293 /// A directories of precompiled Gleam projects 293 294 #[clap(long = "lib")] 294 - libraries_directory: PathBuf, 295 + libraries_directory: Utf8PathBuf, 295 296 296 297 /// Skip Erlang to BEAM bytecode compilation if given 297 298 #[clap(long = "no-beam")] ··· 520 521 } 521 522 522 523 fn project_paths_at_current_directory() -> ProjectPaths { 523 - let current_dir = std::env::current_dir().expect("Could not get current directory"); 524 + let current_dir = get_current_directory().expect("Failed to get current directory"); 524 525 ProjectPaths::new(current_dir) 525 526 } 526 527
+10 -11
compiler-cli/src/new.rs
··· 1 + use camino::{Utf8Path, Utf8PathBuf}; 1 2 use gleam_core::{ 2 3 erlang, 3 4 error::{Error, FileIoAction, FileKind, InvalidProjectNameReason}, ··· 5 6 }; 6 7 use serde::{Deserialize, Serialize}; 7 8 use std::fs::File; 8 - use std::path::{Path, PathBuf}; 9 9 use std::{env, io::Write}; 10 10 use strum::{Display, EnumString, EnumVariantNames}; 11 11 ··· 28 28 29 29 #[derive(Debug)] 30 30 pub struct Creator { 31 - root: PathBuf, 32 - src: PathBuf, 33 - test: PathBuf, 34 - github: PathBuf, 35 - workflows: PathBuf, 31 + root: Utf8PathBuf, 32 + src: Utf8PathBuf, 33 + test: Utf8PathBuf, 34 + github: Utf8PathBuf, 35 + workflows: Utf8PathBuf, 36 36 gleam_version: &'static str, 37 37 options: NewOptions, 38 38 project_name: String, ··· 51 51 validate_name(&project_name)?; 52 52 validate_root_folder(&options.project_root)?; 53 53 54 - let root = PathBuf::from(&options.project_root); 54 + let root = Utf8PathBuf::from(&options.project_root); 55 55 let src = root.join("src"); 56 56 let test = root.join("test"); 57 57 let github = root.join(".github"); ··· 266 266 Ok(()) 267 267 } 268 268 269 - fn write(path: PathBuf, contents: &str) -> Result<()> { 269 + fn write(path: Utf8PathBuf, contents: &str) -> Result<()> { 270 270 let mut f = File::create(&path).map_err(|err| Error::FileIo { 271 271 kind: FileKind::File, 272 272 path: path.clone(), ··· 285 285 } 286 286 287 287 fn validate_root_folder(name: &str) -> Result<(), Error> { 288 - if Path::new(name).exists() { 288 + if Utf8Path::new(name).exists() { 289 289 Err(Error::ProjectRootAlreadyExist { 290 290 path: name.to_string(), 291 291 }) ··· 343 343 .ok_or(Error::UnableToFindProjectRoot { 344 344 path: path.to_string(), 345 345 }), 346 - _ => Path::new(path) 346 + _ => Utf8Path::new(path) 347 347 .file_name() 348 - .and_then(|x| x.to_str()) 349 348 .map(ToString::to_string) 350 349 .ok_or(Error::UnableToFindProjectRoot { 351 350 path: path.to_string(),
+14 -12
compiler-cli/src/new/tests.rs
··· 1 + use camino::Utf8PathBuf; 2 + 1 3 #[test] 2 4 fn new() { 3 5 let tmp = tempfile::tempdir().unwrap(); 4 - let path = tmp.path().join("my_project"); 6 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); 5 7 6 8 let creator = super::Creator::new( 7 9 super::NewOptions { 8 - project_root: path.to_str().unwrap().to_string(), 10 + project_root: path.to_string(), 9 11 template: super::Template::Lib, 10 12 name: None, 11 13 description: "Wibble wobble".into(), ··· 33 35 #[test] 34 36 fn new_with_skip_git() { 35 37 let tmp = tempfile::tempdir().unwrap(); 36 - let path = tmp.path().join("my_project"); 38 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); 37 39 38 40 let creator = super::Creator::new( 39 41 super::NewOptions { 40 - project_root: path.to_str().unwrap().to_string(), 42 + project_root: path.to_string(), 41 43 template: super::Template::Lib, 42 44 name: None, 43 45 description: "Wibble wobble".into(), ··· 55 57 #[test] 56 58 fn new_with_skip_github() { 57 59 let tmp = tempfile::tempdir().unwrap(); 58 - let path = tmp.path().join("my_project"); 60 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); 59 61 60 62 let creator = super::Creator::new( 61 63 super::NewOptions { 62 - project_root: path.to_str().unwrap().to_string(), 64 + project_root: path.to_string(), 63 65 template: super::Template::Lib, 64 66 name: None, 65 67 description: "Wibble wobble".into(), ··· 80 82 #[test] 81 83 fn new_with_skip_git_and_github() { 82 84 let tmp = tempfile::tempdir().unwrap(); 83 - let path = tmp.path().join("my_project"); 85 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); 84 86 85 87 let creator = super::Creator::new( 86 88 super::NewOptions { 87 - project_root: path.to_str().unwrap().to_string(), 89 + project_root: path.to_string(), 88 90 template: super::Template::Lib, 89 91 name: None, 90 92 description: "Wibble wobble".into(), ··· 105 107 #[test] 106 108 fn invalid_path() { 107 109 let tmp = tempfile::tempdir().unwrap(); 108 - let path = tmp.path().join("-------"); 110 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("-------")).expect("Non Utf8 Path"); 109 111 110 112 assert!(super::Creator::new( 111 113 super::NewOptions { 112 - project_root: path.to_str().unwrap().to_string(), 114 + project_root: path.to_string(), 113 115 template: super::Template::Lib, 114 116 name: None, 115 117 description: "Wibble wobble".into(), ··· 124 126 #[test] 125 127 fn invalid_name() { 126 128 let tmp = tempfile::tempdir().unwrap(); 127 - let path = tmp.path().join("projec"); 129 + let path = Utf8PathBuf::from_path_buf(tmp.path().join("projec")).expect("Non Utf8 Path"); 128 130 129 131 assert!(super::Creator::new( 130 132 super::NewOptions { 131 - project_root: path.to_str().unwrap().to_string(), 133 + project_root: path.to_string(), 132 134 template: super::Template::Lib, 133 135 name: Some("-".into()), 134 136 description: "Wibble wobble".into(),
+34 -33
compiler-cli/src/publish.rs
··· 1 + use camino::{Utf8Path, Utf8PathBuf}; 1 2 use flate2::{write::GzEncoder, Compression}; 2 3 use gleam_core::{ 3 4 build::{Codegen, Mode, Options, Package, Target}, ··· 10 11 use hexpm::version::{Range, Version}; 11 12 use itertools::Itertools; 12 13 use sha2::Digest; 13 - use std::{ 14 - io::Write, 15 - path::{Path, PathBuf}, 16 - time::Instant, 17 - }; 14 + use std::{io::Write, time::Instant}; 18 15 19 16 use crate::{build, cli, docs, fs, hex::ApiKeyCommand, http::HttpClient}; 20 17 ··· 48 45 if !generated_files_added.is_empty() { 49 46 println!("\nGenerated files:"); 50 47 for file in generated_files_added.iter().sorted() { 51 - println!(" - {}", file.0.to_string_lossy()); 48 + println!(" - {}", file.0); 52 49 } 53 50 } 54 51 println!("\nSource files:"); 55 52 for file in src_files_added.iter().sorted() { 56 - println!(" - {}", file.to_string_lossy()); 53 + println!(" - {}", file); 57 54 } 58 55 println!("\nName: {}", config.name); 59 56 println!("Version: {}", config.version); ··· 115 112 struct Tarball { 116 113 compile_result: Package, 117 114 data: Vec<u8>, 118 - src_files_added: Vec<PathBuf>, 119 - generated_files_added: Vec<(PathBuf, String)>, 115 + src_files_added: Vec<Utf8PathBuf>, 116 + generated_files_added: Vec<(Utf8PathBuf, String)>, 120 117 } 121 118 122 119 pub fn build_hex_tarball(paths: &ProjectPaths, config: &PackageConfig) -> Result<Vec<u8>> { ··· 189 186 190 187 fn metadata_config<'a>( 191 188 config: &'a PackageConfig, 192 - source_files: &[PathBuf], 193 - generated_files: &[(PathBuf, String)], 189 + source_files: &[Utf8PathBuf], 190 + generated_files: &[(Utf8PathBuf, String)], 194 191 ) -> Result<String> { 195 192 let repo_url = http::Uri::try_from(config.repository.url().unwrap_or_default()).ok(); 196 193 let requirements: Result<Vec<ReleaseRequirement<'a>>> = config ··· 227 224 Ok(metadata) 228 225 } 229 226 230 - fn contents_tarball(files: &[PathBuf], data_files: &[(PathBuf, String)]) -> Result<Vec<u8>, Error> { 227 + fn contents_tarball( 228 + files: &[Utf8PathBuf], 229 + data_files: &[(Utf8PathBuf, String)], 230 + ) -> Result<Vec<u8>, Error> { 231 231 let mut contents_tar_gz = Vec::new(); 232 232 { 233 233 let mut tarball = ··· 246 246 247 247 // TODO: test 248 248 // TODO: Don't include git-ignored native files 249 - fn project_files() -> Result<Vec<PathBuf>> { 250 - let src = Path::new("src"); 251 - let mut files: Vec<PathBuf> = fs::gleam_files_excluding_gitignore(src) 249 + fn project_files() -> Result<Vec<Utf8PathBuf>> { 250 + let src = Utf8Path::new("src"); 251 + let mut files: Vec<Utf8PathBuf> = fs::gleam_files_excluding_gitignore(src) 252 252 .chain(fs::native_files(src)?) 253 253 .collect(); 254 - let private = Path::new("priv"); 255 - let mut private_files: Vec<PathBuf> = fs::private_files_excluding_gitignore(private).collect(); 254 + let private = Utf8Path::new("priv"); 255 + let mut private_files: Vec<Utf8PathBuf> = 256 + fs::private_files_excluding_gitignore(private).collect(); 256 257 files.append(&mut private_files); 257 258 let mut add = |path| { 258 - let path = PathBuf::from(path); 259 + let path = Utf8PathBuf::from(path); 259 260 if path.exists() { 260 261 files.push(path); 261 262 } ··· 277 278 } 278 279 279 280 // TODO: test 280 - fn generated_files(paths: &ProjectPaths, package: &Package) -> Result<Vec<(PathBuf, String)>> { 281 + fn generated_files(paths: &ProjectPaths, package: &Package) -> Result<Vec<(Utf8PathBuf, String)>> { 281 282 let mut files = vec![]; 282 283 283 284 let dir = paths.build_directory_for_package(Mode::Prod, Target::Erlang, &package.config.name); ··· 285 286 let build = dir.join(paths::ARTEFACT_DIRECTORY_NAME); 286 287 let include = dir.join("include"); 287 288 288 - let tar_src = Path::new("src"); 289 - let tar_include = Path::new("include"); 289 + let tar_src = Utf8Path::new("src"); 290 + let tar_include = Utf8Path::new("include"); 290 291 291 292 // Erlang modules 292 293 for module in &package.modules { ··· 315 316 316 317 fn add_to_tar<P, W>(tarball: &mut tar::Builder<W>, path: P, data: &[u8]) -> Result<()> 317 318 where 318 - P: AsRef<Path>, 319 + P: AsRef<Utf8Path>, 319 320 W: Write, 320 321 { 321 322 let path = path.as_ref(); ··· 331 332 332 333 fn add_path_to_tar<P, W>(tarball: &mut tar::Builder<W>, path: P) -> Result<()> 333 334 where 334 - P: AsRef<Path>, 335 + P: AsRef<Utf8Path>, 335 336 W: Write, 336 337 { 337 338 let path = path.as_ref(); ··· 346 347 name: &'a str, 347 348 version: &'a Version, 348 349 description: &'a str, 349 - source_files: &'a [PathBuf], 350 - generated_files: &'a [(PathBuf, String)], 350 + source_files: &'a [Utf8PathBuf], 351 + generated_files: &'a [(Utf8PathBuf, String)], 351 352 licenses: &'a Vec<SpdxLicense>, 352 353 links: Vec<(&'a str, http::Uri)>, 353 354 requirements: Vec<ReleaseRequirement<'a>>, ··· 365 366 url = link.1 366 367 ) 367 368 } 368 - fn file(name: impl AsRef<Path>) -> String { 369 - format!("\n <<\"{name}\">>", name = name.as_ref().to_string_lossy()) 369 + fn file(name: impl AsRef<Utf8Path>) -> String { 370 + format!("\n <<\"{name}\">>", name = name.as_ref()) 370 371 } 371 372 372 373 format!( ··· 448 449 version: &version, 449 450 description: "description goes here", 450 451 source_files: &[ 451 - PathBuf::from("gleam.toml"), 452 - PathBuf::from("src/thingy.gleam"), 453 - PathBuf::from("src/whatever.gleam"), 452 + Utf8PathBuf::from("gleam.toml"), 453 + Utf8PathBuf::from("src/thingy.gleam"), 454 + Utf8PathBuf::from("src/whatever.gleam"), 454 455 ], 455 456 generated_files: &[ 456 - (PathBuf::from("src/myapp.app"), "".into()), 457 - (PathBuf::from("src/thingy.erl"), "".into()), 458 - (PathBuf::from("src/whatever.erl"), "".into()), 457 + (Utf8PathBuf::from("src/myapp.app"), "".into()), 458 + (Utf8PathBuf::from("src/thingy.erl"), "".into()), 459 + (Utf8PathBuf::from("src/whatever.erl"), "".into()), 459 460 ], 460 461 licenses: &licences, 461 462 links: vec![("homepage", homepage), ("github", github)],
+3 -3
compiler-cli/src/remove.rs
··· 1 - use std::path::{Path, PathBuf}; 1 + use camino::{Utf8Path, Utf8PathBuf}; 2 2 3 3 use gleam_core::{ 4 4 error::{FileIoAction, FileKind}, ··· 14 14 .map_err(|e| Error::FileIo { 15 15 kind: FileKind::File, 16 16 action: FileIoAction::Parse, 17 - path: PathBuf::from("gleam.toml"), 17 + path: Utf8PathBuf::from("gleam.toml"), 18 18 err: Some(e.to_string()), 19 19 })?; 20 20 ··· 31 31 } 32 32 33 33 // Write the updated config 34 - fs::write(Path::new("gleam.toml"), &toml.to_string())?; 34 + fs::write(Utf8Path::new("gleam.toml"), &toml.to_string())?; 35 35 let paths = crate::project_paths_at_current_directory(); 36 36 _ = crate::dependencies::download(&paths, cli::Reporter::new(), None, UseManifest::Yes)?; 37 37 for package_to_remove in packages {
+4 -4
compiler-cli/src/run.rs
··· 1 + use camino::Utf8PathBuf; 1 2 use gleam_core::{ 2 3 build::{Codegen, Mode, Options, Runtime, Target}, 3 4 config::{DenoFlag, PackageConfig}, ··· 7 8 type_::ModuleFunction, 8 9 }; 9 10 use lazy_static::lazy_static; 10 - use std::path::PathBuf; 11 11 12 12 use crate::fs::ProjectIO; 13 13 ··· 116 116 117 117 for entry in crate::fs::read_dir(packages)?.filter_map(Result::ok) { 118 118 args.push("-pa".into()); 119 - args.push(entry.path().join("ebin").to_string_lossy().into()); 119 + args.push(entry.path().join("ebin").into()); 120 120 } 121 121 122 122 // gleam modules are seperated by `/`. Erlang modules are seperated by `@`. ··· 165 165 .strip_prefix(paths.root()) 166 166 .expect("Failed to strip prefix from path") 167 167 .to_path_buf(); 168 - let entrypoint = format!("./{}/gleam.main.mjs", entry.to_string_lossy()); 168 + let entrypoint = format!("./{}/gleam.main.mjs", entry); 169 169 let module = format!( 170 170 r#"import {{ main }} from "./{module}.mjs"; 171 171 main(); 172 172 "#, 173 173 ); 174 - crate::fs::write(&PathBuf::from(&entrypoint), &module)?; 174 + crate::fs::write(&Utf8PathBuf::from(&entrypoint), &module)?; 175 175 Ok(entrypoint) 176 176 } 177 177
+1
compiler-core/Cargo.toml
··· 79 79 lsp-types = "0.92" 80 80 # Pubgrub dependency resolution algorithm 81 81 pubgrub = "0.2" 82 + camino = { version = "1.1.6", features = ["serde1"] } 82 83 83 84 [build-dependencies] 84 85 # Data (de)serialisation
+14
compiler-core/clippy.toml
··· 9 9 { path = "std::path::Path::read_link", reason = "IO is not permitted in core" }, 10 10 { path = "std::path::Path::symlink_metadata", reason = "IO is not permitted in core" }, 11 11 { path = "std::path::Path::try_exists", reason = "IO is not permitted in core" }, 12 + 13 + { path = "camino::Utf8Path::canonicalize", reason = "IO is not permitted in core" }, 14 + { path = "camino::Utf8Path::exists", reason = "IO is not permitted in core" }, 15 + { path = "camino::Utf8Path::is_dir", reason = "IO is not permitted in core" }, 16 + { path = "camino::Utf8Path::is_file", reason = "IO is not permitted in core" }, 17 + { path = "camino::Utf8Path::is_symlink", reason = "IO is not permitted in core" }, 18 + { path = "camino::Utf8Path::read_dir", reason = "IO is not permitted in core" }, 19 + { path = "camino::Utf8Path::read_link", reason = "IO is not permitted in core" }, 20 + { path = "camino::Utf8Path::symlink_metadata", reason = "IO is not permitted in core" }, 21 + { path = "camino::Utf8Path::try_exists", reason = "IO is not permitted in core" }, 22 + 23 + 24 + { path = "std::path::Path::new", reason = "Manually constructed paths should use camino::Utf8Path" }, 25 + { path = "std::path::PathBuf::new", reason = "Manually constructed pathbufs should use camino::Utf8Path" }, 12 26 ]
+5 -6
compiler-core/src/build.rs
··· 27 27 parse::extra::{Comment, ModuleExtra}, 28 28 type_, 29 29 }; 30 + use camino::Utf8PathBuf; 30 31 use itertools::Itertools; 31 32 use serde::{Deserialize, Serialize}; 32 33 use smol_str::SmolStr; 33 34 use std::time::SystemTime; 34 - use std::{ 35 - collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, path::PathBuf, process, 36 - }; 35 + use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process}; 37 36 use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames}; 38 37 39 38 #[derive( ··· 185 184 pub name: SmolStr, 186 185 pub code: SmolStr, 187 186 pub mtime: SystemTime, 188 - pub input_path: PathBuf, 187 + pub input_path: Utf8PathBuf, 189 188 pub origin: Origin, 190 189 pub ast: TypedModule, 191 190 pub extra: ModuleExtra, ··· 193 192 } 194 193 195 194 impl Module { 196 - pub fn compiled_erlang_path(&self) -> PathBuf { 195 + pub fn compiled_erlang_path(&self) -> Utf8PathBuf { 197 196 let mut path = self.name.replace("/", "@"); 198 197 path.push_str(".erl"); 199 - PathBuf::from(path) 198 + Utf8PathBuf::from(path) 200 199 } 201 200 202 201 pub fn is_test(&self) -> bool {
+8 -10
compiler-core/src/build/module_loader.rs
··· 1 1 #[cfg(test)] 2 2 mod tests; 3 3 4 - use std::{ 5 - collections::HashMap, 6 - path::{Path, PathBuf}, 7 - time::SystemTime, 8 - }; 4 + use std::{collections::HashMap, time::SystemTime}; 5 + 6 + use camino::{Utf8Path, Utf8PathBuf}; 9 7 10 8 use serde::{Deserialize, Serialize}; 11 9 use smol_str::SmolStr; ··· 39 37 pub target: Target, 40 38 pub codegen: CodegenRequired, 41 39 pub package_name: &'a SmolStr, 42 - pub source_directory: &'a Path, 43 - pub artefact_directory: &'a Path, 40 + pub source_directory: &'a Utf8Path, 41 + pub artefact_directory: &'a Utf8Path, 44 42 pub origin: Origin, 45 43 } 46 44 ··· 56 54 /// Whether the module has changed or not is determined by comparing the 57 55 /// modification time of the source file with the value recorded in the 58 56 /// `.timestamp` file in the artefact directory. 59 - pub fn load(&self, path: PathBuf) -> Result<Input> { 57 + pub fn load(&self, path: Utf8PathBuf) -> Result<Input> { 60 58 let name = module_name(self.source_directory, &path); 61 59 let artefact = name.replace("/", "@"); 62 60 let source_mtime = self.io.modification_time(&path)?; ··· 116 114 117 115 fn read_source( 118 116 &self, 119 - path: PathBuf, 117 + path: Utf8PathBuf, 120 118 name: SmolStr, 121 119 mtime: SystemTime, 122 120 ) -> Result<UncompiledModule, Error> { ··· 147 145 warnings: &WarningEmitter, 148 146 target: Target, 149 147 origin: Origin, 150 - path: PathBuf, 148 + path: Utf8PathBuf, 151 149 name: SmolStr, 152 150 package_name: SmolStr, 153 151 mtime: SystemTime,
+26 -26
compiler-core/src/build/module_loader/tests.rs
··· 8 8 #[test] 9 9 fn no_cache_present() { 10 10 let name = "package".into(); 11 - let src = Path::new("/src"); 12 - let artefact = Path::new("/artefact"); 11 + let src = Utf8Path::new("/src"); 12 + let artefact = Utf8Path::new("/artefact"); 13 13 let fs = InMemoryFileSystem::new(); 14 14 let warnings = WarningEmitter::null(); 15 15 let loader = make_loader(&warnings, &name, &fs, src, artefact); 16 16 17 - fs.write(&Path::new("/src/main.gleam"), "const x = 1") 17 + fs.write(&Utf8Path::new("/src/main.gleam"), "const x = 1") 18 18 .unwrap(); 19 19 20 20 let result = loader 21 - .load(Path::new("/src/main.gleam").to_path_buf()) 21 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 22 22 .unwrap(); 23 23 24 24 assert!(result.is_new()); ··· 27 27 #[test] 28 28 fn cache_present_and_fresh() { 29 29 let name = "package".into(); 30 - let src = Path::new("/src"); 31 - let artefact = Path::new("/artefact"); 30 + let src = Utf8Path::new("/src"); 31 + let artefact = Utf8Path::new("/artefact"); 32 32 let fs = InMemoryFileSystem::new(); 33 33 let warnings = WarningEmitter::null(); 34 34 let loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 38 38 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); 39 39 40 40 let result = loader 41 - .load(Path::new("/src/main.gleam").to_path_buf()) 41 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 42 42 .unwrap(); 43 43 44 44 assert!(result.is_cached()); ··· 47 47 #[test] 48 48 fn cache_present_and_stale() { 49 49 let name = "package".into(); 50 - let src = Path::new("/src"); 51 - let artefact = Path::new("/artefact"); 50 + let src = Utf8Path::new("/src"); 51 + let artefact = Utf8Path::new("/artefact"); 52 52 let fs = InMemoryFileSystem::new(); 53 53 let warnings = WarningEmitter::null(); 54 54 let loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 58 58 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); 59 59 60 60 let result = loader 61 - .load(Path::new("/src/main.gleam").to_path_buf()) 61 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 62 62 .unwrap(); 63 63 64 64 assert!(result.is_new()); ··· 67 67 #[test] 68 68 fn cache_present_and_stale_but_source_is_the_same() { 69 69 let name = "package".into(); 70 - let src = Path::new("/src"); 71 - let artefact = Path::new("/artefact"); 70 + let src = Utf8Path::new("/src"); 71 + let artefact = Utf8Path::new("/artefact"); 72 72 let fs = InMemoryFileSystem::new(); 73 73 let warnings = WarningEmitter::null(); 74 74 let loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 78 78 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); 79 79 80 80 let result = loader 81 - .load(Path::new("/src/main.gleam").to_path_buf()) 81 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 82 82 .unwrap(); 83 83 84 84 assert!(result.is_cached()); ··· 87 87 #[test] 88 88 fn cache_present_without_codegen_when_required() { 89 89 let name = "package".into(); 90 - let src = Path::new("/src"); 91 - let artefact = Path::new("/artefact"); 90 + let src = Utf8Path::new("/src"); 91 + let artefact = Utf8Path::new("/artefact"); 92 92 let fs = InMemoryFileSystem::new(); 93 93 let warnings = WarningEmitter::null(); 94 94 let mut loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 99 99 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); 100 100 101 101 let result = loader 102 - .load(Path::new("/src/main.gleam").to_path_buf()) 102 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 103 103 .unwrap(); 104 104 105 105 assert!(result.is_new()); ··· 108 108 #[test] 109 109 fn cache_present_with_codegen_when_required() { 110 110 let name = "package".into(); 111 - let src = Path::new("/src"); 112 - let artefact = Path::new("/artefact"); 111 + let src = Utf8Path::new("/src"); 112 + let artefact = Utf8Path::new("/artefact"); 113 113 let fs = InMemoryFileSystem::new(); 114 114 let warnings = WarningEmitter::null(); 115 115 let mut loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 120 120 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, true); 121 121 122 122 let result = loader 123 - .load(Path::new("/src/main.gleam").to_path_buf()) 123 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 124 124 .unwrap(); 125 125 126 126 assert!(result.is_cached()); ··· 129 129 #[test] 130 130 fn cache_present_without_codegen_when_not_required() { 131 131 let name = "package".into(); 132 - let src = Path::new("/src"); 133 - let artefact = Path::new("/artefact"); 132 + let src = Utf8Path::new("/src"); 133 + let artefact = Utf8Path::new("/artefact"); 134 134 let fs = InMemoryFileSystem::new(); 135 135 let warnings = WarningEmitter::null(); 136 136 let mut loader = make_loader(&warnings, &name, &fs, src, artefact); ··· 141 141 write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); 142 142 143 143 let result = loader 144 - .load(Path::new("/src/main.gleam").to_path_buf()) 144 + .load(Utf8Path::new("/src/main.gleam").to_path_buf()) 145 145 .unwrap(); 146 146 147 147 assert!(result.is_cached()); ··· 163 163 dependencies: vec![], 164 164 fingerprint: SourceFingerprint::new(source), 165 165 }; 166 - let path = Path::new(path); 166 + let path = Utf8Path::new(path); 167 167 fs.write_bytes(&path, &cache_metadata.to_binary()).unwrap(); 168 168 } 169 169 170 170 fn write_src(fs: &InMemoryFileSystem, source: &str, path: &str, seconds: u64) { 171 - let path = Path::new(path); 171 + let path = Utf8Path::new(path); 172 172 fs.write(&path, source).unwrap(); 173 173 fs.set_modification_time(&path, SystemTime::UNIX_EPOCH + Duration::from_secs(seconds)); 174 174 } ··· 177 177 warnings: &'a WarningEmitter, 178 178 package_name: &'a SmolStr, 179 179 fs: &InMemoryFileSystem, 180 - src: &'a Path, 181 - artefact: &'a Path, 180 + src: &'a Utf8Path, 181 + artefact: &'a Utf8Path, 182 182 ) -> ModuleLoader<'a, InMemoryFileSystem> { 183 183 ModuleLoader { 184 184 warnings,
+14 -19
compiler-core/src/build/native_file_copier.rs
··· 1 1 #[cfg(test)] 2 2 mod tests; 3 3 4 - use std::{ 5 - collections::HashSet, 6 - path::{Path, PathBuf}, 7 - }; 4 + use std::collections::HashSet; 5 + 6 + use camino::{Utf8Path, Utf8PathBuf}; 8 7 9 8 use crate::{ 10 9 io::{FileSystemReader, FileSystemWriter}, ··· 14 13 #[derive(Debug, Clone, PartialEq, Eq)] 15 14 pub(crate) struct CopiedNativeFiles { 16 15 pub any_elixir: bool, 17 - pub to_compile: Vec<PathBuf>, 16 + pub to_compile: Vec<Utf8PathBuf>, 18 17 } 19 18 20 19 pub(crate) struct NativeFileCopier<'a, IO> { 21 20 io: IO, 22 - root: &'a Path, 23 - destination_dir: &'a Path, 24 - seen_native_files: HashSet<PathBuf>, 25 - to_compile: Vec<PathBuf>, 21 + root: &'a Utf8Path, 22 + destination_dir: &'a Utf8Path, 23 + seen_native_files: HashSet<Utf8PathBuf>, 24 + to_compile: Vec<Utf8PathBuf>, 26 25 elixir_files_copied: bool, 27 26 } 28 27 ··· 30 29 where 31 30 IO: FileSystemReader + FileSystemWriter + Clone, 32 31 { 33 - pub(crate) fn new(io: IO, root: &'a Path, out: &'a Path) -> Self { 32 + pub(crate) fn new(io: IO, root: &'a Utf8Path, out: &'a Utf8Path) -> Self { 34 33 Self { 35 34 io, 36 35 root, ··· 64 63 }) 65 64 } 66 65 67 - fn copy_files(&mut self, src_root: &Path) -> Result<()> { 66 + fn copy_files(&mut self, src_root: &Utf8Path) -> Result<()> { 68 67 let mut check_elixir_libs = true; 69 68 70 69 for entry in self.io.read_dir(src_root)? { ··· 74 73 Ok(()) 75 74 } 76 75 77 - fn copy(&mut self, file: PathBuf, src_root: &Path) -> Result<()> { 78 - let extension = file 79 - .extension() 80 - .unwrap_or_default() 81 - .to_str() 82 - .unwrap_or_default(); 76 + fn copy(&mut self, file: Utf8PathBuf, src_root: &Utf8Path) -> Result<()> { 77 + let extension = file.extension().unwrap_or_default(); 83 78 84 79 // Skip unknown file formats that are not supported native files 85 80 if !matches!(extension, "mjs" | "js" | "ts" | "hrl" | "erl" | "ex") { ··· 119 114 Ok(()) 120 115 } 121 116 122 - fn check_for_duplicate(&mut self, relative_path: &PathBuf) -> Result<(), Error> { 117 + fn check_for_duplicate(&mut self, relative_path: &Utf8PathBuf) -> Result<(), Error> { 123 118 if !self.seen_native_files.insert(relative_path.clone()) { 124 119 return Err(Error::DuplicateSourceFile { 125 - file: relative_path.to_string_lossy().to_string(), 120 + file: relative_path.to_string(), 126 121 }); 127 122 } 128 123 Ok(())
+56 -55
compiler-core/src/build/native_file_copier/tests.rs
··· 6 6 use lazy_static::lazy_static; 7 7 use std::{ 8 8 collections::HashMap, 9 - path::{Path, PathBuf}, 10 9 time::{Duration, SystemTime, UNIX_EPOCH}, 11 10 }; 12 11 12 + use camino::{Utf8Path, Utf8PathBuf}; 13 + 13 14 lazy_static! { 14 - static ref ROOT: PathBuf = PathBuf::from("/"); 15 - static ref OUT: PathBuf = PathBuf::from("/out"); 15 + static ref ROOT: Utf8PathBuf = Utf8PathBuf::from("/"); 16 + static ref OUT: Utf8PathBuf = Utf8PathBuf::from("/out"); 16 17 } 17 18 18 19 #[test] 19 20 fn javascript_files_are_copied_from_src() { 20 21 let fs = InMemoryFileSystem::new(); 21 - fs.write(&Path::new("/src/wibble.js"), "1").unwrap(); 22 + fs.write(&Utf8Path::new("/src/wibble.js"), "1").unwrap(); 22 23 23 24 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 24 25 let copied = copier.run().unwrap(); ··· 27 28 assert!(copied.to_compile.is_empty()); 28 29 assert_eq!( 29 30 HashMap::from([ 30 - (PathBuf::from("/src/wibble.js"), "1".into()), 31 - (PathBuf::from("/out/wibble.js"), "1".into()) 31 + (Utf8PathBuf::from("/src/wibble.js"), "1".into()), 32 + (Utf8PathBuf::from("/out/wibble.js"), "1".into()) 32 33 ]), 33 34 fs.into_contents(), 34 35 ); ··· 37 38 #[test] 38 39 fn javascript_files_are_copied_from_test() { 39 40 let fs = InMemoryFileSystem::new(); 40 - fs.write(&Path::new("/test/wibble.js"), "1").unwrap(); 41 + fs.write(&Utf8Path::new("/test/wibble.js"), "1").unwrap(); 41 42 42 43 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 43 44 let copied = copier.run().unwrap(); ··· 46 47 assert!(copied.to_compile.is_empty()); 47 48 assert_eq!( 48 49 HashMap::from([ 49 - (PathBuf::from("/test/wibble.js"), "1".into()), 50 - (PathBuf::from("/out/wibble.js"), "1".into()) 50 + (Utf8PathBuf::from("/test/wibble.js"), "1".into()), 51 + (Utf8PathBuf::from("/out/wibble.js"), "1".into()) 51 52 ]), 52 53 fs.into_contents(), 53 54 ); ··· 56 57 #[test] 57 58 fn mjavascript_files_are_copied_from_src() { 58 59 let fs = InMemoryFileSystem::new(); 59 - fs.write(&Path::new("/src/wibble.mjs"), "1").unwrap(); 60 + fs.write(&Utf8Path::new("/src/wibble.mjs"), "1").unwrap(); 60 61 61 62 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 62 63 let copied = copier.run().unwrap(); ··· 65 66 assert!(copied.to_compile.is_empty()); 66 67 assert_eq!( 67 68 HashMap::from([ 68 - (PathBuf::from("/src/wibble.mjs"), "1".into()), 69 - (PathBuf::from("/out/wibble.mjs"), "1".into()) 69 + (Utf8PathBuf::from("/src/wibble.mjs"), "1".into()), 70 + (Utf8PathBuf::from("/out/wibble.mjs"), "1".into()) 70 71 ]), 71 72 fs.into_contents(), 72 73 ); ··· 75 76 #[test] 76 77 fn mjavascript_files_are_copied_from_test() { 77 78 let fs = InMemoryFileSystem::new(); 78 - fs.write(&Path::new("/test/wibble.mjs"), "1").unwrap(); 79 + fs.write(&Utf8Path::new("/test/wibble.mjs"), "1").unwrap(); 79 80 80 81 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 81 82 let copied = copier.run().unwrap(); ··· 84 85 assert!(copied.to_compile.is_empty()); 85 86 assert_eq!( 86 87 HashMap::from([ 87 - (PathBuf::from("/test/wibble.mjs"), "1".into()), 88 - (PathBuf::from("/out/wibble.mjs"), "1".into()) 88 + (Utf8PathBuf::from("/test/wibble.mjs"), "1".into()), 89 + (Utf8PathBuf::from("/out/wibble.mjs"), "1".into()) 89 90 ]), 90 91 fs.into_contents(), 91 92 ); ··· 94 95 #[test] 95 96 fn typescript_files_are_copied_from_src() { 96 97 let fs = InMemoryFileSystem::new(); 97 - fs.write(&Path::new("/src/wibble.ts"), "1").unwrap(); 98 + fs.write(&Utf8Path::new("/src/wibble.ts"), "1").unwrap(); 98 99 99 100 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 100 101 let copied = copier.run().unwrap(); ··· 103 104 assert!(copied.to_compile.is_empty()); 104 105 assert_eq!( 105 106 HashMap::from([ 106 - (PathBuf::from("/src/wibble.ts"), "1".into()), 107 - (PathBuf::from("/out/wibble.ts"), "1".into()) 107 + (Utf8PathBuf::from("/src/wibble.ts"), "1".into()), 108 + (Utf8PathBuf::from("/out/wibble.ts"), "1".into()) 108 109 ]), 109 110 fs.into_contents(), 110 111 ); ··· 113 114 #[test] 114 115 fn typescript_files_are_copied_from_test() { 115 116 let fs = InMemoryFileSystem::new(); 116 - fs.write(&Path::new("/test/wibble.ts"), "1").unwrap(); 117 + fs.write(&Utf8Path::new("/test/wibble.ts"), "1").unwrap(); 117 118 118 119 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 119 120 let copied = copier.run().unwrap(); ··· 122 123 assert!(copied.to_compile.is_empty()); 123 124 assert_eq!( 124 125 HashMap::from([ 125 - (PathBuf::from("/test/wibble.ts"), "1".into()), 126 - (PathBuf::from("/out/wibble.ts"), "1".into()) 126 + (Utf8PathBuf::from("/test/wibble.ts"), "1".into()), 127 + (Utf8PathBuf::from("/out/wibble.ts"), "1".into()) 127 128 ]), 128 129 fs.into_contents(), 129 130 ); ··· 132 133 #[test] 133 134 fn erlang_header_files_are_copied_from_src() { 134 135 let fs = InMemoryFileSystem::new(); 135 - fs.write(&Path::new("/src/wibble.hrl"), "1").unwrap(); 136 + fs.write(&Utf8Path::new("/src/wibble.hrl"), "1").unwrap(); 136 137 137 138 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 138 139 let copied = copier.run().unwrap(); ··· 141 142 assert!(copied.to_compile.is_empty()); 142 143 assert_eq!( 143 144 HashMap::from([ 144 - (PathBuf::from("/src/wibble.hrl"), "1".into()), 145 - (PathBuf::from("/out/wibble.hrl"), "1".into()) 145 + (Utf8PathBuf::from("/src/wibble.hrl"), "1".into()), 146 + (Utf8PathBuf::from("/out/wibble.hrl"), "1".into()) 146 147 ]), 147 148 fs.into_contents(), 148 149 ); ··· 151 152 #[test] 152 153 fn erlang_header_files_are_copied_from_test() { 153 154 let fs = InMemoryFileSystem::new(); 154 - fs.write(&Path::new("/test/wibble.hrl"), "1").unwrap(); 155 + fs.write(&Utf8Path::new("/test/wibble.hrl"), "1").unwrap(); 155 156 156 157 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 157 158 let copied = copier.run().unwrap(); ··· 160 161 assert!(copied.to_compile.is_empty()); 161 162 assert_eq!( 162 163 HashMap::from([ 163 - (PathBuf::from("/test/wibble.hrl"), "1".into()), 164 - (PathBuf::from("/out/wibble.hrl"), "1".into()) 164 + (Utf8PathBuf::from("/test/wibble.hrl"), "1".into()), 165 + (Utf8PathBuf::from("/out/wibble.hrl"), "1".into()) 165 166 ]), 166 167 fs.into_contents(), 167 168 ); ··· 170 171 #[test] 171 172 fn erlang_files_are_copied_from_src() { 172 173 let fs = InMemoryFileSystem::new(); 173 - fs.write(&Path::new("/src/wibble.erl"), "1").unwrap(); 174 + fs.write(&Utf8Path::new("/src/wibble.erl"), "1").unwrap(); 174 175 175 176 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 176 177 let copied = copier.run().unwrap(); 177 178 178 179 assert!(!copied.any_elixir); 179 - assert_eq!(copied.to_compile, vec![PathBuf::from("wibble.erl")]); 180 + assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.erl")]); 180 181 assert_eq!( 181 182 HashMap::from([ 182 - (PathBuf::from("/src/wibble.erl"), "1".into()), 183 - (PathBuf::from("/out/wibble.erl"), "1".into()) 183 + (Utf8PathBuf::from("/src/wibble.erl"), "1".into()), 184 + (Utf8PathBuf::from("/out/wibble.erl"), "1".into()) 184 185 ]), 185 186 fs.into_contents(), 186 187 ); ··· 189 190 #[test] 190 191 fn erlang_files_are_copied_from_test() { 191 192 let fs = InMemoryFileSystem::new(); 192 - fs.write(&Path::new("/test/wibble.erl"), "1").unwrap(); 193 + fs.write(&Utf8Path::new("/test/wibble.erl"), "1").unwrap(); 193 194 194 195 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 195 196 let copied = copier.run().unwrap(); 196 197 197 198 assert!(!copied.any_elixir); 198 - assert_eq!(copied.to_compile, vec![PathBuf::from("wibble.erl")]); 199 + assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.erl")]); 199 200 assert_eq!( 200 201 HashMap::from([ 201 - (PathBuf::from("/test/wibble.erl"), "1".into()), 202 - (PathBuf::from("/out/wibble.erl"), "1".into()) 202 + (Utf8PathBuf::from("/test/wibble.erl"), "1".into()), 203 + (Utf8PathBuf::from("/out/wibble.erl"), "1".into()) 203 204 ]), 204 205 fs.into_contents(), 205 206 ); ··· 208 209 #[test] 209 210 fn elixir_files_are_copied_from_src() { 210 211 let fs = InMemoryFileSystem::new(); 211 - fs.write(&Path::new("/src/wibble.ex"), "1").unwrap(); 212 + fs.write(&Utf8Path::new("/src/wibble.ex"), "1").unwrap(); 212 213 213 214 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 214 215 let copied = copier.run().unwrap(); 215 216 216 217 assert!(copied.any_elixir); 217 - assert_eq!(copied.to_compile, vec![PathBuf::from("wibble.ex")]); 218 + assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.ex")]); 218 219 assert_eq!( 219 220 HashMap::from([ 220 - (PathBuf::from("/src/wibble.ex"), "1".into()), 221 - (PathBuf::from("/out/wibble.ex"), "1".into()) 221 + (Utf8PathBuf::from("/src/wibble.ex"), "1".into()), 222 + (Utf8PathBuf::from("/out/wibble.ex"), "1".into()) 222 223 ]), 223 224 fs.into_contents(), 224 225 ); ··· 227 228 #[test] 228 229 fn elixir_files_are_copied_from_test() { 229 230 let fs = InMemoryFileSystem::new(); 230 - fs.write(&Path::new("/test/wibble.ex"), "1").unwrap(); 231 + fs.write(&Utf8Path::new("/test/wibble.ex"), "1").unwrap(); 231 232 232 233 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 233 234 let copied = copier.run().unwrap(); 234 235 235 236 assert!(copied.any_elixir); 236 - assert_eq!(copied.to_compile, vec![PathBuf::from("wibble.ex")]); 237 + assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.ex")]); 237 238 assert_eq!( 238 239 HashMap::from([ 239 - (PathBuf::from("/test/wibble.ex"), "1".into()), 240 - (PathBuf::from("/out/wibble.ex"), "1".into()) 240 + (Utf8PathBuf::from("/test/wibble.ex"), "1".into()), 241 + (Utf8PathBuf::from("/out/wibble.ex"), "1".into()) 241 242 ]), 242 243 fs.into_contents(), 243 244 ); ··· 246 247 #[test] 247 248 fn other_files_are_ignored() { 248 249 let fs = InMemoryFileSystem::new(); 249 - fs.write(&Path::new("/src/wibble.cpp"), "1").unwrap(); 250 + fs.write(&Utf8Path::new("/src/wibble.cpp"), "1").unwrap(); 250 251 251 252 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 252 253 let copied = copier.run().unwrap(); ··· 254 255 assert!(!copied.any_elixir); 255 256 assert!(copied.to_compile.is_empty()); 256 257 assert_eq!( 257 - HashMap::from([(PathBuf::from("/src/wibble.cpp"), "1".into())]), 258 + HashMap::from([(Utf8PathBuf::from("/src/wibble.cpp"), "1".into())]), 258 259 fs.into_contents(), 259 260 ); 260 261 } ··· 262 263 #[test] 263 264 fn files_do_not_get_copied_if_there_already_is_a_new_version() { 264 265 let fs = InMemoryFileSystem::new(); 265 - let out = Path::new("/out/wibble.mjs"); 266 - let src = Path::new("/src/wibble.mjs"); 266 + let out = Utf8Path::new("/out/wibble.mjs"); 267 + let src = Utf8Path::new("/src/wibble.mjs"); 267 268 fs.write(&out, "in-out").unwrap(); 268 269 fs.write(&src, "in-src").unwrap(); 269 270 fs.set_modification_time(&out, UNIX_EPOCH + Duration::from_secs(1)); ··· 276 277 assert!(copied.to_compile.is_empty()); 277 278 assert_eq!( 278 279 HashMap::from([ 279 - (PathBuf::from("/src/wibble.mjs"), "in-src".into()), 280 - (PathBuf::from("/out/wibble.mjs"), "in-out".into()) 280 + (Utf8PathBuf::from("/src/wibble.mjs"), "in-src".into()), 281 + (Utf8PathBuf::from("/out/wibble.mjs"), "in-out".into()) 281 282 ]), 282 283 fs.into_contents(), 283 284 ); ··· 286 287 #[test] 287 288 fn files_get_copied_if_the_previously_copied_vesion_is_older() { 288 289 let fs = InMemoryFileSystem::new(); 289 - let out = Path::new("/out/wibble.mjs"); 290 - let src = Path::new("/src/wibble.mjs"); 290 + let out = Utf8Path::new("/out/wibble.mjs"); 291 + let src = Utf8Path::new("/src/wibble.mjs"); 291 292 fs.write(&out, "in-out").unwrap(); 292 293 fs.write(&src, "in-src").unwrap(); 293 294 fs.set_modification_time(&out, UNIX_EPOCH); ··· 300 301 assert!(copied.to_compile.is_empty()); 301 302 assert_eq!( 302 303 HashMap::from([ 303 - (PathBuf::from("/src/wibble.mjs"), "in-src".into()), 304 - (PathBuf::from("/out/wibble.mjs"), "in-src".into()) 304 + (Utf8PathBuf::from("/src/wibble.mjs"), "in-src".into()), 305 + (Utf8PathBuf::from("/out/wibble.mjs"), "in-src".into()) 305 306 ]), 306 307 fs.into_contents(), 307 308 ); ··· 310 311 #[test] 311 312 fn duplicate_native_files_result_in_an_error() { 312 313 let fs = InMemoryFileSystem::new(); 313 - fs.write(&Path::new("/src/wibble.mjs"), "1").unwrap(); 314 - fs.write(&Path::new("/test/wibble.mjs"), "1").unwrap(); 314 + fs.write(&Utf8Path::new("/src/wibble.mjs"), "1").unwrap(); 315 + fs.write(&Utf8Path::new("/test/wibble.mjs"), "1").unwrap(); 315 316 316 317 let copier = NativeFileCopier::new(fs.clone(), &ROOT, &OUT); 317 318 assert!(copier.run().is_err());
+27 -34
compiler-core/src/build/package_compiler.rs
··· 20 20 }; 21 21 use askama::Template; 22 22 use smol_str::SmolStr; 23 + use std::collections::HashSet; 23 24 use std::{collections::HashMap, fmt::write, time::SystemTime}; 24 - use std::{ 25 - collections::HashSet, 26 - path::{Path, PathBuf}, 27 - }; 25 + 26 + use camino::{Utf8Path, Utf8PathBuf}; 28 27 29 28 use super::{ErlangAppCodegenConfiguration, TargetCodegenConfiguration}; 30 29 ··· 36 35 #[derive(Debug)] 37 36 pub struct PackageCompiler<'a, IO> { 38 37 pub io: IO, 39 - pub out: &'a Path, 40 - pub lib: &'a Path, 41 - pub root: &'a Path, 38 + pub out: &'a Utf8Path, 39 + pub lib: &'a Utf8Path, 40 + pub root: &'a Utf8Path, 42 41 pub mode: Mode, 43 42 pub target: &'a TargetCodegenConfiguration, 44 43 pub config: &'a PackageConfig, ··· 58 57 pub fn new( 59 58 config: &'a PackageConfig, 60 59 mode: Mode, 61 - root: &'a Path, 62 - out: &'a Path, 63 - lib: &'a Path, 60 + root: &'a Utf8Path, 61 + out: &'a Utf8Path, 62 + lib: &'a Utf8Path, 64 63 target: &'a TargetCodegenConfiguration, 65 64 ids: UniqueIdGenerator, 66 65 io: IO, ··· 91 90 mut self, 92 91 warnings: &WarningEmitter, 93 92 existing_modules: &mut im::HashMap<SmolStr, type_::ModuleInterface>, 94 - already_defined_modules: &mut im::HashMap<SmolStr, PathBuf>, 93 + already_defined_modules: &mut im::HashMap<SmolStr, Utf8PathBuf>, 95 94 stale_modules: &mut StaleTracker, 96 95 ) -> Result<Vec<Module>, Error> { 97 96 let span = tracing::info_span!("compile", package = %self.config.name.as_str()); ··· 144 143 Ok(modules) 145 144 } 146 145 147 - fn compile_erlang_to_beam(&mut self, modules: &HashSet<PathBuf>) -> Result<(), Error> { 146 + fn compile_erlang_to_beam(&mut self, modules: &HashSet<Utf8PathBuf>) -> Result<(), Error> { 148 147 if modules.is_empty() { 149 148 tracing::debug!("no_erlang_to_compile"); 150 149 return Ok(()); ··· 162 161 } 163 162 164 163 let mut args = vec![ 165 - escript_path.to_string_lossy().to_string(), 164 + escript_path.to_string(), 166 165 // Tell the compiler where to find other libraries 167 166 "--lib".into(), 168 - self.lib.to_string_lossy().to_string(), 167 + self.lib.to_string(), 169 168 // Write compiled .beam to ./ebin 170 169 "--out".into(), 171 - self.out.join("ebin").to_string_lossy().to_string(), 170 + self.out.join("ebin").to_string(), 172 171 ]; 173 172 // Add the list of modules to compile 174 173 for module in modules { 175 174 let path = self.out.join(paths::ARTEFACT_DIRECTORY_NAME).join(module); 176 - args.push(path.to_string_lossy().to_string()); 175 + args.push(path.to_string()); 177 176 } 178 177 // Compile Erlang and Elixir modules 179 178 let status = self ··· 192 191 193 192 fn copy_project_native_files( 194 193 &mut self, 195 - destination_dir: &Path, 196 - to_compile_modules: &mut HashSet<PathBuf>, 194 + destination_dir: &Utf8Path, 195 + to_compile_modules: &mut HashSet<Utf8PathBuf>, 197 196 ) -> Result<(), Error> { 198 197 tracing::debug!("copying_native_source_files"); 199 198 ··· 342 341 343 342 fn render_erlang_entrypoint_module( 344 343 &mut self, 345 - out: &Path, 346 - modules_to_compile: &mut HashSet<PathBuf>, 344 + out: &Utf8Path, 345 + modules_to_compile: &mut HashSet<Utf8PathBuf>, 347 346 ) -> Result<(), Error> { 348 347 let name = format!("{name}@@main.erl", name = self.config.name); 349 348 let path = out.join(&name); ··· 435 434 436 435 pub fn maybe_link_elixir_libs<IO>( 437 436 io: &IO, 438 - build_dir: &PathBuf, 437 + build_dir: &Utf8PathBuf, 439 438 subprocess_stdio: Stdio, 440 439 ) -> Result<(), Error> 441 440 where ··· 490 489 // Each pathfinder line is a system path for an Elixir core library 491 490 let read_pathfinder = io.read(&pathfinder)?; 492 491 for lib_path in read_pathfinder.split('\n') { 493 - let source = PathBuf::from(lib_path); 492 + let source = Utf8PathBuf::from(lib_path); 494 493 let name = source 495 494 .as_path() 496 495 .file_name() ··· 507 506 // Delete the existing link 508 507 io.delete(&dest)?; 509 508 } 510 - tracing::debug!( 511 - "linking_{}_to_build", 512 - name.to_str().unwrap_or("elixir_core_lib"), 513 - ); 509 + tracing::debug!("linking_{}_to_build", name,); 514 510 io.symlink_dir(&source, &dest)?; 515 511 } 516 512 517 513 Ok(()) 518 514 } 519 515 520 - pub(crate) fn module_name(package_path: &Path, full_module_path: &Path) -> SmolStr { 516 + pub(crate) fn module_name(package_path: &Utf8Path, full_module_path: &Utf8Path) -> SmolStr { 521 517 // /path/to/project/_build/default/lib/the_package/src/my/module.gleam 522 518 523 519 // my/module.gleam ··· 530 526 let _ = module_path.set_extension(""); 531 527 532 528 // Stringify 533 - let name = module_path 534 - .to_str() 535 - .expect("Module name path to str") 536 - .to_string(); 529 + let name = module_path.to_string(); 537 530 538 531 // normalise windows paths 539 532 name.replace("\\", "/").into() ··· 553 546 } 554 547 } 555 548 556 - pub fn source_path(&self) -> &Path { 549 + pub fn source_path(&self) -> &Utf8Path { 557 550 match self { 558 551 Input::New(m) => &m.path, 559 552 Input::Cached(m) => &m.source_path, ··· 589 582 pub name: SmolStr, 590 583 pub origin: Origin, 591 584 pub dependencies: Vec<SmolStr>, 592 - pub source_path: PathBuf, 585 + pub source_path: Utf8PathBuf, 593 586 } 594 587 595 588 #[derive(Debug, serde::Serialize, serde::Deserialize)] ··· 618 611 619 612 #[derive(Debug, PartialEq, Eq)] 620 613 pub(crate) struct UncompiledModule { 621 - pub path: PathBuf, 614 + pub path: Utf8PathBuf, 622 615 pub name: SmolStr, 623 616 pub code: SmolStr, 624 617 pub mtime: SystemTime,
+12 -12
compiler-core/src/build/package_loader.rs
··· 3 3 4 4 use std::{ 5 5 collections::{HashMap, HashSet}, 6 - path::{Path, PathBuf}, 7 6 time::{Duration, SystemTime}, 8 7 }; 8 + 9 + use camino::{Utf8Path, Utf8PathBuf}; 9 10 10 11 // TODO: emit warnings for cached modules even if they are not compiled again. 11 12 ··· 52 53 io: IO, 53 54 ids: UniqueIdGenerator, 54 55 mode: Mode, 55 - root: &'a Path, 56 + root: &'a Utf8Path, 56 57 warnings: &'a WarningEmitter, 57 58 codegen: CodegenRequired, 58 - artefact_directory: &'a Path, 59 + artefact_directory: &'a Utf8Path, 59 60 package_name: &'a SmolStr, 60 61 target: Target, 61 62 stale_modules: &'a mut StaleTracker, 62 - already_defined_modules: &'a mut im::HashMap<SmolStr, PathBuf>, 63 + already_defined_modules: &'a mut im::HashMap<SmolStr, Utf8PathBuf>, 63 64 } 64 65 65 66 impl<'a, IO> PackageLoader<'a, IO> ··· 70 71 io: IO, 71 72 ids: UniqueIdGenerator, 72 73 mode: Mode, 73 - root: &'a Path, 74 + root: &'a Utf8Path, 74 75 warnings: &'a WarningEmitter, 75 76 codegen: CodegenRequired, 76 - artefact_directory: &'a Path, 77 + artefact_directory: &'a Utf8Path, 77 78 target: Target, 78 79 package_name: &'a SmolStr, 79 80 stale_modules: &'a mut StaleTracker, 80 - already_defined_modules: &'a mut im::HashMap<SmolStr, PathBuf>, 81 + already_defined_modules: &'a mut im::HashMap<SmolStr, Utf8PathBuf>, 81 82 ) -> Self { 82 83 Self { 83 84 io, ··· 156 157 metadata::ModuleDecoder::new(self.ids.clone()).read(bytes.as_slice()) 157 158 } 158 159 159 - pub fn is_gleam_path(&self, path: &Path, dir: &Path) -> bool { 160 + pub fn is_gleam_path(&self, path: &Utf8Path, dir: &Utf8Path) -> bool { 160 161 use regex::Regex; 161 162 lazy_static! { 162 163 static ref RE: Regex = Regex::new(&format!( ··· 170 171 RE.is_match( 171 172 path.strip_prefix(dir) 172 173 .expect("is_gleam_path(): strip_prefix") 173 - .to_str() 174 - .expect("is_gleam_path(): to_str"), 174 + .as_str(), 175 175 ) 176 176 } 177 177 ··· 260 260 #[derive(Debug)] 261 261 pub struct Inputs<'a> { 262 262 collection: HashMap<SmolStr, Input>, 263 - already_defined_modules: &'a im::HashMap<SmolStr, PathBuf>, 263 + already_defined_modules: &'a im::HashMap<SmolStr, Utf8PathBuf>, 264 264 } 265 265 266 266 impl<'a> Inputs<'a> { 267 - fn new(already_defined_modules: &'a im::HashMap<SmolStr, PathBuf>) -> Self { 267 + fn new(already_defined_modules: &'a im::HashMap<SmolStr, Utf8PathBuf>) -> Self { 268 268 Self { 269 269 collection: Default::default(), 270 270 already_defined_modules,
+24 -24
compiler-core/src/build/package_loader/tests.rs
··· 22 22 const TEST_SOURCE_2: &'static str = "const x = 2"; 23 23 24 24 fn write_src(fs: &InMemoryFileSystem, path: &str, seconds: u64, src: &str) { 25 - let path = Path::new(path); 25 + let path = Utf8Path::new(path); 26 26 fs.write(&path, src).unwrap(); 27 27 fs.set_modification_time(&path, SystemTime::UNIX_EPOCH + Duration::from_secs(seconds)); 28 28 } ··· 35 35 dependencies: deps, 36 36 fingerprint: SourceFingerprint::new(src), 37 37 }; 38 - let path = Path::new("/artefact").join(format!("{name}.cache_meta")); 38 + let path = Utf8Path::new("/artefact").join(format!("{name}.cache_meta")); 39 39 fs.write_bytes(&path, &cache_metadata.to_binary()).unwrap(); 40 40 41 41 let cache = crate::type_::ModuleInterface { ··· 47 47 values: Default::default(), 48 48 accessors: Default::default(), 49 49 }; 50 - let path = Path::new("/artefact").join(format!("{name}.cache")); 50 + let path = Utf8Path::new("/artefact").join(format!("{name}.cache")); 51 51 fs.write_bytes( 52 52 &path, 53 53 &metadata::ModuleEncoder::new(&cache).encode().unwrap(), ··· 55 55 .unwrap(); 56 56 } 57 57 58 - fn run_loader(fs: InMemoryFileSystem, root: &Path, artefact: &Path) -> LoaderTestOutput { 58 + fn run_loader(fs: InMemoryFileSystem, root: &Utf8Path, artefact: &Utf8Path) -> LoaderTestOutput { 59 59 let mut defined = im::HashMap::new(); 60 60 let ids = UniqueIdGenerator::new(); 61 61 let (emitter, warnings) = WarningEmitter::vector(); ··· 85 85 #[test] 86 86 fn no_modules() { 87 87 let fs = InMemoryFileSystem::new(); 88 - let root = Path::new("/"); 89 - let artefact = Path::new("/artefact"); 88 + let root = Utf8Path::new("/"); 89 + let artefact = Utf8Path::new("/artefact"); 90 90 91 91 let loaded = run_loader(fs, root, artefact); 92 92 assert!(loaded.to_compile.is_empty()); ··· 96 96 #[test] 97 97 fn one_src_module() { 98 98 let fs = InMemoryFileSystem::new(); 99 - let root = Path::new("/"); 100 - let artefact = Path::new("/artefact"); 99 + let root = Utf8Path::new("/"); 100 + let artefact = Utf8Path::new("/artefact"); 101 101 102 102 write_src(&fs, "/src/main.gleam", 0, "const x = 1"); 103 103 ··· 109 109 #[test] 110 110 fn one_test_module() { 111 111 let fs = InMemoryFileSystem::new(); 112 - let root = Path::new("/"); 113 - let artefact = Path::new("/artefact"); 112 + let root = Utf8Path::new("/"); 113 + let artefact = Utf8Path::new("/artefact"); 114 114 115 115 write_src(&fs, "/test/main.gleam", 0, "const x = 1"); 116 116 ··· 122 122 #[test] 123 123 fn importing() { 124 124 let fs = InMemoryFileSystem::new(); 125 - let root = Path::new("/"); 126 - let artefact = Path::new("/artefact"); 125 + let root = Utf8Path::new("/"); 126 + let artefact = Utf8Path::new("/artefact"); 127 127 128 128 write_src(&fs, "/src/three.gleam", 0, "import two"); 129 129 write_src(&fs, "/src/one.gleam", 0, ""); ··· 144 144 #[test] 145 145 fn reading_cache() { 146 146 let fs = InMemoryFileSystem::new(); 147 - let root = Path::new("/"); 148 - let artefact = Path::new("/artefact"); 147 + let root = Utf8Path::new("/"); 148 + let artefact = Utf8Path::new("/artefact"); 149 149 150 150 write_src(&fs, "/src/one.gleam", 0, TEST_SOURCE_1); 151 151 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); ··· 158 158 #[test] 159 159 fn module_is_stale_if_cache_older() { 160 160 let fs = InMemoryFileSystem::new(); 161 - let root = Path::new("/"); 162 - let artefact = Path::new("/artefact"); 161 + let root = Utf8Path::new("/"); 162 + let artefact = Utf8Path::new("/artefact"); 163 163 164 164 write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2); 165 165 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); ··· 172 172 #[test] 173 173 fn module_is_stale_if_deps_are_stale() { 174 174 let fs = InMemoryFileSystem::new(); 175 - let root = Path::new("/"); 176 - let artefact = Path::new("/artefact"); 175 + let root = Utf8Path::new("/"); 176 + let artefact = Utf8Path::new("/artefact"); 177 177 178 178 // Cache is stale 179 179 write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2); ··· 198 198 #[test] 199 199 fn invalid_module_name() { 200 200 let fs = InMemoryFileSystem::new(); 201 - let root = Path::new("/"); 202 - let artefact = Path::new("/artefact"); 201 + let root = Utf8Path::new("/"); 202 + let artefact = Utf8Path::new("/artefact"); 203 203 204 204 // Cache is stale 205 205 write_src(&fs, "/src/One.gleam", 1, TEST_SOURCE_2); ··· 210 210 assert_eq!( 211 211 loaded.warnings, 212 212 vec![Warning::InvalidSource { 213 - path: PathBuf::from("/src/One.gleam"), 213 + path: Utf8PathBuf::from("/src/One.gleam"), 214 214 }], 215 215 ); 216 216 } ··· 218 218 #[test] 219 219 fn invalid_nested_module_name() { 220 220 let fs = InMemoryFileSystem::new(); 221 - let root = Path::new("/"); 222 - let artefact = Path::new("/artefact"); 221 + let root = Utf8Path::new("/"); 222 + let artefact = Utf8Path::new("/artefact"); 223 223 224 224 // Cache is stale 225 225 write_src(&fs, "/src/1/one.gleam", 1, TEST_SOURCE_2); ··· 230 230 assert_eq!( 231 231 loaded.warnings, 232 232 vec![Warning::InvalidSource { 233 - path: PathBuf::from("/src/1/one.gleam"), 233 + path: Utf8PathBuf::from("/src/1/one.gleam"), 234 234 }], 235 235 ); 236 236 }
+7 -6
compiler-core/src/build/project_compiler.rs
··· 23 23 collections::{HashMap, HashSet}, 24 24 fmt::Write, 25 25 io::BufReader, 26 - path::{Path, PathBuf}, 27 26 sync::Arc, 28 27 time::Instant, 29 28 }; 30 29 31 30 use super::{Codegen, ErlangAppCodegenConfiguration}; 31 + 32 + use camino::{Utf8Path, Utf8PathBuf}; 32 33 33 34 // On Windows we have to call rebar3 via a little wrapper script. 34 35 // ··· 75 76 pub(crate) config: PackageConfig, 76 77 pub(crate) packages: HashMap<String, ManifestPackage>, 77 78 importable_modules: im::HashMap<SmolStr, type_::ModuleInterface>, 78 - defined_modules: im::HashMap<SmolStr, PathBuf>, 79 + defined_modules: im::HashMap<SmolStr, Utf8PathBuf>, 79 80 stale_modules: StaleTracker, 80 81 warnings: WarningEmitter, 81 82 telemetry: Box<dyn Telemetry>, ··· 281 282 let package = self.paths.build_packages_package(name); 282 283 let build_packages = self.paths.build_directory_for_target(mode, target); 283 284 let ebins = self.paths.build_packages_ebins_glob(mode, target); 284 - let rebar3_path = |path: &Path| format!("../{}", path.to_str().expect("Build path")); 285 + let rebar3_path = |path: &Utf8Path| format!("../{}", path); 285 286 286 287 tracing::debug!("copying_package_to_build"); 287 288 self.io.mkdir(&build_packages)?; ··· 348 349 let mix_build_dir = project_dir.join("_build").join(mix_target); 349 350 let mix_build_lib_dir = mix_build_dir.join("lib"); 350 351 let up = paths::unnest(&project_dir); 351 - let mix_path = |path: &Path| up.join(path).to_str().unwrap_or_default().to_string(); 352 + let mix_path = |path: &Utf8Path| up.join(path).to_string(); 352 353 let ebins = self.paths.build_packages_ebins_glob(mode, target); 353 354 354 355 // Elixir core libs must be loaded ··· 439 440 440 441 fn load_cached_package( 441 442 &mut self, 442 - build_dir: PathBuf, 443 + build_dir: Utf8PathBuf, 443 444 package: &ManifestPackage, 444 445 ) -> Result<(), Error> { 445 446 for path in self.io.gleam_cache_files(&build_dir) { ··· 458 459 &mut self, 459 460 config: &PackageConfig, 460 461 is_root: bool, 461 - root_path: PathBuf, 462 + root_path: Utf8PathBuf, 462 463 ) -> Result<Vec<Module>, Error> { 463 464 let out_path = 464 465 self.paths
+10 -8
compiler-core/src/codegen.rs
··· 3 3 line_numbers::LineNumbers, Result, 4 4 }; 5 5 use itertools::Itertools; 6 - use std::{fmt::Debug, path::Path}; 6 + use std::fmt::Debug; 7 + 8 + use camino::Utf8Path; 7 9 8 10 /// A code generator that creates a .erl Erlang module and record header files 9 11 /// for each Gleam module in the package. 10 12 #[derive(Debug)] 11 13 pub struct Erlang<'a> { 12 - build_directory: &'a Path, 13 - include_directory: &'a Path, 14 + build_directory: &'a Utf8Path, 15 + include_directory: &'a Utf8Path, 14 16 } 15 17 16 18 impl<'a> Erlang<'a> { 17 - pub fn new(build_directory: &'a Path, include_directory: &'a Path) -> Self { 19 + pub fn new(build_directory: &'a Utf8Path, include_directory: &'a Utf8Path) -> Self { 18 20 Self { 19 21 build_directory, 20 22 include_directory, ··· 66 68 /// A code generator that creates a .app Erlang application file for the package 67 69 #[derive(Debug)] 68 70 pub struct ErlangApp<'a> { 69 - output_directory: &'a Path, 71 + output_directory: &'a Utf8Path, 70 72 include_dev_deps: bool, 71 73 } 72 74 73 75 impl<'a> ErlangApp<'a> { 74 - pub fn new(output_directory: &'a Path, include_dev_deps: bool) -> Self { 76 + pub fn new(output_directory: &'a Utf8Path, include_dev_deps: bool) -> Self { 75 77 Self { 76 78 output_directory, 77 79 include_dev_deps, ··· 147 149 148 150 #[derive(Debug)] 149 151 pub struct JavaScript<'a> { 150 - output_directory: &'a Path, 152 + output_directory: &'a Utf8Path, 151 153 typescript: TypeScriptDeclarations, 152 154 } 153 155 154 156 impl<'a> JavaScript<'a> { 155 - pub fn new(output_directory: &'a Path, typescript: TypeScriptDeclarations) -> Self { 157 + pub fn new(output_directory: &'a Utf8Path, typescript: TypeScriptDeclarations) -> Self { 156 158 Self { 157 159 output_directory, 158 160 typescript,
+3 -3
compiler-core/src/config.rs
··· 4 4 use crate::requirement::Requirement; 5 5 use crate::version::COMPILER_VERSION; 6 6 use crate::{Error, Result}; 7 + use camino::{Utf8Path, Utf8PathBuf}; 7 8 use globset::{Glob, GlobSetBuilder}; 8 9 use hexpm::version::Version; 9 10 use http::Uri; ··· 12 13 use std::collections::{HashMap, HashSet}; 13 14 use std::fmt; 14 15 use std::marker::PhantomData; 15 - use std::path::{Path, PathBuf}; 16 16 17 17 #[cfg(test)] 18 18 use crate::manifest::ManifestPackage; ··· 119 119 Ok(deps) 120 120 } 121 121 122 - pub fn read<FS: FileSystemReader, P: AsRef<Path>>( 122 + pub fn read<FS: FileSystemReader, P: AsRef<Utf8Path>>( 123 123 path: P, 124 124 fs: &FS, 125 125 ) -> Result<PackageConfig, Error> { ··· 750 750 pub struct DocsPage { 751 751 pub title: String, 752 752 pub path: String, 753 - pub source: PathBuf, 753 + pub source: Utf8PathBuf, 754 754 } 755 755 756 756 #[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
+3 -6
compiler-core/src/diagnostic.rs
··· 1 - use std::path::PathBuf; 1 + use camino::Utf8PathBuf; 2 2 3 3 pub use codespan_reporting::diagnostic::{LabelStyle, Severity}; 4 4 use codespan_reporting::{diagnostic::Label as CodespanLabel, files::SimpleFile}; ··· 22 22 #[derive(Debug, Clone, PartialEq, Eq)] 23 23 pub struct Location { 24 24 pub src: SmolStr, 25 - pub path: PathBuf, 25 + pub path: Utf8PathBuf, 26 26 pub label: Label, 27 27 pub extra_labels: Vec<Label>, 28 28 } ··· 61 61 } 62 62 63 63 fn write_span(&self, location: &Location, buffer: &mut Buffer) { 64 - let file = SimpleFile::new( 65 - location.path.to_string_lossy().to_string(), 66 - location.src.as_str(), 67 - ); 64 + let file = SimpleFile::new(location.path.to_string(), location.src.as_str()); 68 65 let labels = location 69 66 .labels() 70 67 .map(|l| {
+27 -25
compiler-core/src/docs.rs
··· 1 1 mod source_links; 2 2 3 - use std::{path::PathBuf, time::SystemTime}; 3 + use std::time::SystemTime; 4 + 5 + use camino::Utf8PathBuf; 4 6 5 7 use crate::{ 6 8 ast::{ ··· 112 114 }; 113 115 114 116 files.push(OutputFile { 115 - path: PathBuf::from(&page.path), 117 + path: Utf8PathBuf::from(&page.path), 116 118 content: Content::Text(temp.render().expect("Page template rendering")), 117 119 }); 118 120 ··· 245 247 }; 246 248 247 249 files.push(OutputFile { 248 - path: PathBuf::from(format!("{}.html", module.name)), 250 + path: Utf8PathBuf::from(format!("{}.html", module.name)), 249 251 content: Content::Text( 250 252 template 251 253 .render() ··· 257 259 // Render static assets 258 260 259 261 files.push(OutputFile { 260 - path: PathBuf::from("css/atom-one-light.min.css"), 262 + path: Utf8PathBuf::from("css/atom-one-light.min.css"), 261 263 content: Content::Text( 262 264 std::include_str!("../templates/docs-css/atom-one-light.min.css").to_string(), 263 265 ), 264 266 }); 265 267 266 268 files.push(OutputFile { 267 - path: PathBuf::from("css/atom-one-dark.min.css"), 269 + path: Utf8PathBuf::from("css/atom-one-dark.min.css"), 268 270 content: Content::Text( 269 271 std::include_str!("../templates/docs-css/atom-one-dark.min.css").to_string(), 270 272 ), 271 273 }); 272 274 273 275 files.push(OutputFile { 274 - path: PathBuf::from("css/index.css"), 276 + path: Utf8PathBuf::from("css/index.css"), 275 277 content: Content::Text(std::include_str!("../templates/docs-css/index.css").to_string()), 276 278 }); 277 279 278 280 // highlightjs: 279 281 280 282 files.push(OutputFile { 281 - path: PathBuf::from("js/highlight.min.js"), 283 + path: Utf8PathBuf::from("js/highlight.min.js"), 282 284 content: Content::Text( 283 285 std::include_str!("../templates/docs-js/highlight.min.js").to_string(), 284 286 ), 285 287 }); 286 288 287 289 files.push(OutputFile { 288 - path: PathBuf::from("js/highlightjs-gleam.js"), 290 + path: Utf8PathBuf::from("js/highlightjs-gleam.js"), 289 291 content: Content::Text( 290 292 std::include_str!("../templates/docs-js/highlightjs-gleam.js").to_string(), 291 293 ), 292 294 }); 293 295 294 296 files.push(OutputFile { 295 - path: PathBuf::from("js/highlightjs-erlang.min.js"), 297 + path: Utf8PathBuf::from("js/highlightjs-erlang.min.js"), 296 298 content: Content::Text( 297 299 std::include_str!("../templates/docs-js/highlightjs-erlang.min.js").to_string(), 298 300 ), 299 301 }); 300 302 301 303 files.push(OutputFile { 302 - path: PathBuf::from("js/highlightjs-elixir.min.js"), 304 + path: Utf8PathBuf::from("js/highlightjs-elixir.min.js"), 303 305 content: Content::Text( 304 306 std::include_str!("../templates/docs-js/highlightjs-elixir.min.js").to_string(), 305 307 ), 306 308 }); 307 309 308 310 files.push(OutputFile { 309 - path: PathBuf::from("js/highlightjs-javascript.min.js"), 311 + path: Utf8PathBuf::from("js/highlightjs-javascript.min.js"), 310 312 content: Content::Text( 311 313 std::include_str!("../templates/docs-js/highlightjs-javascript.min.js").to_string(), 312 314 ), 313 315 }); 314 316 315 317 files.push(OutputFile { 316 - path: PathBuf::from("js/highlightjs-typescript.min.js"), 318 + path: Utf8PathBuf::from("js/highlightjs-typescript.min.js"), 317 319 content: Content::Text( 318 320 std::include_str!("../templates/docs-js/highlightjs-typescript.min.js").to_string(), 319 321 ), ··· 322 324 // lunr.min.js, search-data.js and index.js: 323 325 324 326 files.push(OutputFile { 325 - path: PathBuf::from("js/lunr.min.js"), 327 + path: Utf8PathBuf::from("js/lunr.min.js"), 326 328 content: Content::Text(std::include_str!("../templates/docs-js/lunr.min.js").to_string()), 327 329 }); 328 330 329 331 files.push(OutputFile { 330 - path: PathBuf::from("search-data.js"), 332 + path: Utf8PathBuf::from("search-data.js"), 331 333 content: Content::Text(format!( 332 334 "window.Gleam.initSearch({});", 333 335 serde_to_string(&escape_html_contents(search_indexes)) ··· 336 338 }); 337 339 338 340 files.push(OutputFile { 339 - path: PathBuf::from("js/index.js"), 341 + path: Utf8PathBuf::from("js/index.js"), 340 342 content: Content::Text(std::include_str!("../templates/docs-js/index.js").to_string()), 341 343 }); 342 344 343 345 // web fonts: 344 346 345 347 files.push(OutputFile { 346 - path: PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), 348 + path: Utf8PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), 347 349 content: Content::Binary( 348 350 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin-ext.woff2").to_vec(), 349 351 ), 350 352 }); 351 353 352 354 files.push(OutputFile { 353 - path: PathBuf::from("fonts/karla-v23-regular-latin.woff2"), 355 + path: Utf8PathBuf::from("fonts/karla-v23-regular-latin.woff2"), 354 356 content: Content::Binary( 355 357 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin.woff2").to_vec(), 356 358 ), 357 359 }); 358 360 359 361 files.push(OutputFile { 360 - path: PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), 362 + path: Utf8PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), 361 363 content: Content::Binary( 362 364 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin-ext.woff2").to_vec(), 363 365 ), 364 366 }); 365 367 366 368 files.push(OutputFile { 367 - path: PathBuf::from("fonts/karla-v23-bold-latin.woff2"), 369 + path: Utf8PathBuf::from("fonts/karla-v23-bold-latin.woff2"), 368 370 content: Content::Binary( 369 371 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin.woff2").to_vec(), 370 372 ), 371 373 }); 372 374 373 375 files.push(OutputFile { 374 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), 376 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), 375 377 content: Content::Binary( 376 378 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") 377 379 .to_vec(), ··· 379 381 }); 380 382 381 383 files.push(OutputFile { 382 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), 384 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), 383 385 content: Content::Binary( 384 386 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic.woff2") 385 387 .to_vec(), ··· 387 389 }); 388 390 389 391 files.push(OutputFile { 390 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), 392 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), 391 393 content: Content::Binary( 392 394 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek-ext.woff2") 393 395 .to_vec(), ··· 395 397 }); 396 398 397 399 files.push(OutputFile { 398 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), 400 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), 399 401 content: Content::Binary( 400 402 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek.woff2").to_vec(), 401 403 ), 402 404 }); 403 405 404 406 files.push(OutputFile { 405 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), 407 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), 406 408 content: Content::Binary( 407 409 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin-ext.woff2") 408 410 .to_vec(), ··· 410 412 }); 411 413 412 414 files.push(OutputFile { 413 - path: PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), 415 + path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), 414 416 content: Content::Binary( 415 417 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin.woff2").to_vec(), 416 418 ),
+5 -8
compiler-core/src/docs/source_links.rs
··· 5 5 line_numbers::LineNumbers, 6 6 paths::ProjectPaths, 7 7 }; 8 - use std::path::{Component, Path}; 8 + 9 + use camino::{Utf8Component, Utf8Path}; 9 10 10 11 pub struct SourceLinker { 11 12 line_numbers: LineNumbers, ··· 69 70 } 70 71 } 71 72 72 - fn to_url_path(path: &Path) -> Option<String> { 73 + fn to_url_path(path: &Utf8Path) -> Option<String> { 73 74 let mut buf = String::new(); 74 75 for c in path.components() { 75 - if let Component::Normal(s) = c { 76 - if let Some(s) = s.to_str() { 77 - buf.push_str(s); 78 - } else { 79 - return None; 80 - } 76 + if let Utf8Component::Normal(s) = c { 77 + buf.push_str(s); 81 78 } 82 79 buf.push('/'); 83 80 }
+21 -26
compiler-core/src/error.rs
··· 16 16 use smol_str::SmolStr; 17 17 use std::env; 18 18 use std::fmt::Debug; 19 - use std::path::{Path, PathBuf}; 20 19 use termcolor::Buffer; 21 20 use thiserror::Error; 21 + 22 + use camino::{Utf8Path, Utf8PathBuf}; 22 23 23 24 pub type Name = SmolStr; 24 25 ··· 34 35 pub struct UnknownImportDetails { 35 36 pub module: Name, 36 37 pub location: crate::ast::SrcSpan, 37 - pub path: PathBuf, 38 + pub path: Utf8PathBuf, 38 39 pub src: SmolStr, 39 40 pub modules: Vec<SmolStr>, 40 41 } ··· 43 44 pub enum Error { 44 45 #[error("failed to parse Gleam source code")] 45 46 Parse { 46 - path: PathBuf, 47 + path: Utf8PathBuf, 47 48 src: SmolStr, 48 49 error: crate::parse::error::ParseError, 49 50 }, 50 51 51 52 #[error("type checking failed")] 52 53 Type { 53 - path: PathBuf, 54 + path: Utf8PathBuf, 54 55 src: SmolStr, 55 56 error: crate::type_::Error, 56 57 }, ··· 65 66 #[error("duplicate module {module}")] 66 67 DuplicateModule { 67 68 module: Name, 68 - first: PathBuf, 69 - second: PathBuf, 69 + first: Utf8PathBuf, 70 + second: Utf8PathBuf, 70 71 }, 71 72 72 73 #[error("duplicate source file {file}")] ··· 82 83 FileIo { 83 84 kind: FileKind, 84 85 action: FileIoAction, 85 - path: PathBuf, 86 + path: Utf8PathBuf, 86 87 err: Option<String>, 87 88 }, 88 89 ··· 105 106 ExpandTar { error: String }, 106 107 107 108 #[error("{err}")] 108 - AddTar { path: PathBuf, err: String }, 109 + AddTar { path: Utf8PathBuf, err: String }, 109 110 110 111 #[error("{0}")] 111 112 TarFinish(String), ··· 163 164 164 165 #[error("javascript codegen failed")] 165 166 JavaScript { 166 - path: PathBuf, 167 + path: Utf8PathBuf, 167 168 src: SmolStr, 168 169 error: crate::javascript::Error, 169 170 }, ··· 198 199 199 200 #[error("Expected package {expected} at path {path} but found {found} instead")] 200 201 WrongDependencyProvided { 201 - path: PathBuf, 202 + path: Utf8PathBuf, 202 203 expected: String, 203 204 found: String, 204 205 }, ··· 226 227 }, 227 228 228 229 #[error("Opening docs at {path} failed: {error}")] 229 - FailedToOpenDocs { path: PathBuf, error: String }, 230 + FailedToOpenDocs { path: Utf8PathBuf, error: String }, 230 231 231 232 #[error("The package {package} requires a Gleam version satisfying {required_version} and you are using v{gleam_version}")] 232 233 IncompatibleCompilerVersion { ··· 256 257 257 258 pub fn add_tar<P, E>(path: P, error: E) -> Error 258 259 where 259 - P: AsRef<Path>, 260 + P: AsRef<Utf8Path>, 260 261 E: std::error::Error, 261 262 { 262 263 Self::AddTar { ··· 672 673 This was error from the tar library: 673 674 674 675 {}", 675 - path.to_str().unwrap(), 676 - err 676 + path, err 677 677 ); 678 678 Diagnostic { 679 679 title: "Failure creating tar archive".into(), ··· 745 745 746 746 First: {} 747 747 Second: {}", 748 - module, 749 - first.to_str().expect("pretty error print PathBuf to_str"), 750 - second.to_str().expect("pretty error print PathBuf to_str"), 748 + module, first, second, 751 749 ); 752 750 753 751 Diagnostic { ··· 786 784 {}", 787 785 action.text(), 788 786 kind.text(), 789 - path.to_string_lossy(), 787 + path, 790 788 err, 791 789 ); 792 790 Diagnostic { ··· 2343 2341 Error::Format { problem_files } => { 2344 2342 let files: Vec<_> = problem_files 2345 2343 .iter() 2346 - .flat_map(|formatted| formatted.source.to_str()) 2344 + .map(|formatted| formatted.source.as_str()) 2347 2345 .map(|p| format!(" - {p}")) 2348 2346 .sorted() 2349 2347 .collect(); ··· 2490 2488 } => { 2491 2489 let text = format!( 2492 2490 "Expected package {} at path {} but found {} instead", 2493 - expected, 2494 - path.to_string_lossy(), 2495 - found 2491 + expected, path, found 2496 2492 ); 2497 2493 2498 2494 Diagnostic { ··· 2609 2605 2610 2606 {} 2611 2607 {}", 2612 - path.to_string_lossy(), 2613 - error, 2608 + path, error, 2614 2609 ); 2615 2610 Diagnostic { 2616 2611 title: "Failed to open docs".into(), ··· 2750 2745 2751 2746 #[derive(Debug, Clone, PartialEq, Eq)] 2752 2747 pub struct Unformatted { 2753 - pub source: PathBuf, 2754 - pub destination: PathBuf, 2748 + pub source: Utf8PathBuf, 2749 + pub destination: Utf8PathBuf, 2755 2750 pub input: SmolStr, 2756 2751 pub output: String, 2757 2752 }
+4 -2
compiler-core/src/fix.rs
··· 2 2 mod tests; 3 3 4 4 use smol_str::SmolStr; 5 - use std::{collections::HashMap, path::Path}; 5 + use std::collections::HashMap; 6 6 use vec1::vec1; 7 + 8 + use camino::Utf8Path; 7 9 8 10 use crate::{ 9 11 ast::{ ··· 18 20 pub fn parse_fix_and_format( 19 21 assumed_target: Option<Target>, 20 22 src: &SmolStr, 21 - path: &Path, 23 + path: &Utf8Path, 22 24 ) -> Result<(String, bool)> { 23 25 // Parse 24 26 let parsed = crate::parse::parse_module(src).map_err(|error| Error::Parse {
+4 -2
compiler-core/src/fix/tests.rs
··· 1 1 use super::*; 2 2 3 + use camino::Utf8Path; 4 + 3 5 fn fix(target: Option<Target>, src: &str) -> String { 4 - let (out, complete) = parse_fix_and_format(target, &src.into(), Path::new("test")).unwrap(); 6 + let (out, complete) = parse_fix_and_format(target, &src.into(), Utf8Path::new("test")).unwrap(); 5 7 assert!(complete); 6 8 out 7 9 } ··· 496 498 let src = r#"pub external fn main(wibble: Int, wobble: Float) -> Int = 497 499 "app" "main" 498 500 "#; 499 - let (out, complete) = parse_fix_and_format(None, &src.into(), Path::new("test")).unwrap(); 501 + let (out, complete) = parse_fix_and_format(None, &src.into(), Utf8Path::new("test")).unwrap(); 500 502 assert!(!complete); 501 503 assert_eq!(out, src) 502 504 }
+4 -2
compiler-core/src/format.rs
··· 13 13 }; 14 14 use itertools::Itertools; 15 15 use smol_str::SmolStr; 16 - use std::{path::Path, sync::Arc}; 16 + use std::sync::Arc; 17 17 use vec1::Vec1; 18 18 19 + use camino::Utf8Path; 20 + 19 21 const INDENT: isize = 2; 20 22 21 - pub fn pretty(writer: &mut impl Utf8Writer, src: &SmolStr, path: &Path) -> Result<()> { 23 + pub fn pretty(writer: &mut impl Utf8Writer, src: &SmolStr, path: &Utf8Path) -> Result<()> { 22 24 let parsed = crate::parse::parse_module(src).map_err(|error| Error::Parse { 23 25 path: path.to_path_buf(), 24 26 src: src.clone(),
+4 -2
compiler-core/src/format/tests.rs
··· 14 14 macro_rules! assert_format { 15 15 ($src:expr $(,)?) => { 16 16 let mut writer = String::new(); 17 - $crate::format::pretty(&mut writer, &$src.into(), std::path::Path::new("<stdin>")).unwrap(); 17 + $crate::format::pretty(&mut writer, &$src.into(), camino::Utf8Path::new("<stdin>")) 18 + .unwrap(); 18 19 assert_eq!($src, writer); 19 20 }; 20 21 } ··· 23 24 macro_rules! assert_format_rewrite { 24 25 ($src:expr, $output:expr $(,)?) => { 25 26 let mut writer = String::new(); 26 - $crate::format::pretty(&mut writer, &$src.into(), std::path::Path::new("<stdin>")).unwrap(); 27 + $crate::format::pretty(&mut writer, &$src.into(), camino::Utf8Path::new("<stdin>")) 28 + .unwrap(); 27 29 assert_eq!(writer, $output); 28 30 }; 29 31 }
+2 -2
compiler-core/src/hex.rs
··· 1 + use camino::Utf8Path; 1 2 use debug_ignore::DebugIgnore; 2 3 use flate2::read::GzDecoder; 3 4 use futures::future; 4 5 use hexpm::version::Version; 5 - use std::path::Path; 6 6 use tar::Archive; 7 7 8 8 use crate::{ ··· 209 209 210 210 // It would be really nice if this was async but the library is sync 211 211 pub fn extract_package_from_cache(&self, name: &str, version: &Version) -> Result<bool> { 212 - let contents_path = Path::new("contents.tar.gz"); 212 + let contents_path = Utf8Path::new("contents.tar.gz"); 213 213 let destination = self.paths.build_packages_package(name); 214 214 215 215 // If the directory already exists then there's nothing for us to do
+34 -38
compiler-core/src/io.rs
··· 4 4 use async_trait::async_trait; 5 5 use debug_ignore::DebugIgnore; 6 6 use flate2::read::GzDecoder; 7 - use std::{ 8 - fmt::Debug, 9 - io, 10 - path::{Path, PathBuf}, 11 - time::SystemTime, 12 - vec::IntoIter, 13 - }; 7 + use std::{fmt::Debug, io, time::SystemTime, vec::IntoIter}; 14 8 use tar::{Archive, Entry}; 15 9 10 + use camino::{Utf8Path, Utf8PathBuf}; 11 + 16 12 pub trait Reader: std::io::Read { 17 13 /// A wrapper around `std::io::Read` that has Gleam's error handling. 18 14 fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<usize> { ··· 36 32 Error::FileIo { 37 33 action: FileIoAction::WriteTo, 38 34 kind: FileKind::File, 39 - path: PathBuf::from("<in memory>"), 35 + path: Utf8PathBuf::from("<in memory>"), 40 36 err: Some(error.to_string()), 41 37 } 42 38 } ··· 93 89 #[derive(Debug, PartialEq, Eq, Clone)] 94 90 pub struct OutputFile { 95 91 pub content: Content, 96 - pub path: PathBuf, 92 + pub path: Utf8PathBuf, 97 93 } 98 94 99 95 #[derive(Debug)] ··· 130 126 131 127 #[derive(Debug, Clone)] 132 128 pub struct DirEntry { 133 - pub pathbuf: PathBuf, 129 + pub pathbuf: Utf8PathBuf, 134 130 } 135 131 136 132 impl DirEntry { 137 - pub fn from_path<P: AsRef<Path>>(path: P) -> DirEntry { 133 + pub fn from_path<P: AsRef<Utf8Path>>(path: P) -> DirEntry { 138 134 DirEntry { 139 135 pathbuf: path.as_ref().to_path_buf(), 140 136 } 141 137 } 142 138 143 - pub fn from_pathbuf(pathbuf: PathBuf) -> DirEntry { 139 + pub fn from_pathbuf(pathbuf: Utf8PathBuf) -> DirEntry { 144 140 DirEntry { pathbuf } 145 141 } 146 142 147 - pub fn as_path(&self) -> &Path { 143 + pub fn as_path(&self) -> &Utf8Path { 148 144 self.pathbuf.as_path() 149 145 } 150 146 151 - pub fn into_path(self) -> PathBuf { 147 + pub fn into_path(self) -> Utf8PathBuf { 152 148 self.pathbuf 153 149 } 154 150 } ··· 157 153 /// Typically we use an implementation that reads from the file system, 158 154 /// but in tests and in other places other implementations may be used. 159 155 pub trait FileSystemReader { 160 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf>; 161 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf>; 162 - fn read_dir(&self, path: &Path) -> Result<ReadDir>; 163 - fn read(&self, path: &Path) -> Result<String, Error>; 164 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, Error>; 165 - fn reader(&self, path: &Path) -> Result<WrappedReader, Error>; 166 - fn is_file(&self, path: &Path) -> bool; 167 - fn is_directory(&self, path: &Path) -> bool; 168 - fn modification_time(&self, path: &Path) -> Result<SystemTime, Error>; 169 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, Error>; 156 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>; 157 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>; 158 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir>; 159 + fn read(&self, path: &Utf8Path) -> Result<String, Error>; 160 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error>; 161 + fn reader(&self, path: &Utf8Path) -> Result<WrappedReader, Error>; 162 + fn is_file(&self, path: &Utf8Path) -> bool; 163 + fn is_directory(&self, path: &Utf8Path) -> bool; 164 + fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime, Error>; 165 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error>; 170 166 } 171 167 172 168 /// A trait used to run other programs. ··· 176 172 program: &str, 177 173 args: &[String], 178 174 env: &[(&str, String)], 179 - cwd: Option<&Path>, 175 + cwd: Option<&Utf8Path>, 180 176 stdio: Stdio, 181 177 ) -> Result<i32, Error>; 182 178 } ··· 200 196 /// Typically we use an implementation that writes to the file system, 201 197 /// but in tests and in other places other implementations may be used. 202 198 pub trait FileSystemWriter { 203 - fn mkdir(&self, path: &Path) -> Result<(), Error>; 204 - fn write(&self, path: &Path, content: &str) -> Result<(), Error>; 205 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<(), Error>; 206 - fn delete(&self, path: &Path) -> Result<(), Error>; 207 - fn copy(&self, from: &Path, to: &Path) -> Result<(), Error>; 208 - fn copy_dir(&self, from: &Path, to: &Path) -> Result<(), Error>; 209 - fn hardlink(&self, from: &Path, to: &Path) -> Result<(), Error>; 210 - fn symlink_dir(&self, from: &Path, to: &Path) -> Result<(), Error>; 211 - fn delete_file(&self, path: &Path) -> Result<(), Error>; 199 + fn mkdir(&self, path: &Utf8Path) -> Result<(), Error>; 200 + fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error>; 201 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error>; 202 + fn delete(&self, path: &Utf8Path) -> Result<(), Error>; 203 + fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 204 + fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 205 + fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 206 + fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 207 + fn delete_file(&self, path: &Utf8Path) -> Result<(), Error>; 212 208 } 213 209 214 210 #[derive(Debug)] 215 211 /// A wrapper around a Read implementing object that has Gleam's error handling. 216 212 pub struct WrappedReader { 217 - path: PathBuf, 213 + path: Utf8PathBuf, 218 214 inner: DebugIgnore<Box<dyn std::io::Read>>, 219 215 } 220 216 221 217 impl WrappedReader { 222 - pub fn new(path: &Path, inner: Box<dyn std::io::Read>) -> Self { 218 + pub fn new(path: &Utf8Path, inner: Box<dyn std::io::Read>) -> Self { 223 219 Self { 224 220 path: path.to_path_buf(), 225 221 inner: DebugIgnore(inner), ··· 275 271 276 272 fn io_result_unpack( 277 273 &self, 278 - path: &Path, 274 + path: &Utf8Path, 279 275 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>, 280 276 ) -> io::Result<()>; 281 277 282 278 fn unpack( 283 279 &self, 284 - path: &Path, 280 + path: &Utf8Path, 285 281 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>, 286 282 ) -> Result<()> { 287 283 tracing::debug!(path = ?path, "unpacking tar archive");
+34 -28
compiler-core/src/io/memory.rs
··· 1 1 use lazy_static::__Deref; 2 2 3 3 use super::*; 4 - use std::{cell::RefCell, collections::HashMap, ffi::OsStr, rc::Rc, time::Duration}; 4 + use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration}; 5 + 6 + use camino::{Utf8Path, Utf8PathBuf}; 5 7 6 8 // An in memory sharable collection of pretend files that can be used in place 7 9 // of a real file system. It is a shared reference to a set of buffer than can ··· 17 19 // 18 20 #[derive(Clone, Default, Debug, PartialEq, Eq)] 19 21 pub struct InMemoryFileSystem { 20 - files: Rc<RefCell<HashMap<PathBuf, InMemoryFile>>>, 22 + files: Rc<RefCell<HashMap<Utf8PathBuf, InMemoryFile>>>, 21 23 } 22 24 23 25 impl InMemoryFileSystem { ··· 29 31 /// 30 32 /// Panics if this is not the only reference to the underlying files. 31 33 /// 32 - pub fn into_contents(self) -> HashMap<PathBuf, Content> { 34 + pub fn into_contents(self) -> HashMap<Utf8PathBuf, Content> { 33 35 Rc::try_unwrap(self.files) 34 36 .expect("InMemoryFileSystem::into_files called on a clone") 35 37 .into_inner() ··· 38 40 .collect() 39 41 } 40 42 41 - pub fn paths(&self) -> Vec<PathBuf> { 43 + pub fn paths(&self) -> Vec<Utf8PathBuf> { 42 44 self.files.borrow().keys().cloned().collect() 43 45 } 44 46 ··· 47 49 /// 48 50 /// Panics if the file does not exist. 49 51 /// 50 - pub fn set_modification_time(&self, path: &Path, time: SystemTime) { 52 + pub fn set_modification_time(&self, path: &Utf8Path, time: SystemTime) { 51 53 self.files 52 54 .deref() 53 55 .borrow_mut() ··· 56 58 .modification_time = time; 57 59 } 58 60 59 - pub fn try_set_modification_time(&self, path: &Path, time: SystemTime) -> Result<(), Error> { 61 + pub fn try_set_modification_time( 62 + &self, 63 + path: &Utf8Path, 64 + time: SystemTime, 65 + ) -> Result<(), Error> { 60 66 self.files 61 67 .deref() 62 68 .borrow_mut() ··· 73 79 } 74 80 75 81 impl FileSystemWriter for InMemoryFileSystem { 76 - fn delete(&self, path: &Path) -> Result<(), Error> { 82 + fn delete(&self, path: &Utf8Path) -> Result<(), Error> { 77 83 let mut files = self.files.deref().borrow_mut(); 78 84 let _ = files.remove(path); 79 85 Ok(()) 80 86 } 81 87 82 - fn copy(&self, from: &Path, to: &Path) -> Result<(), Error> { 88 + fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { 83 89 self.write_bytes(to, &self.read_bytes(from)?) 84 90 } 85 91 86 - fn copy_dir(&self, _: &Path, _: &Path) -> Result<(), Error> { 92 + fn copy_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 87 93 panic!("unimplemented") // TODO 88 94 } 89 95 90 - fn mkdir(&self, _: &Path) -> Result<(), Error> { 96 + fn mkdir(&self, _: &Utf8Path) -> Result<(), Error> { 91 97 Ok(()) 92 98 } 93 99 94 - fn hardlink(&self, _: &Path, _: &Path) -> Result<(), Error> { 100 + fn hardlink(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 95 101 panic!("unimplemented") // TODO 96 102 } 97 103 98 - fn symlink_dir(&self, _: &Path, _: &Path) -> Result<(), Error> { 104 + fn symlink_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 99 105 panic!("unimplemented") // TODO 100 106 } 101 107 102 - fn delete_file(&self, path: &Path) -> Result<(), Error> { 108 + fn delete_file(&self, path: &Utf8Path) -> Result<(), Error> { 103 109 let _ = self.files.deref().borrow_mut().remove(path); 104 110 Ok(()) 105 111 } 106 112 107 - fn write(&self, path: &Path, content: &str) -> Result<(), Error> { 113 + fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { 108 114 self.write_bytes(path, content.as_bytes()) 109 115 } 110 116 111 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<(), Error> { 117 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { 112 118 let mut file = InMemoryFile::default(); 113 119 _ = io::Write::write(&mut file, content).expect("channel buffer write"); 114 120 _ = self ··· 121 127 } 122 128 123 129 impl FileSystemReader for InMemoryFileSystem { 124 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, Error> { 130 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error> { 125 131 Ok(path.to_path_buf()) 126 132 } 127 133 128 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf> { 134 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 129 135 self.files 130 136 .deref() 131 137 .borrow() 132 138 .iter() 133 139 .map(|(file_path, _)| file_path.to_path_buf()) 134 140 .filter(|file_path| file_path.starts_with(dir)) 135 - .filter(|file_path| file_path.extension() == Some(OsStr::new("gleam"))) 141 + .filter(|file_path| file_path.extension() == Some("gleam")) 136 142 .collect() 137 143 } 138 144 139 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf> { 145 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 140 146 self.files 141 147 .deref() 142 148 .borrow() 143 149 .iter() 144 150 .map(|(file_path, _)| file_path.to_path_buf()) 145 151 .filter(|file_path| file_path.starts_with(dir)) 146 - .filter(|file_path| file_path.extension() == Some(OsStr::new("cache"))) 152 + .filter(|file_path| file_path.extension() == Some("cache")) 147 153 .collect() 148 154 } 149 155 150 - fn read(&self, path: &Path) -> Result<String, Error> { 156 + fn read(&self, path: &Utf8Path) -> Result<String, Error> { 151 157 let path = path.to_path_buf(); 152 158 let files = self.files.deref().borrow(); 153 159 let file = files.get(&path).ok_or_else(|| Error::FileIo { ··· 166 172 Ok(unicode) 167 173 } 168 174 169 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, Error> { 175 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error> { 170 176 let path = path.to_path_buf(); 171 177 let files = self.files.deref().borrow(); 172 178 let file = files.get(&path).ok_or_else(|| Error::FileIo { ··· 179 185 Ok(bytes) 180 186 } 181 187 182 - fn is_file(&self, path: &Path) -> bool { 188 + fn is_file(&self, path: &Utf8Path) -> bool { 183 189 self.files.deref().borrow().contains_key(path) 184 190 } 185 191 186 - fn is_directory(&self, path: &Path) -> bool { 192 + fn is_directory(&self, path: &Utf8Path) -> bool { 187 193 self.files 188 194 .deref() 189 195 .borrow() ··· 191 197 .any(|file_path| file_path.starts_with(path)) 192 198 } 193 199 194 - fn reader(&self, _path: &Path) -> Result<WrappedReader, Error> { 200 + fn reader(&self, _path: &Utf8Path) -> Result<WrappedReader, Error> { 195 201 // TODO 196 202 unreachable!("Memory reader unimplemented") 197 203 } 198 204 199 - fn read_dir(&self, path: &Path) -> Result<ReadDir> { 205 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir> { 200 206 let read_dir = ReadDir::from_iter( 201 207 self.files 202 208 .deref() ··· 211 217 Ok(read_dir) 212 218 } 213 219 214 - fn modification_time(&self, path: &Path) -> Result<SystemTime, Error> { 220 + fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime, Error> { 215 221 let files = self.files.deref().borrow(); 216 222 let file = files.get(path).ok_or_else(|| Error::FileIo { 217 223 kind: FileKind::File, ··· 283 289 _program: &str, 284 290 _args: &[String], 285 291 _env: &[(&str, String)], 286 - _cwd: Option<&Path>, 292 + _cwd: Option<&Utf8Path>, 287 293 _stdio: Stdio, 288 294 ) -> Result<i32, Error> { 289 295 Ok(0) // Always succeed.
+3 -3
compiler-core/src/javascript.rs
··· 5 5 mod tests; 6 6 mod typescript; 7 7 8 - use std::path::Path; 8 + use camino::Utf8Path; 9 9 10 10 use crate::type_::PRELUDE_MODULE_NAME; 11 11 use crate::{ ··· 522 522 pub fn module( 523 523 module: &TypedModule, 524 524 line_numbers: &LineNumbers, 525 - path: &Path, 525 + path: &Utf8Path, 526 526 src: &SmolStr, 527 527 ) -> Result<String, crate::Error> { 528 528 let document = Generator::new(line_numbers, module) ··· 537 537 538 538 pub fn ts_declaration( 539 539 module: &TypedModule, 540 - path: &Path, 540 + path: &Utf8Path, 541 541 src: &SmolStr, 542 542 ) -> Result<String, crate::Error> { 543 543 let document = typescript::TypeScriptGenerator::new(module)
+3 -3
compiler-core/src/javascript/tests.rs
··· 4 4 uid::UniqueIdGenerator, 5 5 warning::TypeWarningEmitter, 6 6 }; 7 - use std::path::Path; 7 + use camino::Utf8Path; 8 8 9 9 mod assignments; 10 10 mod bit_strings; ··· 118 118 pub fn compile_js(src: &str, dep: Option<(&str, &str, &str)>) -> String { 119 119 let ast = compile(src, dep); 120 120 let line_numbers = LineNumbers::new(src); 121 - module(&ast, &line_numbers, Path::new(""), &"".into()).unwrap() 121 + module(&ast, &line_numbers, Utf8Path::new(""), &"".into()).unwrap() 122 122 } 123 123 124 124 pub fn compile_ts(src: &str, dep: Option<(&str, &str, &str)>) -> String { 125 125 let ast = compile(src, dep); 126 - ts_declaration(&ast, Path::new(""), &src.into()).unwrap() 126 + ts_declaration(&ast, Utf8Path::new(""), &src.into()).unwrap() 127 127 }
+4 -2
compiler-core/src/language_server/compiler.rs
··· 14 14 warning::VectorWarningEmitterIO, 15 15 Error, Result, Warning, 16 16 }; 17 - use std::{collections::HashMap, path::PathBuf, sync::Arc}; 17 + use std::{collections::HashMap, sync::Arc}; 18 + 19 + use camino::Utf8PathBuf; 18 20 19 21 /// A wrapper around the project compiler which makes it possible to repeatedly 20 22 /// recompile the top level package, reusing the information about the already ··· 91 93 }) 92 94 } 93 95 94 - pub fn compile(&mut self) -> Result<Vec<PathBuf>, Error> { 96 + pub fn compile(&mut self) -> Result<Vec<Utf8PathBuf>, Error> { 95 97 // Lock the build directory to ensure to ensure we are the only one compiling 96 98 let _lock_guard = self.locker.lock_for_build(); 97 99
+4 -4
compiler-core/src/language_server/engine.rs
··· 11 11 type_::{pretty::Printer, PreludeType, ValueConstructorVariant}, 12 12 Error, Result, Warning, 13 13 }; 14 + use camino::Utf8PathBuf; 14 15 use lsp_types::{self as lsp, Hover, HoverContents, MarkedString, Url}; 15 16 use smol_str::SmolStr; 16 - use std::path::PathBuf; 17 17 use strum::IntoEnumIterator; 18 18 19 19 use super::{src_span_to_lsp_range, DownloadDependencies, MakeLocker}; ··· 28 28 #[derive(Debug, PartialEq, Eq)] 29 29 pub enum Compilation { 30 30 /// Compilation was attempted and succeeded for these modules. 31 - Yes(Vec<PathBuf>), 31 + Yes(Vec<Utf8PathBuf>), 32 32 /// Compilation was not attempted for this operation. 33 33 No, 34 34 } ··· 43 43 /// discarded and reloaded to handle any changes to dependencies. 44 44 pub(crate) compiler: LspProjectCompiler<FileSystemProxy<IO>>, 45 45 46 - modules_compiled_since_last_feedback: Vec<PathBuf>, 46 + modules_compiled_since_last_feedback: Vec<Utf8PathBuf>, 47 47 compiled_since_last_feedback: bool, 48 48 49 49 // Used to publish progress notifications to the client without waiting for ··· 283 283 let path = uri.to_file_path().expect("URL file"); 284 284 285 285 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 286 - let path: PathBuf = uri.path().into(); 286 + let path: Utf8PathBuf = uri.path().into(); 287 287 288 288 let components = path 289 289 .strip_prefix(self.paths.root())
+20 -22
compiler-core/src/language_server/feedback.rs
··· 1 1 use crate::{diagnostic::Diagnostic, Error, Warning}; 2 - use std::{ 3 - collections::{HashMap, HashSet}, 4 - path::PathBuf, 5 - }; 2 + use std::collections::{HashMap, HashSet}; 3 + 4 + use camino::Utf8PathBuf; 6 5 7 6 use super::engine::Compilation; 8 7 9 8 #[derive(Debug, Default, PartialEq, Eq)] 10 9 pub struct Feedback { 11 - pub diagnostics: HashMap<PathBuf, Vec<Diagnostic>>, 10 + pub diagnostics: HashMap<Utf8PathBuf, Vec<Diagnostic>>, 12 11 pub messages: Vec<Diagnostic>, 13 12 } 14 13 15 14 impl Feedback { 16 15 /// Set the diagnostics for a file to an empty vector. This will overwrite 17 16 /// any existing diagnostics on the client. 18 - pub fn unset_existing_diagnostics(&mut self, path: PathBuf) { 17 + pub fn unset_existing_diagnostics(&mut self, path: Utf8PathBuf) { 19 18 _ = self.diagnostics.insert(path, vec![]); 20 19 } 21 20 22 - pub fn append_diagnostic(&mut self, path: PathBuf, diagnostic: Diagnostic) { 21 + pub fn append_diagnostic(&mut self, path: Utf8PathBuf, diagnostic: Diagnostic) { 23 22 self.diagnostics 24 23 .entry(path) 25 24 .or_insert_with(Vec::new) ··· 44 43 /// 45 44 #[derive(Debug, Default)] 46 45 pub struct FeedbackBookKeeper { 47 - files_with_warnings: HashSet<PathBuf>, 48 - files_with_errors: HashSet<PathBuf>, 46 + files_with_warnings: HashSet<Utf8PathBuf>, 47 + files_with_errors: HashSet<Utf8PathBuf>, 49 48 } 50 49 51 50 impl FeedbackBookKeeper { ··· 137 136 138 137 #[cfg(test)] 139 138 mod tests { 140 - use std::path::Path; 141 139 142 140 use super::*; 143 141 use crate::{ ··· 149 147 #[test] 150 148 fn feedback() { 151 149 let mut book_keeper = FeedbackBookKeeper::default(); 152 - let file1 = PathBuf::from("src/file1.gleam"); 153 - let file2 = PathBuf::from("src/file2.gleam"); 154 - let file3 = PathBuf::from("src/file3.gleam"); 150 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 151 + let file2 = Utf8PathBuf::from("src/file2.gleam"); 152 + let file3 = Utf8PathBuf::from("src/file3.gleam"); 155 153 156 154 let warning1 = Warning::Type { 157 155 path: file1.clone(), ··· 212 210 // location. 213 211 214 212 let mut book_keeper = FeedbackBookKeeper::default(); 215 - let file1 = PathBuf::from("src/file1.gleam"); 213 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 216 214 217 215 let warning1 = Warning::Type { 218 216 path: file1.clone(), ··· 245 243 // location. 246 244 247 245 let mut book_keeper = FeedbackBookKeeper::default(); 248 - let file1 = PathBuf::from("src/file1.gleam"); 249 - let file3 = PathBuf::from("src/file2.gleam"); 246 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 247 + let file3 = Utf8PathBuf::from("src/file2.gleam"); 250 248 251 249 let warning1 = Warning::Type { 252 250 path: file1.clone(), ··· 312 310 // when a successful compilation occurs. 313 311 314 312 let mut book_keeper = FeedbackBookKeeper::default(); 315 - let file1 = PathBuf::from("src/file1.gleam"); 316 - let file2 = PathBuf::from("src/file2.gleam"); 313 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 314 + let file2 = Utf8PathBuf::from("src/file2.gleam"); 317 315 318 316 let error = Error::Parse { 319 317 path: file1.clone(), ··· 353 351 #[test] 354 352 fn second_failure_unsets_previous_error() { 355 353 let mut book_keeper = FeedbackBookKeeper::default(); 356 - let file1 = PathBuf::from("src/file1.gleam"); 357 - let file2 = PathBuf::from("src/file2.gleam"); 354 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 355 + let file2 = Utf8PathBuf::from("src/file2.gleam"); 358 356 359 - let error = |file: &Path| Error::Parse { 357 + let error = |file: &camino::Utf8Path| Error::Parse { 360 358 path: file.to_path_buf(), 361 359 src: "blah".into(), 362 360 error: ParseError { ··· 397 395 #[test] 398 396 fn successful_non_compilation_does_not_remove_error_diagnostic() { 399 397 let mut book_keeper = FeedbackBookKeeper::default(); 400 - let file1 = PathBuf::from("src/file1.gleam"); 398 + let file1 = Utf8PathBuf::from("src/file1.gleam"); 401 399 402 400 let error = Error::Parse { 403 401 path: file1.clone(),
+25 -26
compiler-core/src/language_server/files.rs
··· 1 - use std::{ 2 - path::{Path, PathBuf}, 3 - time::SystemTime, 4 - }; 1 + use std::time::SystemTime; 5 2 6 3 use debug_ignore::DebugIgnore; 7 4 ··· 12 9 }, 13 10 Result, 14 11 }; 12 + 13 + use camino::{Utf8Path, Utf8PathBuf}; 15 14 16 15 // A proxy intended for `LanguageServer` to use when files are modified in 17 16 // memory but not yet saved to disc by the client. ··· 42 41 &self.io 43 42 } 44 43 45 - pub fn write_mem_cache(&mut self, path: &Path, content: &str) -> Result<()> { 44 + pub fn write_mem_cache(&mut self, path: &Utf8Path, content: &str) -> Result<()> { 46 45 let write_result = self.edit_cache.write(path, content); 47 46 self.edit_cache 48 47 .try_set_modification_time(path, SystemTime::now())?; 49 48 write_result 50 49 } 51 50 52 - pub fn delete_mem_cache(&self, path: &Path) -> Result<()> { 51 + pub fn delete_mem_cache(&self, path: &Utf8Path) -> Result<()> { 53 52 self.edit_cache.delete(path) 54 53 } 55 54 } ··· 59 58 where 60 59 IO: FileSystemWriter, 61 60 { 62 - fn mkdir(&self, path: &Path) -> Result<()> { 61 + fn mkdir(&self, path: &Utf8Path) -> Result<()> { 63 62 self.io.mkdir(path) 64 63 } 65 64 66 - fn write(&self, path: &Path, content: &str) -> Result<()> { 65 + fn write(&self, path: &Utf8Path, content: &str) -> Result<()> { 67 66 self.io.write(path, content) 68 67 } 69 68 70 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<()> { 69 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<()> { 71 70 self.io.write_bytes(path, content) 72 71 } 73 72 74 - fn delete(&self, path: &Path) -> Result<()> { 73 + fn delete(&self, path: &Utf8Path) -> Result<()> { 75 74 self.io.delete(path) 76 75 } 77 76 78 - fn copy(&self, from: &Path, to: &Path) -> Result<()> { 77 + fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 79 78 self.io.copy(from, to) 80 79 } 81 80 82 - fn copy_dir(&self, from: &Path, to: &Path) -> Result<()> { 81 + fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 83 82 self.io.copy_dir(from, to) 84 83 } 85 84 86 - fn hardlink(&self, from: &Path, to: &Path) -> Result<()> { 85 + fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 87 86 self.io.hardlink(from, to) 88 87 } 89 88 90 - fn symlink_dir(&self, from: &Path, to: &Path) -> Result<()> { 89 + fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 91 90 self.io.symlink_dir(from, to) 92 91 } 93 92 94 - fn delete_file(&self, path: &Path) -> Result<()> { 93 + fn delete_file(&self, path: &Utf8Path) -> Result<()> { 95 94 self.io.delete_file(path) 96 95 } 97 96 } ··· 100 99 where 101 100 IO: FileSystemReader, 102 101 { 103 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf> { 102 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 104 103 self.io.gleam_source_files(dir) 105 104 } 106 105 107 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf> { 106 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 108 107 self.io.gleam_cache_files(dir) 109 108 } 110 109 111 - fn read_dir(&self, path: &Path) -> Result<ReadDir> { 110 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir> { 112 111 self.io.read_dir(path) 113 112 } 114 113 115 - fn read(&self, path: &Path) -> Result<String> { 114 + fn read(&self, path: &Utf8Path) -> Result<String> { 116 115 match self.edit_cache.read(path) { 117 116 result @ Ok(_) => result, 118 117 Err(_) => self.io.read(path), 119 118 } 120 119 } 121 120 122 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>> { 121 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>> { 123 122 match self.edit_cache.read_bytes(path) { 124 123 result @ Ok(_) => result, 125 124 Err(_) => self.io.read_bytes(path), 126 125 } 127 126 } 128 127 129 - fn reader(&self, path: &Path) -> Result<WrappedReader> { 128 + fn reader(&self, path: &Utf8Path) -> Result<WrappedReader> { 130 129 self.io.reader(path) 131 130 } 132 131 133 132 // Cache overides existence of file 134 - fn is_file(&self, path: &Path) -> bool { 133 + fn is_file(&self, path: &Utf8Path) -> bool { 135 134 self.edit_cache.is_file(path) || self.io.is_file(path) 136 135 } 137 136 138 137 // Cache overides existence of directory 139 - fn is_directory(&self, path: &Path) -> bool { 138 + fn is_directory(&self, path: &Utf8Path) -> bool { 140 139 self.edit_cache.is_directory(path) || self.io.is_directory(path) 141 140 } 142 141 143 - fn modification_time(&self, path: &Path) -> Result<SystemTime> { 142 + fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime> { 144 143 match self.edit_cache.modification_time(path) { 145 144 result @ Ok(_) => result, 146 145 Err(_) => self.io.modification_time(path), 147 146 } 148 147 } 149 148 150 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, crate::Error> { 149 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, crate::Error> { 151 150 self.io.canonicalise(path) 152 151 } 153 152 } ··· 161 160 _program: &str, 162 161 _args: &[String], 163 162 _env: &[(&str, String)], 164 - _cwd: Option<&Path>, 163 + _cwd: Option<&Utf8Path>, 165 164 _stdio: Stdio, 166 165 ) -> Result<i32> { 167 166 panic!("The language server is not permitted to create subprocesses")
+29 -27
compiler-core/src/language_server/router.rs
··· 8 8 paths::ProjectPaths, 9 9 Error, Result, 10 10 }; 11 - use std::{ 12 - collections::{hash_map::Entry, HashMap}, 13 - path::{Path, PathBuf}, 14 - }; 11 + use std::collections::{hash_map::Entry, HashMap}; 12 + 13 + use camino::{Utf8Path, Utf8PathBuf}; 15 14 16 15 use super::feedback::FeedbackBookKeeper; 17 16 ··· 25 24 #[derive(Debug)] 26 25 pub struct Router<IO, Reporter> { 27 26 io: FileSystemProxy<IO>, 28 - engines: HashMap<PathBuf, Project<IO, Reporter>>, 27 + engines: HashMap<Utf8PathBuf, Project<IO, Reporter>>, 29 28 progress_reporter: Reporter, 30 29 } 31 30 ··· 49 48 } 50 49 } 51 50 52 - pub fn project_for_path(&mut self, path: &Path) -> Result<Option<&mut Project<IO, Reporter>>> { 51 + pub fn project_for_path( 52 + &mut self, 53 + path: &Utf8Path, 54 + ) -> Result<Option<&mut Project<IO, Reporter>>> { 53 55 let path = match find_gleam_project_parent(&self.io, path) { 54 56 Some(x) => x, 55 57 None => return Ok(None), ··· 84 86 Ok(Some(entry.insert(project))) 85 87 } 86 88 87 - pub fn delete_engine_for_path(&mut self, path: &Path) { 89 + pub fn delete_engine_for_path(&mut self, path: &Utf8Path) { 88 90 if let Some(path) = find_gleam_project_parent(&self.io, path) { 89 91 _ = self.engines.remove(&path); 90 92 } ··· 96 98 /// 97 99 /// The file must be in either the `src` or `test` directory if it is not a 98 100 /// `.gleam` file. 99 - fn find_gleam_project_parent<IO>(io: &IO, path: &Path) -> Option<PathBuf> 101 + fn find_gleam_project_parent<IO>(io: &IO, path: &Utf8Path) -> Option<Utf8PathBuf> 100 102 where 101 103 IO: FileSystemReader, 102 104 { ··· 135 137 #[test] 136 138 fn root() { 137 139 let io = InMemoryFileSystem::new(); 138 - assert_eq!(find_gleam_project_parent(&io, Path::new("/")), None); 140 + assert_eq!(find_gleam_project_parent(&io, Utf8Path::new("/")), None); 139 141 } 140 142 141 143 #[test] 142 144 fn outside_a_project() { 143 145 let io = InMemoryFileSystem::new(); 144 146 assert_eq!( 145 - find_gleam_project_parent(&io, Path::new("/app/src/one.gleam")), 147 + find_gleam_project_parent(&io, Utf8Path::new("/app/src/one.gleam")), 146 148 None 147 149 ); 148 150 } ··· 150 152 #[test] 151 153 fn gleam_toml_itself() { 152 154 let io = InMemoryFileSystem::new(); 153 - io.write(Path::new("/app/gleam.toml"), "").unwrap(); 155 + io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); 154 156 assert_eq!( 155 - find_gleam_project_parent(&io, Path::new("/app/gleam.toml")), 156 - Some(PathBuf::from("/app")) 157 + find_gleam_project_parent(&io, Utf8Path::new("/app/gleam.toml")), 158 + Some(Utf8PathBuf::from("/app")) 157 159 ); 158 160 } 159 161 160 162 #[test] 161 163 fn test_module() { 162 164 let io = InMemoryFileSystem::new(); 163 - io.write(Path::new("/app/gleam.toml"), "").unwrap(); 165 + io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); 164 166 assert_eq!( 165 - find_gleam_project_parent(&io, Path::new("/app/test/one/two/three.gleam")), 166 - Some(PathBuf::from("/app")) 167 + find_gleam_project_parent(&io, Utf8Path::new("/app/test/one/two/three.gleam")), 168 + Some(Utf8PathBuf::from("/app")) 167 169 ); 168 170 } 169 171 170 172 #[test] 171 173 fn src_module() { 172 174 let io = InMemoryFileSystem::new(); 173 - io.write(Path::new("/app/gleam.toml"), "").unwrap(); 175 + io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); 174 176 assert_eq!( 175 - find_gleam_project_parent(&io, Path::new("/app/src/one/two/three.gleam")), 176 - Some(PathBuf::from("/app")) 177 + find_gleam_project_parent(&io, Utf8Path::new("/app/src/one/two/three.gleam")), 178 + Some(Utf8PathBuf::from("/app")) 177 179 ); 178 180 } 179 181 ··· 181 183 #[test] 182 184 fn module_in_project_but_not_src_or_test() { 183 185 let io = InMemoryFileSystem::new(); 184 - io.write(Path::new("/app/gleam.toml"), "").unwrap(); 186 + io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); 185 187 assert_eq!( 186 - find_gleam_project_parent(&io, Path::new("/app/other/one/two/three.gleam")), 188 + find_gleam_project_parent(&io, Utf8Path::new("/app/other/one/two/three.gleam")), 187 189 None, 188 190 ); 189 191 } ··· 191 193 #[test] 192 194 fn nested_projects() { 193 195 let io = InMemoryFileSystem::new(); 194 - io.write(Path::new("/app/gleam.toml"), "").unwrap(); 195 - io.write(Path::new("/app/examples/wibble/gleam.toml"), "") 196 + io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); 197 + io.write(Utf8Path::new("/app/examples/wibble/gleam.toml"), "") 196 198 .unwrap(); 197 199 assert_eq!( 198 - find_gleam_project_parent(&io, Path::new("/app/src/one.gleam")), 199 - Some(PathBuf::from("/app")) 200 + find_gleam_project_parent(&io, Utf8Path::new("/app/src/one.gleam")), 201 + Some(Utf8PathBuf::from("/app")) 200 202 ); 201 203 assert_eq!( 202 - find_gleam_project_parent(&io, Path::new("/app/examples/wibble/src/one.gleam")), 203 - Some(PathBuf::from("/app/examples/wibble")) 204 + find_gleam_project_parent(&io, Utf8Path::new("/app/examples/wibble/src/one.gleam")), 205 + Some(Utf8PathBuf::from("/app/examples/wibble")) 204 206 ); 205 207 } 206 208 }
+11 -8
compiler-core/src/language_server/server.rs
··· 24 24 InitializeParams, PublishDiagnosticsParams, 25 25 }; 26 26 use serde_json::Value as Json; 27 - use std::{collections::HashMap, path::PathBuf}; 27 + use std::collections::HashMap; 28 + 29 + use camino::Utf8PathBuf; 28 30 29 31 use super::progress::ConnectionProgressReporter; 30 32 ··· 185 187 self.publish_messages(feedback.messages); 186 188 } 187 189 188 - fn publish_diagnostics(&self, diagnostics: HashMap<PathBuf, Vec<Diagnostic>>) { 190 + fn publish_diagnostics(&self, diagnostics: HashMap<Utf8PathBuf, Vec<Diagnostic>>) { 189 191 for (path, diagnostics) in diagnostics { 190 192 let diagnostics = diagnostics 191 193 .into_iter() ··· 277 279 278 280 fn respond_with_engine<T, Handler>( 279 281 &mut self, 280 - path: PathBuf, 282 + path: Utf8PathBuf, 281 283 handler: Handler, 282 284 ) -> (Json, Feedback) 283 285 where ··· 314 316 315 317 fn notified_with_engine( 316 318 &mut self, 317 - path: PathBuf, 319 + path: Utf8PathBuf, 318 320 handler: impl FnOnce( 319 321 &mut LanguageServerEngine<IO, ConnectionProgressReporter<'a>>, 320 322 ) -> engine::Response<()>, ··· 577 579 } 578 580 } 579 581 580 - fn path_to_uri(path: PathBuf) -> Url { 582 + fn path_to_uri(path: Utf8PathBuf) -> Url { 581 583 let mut file: String = "file://".into(); 582 584 file.push_str(&path.as_os_str().to_string_lossy()); 583 585 Url::parse(&file).expect("path_to_uri URL parse") 584 586 } 585 587 586 - fn path(uri: &Url) -> PathBuf { 588 + fn path(uri: &Url) -> Utf8PathBuf { 587 589 // The to_file_path method is available on these platforms 588 590 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 589 - return uri.to_file_path().expect("URL file"); 591 + return Utf8PathBuf::from_path_buf(uri.to_file_path().expect("URL file")) 592 + .expect("Non Utf8 Path"); 590 593 591 594 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 592 - return uri.path().into(); 595 + return Utf8PathBuf::from_path_buf(uri.path().into()).expect("Non Utf8 Path"); 593 596 }
+1 -1
compiler-core/src/language_server/tests/completion.rs
··· 28 28 let response = engine.compile_please(); 29 29 assert!(response.result.is_ok()); 30 30 31 - let path = PathBuf::from(if cfg!(target_family = "windows") { 31 + let path = Utf8PathBuf::from(if cfg!(target_family = "windows") { 32 32 r#"\\?\C:\src\app.gleam"# 33 33 } else { 34 34 "/src/app.gleam"
+26 -25
compiler-core/src/language_server/tests/mod.rs
··· 3 3 4 4 use std::{ 5 5 collections::HashMap, 6 - path::{Path, PathBuf}, 7 6 sync::{Arc, Mutex}, 8 7 time::SystemTime, 9 8 }; 10 9 11 10 use hexpm::version::Version; 11 + 12 + use camino::{Utf8Path, Utf8PathBuf}; 12 13 13 14 use crate::{ 14 15 config::PackageConfig, ··· 58 59 Arc::try_unwrap(self.actions).unwrap().into_inner().unwrap() 59 60 } 60 61 61 - pub fn src_module(&self, name: &str, code: &str) -> PathBuf { 62 + pub fn src_module(&self, name: &str, code: &str) -> Utf8PathBuf { 62 63 let src_dir = self.paths.src_directory(); 63 64 let path = src_dir.join(name).with_extension("gleam"); 64 65 self.module(&path, code); 65 66 path 66 67 } 67 68 68 - pub fn test_module(&self, name: &str, code: &str) -> PathBuf { 69 + pub fn test_module(&self, name: &str, code: &str) -> Utf8PathBuf { 69 70 let test_dir = self.paths.test_directory(); 70 71 let path = test_dir.join(name).with_extension("gleam"); 71 72 self.module(&path, code); 72 73 path 73 74 } 74 75 75 - pub fn dep_module(&self, dep: &str, name: &str, code: &str) -> PathBuf { 76 + pub fn dep_module(&self, dep: &str, name: &str, code: &str) -> Utf8PathBuf { 76 77 let dep_dir = self.paths.root().join(dep).join("src"); 77 78 let path = dep_dir.join(name).with_extension("gleam"); 78 79 self.module(&path, code); 79 80 path 80 81 } 81 82 82 - fn module(&self, path: &Path, code: &str) { 83 + fn module(&self, path: &Utf8Path, code: &str) { 83 84 self.io.write(path, code).unwrap(); 84 85 self.io.set_modification_time(path, SystemTime::now()); 85 86 } ··· 90 91 } 91 92 92 93 impl FileSystemReader for LanguageServerTestIO { 93 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf> { 94 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 94 95 self.io.gleam_source_files(dir) 95 96 } 96 97 97 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf> { 98 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 98 99 self.io.gleam_cache_files(dir) 99 100 } 100 101 101 - fn read_dir(&self, path: &Path) -> Result<ReadDir> { 102 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir> { 102 103 self.io.read_dir(path) 103 104 } 104 105 105 - fn read(&self, path: &Path) -> Result<String> { 106 + fn read(&self, path: &Utf8Path) -> Result<String> { 106 107 self.io.read(path) 107 108 } 108 109 109 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>> { 110 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>> { 110 111 self.io.read_bytes(path) 111 112 } 112 113 113 - fn reader(&self, path: &Path) -> Result<WrappedReader> { 114 + fn reader(&self, path: &Utf8Path) -> Result<WrappedReader> { 114 115 self.io.reader(path) 115 116 } 116 117 117 - fn is_file(&self, path: &Path) -> bool { 118 + fn is_file(&self, path: &Utf8Path) -> bool { 118 119 self.io.is_file(path) 119 120 } 120 121 121 - fn is_directory(&self, path: &Path) -> bool { 122 + fn is_directory(&self, path: &Utf8Path) -> bool { 122 123 self.io.is_directory(path) 123 124 } 124 125 125 - fn modification_time(&self, path: &Path) -> Result<SystemTime> { 126 + fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime> { 126 127 self.io.modification_time(path) 127 128 } 128 129 129 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, crate::Error> { 130 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, crate::Error> { 130 131 self.io.canonicalise(path) 131 132 } 132 133 } 133 134 134 135 impl FileSystemWriter for LanguageServerTestIO { 135 - fn mkdir(&self, path: &Path) -> Result<()> { 136 + fn mkdir(&self, path: &Utf8Path) -> Result<()> { 136 137 self.io.mkdir(path) 137 138 } 138 139 139 - fn delete(&self, path: &Path) -> Result<()> { 140 + fn delete(&self, path: &Utf8Path) -> Result<()> { 140 141 self.io.delete(path) 141 142 } 142 143 143 - fn copy(&self, from: &Path, to: &Path) -> Result<()> { 144 + fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 144 145 self.io.copy(from, to) 145 146 } 146 147 147 - fn copy_dir(&self, from: &Path, to: &Path) -> Result<()> { 148 + fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 148 149 self.io.copy_dir(from, to) 149 150 } 150 151 151 - fn hardlink(&self, from: &Path, to: &Path) -> Result<()> { 152 + fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 152 153 self.io.hardlink(from, to) 153 154 } 154 155 155 - fn symlink_dir(&self, from: &Path, to: &Path) -> Result<()> { 156 + fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { 156 157 self.io.symlink_dir(from, to) 157 158 } 158 159 159 - fn delete_file(&self, path: &Path) -> Result<()> { 160 + fn delete_file(&self, path: &Utf8Path) -> Result<()> { 160 161 self.io.delete_file(path) 161 162 } 162 163 163 - fn write(&self, path: &Path, content: &str) -> Result<(), crate::Error> { 164 + fn write(&self, path: &Utf8Path, content: &str) -> Result<(), crate::Error> { 164 165 self.io.write(path, content) 165 166 } 166 167 167 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<(), crate::Error> { 168 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), crate::Error> { 168 169 self.io.write_bytes(path, content) 169 170 } 170 171 } ··· 185 186 program: &str, 186 187 args: &[String], 187 188 env: &[(&str, String)], 188 - cwd: Option<&Path>, 189 + cwd: Option<&Utf8Path>, 189 190 stdio: crate::io::Stdio, 190 191 ) -> Result<i32> { 191 192 panic!(
+3 -3
compiler-core/src/manifest.rs
··· 1 1 use std::collections::HashMap; 2 - use std::path::PathBuf; 3 2 4 3 use crate::requirement::Requirement; 5 4 use crate::Result; 5 + use camino::Utf8PathBuf; 6 6 use hexpm::version::Version; 7 7 use itertools::Itertools; 8 8 use smol_str::SmolStr; ··· 92 92 } 93 93 ManifestPackageSource::Local { path } => { 94 94 buffer.push_str(r#", source = "local", path = ""#); 95 - buffer.push_str(path.to_str().expect("local path non utf-8")); 95 + buffer.push_str(path.as_str()); 96 96 buffer.push('"'); 97 97 } 98 98 }; ··· 309 309 #[serde(rename = "git")] 310 310 Git { repo: SmolStr, commit: SmolStr }, 311 311 #[serde(rename = "local")] 312 - Local { path: PathBuf }, // should be the canonical path 312 + Local { path: Utf8PathBuf }, // should be the canonical path 313 313 } 314 314 315 315 fn ordered_map<S, K, V>(value: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
+3 -3
compiler-core/src/parse/tests.rs
··· 1 1 use crate::ast::SrcSpan; 2 2 use crate::parse::error::{LexicalError, LexicalErrorType, ParseError, ParseErrorType}; 3 - use std::path::PathBuf; 3 + use camino::Utf8PathBuf; 4 4 5 5 use pretty_assertions::assert_eq; 6 6 ··· 33 33 let result = crate::parse::parse_module(src).expect_err("should not parse"); 34 34 let error = crate::error::Error::Parse { 35 35 src: src.into(), 36 - path: PathBuf::from("/src/parse/error.gleam"), 36 + path: Utf8PathBuf::from("/src/parse/error.gleam"), 37 37 error: result, 38 38 }; 39 39 error.pretty_string() ··· 43 43 let result = crate::parse::parse_statement_sequence(src).expect_err("should not parse"); 44 44 let error = crate::error::Error::Parse { 45 45 src: src.into(), 46 - path: PathBuf::from("/src/parse/error.gleam"), 46 + path: Utf8PathBuf::from("/src/parse/error.gleam"), 47 47 error: result, 48 48 }; 49 49 error.pretty_string()
+35 -32
compiler-core/src/paths.rs
··· 1 - use std::path::{Path, PathBuf}; 2 - 3 1 use crate::build::{Mode, Target}; 2 + 3 + use camino::{Utf8Path, Utf8PathBuf}; 4 4 5 5 pub const ARTEFACT_DIRECTORY_NAME: &str = "_gleam_artefacts"; 6 6 7 7 #[derive(Debug, Clone)] 8 8 pub struct ProjectPaths { 9 - root: PathBuf, 9 + root: Utf8PathBuf, 10 10 } 11 11 12 12 impl ProjectPaths { 13 - pub fn new(root: PathBuf) -> Self { 13 + pub fn new(root: Utf8PathBuf) -> Self { 14 14 Self { root } 15 15 } 16 16 ··· 21 21 "/" 22 22 }; 23 23 24 - Self::new(PathBuf::from(path)) 24 + Self::new(Utf8PathBuf::from(path)) 25 25 } 26 26 27 - pub fn root(&self) -> &Path { 27 + pub fn root(&self) -> &Utf8Path { 28 28 &self.root 29 29 } 30 30 31 - pub fn root_config(&self) -> PathBuf { 31 + pub fn root_config(&self) -> Utf8PathBuf { 32 32 self.root.join("gleam.toml") 33 33 } 34 34 35 - pub fn readme(&self) -> PathBuf { 35 + pub fn readme(&self) -> Utf8PathBuf { 36 36 self.root.join("README.md") 37 37 } 38 38 39 - pub fn manifest(&self) -> PathBuf { 39 + pub fn manifest(&self) -> Utf8PathBuf { 40 40 self.root.join("manifest.toml") 41 41 } 42 42 43 - pub fn src_directory(&self) -> PathBuf { 43 + pub fn src_directory(&self) -> Utf8PathBuf { 44 44 self.root.join("src") 45 45 } 46 46 47 - pub fn test_directory(&self) -> PathBuf { 47 + pub fn test_directory(&self) -> Utf8PathBuf { 48 48 self.root.join("test") 49 49 } 50 50 51 - pub fn build_directory(&self) -> PathBuf { 51 + pub fn build_directory(&self) -> Utf8PathBuf { 52 52 self.root.join("build") 53 53 } 54 54 55 - pub fn build_packages_directory(&self) -> PathBuf { 55 + pub fn build_packages_directory(&self) -> Utf8PathBuf { 56 56 self.build_directory().join("packages") 57 57 } 58 58 59 - pub fn build_packages_toml(&self) -> PathBuf { 59 + pub fn build_packages_toml(&self) -> Utf8PathBuf { 60 60 self.build_packages_directory().join("packages.toml") 61 61 } 62 62 63 - pub fn build_packages_package(&self, package_name: &str) -> PathBuf { 63 + pub fn build_packages_package(&self, package_name: &str) -> Utf8PathBuf { 64 64 self.build_packages_directory().join(package_name) 65 65 } 66 66 67 67 // build_deps_package_config 68 - pub fn build_packages_package_config(&self, package_name: &str) -> PathBuf { 68 + pub fn build_packages_package_config(&self, package_name: &str) -> Utf8PathBuf { 69 69 self.build_packages_package(package_name).join("gleam.toml") 70 70 } 71 71 72 - pub fn build_export_hex_tarball(&self, package_name: &str, version: &str) -> PathBuf { 72 + pub fn build_export_hex_tarball(&self, package_name: &str, version: &str) -> Utf8PathBuf { 73 73 self.build_directory() 74 74 .join(format!("{package_name}-{version}.tar")) 75 75 } 76 76 77 - pub fn build_directory_for_mode(&self, mode: Mode) -> PathBuf { 77 + pub fn build_directory_for_mode(&self, mode: Mode) -> Utf8PathBuf { 78 78 self.build_directory().join(mode.to_string()) 79 79 } 80 80 81 - pub fn erlang_shipment_directory(&self) -> PathBuf { 81 + pub fn erlang_shipment_directory(&self) -> Utf8PathBuf { 82 82 self.build_directory().join("erlang-shipment") 83 83 } 84 84 85 - pub fn build_documentation_directory(&self, package: &str) -> PathBuf { 85 + pub fn build_documentation_directory(&self, package: &str) -> Utf8PathBuf { 86 86 self.build_directory_for_mode(Mode::Dev) 87 87 .join("docs") 88 88 .join(package) 89 89 } 90 90 91 - pub fn build_directory_for_target(&self, mode: Mode, target: Target) -> PathBuf { 91 + pub fn build_directory_for_target(&self, mode: Mode, target: Target) -> Utf8PathBuf { 92 92 self.build_directory_for_mode(mode).join(target.to_string()) 93 93 } 94 94 ··· 97 97 mode: Mode, 98 98 target: Target, 99 99 package: &str, 100 - ) -> PathBuf { 100 + ) -> Utf8PathBuf { 101 101 self.build_directory_for_target(mode, target).join(package) 102 102 } 103 103 104 - pub fn build_packages_ebins_glob(&self, mode: Mode, target: Target) -> PathBuf { 104 + pub fn build_packages_ebins_glob(&self, mode: Mode, target: Target) -> Utf8PathBuf { 105 105 self.build_directory_for_package(mode, target, "*") 106 106 .join("ebin") 107 107 } ··· 109 109 /// A path to a special file that contains the version of gleam that last built 110 110 /// the artifacts. If this file does not match the current version of gleam we 111 111 /// will rebuild from scratch 112 - pub fn build_gleam_version(&self, mode: Mode, target: Target) -> PathBuf { 112 + pub fn build_gleam_version(&self, mode: Mode, target: Target) -> Utf8PathBuf { 113 113 self.build_directory_for_target(mode, target) 114 114 .join("gleam_version") 115 115 } 116 116 } 117 117 118 - pub fn global_package_cache_package_tarball(package_name: &str, version: &str) -> PathBuf { 118 + pub fn global_package_cache_package_tarball(package_name: &str, version: &str) -> Utf8PathBuf { 119 119 global_packages_cache().join(format!("{package_name}-{version}.tar")) 120 120 } 121 121 122 - fn global_packages_cache() -> PathBuf { 122 + fn global_packages_cache() -> Utf8PathBuf { 123 123 default_global_gleam_cache() 124 124 .join("hex") 125 125 .join("hexpm") 126 126 .join("packages") 127 127 } 128 128 129 - pub fn default_global_gleam_cache() -> PathBuf { 130 - dirs_next::cache_dir() 131 - .expect("Failed to determine user cache directory") 132 - .join("gleam") 129 + pub fn default_global_gleam_cache() -> Utf8PathBuf { 130 + Utf8PathBuf::from_path_buf( 131 + dirs_next::cache_dir() 132 + .expect("Failed to determine user cache directory") 133 + .join("gleam"), 134 + ) 135 + .expect("Non Utf8 Path") 133 136 } 134 137 135 - pub fn unnest(within: &Path) -> PathBuf { 136 - let mut path = PathBuf::new(); 138 + pub fn unnest(within: &Utf8Path) -> Utf8PathBuf { 139 + let mut path = Utf8PathBuf::new(); 137 140 for _ in within { 138 141 path = path.join("..") 139 142 }
+3 -3
compiler-core/src/requirement.rs
··· 1 1 use std::fmt; 2 - use std::path::PathBuf; 3 2 use std::str::FromStr; 4 3 5 4 use crate::error::Result; 5 + use camino::Utf8PathBuf; 6 6 use hexpm::version::Range; 7 7 use serde::de::{self, Deserializer, MapAccess, Visitor}; 8 8 use serde::ser::{Serialize, SerializeMap, Serializer}; ··· 13 13 #[serde(untagged, remote = "Self")] 14 14 pub enum Requirement { 15 15 Hex { version: Range }, 16 - Path { path: PathBuf }, 16 + Path { path: Utf8PathBuf }, 17 17 Git { git: SmolStr }, 18 18 } 19 19 ··· 37 37 Requirement::Hex { version: range } => { 38 38 format!(r#"{{ version = "{}" }}"#, range) 39 39 } 40 - Requirement::Path { path } => format!(r#"{{ path = "{}" }}"#, path.display()), 40 + Requirement::Path { path } => format!(r#"{{ path = "{}" }}"#, path), 41 41 Requirement::Git { git: url } => format!(r#"{{ git = "{}" }}"#, url), 42 42 } 43 43 }
+3 -2
compiler-core/src/type_/error.rs
··· 3 3 type_::Type, 4 4 }; 5 5 6 - use std::{path::PathBuf, sync::Arc}; 6 + use camino::Utf8PathBuf; 7 + use std::sync::Arc; 7 8 8 9 #[cfg(test)] 9 10 use pretty_assertions::assert_eq; ··· 410 411 } 411 412 412 413 impl Warning { 413 - pub fn into_warning(self, path: PathBuf, src: SmolStr) -> crate::Warning { 414 + pub fn into_warning(self, path: Utf8PathBuf, src: SmolStr) -> crate::Warning { 414 415 crate::Warning::Type { 415 416 path, 416 417 src,
+8 -6
compiler-core/src/type_/tests.rs
··· 9 9 }; 10 10 use itertools::Itertools; 11 11 use smol_str::SmolStr; 12 - use std::{path::PathBuf, sync::Arc}; 12 + use std::sync::Arc; 13 13 use vec1::Vec1; 14 + 15 + use camino::Utf8PathBuf; 14 16 15 17 mod assert; 16 18 mod assignments; ··· 80 82 .expect_err("should infer an error"); 81 83 let error = $crate::error::Error::Type { 82 84 src: $src.into(), 83 - path: std::path::PathBuf::from("/src/one/two.gleam"), 85 + path: camino::Utf8PathBuf::from("/src/one/two.gleam"), 84 86 error, 85 87 }; 86 88 let output = error.pretty_string(); ··· 130 132 let warnings = get_warnings(src, deps); 131 133 let mut nocolor = termcolor::Buffer::no_color(); 132 134 for warning in warnings { 133 - let path = std::path::PathBuf::from("/src/warning/wrn.gleam"); 135 + let path = Utf8PathBuf::from("/src/warning/wrn.gleam"); 134 136 let warning = warning.into_warning(path, src.into()); 135 137 warning.pretty(&mut nocolor); 136 138 } ··· 245 247 let ids = UniqueIdGenerator::new(); 246 248 let mut modules = im::HashMap::new(); 247 249 let warnings = TypeWarningEmitter::new( 248 - PathBuf::new(), 250 + Utf8PathBuf::new(), 249 251 "".into(), 250 252 WarningEmitter::new( 251 253 warnings.unwrap_or_else(|| Arc::new(VectorWarningEmitterIO::default())), ··· 292 294 let error = compile_module(src, None, deps).expect_err("should infer an error"); 293 295 let error = Error::Type { 294 296 src: src.into(), 295 - path: PathBuf::from("/src/one/two.gleam"), 297 + path: Utf8PathBuf::from("/src/one/two.gleam"), 296 298 error, 297 299 }; 298 300 error.pretty_string() ··· 302 304 let error = crate::parse::parse_module(src).expect_err("should trigger an error when parsing"); 303 305 let error = Error::Parse { 304 306 src: src.into(), 305 - path: PathBuf::from("/src/one/two.gleam"), 307 + path: Utf8PathBuf::from("/src/one/two.gleam"), 306 308 error, 307 309 }; 308 310 error.pretty_string()
+9 -8
compiler-core/src/warning.rs
··· 4 4 diagnostic::{self, Diagnostic, Location}, 5 5 type_, 6 6 }; 7 + use camino::Utf8PathBuf; 7 8 use debug_ignore::DebugIgnore; 8 9 use smol_str::SmolStr; 10 + use std::sync::atomic::AtomicUsize; 9 11 use std::{ 10 12 io::Write, 11 13 sync::{atomic::Ordering, Arc}, 12 14 }; 13 - use std::{path::PathBuf, sync::atomic::AtomicUsize}; 14 15 use termcolor::Buffer; 15 16 16 17 pub trait WarningEmitterIO { ··· 91 92 92 93 #[derive(Debug, Clone)] 93 94 pub struct TypeWarningEmitter { 94 - module_path: PathBuf, 95 + module_path: Utf8PathBuf, 95 96 module_src: SmolStr, 96 97 emitter: WarningEmitter, 97 98 } 98 99 99 100 impl TypeWarningEmitter { 100 - pub fn new(module_path: PathBuf, module_src: SmolStr, emitter: WarningEmitter) -> Self { 101 + pub fn new(module_path: Utf8PathBuf, module_src: SmolStr, emitter: WarningEmitter) -> Self { 101 102 Self { 102 103 module_path, 103 104 module_src, ··· 107 108 108 109 pub fn null() -> Self { 109 110 Self { 110 - module_path: PathBuf::new(), 111 + module_path: Utf8PathBuf::new(), 111 112 module_src: SmolStr::new(""), 112 113 emitter: WarningEmitter::new(Arc::new(NullWarningEmitterIO)), 113 114 } ··· 125 126 #[derive(Debug, Clone, Eq, PartialEq)] 126 127 pub enum Warning { 127 128 Type { 128 - path: PathBuf, 129 + path: Utf8PathBuf, 129 130 src: SmolStr, 130 131 warning: crate::type_::Warning, 131 132 }, 132 133 Parse { 133 - path: PathBuf, 134 + path: Utf8PathBuf, 134 135 src: SmolStr, 135 136 warning: crate::parse::Warning, 136 137 }, 137 138 InvalidSource { 138 - path: PathBuf, 139 + path: Utf8PathBuf, 139 140 }, 140 141 } 141 142 ··· 241 242 location: None, 242 243 hint: Some(format!( 243 244 "Rename `{}` to be valid, or remove this file from the project source.", 244 - path.to_string_lossy() 245 + path 245 246 )), 246 247 }, 247 248 Self::Type { path, warning, src } => match warning {
+1
compiler-wasm/Cargo.toml
··· 20 20 tracing-wasm = "*" 21 21 tracing = "*" 22 22 termcolor = "1.1.2" 23 + camino = "1.1.6" 23 24 24 25 [dev-dependencies] 25 26 wasm-bindgen-test = "0.3.28"
+6 -5
compiler-wasm/src/lib.rs
··· 1 - use std::{collections::HashMap, ffi::OsStr, path::Path, sync::Arc}; 1 + use camino::Utf8Path; 2 + use std::{collections::HashMap, sync::Arc}; 2 3 3 4 use gleam_core::{ 4 5 build::{Built, Codegen, Mode, Options, ProjectCompiler, Target}, ··· 60 61 Ok(gather_compiled_files(&paths, &wfs, options.target).unwrap()) 61 62 } 62 63 63 - fn write_source_file<P: AsRef<Path>>(source: &str, path: P, wfs: &mut WasmFileSystem) { 64 + fn write_source_file<P: AsRef<Utf8Path>>(source: &str, path: P, wfs: &mut WasmFileSystem) { 64 65 wfs.write(path.as_ref(), source) 65 66 .expect("should always succeed with the virtual file system"); 66 67 } ··· 127 128 let mut files: HashMap<String, String> = HashMap::new(); 128 129 129 130 let extension_to_search_for = match target { 130 - Target::Erlang => OsStr::new("erl"), 131 - Target::JavaScript => OsStr::new("mjs"), 131 + Target::Erlang => "erl", 132 + Target::JavaScript => "mjs", 132 133 }; 133 134 134 135 wfs.read_dir(&paths.build_directory()) ··· 139 140 .for_each(|dir_entry| { 140 141 let path = dir_entry.as_path(); 141 142 let contents: String = wfs.read(path).expect("iterated dir entries should exist"); 142 - let path = path.to_str().unwrap().replace('\\', "/"); 143 + let path = path.as_str().replace('\\', "/"); 143 144 144 145 files.insert(path, contents); 145 146 });
+21 -21
compiler-wasm/src/wasm_filesystem.rs
··· 1 + use camino::{Utf8Path, Utf8PathBuf}; 1 2 use gleam_core::{ 2 3 io::{ 3 4 memory::InMemoryFileSystem, CommandExecutor, FileSystemReader, FileSystemWriter, ReadDir, ··· 5 6 }, 6 7 Error, Result, 7 8 }; 8 - use std::path::{Path, PathBuf}; 9 9 10 10 #[derive(Clone, Debug)] 11 11 pub struct WasmFileSystem { ··· 26 26 _program: &str, 27 27 _args: &[String], 28 28 _env: &[(&str, String)], 29 - _cwd: Option<&Path>, 29 + _cwd: Option<&Utf8Path>, 30 30 _stdio: Stdio, 31 31 ) -> Result<i32, Error> { 32 32 Ok(0) // Always succeed. ··· 34 34 } 35 35 36 36 impl FileSystemWriter for WasmFileSystem { 37 - fn delete(&self, path: &Path) -> Result<(), Error> { 37 + fn delete(&self, path: &Utf8Path) -> Result<(), Error> { 38 38 tracing::trace!("delete {:?}", path); 39 39 self.imfs.delete(path) 40 40 } 41 41 42 - fn copy(&self, _from: &Path, _to: &Path) -> Result<(), Error> { 42 + fn copy(&self, _from: &Utf8Path, _to: &Utf8Path) -> Result<(), Error> { 43 43 Ok(()) 44 44 } 45 - fn copy_dir(&self, _: &Path, _: &Path) -> Result<(), Error> { 45 + fn copy_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 46 46 Ok(()) 47 47 } 48 48 49 - fn mkdir(&self, _: &Path) -> Result<(), Error> { 49 + fn mkdir(&self, _: &Utf8Path) -> Result<(), Error> { 50 50 Ok(()) 51 51 } 52 52 53 - fn hardlink(&self, _: &Path, _: &Path) -> Result<(), Error> { 53 + fn hardlink(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 54 54 Ok(()) 55 55 } 56 56 57 - fn symlink_dir(&self, _: &Path, _: &Path) -> Result<(), Error> { 57 + fn symlink_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { 58 58 Ok(()) 59 59 } 60 60 61 - fn delete_file(&self, path: &Path) -> Result<(), Error> { 61 + fn delete_file(&self, path: &Utf8Path) -> Result<(), Error> { 62 62 tracing::trace!("delete file {:?}", path); 63 63 self.imfs.delete_file(path) 64 64 } 65 65 66 - fn write(&self, path: &Path, content: &str) -> Result<(), Error> { 66 + fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { 67 67 tracing::trace!("write {:?}", path); 68 68 self.imfs.write(path, content) 69 69 } 70 70 71 - fn write_bytes(&self, path: &Path, content: &[u8]) -> Result<(), Error> { 71 + fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { 72 72 tracing::trace!("write_bytes {:?}", path); 73 73 self.imfs.write_bytes(path, content) 74 74 } 75 75 } 76 76 77 77 impl FileSystemReader for WasmFileSystem { 78 - fn gleam_source_files(&self, dir: &Path) -> Vec<PathBuf> { 78 + fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 79 79 tracing::trace!("gleam_source_files {:?}", dir); 80 80 self.imfs.gleam_source_files(dir) 81 81 } 82 82 83 - fn gleam_cache_files(&self, dir: &Path) -> Vec<PathBuf> { 83 + fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf> { 84 84 tracing::trace!("gleam_metadata_files {:?}", dir); 85 85 self.imfs.gleam_cache_files(dir) 86 86 } 87 87 88 - fn read(&self, path: &Path) -> Result<String, Error> { 88 + fn read(&self, path: &Utf8Path) -> Result<String, Error> { 89 89 tracing::trace!("read {:?}", path); 90 90 self.imfs.read(path) 91 91 } 92 92 93 - fn is_file(&self, path: &Path) -> bool { 93 + fn is_file(&self, path: &Utf8Path) -> bool { 94 94 tracing::info!("is_file {:?}", path); 95 95 self.imfs.is_file(path) 96 96 } 97 97 98 - fn is_directory(&self, path: &Path) -> bool { 98 + fn is_directory(&self, path: &Utf8Path) -> bool { 99 99 tracing::trace!("is_directory {:?}", path); 100 100 false 101 101 } 102 102 103 - fn reader(&self, path: &Path) -> Result<WrappedReader, Error> { 103 + fn reader(&self, path: &Utf8Path) -> Result<WrappedReader, Error> { 104 104 tracing::trace!("reader {:?}", path); 105 105 self.imfs.reader(path) 106 106 } 107 107 108 - fn read_dir(&self, path: &Path) -> Result<ReadDir> { 108 + fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir> { 109 109 tracing::trace!("read_dir {:?}", path); 110 110 self.imfs.read_dir(path) 111 111 } 112 112 113 - fn modification_time(&self, path: &Path) -> Result<std::time::SystemTime, Error> { 113 + fn modification_time(&self, path: &Utf8Path) -> Result<std::time::SystemTime, Error> { 114 114 self.imfs.modification_time(path) 115 115 } 116 116 117 - fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, Error> { 117 + fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error> { 118 118 self.imfs.read_bytes(path) 119 119 } 120 120 121 - fn canonicalise(&self, path: &Path) -> Result<PathBuf, Error> { 121 + fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error> { 122 122 self.imfs.canonicalise(path) 123 123 } 124 124 }
+1
test-package-compiler/Cargo.toml
··· 17 17 walkdir = "2.3.2" 18 18 # Regular expressions 19 19 regex = "*" 20 + camino = "1.1.6" 20 21 21 22 [dev-dependencies] 22 23 # Snapshot testing to make test maintenance easier
+13 -16
test-package-compiler/src/lib.rs
··· 13 13 }; 14 14 use itertools::Itertools; 15 15 use regex::Regex; 16 - use std::{ 17 - collections::HashMap, 18 - ffi::OsStr, 19 - fmt::Write, 20 - path::{Path, PathBuf}, 21 - sync::Arc, 22 - }; 16 + use std::{collections::HashMap, fmt::Write, sync::Arc}; 17 + 18 + use camino::{Utf8Path, Utf8PathBuf}; 23 19 24 20 pub fn prepare(path: &str) -> String { 25 - let root = PathBuf::from(path).canonicalize().unwrap(); 21 + let root = Utf8PathBuf::from(path).canonicalize_utf8().unwrap(); 26 22 27 23 let toml = std::fs::read_to_string(root.join("gleam.toml")).unwrap(); 28 24 let config: PackageConfig = toml::from_str(&toml).unwrap(); ··· 44 40 let warning_emitter = WarningEmitter::new(Arc::new(warnings.clone())); 45 41 let filesystem = to_in_memory_filesystem(&root); 46 42 let initial_files = filesystem.paths(); 47 - let root = PathBuf::from(""); 48 - let out = PathBuf::from("/out/lib/the_package"); 49 - let lib = PathBuf::from("/out/lib"); 43 + let root = Utf8PathBuf::from(""); 44 + let out = Utf8PathBuf::from("/out/lib/the_package"); 45 + let lib = Utf8PathBuf::from("/out/lib"); 50 46 let mut compiler = gleam_core::build::PackageCompiler::new( 51 47 &config, 52 48 Mode::Dev, ··· 94 90 95 91 #[derive(Debug)] 96 92 pub struct TestCompileOutput { 97 - files: HashMap<PathBuf, Content>, 93 + files: HashMap<Utf8PathBuf, Content>, 98 94 warnings: Vec<gleam_core::Warning>, 99 95 } 100 96 ··· 103 99 let mut buffer = String::new(); 104 100 for (path, content) in self.files.iter().sorted_by(|a, b| a.0.cmp(b.0)) { 105 101 buffer.push_str("//// "); 106 - buffer.push_str(&path.to_str().unwrap().replace('\\', "/")); 102 + buffer.push_str(&path.as_str().replace('\\', "/")); 107 103 buffer.push('\n'); 108 104 109 - let extension = path.extension().and_then(OsStr::to_str); 105 + let extension = path.extension(); 110 106 match content { 111 107 _ if extension == Some("cache") => buffer.push_str("<.cache binary>"), 112 108 ··· 132 128 } 133 129 } 134 130 135 - fn to_in_memory_filesystem(path: &Path) -> InMemoryFileSystem { 131 + fn to_in_memory_filesystem(path: &Utf8Path) -> InMemoryFileSystem { 136 132 let fs = InMemoryFileSystem::new(); 137 133 138 134 let files = walkdir::WalkDir::new(path) ··· 145 141 for fullpath in files { 146 142 let content = std::fs::read(&fullpath).unwrap(); 147 143 let path = fullpath.strip_prefix(path).unwrap(); 148 - fs.write_bytes(path, &content).unwrap(); 144 + fs.write_bytes(Utf8Path::from_path(path).unwrap(), &content) 145 + .unwrap(); 149 146 } 150 147 151 148 fs