Repository: astral-sh/uv (Rust)

Issue:
`uv init` fails with dot as path argument
```
/tmp                                                                                                                                                                                           13:41:13
:) mkdir example
/tmp                                                                                                                                                                                           13:41:19
:) cd example
/tmp/example                                                                                                                                                                                   13:41:20
:) uv init .    
warning: `uv init` is experimental and may change without warning
thread 'main' panicked at /Users/eth/workspace/astral-sh/uv/crates/uv/src/commands/project/init.rs:44:18:
Invalid package name
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Since this is a fairly common valid init argument, I believe it should not panic due to an `Invalid package name` error.


Discussion:

Patch:
diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs
index a44625453b3d7..a811b6cc25047 100644
--- a/crates/uv/src/commands/project/init.rs
+++ b/crates/uv/src/commands/project/init.rs
@@ -1,7 +1,7 @@
 use std::fmt::Write;
 use std::path::PathBuf;
 
-use anyhow::Result;
+use anyhow::{Context, Result};
 use owo_colors::OwoColorize;
 
 use pep508_rs::PackageName;
@@ -34,19 +34,6 @@ pub(crate) async fn init(
         Some(ref path) => PathBuf::from(path),
     };
 
-    // Default to the directory name if a name was not provided.
-    let name = match name {
-        Some(name) => name,
-        None => {
-            let name = path
-                .file_name()
-                .and_then(|path| path.to_str())
-                .expect("Invalid package name");
-
-            PackageName::new(name.to_string())?
-        }
-    };
-
     // Make sure a project does not already exist in the given directory.
     if path.join("pyproject.toml").exists() {
         let path = path
@@ -62,6 +49,19 @@ pub(crate) async fn init(
     // Canonicalize the path to the project.
     let path = absolutize_path(&path)?;
 
+    // Default to the directory name if a name was not provided.
+    let name = match name {
+        Some(name) => name,
+        None => {
+            let name = path
+                .file_name()
+                .and_then(|path| path.to_str())
+                .context("Missing directory name")?;
+
+            PackageName::new(name.to_string())?
+        }
+    };
+
     // Create the `pyproject.toml`.
     let pyproject = indoc::formatdoc! {r#"
         [project]


Test patch:
diff --git a/crates/uv/tests/init.rs b/crates/uv/tests/init.rs
index c2654f28bc350..3cb16c27eb41e 100644
--- a/crates/uv/tests/init.rs
+++ b/crates/uv/tests/init.rs
@@ -175,6 +175,72 @@ fn current_dir() -> Result<()> {
     Ok(())
 }
 
+#[test]
+fn init_dot_args() -> Result<()> {
+    let context = TestContext::new("3.12");
+
+    let dir = context.temp_dir.join("foo");
+    fs_err::create_dir(&dir)?;
+
+    uv_snapshot!(context.filters(), context.init().current_dir(&dir).arg("."), @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+
+    ----- stderr -----
+    warning: `uv init` is experimental and may change without warning
+    Initialized project `foo` at `[TEMP_DIR]/foo`
+    "###);
+
+    let pyproject = fs_err::read_to_string(dir.join("pyproject.toml"))?;
+    let init_py = fs_err::read_to_string(dir.join("src/foo/__init__.py"))?;
+    let _ = fs_err::read_to_string(dir.join("README.md")).unwrap();
+
+    insta::with_settings!({
+        filters => context.filters(),
+    }, {
+        assert_snapshot!(
+            pyproject, @r###"
+        [project]
+        name = "foo"
+        version = "0.1.0"
+        description = "Add your description here"
+        readme = "README.md"
+        dependencies = []
+
+        [tool.uv]
+        dev-dependencies = []
+        "###
+        );
+    });
+
+    insta::with_settings!({
+        filters => context.filters(),
+    }, {
+        assert_snapshot!(
+            init_py, @r###"
+        def hello() -> str:
+            return "Hello from foo!"
+        "###
+        );
+    });
+
+    // Run `uv lock` in the new project.
+    uv_snapshot!(context.filters(), context.lock().current_dir(&dir), @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+
+    ----- stderr -----
+    warning: `uv lock` is experimental and may change without warning
+    Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
+    warning: No `requires-python` field found in the workspace. Defaulting to `>=3.12`.
+    Resolved 1 package in [TIME]
+    "###);
+
+    Ok(())
+}
+
 #[test]
 fn init_workspace() -> Result<()> {
     let context = TestContext::new("3.12");