use std::path::PathBuf; use anstream::println; use anyhow::{bail, Result}; use pretty_assertions::StrComparison; use schemars::{schema_for, JsonSchema}; use serde::Deserialize; use uv_settings::Options as SettingsOptions; use uv_workspace::pyproject::ToolUv as WorkspaceOptions; use crate::generate_all::Mode; use crate::ROOT_DIR; #[derive(Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] #[allow(dead_code)] // The names and docstrings of this struct and the types it contains are used as `title` and // `description` in uv.schema.json, see https://github.com/SchemaStore/schemastore/blob/master/editor-features.md#title-as-an-expected-object-type /// Metadata and configuration for uv. struct CombinedOptions { #[serde(flatten)] options: SettingsOptions, #[serde(flatten)] workspace: WorkspaceOptions, } #[derive(clap::Args)] pub(crate) struct Args { /// Write the generated output to stdout (rather than to `uv.schema.json`). #[arg(long, default_value_t, value_enum)] pub(crate) mode: Mode, } pub(crate) fn main(args: &Args) -> Result<()> { // Generate the schema. let schema_string = generate(); let filename = "uv.schema.json"; let schema_path = PathBuf::from(ROOT_DIR).join(filename); match args.mode { Mode::DryRun => { println!("{schema_string}"); } Mode::Check => match fs_err::read_to_string(schema_path) { Ok(current) => { if current == schema_string { println!("Up-to-date: {filename}"); } else { let comparison = StrComparison::new(¤t, &schema_string); bail!("{filename} changed, please run `cargo dev generate-json-schema`:\n{comparison}"); } } Err(err) if err.kind() == std::io::ErrorKind::NotFound => { bail!("{filename} not found, please run `cargo dev generate-json-schema`"); } Err(err) => { bail!("{filename} changed, please run `cargo dev generate-json-schema`:\n{err}"); } }, Mode::Write => match fs_err::read_to_string(&schema_path) { Ok(current) => { if current == schema_string { println!("Up-to-date: {filename}"); } else { println!("Updating: {filename}"); fs_err::write(schema_path, schema_string.as_bytes())?; } } Err(err) if err.kind() == std::io::ErrorKind::NotFound => { println!("Updating: {filename}"); fs_err::write(schema_path, schema_string.as_bytes())?; } Err(err) => { bail!("{filename} changed, please run `cargo dev generate-json-schema`:\n{err}"); } }, } Ok(()) } const REPLACEMENTS: &[(&str, &str)] = &[ // Use the fully-resolved URL rather than the relative Markdown path. ( "(../concepts/dependencies.md)", "(https://docs.astral.sh/uv/concepts/dependencies/)", ), ]; /// Generate the JSON schema for the combined options as a string. fn generate() -> String { let schema = schema_for!(CombinedOptions); let mut output = serde_json::to_string_pretty(&schema).unwrap(); for (value, replacement) in REPLACEMENTS { assert_ne!( value, replacement, "`value` and `replacement` must be different, but both are `{value}`" ); let before = &output; let after = output.replace(value, replacement); assert_ne!(*before, after, "Could not find `{value}` in the output"); output = after; } output } #[cfg(test)] mod tests;