1// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, 2// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and 3// Ana Hobden (@hoverbear) <operator@hoverbear.org> 4// 5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or 6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license 7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your 8// option. This file may not be copied, modified, or distributed 9// except according to those terms. 10// 11// This work was derived from Structopt (https://github.com/TeXitoi/structopt) 12// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the 13// MIT/Apache 2.0 license. 14 15use crate::utils; 16 17use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; 18 19#[test] 20fn doc_comments() { 21 /// Lorem ipsum 22 #[derive(Parser, PartialEq, Debug)] 23 struct LoremIpsum { 24 /// Fooify a bar 25 /// and a baz 26 #[arg(short, long)] 27 foo: bool, 28 } 29 30 let help = utils::get_long_help::<LoremIpsum>(); 31 assert!(help.contains("Lorem ipsum")); 32 assert!(help.contains("Fooify a bar and a baz")); 33} 34 35#[test] 36fn help_is_better_than_comments() { 37 /// Lorem ipsum 38 #[derive(Parser, PartialEq, Debug)] 39 #[command(name = "lorem-ipsum", about = "Dolor sit amet")] 40 struct LoremIpsum { 41 /// Fooify a bar 42 #[arg(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] 43 foo: bool, 44 } 45 46 let help = utils::get_long_help::<LoremIpsum>(); 47 assert!(help.contains("Dolor sit amet")); 48 assert!(!help.contains("Lorem ipsum")); 49 assert!(help.contains("DO NOT PASS A BAR")); 50} 51 52#[test] 53fn empty_line_in_doc_comment_is_double_linefeed() { 54 /// Foo. 55 /// 56 /// Bar 57 #[derive(Parser, PartialEq, Debug)] 58 #[command(name = "lorem-ipsum")] 59 struct LoremIpsum {} 60 61 let help = utils::get_long_help::<LoremIpsum>(); 62 assert!(help.starts_with( 63 "\ 64Foo. 65 66Bar 67 68Usage:" 69 )); 70} 71 72#[test] 73fn field_long_doc_comment_both_help_long_help() { 74 /// Lorem ipsumclap 75 #[derive(Parser, PartialEq, Debug)] 76 #[command(name = "lorem-ipsum", about = "Dolor sit amet")] 77 struct LoremIpsum { 78 /// Dot is removed from multiline comments. 79 /// 80 /// Long help 81 #[arg(long)] 82 foo: bool, 83 84 /// Dot is removed from one short comment. 85 #[arg(long)] 86 bar: bool, 87 } 88 89 let short_help = utils::get_help::<LoremIpsum>(); 90 let long_help = utils::get_long_help::<LoremIpsum>(); 91 92 assert!(short_help.contains("Dot is removed from one short comment")); 93 assert!(!short_help.contains("Dot is removed from one short comment.")); 94 assert!(short_help.contains("Dot is removed from multiline comments")); 95 assert!(!short_help.contains("Dot is removed from multiline comments.")); 96 assert!(long_help.contains("Long help")); 97 assert!(!short_help.contains("Long help")); 98} 99 100#[test] 101fn top_long_doc_comment_both_help_long_help() { 102 /// Lorem ipsumclap 103 #[derive(Parser, Debug)] 104 #[command(name = "lorem-ipsum", about = "Dolor sit amet")] 105 struct LoremIpsum { 106 #[command(subcommand)] 107 foo: SubCommand, 108 } 109 110 #[derive(Parser, Debug)] 111 pub enum SubCommand { 112 /// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES 113 /// 114 /// Or something else 115 Foo { 116 #[arg(help = "foo")] 117 bars: String, 118 }, 119 } 120 121 let short_help = utils::get_help::<LoremIpsum>(); 122 let long_help = utils::get_subcommand_long_help::<LoremIpsum>("foo"); 123 124 assert!(!short_help.contains("Or something else")); 125 assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")); 126 assert!(long_help.contains("Or something else")); 127} 128 129#[test] 130fn verbatim_doc_comment() { 131 /// DANCE! 132 /// 133 /// () 134 /// | 135 /// ( () ) 136 /// ) ________ // ) 137 /// () |\ \ // 138 /// ( \\__ \ ______\// 139 /// \__) | | 140 /// | | | 141 /// \ | | 142 /// \|_______| 143 /// // \\ 144 /// (( || 145 /// \\ || 146 /// ( () || 147 /// ( () ) ) 148 #[derive(Parser, Debug)] 149 #[command(verbatim_doc_comment)] 150 struct SeeFigure1 { 151 #[arg(long)] 152 foo: bool, 153 } 154 155 let help = utils::get_long_help::<SeeFigure1>(); 156 let sample = r#" 157 () 158 | 159 ( () ) 160 ) ________ // ) 161 () |\ \ // 162( \\__ \ ______\// 163 \__) | | 164 | | | 165 \ | | 166 \|_______| 167 // \\ 168 (( || 169 \\ || 170 ( () || 171 ( () ) )"#; 172 173 assert!(help.contains(sample)) 174} 175 176#[test] 177fn verbatim_doc_comment_field() { 178 #[derive(Parser, Debug)] 179 struct Command { 180 /// This help ends in a period. 181 #[arg(long, verbatim_doc_comment)] 182 foo: bool, 183 /// This help does not end in a period. 184 #[arg(long)] 185 bar: bool, 186 } 187 188 let help = utils::get_long_help::<Command>(); 189 190 assert!(help.contains("This help ends in a period.")); 191 assert!(help.contains("This help does not end in a period")); 192} 193 194#[test] 195fn multiline_separates_default() { 196 #[derive(Parser, Debug)] 197 struct Command { 198 /// Multiline 199 /// 200 /// Doc comment 201 #[arg(long, default_value = "x")] 202 x: String, 203 } 204 205 let help = utils::get_long_help::<Command>(); 206 assert!(!help.contains("Doc comment [default")); 207 assert!(help.lines().any(|s| s.trim().starts_with("[default"))); 208 209 // The short help should still have the default on the same line 210 let help = utils::get_help::<Command>(); 211 assert!(help.contains("Multiline [default")); 212} 213 214#[test] 215fn value_enum_multiline_doc_comment() { 216 #[derive(Parser, Debug)] 217 struct Command { 218 x: LoremIpsum, 219 } 220 221 #[derive(ValueEnum, Clone, PartialEq, Debug)] 222 enum LoremIpsum { 223 /// Doc comment summary 224 /// 225 /// The doc comment body is ignored 226 Bar, 227 } 228 229 let help = utils::get_long_help::<Command>(); 230 231 assert!(help.contains("Doc comment summary")); 232 233 // There is no long help text for possible values. The long help only contains the summary. 234 assert!(!help.contains("The doc comment body is ignored")); 235} 236 237#[test] 238fn doc_comment_about_handles_both_abouts() { 239 /// Opts doc comment summary 240 #[derive(Parser, Debug)] 241 pub struct Opts { 242 #[command(subcommand)] 243 pub cmd: Sub, 244 } 245 246 /// Sub doc comment summary 247 /// 248 /// Sub doc comment body 249 #[derive(Parser, PartialEq, Eq, Debug)] 250 pub enum Sub { 251 Compress { output: String }, 252 } 253 254 let cmd = Opts::command(); 255 assert_eq!( 256 cmd.get_about().map(|s| s.to_string()), 257 Some("Opts doc comment summary".to_owned()) 258 ); 259 // clap will fallback to `about` on `None`. The main care about is not providing a `Sub` doc 260 // comment. 261 assert_eq!(cmd.get_long_about(), None); 262} 263 264#[test] 265fn respect_subcommand_doc_comment() { 266 #[derive(Parser, Debug)] 267 pub enum Cmd { 268 /// For child 269 #[command(subcommand)] 270 Child(Child), 271 } 272 273 #[derive(Subcommand, Debug)] 274 pub enum Child { 275 One, 276 Twp, 277 } 278 279 const OUTPUT: &str = "\ 280Usage: cmd <COMMAND> 281 282Commands: 283 child For child 284 help Print this message or the help of the given subcommand(s) 285 286Options: 287 -h, --help Print help 288"; 289 utils::assert_output::<Cmd>("cmd --help", OUTPUT, false); 290} 291 292#[test] 293fn force_long_help() { 294 /// Lorem ipsum 295 #[derive(Parser, PartialEq, Debug)] 296 struct LoremIpsum { 297 /// Fooify a bar 298 /// and a baz. 299 #[arg(short, long, long_help)] 300 foo: bool, 301 } 302 303 let help = utils::get_long_help::<LoremIpsum>(); 304 assert!(help.contains("Fooify a bar and a baz.")); 305} 306