Skip to main content

uv_options_metadata/
lib.rs

1//! Taken directly from Ruff.
2//!
3//! See: <https://github.com/astral-sh/ruff/blob/dc8db1afb08704ad6a788c497068b01edf8b460d/crates/ruff_workspace/sr.rs>
4
5use serde::{Serialize, Serializer};
6use std::collections::BTreeMap;
7
8use std::fmt::{Debug, Display, Formatter};
9
10/// Visits [`OptionsMetadata`].
11///
12/// An instance of [`Visit`] represents the logic for inspecting an object's options metadata.
13pub trait Visit {
14    /// Visits an [`OptionField`] value named `name`.
15    fn record_field(&mut self, name: &str, field: OptionField);
16
17    /// Visits an [`OptionSet`] value named `name`.
18    fn record_set(&mut self, name: &str, group: OptionSet);
19}
20
21/// Returns metadata for its options.
22pub trait OptionsMetadata {
23    /// Visits the options metadata of this object by calling `visit` for each option.
24    fn record(visit: &mut dyn Visit);
25
26    fn documentation() -> Option<&'static str> {
27        None
28    }
29
30    /// Returns the extracted metadata.
31    fn metadata() -> OptionSet
32    where
33        Self: Sized + 'static,
34    {
35        OptionSet::of::<Self>()
36    }
37}
38
39impl<T> OptionsMetadata for Option<T>
40where
41    T: OptionsMetadata,
42{
43    fn record(visit: &mut dyn Visit) {
44        T::record(visit);
45    }
46}
47
48/// A set of options.
49///
50/// It extracts the options by calling the [`OptionsMetadata::record`] of a type implementing
51/// [`OptionsMetadata`].
52#[derive(Copy, Clone)]
53pub struct OptionSet {
54    record: fn(&mut dyn Visit),
55    doc: fn() -> Option<&'static str>,
56}
57
58impl PartialEq for OptionSet {
59    fn eq(&self, other: &Self) -> bool {
60        std::ptr::fn_addr_eq(self.record, other.record) && std::ptr::fn_addr_eq(self.doc, other.doc)
61    }
62}
63
64impl Eq for OptionSet {}
65
66impl OptionSet {
67    pub fn of<T>() -> Self
68    where
69        T: OptionsMetadata + 'static,
70    {
71        Self {
72            record: T::record,
73            doc: T::documentation,
74        }
75    }
76
77    /// Visits the options in this set by calling `visit` for each option.
78    pub fn record(&self, visit: &mut dyn Visit) {
79        let record = self.record;
80        record(visit);
81    }
82
83    pub fn documentation(&self) -> Option<&'static str> {
84        let documentation = self.doc;
85        documentation()
86    }
87}
88
89/// Visitor that writes out the names of all fields and sets.
90struct DisplayVisitor<'fmt, 'buf> {
91    f: &'fmt mut Formatter<'buf>,
92    result: std::fmt::Result,
93}
94
95impl<'fmt, 'buf> DisplayVisitor<'fmt, 'buf> {
96    fn new(f: &'fmt mut Formatter<'buf>) -> Self {
97        Self { f, result: Ok(()) }
98    }
99
100    fn finish(self) -> std::fmt::Result {
101        self.result
102    }
103}
104
105impl Visit for DisplayVisitor<'_, '_> {
106    fn record_set(&mut self, name: &str, _: OptionSet) {
107        self.result = self.result.and_then(|()| writeln!(self.f, "{name}"));
108    }
109
110    fn record_field(&mut self, name: &str, field: OptionField) {
111        self.result = self.result.and_then(|()| {
112            write!(self.f, "{name}")?;
113
114            if field.deprecated.is_some() {
115                write!(self.f, " (deprecated)")?;
116            }
117
118            writeln!(self.f)
119        });
120    }
121}
122
123impl Display for OptionSet {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        let mut visitor = DisplayVisitor::new(f);
126        self.record(&mut visitor);
127        visitor.finish()
128    }
129}
130
131struct SerializeVisitor<'a> {
132    entries: &'a mut BTreeMap<String, OptionField>,
133}
134
135impl Visit for SerializeVisitor<'_> {
136    fn record_set(&mut self, name: &str, set: OptionSet) {
137        // Collect the entries of the set.
138        let mut entries = BTreeMap::new();
139        let mut visitor = SerializeVisitor {
140            entries: &mut entries,
141        };
142        set.record(&mut visitor);
143
144        // Insert the set into the entries.
145        for (key, value) in entries {
146            self.entries.insert(format!("{name}.{key}"), value);
147        }
148    }
149
150    fn record_field(&mut self, name: &str, field: OptionField) {
151        self.entries.insert(name.to_string(), field);
152    }
153}
154
155impl Serialize for OptionSet {
156    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157    where
158        S: Serializer,
159    {
160        let mut entries = BTreeMap::new();
161        let mut visitor = SerializeVisitor {
162            entries: &mut entries,
163        };
164        self.record(&mut visitor);
165        entries.serialize(serializer)
166    }
167}
168
169impl Debug for OptionSet {
170    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
171        Display::fmt(self, f)
172    }
173}
174
175#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
176pub struct OptionField {
177    pub doc: &'static str,
178    /// Ex) `"false"`
179    pub default: &'static str,
180    /// Ex) `"bool"`
181    pub value_type: &'static str,
182    /// Ex) `"per-file-ignores"`
183    pub scope: Option<&'static str>,
184    pub example: &'static str,
185    pub deprecated: Option<Deprecated>,
186    pub possible_values: Option<Vec<PossibleValue>>,
187    /// If true, this option is only available in `uv.toml`, not `pyproject.toml`.
188    pub uv_toml_only: bool,
189}
190
191#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
192pub struct Deprecated {
193    pub since: Option<&'static str>,
194    pub message: Option<&'static str>,
195}
196
197impl Display for OptionField {
198    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199        writeln!(f, "{}", self.doc)?;
200        writeln!(f)?;
201
202        writeln!(f, "Default value: {}", self.default)?;
203
204        if let Some(possible_values) = self
205            .possible_values
206            .as_ref()
207            .filter(|values| !values.is_empty())
208        {
209            writeln!(f, "Possible values:")?;
210            writeln!(f)?;
211            for value in possible_values {
212                writeln!(f, "- {value}")?;
213            }
214        } else {
215            writeln!(f, "Type: {}", self.value_type)?;
216        }
217
218        if let Some(deprecated) = &self.deprecated {
219            write!(f, "Deprecated")?;
220
221            if let Some(since) = deprecated.since {
222                write!(f, " (since {since})")?;
223            }
224
225            if let Some(message) = deprecated.message {
226                write!(f, ": {message}")?;
227            }
228
229            writeln!(f)?;
230        }
231
232        writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
233    }
234}
235
236/// A possible value for an enum, similar to Clap's `PossibleValue` type (but without a dependency
237/// on Clap).
238#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
239pub struct PossibleValue {
240    pub name: String,
241    pub help: Option<String>,
242}
243
244impl Display for PossibleValue {
245    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
246        write!(f, "`\"{}\"`", self.name)?;
247        if let Some(help) = &self.help {
248            write!(f, ": {help}")?;
249        }
250        Ok(())
251    }
252}