use std::{
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
use clap::{Parser, ValueEnum};
use clap_cargo::style::CLAP_STYLING;
use crate::config::{BoolOrString, CliConfig, PackageConfigPatch, WorkspaceConfigPatch};
pub struct Cli {
pub cfg: CliConfig,
pub workspace_patch: WorkspaceConfigPatch,
pub package_patch: PackageConfigPatch,
}
impl Cli {
pub fn parse() -> Self {
Self::from_args(&parse_args())
}
fn from_args(args: &Args) -> Self {
let Args {
print_supported_toolchain,
color,
verbose,
quiet,
quiet_cargo,
ref manifest_path,
print_config,
ref package,
workspace,
ref exclude,
command,
ref feature_label,
ref feature_section_name,
ref crate_section_name,
shrink_headings,
link_to_latest,
document_private_items,
no_deps,
check,
allow_missing_section,
allow_dirty,
allow_staged,
ref features,
all_features,
no_default_features,
ref hidden_features,
ref target_selection,
ref toolchain,
ref target,
ref target_dir,
ref readme_path,
..
} = *args;
Self {
cfg: CliConfig {
print_supported_toolchain,
print_config,
color: match color.unwrap_or(ColorChoice::Auto) {
ColorChoice::Auto => anstream::ColorChoice::Auto,
ColorChoice::Always => anstream::ColorChoice::Always,
ColorChoice::Never => anstream::ColorChoice::Never,
},
verbose,
quiet,
quiet_cargo: quiet || quiet_cargo,
manifest_path: manifest_path.clone(),
},
workspace_patch: WorkspaceConfigPatch {
package: (!package.is_empty()).then(|| package.clone()),
workspace: workspace.then_some(true),
exclude: (!exclude.is_empty()).then(|| exclude.clone()),
},
package_patch: PackageConfigPatch {
feature_into_crate: command.map(|c| c == Command::FeatureIntoCrate),
crate_into_readme: command.map(|c| c == Command::CrateIntoReadme),
feature_label: feature_label.clone(),
feature_section_name: feature_section_name.clone(),
crate_section_name: crate_section_name.clone(),
shrink_headings,
link_to_latest: link_to_latest.then_some(true),
document_private_items: document_private_items.then_some(true),
no_deps: no_deps.then_some(true),
check: check.then_some(true),
allow_missing_section: allow_missing_section.then_some(true),
allow_dirty: allow_dirty.then_some(true),
allow_staged: allow_staged.then_some(true),
features: (!features.is_empty()).then(|| {
features.iter().flat_map(|f| f.split(' ').map(|s| s.to_string())).collect()
}),
hidden_features: (!hidden_features.is_empty()).then(|| {
hidden_features
.iter()
.flat_map(|f| f.split(' ').map(|s| s.to_string()))
.collect()
}),
all_features: all_features.then_some(true),
no_default_features: no_default_features.then_some(true),
lib: target_selection.lib.then_some(true),
bin: target_selection.bin.clone().map(|bin| match bin {
Some(name) => BoolOrString::String(name),
None => BoolOrString::Bool(true),
}),
toolchain: toolchain.clone(),
target: target.clone(),
target_dir: target_dir.clone(),
readme_path: readme_path.clone(),
},
}
}
}
fn parse_args() -> Args {
let command = std::env::args_os().next().expect("first argument is missing");
let command = subcommand_name(command.as_os_str());
let command = command.as_ref();
let args_os = std::env::args_os()
.enumerate()
.filter(|(index, arg)| *index != 1 || Some(arg) != command)
.map(|(_, arg)| arg);
Args::parse_from(args_os)
}
fn subcommand_name(bin: &OsStr) -> Option<OsString> {
Some(
Path::new(bin)
.file_name()?
.to_string_lossy()
.strip_prefix("cargo-")?
.strip_suffix(std::env::consts::EXE_SUFFIX)?
.to_string()
.into(),
)
}
mod heading {
pub const PACKAGE_SELECTION: &str = "Package Selection";
pub const TARGET_SELECTION: &str = "Target Selection";
pub const FEATURE_SELECTION: &str = "Feature Selection";
pub const COMPILATION_OPTIONS: &str = "Compilation Options";
pub const MANIFEST_OPTIONS: &str = "Manifest Options";
pub const ERROR_BEHAVIOR: &str = "Error Behavior";
pub const MESSAGE_OPTIONS: &str = "Message Options";
pub const MODE_SELECTION: &str = "Mode Selection";
pub const CARGO_DOC_OPTIONS: &str = "Cargo Doc Options";
}
#[derive(Parser)]
#[command(
version,
about = "Inserts crate docs into a readme file and feature docs into the crate docs.",
long_about = "\
Inserts feature documentation into the crate documentation and the crate documentation into the readme.\n\n\
Website: https://github.com/bluurryy/cargo-insert-docs",
bin_name = "cargo insert-docs",
styles = CLAP_STYLING
)]
struct Args {
#[command(subcommand)]
command: Option<Command>,
#[arg(global = true, long)]
feature_label: Option<String>,
#[arg(global = true, long, value_name = "NAME")]
feature_section_name: Option<String>,
#[arg(global = true, long, value_name = "NAME")]
crate_section_name: Option<String>,
#[arg(global = true, long, value_name = "AMOUNT")]
shrink_headings: Option<i8>,
#[expect(rustdoc::bare_urls)]
#[arg(global = true, long, verbatim_doc_comment)]
link_to_latest: bool,
#[arg(global = true, long)]
print_supported_toolchain: bool,
#[arg(global = true, long)]
print_config: bool,
#[arg(global = true, help_heading = heading::CARGO_DOC_OPTIONS, long)]
document_private_items: bool,
#[arg(global = true, help_heading = heading::CARGO_DOC_OPTIONS, long)]
no_deps: bool,
#[arg(global = true, help_heading = heading::MODE_SELECTION, long, verbatim_doc_comment)]
check: bool,
#[arg(global = true, help_heading = heading::ERROR_BEHAVIOR, long)]
allow_missing_section: bool,
#[arg(global = true, help_heading = heading::ERROR_BEHAVIOR, long)]
allow_dirty: bool,
#[arg(global = true, help_heading = heading::ERROR_BEHAVIOR, long)]
allow_staged: bool,
#[arg(global = true, help_heading = heading::MESSAGE_OPTIONS, long, value_name = "WHEN", value_enum)]
color: Option<ColorChoice>,
#[arg(global = true, help_heading = heading::MESSAGE_OPTIONS, short, long, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(global = true, help_heading = heading::MESSAGE_OPTIONS, long, short = 'q')]
quiet: bool,
#[arg(global = true, help_heading = heading::MESSAGE_OPTIONS, long)]
quiet_cargo: bool,
#[arg(global = true, help_heading = heading::PACKAGE_SELECTION, long, short = 'p', value_name = "SPEC")]
package: Vec<String>,
#[arg(global = true, help_heading = heading::PACKAGE_SELECTION, long)]
workspace: bool,
#[arg(global = true, help_heading = heading::PACKAGE_SELECTION, long, value_name = "SPEC", requires = "workspace")]
exclude: Vec<String>,
#[arg(global = true, help_heading = heading::FEATURE_SELECTION, long, short = 'F', value_delimiter = ',')]
features: Vec<String>,
#[arg(global = true, help_heading = heading::FEATURE_SELECTION, long)]
all_features: bool,
#[arg(global = true, help_heading = heading::FEATURE_SELECTION, long)]
no_default_features: bool,
#[arg(global = true, help_heading = heading::FEATURE_SELECTION, long, value_delimiter = ',', value_name = "FEATURES")]
hidden_features: Vec<String>,
#[command(flatten)]
target_selection: TargetSelection,
#[arg(global = true, help_heading = heading::COMPILATION_OPTIONS, long, verbatim_doc_comment)]
toolchain: Option<String>,
#[arg(global = true, help_heading = heading::COMPILATION_OPTIONS, long, value_name = "TRIPLE")]
target: Option<String>,
#[arg(global = true, help_heading = heading::COMPILATION_OPTIONS, long, value_name = "DIRECTORY")]
target_dir: Option<PathBuf>,
#[arg(global = true, help_heading = heading::MANIFEST_OPTIONS, long, value_name = "PATH")]
manifest_path: Option<PathBuf>,
#[arg(global = true, help_heading = heading::MANIFEST_OPTIONS, long, value_name = "PATH")]
readme_path: Option<PathBuf>,
}
#[derive(clap::Subcommand, Clone, Copy, PartialEq, Eq)]
enum Command {
FeatureIntoCrate,
CrateIntoReadme,
}
#[derive(clap::Args)]
#[group(multiple = false)]
struct TargetSelection {
#[arg(help_heading = heading::TARGET_SELECTION, long)]
lib: bool,
#[arg(help_heading = heading::TARGET_SELECTION, long, value_name = "NAME")]
bin: Option<Option<String>>,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum ColorChoice {
Auto,
Always,
Never,
}