diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 0831f1f42..bddc6a5f7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2568,9 +2568,13 @@ pub struct InitArgs { /// /// By default, adds a requirement on the system Python version; use `--python` to specify an /// alternative Python version requirement. - #[arg(long, conflicts_with_all=["app", "lib", "package", "build_backend"])] + #[arg(long, conflicts_with_all=["app", "lib", "package", "build_backend", "description"])] pub r#script: bool, + /// Set the project description. + #[arg(long, conflicts_with = "script")] + pub description: Option, + /// Initialize a version control system for the project. /// /// By default, uv will initialize a Git repository (`git`). Use `--vcs none` to explicitly diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 18f2b587b..708a7dc04 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -41,6 +41,7 @@ pub(crate) async fn init( name: Option, package: bool, init_kind: InitKind, + description: Option, vcs: Option, build_backend: Option, no_readme: bool, @@ -129,6 +130,7 @@ pub(crate) async fn init( &name, package, project_kind, + description, vcs, build_backend, no_readme, @@ -270,6 +272,7 @@ async fn init_project( name: &PackageName, package: bool, project_kind: InitProjectKind, + description: Option, vcs: Option, build_backend: Option, no_readme: bool, @@ -564,6 +567,7 @@ async fn init_project( name, path, &requires_python, + description.as_deref(), vcs, build_backend, author_from, @@ -687,6 +691,7 @@ impl InitProjectKind { name: &PackageName, path: &Path, requires_python: &RequiresPython, + description: Option<&str>, vcs: Option, build_backend: Option, author_from: Option, @@ -698,6 +703,7 @@ impl InitProjectKind { name, path, requires_python, + description, vcs, build_backend, author_from, @@ -708,6 +714,7 @@ impl InitProjectKind { name, path, requires_python, + description, vcs, build_backend, author_from, @@ -722,6 +729,7 @@ impl InitProjectKind { name: &PackageName, path: &Path, requires_python: &RequiresPython, + description: Option<&str>, vcs: Option, build_backend: Option, author_from: Option, @@ -741,7 +749,13 @@ impl InitProjectKind { let author = get_author_info(path, author_from); // Create the `pyproject.toml` - let mut pyproject = pyproject_project(name, requires_python, author.as_ref(), no_readme); + let mut pyproject = pyproject_project( + name, + requires_python, + author.as_ref(), + description, + no_readme, + ); // Include additional project configuration for packaged applications if package { @@ -788,6 +802,7 @@ impl InitProjectKind { name: &PackageName, path: &Path, requires_python: &RequiresPython, + description: Option<&str>, vcs: Option, build_backend: Option, author_from: Option, @@ -803,7 +818,13 @@ impl InitProjectKind { let author = get_author_info(path, author_from.unwrap_or_default()); // Create the `pyproject.toml` - let mut pyproject = pyproject_project(name, requires_python, author.as_ref(), no_readme); + let mut pyproject = pyproject_project( + name, + requires_python, + author.as_ref(), + description, + no_readme, + ); // Always include a build system if the project is packaged. let build_backend = build_backend.unwrap_or_default(); @@ -847,19 +868,21 @@ fn pyproject_project( name: &PackageName, requires_python: &RequiresPython, author: Option<&Author>, + description: Option<&str>, no_readme: bool, ) -> String { indoc::formatdoc! {r#" [project] name = "{name}" version = "0.1.0" - description = "Add your description here"{readme}{authors} + description = "{description}"{readme}{authors} requires-python = "{requires_python}" dependencies = [] "#, readme = if no_readme { "" } else { "\nreadme = \"README.md\"" }, authors = author.map_or_else(String::new, |author| format!("\nauthors = [\n {}\n]", author.to_toml_string())), requires_python = requires_python.specifiers(), + description = description.unwrap_or("Add your description here"), } } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e4ec66076..e30baf2df 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1360,6 +1360,7 @@ async fn run_project( args.name, args.package, args.kind, + args.description, args.vcs, args.build_backend, args.no_readme, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1de1ebcbb..3acbc107a 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -189,6 +189,7 @@ pub(crate) struct InitSettings { pub(crate) name: Option, pub(crate) package: bool, pub(crate) kind: InitKind, + pub(crate) description: Option, pub(crate) vcs: Option, pub(crate) build_backend: Option, pub(crate) no_readme: bool, @@ -212,6 +213,7 @@ impl InitSettings { app, lib, script, + description, vcs, build_backend, no_readme, @@ -241,6 +243,7 @@ impl InitSettings { name, package, kind, + description, vcs, build_backend, no_readme, diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index 1daef7afc..d43246e87 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -3271,3 +3271,89 @@ fn init_application_package_uv() -> Result<()> { Ok(()) } + +#[test] +fn init_with_description() -> Result<()> { + let context = TestContext::new("3.12"); + + let child = context.temp_dir.join("foo"); + fs_err::create_dir_all(&child)?; + + // Initialize the project with a description + context + .init() + .current_dir(&child) + .arg("--description") + .arg("A sample project description") + .arg("--lib") + .assert() + .success(); + + // Read the generated pyproject.toml + let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; + + // Verify the description in pyproject.toml + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject, @r#" + [project] + name = "foo" + version = "0.1.0" + description = "A sample project description" + readme = "README.md" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "# + ); + }); + + Ok(()) +} + +#[test] +fn init_without_description() -> Result<()> { + let context = TestContext::new("3.12"); + + let child = context.temp_dir.join("bar"); + fs_err::create_dir_all(&child)?; + + // Initialize the project without a description + context + .init() + .current_dir(&child) + .arg("--lib") + .assert() + .success(); + + // Read the generated pyproject.toml + let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; + + // Verify the default description in pyproject.toml + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject, @r#" + [project] + name = "bar" + version = "0.1.0" + description = "Add your description here" + readme = "README.md" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "# + ); + }); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 80b562ae0..80dae9dbb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -589,6 +589,8 @@ uv init [OPTIONS] [PATH]

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

May also be set with the UV_CONFIG_FILE environment variable.

+
--description description

Set the project description

+
--directory directory

Change to the given directory prior to running the command.

Relative paths are resolved with the given directory as the base.