raps_cli/commands/config/
mod.rs1mod config_ops;
9mod context;
10mod profiles;
11
12use anyhow::{Context, Result};
13use clap::Subcommand;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::path::PathBuf;
17
18use crate::output::OutputFormat;
19
20#[derive(Debug, Subcommand)]
21pub enum ConfigCommands {
22 #[command(subcommand)]
24 Profile(ProfileCommands),
25
26 Get {
28 key: String,
30 },
31
32 Set {
34 key: String,
36 value: String,
38 },
39
40 #[command(subcommand)]
42 Context(ContextCommands),
43
44 MigrateTokens,
46}
47
48#[derive(Debug, Subcommand)]
49pub enum ContextCommands {
50 Show,
52
53 Set {
55 key: String,
57 value: String,
59 },
60
61 Clear,
63}
64
65#[derive(Debug, Subcommand)]
66pub enum ProfileCommands {
67 Create {
69 name: String,
71 },
72
73 List,
75
76 Use {
78 name: String,
80 },
81
82 Delete {
84 name: String,
86 },
87
88 Current,
90
91 Export {
93 #[arg(long = "out-file", default_value = "profiles-export.json")]
95 out_file: std::path::PathBuf,
96
97 #[arg(long)]
99 include_secrets: bool,
100
101 #[arg(short, long)]
103 name: Option<String>,
104 },
105
106 Import {
108 file: std::path::PathBuf,
110
111 #[arg(long)]
113 overwrite: bool,
114 },
115
116 Diff {
118 profile1: String,
120 profile2: String,
122 },
123}
124
125impl ConfigCommands {
126 pub async fn execute(self, output_format: OutputFormat) -> Result<()> {
127 match self {
128 ConfigCommands::Profile(cmd) => cmd.execute(output_format).await,
129 ConfigCommands::Get { key } => config_ops::get_config(&key, output_format).await,
130 ConfigCommands::Set { key, value } => {
131 config_ops::set_config(&key, &value, output_format).await
132 }
133 ConfigCommands::Context(cmd) => cmd.execute(output_format).await,
134 ConfigCommands::MigrateTokens => {
135 raps_kernel::storage::TokenStorage::migrate_to_keychain()
136 }
137 }
138 }
139}
140
141impl ContextCommands {
142 pub async fn execute(self, output_format: OutputFormat) -> Result<()> {
143 match self {
144 ContextCommands::Show => context::show_context(output_format).await,
145 ContextCommands::Set { key, value } => {
146 context::set_context(&key, &value, output_format).await
147 }
148 ContextCommands::Clear => context::clear_context(output_format).await,
149 }
150 }
151}
152
153impl ProfileCommands {
154 pub async fn execute(self, output_format: OutputFormat) -> Result<()> {
155 match self {
156 ProfileCommands::Create { name } => {
157 profiles::create_profile(&name, output_format).await
158 }
159 ProfileCommands::List => profiles::list_profiles(output_format).await,
160 ProfileCommands::Use { name } => profiles::use_profile(&name, output_format).await,
161 ProfileCommands::Delete { name } => {
162 profiles::delete_profile(&name, output_format).await
163 }
164 ProfileCommands::Current => profiles::show_current_profile(output_format).await,
165 ProfileCommands::Export {
166 out_file,
167 include_secrets,
168 name,
169 } => profiles::export_profiles(&out_file, include_secrets, name, output_format).await,
170 ProfileCommands::Import { file, overwrite } => {
171 profiles::import_profiles(&file, overwrite, output_format).await
172 }
173 ProfileCommands::Diff { profile1, profile2 } => {
174 profiles::diff_profiles(&profile1, &profile2, output_format).await
175 }
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct ProfileConfig {
183 pub client_id: Option<String>,
184 pub client_secret: Option<String>,
185 pub base_url: Option<String>,
186 pub callback_url: Option<String>,
187 pub da_nickname: Option<String>,
188 pub use_keychain: Option<bool>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub context_hub_id: Option<String>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub context_project_id: Option<String>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub context_account_id: Option<String>,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub(crate) struct ProfilesData {
203 pub active_profile: Option<String>,
204 pub profiles: HashMap<String, ProfileConfig>,
205}
206
207fn profiles_path() -> Result<PathBuf> {
208 let proj_dirs = directories::ProjectDirs::from("com", "autodesk", "raps")
209 .context("Failed to get project directories")?;
210 let config_dir = proj_dirs.config_dir();
211 std::fs::create_dir_all(config_dir)?;
212 Ok(config_dir.join("profiles.json"))
213}
214
215pub(crate) fn load_profiles() -> Result<ProfilesData> {
216 let path = profiles_path()?;
217 if !path.exists() {
218 return Ok(ProfilesData {
219 active_profile: None,
220 profiles: HashMap::new(),
221 });
222 }
223
224 let content = std::fs::read_to_string(&path)?;
225 let data: ProfilesData =
226 serde_json::from_str(&content).context("Failed to parse profiles.json")?;
227 Ok(data)
228}
229
230fn save_profiles(data: &ProfilesData) -> Result<()> {
231 let path = profiles_path()?;
232 let content = serde_json::to_string_pretty(data)?;
233 std::fs::write(&path, content)?;
234 Ok(())
235}