mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
Refactor Options
representation (#7591)
This commit is contained in:
parent
f137819536
commit
2ecf59726f
7 changed files with 343 additions and 177 deletions
|
@ -1,12 +1,13 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
|
use ruff_workspace::options_base::OptionsMetadata;
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
pub(crate) fn config(key: Option<&str>) -> Result<()> {
|
pub(crate) fn config(key: Option<&str>) -> Result<()> {
|
||||||
match key {
|
match key {
|
||||||
None => print!("{}", Options::metadata()),
|
None => print!("{}", Options::metadata()),
|
||||||
Some(key) => match Options::metadata().get(key) {
|
Some(key) => match Options::metadata().find(key) {
|
||||||
None => {
|
None => {
|
||||||
return Err(anyhow!("Unknown option: {key}"));
|
return Err(anyhow!("Unknown option: {key}"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use strum::IntoEnumIterator;
|
||||||
use ruff_diagnostics::AutofixKind;
|
use ruff_diagnostics::AutofixKind;
|
||||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
|
use ruff_workspace::options_base::OptionsMetadata;
|
||||||
|
|
||||||
use crate::ROOT_DIR;
|
use crate::ROOT_DIR;
|
||||||
|
|
||||||
|
@ -96,10 +97,7 @@ fn process_documentation(documentation: &str, out: &mut String) {
|
||||||
if let Some(rest) = line.strip_prefix("- `") {
|
if let Some(rest) = line.strip_prefix("- `") {
|
||||||
let option = rest.trim_end().trim_end_matches('`');
|
let option = rest.trim_end().trim_end_matches('`');
|
||||||
|
|
||||||
assert!(
|
assert!(Options::metadata().has(option), "unknown option {option}");
|
||||||
Options::metadata().get(option).is_some(),
|
|
||||||
"unknown option {option}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let anchor = option.replace('.', "-");
|
let anchor = option.replace('.', "-");
|
||||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||||
|
|
|
@ -1,9 +1,68 @@
|
||||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||||
//!
|
//!
|
||||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||||
use itertools::Itertools;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
use ruff_workspace::options_base::{OptionEntry, OptionField};
|
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||||
|
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
generate_set(&mut output, &Set::Toplevel(Options::metadata()));
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_set(output: &mut String, set: &Set) {
|
||||||
|
writeln!(output, "### {title}\n", title = set.title()).unwrap();
|
||||||
|
|
||||||
|
let mut visitor = CollectOptionsVisitor::default();
|
||||||
|
set.metadata().record(&mut visitor);
|
||||||
|
|
||||||
|
let (mut fields, mut sets) = (visitor.fields, visitor.groups);
|
||||||
|
|
||||||
|
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||||
|
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||||
|
|
||||||
|
// Generate the fields.
|
||||||
|
for (name, field) in &fields {
|
||||||
|
emit_field(output, name, field, set.name());
|
||||||
|
output.push_str("---\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate all the sub-sets.
|
||||||
|
for (set_name, sub_set) in &sets {
|
||||||
|
generate_set(output, &Set::Named(set_name, *sub_set));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Set<'a> {
|
||||||
|
Toplevel(OptionSet),
|
||||||
|
Named(&'a str, OptionSet),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Set<'a> {
|
||||||
|
fn name(&self) -> Option<&'a str> {
|
||||||
|
match self {
|
||||||
|
Set::Toplevel(_) => None,
|
||||||
|
Set::Named(name, _) => Some(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> &'a str {
|
||||||
|
match self {
|
||||||
|
Set::Toplevel(_) => "Top-level",
|
||||||
|
Set::Named(name, _) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata(&self) -> &OptionSet {
|
||||||
|
match self {
|
||||||
|
Set::Toplevel(set) => set,
|
||||||
|
Set::Named(_, set) => set,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
||||||
// if there's a group name, we need to add it to the anchor
|
// if there's a group name, we need to add it to the anchor
|
||||||
|
@ -37,38 +96,18 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name:
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generate() -> String {
|
#[derive(Default)]
|
||||||
let mut output: String = "### Top-level\n\n".into();
|
struct CollectOptionsVisitor {
|
||||||
|
groups: Vec<(String, OptionSet)>,
|
||||||
let sorted_options: Vec<_> = Options::metadata()
|
fields: Vec<(String, OptionField)>,
|
||||||
.into_iter()
|
}
|
||||||
.sorted_by_key(|(name, _)| *name)
|
|
||||||
.collect();
|
impl Visit for CollectOptionsVisitor {
|
||||||
|
fn record_set(&mut self, name: &str, group: OptionSet) {
|
||||||
// Generate all the top-level fields.
|
self.groups.push((name.to_owned(), group));
|
||||||
for (name, entry) in &sorted_options {
|
}
|
||||||
let OptionEntry::Field(field) = entry else {
|
|
||||||
continue;
|
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||||
};
|
self.fields.push((name.to_owned(), field));
|
||||||
emit_field(&mut output, name, field, None);
|
}
|
||||||
output.push_str("---\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate all the sub-groups.
|
|
||||||
for (group_name, entry) in &sorted_options {
|
|
||||||
let OptionEntry::Group(fields) = entry else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
output.push_str(&format!("### {group_name}\n"));
|
|
||||||
output.push('\n');
|
|
||||||
for (name, entry) in fields.iter().sorted_by_key(|(name, _)| name) {
|
|
||||||
let OptionEntry::Field(field) = entry else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
emit_field(&mut output, name, field, Some(group_name));
|
|
||||||
output.push_str("---\n\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use ruff_diagnostics::AutofixKind;
|
||||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||||
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
|
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
|
use ruff_workspace::options_base::OptionsMetadata;
|
||||||
|
|
||||||
const FIX_SYMBOL: &str = "🛠️";
|
const FIX_SYMBOL: &str = "🛠️";
|
||||||
const PREVIEW_SYMBOL: &str = "🧪";
|
const PREVIEW_SYMBOL: &str = "🧪";
|
||||||
|
@ -104,10 +105,7 @@ pub(crate) fn generate() -> String {
|
||||||
table_out.push('\n');
|
table_out.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
if Options::metadata()
|
if Options::metadata().has(linter.name()) {
|
||||||
.iter()
|
|
||||||
.any(|(name, _)| name == &linter.name())
|
|
||||||
{
|
|
||||||
table_out.push_str(&format!(
|
table_out.push_str(&format!(
|
||||||
"For related settings, see [{}](settings.md#{}).",
|
"For related settings, see [{}](settings.md#{}).",
|
||||||
linter.name(),
|
linter.name(),
|
||||||
|
|
|
@ -50,14 +50,11 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let options_len = output.len();
|
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
|
|
||||||
impl #ident {
|
impl crate::options_base::OptionsMetadata for #ident {
|
||||||
pub const fn metadata() -> crate::options_base::OptionGroup {
|
fn record(visit: &mut dyn crate::options_base::Visit) {
|
||||||
const OPTIONS: [(&'static str, crate::options_base::OptionEntry); #options_len] = [#(#output),*];
|
#(#output);*
|
||||||
crate::options_base::OptionGroup::new(&OPTIONS)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -92,7 +89,7 @@ fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||||
|
|
||||||
Ok(quote_spanned!(
|
Ok(quote_spanned!(
|
||||||
ident.span() => (#kebab_name, crate::options_base::OptionEntry::Group(#path::metadata()))
|
ident.span() => (visit.record_set(#kebab_name, crate::options_base::OptionSet::of::<#path>()))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => Err(syn::Error::new(
|
_ => Err(syn::Error::new(
|
||||||
|
@ -150,12 +147,14 @@ fn handle_option(
|
||||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||||
|
|
||||||
Ok(quote_spanned!(
|
Ok(quote_spanned!(
|
||||||
ident.span() => (#kebab_name, crate::options_base::OptionEntry::Field(crate::options_base::OptionField {
|
ident.span() => {
|
||||||
doc: &#doc,
|
visit.record_field(#kebab_name, crate::options_base::OptionField{
|
||||||
default: &#default,
|
doc: &#doc,
|
||||||
value_type: &#value_type,
|
default: &#default,
|
||||||
example: &#example,
|
value_type: &#value_type,
|
||||||
}))
|
example: &#example,
|
||||||
|
})
|
||||||
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::options_base::{OptionsMetadata, Visit};
|
||||||
use ruff_linter::line_width::{LineLength, TabSize};
|
use ruff_linter::line_width::{LineLength, TabSize};
|
||||||
use ruff_linter::rules::flake8_pytest_style::settings::SettingsError;
|
use ruff_linter::rules::flake8_pytest_style::settings::SettingsError;
|
||||||
use ruff_linter::rules::flake8_pytest_style::types;
|
use ruff_linter::rules::flake8_pytest_style::types;
|
||||||
|
@ -2399,10 +2400,6 @@ pub enum FormatOrOutputFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatOrOutputFormat {
|
impl FormatOrOutputFormat {
|
||||||
pub const fn metadata() -> crate::options_base::OptionGroup {
|
|
||||||
FormatOptions::metadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn as_output_format(&self) -> Option<SerializationFormat> {
|
pub const fn as_output_format(&self) -> Option<SerializationFormat> {
|
||||||
match self {
|
match self {
|
||||||
FormatOrOutputFormat::Format(_) => None,
|
FormatOrOutputFormat::Format(_) => None,
|
||||||
|
@ -2411,6 +2408,12 @@ impl FormatOrOutputFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OptionsMetadata for FormatOrOutputFormat {
|
||||||
|
fn record(visit: &mut dyn Visit) {
|
||||||
|
FormatOptions::record(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, CombineOptions,
|
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, CombineOptions,
|
||||||
)]
|
)]
|
||||||
|
|
|
@ -1,167 +1,295 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
/// Visits [`OptionsMetadata`].
|
||||||
|
///
|
||||||
|
/// An instance of [`Visit`] represents the logic for inspecting an object's options metadata.
|
||||||
|
pub trait Visit {
|
||||||
|
/// Visits an [`OptionField`] value named `name`.
|
||||||
|
fn record_field(&mut self, name: &str, field: OptionField);
|
||||||
|
|
||||||
|
/// Visits an [`OptionSet`] value named `name`.
|
||||||
|
fn record_set(&mut self, name: &str, group: OptionSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns metadata for its options.
|
||||||
|
pub trait OptionsMetadata {
|
||||||
|
/// Visits the options metadata of this object by calling `visit` for each option.
|
||||||
|
fn record(visit: &mut dyn Visit);
|
||||||
|
|
||||||
|
/// Returns the extracted metadata.
|
||||||
|
fn metadata() -> OptionSet
|
||||||
|
where
|
||||||
|
Self: Sized + 'static,
|
||||||
|
{
|
||||||
|
OptionSet::of::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata of an option that can either be a [`OptionField`] or [`OptionSet`].
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum OptionEntry {
|
pub enum OptionEntry {
|
||||||
|
/// A single option.
|
||||||
Field(OptionField),
|
Field(OptionField),
|
||||||
Group(OptionGroup),
|
|
||||||
|
/// A set of options
|
||||||
|
Set(OptionSet),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for OptionEntry {
|
impl Display for OptionEntry {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
OptionEntry::Field(field) => field.fmt(f),
|
OptionEntry::Set(set) => std::fmt::Display::fmt(set, f),
|
||||||
OptionEntry::Group(group) => group.fmt(f),
|
OptionEntry::Field(field) => std::fmt::Display::fmt(&field, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
/// A set of options.
|
||||||
pub struct OptionGroup(&'static [(&'static str, OptionEntry)]);
|
///
|
||||||
|
/// It extracts the options by calling the [`OptionsMetadata::record`] of a type implementing
|
||||||
|
/// [`OptionsMetadata`].
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct OptionSet {
|
||||||
|
record: fn(&mut dyn Visit),
|
||||||
|
}
|
||||||
|
|
||||||
impl OptionGroup {
|
impl OptionSet {
|
||||||
pub const fn new(options: &'static [(&'static str, OptionEntry)]) -> Self {
|
pub fn of<T>() -> Self
|
||||||
Self(options)
|
where
|
||||||
|
T: OptionsMetadata + 'static,
|
||||||
|
{
|
||||||
|
Self { record: T::record }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> std::slice::Iter<(&str, OptionEntry)> {
|
/// Visits the options in this set by calling `visit` for each option.
|
||||||
self.into_iter()
|
pub fn record(&self, visit: &mut dyn Visit) {
|
||||||
|
let record = self.record;
|
||||||
|
record(visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an option entry by its fully-qualified name
|
/// Returns `true` if this set has an option that resolves to `name`.
|
||||||
/// (e.g. `foo.bar` refers to the `bar` option in the `foo` group).
|
///
|
||||||
|
/// The name can be separated by `.` to find a nested option.
|
||||||
///
|
///
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
///
|
///
|
||||||
/// ### Find a direct child
|
/// ### Test for the existence of a child option
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ruff_workspace::options_base::{OptionGroup, OptionEntry, OptionField};
|
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
|
||||||
///
|
///
|
||||||
/// const OPTIONS: [(&'static str, OptionEntry); 2] = [
|
/// struct WithOptions;
|
||||||
/// ("ignore_names", OptionEntry::Field(OptionField {
|
|
||||||
/// doc: "ignore_doc",
|
|
||||||
/// default: "ignore_default",
|
|
||||||
/// value_type: "value_type",
|
|
||||||
/// example: "ignore code"
|
|
||||||
/// })),
|
|
||||||
///
|
///
|
||||||
/// ("global_names", OptionEntry::Field(OptionField {
|
/// impl OptionsMetadata for WithOptions {
|
||||||
/// doc: "global_doc",
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
/// default: "global_default",
|
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||||
/// value_type: "value_type",
|
/// doc: "Whether Ruff should respect the gitignore file",
|
||||||
/// example: "global code"
|
/// default: "false",
|
||||||
/// }))
|
/// value_type: "bool",
|
||||||
/// ];
|
/// example: "",
|
||||||
///
|
/// });
|
||||||
/// let group = OptionGroup::new(&OPTIONS);
|
|
||||||
///
|
|
||||||
/// let ignore_names = group.get("ignore_names");
|
|
||||||
///
|
|
||||||
/// match ignore_names {
|
|
||||||
/// None => panic!("Expect option 'ignore_names' to be Some"),
|
|
||||||
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
|
||||||
/// Some(OptionEntry::Field(field)) => {
|
|
||||||
/// assert_eq!("ignore_doc", field.doc);
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// assert_eq!(None, group.get("not_existing_option"));
|
/// assert!(WithOptions::metadata().has("ignore-git-ignore"));
|
||||||
|
/// assert!(!WithOptions::metadata().has("does-not-exist"));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
/// ### Test for the existence of a nested option
|
||||||
/// ### Find a nested options
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ruff_workspace::options_base::{OptionGroup, OptionEntry, OptionField};
|
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
|
||||||
///
|
///
|
||||||
/// const IGNORE_OPTIONS: [(&'static str, OptionEntry); 2] = [
|
/// struct Root;
|
||||||
/// ("names", OptionEntry::Field(OptionField {
|
|
||||||
/// doc: "ignore_name_doc",
|
|
||||||
/// default: "ignore_name_default",
|
|
||||||
/// value_type: "value_type",
|
|
||||||
/// example: "ignore name code"
|
|
||||||
/// })),
|
|
||||||
///
|
///
|
||||||
/// ("extensions", OptionEntry::Field(OptionField {
|
/// impl OptionsMetadata for Root {
|
||||||
/// doc: "ignore_extensions_doc",
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
/// default: "ignore_extensions_default",
|
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||||
/// value_type: "value_type",
|
/// doc: "Whether Ruff should respect the gitignore file",
|
||||||
/// example: "ignore extensions code"
|
/// default: "false",
|
||||||
/// }))
|
/// value_type: "bool",
|
||||||
/// ];
|
/// example: "",
|
||||||
|
/// });
|
||||||
///
|
///
|
||||||
/// const OPTIONS: [(&'static str, OptionEntry); 2] = [
|
/// visit.record_set("format", Nested::metadata());
|
||||||
/// ("ignore", OptionEntry::Group(OptionGroup::new(&IGNORE_OPTIONS))),
|
|
||||||
///
|
|
||||||
/// ("global_names", OptionEntry::Field(OptionField {
|
|
||||||
/// doc: "global_doc",
|
|
||||||
/// default: "global_default",
|
|
||||||
/// value_type: "value_type",
|
|
||||||
/// example: "global code"
|
|
||||||
/// }))
|
|
||||||
/// ];
|
|
||||||
///
|
|
||||||
/// let group = OptionGroup::new(&OPTIONS);
|
|
||||||
///
|
|
||||||
/// let ignore_names = group.get("ignore.names");
|
|
||||||
///
|
|
||||||
/// match ignore_names {
|
|
||||||
/// None => panic!("Expect option 'ignore.names' to be Some"),
|
|
||||||
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
|
||||||
/// Some(OptionEntry::Field(field)) => {
|
|
||||||
/// assert_eq!("ignore_name_doc", field.doc);
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// struct Nested;
|
||||||
|
///
|
||||||
|
/// impl OptionsMetadata for Nested {
|
||||||
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
|
/// visit.record_field("hard-tabs", OptionField {
|
||||||
|
/// doc: "Use hard tabs for indentation and spaces for alignment.",
|
||||||
|
/// default: "false",
|
||||||
|
/// value_type: "bool",
|
||||||
|
/// example: "",
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert!(Root::metadata().has("format.hard-tabs"));
|
||||||
|
/// assert!(!Root::metadata().has("format.spaces"));
|
||||||
|
/// assert!(!Root::metadata().has("lint.hard-tabs"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get(&self, name: &str) -> Option<&OptionEntry> {
|
pub fn has(&self, name: &str) -> bool {
|
||||||
let mut parts = name.split('.').peekable();
|
self.find(name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
let mut options = self.iter();
|
/// Returns `Some` if this set has an option that resolves to `name` and `None` otherwise.
|
||||||
|
///
|
||||||
|
/// The name can be separated by `.` to find a nested option.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ### Find a child option
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
|
||||||
|
///
|
||||||
|
/// struct WithOptions;
|
||||||
|
///
|
||||||
|
/// static IGNORE_GIT_IGNORE: OptionField = OptionField {
|
||||||
|
/// doc: "Whether Ruff should respect the gitignore file",
|
||||||
|
/// default: "false",
|
||||||
|
/// value_type: "bool",
|
||||||
|
/// example: "",
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// impl OptionsMetadata for WithOptions {
|
||||||
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
|
/// visit.record_field("ignore-git-ignore", IGNORE_GIT_IGNORE.clone());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(WithOptions::metadata().find("ignore-git-ignore"), Some(OptionEntry::Field(IGNORE_GIT_IGNORE.clone())));
|
||||||
|
/// assert_eq!(WithOptions::metadata().find("does-not-exist"), None);
|
||||||
|
/// ```
|
||||||
|
/// ### Find a nested option
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
|
||||||
|
///
|
||||||
|
/// static HARD_TABS: OptionField = OptionField {
|
||||||
|
/// doc: "Use hard tabs for indentation and spaces for alignment.",
|
||||||
|
/// default: "false",
|
||||||
|
/// value_type: "bool",
|
||||||
|
/// example: "",
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// struct Root;
|
||||||
|
///
|
||||||
|
/// impl OptionsMetadata for Root {
|
||||||
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
|
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||||
|
/// doc: "Whether Ruff should respect the gitignore file",
|
||||||
|
/// default: "false",
|
||||||
|
/// value_type: "bool",
|
||||||
|
/// example: "",
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// visit.record_set("format", Nested::metadata());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct Nested;
|
||||||
|
///
|
||||||
|
/// impl OptionsMetadata for Nested {
|
||||||
|
/// fn record(visit: &mut dyn Visit) {
|
||||||
|
/// visit.record_field("hard-tabs", HARD_TABS.clone());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(Root::metadata().find("format.hard-tabs"), Some(OptionEntry::Field(HARD_TABS.clone())));
|
||||||
|
/// assert_eq!(Root::metadata().find("format"), Some(OptionEntry::Set(Nested::metadata())));
|
||||||
|
/// assert_eq!(Root::metadata().find("format.spaces"), None);
|
||||||
|
/// assert_eq!(Root::metadata().find("lint.hard-tabs"), None);
|
||||||
|
/// ```
|
||||||
|
pub fn find(&self, name: &str) -> Option<OptionEntry> {
|
||||||
|
struct FindOptionVisitor<'a> {
|
||||||
|
option: Option<OptionEntry>,
|
||||||
|
parts: std::str::Split<'a, char>,
|
||||||
|
needle: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
impl Visit for FindOptionVisitor<'_> {
|
||||||
let part = parts.next()?;
|
fn record_set(&mut self, name: &str, set: OptionSet) {
|
||||||
|
if self.option.is_none() && name == self.needle {
|
||||||
|
if let Some(next) = self.parts.next() {
|
||||||
|
self.needle = next;
|
||||||
|
set.record(self);
|
||||||
|
} else {
|
||||||
|
self.option = Some(OptionEntry::Set(set));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (_, field) = options.find(|(name, _)| *name == part)?;
|
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||||
|
if self.option.is_none() && name == self.needle {
|
||||||
match (parts.peek(), field) {
|
if self.parts.next().is_none() {
|
||||||
(None, field) => return Some(field),
|
self.option = Some(OptionEntry::Field(field));
|
||||||
(Some(..), OptionEntry::Field(..)) => return None,
|
}
|
||||||
(Some(..), OptionEntry::Group(group)) => {
|
|
||||||
options = group.iter();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a OptionGroup {
|
let mut parts = name.split('.');
|
||||||
type IntoIter = std::slice::Iter<'a, (&'a str, OptionEntry)>;
|
|
||||||
type Item = &'a (&'a str, OptionEntry);
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
if let Some(first) = parts.next() {
|
||||||
self.0.iter()
|
let mut visitor = FindOptionVisitor {
|
||||||
}
|
parts,
|
||||||
}
|
needle: first,
|
||||||
|
option: None,
|
||||||
|
};
|
||||||
|
|
||||||
impl IntoIterator for OptionGroup {
|
self.record(&mut visitor);
|
||||||
type IntoIter = std::slice::Iter<'static, (&'static str, OptionEntry)>;
|
visitor.option
|
||||||
type Item = &'static (&'static str, OptionEntry);
|
} else {
|
||||||
|
None
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.0.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for OptionGroup {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
for (name, _) in self {
|
|
||||||
writeln!(f, "{name}")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
/// Visitor that writes out the names of all fields and sets.
|
||||||
|
struct DisplayVisitor<'fmt, 'buf> {
|
||||||
|
f: &'fmt mut Formatter<'buf>,
|
||||||
|
result: std::fmt::Result,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'fmt, 'buf> DisplayVisitor<'fmt, 'buf> {
|
||||||
|
fn new(f: &'fmt mut Formatter<'buf>) -> Self {
|
||||||
|
Self { f, result: Ok(()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> std::fmt::Result {
|
||||||
|
self.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visit for DisplayVisitor<'_, '_> {
|
||||||
|
fn record_set(&mut self, name: &str, _: OptionSet) {
|
||||||
|
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_field(&mut self, name: &str, _: OptionField) {
|
||||||
|
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for OptionSet {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut visitor = DisplayVisitor::new(f);
|
||||||
|
self.record(&mut visitor);
|
||||||
|
visitor.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for OptionSet {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct OptionField {
|
pub struct OptionField {
|
||||||
pub doc: &'static str,
|
pub doc: &'static str,
|
||||||
pub default: &'static str,
|
pub default: &'static str,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue