cargo_mobile2/config/
mod.rs

1pub mod app;
2pub mod metadata;
3mod raw;
4pub use raw::Raw;
5
6use self::{app::App, raw::*};
7#[cfg(target_os = "macos")]
8use crate::apple;
9use crate::{
10    android, bicycle, templating,
11    util::cli::{Report, Reportable, TextWrapper},
12};
13use serde::Serialize;
14use std::{
15    fmt::Debug,
16    io,
17    path::{Path, PathBuf},
18};
19use thiserror::Error;
20
21pub fn file_name() -> String {
22    format!("{}.toml", crate::NAME)
23}
24
25#[derive(Debug, Error)]
26pub enum FromRawError {
27    #[error(transparent)]
28    AppConfigInvalid(app::Error),
29    #[cfg(target_os = "macos")]
30    #[error(transparent)]
31    AppleConfigInvalid(apple::config::Error),
32    #[error(transparent)]
33    AndroidConfigInvalid(android::config::Error),
34}
35
36impl FromRawError {
37    pub fn report(&self, msg: &str) -> Report {
38        match self {
39            Self::AppConfigInvalid(err) => err.report(msg),
40            #[cfg(target_os = "macos")]
41            Self::AppleConfigInvalid(err) => err.report(msg),
42            Self::AndroidConfigInvalid(err) => err.report(msg),
43        }
44    }
45}
46
47#[derive(Debug, Error)]
48pub enum GenError {
49    #[error(transparent)]
50    PromptFailed(PromptError),
51    #[error(transparent)]
52    DetectFailed(DetectError),
53    #[error("Failed to canonicalize root dir: {0}")]
54    CanonicalizeFailed(io::Error),
55    #[error(transparent)]
56    FromRawFailed(FromRawError),
57    #[error(transparent)]
58    WriteFailed(WriteError),
59}
60
61impl Reportable for GenError {
62    fn report(&self) -> Report {
63        Report::error("Failed to generate config", self)
64    }
65}
66
67#[derive(Debug, Error)]
68pub enum LoadOrGenError {
69    #[error("Failed to load config: {0}")]
70    LoadFailed(LoadError),
71    #[error("Config file at {path} invalid: {cause}")]
72    FromRawFailed { path: PathBuf, cause: FromRawError },
73    #[error(transparent)]
74    GenFailed(GenError),
75}
76
77impl Reportable for LoadOrGenError {
78    fn report(&self) -> Report {
79        Report::error("Config error", self)
80    }
81}
82
83#[derive(Clone, Copy, Debug)]
84pub enum Origin {
85    FreshlyMinted,
86    Loaded,
87}
88
89impl Origin {
90    pub fn freshly_minted(self) -> bool {
91        matches!(self, Self::FreshlyMinted)
92    }
93}
94
95#[derive(Debug, Serialize)]
96#[serde(rename_all = "kebab-case")]
97pub struct Config {
98    app: App,
99    #[cfg(target_os = "macos")]
100    apple: apple::config::Config,
101    android: android::config::Config,
102}
103
104impl Config {
105    pub fn from_raw(root_dir: PathBuf, raw: Raw) -> Result<Self, FromRawError> {
106        let app = App::from_raw(root_dir, raw.app).map_err(FromRawError::AppConfigInvalid)?;
107        #[cfg(target_os = "macos")]
108        let apple = apple::config::Config::from_raw(app.clone(), raw.apple)
109            .map_err(FromRawError::AppleConfigInvalid)?;
110        let android = android::config::Config::from_raw(app.clone(), raw.android)
111            .map_err(FromRawError::AndroidConfigInvalid)?;
112        Ok(Self {
113            app,
114            #[cfg(target_os = "macos")]
115            apple,
116            android,
117        })
118    }
119
120    fn gen(
121        cwd: impl AsRef<Path>,
122        non_interactive: bool,
123        wrapper: &TextWrapper,
124    ) -> Result<Self, GenError> {
125        let raw = if !non_interactive {
126            Raw::prompt(wrapper).map_err(GenError::PromptFailed)
127        } else {
128            Raw::detect(wrapper).map_err(GenError::DetectFailed)
129        }?;
130        let root_dir = cwd
131            .as_ref()
132            .canonicalize()
133            .map_err(GenError::CanonicalizeFailed)?;
134        let config =
135            Self::from_raw(root_dir.clone(), raw.clone()).map_err(GenError::FromRawFailed)?;
136        log::info!("generated config: {:#?}", config);
137        raw.write(&root_dir).map_err(GenError::WriteFailed)?;
138        Ok(config)
139    }
140
141    pub fn load_or_gen(
142        cwd: impl AsRef<Path>,
143        non_interactive: bool,
144        wrapper: &TextWrapper,
145    ) -> Result<(Self, Origin), LoadOrGenError> {
146        let cwd = cwd.as_ref();
147        if let Some((root_dir, raw)) = Raw::load(cwd).map_err(LoadOrGenError::LoadFailed)? {
148            Self::from_raw(root_dir.clone(), raw)
149                .map(|config| (config, Origin::Loaded))
150                .map_err(|cause| LoadOrGenError::FromRawFailed {
151                    path: root_dir,
152                    cause,
153                })
154        } else {
155            Self::gen(cwd, non_interactive, wrapper)
156                .map(|config| (config, Origin::FreshlyMinted))
157                .map_err(LoadOrGenError::GenFailed)
158        }
159    }
160
161    pub fn path(&self) -> PathBuf {
162        self.app().root_dir().join(file_name())
163    }
164
165    pub fn app(&self) -> &App {
166        &self.app
167    }
168
169    #[cfg(target_os = "macos")]
170    pub fn apple(&self) -> &apple::config::Config {
171        &self.apple
172    }
173
174    pub fn android(&self) -> &android::config::Config {
175        &self.android
176    }
177
178    pub fn build_a_bike(&self) -> bicycle::Bicycle {
179        templating::init(Some(self))
180    }
181}