mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:47 +00:00
[ty] CLI reference (#17978)
This commit is contained in:
parent
6cd8a49638
commit
c1b875799b
21 changed files with 905 additions and 416 deletions
|
@ -3,8 +3,8 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
generate_cli_help, generate_docs, generate_json_schema, generate_ty_options, generate_ty_rules,
|
||||
generate_ty_schema,
|
||||
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
|
||||
generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
};
|
||||
|
||||
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
|
||||
|
@ -43,5 +43,6 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
|||
})?;
|
||||
generate_ty_options::main(&generate_ty_options::Args { mode: args.mode })?;
|
||||
generate_ty_rules::main(&generate_ty_rules::Args { mode: args.mode })?;
|
||||
generate_ty_cli_reference::main(&generate_ty_cli_reference::Args { mode: args.mode })?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Generate CLI help.
|
||||
#![allow(clippy::print_stdout)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, str};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Generate Markdown documentation for applicable rules.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write as _;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
334
crates/ruff_dev/src/generate_ty_cli_reference.rs
Normal file
334
crates/ruff_dev/src/generate_ty_cli_reference.rs
Normal file
|
@ -0,0 +1,334 @@
|
|||
//! Generate a Markdown-compatible reference for the ty command-line interface.
|
||||
use std::cmp::max;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{Command, CommandFactory};
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
use ty::Cli;
|
||||
|
||||
const SHOW_HIDDEN_COMMANDS: &[&str] = &["generate-shell-completion"];
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let reference_string = generate();
|
||||
let filename = "crates/ty/docs/cli.md";
|
||||
let reference_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{reference_string}");
|
||||
}
|
||||
Mode::Check => {
|
||||
match std::fs::read_to_string(reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &reference_string);
|
||||
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!("{filename} not found, please run `{REGENERATE_ALL_COMMAND}`");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Mode::Write => match std::fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
println!("Updating: {filename}");
|
||||
std::fs::write(reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
println!("Updating: {filename}");
|
||||
std::fs::write(reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("{filename} changed, please run `cargo dev generate-cli-reference`:\n{err}");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
let mut ty = Cli::command();
|
||||
|
||||
// It is very important to build the command before beginning inspection or subcommands
|
||||
// will be missing all of the propagated options.
|
||||
ty.build();
|
||||
|
||||
let mut parents = Vec::new();
|
||||
|
||||
output.push_str("# CLI Reference\n\n");
|
||||
generate_command(&mut output, &ty, &mut parents);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[allow(clippy::format_push_string)]
|
||||
fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut Vec<&'a Command>) {
|
||||
if command.is_hide_set() && !SHOW_HIDDEN_COMMANDS.contains(&command.get_name()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the command header.
|
||||
let name = if parents.is_empty() {
|
||||
command.get_name().to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
parents.iter().map(|cmd| cmd.get_name()).join(" "),
|
||||
command.get_name()
|
||||
)
|
||||
};
|
||||
|
||||
// Display the top-level `ty` command at the same level as its children
|
||||
let level = max(2, parents.len() + 1);
|
||||
output.push_str(&format!("{} {name}\n\n", "#".repeat(level)));
|
||||
|
||||
// Display the command description.
|
||||
if let Some(about) = command.get_long_about().or_else(|| command.get_about()) {
|
||||
output.push_str(&about.to_string());
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
// Display the usage
|
||||
{
|
||||
// This appears to be the simplest way to get rendered usage from Clap,
|
||||
// it is complicated to render it manually. It's annoying that it
|
||||
// requires a mutable reference but it doesn't really matter.
|
||||
let mut command = command.clone();
|
||||
output.push_str("<h3 class=\"cli-reference\">Usage</h3>\n\n");
|
||||
output.push_str(&format!(
|
||||
"```\n{}\n```",
|
||||
command
|
||||
.render_usage()
|
||||
.to_string()
|
||||
.trim_start_matches("Usage: "),
|
||||
));
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
if command.get_name() == "help" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a list of child commands
|
||||
let mut subcommands = command.get_subcommands().peekable();
|
||||
let has_subcommands = subcommands.peek().is_some();
|
||||
if has_subcommands {
|
||||
output.push_str("<h3 class=\"cli-reference\">Commands</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
|
||||
for subcommand in subcommands {
|
||||
if subcommand.is_hide_set() {
|
||||
continue;
|
||||
}
|
||||
let subcommand_name = format!("{name} {}", subcommand.get_name());
|
||||
output.push_str(&format!(
|
||||
"<dt><a href=\"#{}\"><code>{subcommand_name}</code></a></dt>",
|
||||
subcommand_name.replace(' ', "-")
|
||||
));
|
||||
if let Some(about) = subcommand.get_about() {
|
||||
output.push_str(&format!(
|
||||
"<dd>{}</dd>\n",
|
||||
markdown::to_html(&about.to_string())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>\n\n");
|
||||
}
|
||||
|
||||
// Do not display options for commands with children
|
||||
if !has_subcommands {
|
||||
let name_key = name.replace(' ', "-");
|
||||
|
||||
// Display positional arguments
|
||||
let mut arguments = command
|
||||
.get_positionals()
|
||||
.filter(|arg| !arg.is_hide_set())
|
||||
.peekable();
|
||||
|
||||
if arguments.peek().is_some() {
|
||||
output.push_str("<h3 class=\"cli-reference\">Arguments</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
|
||||
for arg in arguments {
|
||||
let id = format!("{name_key}--{}", arg.get_id());
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!(
|
||||
"<a href=\"#{id}\"<code>{}</code></a>",
|
||||
arg.get_id().to_string().to_uppercase(),
|
||||
));
|
||||
output.push_str("</dt>");
|
||||
if let Some(help) = arg.get_long_help().or_else(|| arg.get_help()) {
|
||||
output.push_str("<dd>");
|
||||
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
|
||||
output.push_str("</dd>");
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>\n\n");
|
||||
}
|
||||
|
||||
// Display options and flags
|
||||
let mut options = command
|
||||
.get_arguments()
|
||||
.filter(|arg| !arg.is_positional())
|
||||
.filter(|arg| !arg.is_hide_set())
|
||||
.sorted_by_key(|arg| arg.get_id())
|
||||
.peekable();
|
||||
|
||||
if options.peek().is_some() {
|
||||
output.push_str("<h3 class=\"cli-reference\">Options</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
for opt in options {
|
||||
let Some(long) = opt.get_long() else { continue };
|
||||
let id = format!("{name_key}--{long}");
|
||||
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!("<a href=\"#{id}\"><code>--{long}</code></a>"));
|
||||
for long_alias in opt.get_all_aliases().into_iter().flatten() {
|
||||
output.push_str(&format!(", <code>--{long_alias}</code>"));
|
||||
}
|
||||
if let Some(short) = opt.get_short() {
|
||||
output.push_str(&format!(", <code>-{short}</code>"));
|
||||
}
|
||||
for short_alias in opt.get_all_short_aliases().into_iter().flatten() {
|
||||
output.push_str(&format!(", <code>-{short_alias}</code>"));
|
||||
}
|
||||
|
||||
// Re-implements private `Arg::is_takes_value_set` used in `Command::get_opts`
|
||||
if opt
|
||||
.get_num_args()
|
||||
.unwrap_or_else(|| 1.into())
|
||||
.takes_values()
|
||||
{
|
||||
if let Some(values) = opt.get_value_names() {
|
||||
for value in values {
|
||||
output.push_str(&format!(
|
||||
" <i>{}</i>",
|
||||
value.to_lowercase().replace('_', "-")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push_str("</dt>");
|
||||
if let Some(help) = opt.get_long_help().or_else(|| opt.get_help()) {
|
||||
output.push_str("<dd>");
|
||||
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
|
||||
emit_env_option(opt, output);
|
||||
emit_default_option(opt, output);
|
||||
emit_possible_options(opt, output);
|
||||
output.push_str("</dd>");
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>");
|
||||
}
|
||||
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
parents.push(command);
|
||||
|
||||
// Recurse to all of the subcommands.
|
||||
for subcommand in command.get_subcommands() {
|
||||
generate_command(output, subcommand, parents);
|
||||
}
|
||||
|
||||
parents.pop();
|
||||
}
|
||||
|
||||
fn emit_env_option(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_env_set() {
|
||||
return;
|
||||
}
|
||||
if let Some(env) = opt.get_env() {
|
||||
output.push_str(&markdown::to_html(&format!(
|
||||
"May also be set with the `{}` environment variable.",
|
||||
env.to_string_lossy()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_default_option(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_default_value_set() || !opt.get_num_args().expect("built").takes_values() {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = opt.get_default_values();
|
||||
if !values.is_empty() {
|
||||
let value = format!(
|
||||
"\n[default: {}]",
|
||||
opt.get_default_values()
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.join(",")
|
||||
);
|
||||
output.push_str(&markdown::to_html(&value));
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_possible_options(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_possible_values_set() {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = opt.get_possible_values();
|
||||
if !values.is_empty() {
|
||||
let value = format!(
|
||||
"\nPossible values:\n{}",
|
||||
values
|
||||
.into_iter()
|
||||
.filter(|value| !value.is_hide_set())
|
||||
.map(|value| {
|
||||
let name = value.get_name();
|
||||
value.get_help().map_or_else(
|
||||
|| format!(" - `{name}`"),
|
||||
|help| format!(" - `{name}`: {help}"),
|
||||
)
|
||||
})
|
||||
.collect_vec()
|
||||
.join("\n"),
|
||||
);
|
||||
output.push_str(&markdown::to_html(&value));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
use super::{main, Args};
|
||||
|
||||
#[test]
|
||||
fn ty_cli_reference_is_up_to_date() -> Result<()> {
|
||||
main(&Args { mode: Mode::Check })
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use anyhow::bail;
|
||||
use itertools::Itertools;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Generates the rules table for ty
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write as _;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//!
|
||||
//! Within the ruff repository you can run it with `cargo dev`.
|
||||
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff::{args::GlobalConfigArgs, check};
|
||||
|
@ -15,6 +17,7 @@ mod generate_docs;
|
|||
mod generate_json_schema;
|
||||
mod generate_options;
|
||||
mod generate_rules_table;
|
||||
mod generate_ty_cli_reference;
|
||||
mod generate_ty_options;
|
||||
mod generate_ty_rules;
|
||||
mod generate_ty_schema;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Print the AST for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Print the `LibCST` CST for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Print the token stream for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Run round-trip source code generation on a given Python or Jupyter notebook file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue