zebrad/commands/
entry_point.rs

1//! Zebrad EntryPoint
2
3use abscissa_core::{Command, Configurable, FrameworkError, Runnable};
4use clap::Parser;
5use std::{ffi::OsString, path::PathBuf};
6
7use crate::config::ZebradConfig;
8
9use super::ZebradCmd;
10
11/// Toplevel entrypoint command.
12///
13/// Handles obtaining toplevel help as well as verbosity settings.
14#[derive(Debug, clap::Parser)]
15#[clap(
16    version = clap::crate_version!(),
17    author="Zcash Foundation <zebra@zfnd.org>",
18    help_template = "\
19{name} {version}\n
20{author}\n
21{usage-heading} {usage}\n
22{all-args}\
23"
24)]
25pub struct EntryPoint {
26    /// Subcommand to execute.
27    ///
28    /// The `command` option will delegate option parsing to the command type,
29    /// starting at the first free argument. Defaults to start.
30    #[clap(subcommand)]
31    pub cmd: Option<ZebradCmd>,
32
33    /// Path to the configuration file
34    #[clap(long, short, help = "path to configuration file")]
35    pub config: Option<PathBuf>,
36
37    /// Increase verbosity setting
38    #[clap(long, short, help = "be verbose")]
39    pub verbose: bool,
40
41    /// Filter strings which override the config file and defaults
42    // This can be applied to the default start command if no subcommand is provided.
43    #[clap(long, help = "tracing filters which override the zebrad.toml config")]
44    filters: Vec<String>,
45}
46
47impl EntryPoint {
48    /// Borrow the command in the option
49    ///
50    /// # Panics
51    ///
52    /// If `cmd` is None
53    pub fn cmd(&self) -> &ZebradCmd {
54        self.cmd
55            .as_ref()
56            .expect("should default to start if not provided")
57    }
58
59    /// Returns a string that parses to the default subcommand
60    pub fn default_cmd_as_str() -> &'static str {
61        "start"
62    }
63
64    /// Checks if the provided arguments include a subcommand
65    fn should_add_default_subcommand(&self) -> bool {
66        self.cmd.is_none()
67    }
68
69    /// Process command arguments and insert the default subcommand
70    /// if no subcommand is provided.
71    pub fn process_cli_args(mut args: Vec<OsString>) -> clap::error::Result<Vec<OsString>> {
72        let entry_point = EntryPoint::try_parse_from(&args)?;
73
74        // Add the default subcommand to args after the top-level args if cmd is None
75        if entry_point.should_add_default_subcommand() {
76            args.push(EntryPoint::default_cmd_as_str().into());
77            // This duplicates the top-level filters args, but the tracing component only checks `StartCmd.filters`.
78            for filter in entry_point.filters {
79                args.push(filter.into())
80            }
81        }
82
83        Ok(args)
84    }
85}
86
87impl Runnable for EntryPoint {
88    fn run(&self) {
89        self.cmd().run()
90    }
91}
92
93impl Command for EntryPoint {
94    /// Name of this program as a string
95    fn name() -> &'static str {
96        ZebradCmd::name()
97    }
98
99    /// Description of this program
100    fn description() -> &'static str {
101        ZebradCmd::description()
102    }
103
104    /// Authors of this program
105    fn authors() -> &'static str {
106        ZebradCmd::authors()
107    }
108}
109
110impl Configurable<ZebradConfig> for EntryPoint {
111    /// Path to the command's configuration file
112    fn config_path(&self) -> Option<PathBuf> {
113        match &self.config {
114            // Use explicit `-c`/`--config` argument if passed
115            Some(cfg) => Some(cfg.clone()),
116
117            // Otherwise defer to the toplevel command's config path logic
118            None => self.cmd().config_path(),
119        }
120    }
121
122    /// Process the configuration after it has been loaded, potentially
123    /// modifying it or returning an error if options are incompatible
124    fn process_config(&self, config: ZebradConfig) -> Result<ZebradConfig, FrameworkError> {
125        self.cmd().process_config(config)
126    }
127}