@repokit/core 2.0.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/Cargo.lock +113 -7
  2. package/Cargo.toml +3 -2
  3. package/README.md +45 -0
  4. package/dist/RepoKitConfig.d.mts +4 -1
  5. package/dist/RepoKitConfig.mjs +3 -1
  6. package/dist/RepoKitTheme.d.mts +47 -0
  7. package/dist/RepoKitTheme.mjs +45 -0
  8. package/dist/index.d.mts +4 -3
  9. package/dist/index.mjs +2 -1
  10. package/dist/types.d.mts +18 -5
  11. package/externals/RepoKitConfig.ts +10 -2
  12. package/externals/RepoKitTheme.ts +44 -0
  13. package/externals/index.ts +1 -0
  14. package/externals/types.ts +25 -4
  15. package/installation/install.sh +9 -3
  16. package/internals/argv/argv.rs +133 -0
  17. package/internals/argv/mod.rs +1 -0
  18. package/internals/configuration/configuration.rs +2 -2
  19. package/internals/internal_commands/help.rs +57 -45
  20. package/internals/internal_commands/internal_registry.rs +5 -4
  21. package/internals/internal_commands/list_commands.rs +3 -3
  22. package/internals/internal_commands/list_owners.rs +10 -10
  23. package/internals/internal_commands/list_themes.rs +113 -0
  24. package/internals/internal_commands/locate_command.rs +8 -2
  25. package/internals/internal_commands/mod.rs +1 -0
  26. package/internals/internal_commands/onboarder.rs +10 -4
  27. package/internals/internal_commands/register_command.rs +3 -3
  28. package/internals/internal_commands/search_commands.rs +3 -3
  29. package/internals/internal_commands/upgrade_repokit.rs +5 -1
  30. package/internals/internal_filesystem/internal_filesystem.rs +124 -3
  31. package/internals/logger/logger.rs +44 -56
  32. package/internals/main.rs +2 -0
  33. package/internals/repokit/interfaces.rs +3 -0
  34. package/internals/repokit/repokit.rs +9 -6
  35. package/internals/themes/built_in_themes/mod.rs +3 -0
  36. package/internals/themes/built_in_themes/money.rs +41 -0
  37. package/internals/themes/built_in_themes/seeing_red.rs +41 -0
  38. package/internals/themes/built_in_themes/the_blues.rs +41 -0
  39. package/internals/themes/mod.rs +5 -0
  40. package/internals/themes/theme.rs +108 -0
  41. package/internals/themes/theme_colors.rs +36 -0
  42. package/internals/themes/theme_inputs.rs +34 -0
  43. package/internals/themes/theme_registry.rs +102 -0
  44. package/internals/validations/command_validations.rs +8 -8
  45. package/media/seeing-red.webp +0 -0
  46. package/package.json +5 -4
@@ -1,6 +1,10 @@
1
1
  use normalize_path::NormalizePath;
2
+
3
+ use regex::Regex;
4
+ use shellexpand::tilde;
2
5
  use std::{
3
- fs::File,
6
+ fs::{self, File},
7
+ io::{BufRead, BufReader, Lines},
4
8
  path::{Path, PathBuf},
5
9
  };
6
10
 
@@ -49,8 +53,8 @@ impl InternalFileSystem {
49
53
  Logger::exit_with_info(
50
54
  format!(
51
55
  "To start using {}, please initialize your git repository by running {}",
52
- Logger::blue("Repokit"),
53
- Logger::green_bright("git init")
56
+ Logger::with_theme(|theme| theme.highlight("Repokit")),
57
+ Logger::with_theme(|theme| theme.highlight("git init"))
54
58
  )
55
59
  .as_str(),
56
60
  );
@@ -58,6 +62,58 @@ impl InternalFileSystem {
58
62
  root
59
63
  }
60
64
 
65
+ pub fn read_theme_preference(&self) -> String {
66
+ let default = Logger::with_registry(|registry| registry.default_theme.clone());
67
+ let theme = self.read_dot_file(&mut |mut lines, _| {
68
+ if let Some(theme_preference) = lines.nth(1)
69
+ && let Ok(preference) = theme_preference
70
+ {
71
+ return preference;
72
+ }
73
+ default.to_string()
74
+ });
75
+ theme.unwrap_or(default)
76
+ }
77
+
78
+ pub fn store_theme_preference(&self, theme: &str) {
79
+ self.read_dot_file(&mut |lines, path| {
80
+ let mut content: Vec<String> = lines.map(|line| line.unwrap()).collect();
81
+ let theme_text = theme.to_string();
82
+ if content.len() >= 2 {
83
+ content[1] = theme_text;
84
+ } else {
85
+ content.push(theme_text);
86
+ }
87
+ fs::write(path, content.join("\n"))
88
+ });
89
+ }
90
+
91
+ pub fn read_dot_file<R>(
92
+ &self,
93
+ func: &mut impl FnMut(Lines<BufReader<File>>, PathBuf) -> R,
94
+ ) -> Option<R> {
95
+ let file_path = self.create_dot_file_if_not_exists();
96
+ match file_path {
97
+ Some(path) => {
98
+ if let Ok(file) = File::open(&path) {
99
+ let lines = BufReader::new(file).lines();
100
+ return Some(func(lines, path));
101
+ }
102
+ None
103
+ }
104
+ None => None,
105
+ }
106
+ }
107
+
108
+ pub fn home() -> Option<PathBuf> {
109
+ let expanded_path_str = tilde("~/");
110
+ let path = Path::new(expanded_path_str.as_ref()).normalize();
111
+ if path.is_absolute() && path.exists() {
112
+ return Some(path);
113
+ }
114
+ None
115
+ }
116
+
61
117
  fn commands_directory(&self) -> PathBuf {
62
118
  self.absolute(format!("{}/dist/commands", self.package_directory()).as_str())
63
119
  }
@@ -80,4 +136,69 @@ impl InternalFileSystem {
80
136
  .into_string()
81
137
  .expect("Cannot construct path")
82
138
  }
139
+
140
+ fn create_dot_file_if_not_exists(&self) -> Option<PathBuf> {
141
+ match InternalFileSystem::home() {
142
+ Some(home) => {
143
+ let dot_file_path = home.join(".repokit");
144
+ if !&dot_file_path.exists() {
145
+ let found_version = self.current_version();
146
+ found_version.as_ref()?;
147
+ let version_string = found_version.unwrap();
148
+ let result = fs::write(&dot_file_path, format!("{version_string}\n"));
149
+ if result.is_err() {
150
+ return None;
151
+ }
152
+ }
153
+ Some(dot_file_path)
154
+ }
155
+ None => {
156
+ Logger::error(
157
+ "I encountered an issue when attempting to create a cache file on your machine",
158
+ );
159
+ Logger::error(
160
+ format!(
161
+ "Please create a file called {} in your home directory",
162
+ Logger::with_theme(|theme| theme.highlight(".repokit"))
163
+ )
164
+ .as_str(),
165
+ );
166
+ Logger::error(
167
+ "This file will be used to store settings and indicators that optimize how repokit runs in your repository",
168
+ );
169
+ None
170
+ }
171
+ }
172
+ }
173
+
174
+ pub fn current_version(&self) -> Option<String> {
175
+ let package_path = Path::new(&self.root)
176
+ .join(InternalFileSystem::package_directory(self))
177
+ .normalize();
178
+ let package_json_path = package_path.join("package.json");
179
+ let file = File::open(package_json_path);
180
+ if file.is_err() {
181
+ return None;
182
+ }
183
+ let lines = BufReader::new(file.unwrap()).lines();
184
+ let version_matcher = Regex::new(r#""([^"]*)""#).unwrap();
185
+ for line in lines.flatten() {
186
+ if line.contains("\"version\": ") {
187
+ let captures: Vec<String> = version_matcher
188
+ .captures_iter(&line)
189
+ .filter_map(|item| {
190
+ item.get(1)
191
+ .map(|match_text| match_text.as_str().to_string())
192
+ })
193
+ .collect();
194
+ let version = captures.get(1);
195
+ if version.is_some() {
196
+ return Some(version.unwrap().to_string());
197
+ } else {
198
+ return None;
199
+ }
200
+ }
201
+ }
202
+ None
203
+ }
83
204
  }
@@ -1,17 +1,27 @@
1
1
  use std::process::exit;
2
2
  use std::sync::LazyLock;
3
3
  use std::sync::Mutex;
4
+ use std::sync::MutexGuard;
4
5
 
5
- use colored::{ColoredString, Colorize, CustomColor};
6
+ use colored::{ColoredString, Colorize};
7
+
8
+ use crate::repokit::interfaces::RepoKitConfig;
9
+ use crate::themes::theme::Theme;
10
+ use crate::themes::theme_registry::ThemeRegistry;
6
11
 
7
12
  static REGISTERED_NAME: LazyLock<Mutex<String>> =
8
13
  LazyLock::new(|| Mutex::new("Repokit".to_string()));
9
14
 
15
+ static THEMES: LazyLock<Mutex<ThemeRegistry>> = LazyLock::new(|| Mutex::new(ThemeRegistry::new()));
16
+
10
17
  pub struct Logger {}
11
18
 
12
19
  impl Logger {
13
- pub fn set_name(value: &str) {
14
- *REGISTERED_NAME.lock().unwrap() = value.to_string();
20
+ pub fn configure(configuration: &RepoKitConfig) {
21
+ Logger::set_name(&configuration.project);
22
+ for theme in &configuration.themes {
23
+ Logger::with_registry(|mut registry| registry.register_user_theme(theme))
24
+ }
15
25
  }
16
26
 
17
27
  pub fn info(message: &str) {
@@ -36,8 +46,19 @@ impl Logger {
36
46
  println!("\n{}{}\n", Logger::info_prefix(), message);
37
47
  }
38
48
 
49
+ pub fn with_surrounding_space<F>(mut func: impl FnMut() -> F) -> F {
50
+ println!();
51
+ let result = func();
52
+ println!();
53
+ result
54
+ }
55
+
39
56
  pub fn log_file_path(path: &str) {
40
- println!("\n{}{}\n", Logger::indent(None), Logger::blue_bright(path));
57
+ println!(
58
+ "\n{}{}\n",
59
+ Logger::indent(None),
60
+ Logger::with_theme(|theme| theme.highlight(path))
61
+ );
41
62
  }
42
63
 
43
64
  pub fn indent(times: Option<i32>) -> String {
@@ -45,54 +66,10 @@ impl Logger {
45
66
  " ".repeat(indentation.try_into().unwrap())
46
67
  }
47
68
 
48
- pub fn blue(message: &str) -> ColoredString {
49
- message.bright_blue()
50
- }
51
-
52
- pub fn blue_bright(message: &str) -> ColoredString {
53
- message.bright_blue().bold()
54
- }
55
-
56
- pub fn magenta_bright(message: &str) -> ColoredString {
57
- message.bright_magenta().bold()
58
- }
59
-
60
- pub fn magenta(message: &str) -> ColoredString {
61
- message.magenta()
62
- }
63
-
64
- pub fn green(message: &str) -> ColoredString {
65
- message.green()
66
- }
67
-
68
- pub fn green_bright(message: &str) -> ColoredString {
69
- message.bright_green()
70
- }
71
-
72
69
  pub fn cyan(message: &str) -> ColoredString {
73
70
  message.cyan()
74
71
  }
75
72
 
76
- pub fn cyan_bright(message: &str) -> ColoredString {
77
- message.bright_cyan().bold()
78
- }
79
-
80
- pub fn gray(message: &str) -> ColoredString {
81
- message.custom_color(CustomColor {
82
- r: 128,
83
- g: 128,
84
- b: 128,
85
- })
86
- }
87
-
88
- pub fn lime(message: &str) -> ColoredString {
89
- message.custom_color(CustomColor {
90
- r: 175,
91
- g: 247,
92
- b: 7,
93
- })
94
- }
95
-
96
73
  pub fn file_create_error() {
97
74
  Logger::file_error("create a file");
98
75
  }
@@ -113,6 +90,15 @@ impl Logger {
113
90
  Logger::log_file_path("https://github.com/alexfigliolia/repokit/issues");
114
91
  }
115
92
 
93
+ pub fn with_theme<R>(func: impl Fn(&Theme) -> R) -> R {
94
+ Logger::with_registry(|registry| func(registry.current_theme()))
95
+ }
96
+
97
+ pub fn with_registry<R>(func: impl Fn(MutexGuard<'_, ThemeRegistry>) -> R) -> R {
98
+ let registry = THEMES.lock().unwrap();
99
+ func(registry)
100
+ }
101
+
116
102
  fn file_error(operation: &str) {
117
103
  Logger::info(format!("I was unable to {operation} in your repository").as_str());
118
104
  Logger::error("Please verify the permissions on your working directory or file a bug here");
@@ -120,15 +106,17 @@ impl Logger {
120
106
  exit(0);
121
107
  }
122
108
 
123
- fn info_prefix() -> ColoredString {
124
- format!("{}: ", *REGISTERED_NAME.lock().unwrap())
125
- .bright_magenta()
126
- .bold()
109
+ fn info_prefix() -> String {
110
+ Logger::with_theme(|theme| format!("{}: ", theme.prefix(&REGISTERED_NAME.lock().unwrap())))
127
111
  }
128
112
 
129
- fn error_prefix() -> ColoredString {
130
- format!("{}: ", *REGISTERED_NAME.lock().unwrap())
131
- .red()
132
- .bold()
113
+ fn error_prefix() -> String {
114
+ Logger::with_theme(|theme| {
115
+ format!("{}: ", theme.error_prefix(&REGISTERED_NAME.lock().unwrap()))
116
+ })
117
+ }
118
+
119
+ fn set_name(value: &str) {
120
+ *REGISTERED_NAME.lock().unwrap() = value.to_string();
133
121
  }
134
122
  }
package/internals/main.rs CHANGED
@@ -3,6 +3,7 @@ use crate::{
3
3
  internal_filesystem::internal_filesystem::InternalFileSystem, repokit::repokit::RepoKit,
4
4
  };
5
5
 
6
+ mod argv;
6
7
  mod configuration;
7
8
  mod executables;
8
9
  mod executor;
@@ -11,6 +12,7 @@ mod internal_commands;
11
12
  mod internal_filesystem;
12
13
  mod logger;
13
14
  mod repokit;
15
+ mod themes;
14
16
  mod validations;
15
17
 
16
18
  fn main() {
@@ -2,6 +2,8 @@ use std::collections::HashMap;
2
2
 
3
3
  use serde::Deserialize;
4
4
 
5
+ use crate::themes::theme_inputs::RepoKitTheme;
6
+
5
7
  #[derive(Debug, Deserialize, Clone)]
6
8
  pub struct CommandDefinition {
7
9
  pub command: String,
@@ -33,6 +35,7 @@ pub struct RepoKitConfig {
33
35
  pub project: String,
34
36
  pub thirdParty: Vec<RepoKitCommand>,
35
37
  pub commands: HashMap<String, CommandDefinition>,
38
+ pub themes: Vec<RepoKitTheme>,
36
39
  }
37
40
 
38
41
  #[derive(Debug, Deserialize, Clone)]
@@ -11,6 +11,7 @@ use crate::{
11
11
  },
12
12
  executor::executor::Executor,
13
13
  internal_commands::help::Help,
14
+ internal_filesystem::internal_filesystem::InternalFileSystem,
14
15
  logger::logger::Logger,
15
16
  repokit::interfaces::{RepoKitCommand, RepoKitConfig},
16
17
  validations::command_validations::CommandValidations,
@@ -22,7 +23,9 @@ pub struct RepoKit {
22
23
 
23
24
  impl RepoKit {
24
25
  pub fn new(root: String, configuration: RepoKitConfig) -> RepoKit {
25
- Logger::set_name(&configuration.project);
26
+ Logger::configure(&configuration);
27
+ let theme = InternalFileSystem::new(&root).read_theme_preference();
28
+ Logger::with_registry(|mut registry| registry.set_theme(&root, &theme));
26
29
  RepoKit {
27
30
  scope: RepoKitScope {
28
31
  root,
@@ -113,7 +116,7 @@ impl RepoKit {
113
116
  Logger::info(
114
117
  format!(
115
118
  "I'm not aware of a command named {}",
116
- Logger::blue_bright(command)
119
+ Logger::with_theme(|theme| theme.highlight(command))
117
120
  )
118
121
  .as_str(),
119
122
  );
@@ -123,15 +126,15 @@ impl RepoKit {
123
126
  Logger::info(
124
127
  format!(
125
128
  "The command {} was not found on {}",
126
- Logger::blue_bright(sub_command),
127
- Logger::blue_bright(&command.name)
129
+ Logger::with_theme(|theme| theme.highlight(sub_command)),
130
+ Logger::with_theme(|theme| theme.highlight(&command.name))
128
131
  )
129
132
  .as_str(),
130
133
  );
131
134
  Logger::info(
132
135
  format!(
133
136
  "Here are the commands that belong to {}",
134
- Logger::blue_bright(&command.name)
137
+ Logger::with_theme(|theme| theme.highlight(&command.name))
135
138
  )
136
139
  .as_str(),
137
140
  );
@@ -142,7 +145,7 @@ impl RepoKit {
142
145
  Logger::info(
143
146
  format!(
144
147
  "Listing available commands for {}\n",
145
- Logger::blue(&command.name)
148
+ Logger::with_theme(|theme| theme.command(&command.name))
146
149
  )
147
150
  .as_str(),
148
151
  );
@@ -0,0 +1,3 @@
1
+ pub mod money;
2
+ pub mod seeing_red;
3
+ pub mod the_blues;
@@ -0,0 +1,41 @@
1
+ use colored::Color;
2
+
3
+ use crate::themes::theme_colors::ThemeColors;
4
+
5
+ pub const MONEY: ThemeColors = ThemeColors {
6
+ prefixColor: Color::TrueColor {
7
+ r: 26,
8
+ g: 227,
9
+ b: 133,
10
+ },
11
+ commandColor: Color::TrueColor {
12
+ r: 82,
13
+ g: 234,
14
+ b: 74,
15
+ },
16
+ subcommandColor: Color::TrueColor {
17
+ r: 51,
18
+ g: 241,
19
+ b: 162,
20
+ },
21
+ argColor: Color::TrueColor {
22
+ r: 124,
23
+ g: 244,
24
+ b: 102,
25
+ },
26
+ descriptionColor: Color::TrueColor {
27
+ r: 126,
28
+ g: 168,
29
+ b: 140,
30
+ },
31
+ errorPrefixColor: Color::TrueColor {
32
+ r: 220,
33
+ g: 36,
34
+ b: 100,
35
+ },
36
+ highlightColor: Color::TrueColor {
37
+ r: 25,
38
+ g: 206,
39
+ b: 91,
40
+ },
41
+ };
@@ -0,0 +1,41 @@
1
+ use colored::Color;
2
+
3
+ use crate::themes::theme_colors::ThemeColors;
4
+
5
+ pub const SEEING_RED: ThemeColors = ThemeColors {
6
+ prefixColor: Color::TrueColor {
7
+ r: 220,
8
+ g: 36,
9
+ b: 91,
10
+ },
11
+ commandColor: Color::TrueColor {
12
+ r: 220,
13
+ g: 36,
14
+ b: 36,
15
+ },
16
+ subcommandColor: Color::TrueColor {
17
+ r: 220,
18
+ g: 131,
19
+ b: 36,
20
+ },
21
+ argColor: Color::TrueColor {
22
+ r: 220,
23
+ g: 205,
24
+ b: 36,
25
+ },
26
+ descriptionColor: Color::TrueColor {
27
+ r: 179,
28
+ g: 100,
29
+ b: 151,
30
+ },
31
+ errorPrefixColor: Color::TrueColor {
32
+ r: 220,
33
+ g: 36,
34
+ b: 39,
35
+ },
36
+ highlightColor: Color::TrueColor {
37
+ r: 237,
38
+ g: 175,
39
+ b: 41,
40
+ },
41
+ };
@@ -0,0 +1,41 @@
1
+ use colored::Color;
2
+
3
+ use crate::themes::theme_colors::ThemeColors;
4
+
5
+ pub const THE_BLUES: ThemeColors = ThemeColors {
6
+ prefixColor: Color::TrueColor {
7
+ r: 36,
8
+ g: 111,
9
+ b: 255,
10
+ },
11
+ commandColor: Color::TrueColor {
12
+ r: 52,
13
+ g: 96,
14
+ b: 255,
15
+ },
16
+ subcommandColor: Color::TrueColor {
17
+ r: 0,
18
+ g: 157,
19
+ b: 255,
20
+ },
21
+ argColor: Color::TrueColor {
22
+ r: 40,
23
+ g: 175,
24
+ b: 253,
25
+ },
26
+ descriptionColor: Color::TrueColor {
27
+ r: 100,
28
+ g: 165,
29
+ b: 179,
30
+ },
31
+ errorPrefixColor: Color::TrueColor {
32
+ r: 220,
33
+ g: 36,
34
+ b: 100,
35
+ },
36
+ highlightColor: Color::TrueColor {
37
+ r: 69,
38
+ g: 219,
39
+ b: 229,
40
+ },
41
+ };
@@ -0,0 +1,5 @@
1
+ pub mod built_in_themes;
2
+ pub mod theme;
3
+ pub mod theme_colors;
4
+ pub mod theme_inputs;
5
+ pub mod theme_registry;
@@ -0,0 +1,108 @@
1
+ use colored::{Color, ColoredString, Colorize};
2
+
3
+ use crate::themes::{
4
+ theme_colors::ThemeColors,
5
+ theme_inputs::{RepoKitTheme, ThemeInput, ThemeInputColors},
6
+ };
7
+
8
+ #[derive(Clone)]
9
+ pub struct Theme {
10
+ pub name: String,
11
+ pub colors: ThemeColors,
12
+ }
13
+
14
+ impl Theme {
15
+ pub fn new(input: ThemeInput) -> Theme {
16
+ Theme {
17
+ name: input.name,
18
+ colors: ThemeColors {
19
+ prefixColor: input.colors.prefixColor.unwrap_or(Color::BrightMagenta),
20
+ commandColor: input.colors.commandColor.unwrap_or(Color::BrightBlue),
21
+ subcommandColor: input.colors.subcommandColor.unwrap_or(Color::TrueColor {
22
+ r: 175,
23
+ g: 247,
24
+ b: 7,
25
+ }),
26
+ argColor: input.colors.argColor.unwrap_or(Color::Green),
27
+ descriptionColor: input.colors.descriptionColor.unwrap_or(Color::TrueColor {
28
+ r: 128,
29
+ g: 128,
30
+ b: 128,
31
+ }),
32
+ errorPrefixColor: input.colors.errorPrefixColor.unwrap_or(Color::Red),
33
+ highlightColor: input.colors.highlightColor.unwrap_or(Color::BrightBlue),
34
+ },
35
+ }
36
+ }
37
+
38
+ pub fn prefix(&self, msg: &str) -> ColoredString {
39
+ msg.color(self.colors.prefixColor).bold()
40
+ }
41
+
42
+ pub fn command(&self, msg: &str) -> ColoredString {
43
+ msg.color(self.colors.commandColor)
44
+ }
45
+
46
+ pub fn sub_command(&self, msg: &str) -> ColoredString {
47
+ msg.color(self.colors.subcommandColor)
48
+ }
49
+
50
+ pub fn arg(&self, msg: &str) -> ColoredString {
51
+ msg.color(self.colors.argColor)
52
+ }
53
+
54
+ pub fn description(&self, msg: &str) -> ColoredString {
55
+ msg.color(self.colors.descriptionColor)
56
+ }
57
+
58
+ pub fn error_prefix(&self, msg: &str) -> ColoredString {
59
+ msg.color(self.colors.errorPrefixColor).bold()
60
+ }
61
+
62
+ pub fn highlight(&self, msg: &str) -> ColoredString {
63
+ msg.color(self.colors.highlightColor)
64
+ }
65
+
66
+ pub fn from_configuration(theme: &RepoKitTheme) -> Theme {
67
+ Theme::new(ThemeInput {
68
+ name: theme.name.clone(),
69
+ colors: ThemeInputColors {
70
+ prefixColor: Theme::parse_rgb(&theme.colors.prefixColor),
71
+ commandColor: Theme::parse_rgb(&theme.colors.commandColor),
72
+ subcommandColor: Theme::parse_rgb(&theme.colors.subcommandColor),
73
+ argColor: Theme::parse_rgb(&theme.colors.argColor),
74
+ descriptionColor: Theme::parse_rgb(&theme.colors.descriptionColor),
75
+ errorPrefixColor: Theme::parse_rgb(&theme.colors.errorPrefixColor),
76
+ highlightColor: Theme::parse_rgb(&theme.colors.highlightColor),
77
+ },
78
+ })
79
+ }
80
+
81
+ fn parse_rgb(rgb_str: &Option<String>) -> Option<Color> {
82
+ match rgb_str {
83
+ Some(rgb) => {
84
+ let trimmed = rgb
85
+ .strip_prefix("rgb(")
86
+ .and_then(|s| s.strip_suffix(')'))
87
+ .map(|s| s.trim())?;
88
+
89
+ // 2. Split the remaining string by commas.
90
+ let parts: Vec<&str> = trimmed.split(',').collect();
91
+
92
+ if parts.len() == 3 {
93
+ // 3. Trim whitespace from each part and parse to a u8.
94
+ // `.parse()` returns a `Result`, so we use `.ok()` to convert to an `Option`,
95
+ // and the `?` operator to return early if any parse fails.
96
+ let r = parts[0].trim().parse::<u8>().ok()?;
97
+ let g = parts[1].trim().parse::<u8>().ok()?;
98
+ let b = parts[2].trim().parse::<u8>().ok()?;
99
+
100
+ Some(Color::TrueColor { r, g, b })
101
+ } else {
102
+ None
103
+ }
104
+ }
105
+ None => None,
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,36 @@
1
+ use colored::Color;
2
+
3
+ use crate::themes::theme_inputs::ThemeInputColors;
4
+
5
+ #[derive(Clone)]
6
+ pub struct ThemeColors {
7
+ pub prefixColor: Color,
8
+ pub commandColor: Color,
9
+ pub subcommandColor: Color,
10
+ pub argColor: Color,
11
+ pub descriptionColor: Color,
12
+ pub errorPrefixColor: Color,
13
+ pub highlightColor: Color,
14
+ }
15
+
16
+ impl ThemeColors {
17
+ pub fn from_options(input: ThemeInputColors) -> ThemeColors {
18
+ ThemeColors {
19
+ prefixColor: input.prefixColor.unwrap_or(Color::BrightMagenta),
20
+ commandColor: input.commandColor.unwrap_or(Color::BrightBlue),
21
+ subcommandColor: input.subcommandColor.unwrap_or(Color::TrueColor {
22
+ r: 175,
23
+ g: 247,
24
+ b: 7,
25
+ }),
26
+ argColor: input.argColor.unwrap_or(Color::Green),
27
+ descriptionColor: input.descriptionColor.unwrap_or(Color::TrueColor {
28
+ r: 128,
29
+ g: 128,
30
+ b: 128,
31
+ }),
32
+ errorPrefixColor: input.errorPrefixColor.unwrap_or(Color::Red),
33
+ highlightColor: input.highlightColor.unwrap_or(Color::BrightBlue),
34
+ }
35
+ }
36
+ }