use std::collections::{BTreeSet, HashMap, HashSet};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::{ast::Sol, msbuild};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Solution<'a> {
pub path: &'a str,
pub format: &'a str,
pub product: &'a str,
pub versions: Vec<Version<'a>>,
pub projects: Vec<Project<'a>>,
pub configurations: BTreeSet<SolutionConfiguration<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dangling_project_configurations: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Version<'a> {
pub name: &'a str,
pub version: &'a str,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Project<'a> {
pub type_id: &'a str,
pub type_description: &'a str,
pub id: &'a str,
pub name: &'a str,
pub path_or_uri: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub configurations: Option<BTreeSet<ProjectConfiguration<'a>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Vec<&'a str>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub depends_from: Option<Vec<&'a str>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SolutionConfiguration<'a> {
pub configuration: &'a str,
pub platform: &'a str,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ProjectConfiguration<'a> {
pub configuration: &'a str,
pub solution_configuration: &'a str,
pub platform: &'a str,
pub tags: Vec<Tag>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Tag {
#[default]
Build,
Deploy,
}
impl<'a> Solution<'a> {
#[must_use]
pub fn from(solution: &Sol<'a>) -> Self {
Self {
path: solution.path,
format: solution.format,
product: solution.product,
versions: Self::versions(solution),
projects: Self::projects(solution),
configurations: Self::configurations(solution),
dangling_project_configurations: Self::dangings(solution),
}
}
pub fn iterate_projects(&'a self) -> impl Iterator<Item = &'a Project<'a>> {
self.projects
.iter()
.filter(|p| !msbuild::is_solution_folder(p.type_id))
}
pub fn iterate_projects_without_web_sites(&'a self) -> impl Iterator<Item = &'a Project<'a>> {
self.iterate_projects()
.filter(|p| !msbuild::is_web_site_project(p.type_id))
}
fn versions(solution: &Sol<'a>) -> Vec<Version<'a>> {
solution
.versions
.iter()
.map(|v| Version {
name: v.name,
version: v.ver,
})
.collect()
}
fn configurations(solution: &Sol<'a>) -> BTreeSet<SolutionConfiguration<'a>> {
solution
.solution_configs
.iter()
.map(|c| SolutionConfiguration {
configuration: c.config,
platform: c.platform,
})
.collect()
}
fn projects(solution: &Sol<'a>) -> Vec<Project<'a>> {
let project_configs = solution
.project_configs
.iter()
.map(|c| {
(
c.project_id,
c.configs
.iter()
.into_grouping_map_by(|pc| {
(pc.project_config, pc.solution_config, pc.platform)
})
.fold(
ProjectConfiguration::default(),
|mut pc, (p, s, plat), val| {
pc.configuration = p;
pc.solution_configuration = s;
pc.platform = plat;
match val.tag {
crate::ast::ProjectConfigTag::ActiveCfg => {}
crate::ast::ProjectConfigTag::Build => pc.tags.push(Tag::Build),
crate::ast::ProjectConfigTag::Deploy => {
pc.tags.push(Tag::Deploy);
}
};
pc
},
)
.into_values()
.collect(),
)
})
.collect::<HashMap<&str, BTreeSet<ProjectConfiguration>>>();
solution
.projects
.iter()
.map(|p| {
let items = if p.items.is_empty() {
None
} else {
Some(p.items.clone())
};
let depends_from = if p.depends_from.is_empty() {
None
} else {
Some(p.depends_from.clone())
};
Project {
type_id: p.type_id,
type_description: p.type_descr,
id: p.id,
name: p.name,
path_or_uri: p.path_or_uri,
configurations: project_configs.get(p.id).cloned(),
items,
depends_from,
}
})
.collect()
}
fn dangings(solution: &Sol<'a>) -> Option<Vec<String>> {
let project_ids: HashSet<String> = solution
.projects
.iter()
.filter(|p| !msbuild::is_solution_folder(p.type_id))
.map(|p| p.id.to_uppercase())
.collect();
let dangilings = solution
.project_configs
.iter()
.map(|p| p.project_id.to_uppercase())
.collect::<HashSet<String>>()
.difference(&project_ids)
.cloned()
.collect_vec();
if dangilings.is_empty() {
None
} else {
Some(dangilings)
}
}
}