use crate::dependent_version_bundled::bundled_development_edges;
use crate::dependent_version_content::{PlanError, TrackedMode, TrackedPath, WorkspaceContent};
use crate::dependent_version_graph::{PlannedBump, WorkspaceNode, plan_dependent_bumps};
use crate::dependent_version_manifest::strict_text;
use crate::dependent_version_paths::PNPR_CONFIG_PATH;
use crate::dependent_version_publishable::read_publishable_names;
use crate::dependent_version_release::UnsupportedVersion;
use crate::dependent_version_states::{
ManifestState, bumped_names, duplicate_name, read_manifest_states,
};
use crate::dependent_version_text::replace_manifest_version;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ManifestBump {
pub planned: PlannedBump,
pub path: Vec<u8>,
pub mode: TrackedMode,
pub original: Vec<u8>,
pub replacement: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WorkspaceBumpPlan {
pub bumped_names: Vec<String>,
pub bumps: Vec<ManifestBump>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PlanOutcome {
Planned(
WorkspaceBumpPlan,
),
Unsupported(
UnsupportedVersion,
),
}
fn publishable_names(
content: &mut dyn WorkspaceContent,
tracked: &[TrackedPath],
) -> Result<Option<Vec<String>>, PlanError> {
let Some(config) = tracked
.iter()
.find(|entry: &&TrackedPath| return entry.path == PNPR_CONFIG_PATH)
else {
return Ok(None);
};
let bytes: Vec<u8> = content.candidate_bytes(&config.path)?;
return Ok(Some(read_publishable_names(strict_text(
&config.path,
&bytes,
)?)));
}
fn manifest_bump(planned: PlannedBump, state: &ManifestState) -> Result<ManifestBump, PlanError> {
let edited: String = replace_manifest_version(
&state.path,
&state.text,
&planned.bump.from,
&planned.bump.to,
)?;
let prefix: &[u8] = &state.bytes[..state.bytes.len() - state.text.len()];
return Ok(ManifestBump {
planned,
path: state.path.clone(),
mode: state.mode,
original: state.bytes.clone(),
replacement: [prefix, edited.as_bytes()].concat(),
});
}
pub fn plan_workspace_bumps(content: &mut dyn WorkspaceContent) -> Result<PlanOutcome, PlanError> {
let tracked: Vec<TrackedPath> = content.tracked_paths()?;
let states: Vec<ManifestState> = read_manifest_states(content, &tracked)?;
let bumped: Vec<String> = bumped_names(&states);
let empty = |bumped_names: Vec<String>| {
return Ok(PlanOutcome::Planned(WorkspaceBumpPlan {
bumped_names,
bumps: Vec::new(),
}));
};
if bumped.is_empty() {
return empty(bumped);
}
let Some(publishable) = publishable_names(content, &tracked)? else {
return empty(bumped);
};
if let Some(duplicate) = duplicate_name(&states) {
return Err(PlanError::PolicyIncomplete(duplicate));
}
let bundled: Vec<Vec<String>> = bundled_development_edges(content, &tracked, &states, &bumped)?;
let nodes: Vec<WorkspaceNode> = states
.iter()
.zip(bundled)
.map(|(state, edges): (&ManifestState, Vec<String>)| {
return WorkspaceNode {
name: state.facts.name.clone(),
directory: state.directory.clone(),
version: state.facts.version.clone(),
edge_names: [state.facts.runtime_dependency_names.clone(), edges].concat(),
};
})
.collect();
let planned: Vec<PlannedBump> = match plan_dependent_bumps(&nodes, &bumped, &publishable) {
Ok(found) => found,
Err(unsupported) => return Ok(PlanOutcome::Unsupported(unsupported)),
};
let mut bumps: Vec<ManifestBump> = Vec::new();
for bump in planned {
if let Some(state) = states
.iter()
.find(|state: &&ManifestState| return state.facts.name == bump.name)
{
bumps.push(manifest_bump(bump, state)?);
}
}
return Ok(PlanOutcome::Planned(WorkspaceBumpPlan {
bumped_names: bumped,
bumps,
}));
}
#[cfg(test)]
#[path = "dependent_version_plan_tests.rs"]
mod tests;