55
66use anyhow:: Result ;
77use clap:: { CommandFactory , crate_authors} ;
8+ use clap_mangen:: {
9+ Man ,
10+ roff:: { Roff , roman} ,
11+ } ;
812use serde:: Deserialize ;
913use strum:: { Display , EnumIter , EnumString , VariantNames } ;
1014
1115use crate :: LycheeOptions ;
1216
1317const CONTRIBUTOR_THANK_NOTE : & str = "\n \n A huge thank you to all the wonderful contributors who helped make this project a success." ;
18+ const EXIT_CODE_DESCRIPTION : & ' static str = "
19+ 0 Success. The operation was completed successfully as instructed.
20+
21+ 1 Missing inputs or any unexpected runtime failures or configuration errors
22+
23+ 2 Link check failures. At least one non-excluded link failed the check.
24+
25+ 3 Encountered errors in the config file.
26+ " ;
1427
1528/// What to generate when provided the --generate flag
1629#[ derive( Debug , Deserialize , Clone , Display , EnumIter , EnumString , VariantNames , PartialEq ) ]
@@ -33,22 +46,38 @@ fn man_page() -> Result<String> {
3346 let date = chrono:: offset:: Local :: now ( ) . format ( "%Y-%m-%d" ) ;
3447 let authors = crate_authors ! ( "\n \n " ) . to_owned ( ) + CONTRIBUTOR_THANK_NOTE ;
3548
36- let man =
37- clap_mangen:: Man :: new ( LycheeOptions :: command ( ) . author ( authors) ) . date ( format ! ( "{date}" ) ) ;
38-
49+ let man = Man :: new ( LycheeOptions :: command ( ) . author ( authors) ) . date ( format ! ( "{date}" ) ) ;
3950 let mut buffer: Vec < u8 > = Vec :: default ( ) ;
40- man. render ( & mut buffer) ?;
51+
52+ // Manually customise `Man::render` (see https://github.com/clap-rs/clap/issues/3354)
53+ man. render_title ( & mut buffer) ?;
54+ man. render_name_section ( & mut buffer) ?;
55+ man. render_synopsis_section ( & mut buffer) ?;
56+ man. render_description_section ( & mut buffer) ?;
57+ man. render_options_section ( & mut buffer) ?;
58+ render_exit_codes ( & mut buffer) ?;
59+ man. render_version_section ( & mut buffer) ?;
60+ man. render_authors_section ( & mut buffer) ?;
4161
4262 Ok ( std:: str:: from_utf8 ( & buffer) ?. to_owned ( ) )
4363}
4464
65+ fn render_exit_codes ( buffer : & mut Vec < u8 > ) -> Result < ( ) , anyhow:: Error > {
66+ let mut roff = Roff :: default ( ) ;
67+ roff. control ( "SH" , [ "EXIT CODES" ] ) ;
68+ roff. text ( [ roman ( EXIT_CODE_DESCRIPTION ) ] ) ;
69+ roff. to_writer ( buffer) ?;
70+ Ok ( ( ) )
71+ }
72+
4573#[ cfg( test) ]
4674mod tests {
4775 use super :: man_page;
76+ use crate :: generate:: EXIT_CODE_DESCRIPTION ;
4877 use anyhow:: Result ;
4978
5079 #[ test]
51- fn test_man_pages ( ) -> Result < ( ) > {
80+ fn test_man_page ( ) -> Result < ( ) > {
5281 let roff = man_page ( ) ?;
5382
5483 // Must contain description
@@ -67,4 +96,34 @@ mod tests {
6796 assert_eq ! ( roff. matches( "\\ -\\ -version" ) . count( ) , 2 ) ;
6897 Ok ( ( ) )
6998 }
99+
100+ /// Test that the Exit Codes section in `README.md` is up to date with
101+ /// lychee's manual page.
102+ #[ test]
103+ #[ cfg( unix) ]
104+ fn test_readme_exit_codes_up_to_date ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
105+ use test_utils:: load_readme_text;
106+
107+ const BEGIN : & str = "### Exit codes" ;
108+ const END : & str = "# " ;
109+
110+ let readme = load_readme_text ! ( ) ;
111+ let start = readme. find ( BEGIN ) . ok_or ( "Beginning not found in README" ) ? + BEGIN . len ( ) ;
112+ let end = readme[ start..] . find ( END ) . ok_or ( "End not found in README" ) ? - END . len ( ) ;
113+
114+ let section = & readme[ start..start + end] ;
115+ assert_eq ! (
116+ filter_empty_lines( section) ,
117+ filter_empty_lines( EXIT_CODE_DESCRIPTION )
118+ ) ;
119+
120+ Ok ( ( ) )
121+ }
122+
123+ fn filter_empty_lines ( s : & str ) -> String {
124+ s. lines ( )
125+ . filter ( |line| !line. trim ( ) . is_empty ( ) )
126+ . collect :: < Vec < _ > > ( )
127+ . join ( "\n " )
128+ }
70129}
0 commit comments