diff --git a/Cargo.lock b/Cargo.lock index a0898a9..b25c0bd 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,6 +873,35 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap 2.9.0", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2981,6 +3010,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "ureq", "url", "uuid", "windows 0.61.1", @@ -3379,7 +3409,9 @@ version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -4300,6 +4332,39 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea" +dependencies = [ + "base64", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -4312,6 +4377,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -4618,6 +4689,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "0.26.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 35b051b..341cd3a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,4 @@ uuid = { version = "1.16.0", features = ["serde"] } url = { version = "2.5.4", features = ["serde"] } langtag = { version = "0.4.0", features = ["serde"] } serde_with = "3.12.0" +ureq = { version = "3.0.11", features = ["json"] } diff --git a/src/app.rs b/src/app.rs index 5dbf4c3..eea5601 100755 --- a/src/app.rs +++ b/src/app.rs @@ -1,21 +1,23 @@ use std::{fmt::Display, string::ToString, sync::Arc}; -use arch::Arch; +use arch::{Arch, ArchList}; use choice::ChoiceOption; use egui::{Galley, Rect, Ui}; +use versions::ChoiceHolder; pub(crate) mod arch; #[macro_use] pub(crate) mod choice; +mod versions; #[derive(Debug, Clone)] pub struct App { env_setup_level: EnvSetupLevel, preview_channel: bool, - msvc_version: String, - sdk_version: String, - host_arch: String, - target_arch: String, + msvc: ChoiceHolder>, + sdk: ChoiceHolder>, + host_arch: ChoiceHolder, + target_arch: ChoiceHolder, install_path: String, } @@ -23,16 +25,15 @@ impl Default for App { fn default() -> Self { Self { env_setup_level: EnvSetupLevel::default(), - preview_channel: bool::default(), - msvc_version: String::default(), - sdk_version: String::default(), - host_arch: Arch::default().to_string(), - target_arch: Arch::default().to_string(), - install_path: String::default(), + preview_channel: false, + msvc: ChoiceHolder::new(ChoiceOption::MsvcVersion(choices!("2019", "2022"))).unwrap(), + sdk: ChoiceHolder::new(ChoiceOption::SdkVersion(choices!("10.0.22621.0"))).unwrap(), + host_arch: ChoiceHolder::new(ChoiceOption::HostArch(ArchList::default())).unwrap(), + target_arch: ChoiceHolder::new(ChoiceOption::TargetArch(ArchList::default())).unwrap(), + install_path: String::new(), } } } - #[derive(Debug, Clone, Copy, Default, PartialEq)] pub enum EnvSetupLevel { #[default] @@ -69,21 +70,21 @@ impl App { }); } - fn selection_menu( - selected: &mut String, + fn selection_menu + PartialEq, V: Into>>( + selected: &mut T, ui: &mut Ui, width: f32, - component: ChoiceOption>, + component: ChoiceOption, ) { let name = component.to_string(); ui.label(name); egui::ComboBox::from_id_salt(&component) .width(width) - .selected_text(selected.clone()) // FIXME + .selected_text(selected.to_string()) // FIXME .show_ui(ui, |ui| { component.options().iter().for_each(|opt| { - ui.selectable_value(selected, opt.to_string(), opt.to_string()); + ui.selectable_value(selected.into(), opt.clone().into(), opt.to_string()); }); }); } @@ -186,19 +187,21 @@ impl eframe::App for App { ui.add_space(15.0); ui.horizontal(|ui| { let width_per_choice = ctx.screen_rect().width() / 4.75; - #[rustfmt::skip] - let choices = vec![ - (&mut self.msvc_version, ChoiceOption::MsvcVersion(choices!("12.3", "12.4", "12.4+extra"))), - (&mut self.sdk_version, ChoiceOption::SdkVersion(choices!("12.3", "12.4", "12.4+extra"))), - (&mut self.host_arch, ChoiceOption::HostArch(choices!(Arch::X86, Arch::X64))), - (&mut self.target_arch, ChoiceOption::TargetArch(choices!(Arch::X86, Arch::X64))), - ]; - - for (selection, choice) in choices { - ui.vertical(|ui| { - Self::selection_menu(selection, ui, width_per_choice, choice.clone()) - }); + macro_rules! selection_menus { + ($($choice:expr),*) => { + $(ui.vertical(|ui| { + let x = $choice.available.clone(); + Self::selection_menu($choice.selected_mut(), ui, width_per_choice, x) + });)* + }; } + + selection_menus![ + self.msvc, + self.sdk, + self.host_arch, + self.target_arch + ]; }); ui.add_space(25.0); diff --git a/src/app/arch.rs b/src/app/arch.rs index a9e8225..fdb434d 100755 --- a/src/app/arch.rs +++ b/src/app/arch.rs @@ -1,22 +1,80 @@ -use std::env::consts; +use std::{env::consts, ops::Deref, str::FromStr}; use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; -#[derive(Debug, Clone, Copy, EnumString, Display, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, EnumString, Display, Deserialize, Serialize, PartialEq)] #[strum(serialize_all = "lowercase")] #[serde(rename_all = "lowercase")] pub enum Arch { X86, + #[strum(to_string = "x64", serialize = "x86_64")] X64, } +impl Arch { + pub fn all() -> [Arch; 2] { + match Arch::default() { + Arch::X86 => [Arch::X86, Arch::X64], + Arch::X64 => [Arch::X64, Arch::X86], + } + } +} + impl Default for Arch { fn default() -> Self { - match consts::ARCH { - "x86" => Self::X86, - "x86_64" => Self::X64, - _ => unreachable!(), - } + Self::from_str(consts::ARCH).unwrap() + } +} + +impl From for Arch { + fn from(s: String) -> Self { + Self::from_str(s.as_str()).unwrap_or_default() + } +} + +impl Into for Arch { + fn into(self) -> String { + self.to_string() + } +} + +#[derive(Debug, Clone)] +pub struct ArchList(pub Vec); + +impl From for Vec { + fn from(archs: ArchList) -> Vec { + archs.0.into_iter().map(|arch| arch.to_string()).collect() + } +} + +impl From> for ArchList { + fn from(archs: Vec) -> ArchList { + ArchList(archs.into_iter().map(|arch| arch.into()).collect()) + } +} + +impl From> for ArchList { + fn from(archs: Vec) -> ArchList { + ArchList(archs) + } +} + +impl Deref for ArchList { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Into> for ArchList { + fn into(self) -> Vec { + self.0 + } +} + +impl Default for ArchList { + fn default() -> Self { + Self(Arch::all().to_vec()) } } \ No newline at end of file diff --git a/src/app/choice.rs b/src/app/choice.rs index d678231..d26b36f 100755 --- a/src/app/choice.rs +++ b/src/app/choice.rs @@ -40,3 +40,11 @@ impl>> Hash for ChoiceOption { self.to_string().hash(state); } } + +impl>> IntoIterator for ChoiceOption { + type Item = String; + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.options().into_iter() + } +} \ No newline at end of file diff --git a/src/app/versions.rs b/src/app/versions.rs new file mode 100755 index 0000000..e595b63 --- /dev/null +++ b/src/app/versions.rs @@ -0,0 +1,82 @@ +use std::str::FromStr; + +use super::{ + arch::{Arch, ArchList}, + choice::ChoiceOption, +}; + +use color_eyre::{Result, eyre::eyre}; + +#[derive(Debug, Clone)] +pub struct ChoiceHolder>> +where + Vec: From, +{ + pub available: ChoiceOption, + pub selected: T, +} + +impl>> ChoiceHolder +where + Vec: From, + T: From, +{ + pub fn new(available: ChoiceOption) -> Result { + Ok(Self { + available: available.clone(), + selected: Into::::into( + available + .options() + .first() + .ok_or(eyre!("Available options must not be an empty Vec"))? + .clone(), + ), + }) + } + + pub fn selected_mut(&mut self) -> &mut T { + &mut self.selected + } +} + +// FIXME: Following code is very ugly, consider using a macro + +impl Into>> for ChoiceHolder { + fn into(self) -> ChoiceHolder> { + match self.available { + ChoiceOption::HostArch(archs) => ChoiceHolder::new(ChoiceOption::HostArch( + archs.clone().iter().map(|arch| arch.to_string()).collect(), + )) + .unwrap(), + ChoiceOption::TargetArch(archs) => ChoiceHolder::new(ChoiceOption::TargetArch( + archs.clone().iter().map(|arch| arch.to_string()).collect(), + )) + .unwrap(), + _ => unreachable!(), + } + } +} + +impl From>> for ChoiceHolder { + fn from(value: ChoiceHolder>) -> Self { + match value.available { + ChoiceOption::HostArch(archs) => ChoiceHolder::new(ChoiceOption::HostArch( + archs + .iter() + .map(|arch| Arch::from_str(arch.as_str()).unwrap()) + .collect::>() + .into(), + )) + .unwrap(), + ChoiceOption::TargetArch(archs) => ChoiceHolder::new(ChoiceOption::TargetArch( + archs + .iter() + .map(|arch| Arch::from_str(arch.as_str()).unwrap()) + .collect::>() + .into(), + )) + .unwrap(), + _ => unreachable!(), + } + } +}