#![allow(clippy::derivable_impls)]
use std::convert::identity;
use camino::Utf8PathBuf;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use crate::errors::*;
pub mod axoproject;
mod builds;
mod components;
mod marketing;
pub mod oranda_config;
pub mod project;
pub mod style;
mod workspace;
pub use self::axoproject::AxoprojectLayer;
pub use self::oranda_config::OrandaLayer;
pub use builds::{BuildConfig, BuildLayer};
pub use components::{
ArtifactsConfig, ArtifactsLayer, ComponentConfig, ComponentLayer, FundingConfig, FundingLayer,
MdBookConfig, MdBookLayer, PackageManagersConfig, PackageManagersLayer, ReleasesSource,
};
pub use marketing::{AnalyticsConfig, MarketingConfig, MarketingLayer, SocialConfig, SocialLayer};
pub use workspace::{WorkspaceConfig, WorkspaceLayer, WorkspaceMember};
pub use project::{ProjectConfig, ProjectLayer};
pub use style::{StyleConfig, StyleLayer};
#[derive(Debug, Clone)]
pub struct Config {
pub project: ProjectConfig,
pub build: BuildConfig,
pub marketing: MarketingConfig,
pub styles: StyleConfig,
pub components: ComponentConfig,
pub workspace: WorkspaceConfig,
}
impl Config {
pub fn build(config_path: &Utf8PathBuf) -> Result<Config> {
let custom = OrandaLayer::load(config_path)?;
let project = AxoprojectLayer::load(None)?;
let mut cfg = Config::default();
cfg.apply_project_layer(project);
cfg.apply_custom_layer(custom);
cfg.apply_autodetect_layer(None)?;
Ok(cfg)
}
pub fn build_workspace_root(config_path: &Utf8PathBuf) -> Result<Config> {
let conf = OrandaLayer::load(config_path)?;
let (set_members, set_auto) = conf
.as_ref()
.and_then(|c| {
c.workspace.as_ref().map(|w| {
let set_members = w.members.is_some();
let set_auto = w.auto.is_some_and(identity);
(set_members, set_auto)
})
})
.unwrap_or((false, false));
let mut cfg = Config::default();
cfg.apply_custom_layer(conf);
if !set_members && set_auto {
let root_path = config_path.parent().unwrap();
let workspace = AxoprojectLayer::load_workspace(root_path)?;
if let Some(detected_members) = workspace.and_then(|w| w.members) {
cfg.workspace.members = detected_members;
}
}
Ok(cfg)
}
#[instrument("workspace_page", fields(prefix = prefix))]
pub fn build_workspace_member(
config_path: &Utf8PathBuf,
root_config_path: &Utf8PathBuf,
project_root: &Utf8PathBuf,
workspace_member: &WorkspaceMember,
prefix: Option<String>,
) -> Result<Config> {
let member_conf = OrandaLayer::load(config_path)?;
let root_conf = OrandaLayer::load(root_config_path)?;
let project = AxoprojectLayer::load(Some(project_root.into()))?;
if let Some(member_conf) = &member_conf {
if member_conf.workspace.is_some() {
tracing::warn!("oranda.json contains workspace configuration! You likely want to set this in the root oranda-workspace.json.");
}
}
let mut cfg = Config::default();
cfg.apply_project_layer(project);
cfg.apply_custom_layer(root_conf);
cfg.apply_custom_layer(member_conf);
cfg.apply_autodetect_layer(Some(workspace_member))?;
Ok(cfg)
}
fn apply_project_layer(&mut self, layer: Option<AxoprojectLayer>) {
if let Some(layer) = layer {
let AxoprojectLayer {
project,
cargo_dist,
members: _,
} = layer;
self.project.apply_val_layer(project);
if let Some(artifacts) = &mut self.components.artifacts {
artifacts.cargo_dist.apply_val(cargo_dist);
}
}
}
fn apply_custom_layer(&mut self, layer: Option<OrandaLayer>) {
if let Some(layer) = layer {
let OrandaLayer {
project,
build,
marketing,
styles,
components,
workspace,
_schema,
} = layer;
self.project.apply_val_layer(project);
self.build.apply_val_layer(build);
self.marketing.apply_val_layer(marketing);
self.styles.apply_val_layer(styles);
self.components.apply_val_layer(components);
self.workspace.apply_val_layer(workspace);
}
}
fn apply_autodetect_layer(&mut self, workspace_member: Option<&WorkspaceMember>) -> Result<()> {
let start_dir = workspace_member
.map(|m| m.path.clone())
.unwrap_or(".".into());
MdBookConfig::find_paths(&mut self.components.mdbook, &start_dir)?;
FundingConfig::find_paths(&mut self.components.funding, &start_dir)?;
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Config {
project: ProjectConfig::default(),
build: BuildConfig::default(),
marketing: MarketingConfig::default(),
styles: StyleConfig::default(),
components: ComponentConfig::default(),
workspace: WorkspaceConfig::default(),
}
}
}
pub trait ApplyLayer
where
Self: Sized,
{
type Layer;
fn apply_layer(&mut self, layer: Self::Layer);
fn apply_val_layer(&mut self, layer: Option<Self::Layer>) {
if let Some(val) = layer {
self.apply_layer(val);
}
}
}
pub trait ApplyBoolLayerExt {
type Inner;
fn apply_bool_layer(&mut self, layer: Option<BoolOr<Self::Inner>>);
}
impl<T> ApplyBoolLayerExt for Option<T>
where
T: ApplyLayer + Default,
{
type Inner = T::Layer;
fn apply_bool_layer(&mut self, layer: Option<BoolOr<Self::Inner>>) {
match layer {
Some(BoolOr::Val(val)) => {
if let Some(this) = self {
this.apply_layer(val);
} else {
let mut t = T::default();
t.apply_layer(val);
*self = Some(t);
}
}
Some(BoolOr::Bool(false)) => {
*self = None;
}
Some(BoolOr::Bool(true)) => {
if self.is_none() {
*self = Some(T::default());
}
}
None => {}
}
}
}
pub trait ApplyValExt
where
Self: Sized,
{
fn apply_val(&mut self, layer: Option<Self>);
}
impl<T> ApplyValExt for T {
fn apply_val(&mut self, layer: Option<Self>) {
if let Some(val) = layer {
*self = val;
}
}
}
pub trait ApplyOptExt
where
Self: Sized,
{
fn apply_opt(&mut self, layer: Self);
}
impl<T> ApplyOptExt for Option<T> {
fn apply_opt(&mut self, layer: Self) {
if let Some(val) = layer {
*self = Some(val);
}
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(untagged)]
pub enum BoolOr<T> {
Bool(bool),
Val(T),
}