@@ -6,8 +6,7 @@ use std::str::FromStr;
66use anyhow:: { anyhow, Error , Result } ;
77use clap:: {
88 builder:: { EnumValueParser , PossibleValue , PossibleValuesParser } ,
9- Arg , ArgAction , ArgGroup , ArgMatches , Args , Command , FromArgMatches as _, Parser , Subcommand ,
10- ValueEnum ,
9+ Arg , ArgAction , ArgMatches , Args , Command , FromArgMatches as _, Parser , Subcommand , ValueEnum ,
1110} ;
1211use clap_complete:: Shell ;
1312use itertools:: Itertools ;
@@ -187,6 +186,26 @@ enum RustupSubcmd {
187186 #[ arg( long, help = RESOLVABLE_TOOLCHAIN_ARG_HELP ) ]
188187 toolchain : Option < ResolvableToolchainName > ,
189188 } ,
189+
190+ /// Open the documentation for the current toolchain
191+ #[ command(
192+ alias = "docs" ,
193+ after_help = DOC_HELP ,
194+ ) ]
195+ Doc {
196+ /// Only print the path to the documentation
197+ #[ arg( long) ]
198+ path : bool ,
199+
200+ #[ arg( long, help = OFFICIAL_TOOLCHAIN_ARG_HELP ) ]
201+ toolchain : Option < PartialToolchainDesc > ,
202+
203+ #[ arg( help = TOPIC_ARG_HELP ) ]
204+ topic : Option < String > ,
205+
206+ #[ command( flatten) ]
207+ page : DocPage ,
208+ } ,
190209}
191210
192211#[ derive( Debug , Subcommand ) ]
@@ -479,6 +498,12 @@ impl Rustup {
479498 install,
480499 } => run ( cfg, toolchain, command, install) ,
481500 RustupSubcmd :: Which { command, toolchain } => which ( cfg, & command, toolchain) ,
501+ RustupSubcmd :: Doc {
502+ path,
503+ toolchain,
504+ topic,
505+ page,
506+ } => doc ( cfg, path, toolchain, topic. as_deref ( ) , & page) ,
482507 }
483508 }
484509}
@@ -556,10 +581,10 @@ pub fn main() -> Result<utils::ExitCode> {
556581 Some ( s) => match s {
557582 (
558583 "dump-testament" | "show" | "update" | "install" | "uninstall" | "toolchain"
559- | "check" | "default" | "target" | "component" | "override" | "run" | "which" ,
584+ | "check" | "default" | "target" | "component" | "override" | "run" | "which"
585+ | "doc" ,
560586 _,
561587 ) => Rustup :: from_arg_matches ( & matches) ?. dispatch ( cfg) ?,
562- ( "doc" , m) => doc ( cfg, m) ?,
563588 #[ cfg( not( windows) ) ]
564589 ( "man" , m) => man ( cfg, m) ?,
565590 ( "self" , c) => match c. subcommand ( ) {
@@ -629,45 +654,6 @@ pub(crate) fn cli() -> Command {
629654 Err ( Error :: raw ( ErrorKind :: InvalidSubcommand , format ! ( "\" {s}\" is not a valid subcommand, so it was interpreted as a toolchain name, but it is also invalid. {TOOLCHAIN_OVERRIDE_ERROR}" ) ) )
630655 }
631656 } ) ,
632- )
633- . subcommand (
634- Command :: new ( "doc" )
635- . alias ( "docs" )
636- . about ( "Open the documentation for the current toolchain" )
637- . after_help ( DOC_HELP )
638- . arg (
639- Arg :: new ( "path" )
640- . long ( "path" )
641- . help ( "Only print the path to the documentation" )
642- . action ( ArgAction :: SetTrue ) ,
643- )
644- . arg (
645- Arg :: new ( "toolchain" )
646- . help ( OFFICIAL_TOOLCHAIN_ARG_HELP )
647- . long ( "toolchain" )
648- . num_args ( 1 )
649- . value_parser ( partial_toolchain_desc_parser) ,
650- )
651- . arg ( Arg :: new ( "topic" ) . help ( TOPIC_ARG_HELP ) )
652- . group (
653- ArgGroup :: new ( "page" ) . args (
654- DOCS_DATA
655- . iter ( )
656- . map ( |( name, _, _) | * name)
657- . collect :: < Vec < _ > > ( ) ,
658- ) ,
659- )
660- . args (
661- & DOCS_DATA
662- . iter ( )
663- . map ( |& ( name, help_msg, _) | {
664- Arg :: new ( name)
665- . long ( name)
666- . help ( help_msg)
667- . action ( ArgAction :: SetTrue )
668- } )
669- . collect :: < Vec < _ > > ( ) ,
670- ) ,
671657 ) ;
672658
673659 if cfg ! ( not( target_os = "windows" ) ) {
@@ -1444,28 +1430,67 @@ fn override_remove(cfg: &Cfg, path: Option<&Path>, nonexistent: bool) -> Result<
14441430 Ok ( utils:: ExitCode ( 0 ) )
14451431}
14461432
1447- const DOCS_DATA : & [ ( & str , & str , & str ) ] = & [
1433+ macro_rules! docs_data {
1434+ (
1435+ $(
1436+ $( #[ $meta: meta] ) *
1437+ ( $ident: ident, $help: expr, $path: expr $( , ) ?)
1438+ ) ,+ $( , ) ?
1439+ ) => {
1440+ #[ derive( Debug , Args ) ]
1441+ struct DocPage {
1442+ $(
1443+ #[ doc = $help]
1444+ #[ arg( long, group = "page" ) ]
1445+ $( #[ $meta] ) *
1446+ $ident: bool ,
1447+ ) +
1448+ }
1449+
1450+ impl DocPage {
1451+ fn name( & self ) -> Option <& ' static str > {
1452+ Some ( self . path( ) ?. rsplit_once( '/' ) ?. 0 )
1453+ }
1454+
1455+ fn path( & self ) -> Option <& ' static str > {
1456+ $( if self . $ident { return Some ( $path) ; } ) +
1457+ None
1458+ }
1459+ }
1460+ } ;
1461+ }
1462+
1463+ docs_data ! [
14481464 // flags can be used to open specific documents, e.g. `rustup doc --nomicon`
14491465 // tuple elements: document name used as flag, help message, document index path
1450- ( "alloc" , "The Rust core allocation and collections library" , "alloc/index.html" ) ,
1451- ( "book" , "The Rust Programming Language book" , "book/index.html" ) ,
1452- ( "cargo" , "The Cargo Book" , "cargo/index.html" ) ,
1453- ( "core" , "The Rust Core Library" , "core/index.html" ) ,
1454- ( "edition-guide" , "The Rust Edition Guide" , "edition-guide/index.html" ) ,
1455- ( "nomicon" , "The Dark Arts of Advanced and Unsafe Rust Programming" , "nomicon/index.html" ) ,
1456- ( "proc_macro" , "A support library for macro authors when defining new macros" , "proc_macro/index.html" ) ,
1457- ( "reference" , "The Rust Reference" , "reference/index.html" ) ,
1458- ( "rust-by-example" , "A collection of runnable examples that illustrate various Rust concepts and standard libraries" , "rust-by-example/index.html" ) ,
1459- ( "rustc" , "The compiler for the Rust programming language" , "rustc/index.html" ) ,
1460- ( "rustdoc" , "Documentation generator for Rust projects" , "rustdoc/index.html" ) ,
1461- ( "std" , "Standard library API documentation" , "std/index.html" ) ,
1462- ( "test" , "Support code for rustc's built in unit-test and micro-benchmarking framework" , "test/index.html" ) ,
1463- ( "unstable-book" , "The Unstable Book" , "unstable-book/index.html" ) ,
1464- ( "embedded-book" , "The Embedded Rust Book" , "embedded-book/index.html" ) ,
1466+ ( alloc, "The Rust core allocation and collections library" , "alloc/index.html" ) ,
1467+ ( book, "The Rust Programming Language book" , "book/index.html" ) ,
1468+ ( cargo, "The Cargo Book" , "cargo/index.html" ) ,
1469+ ( core, "The Rust Core Library" , "core/index.html" ) ,
1470+ ( edition_guide, "The Rust Edition Guide" , "edition-guide/index.html" ) ,
1471+ ( nomicon, "The Dark Arts of Advanced and Unsafe Rust Programming" , "nomicon/index.html" ) ,
1472+
1473+ #[ arg( long = "proc_macro" ) ]
1474+ ( proc_macro, "A support library for macro authors when defining new macros" , "proc_macro/index.html" ) ,
1475+
1476+ ( reference, "The Rust Reference" , "reference/index.html" ) ,
1477+ ( rust_by_example, "A collection of runnable examples that illustrate various Rust concepts and standard libraries" , "rust-by-example/index.html" ) ,
1478+ ( rustc, "The compiler for the Rust programming language" , "rustc/index.html" ) ,
1479+ ( rustdoc, "Documentation generator for Rust projects" , "rustdoc/index.html" ) ,
1480+ ( std, "Standard library API documentation" , "std/index.html" ) ,
1481+ ( test, "Support code for rustc's built in unit-test and micro-benchmarking framework" , "test/index.html" ) ,
1482+ ( unstable_book, "The Unstable Book" , "unstable-book/index.html" ) ,
1483+ ( embedded_book, "The Embedded Rust Book" , "embedded-book/index.html" ) ,
14651484] ;
14661485
1467- fn doc ( cfg : & Cfg , m : & ArgMatches ) -> Result < utils:: ExitCode > {
1468- let toolchain = explicit_desc_or_dir_toolchain_old ( cfg, m) ?;
1486+ fn doc (
1487+ cfg : & Cfg ,
1488+ path_only : bool ,
1489+ toolchain : Option < PartialToolchainDesc > ,
1490+ mut topic : Option < & str > ,
1491+ doc_page : & DocPage ,
1492+ ) -> Result < utils:: ExitCode > {
1493+ let toolchain = explicit_desc_or_dir_toolchain ( cfg, toolchain) ?;
14691494
14701495 if let Ok ( distributable) = DistributableToolchain :: try_from ( & toolchain) {
14711496 if let [ _] = distributable
@@ -1493,24 +1518,21 @@ fn doc(cfg: &Cfg, m: &ArgMatches) -> Result<utils::ExitCode> {
14931518 } ;
14941519
14951520 let topical_path: PathBuf ;
1496- let mut doc_name = m. get_one :: < String > ( "topic" ) . map ( |s| s. as_str ( ) ) ;
14971521
1498- let doc_url = if let Some ( topic) = doc_name {
1522+ let doc_url = if let Some ( topic) = topic {
14991523 topical_path = topical_doc:: local_path ( & toolchain. doc_path ( "" ) . unwrap ( ) , topic) ?;
15001524 topical_path. to_str ( ) . unwrap ( )
1501- } else if let Some ( ( name, _, path) ) = DOCS_DATA . iter ( ) . find ( |( name, _, _) | m. get_flag ( name) ) {
1502- doc_name = Some ( name) ;
1503- path
15041525 } else {
1505- "index.html"
1526+ topic = doc_page. name ( ) ;
1527+ doc_page. path ( ) . unwrap_or ( "index.html" )
15061528 } ;
15071529
1508- if m . get_flag ( "path" ) {
1530+ if path_only {
15091531 let doc_path = toolchain. doc_path ( doc_url) ?;
15101532 writeln ! ( process( ) . stdout( ) . lock( ) , "{}" , doc_path. display( ) ) ?;
15111533 Ok ( utils:: ExitCode ( 0 ) )
15121534 } else {
1513- if let Some ( name) = doc_name {
1535+ if let Some ( name) = topic {
15141536 writeln ! (
15151537 process( ) . stderr( ) . lock( ) ,
15161538 "Opening docs named `{name}` in your browser"
0 commit comments