-
Notifications
You must be signed in to change notification settings - Fork 760
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Load configuration options from workspace root
- Loading branch information
1 parent
cfb022a
commit 623d2da
Showing
13 changed files
with
252 additions
and
257 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
use std::ops::Deref; | ||
use std::path::{Path, PathBuf}; | ||
|
||
use tracing::debug; | ||
|
||
use uv_fs::Simplified; | ||
|
||
pub use crate::combine::*; | ||
pub use crate::settings::*; | ||
|
||
mod combine; | ||
mod settings; | ||
|
||
/// The [`Options`] as loaded from a configuration file on disk. | ||
#[derive(Debug, Clone)] | ||
pub struct ResolvedOptions(Options); | ||
|
||
impl ResolvedOptions { | ||
/// Convert the [`ResolvedOptions`] into [`Options`]. | ||
pub fn into_options(self) -> Options { | ||
self.0 | ||
} | ||
} | ||
|
||
impl Deref for ResolvedOptions { | ||
type Target = Options; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl ResolvedOptions { | ||
/// Load the user [`ResolvedOptions`]. | ||
pub fn user() -> Result<Option<Self>, Error> { | ||
let Some(dir) = config_dir() else { | ||
return Ok(None); | ||
}; | ||
let root = dir.join("uv"); | ||
let file = root.join("uv.toml"); | ||
|
||
debug!("Loading user configuration from: `{}`", file.display()); | ||
match read_file(&file) { | ||
Ok(options) => Ok(Some(Self(options))), | ||
Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), | ||
Err(_) if !dir.is_dir() => { | ||
// Ex) `XDG_CONFIG_HOME=/dev/null` | ||
debug!( | ||
"User configuration directory `{}` does not exist or is not a directory", | ||
dir.display() | ||
); | ||
Ok(None) | ||
} | ||
Err(err) => Err(err), | ||
} | ||
} | ||
|
||
/// Find the [`ResolvedOptions`] for the given path. | ||
/// | ||
/// The search starts at the given path and goes up the directory tree until a `uv.toml` file is | ||
/// found. | ||
pub fn find(path: impl AsRef<Path>) -> Result<Option<Self>, Error> { | ||
for ancestor in path.as_ref().ancestors() { | ||
// Read a `uv.toml` file in the current directory. | ||
let path = ancestor.join("uv.toml"); | ||
match fs_err::read_to_string(&path) { | ||
Ok(content) => { | ||
let options: Options = toml::from_str(&content) | ||
.map_err(|err| Error::UvToml(path.user_display().to_string(), err))?; | ||
|
||
debug!("Found workspace configuration at `{}`", path.display()); | ||
return Ok(Some(Self(options))); | ||
} | ||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | ||
Err(err) => return Err(err.into()), | ||
} | ||
} | ||
Ok(None) | ||
} | ||
|
||
/// Load a [`ResolvedOptions`] from a directory, preferring a `uv.toml` file over a | ||
/// `pyproject.toml` file. | ||
pub fn from_directory(dir: impl AsRef<Path>) -> Result<Option<Self>, Error> { | ||
// Read a `uv.toml` file in the current directory. | ||
let path = dir.as_ref().join("uv.toml"); | ||
match fs_err::read_to_string(&path) { | ||
Ok(content) => { | ||
let options: Options = toml::from_str(&content) | ||
.map_err(|err| Error::UvToml(path.user_display().to_string(), err))?; | ||
|
||
debug!("Found workspace configuration at `{}`", path.display()); | ||
return Ok(Some(Self(options))); | ||
} | ||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | ||
Err(err) => return Err(err.into()), | ||
} | ||
|
||
// Read a `pyproject.toml` file in the current directory. | ||
let path = dir.as_ref().join("pyproject.toml"); | ||
match fs_err::read_to_string(&path) { | ||
Ok(content) => { | ||
// Parse, but skip any `pyproject.toml` that doesn't have a `[tool.uv]` section. | ||
let pyproject: PyProjectToml = toml::from_str(&content) | ||
.map_err(|err| Error::PyprojectToml(path.user_display().to_string(), err))?; | ||
let Some(tool) = pyproject.tool else { | ||
return Ok(None); | ||
}; | ||
let Some(options) = tool.uv else { | ||
return Ok(None); | ||
}; | ||
|
||
debug!("Found workspace configuration at `{}`", path.display()); | ||
return Ok(Some(Self(options))); | ||
} | ||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | ||
Err(err) => return Err(err.into()), | ||
} | ||
|
||
Ok(None) | ||
} | ||
|
||
/// Load a [`ResolvedOptions`] from a `uv.toml` file. | ||
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> { | ||
Ok(Self(read_file(path.as_ref())?)) | ||
} | ||
} | ||
|
||
/// Returns the path to the user configuration directory. | ||
/// | ||
/// This is similar to the `config_dir()` returned by the `dirs` crate, but it uses the | ||
/// `XDG_CONFIG_HOME` environment variable on both Linux _and_ macOS, rather than the | ||
/// `Application Support` directory on macOS. | ||
fn config_dir() -> Option<PathBuf> { | ||
// On Windows, use, e.g., C:\Users\Alice\AppData\Roaming | ||
#[cfg(windows)] | ||
{ | ||
dirs_sys::known_folder_roaming_app_data() | ||
} | ||
|
||
// On Linux and macOS, use, e.g., /home/alice/.config. | ||
#[cfg(not(windows))] | ||
{ | ||
std::env::var_os("XDG_CONFIG_HOME") | ||
.and_then(dirs_sys::is_absolute_path) | ||
.or_else(|| dirs_sys::home_dir().map(|path| path.join(".config"))) | ||
} | ||
} | ||
|
||
/// Load [`Options`] from a `uv.toml` file. | ||
fn read_file(path: &Path) -> Result<Options, Error> { | ||
let content = fs_err::read_to_string(path)?; | ||
let options: Options = toml::from_str(&content) | ||
.map_err(|err| Error::UvToml(path.user_display().to_string(), err))?; | ||
Ok(options) | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
#[error(transparent)] | ||
Io(#[from] std::io::Error), | ||
|
||
#[error("Failed to parse: `{0}`")] | ||
PyprojectToml(String, #[source] toml::de::Error), | ||
|
||
#[error("Failed to parse: `{0}`")] | ||
UvToml(String, #[source] toml::de::Error), | ||
} |
File renamed without changes.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.