1use crate::{
20 config::{DEFAULT_SLEEP_DURATION, DEFAULT_SLEEP_DURATION_SHORT},
21 types::{
22 AdjustmentMethodType, Brightness, BrightnessRange, Gamma, GammaRange,
23 LocationProviderType, Temperature, TemperatureRange, TransitionScheme,
24 MAX_TEMPERATURE, MIN_TEMPERATURE,
25 },
26};
27use anstream::ColorChoice;
28use clap::{
29 ArgAction, Args, ColorChoice as ClapColorChoice, Command, CommandFactory,
30 Parser, Subcommand,
31};
32use const_format::formatcp;
33use std::{cmp::Ordering, marker::PhantomData, path::PathBuf, str::FromStr};
34use tracing::{level_filters::LevelFilter, Level};
35
36const VERSION: &str = {
37 const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
38 const GIT_DESCRIBE: &str = env!("VERGEN_GIT_DESCRIBE");
39 const GIT_COMMIT_DATE: &str = env!("VERGEN_GIT_COMMIT_DATE");
40
41 #[allow(clippy::const_is_empty)]
42 if GIT_DESCRIBE.is_empty() {
43 formatcp!("{PKG_VERSION}")
44 } else {
45 formatcp!("{PKG_VERSION} ({GIT_DESCRIBE} {GIT_COMMIT_DATE})")
46 }
47};
48
49const LONG_VERSION: &str = {
50 const RUSTC_SEMVER: &str = env!("VERGEN_RUSTC_SEMVER");
51 const RUSTC_HOST_TRIPLE: &str = env!("VERGEN_RUSTC_HOST_TRIPLE");
52 const CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES");
53 const CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE");
54
55 formatcp!(
56 "{VERSION}
57
58rustc version: {RUSTC_SEMVER}
59rustc host triple: {RUSTC_HOST_TRIPLE}
60cargo features: {CARGO_FEATURES}
61cargo target triple: {CARGO_TARGET_TRIPLE}"
62 )
63};
64
65#[derive(Debug, Parser)]
66#[command(about, version = VERSION, long_version = LONG_VERSION)]
67#[command(propagate_version = true, next_line_help(false))]
68pub struct CliArgs {
69 #[command(subcommand)]
70 pub mode: ModeArgs,
71
72 #[arg(long, value_name = "WHEN", value_parser = ClapColorChoice::from_str)]
74 #[arg(global = true, display_order(100))]
75 pub color: Option<ClapColorChoice>,
76
77 #[command(flatten)]
78 pub verbosity: Verbosity<InfoLevel>,
79}
80
81#[derive(Debug, Subcommand)]
82pub enum ModeArgs {
83 #[command(next_line_help(true))]
85 Daemon {
86 #[command(flatten)]
87 c: CmdArgs,
88
89 #[arg(verbatim_doc_comment)]
94 #[arg(long, action = ArgAction::SetTrue)]
95 disable_fade: Option<bool>,
96
97 #[arg(help = formatcp!("Duration of sleep between screen updates [default: {DEFAULT_SLEEP_DURATION}]"))]
98 #[arg(long, value_name = "MILLISECONDS")]
99 sleep_duration: Option<u16>,
100
101 #[arg(help = formatcp!("Duration of sleep between screen updates for fade [default: {DEFAULT_SLEEP_DURATION_SHORT}]"))]
102 #[arg(long, value_name = "MILLISECONDS")]
103 sleep_duration_short: Option<u16>,
104 },
105
106 #[command(next_line_help(true))]
108 Oneshot {
109 #[command(flatten)]
110 c: CmdArgs,
111 },
112
113 #[command(next_line_help(true))]
115 Set {
116 #[command(flatten)]
117 cs: ColorSettingsArgs,
118 #[command(flatten)]
119 i: CmdInnerArgs,
120 },
121
122 #[command(next_line_help(true))]
124 Reset {
125 #[command(flatten)]
126 i: CmdInnerArgs,
127 },
128
129 #[command(next_line_help(true))]
131 Print {
132 #[arg(verbatim_doc_comment)]
139 #[arg(long, short, value_parser = LocationProviderType::from_str)]
140 #[arg(value_name = "LATITUDE:LONGITUDE | PROVIDER")]
141 #[arg(allow_hyphen_values = true)]
142 location: LocationProviderType,
143 },
144}
145
146#[derive(Debug, Args)]
147pub struct ColorSettingsArgs {
148 #[arg(verbatim_doc_comment)]
155 #[arg(long, short, value_parser = Temperature::from_str)]
156 #[arg(value_name = formatcp!("FROM {MIN_TEMPERATURE} TO {MAX_TEMPERATURE}"))]
157 #[arg(default_value_t)]
158 pub temperature: Temperature,
159
160 #[arg(verbatim_doc_comment)]
166 #[arg(long, short, value_parser = Gamma::from_str)]
167 #[arg(value_name = "FROM 0.1 TO 10")]
168 #[arg(default_value_t)]
169 pub gamma: Gamma,
170
171 #[arg(verbatim_doc_comment)]
173 #[arg(long, short, value_parser = Brightness::from_str)]
174 #[arg(value_name = "FROM 0.1 TO 1.0")]
175 #[arg(default_value_t)]
176 pub brightness: Brightness,
177}
178
179#[derive(Debug, Args)]
180pub struct CmdArgs {
181 #[arg(verbatim_doc_comment)]
190 #[arg(long, short, value_parser = TemperatureRange::from_str)]
191 #[arg(value_name = formatcp!("FROM {MIN_TEMPERATURE} TO {MAX_TEMPERATURE}"))]
192 pub temperature: Option<TemperatureRange>,
193
194 #[arg(verbatim_doc_comment)]
200 #[arg(long, short, value_parser = GammaRange::from_str)]
201 #[arg(value_name = "FROM 0.1 TO 10")]
202 pub gamma: Option<GammaRange>,
203
204 #[arg(verbatim_doc_comment)]
211 #[arg(long, short, value_parser = BrightnessRange::from_str)]
212 #[arg(value_name = "FROM 0.1 TO 1.0")]
213 pub brightness: Option<BrightnessRange>,
214
215 #[arg(verbatim_doc_comment)]
225 #[arg(long, short, value_parser = TransitionScheme::from_str)]
226 #[arg(value_name = "TIME-TIME - TIME-TIME | TIME-TIME | DEGREE:DEGREE")]
227 #[arg(allow_hyphen_values = true)]
228 pub scheme: Option<TransitionScheme>,
229
230 #[arg(verbatim_doc_comment)]
238 #[arg(long, short, value_parser = LocationProviderType::from_str)]
239 #[arg(value_name = "LATITUDE:LONGITUDE | PROVIDER")]
240 #[arg(allow_hyphen_values = true)]
241 pub location: Option<LocationProviderType>,
242
243 #[command(flatten)]
244 pub i: CmdInnerArgs,
245}
246
247#[derive(Debug, Args)]
248pub struct CmdInnerArgs {
249 #[arg(verbatim_doc_comment)]
267 #[arg(long, short, value_parser = AdjustmentMethodType::from_str)]
268 #[arg(
269 value_name = "METHOD [:(DISPLAY_NUM | CARD_NUM) [:CRTC1,CRTC2,...]]"
270 )]
271 pub method: Option<AdjustmentMethodType>,
272
273 #[arg(long, action = ArgAction::SetTrue)]
275 pub reset_ramps: Option<bool>,
276
277 #[arg(long, short, value_name = "FILE", display_order(99))]
282 pub config: Option<PathBuf>,
283}
284
285pub trait DefaultLevel {
288 fn default() -> Option<Level>;
289}
290
291#[derive(Debug, Clone, Copy, Default)]
292pub struct InfoLevel;
293impl DefaultLevel for InfoLevel {
294 fn default() -> Option<Level> {
295 Some(Level::INFO)
296 }
297}
298
299#[derive(Args, Debug, Clone, Copy, Default)]
300pub struct Verbosity<L: DefaultLevel = InfoLevel> {
301 #[arg(skip)]
305 verbose: u8,
306
307 #[arg(short, long, action = clap::ArgAction::Count, global = true)]
309 #[arg(global = true, display_order(100))]
310 quiet: u8,
311
312 #[arg(skip)]
313 phantom: PhantomData<L>,
314}
315
316impl<L: DefaultLevel> Verbosity<L> {
317 pub fn level_filter(&self) -> LevelFilter {
318 self.level().into()
319 }
320
321 pub fn level(&self) -> Option<Level> {
323 match self.verbosity() {
324 i8::MIN..=-1 => None,
325 0 => Some(Level::ERROR),
326 1 => Some(Level::WARN),
327 2 => Some(Level::INFO),
328 3 => Some(Level::DEBUG),
329 4..=i8::MAX => Some(Level::TRACE),
330 }
331 }
332
333 fn verbosity(&self) -> i8 {
334 Self::level_i8(L::default()) - (self.quiet as i8)
335 + (self.verbose as i8)
336 }
337
338 fn level_i8(level: Option<Level>) -> i8 {
339 match level {
340 None => -1,
341 Some(Level::ERROR) => 0,
342 Some(Level::WARN) => 1,
343 Some(Level::INFO) => 2,
344 Some(Level::DEBUG) => 3,
345 Some(Level::TRACE) => 4,
346 }
347 }
348}
349
350impl<L: DefaultLevel> Eq for Verbosity<L> {}
351impl<L: DefaultLevel> PartialEq for Verbosity<L> {
352 fn eq(&self, other: &Self) -> bool {
353 self.level() == other.level()
354 }
355}
356
357impl<L: DefaultLevel> Ord for Verbosity<L> {
358 fn cmp(&self, other: &Self) -> Ordering {
359 self.level().cmp(&other.level())
360 }
361}
362impl<L: DefaultLevel> PartialOrd for Verbosity<L> {
363 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
364 Some(self.cmp(other))
365 }
366}
367
368pub trait ClapColorChoiceExt {
369 fn to_choice(&self) -> ColorChoice;
370}
371
372impl ClapColorChoiceExt for ClapColorChoice {
373 fn to_choice(&self) -> ColorChoice {
374 match self {
375 ClapColorChoice::Auto => ColorChoice::Auto,
376 ClapColorChoice::Always => ColorChoice::Always,
377 ClapColorChoice::Never => ColorChoice::Never,
378 }
379 }
380}
381
382pub fn cli_args_command() -> Command {
385 CliArgs::command()
386}