zoi/
cli.rs

1use crate::cmd;
2use crate::pkg::lock;
3use crate::utils;
4use clap::{
5    ColorChoice, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint,
6    builder::PossibleValue, builder::TypedValueParser, builder::styling,
7};
8use clap_complete::Shell;
9use clap_complete::generate;
10use std::io::{self};
11
12// Development, Special, Public or Production
13const BRANCH: &str = "Production";
14const STATUS: &str = "Release";
15const NUMBER: &str = "1.2.0";
16
17/// Zoi - The Universal Package Manager & Environment Setup Tool.
18///
19/// Part of the Zillowe Development Suite (ZDS), Zoi is designed to streamline
20/// your development workflow by managing tools and project environments.
21#[derive(Parser)]
22#[command(name = "zoi", author, about, long_about = None, disable_version_flag = true,
23    trailing_var_arg = true,
24    color = ColorChoice::Auto,
25)]
26pub struct Cli {
27    #[command(subcommand)]
28    command: Option<Commands>,
29
30    #[arg(
31        short = 'v',
32        long = "version",
33        help = "Print detailed version information"
34    )]
35    version_flag: bool,
36
37    #[arg(
38        short = 'y',
39        long,
40        help = "Automatically answer yes to all prompts",
41        global = true
42    )]
43    yes: bool,
44}
45
46#[derive(Clone, Debug)]
47struct PackageValueParser;
48
49impl TypedValueParser for PackageValueParser {
50    type Value = String;
51
52    fn parse_ref(
53        &self,
54        _cmd: &clap::Command,
55        _arg: Option<&clap::Arg>,
56        value: &std::ffi::OsStr,
57    ) -> Result<Self::Value, clap::Error> {
58        Ok(value.to_string_lossy().into_owned())
59    }
60
61    fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
62        Some(Box::new(
63            utils::get_all_packages_for_completion()
64                .into_iter()
65                .map(|pkg| {
66                    let help = if pkg.description.is_empty() {
67                        pkg.repo
68                    } else {
69                        format!("[{}] {}", pkg.repo, pkg.description)
70                    };
71                    PossibleValue::new(Box::leak(pkg.display.into_boxed_str()) as &'static str)
72                        .help(Box::leak(help.into_boxed_str()) as &'static str)
73                }),
74        ))
75    }
76}
77
78#[derive(Clone, Debug)]
79struct PkgOrPathParser;
80
81impl TypedValueParser for PkgOrPathParser {
82    type Value = String;
83
84    fn parse_ref(
85        &self,
86        _cmd: &clap::Command,
87        _arg: Option<&clap::Arg>,
88        value: &std::ffi::OsStr,
89    ) -> Result<Self::Value, clap::Error> {
90        Ok(value.to_string_lossy().into_owned())
91    }
92
93    fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
94        Some(Box::new(
95            utils::get_all_packages_for_completion()
96                .into_iter()
97                .map(|pkg| {
98                    let help = if pkg.description.is_empty() {
99                        pkg.repo
100                    } else {
101                        format!("[{}] {}", pkg.repo, pkg.description)
102                    };
103                    PossibleValue::new(Box::leak(pkg.display.into_boxed_str()) as &'static str)
104                        .help(Box::leak(help.into_boxed_str()) as &'static str)
105                }),
106        ))
107    }
108}
109
110#[derive(clap::ValueEnum, Clone, Debug, Copy)]
111pub enum SetupScope {
112    User,
113    System,
114}
115
116#[derive(clap::ValueEnum, Clone, Debug, Copy)]
117pub enum InstallScope {
118    User,
119    System,
120    Project,
121}
122
123#[derive(Subcommand)]
124enum Commands {
125    /// Generates shell completion scripts
126    #[command(hide = true)]
127    GenerateCompletions {
128        /// The shell to generate completions for
129        #[arg(value_enum)]
130        shell: Shell,
131    },
132
133    /// Generates man pages for zoi
134    #[command(hide = true)]
135    GenerateManual,
136
137    /// Prints concise version and build information
138    #[command(
139        alias = "v",
140        long_about = "Displays the version number, build status, branch, and commit hash. This is the same output provided by the -v and --version flags."
141    )]
142    Version,
143
144    /// Shows detailed application information and credits
145    #[command(
146        long_about = "Displays the full application name, description, author, license, and homepage information."
147    )]
148    About,
149
150    /// Displays detected operating system and architecture information
151    #[command(
152        long_about = "Detects and displays key system details, including the OS, CPU architecture, Linux distribution (if applicable), and available package managers."
153    )]
154    Info,
155
156    /// Checks for essential third-party command-line tools
157    #[command(
158        long_about = "Verifies that all required dependencies (like git) are installed and available in the system's PATH. This is useful for diagnostics."
159    )]
160    Check,
161
162    /// Downloads or updates the package database from the remote repository
163    #[command(
164        alias = "sy",
165        long_about = "Clones the official package database from GitLab to your local machine (~/.zoi/pkgs/db). If the database already exists, it verifies the remote URL and pulls the latest changes."
166    )]
167    Sync {
168        #[command(subcommand)]
169        command: Option<SyncCommands>,
170
171        /// Show the full git output
172        #[arg(short, long)]
173        verbose: bool,
174
175        /// Fallback to other mirrors if the default one fails
176        #[arg(long)]
177        fallback: bool,
178
179        /// Do not check for installed package managers
180        #[arg(long = "no-pm")]
181        no_package_managers: bool,
182
183        /// Do not attempt to set up shell completions after syncing
184        #[arg(long)]
185        no_shell_setup: bool,
186    },
187
188    /// Lists installed or all available packages
189    #[command(alias = "ls")]
190    List {
191        /// List all packages from the database, not just installed ones
192        #[arg(short, long)]
193        all: bool,
194        /// Filter by repository (e.g. 'main', 'extra')
195        #[arg(long)]
196        repo: Option<String>,
197        /// Filter by package type (package, app, collection, extension)
198        #[arg(short = 't', long = "type")]
199        package_type: Option<String>,
200    },
201
202    /// Shows detailed information about a package
203    Show {
204        /// The name of the package to show
205        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
206        package_name: String,
207        /// Display the raw, unformatted package file
208        #[arg(long)]
209        raw: bool,
210    },
211
212    /// Pin a package to a specific version
213    Pin {
214        /// The name of the package to pin
215        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
216        package: String,
217        /// The version to pin the package to
218        version: String,
219    },
220
221    /// Unpin a package, allowing it to be updated
222    Unpin {
223        /// The name of the package to unpin
224        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
225        package: String,
226    },
227
228    /// Updates one or more packages to their latest versions
229    #[command(alias = "up")]
230    Update {
231        /// The name(s) of the package(s) to update
232        #[arg(value_name = "PACKAGES", value_parser = PackageValueParser, hide_possible_values = true)]
233        package_names: Vec<String>,
234
235        /// Update all installed packages
236        #[arg(long, conflicts_with = "package_names")]
237        all: bool,
238    },
239
240    /// Installs one or more packages from a name, local file, URL, or git repository
241    #[command(alias = "i")]
242    Install {
243        /// Package names, local paths, or URLs to .pkg.lua files
244        #[arg(value_name = "SOURCES", value_hint = ValueHint::FilePath, value_parser = PkgOrPathParser, hide_possible_values = true)]
245        sources: Vec<String>,
246        /// Install from a git repository (e.g. 'Zillowe/Hello', 'gl:Zillowe/Hello')
247        #[arg(long, value_name = "REPO", conflicts_with = "sources")]
248        repo: Option<String>,
249        /// Force re-installation even if the package is already installed
250        #[arg(long)]
251        force: bool,
252        /// Accept all optional dependencies
253        #[arg(long)]
254        all_optional: bool,
255        /// The scope to install the package to
256        #[arg(long, value_enum, conflicts_with_all = &["local", "global"])]
257        scope: Option<InstallScope>,
258        /// Install packages to the current project (alias for --scope=project)
259        #[arg(long, conflicts_with = "global")]
260        local: bool,
261        /// Install packages globally for the current user (alias for --scope=user)
262        #[arg(long)]
263        global: bool,
264        /// Save the package to the project's zoi.yaml
265        #[arg(long)]
266        save: bool,
267        /// The type of package to build if building from source (e.g. 'source', 'pre-compiled').
268        #[arg(long)]
269        r#type: Option<String>,
270    },
271
272    /// Uninstalls one or more packages previously installed by Zoi
273    #[command(
274        aliases = ["un", "rm", "remove"],
275        long_about = "Removes one or more packages' files from the Zoi store and deletes their symlinks from the bin directory. This command will fail if a package was not installed by Zoi."
276    )]
277    Uninstall {
278        /// One or more packages to uninstall
279        #[arg(value_name = "PACKAGES", required = true, value_parser = PackageValueParser, hide_possible_values = true)]
280        packages: Vec<String>,
281        /// The scope to uninstall the package from
282        #[arg(long, value_enum, conflicts_with_all = &["local", "global"])]
283        scope: Option<InstallScope>,
284        /// Uninstall packages from the current project (alias for --scope=project)
285        #[arg(long, conflicts_with = "global")]
286        local: bool,
287        /// Uninstall packages globally for the current user (alias for --scope=user)
288        #[arg(long)]
289        global: bool,
290        /// Remove the package from the project's zoi.yaml
291        #[arg(long)]
292        save: bool,
293    },
294
295    /// Execute a command defined in a local zoi.yaml file
296    #[command(
297        long_about = "Execute a command from zoi.yaml. If no command is specified, it will launch an interactive prompt to choose one."
298    )]
299    Run {
300        /// The alias of the command to execute
301        cmd_alias: Option<String>,
302        /// Arguments to pass to the command
303        args: Vec<String>,
304    },
305
306    /// Manage and set up project environments from a local zoi.yaml file
307    #[command(
308        long_about = "Checks for required packages and runs setup commands for a defined environment. If no environment is specified, it launches an interactive prompt."
309    )]
310    Env {
311        /// The alias of the environment to set up
312        env_alias: Option<String>,
313    },
314
315    /// Upgrades the Zoi binary to the latest version
316    #[command(
317        alias = "ug",
318        long_about = "Downloads the latest release from GitLab, verifies its checksum, and replaces the current executable."
319    )]
320    Upgrade {
321        /// Force a full download
322        #[arg(long)]
323        force: bool,
324
325        /// Upgrade to a specific git tag
326        #[arg(long)]
327        tag: Option<String>,
328
329        /// Upgrade to the latest release of a specific branch (e.g. Prod, Pub)
330        #[arg(long)]
331        branch: Option<String>,
332    },
333
334    /// Removes packages that were installed as dependencies but are no longer needed
335    Autoremove,
336
337    /// Explains why a package is installed
338    Why {
339        /// The name of the package to inspect
340        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
341        package_name: String,
342    },
343
344    /// Find which package owns a file
345    #[command(alias = "owns")]
346    Owner {
347        /// Path to the file
348        #[arg(value_hint = ValueHint::FilePath)]
349        path: std::path::PathBuf,
350    },
351
352    /// List all files owned by a package
353    Files {
354        /// The name of the package
355        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
356        package: String,
357    },
358
359    /// Searches for packages by name or description
360    #[command(
361        alias = "s",
362        long_about = "Searches for a case-insensitive term in the name, description, and tags of all available packages in the database. Filter by repo, type, or tags."
363    )]
364    Search {
365        /// The term to search for (e.g. 'editor', 'cli')
366        search_term: String,
367        /// Filter by repository (e.g. 'main', 'extra')
368        #[arg(long)]
369        repo: Option<String>,
370        /// Filter by package type (package, app, collection, extension)
371        #[arg(long = "type")]
372        package_type: Option<String>,
373        /// Filter by tags (any match). Multiple via comma or repeated -t
374        #[arg(short = 't', long = "tag", value_delimiter = ',', num_args = 1..)]
375        tags: Option<Vec<String>>,
376    },
377
378    /// Installs completion scripts for a given shell
379    Shell {
380        /// The shell to install completions for
381        #[arg(value_enum)]
382        shell: Shell,
383    },
384
385    /// Configures the shell environment for Zoi
386    #[command(
387        long_about = "Adds the Zoi binary directory to your shell's PATH to make Zoi packages' executables available as commands."
388    )]
389    Setup {
390        /// The scope to apply the setup to (user or system-wide)
391        #[arg(long, value_enum, default_value = "user")]
392        scope: SetupScope,
393    },
394
395    /// Download and execute a binary package without installing it
396    #[command(
397        alias = "x",
398        long_about = "Downloads a binary to a temporary cache and executes it in a shell. All arguments after the package name are passed as arguments to the shell command."
399    )]
400    Exec {
401        /// Package name, local path, or URL to execute
402        #[arg(value_name = "SOURCE", value_parser = PkgOrPathParser, value_hint = ValueHint::FilePath, hide_possible_values = true)]
403        source: String,
404
405        /// Force execution from a fresh download, bypassing any cache.
406        #[arg(long)]
407        upstream: bool,
408
409        /// Force execution from the cache, failing if the package is not cached.
410        #[arg(long)]
411        cache: bool,
412
413        /// Force execution from the local project installation.
414        #[arg(long)]
415        local: bool,
416
417        /// Arguments to pass to the executed command
418        #[arg(value_name = "ARGS")]
419        args: Vec<String>,
420    },
421
422    /// Clears the cache of downloaded package binaries
423    Clean,
424
425    /// Manage package repositories
426    #[command(
427        aliases = ["repositories"],
428        long_about = "Manages the list of package repositories used by Zoi.\n\nCommands:\n- add (alias: a): Add an official repo by name or clone from a git URL.\n- remove|rm: Remove a repo from active list (repo rm <name>).\n- list|ls: Show active repositories by default; use 'list all' to show all available repositories.\n- git: Manage cloned git repositories (git ls, git rm <repo-name>)."
429    )]
430    Repo(cmd::repo::RepoCommand),
431
432    /// Manage telemetry settings (opt-in analytics)
433    #[command(
434        long_about = "Manage opt-in anonymous telemetry used to understand package popularity. Default is disabled."
435    )]
436    Telemetry {
437        #[arg(value_enum)]
438        action: TelemetryAction,
439    },
440
441    /// Create an application using a package template
442    Create {
443        /// Package name, @repo/name, local .pkg.lua path, or URL
444        source: String,
445        /// The application name to substitute into template commands
446        app_name: Option<String>,
447    },
448
449    /// Manage Zoi extensions
450    #[command(alias = "ext")]
451    Extension(ExtensionCommand),
452
453    /// Rollback a package to the previously installed version
454    Rollback {
455        /// The name of the package to rollback
456        #[arg(value_name = "PACKAGE", value_parser = PackageValueParser, hide_possible_values = true, required_unless_present = "last_transaction")]
457        package: Option<String>,
458
459        /// Rollback the last transaction
460        #[arg(long, conflicts_with = "package")]
461        last_transaction: bool,
462    },
463
464    /// Shows a package's manual
465    Man {
466        /// The name of the package to show the manual for
467        #[arg(value_parser = PackageValueParser, hide_possible_values = true)]
468        package_name: String,
469        /// Always look at the upstream manual even if it's downloaded
470        #[arg(long)]
471        upstream: bool,
472        /// Print the manual to the terminal raw
473        #[arg(long)]
474        raw: bool,
475    },
476
477    /// Build, create, and manage Zoi packages
478    #[command(alias = "pkg")]
479    Package(cmd::package::PackageCommand),
480
481    /// Manage PGP keys for package signature verification
482    Pgp(cmd::pgp::PgpCommand),
483
484    /// Helper commands for various tasks
485    Helper(cmd::helper::HelperCommand),
486
487    /// Checks for common issues and provides actionable suggestions
488    Doctor,
489}
490
491#[derive(clap::Parser, Debug)]
492pub struct ExtensionCommand {
493    #[command(subcommand)]
494    pub command: ExtensionCommands,
495}
496
497#[derive(clap::Subcommand, Debug)]
498pub enum ExtensionCommands {
499    /// Add an extension
500    Add {
501        /// The name of the extension to add
502        #[arg(required = true)]
503        name: String,
504    },
505    /// Remove an extension
506    Remove {
507        /// The name of the extension to remove
508        #[arg(required = true)]
509        name: String,
510    },
511}
512
513#[derive(clap::Subcommand, Clone)]
514pub enum SyncCommands {
515    /// Add a new registry
516    Add {
517        /// URL of the registry to add
518        url: String,
519    },
520    /// Remove a configured registry by its handle
521    Remove {
522        /// Handle of the registry to remove
523        handle: String,
524    },
525    /// List configured registries
526    #[command(alias = "ls")]
527    List,
528    /// Set the default registry URL
529    Set {
530        /// URL or keyword (default, github, gitlab, codeberg)
531        url: String,
532    },
533}
534
535#[derive(clap::ValueEnum, Clone)]
536enum TelemetryAction {
537    Status,
538    Enable,
539    Disable,
540}
541
542pub fn run() {
543    let styles = styling::Styles::styled()
544        .header(styling::AnsiColor::Yellow.on_default() | styling::Effects::BOLD)
545        .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
546        .literal(styling::AnsiColor::Green.on_default())
547        .placeholder(styling::AnsiColor::Cyan.on_default());
548
549    let commit: &str = option_env!("ZOI_COMMIT_HASH").unwrap_or("dev");
550    let mut cmd = Cli::command().styles(styles);
551    let matches = cmd.clone().get_matches();
552    let cli = match Cli::from_arg_matches(&matches) {
553        Ok(cli) => cli,
554        Err(err) => {
555            err.print().unwrap();
556            std::process::exit(1);
557        }
558    };
559
560    utils::check_path();
561
562    if cli.version_flag {
563        cmd::version::run(BRANCH, STATUS, NUMBER, commit);
564        return;
565    }
566
567    if let Some(command) = cli.command {
568        let needs_lock = matches!(
569            command,
570            Commands::Install { .. }
571                | Commands::Uninstall { .. }
572                | Commands::Update { .. }
573                | Commands::Autoremove
574                | Commands::Rollback { .. }
575                | Commands::Package(_)
576        );
577
578        let _lock_guard = if needs_lock {
579            match lock::acquire_lock() {
580                Ok(guard) => Some(guard),
581                Err(e) => {
582                    eprintln!("Error: {}", e);
583                    std::process::exit(1);
584                }
585            }
586        } else {
587            None
588        };
589
590        let result = match command {
591            Commands::GenerateCompletions { shell } => {
592                let mut cmd = Cli::command();
593                let bin_name = cmd.get_name().to_string();
594                generate(shell, &mut cmd, bin_name, &mut io::stdout());
595                Ok(())
596            }
597            Commands::GenerateManual => cmd::gen_man::run().map_err(Into::into),
598            Commands::Version => {
599                cmd::version::run(BRANCH, STATUS, NUMBER, commit);
600                Ok(())
601            }
602            Commands::About => {
603                cmd::about::run(BRANCH, STATUS, NUMBER, commit);
604                Ok(())
605            }
606            Commands::Info => {
607                cmd::info::run(BRANCH, STATUS, NUMBER, commit);
608                Ok(())
609            }
610            Commands::Check => {
611                cmd::check::run();
612                Ok(())
613            }
614            Commands::Sync {
615                command,
616                verbose,
617                fallback,
618                no_package_managers,
619                no_shell_setup,
620            } => {
621                if let Some(cmd) = command {
622                    match cmd {
623                        SyncCommands::Add { url } => cmd::sync::add_registry(&url),
624                        SyncCommands::Remove { handle } => cmd::sync::remove_registry(&handle),
625                        SyncCommands::List => cmd::sync::list_registries(),
626                        SyncCommands::Set { url } => cmd::sync::set_registry(&url),
627                    }
628                } else {
629                    cmd::sync::run(verbose, fallback, no_package_managers, no_shell_setup);
630                }
631                Ok(())
632            }
633            Commands::List {
634                all,
635                repo,
636                package_type,
637            } => {
638                let _ = cmd::list::run(all, repo, package_type);
639                Ok(())
640            }
641            Commands::Show { package_name, raw } => {
642                cmd::show::run(&package_name, raw);
643                Ok(())
644            }
645            Commands::Pin { package, version } => {
646                cmd::pin::run(&package, &version);
647                Ok(())
648            }
649            Commands::Unpin { package } => {
650                cmd::unpin::run(&package);
651                Ok(())
652            }
653            Commands::Update { package_names, all } => {
654                if !all && package_names.is_empty() {
655                    let mut cmd = Cli::command();
656                    if let Some(subcmd) = cmd.find_subcommand_mut("update") {
657                        subcmd.print_help().unwrap();
658                    }
659                } else {
660                    let _ = cmd::update::run(all, &package_names, cli.yes);
661                }
662                Ok(())
663            }
664            Commands::Install {
665                sources,
666                repo,
667                force,
668                all_optional,
669                scope,
670                local,
671                global,
672                save,
673                r#type,
674            } => {
675                cmd::install::run(
676                    &sources,
677                    repo,
678                    force,
679                    all_optional,
680                    cli.yes,
681                    scope,
682                    local,
683                    global,
684                    save,
685                    r#type,
686                );
687                Ok(())
688            }
689            Commands::Uninstall {
690                packages,
691                scope,
692                local,
693                global,
694                save,
695            } => {
696                cmd::uninstall::run(&packages, scope, local, global, save);
697                Ok(())
698            }
699            Commands::Run { cmd_alias, args } => {
700                cmd::run::run(cmd_alias, args);
701                Ok(())
702            }
703            Commands::Env { env_alias } => {
704                cmd::env::run(env_alias);
705                Ok(())
706            }
707            Commands::Upgrade { force, tag, branch } => {
708                cmd::upgrade::run(BRANCH, STATUS, NUMBER, force, tag, branch);
709                Ok(())
710            }
711            Commands::Autoremove => {
712                cmd::autoremove::run(cli.yes);
713                Ok(())
714            }
715            Commands::Why { package_name } => cmd::why::run(&package_name),
716            Commands::Owner { path } => {
717                cmd::owner::run(&path);
718                Ok(())
719            }
720            Commands::Files { package } => {
721                cmd::files::run(&package);
722                Ok(())
723            }
724            Commands::Search {
725                search_term,
726                repo,
727                package_type,
728                tags,
729            } => cmd::search::run(search_term, repo, package_type, tags),
730            Commands::Shell { shell } => {
731                cmd::shell::run(shell);
732                Ok(())
733            }
734            Commands::Setup { scope } => {
735                cmd::setup::run(scope);
736                Ok(())
737            }
738            Commands::Exec {
739                source,
740                upstream,
741                cache,
742                local,
743                args,
744            } => {
745                cmd::exec::run(source, args, upstream, cache, local);
746                Ok(())
747            }
748            Commands::Clean => {
749                cmd::clean::run();
750                Ok(())
751            }
752            Commands::Repo(args) => {
753                cmd::repo::run(args);
754                Ok(())
755            }
756            Commands::Telemetry { action } => {
757                use cmd::telemetry::{TelemetryCommand, run};
758                let cmd = match action {
759                    TelemetryAction::Status => TelemetryCommand::Status,
760                    TelemetryAction::Enable => TelemetryCommand::Enable,
761                    TelemetryAction::Disable => TelemetryCommand::Disable,
762                };
763                run(cmd);
764                Ok(())
765            }
766            Commands::Create { source, app_name } => {
767                cmd::create::run(cmd::create::CreateCommand { source, app_name }, cli.yes);
768                Ok(())
769            }
770            Commands::Extension(args) => cmd::extension::run(args, cli.yes),
771            Commands::Rollback {
772                package,
773                last_transaction,
774            } => {
775                if last_transaction {
776                    cmd::rollback::run_transaction_rollback(cli.yes)
777                } else if let Some(pkg) = package {
778                    cmd::rollback::run(&pkg, cli.yes)
779                } else {
780                    Ok(())
781                }
782            }
783            Commands::Man {
784                package_name,
785                upstream,
786                raw,
787            } => cmd::man::run(&package_name, upstream, raw),
788            Commands::Package(args) => {
789                cmd::package::run(args);
790                Ok(())
791            }
792            Commands::Pgp(args) => cmd::pgp::run(args),
793            Commands::Helper(args) => cmd::helper::run(args),
794            Commands::Doctor => cmd::doctor::run(),
795        };
796
797        if let Err(e) = result {
798            eprintln!("Error: {}", e);
799            std::process::exit(1);
800        }
801    } else {
802        cmd.print_help().unwrap();
803    }
804}