, D: fmt::Display>(self, p: P, msg: D) -> Self::Out {
+ self.with_context(|| format!("{} (at '{}')", msg, p.as_ref().display()))
+ }
+}
+
#[cfg(windows)]
pub fn symlink_dir(original: P, link: Q) -> Result<(), std::io::Error>
where
@@ -71,7 +86,8 @@ pub fn mark_path_sync_ignore(venv: &Path, mark_ignore: bool) -> Result<(), Error
for flag in ATTRS {
if mark_ignore {
- xattr::set(venv, flag, b"1")?;
+ xattr::set(venv, flag, b"1")
+ .path_context(venv, "failed to write extended attribute")?;
} else {
xattr::remove(venv, flag).ok();
}
@@ -83,7 +99,7 @@ pub fn mark_path_sync_ignore(venv: &Path, mark_ignore: bool) -> Result<(), Error
let mut stream_path = venv.as_os_str().to_os_string();
stream_path.push(":com.dropbox.ignored");
if mark_ignore {
- fs::write(stream_path, b"1")?;
+ fs::write(&stream_path, b"1").path_context(&stream_path, "failed to write stream")?;
} else {
fs::remove_file(stream_path).ok();
}
@@ -268,20 +284,25 @@ pub fn unpack_archive(contents: &[u8], dst: &Path, strip_components: usize) -> R
let path = dst.join(components.as_path());
if path != Path::new("") && path.strip_prefix(dst).is_ok() {
if file.name().ends_with('/') {
- fs::create_dir_all(&path)?;
+ fs::create_dir_all(&path).path_context(&path, "failed to create directory")?;
} else {
if let Some(p) = path.parent() {
if !p.exists() {
- fs::create_dir_all(p)?;
+ fs::create_dir_all(p).path_context(p, "failed to create directory")?;
}
}
- std::io::copy(&mut file, &mut fs::File::create(&path)?)?;
+ std::io::copy(
+ &mut file,
+ &mut fs::File::create(&path)
+ .path_context(&path, "failed to create file")?,
+ )?;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
- fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
+ fs::set_permissions(&path, fs::Permissions::from_mode(mode))
+ .path_context(&path, "failed to set permissions")?;
}
}
}
@@ -398,7 +419,10 @@ pub fn copy_dir>(from: T, to: T, options: &CopyDirOptions) -> Res
let to = to.as_ref();
if from.is_dir() {
- for entry in fs::read_dir(from)?.filter_map(|e| e.ok()) {
+ for entry in fs::read_dir(from)
+ .path_context(from, "failed to enumerate directory")?
+ .filter_map(|e| e.ok())
+ {
let entry_path = entry.path();
if options.exclude.iter().any(|dir| *dir == entry_path) {
continue;
@@ -406,10 +430,12 @@ pub fn copy_dir>(from: T, to: T, options: &CopyDirOptions) -> Res
let destination = to.join(entry.file_name());
if entry.file_type()?.is_dir() {
- fs::create_dir_all(&destination)?;
+ fs::create_dir_all(&destination)
+ .path_context(&destination, "failed to create directory")?;
copy_dir(entry.path(), destination, options)?;
} else {
- fs::copy(entry.path(), &destination)?;
+ fs::copy(entry.path(), &destination)
+ .path_context(entry.path(), "failed to copy file")?;
}
}
}
diff --git a/rye/src/utils/unix.rs b/rye/src/utils/unix.rs
index 775aa89da6..9e06df961c 100644
--- a/rye/src/utils/unix.rs
+++ b/rye/src/utils/unix.rs
@@ -3,6 +3,8 @@ use std::{env, fs};
use anyhow::{Context, Error};
+use crate::utils::IoPathContext;
+
pub(crate) fn add_to_path(rye_home: &Path) -> Result<(), Error> {
// for regular shells just add the path to `.profile`
add_source_line_to_profile(
@@ -19,7 +21,8 @@ pub(crate) fn add_to_path(rye_home: &Path) -> Result<(), Error> {
fn add_source_line_to_profile(profile_path: &Path, source_line: &str) -> Result<(), Error> {
let mut profile = if profile_path.is_file() {
- fs::read_to_string(profile_path)?
+ fs::read_to_string(profile_path)
+ .path_context(profile_path, "failed to read profile file")?
} else {
String::new()
};
@@ -27,7 +30,8 @@ fn add_source_line_to_profile(profile_path: &Path, source_line: &str) -> Result<
if !profile.lines().any(|x| x.trim() == source_line) {
profile.push_str(source_line);
profile.push('\n');
- fs::write(profile_path, profile).context("failed to write updated .profile")?;
+ fs::write(profile_path, profile)
+ .path_context(profile_path, "failed to write updated .profile")?;
}
Ok(())
diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs
index 0963939102..4bab40c824 100644
--- a/rye/tests/common/mod.rs
+++ b/rye/tests/common/mod.rs
@@ -25,7 +25,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[
// windows temp folders
(r"\b[A-Z]:\\.*\\Local\\Temp\\\S+", "[TEMP_FILE]"),
(r" in (\d+\.)?\d+(ms|s)\b", " in [EXECUTION_TIME]"),
- (r"\\([\w\d.])", "/$1"),
+ (r"\\\\?([\w\d.])", "/$1"),
(r"rye.exe", "rye"),
];
@@ -151,6 +151,12 @@ impl Space {
rv
}
+ #[allow(unused)]
+ pub fn read_toml>(&self, path: P) -> toml_edit::Document {
+ let p = self.project_path().join(path.as_ref());
+ std::fs::read_to_string(&p).unwrap().parse().unwrap()
+ }
+
#[allow(unused)]
pub fn write, B: AsRef<[u8]>>(&self, path: P, contents: B) {
let p = self.project_path().join(path.as_ref());
diff --git a/rye/tests/test_init.rs b/rye/tests/test_init.rs
new file mode 100644
index 0000000000..39da78b7d2
--- /dev/null
+++ b/rye/tests/test_init.rs
@@ -0,0 +1,183 @@
+use crate::common::{get_bin, rye_cmd_snapshot, Space};
+
+mod common;
+
+// Test that init --lib works
+#[test]
+fn test_init_lib() {
+ let space = Space::new();
+ space
+ .cmd(get_bin())
+ .arg("init")
+ .arg("--name")
+ .arg("my-project")
+ .arg("-q")
+ .arg("--lib")
+ .current_dir(space.project_path())
+ .status()
+ .expect("initialization successful");
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Initializing new virtualenv in [TEMP_PATH]/project/.venv
+ Python version: cpython@3.12.1
+ Generating production lockfile: [TEMP_PATH]/project/requirements.lock
+ Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock
+ Installing dependencies
+ Done!
+
+ ----- stderr -----
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ Built 1 editable in [EXECUTION_TIME]
+ Installed 1 package in [EXECUTION_TIME]
+ + my-project==0.1.0 (from file:[TEMP_PATH]/project)
+ "###);
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-c").arg("import my_project; print(my_project.hello())"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Hello from my-project!
+
+ ----- stderr -----
+ "###);
+
+ assert!(
+ space.read_toml("pyproject.toml")["project"]
+ .get("scripts")
+ .is_none(),
+ "[project.scripts] should not be present"
+ )
+}
+
+// The default is the same as --lib
+#[test]
+fn test_init_default() {
+ let space = Space::new();
+ space
+ .cmd(get_bin())
+ .arg("init")
+ .arg("--name")
+ .arg("my-project")
+ .arg("-q")
+ .current_dir(space.project_path())
+ .status()
+ .expect("initialization successful");
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Initializing new virtualenv in [TEMP_PATH]/project/.venv
+ Python version: cpython@3.12.1
+ Generating production lockfile: [TEMP_PATH]/project/requirements.lock
+ Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock
+ Installing dependencies
+ Done!
+
+ ----- stderr -----
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ Built 1 editable in [EXECUTION_TIME]
+ Installed 1 package in [EXECUTION_TIME]
+ + my-project==0.1.0 (from file:[TEMP_PATH]/project)
+ "###);
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-c").arg("import my_project; print(my_project.hello())"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Hello from my-project!
+
+ ----- stderr -----
+ "###);
+
+ assert!(
+ space.read_toml("pyproject.toml")["project"]
+ .get("scripts")
+ .is_none(),
+ "[project.scripts] should not be present"
+ )
+}
+
+// Test that init --script works
+#[test]
+fn test_init_script() {
+ let space = Space::new();
+ space
+ .cmd(get_bin())
+ .arg("init")
+ .arg("--name")
+ .arg("my-project")
+ .arg("-q")
+ .arg("--script")
+ .current_dir(space.project_path())
+ .status()
+ .expect("initialization successful");
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Initializing new virtualenv in [TEMP_PATH]/project/.venv
+ Python version: cpython@3.12.1
+ Generating production lockfile: [TEMP_PATH]/project/requirements.lock
+ Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock
+ Installing dependencies
+ Done!
+
+ ----- stderr -----
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ warning: Requirements file [TEMP_FILE] does not contain any dependencies
+ Built 1 editable in [EXECUTION_TIME]
+ Resolved 1 package in [EXECUTION_TIME]
+ Built 1 editable in [EXECUTION_TIME]
+ Installed 1 package in [EXECUTION_TIME]
+ + my-project==0.1.0 (from file:[TEMP_PATH]/project)
+ "###);
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("hello"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Hello from my-project!
+
+ ----- stderr -----
+ "###);
+
+ rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-mmy_project"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Hello from my-project!
+
+ ----- stderr -----
+ "###);
+}
+
+// Test that init --script and --lib are incompatible.
+#[test]
+fn test_init_lib_and_script_incompatible() {
+ let space = Space::new();
+ rye_cmd_snapshot!(space.cmd(get_bin()).arg("init").arg("--name").arg("my-project").arg("--script").arg("--lib").current_dir(space.project_path()), @r###"
+ success: false
+ exit_code: 2
+ ----- stdout -----
+
+ ----- stderr -----
+ error: an argument cannot be used with one or more of the other specified arguments
+ "###);
+}