Skip to main content

valheim_mod_manager/
cli.rs

1use clap::{Args, Parser, Subcommand, ValueEnum};
2use std::path::PathBuf;
3
4/// Root command-line interface structure for the application.
5///
6/// This struct uses clap's derive macros to parse command-line arguments
7/// and route to the appropriate subcommand handler.
8#[derive(Parser)]
9#[command(version, about, long_about = None)]
10pub struct AppCli {
11  /// Path to a config file, overriding the default XDG location.
12  #[arg(long, global = true)]
13  pub config: Option<PathBuf>,
14  /// The subcommand to execute.
15  #[command(subcommand)]
16  pub command: Command,
17}
18
19/// Top-level commands available to the user.
20#[derive(Subcommand)]
21pub enum Command {
22  /// Update the mod manifest or installed mods.
23  Update(CommandArgs),
24  /// Search for mods by name.
25  Search(SearchArgs),
26  /// List all mods from config including resolved dependencies.
27  List(ListArgs),
28}
29
30/// Arguments for the list command.
31#[derive(Args)]
32pub struct ListArgs {
33  /// Output format.
34  #[arg(long, value_enum, default_value_t = ListFormat::Text)]
35  pub format: ListFormat,
36}
37
38/// Output format for the list command.
39#[derive(Clone, ValueEnum)]
40pub enum ListFormat {
41  /// Plain text, one mod per line.
42  Text,
43  /// JSON array.
44  Json,
45}
46
47/// Arguments for the update command.
48#[derive(Args)]
49pub struct CommandArgs {
50  /// The specific update operation to perform.
51  #[command(subcommand)]
52  pub command: UpdatesCommand,
53}
54
55/// Arguments for the search command.
56#[derive(Args)]
57pub struct SearchArgs {
58  /// Search term to find mods by name.
59  pub term: String,
60}
61
62/// Subcommands for the update operation.
63#[derive(Subcommand)]
64pub enum UpdatesCommand {
65  /// Update the mod manifest from the server.
66  Manifest,
67  /// Update installed mods to their latest versions.
68  Mods,
69}
70
71#[cfg(test)]
72mod tests {
73  use super::*;
74  use clap::CommandFactory;
75
76  #[test]
77  fn test_command_search() {
78    let app = AppCli::command();
79    let search_command = app.find_subcommand("search").unwrap();
80
81    assert_eq!(search_command.get_name(), "search");
82    assert!(search_command.get_about().is_some());
83    assert!(
84      search_command
85        .get_about()
86        .unwrap()
87        .to_string()
88        .contains("Search for mods by name")
89    );
90
91    let search_args = search_command.get_arguments().collect::<Vec<_>>();
92    assert_eq!(search_args.len(), 1);
93    assert_eq!(search_args[0].get_id().as_str(), "term");
94    assert!(search_args[0].get_help().is_some());
95    assert!(
96      search_args[0]
97        .get_help()
98        .unwrap()
99        .to_string()
100        .contains("Search term to find mods by name")
101    );
102  }
103
104  #[test]
105  fn test_command_update() {
106    let app = AppCli::command();
107    let update_command = app.find_subcommand("update").unwrap();
108
109    assert_eq!(update_command.get_name(), "update");
110    assert!(update_command.get_about().is_some());
111    assert!(
112      update_command
113        .get_about()
114        .unwrap()
115        .to_string()
116        .contains("Update the mod manifest or installed mods")
117    );
118
119    let update_subcommands = update_command.get_subcommands().collect::<Vec<_>>();
120    assert_eq!(update_subcommands.len(), 2);
121
122    let manifest_cmd = update_subcommands
123      .iter()
124      .find(|cmd| cmd.get_name() == "manifest")
125      .unwrap();
126    let mods_cmd = update_subcommands
127      .iter()
128      .find(|cmd| cmd.get_name() == "mods")
129      .unwrap();
130
131    assert!(manifest_cmd.get_about().is_some());
132    assert!(
133      manifest_cmd
134        .get_about()
135        .unwrap()
136        .to_string()
137        .contains("Update the mod manifest from the server")
138    );
139    assert!(mods_cmd.get_about().is_some());
140    assert!(
141      mods_cmd
142        .get_about()
143        .unwrap()
144        .to_string()
145        .contains("Update installed mods to their latest versions")
146    );
147  }
148
149  #[test]
150  fn test_command_list() {
151    let app = AppCli::command();
152    let list_command = app.find_subcommand("list").unwrap();
153
154    assert_eq!(list_command.get_name(), "list");
155    assert!(list_command.get_about().is_some());
156    assert!(
157      list_command
158        .get_about()
159        .unwrap()
160        .to_string()
161        .contains("List all mods from config including resolved dependencies")
162    );
163
164    let list_args = list_command.get_arguments().collect::<Vec<_>>();
165    let format_arg = list_args
166      .iter()
167      .find(|a| a.get_id().as_str() == "format")
168      .unwrap();
169    assert!(format_arg.get_default_values().iter().any(|v| v == "text"));
170  }
171}