killswitch/cli/commands/
mod.rs1use clap::{
2 Arg, ArgAction, ColorChoice, Command,
3 builder::styling::{AnsiColor, Effects, Styles},
4};
5
6pub mod built_info {
7 #![allow(clippy::doc_markdown)]
8 include!(concat!(env!("OUT_DIR"), "/built.rs"));
9}
10
11#[must_use]
12pub fn new() -> Command {
13 let styles = Styles::styled()
14 .header(AnsiColor::Yellow.on_default() | Effects::BOLD)
15 .usage(AnsiColor::Green.on_default() | Effects::BOLD)
16 .literal(AnsiColor::Blue.on_default() | Effects::BOLD)
17 .placeholder(AnsiColor::Green.on_default());
18
19 let git_hash = built_info::GIT_COMMIT_HASH.unwrap_or("unknown");
20 let long_version: &'static str =
21 Box::leak(format!("{} - {}", env!("CARGO_PKG_VERSION"), git_hash).into_boxed_str());
22
23 Command::new(env!("CARGO_PKG_NAME"))
24 .about(env!("CARGO_PKG_DESCRIPTION"))
25 .version(env!("CARGO_PKG_VERSION"))
26 .author(env!("CARGO_PKG_AUTHORS"))
27 .color(ColorChoice::Auto)
28 .long_version(long_version)
29 .styles(styles)
30 .arg(
31 Arg::new("enable")
32 .short('e')
33 .long("enable")
34 .help("Enable the VPN kill switch")
35 .action(ArgAction::SetTrue)
36 .conflicts_with("disable"),
37 )
38 .arg(
39 Arg::new("disable")
40 .short('d')
41 .long("disable")
42 .help("Disable the VPN kill switch")
43 .action(ArgAction::SetTrue)
44 .conflicts_with("enable"),
45 )
46 .arg(
47 Arg::new("status")
48 .short('s')
49 .long("status")
50 .help("Show kill switch status")
51 .action(ArgAction::SetTrue)
52 .conflicts_with_all(["enable", "disable"]),
53 )
54 .arg(
55 Arg::new("ipv4")
56 .long("ipv4")
57 .help("VPN peer IPv4 address (auto-detected if not specified)")
58 .value_name("IP")
59 .conflicts_with_all(["disable", "status"]),
60 )
61 .arg(
62 Arg::new("leak")
63 .long("leak")
64 .help("Allow ICMP (ping) and DNS requests outside the VPN")
65 .action(ArgAction::SetTrue)
66 .conflicts_with_all(["disable", "status"]),
67 )
68 .arg(
69 Arg::new("local")
70 .long("local")
71 .help("Allow local network traffic")
72 .action(ArgAction::SetTrue)
73 .conflicts_with_all(["disable", "status"]),
74 )
75 .arg(
76 Arg::new("print")
77 .short('p')
78 .long("print")
79 .help("Print the pf firewall rules without applying them")
80 .action(ArgAction::SetTrue)
81 .conflicts_with_all(["disable", "status"]),
82 )
83 .arg(
84 Arg::new("verbose")
85 .short('v')
86 .long("verbose")
87 .help("Increase output verbosity (-v: verbose, -vv: debug)")
88 .action(ArgAction::Count),
89 )
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn verify_command() {
98 new().debug_assert();
99 }
100
101 #[allow(clippy::unwrap_used)]
102 #[test]
103 fn test_command_metadata() {
104 let cmd = new();
105 assert_eq!(cmd.get_name(), env!("CARGO_PKG_NAME"));
106 assert_eq!(cmd.get_version().unwrap(), env!("CARGO_PKG_VERSION"));
107 }
108
109 #[test]
110 fn test_enable_flag() {
111 let matches = new().get_matches_from(vec!["killswitch", "--enable"]);
112 assert!(matches.get_flag("enable"));
113 }
114
115 #[test]
116 fn test_disable_flag() {
117 let matches = new().get_matches_from(vec!["killswitch", "-d"]);
118 assert!(matches.get_flag("disable"));
119 }
120
121 #[test]
122 fn test_verbose_count() {
123 let matches = new().get_matches_from(vec!["killswitch", "-vvv"]);
124 assert_eq!(matches.get_count("verbose"), 3);
125 }
126}