2023-02-21 14:43:43 +03:00
use std ::env ;
use std ::fs ;
2024-01-05 15:37:36 +03:00
use std ::path ::PathBuf ;
2024-01-03 18:29:20 +03:00
use clap ::ValueEnum ;
2023-02-21 14:43:43 +03:00
use clap_complete ::Shell ;
2024-01-10 19:59:01 +03:00
use anyhow ::Result ;
2023-02-21 14:43:43 +03:00
2023-10-20 14:04:00 +03:00
pub mod cli {
2023-11-30 12:22:15 +03:00
#![ allow(unused_macros) ]
2023-11-29 19:54:40 +03:00
include! ( " src/macros.rs " ) ;
2023-10-20 14:04:00 +03:00
include! ( " src/cli/mod.rs " ) ;
2023-02-21 14:43:43 +03:00
}
2024-01-05 15:37:36 +03:00
pub mod man {
include! ( " src/man.rs " ) ;
}
2023-02-21 14:43:43 +03:00
fn main ( ) {
println! ( " cargo:rerun-if-changed=build.rs " ) ;
// Generate subplot tests.
2023-04-15 13:04:53 +03:00
#[ cfg(feature = " subplot " ) ]
2024-01-05 15:37:36 +03:00
subplot_build ::codegen ( " sq.subplot " )
2023-02-21 14:43:43 +03:00
. expect ( " failed to generate code with Subplot " ) ;
2024-02-21 13:35:30 +03:00
let mut sq = cli ::build ( false ) ;
2024-01-03 18:29:20 +03:00
generate_shell_completions ( & mut sq ) . unwrap ( ) ;
2024-01-03 18:43:31 +03:00
generate_man_pages ( & sq ) . unwrap ( ) ;
2024-12-08 16:58:36 +03:00
lint_help_texts ( & sq ) . unwrap ( ) ;
2024-01-03 18:29:20 +03:00
}
2023-02-21 14:43:43 +03:00
2024-01-05 17:57:05 +03:00
/// Variable name to control the asset out directory with.
const ASSET_OUT_DIR : & str = " ASSET_OUT_DIR " ;
2024-01-05 17:45:54 +03:00
/// Returns the directory to write the given assets to.
fn asset_out_dir ( asset : & str ) -> Result < PathBuf > {
2024-01-05 17:57:05 +03:00
println! ( " cargo:rerun-if-env-changed= {} " , ASSET_OUT_DIR ) ;
2024-01-03 18:29:20 +03:00
let outdir : PathBuf =
2024-01-05 17:57:05 +03:00
env ::var_os ( ASSET_OUT_DIR ) . unwrap_or_else (
| | env ::var_os ( " OUT_DIR " ) . expect ( " OUT_DIR not set " ) ) . into ( ) ;
if outdir . exists ( ) & & ! outdir . is_dir ( ) {
return Err (
anyhow ::anyhow! ( " {}={:?} is not a directory " ,
ASSET_OUT_DIR , outdir ) ) ;
}
2024-01-05 17:45:54 +03:00
let path = outdir . join ( asset ) ;
2024-01-03 18:29:20 +03:00
fs ::create_dir_all ( & path ) ? ;
2024-01-05 17:45:54 +03:00
Ok ( path )
}
/// Generates shell completions.
fn generate_shell_completions ( sq : & mut clap ::Command ) -> Result < ( ) > {
let path = asset_out_dir ( " shell-completions " ) ? ;
2023-02-21 14:43:43 +03:00
2024-01-03 18:29:20 +03:00
for shell in Shell ::value_variants ( ) {
clap_complete ::generate_to ( * shell , sq , " sq " , & path ) ? ;
2023-02-21 14:43:43 +03:00
} ;
2023-04-08 10:49:58 +03:00
2024-01-03 18:29:20 +03:00
println! ( " cargo:warning=shell completions written to {} " , path . display ( ) ) ;
Ok ( ( ) )
2023-02-21 14:43:43 +03:00
}
2024-01-03 18:43:31 +03:00
/// Generates man pages.
fn generate_man_pages ( sq : & clap ::Command ) -> Result < ( ) > {
2024-01-05 17:45:54 +03:00
let path = asset_out_dir ( " man-pages " ) ? ;
2023-04-08 10:49:58 +03:00
2024-01-05 15:37:36 +03:00
for man in man ::manpages ( sq ) {
std ::fs ::write ( path . join ( man . filename ( ) ) , man . troff_source ( ) ) ? ;
2023-04-08 10:49:58 +03:00
}
2024-01-03 18:43:31 +03:00
println! ( " cargo:warning=man pages written to {} " , path . display ( ) ) ;
2023-04-08 10:49:58 +03:00
Ok ( ( ) )
}
2024-12-08 16:58:36 +03:00
/// Lints the help texts.
fn lint_help_texts ( sq : & clap ::Command ) -> Result < ( ) > {
let mut lints = Vec ::new ( ) ;
walk ( & mut Vec ::new ( ) , sq ,
& mut | path : & [ & str ] , c : & clap ::Command | {
let top_level = path . len ( ) = = 1 ;
let path = path . join ( " " ) ;
lint_short_long ( & mut lints , & path ,
c . get_about ( ) , c . get_long_about ( ) ) ;
for arg in c . get_arguments ( )
. filter ( | a | a . get_id ( ) . as_str ( ) ! = " help " )
. filter ( | a | ! a . is_global_set ( ) | | top_level )
{
let slug = format! ( " {} {} " , path ,
if let Some ( l ) = arg . get_long ( ) {
format! ( " -- {} " , l )
} else if let Some ( s ) = arg . get_short ( ) {
format! ( " - {} " , s )
} else {
arg . get_id ( ) . as_str ( ) . to_string ( )
} ) ;
lint_short_long ( & mut lints , & slug ,
arg . get_help ( ) , arg . get_long_help ( ) ) ;
}
Ok ( ( ) )
} ,
& mut | _ , _ , _ | Ok ( ( ) ) ) ? ;
if lints . is_empty ( ) {
Ok ( ( ) )
} else {
println! ( " cargo:warning=Linting help texts found {} issues " ,
lints . len ( ) ) ;
println! ( " cargo:warning= " ) ;
for lint in lints {
println! ( " cargo:warning=lint: {} " , lint ) ;
}
Err ( anyhow ::anyhow! ( " linting help texts failed " ) )
}
}
use clap ::builder ::StyledStr ;
/// Lints short and long about and help texts.
fn lint_short_long ( lints : & mut Vec < String > ,
slug : & str ,
short : Option < & StyledStr > ,
long : Option < & StyledStr > )
{
let short = if let Some ( short ) = short {
short . to_string ( )
} else {
lints . push ( format! ( " {} : no short help " , slug ) ) ;
return ;
} ;
if short . contains ( " . " ) {
lints . push ( format! ( " {} : short contains more than one sentence " , slug ) ) ;
}
if short . ends_with ( " . " ) {
lints . push ( format! ( " {} : short ends in period " , slug ) ) ;
}
let long = if let Some ( long ) = long {
long . to_string ( )
} else {
return ;
} ;
if ! long . starts_with ( & format! ( " {} \n \n " , short ) ) {
lints . push ( format! ( " {} : long help doesn't start with subject (short help + \\ n \\ n) " , slug ) ) ;
}
if long . split ( " \n " ) . count ( ) = = 1 {
lints . push ( format! ( " {} : long help consists of a single line " , slug ) ) ;
}
}
fn walk < ' sq , F , G , R > ( path : & mut Vec < & ' sq str > , c : & ' sq clap ::Command ,
fun0 : & mut F ,
fun1 : & mut G )
-> Result < R >
where
F : FnMut ( & [ & str ] , & clap ::Command ) -> Result < ( ) > ,
G : FnMut ( & [ & str ] , & clap ::Command , & [ R ] ) -> Result < R > ,
{
path . push ( c . get_name ( ) ) ;
( fun0 ) ( & path , c ) ? ;
let mut r = Vec ::new ( ) ;
for s in c . get_subcommands ( ) . filter ( | cmd | cmd . get_name ( ) ! = " help " ) {
r . push ( walk ( path , s , fun0 , fun1 ) ? ) ;
}
let r = ( fun1 ) ( & path , c , & r ) ? ;
path . pop ( ) ;
Ok ( r )
}