2021-03-23 14:14:04 -07:00
//! Global CLI Setup.
2021-12-04 01:03:26 -08:00
use std ::env ;
2022-10-09 15:26:37 -06:00
use clap ::{
2022-11-10 12:51:32 -05:00
builder ::PossibleValue , value_parser , Arg , ArgAction , ArgMatches , ColorChoice ,
Command as ClapCommand , ValueEnum ,
2022-10-09 15:26:37 -06:00
} ;
2022-01-03 10:37:03 -08:00
use clap_complete ::Shell ;
2021-11-18 13:15:20 -08:00
use const_format ::concatcp ;
2021-12-04 01:03:26 -08:00
use env_logger ::fmt ::WriteStyle ;
2021-11-17 22:21:00 -08:00
use lazy_static ::lazy_static ;
2021-03-23 14:14:04 -07:00
use crate ::command ;
2021-11-18 13:15:20 -08:00
/// Base URL of the manual, without the trailing slash.
2022-05-13 19:14:58 -07:00
const MANUAL_URL_BASE : & str = " https://colmena.cli.rs " ;
2021-11-18 13:15:20 -08:00
/// URL to the manual.
///
/// We maintain CLI and Nix API stability for each minor version.
/// This ensures that the user always sees accurate documentations, and we can
/// easily perform updates to the manual after a release.
2022-07-29 22:13:09 -07:00
const MANUAL_URL : & str = concatcp! (
MANUAL_URL_BASE ,
" / " ,
env! ( " CARGO_PKG_VERSION_MAJOR " ) ,
" . " ,
env! ( " CARGO_PKG_VERSION_MINOR " )
) ;
2021-11-18 13:15:20 -08:00
/// The note shown when the user is using a pre-release version.
///
/// API stability cannot be guaranteed for pre-release versions.
/// Links to the version currently in development automatically
/// leads the user to the unstable manual.
2021-11-23 13:33:23 -08:00
const MANUAL_DISCREPANCY_NOTE : & str = " Note: You are using a pre-release version of Colmena, so the supported options may be different from what's in the manual. " ;
2021-11-18 13:15:20 -08:00
2021-11-17 22:21:00 -08:00
lazy_static! {
2021-11-18 13:15:20 -08:00
static ref LONG_ABOUT : String = {
2022-07-29 22:13:09 -07:00
let mut message = format! (
r #" NixOS deployment tool
2021-11-18 13:15:20 -08:00
Colmena helps you deploy to multiple hosts running NixOS .
For more details , read the manual at < { } > .
2022-07-29 22:13:09 -07:00
" #,
MANUAL_URL
) ;
2021-11-18 13:15:20 -08:00
2021-11-23 13:33:23 -08:00
if ! env! ( " CARGO_PKG_VERSION_PRE " ) . is_empty ( ) {
message + = MANUAL_DISCREPANCY_NOTE ;
2021-11-18 13:15:20 -08:00
}
message
} ;
2021-11-17 22:21:00 -08:00
static ref CONFIG_HELP : String = {
2022-07-29 22:13:09 -07:00
format! (
r #" If this argument is not specified, Colmena will search upwards from the current working directory for a file named " flake . nix " or " hive . nix " . This behavior is disabled if --config/-f is given explicitly.
2021-11-17 22:21:00 -08:00
2021-11-18 13:15:20 -08:00
For a sample configuration , check the manual at < { } > .
2022-07-29 22:13:09 -07:00
" #,
MANUAL_URL
)
2021-11-17 22:21:00 -08:00
} ;
}
2021-12-04 01:03:26 -08:00
/// Display order in `--help` for arguments that should be shown first.
///
/// Currently reserved for -f/--config.
const HELP_ORDER_FIRST : usize = 100 ;
/// Display order in `--help` for arguments that are not very important.
const HELP_ORDER_LOW : usize = 2000 ;
2021-03-23 14:14:04 -07:00
macro_rules ! register_command {
( $module :ident , $app :ident ) = > {
$app = $app . subcommand ( command ::$module ::subcommand ( ) ) ;
} ;
}
macro_rules ! handle_command {
( $module :ident , $matches :ident ) = > {
if let Some ( sub_matches ) = $matches . subcommand_matches ( stringify! ( $module ) ) {
2022-07-29 22:13:09 -07:00
crate ::troubleshooter ::run_wrapped ( & $matches , & sub_matches , command ::$module ::run )
. await ;
2021-03-23 14:14:04 -07:00
return ;
}
} ;
( $name :expr , $module :ident , $matches :ident ) = > {
if let Some ( sub_matches ) = $matches . subcommand_matches ( $name ) {
2022-07-29 22:13:09 -07:00
crate ::troubleshooter ::run_wrapped ( & $matches , & sub_matches , command ::$module ::run )
. await ;
2021-03-23 14:14:04 -07:00
return ;
}
} ;
}
2022-10-09 15:26:37 -06:00
/// When to display color.
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
enum ColorWhen {
/// Detect automatically.
Auto ,
/// Always display colors.
Always ,
/// Never display colors.
Never ,
}
impl ValueEnum for ColorWhen {
fn value_variants < ' a > ( ) -> & ' a [ Self ] {
& [ Self ::Auto , Self ::Always , Self ::Never ]
}
fn to_possible_value < ' a > ( & self ) -> Option < PossibleValue > {
match self {
Self ::Auto = > Some ( PossibleValue ::new ( " auto " ) ) ,
Self ::Always = > Some ( PossibleValue ::new ( " always " ) ) ,
Self ::Never = > Some ( PossibleValue ::new ( " never " ) ) ,
}
}
}
pub fn build_cli ( include_internal : bool ) -> ClapCommand {
2021-11-17 22:21:00 -08:00
let version = env! ( " CARGO_PKG_VERSION " ) ;
2022-03-07 22:02:04 -08:00
let mut app = ClapCommand ::new ( " Colmena " )
2021-03-23 14:14:04 -07:00
. bin_name ( " colmena " )
2021-11-17 22:21:00 -08:00
. version ( version )
2021-03-23 14:14:04 -07:00
. author ( " Zhaofeng Li <hello@zhaofeng.li> " )
. about ( " NixOS deployment tool " )
2021-11-18 13:15:20 -08:00
. long_about ( LONG_ABOUT . as_str ( ) )
2022-03-07 22:02:04 -08:00
. arg_required_else_help ( true )
2022-01-03 10:37:03 -08:00
. arg ( Arg ::new ( " config " )
. short ( 'f' )
2021-03-23 14:14:04 -07:00
. long ( " config " )
. value_name ( " CONFIG " )
2021-06-29 01:19:13 -07:00
. help ( " Path to a Hive expression, a flake.nix, or a Nix Flake URI " )
2022-01-03 10:37:03 -08:00
. long_help ( Some ( CONFIG_HELP . as_str ( ) ) )
2021-12-04 01:03:26 -08:00
. display_order ( HELP_ORDER_FIRST )
2021-03-23 14:14:04 -07:00
// The default value is a lie (sort of)!
//
// The default behavior is to search upwards from the
2021-06-29 01:02:43 -07:00
// current working directory for a file named "flake.nix"
// or "hive.nix". This behavior is disabled if --config/-f
// is explicitly supplied by the user (occurrences_of > 0).
2021-03-23 14:14:04 -07:00
. default_value ( " hive.nix " )
. global ( true ) )
2022-01-03 10:37:03 -08:00
. arg ( Arg ::new ( " show-trace " )
2021-03-23 14:14:04 -07:00
. long ( " show-trace " )
. help ( " Show debug information for Nix commands " )
. long_help ( " Passes --show-trace to Nix commands " )
. global ( true )
2022-10-09 15:26:37 -06:00
. num_args ( 0 ) )
2022-08-16 20:15:43 -06:00
. arg ( Arg ::new ( " impure " )
. long ( " impure " )
. help ( " Allow impure expressions " )
. long_help ( " Passes --impure to Nix commands " )
. global ( true )
2022-10-09 15:26:37 -06:00
. num_args ( 0 ) )
2022-11-10 12:51:32 -05:00
. arg ( Arg ::new ( " nix-option " )
. long ( " nix-option " )
. help ( " Passes an arbitrary option to Nix commands " )
. long_help ( r #" Passes arbitrary options to Nix commands
This only works when building locally .
" #)
. global ( true )
. num_args ( 2 )
. value_names ( [ " NAME " , " VALUE " ] )
. action ( ArgAction ::Append ) )
2022-01-03 10:37:03 -08:00
. arg ( Arg ::new ( " color " )
2021-12-04 01:03:26 -08:00
. long ( " color " )
. help ( " When to colorize the output " )
. long_help ( r #" When to colorize the output. By default, Colmena enables colorized output when the terminal supports it.
It ' s also possible to specify the preference using environment variables . See < https ://bixense.com/clicolors>.
" #)
2021-12-04 01:03:26 -08:00
. display_order ( HELP_ORDER_LOW )
2021-12-04 01:03:26 -08:00
. value_name ( " WHEN " )
2022-10-09 15:26:37 -06:00
. value_parser ( value_parser! ( ColorWhen ) )
2021-12-04 01:03:26 -08:00
. default_value ( " auto " )
. global ( true ) ) ;
2021-03-23 14:14:04 -07:00
2021-03-23 14:14:04 -07:00
if include_internal {
2022-07-29 22:13:09 -07:00
app = app . subcommand (
ClapCommand ::new ( " gen-completions " )
. about ( " Generate shell auto-completion files (Internal) " )
. hide ( true )
. arg (
Arg ::new ( " shell " )
. index ( 1 )
2022-08-16 20:15:43 -06:00
. value_parser ( value_parser! ( Shell ) )
2022-07-29 22:13:09 -07:00
. required ( true )
2022-10-09 15:26:37 -06:00
. num_args ( 1 ) ,
2022-07-29 22:13:09 -07:00
) ,
) ;
2021-10-28 17:27:30 -07:00
// deprecated alias
app = app . subcommand ( command ::eval ::deprecated_alias ( ) ) ;
2021-11-16 21:01:33 -08:00
2022-01-01 16:41:35 -08:00
#[ cfg(debug_assertions) ]
2021-11-16 21:01:33 -08:00
register_command! ( test_progress , app ) ;
2021-03-23 14:14:04 -07:00
}
2021-03-23 14:14:04 -07:00
register_command! ( apply , app ) ;
2022-01-01 16:41:35 -08:00
#[ cfg(target_os = " linux " ) ]
2021-03-23 14:14:04 -07:00
register_command! ( apply_local , app ) ;
register_command! ( build , app ) ;
2021-10-28 17:27:30 -07:00
register_command! ( eval , app ) ;
2021-03-23 14:14:04 -07:00
register_command! ( upload_keys , app ) ;
register_command! ( exec , app ) ;
2022-08-16 20:15:43 -06:00
register_command! ( repl , app ) ;
2021-06-29 01:02:43 -07:00
register_command! ( nix_info , app ) ;
2021-03-23 14:14:04 -07:00
2022-01-03 10:37:03 -08:00
// This does _not_ take the --color flag into account (haven't
// parsed yet), only the CLICOLOR environment variable.
if clicolors_control ::colors_enabled ( ) {
app . color ( ColorChoice ::Always )
} else {
app
}
2021-03-23 14:14:04 -07:00
}
pub async fn run ( ) {
2021-03-23 14:14:04 -07:00
let mut app = build_cli ( true ) ;
2021-03-23 14:14:04 -07:00
let matches = app . clone ( ) . get_matches ( ) ;
2022-10-09 15:26:37 -06:00
set_color_pref ( matches . get_one ( " color " ) . unwrap ( ) ) ;
2021-12-04 01:03:26 -08:00
init_logging ( ) ;
2021-03-23 14:14:04 -07:00
handle_command! ( apply , matches ) ;
2022-01-01 16:41:35 -08:00
#[ cfg(target_os = " linux " ) ]
2021-03-23 14:14:04 -07:00
handle_command! ( " apply-local " , apply_local , matches ) ;
handle_command! ( build , matches ) ;
2021-10-28 17:27:30 -07:00
handle_command! ( eval , matches ) ;
2021-03-23 14:14:04 -07:00
handle_command! ( " upload-keys " , upload_keys , matches ) ;
handle_command! ( exec , matches ) ;
2022-08-16 20:15:43 -06:00
handle_command! ( repl , matches ) ;
2021-06-29 01:02:43 -07:00
handle_command! ( " nix-info " , nix_info , matches ) ;
2021-03-23 14:14:04 -07:00
2022-01-01 16:41:35 -08:00
#[ cfg(debug_assertions) ]
2021-11-16 21:01:33 -08:00
handle_command! ( " test-progress " , test_progress , matches ) ;
2021-03-23 14:14:04 -07:00
if let Some ( args ) = matches . subcommand_matches ( " gen-completions " ) {
return gen_completions ( args ) ;
}
2022-01-03 10:37:03 -08:00
// deprecated alias
handle_command! ( " introspect " , eval , matches ) ;
2021-03-23 14:14:04 -07:00
app . print_long_help ( ) . unwrap ( ) ;
println! ( ) ;
}
2021-03-23 14:14:04 -07:00
2022-01-03 10:37:03 -08:00
fn gen_completions ( args : & ArgMatches ) {
2021-03-23 14:14:04 -07:00
let mut app = build_cli ( false ) ;
2022-08-16 20:15:43 -06:00
let shell = args . get_one ::< Shell > ( " shell " ) . unwrap ( ) . to_owned ( ) ;
2021-03-23 14:14:04 -07:00
2022-01-03 10:37:03 -08:00
clap_complete ::generate ( shell , & mut app , " colmena " , & mut std ::io ::stdout ( ) ) ;
2021-03-23 14:14:04 -07:00
}
2021-11-17 22:21:00 -08:00
2022-10-09 15:26:37 -06:00
fn set_color_pref ( when : & ColorWhen ) {
if when ! = & ColorWhen ::Auto {
clicolors_control ::set_colors_enabled ( when = = & ColorWhen ::Always ) ;
2021-12-04 01:03:26 -08:00
}
}
fn init_logging ( ) {
if env ::var ( " RUST_LOG " ) . is_err ( ) {
// HACK
env ::set_var ( " RUST_LOG " , " info " )
}
// make env_logger conform to our detection logic
let style = if clicolors_control ::colors_enabled ( ) {
WriteStyle ::Always
} else {
WriteStyle ::Never
} ;
env_logger ::builder ( )
. format_timestamp ( None )
. format_module_path ( false )
2022-01-05 14:01:05 -08:00
. format_target ( false )
2021-12-04 01:03:26 -08:00
. write_style ( style )
. init ( ) ;
}
2022-10-09 15:26:37 -06:00
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn test_cli_debug_assert ( ) {
build_cli ( true ) . debug_assert ( )
}
}