use std::collections::BTreeMap;
use std::fmt;
use std::ops::RangeInclusive;
use miette::SourceSpan;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize};
use strum::{EnumString, IntoStaticStr, VariantNames};
use crate::graph::WorkflowGraph;
use crate::path::ElementPath;
use crate::properties::{ComponentProperties, SpannedText};
use crate::unique_key_map::UniqueKeyMapVisitor;
use crate::workflow::{Workflow, WorkflowId};
pub const MAX_MAGNITUDE: f64 = 1_000_000.0;
#[derive(Clone, Debug, PartialEq)]
pub struct Model {
pub(crate) view: Option<ViewAngle>,
pub(crate) elements: BTreeMap<ElementPath, Element>,
pub(crate) workflows: Vec<Workflow>,
}
impl Model {
pub fn view(&self) -> Option<ViewAngle> {
self.view
}
pub fn element(&self, path: &ElementPath) -> Option<&Element> {
self.elements.get(path)
}
pub fn elements(&self) -> &BTreeMap<ElementPath, Element> {
&self.elements
}
pub fn workflow(&self, id: &WorkflowId) -> Option<&Workflow> {
let mut workflows = self.workflows.iter();
workflows.find(|workflow| workflow.id() == id)
}
pub fn workflows(&self) -> &[Workflow] {
&self.workflows
}
pub fn descriptions(&self) -> impl Iterator<Item = &str> {
let element_descriptions = self.elements.values().filter_map(Element::description);
let workflow_descriptions = self.workflows.iter().flat_map(Workflow::descriptions);
element_descriptions.chain(workflow_descriptions)
}
pub fn showing(&self, id: &WorkflowId) -> Option<ShownWorkflow<'_>> {
let workflow = self.workflow(id)?;
let shown_workflow = ShownWorkflow {
model: self,
workflow,
};
Some(shown_workflow)
}
pub fn showing_first_workflow(&self) -> ShownWorkflow<'_> {
let workflow = self
.workflows
.first()
.expect("every model handed out of this crate has a workflow");
ShownWorkflow {
model: self,
workflow,
}
}
pub fn showing_each_workflow(&self) -> impl Iterator<Item = ShownWorkflow<'_>> {
self.workflows.iter().map(|workflow| ShownWorkflow {
model: self,
workflow,
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct ShownWorkflow<'m> {
model: &'m Model,
workflow: &'m Workflow,
}
impl<'m> ShownWorkflow<'m> {
pub fn projected(self) -> ProjectedWorkflow<'m> {
let graph = WorkflowGraph::of(self.model, self.workflow);
ProjectedWorkflow {
workflow: self.workflow,
graph,
}
}
pub fn model(self) -> &'m Model {
self.model
}
pub fn workflow(self) -> &'m Workflow {
self.workflow
}
}
#[derive(Clone, Debug)]
pub struct ProjectedWorkflow<'m> {
workflow: &'m Workflow,
graph: WorkflowGraph,
}
impl<'m> ProjectedWorkflow<'m> {
pub fn into_graph(self) -> WorkflowGraph {
self.graph
}
pub fn workflow(&self) -> &'m Workflow {
self.workflow
}
pub fn graph(&self) -> &WorkflowGraph {
&self.graph
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(from = "ElementDocument", into = "ElementDocument")]
pub struct Element {
label: String,
pub(crate) description: Option<String>,
pub(crate) properties: ComponentProperties,
span: Option<ByteRange>,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct ElementDocument {
label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
caption: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
shape: Option<Shape>,
#[serde(default, skip_serializing_if = "Option::is_none")]
height: Option<f64>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "UniqueKeyMapVisitor::deserialize"
)]
style: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
link: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
link_span: Option<ByteRange>,
#[serde(default, skip_serializing_if = "Option::is_none")]
code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
code_span: Option<ByteRange>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "UniqueKeyMapVisitor::deserialize"
)]
metadata: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
span: Option<ByteRange>,
#[serde(
default,
rename = "badge",
skip_serializing,
deserialize_with = "ElementDocument::reject_badge"
)]
_moved_badge: (),
#[serde(
default,
rename = "position",
skip_serializing,
deserialize_with = "ElementDocument::reject_position"
)]
_moved_position: (),
#[serde(
default,
rename = "child_placement",
skip_serializing,
deserialize_with = "ElementDocument::reject_child_placement"
)]
_moved_child_placement: (),
}
impl Element {
pub fn new(label: impl Into<String>) -> Self {
let label = label.into();
Self {
label,
description: None,
properties: ComponentProperties::new(),
span: None,
}
}
pub fn relabeled(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
let description = description.into();
self.description = Some(description);
self
}
pub fn with_properties(mut self, properties: ComponentProperties) -> Self {
self.properties = properties;
self
}
pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
self.properties = self.properties.with_caption(caption);
self
}
pub fn with_shape(mut self, shape: Shape) -> Self {
self.properties = self.properties.with_shape(shape);
self
}
pub fn with_height(mut self, height: f64) -> Self {
self.properties = self.properties.with_height(height);
self
}
pub fn with_style(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.properties = self.properties.with_style(key, value);
self
}
pub fn with_link_written_at(
mut self,
link: impl Into<String>,
span: Option<ByteRange>,
) -> Self {
self.properties = self.properties.with_link_written_at(link, span);
self
}
pub fn with_span(mut self, span: ByteRange) -> Self {
self.span = Some(span);
self
}
pub fn label(&self) -> &str {
&self.label
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn properties(&self) -> &ComponentProperties {
&self.properties
}
pub fn span(&self) -> Option<ByteRange> {
self.span
}
pub fn source_span(&self) -> Option<SourceSpan> {
self.span.map(SourceSpan::from)
}
}
impl From<ElementDocument> for Element {
fn from(document: ElementDocument) -> Self {
let ElementDocument {
label,
caption,
description,
shape,
height,
style,
link,
link_span,
code,
code_span,
metadata,
span,
_moved_badge,
_moved_position,
_moved_child_placement,
} = document;
let link = SpannedText::written(link, link_span);
let code = SpannedText::written(code, code_span);
let properties = ComponentProperties {
caption,
shape,
height,
style,
link,
code,
metadata,
};
Self {
label,
description,
properties,
span,
}
}
}
impl From<Element> for ElementDocument {
fn from(element: Element) -> Self {
let ComponentProperties {
caption,
shape,
height,
style,
link,
code,
metadata,
} = element.properties;
let (link, link_span) = SpannedText::unpaired(link);
let (code, code_span) = SpannedText::unpaired(code);
Self {
label: element.label,
caption,
description: element.description,
shape,
height,
style,
link,
link_span,
code,
code_span,
metadata,
span: element.span,
_moved_badge: (),
_moved_position: (),
_moved_child_placement: (),
}
}
}
impl ElementDocument {
fn reject_badge<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
let moved_badge_message = "`badge` moved from elements to edges: set `\"badge\": true` on each edge into this element";
let moved_badge_error = de::Error::custom(moved_badge_message);
Err(moved_badge_error)
}
fn reject_position<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
let moved_position_message =
"`position` moved from elements to workflows: set it in each workflow's `overrides`";
let moved_position_error = de::Error::custom(moved_position_message);
Err(moved_position_error)
}
fn reject_child_placement<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
let moved_child_placement_message = "`child_placement` moved from elements to workflows: set it in each workflow's `overrides`";
let moved_child_placement_error = de::Error::custom(moved_child_placement_message);
Err(moved_child_placement_error)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum Shape {
Box,
Slab,
Cylinder,
Disc,
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(rename_all = "lowercase")]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum ViewAngle {
#[default]
Isometric,
Top,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum Traveler {
Dot,
Envelope,
Parcel,
Document,
Key,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", deny_unknown_fields)]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum PlacementHint {
Layered,
Grid { columns: u32 },
Row,
Column,
}
impl PlacementHint {
pub const MAX_PACKED_TRACKS: u32 = 32_766;
pub fn packed_tracks(self, child_count: usize) -> Option<(usize, usize)> {
match self {
Self::Layered | Self::Grid { columns: 0 } => None,
Self::Grid { columns } => {
let column_count = usize::try_from(columns).unwrap_or(usize::MAX);
let row_count = child_count.div_ceil(column_count);
Some((column_count, row_count))
}
Self::Row => Some((child_count, 1)),
Self::Column => Some((1, child_count)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Position {
pub x: f64,
pub y: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ByteRange {
pub start: usize,
pub end: usize,
}
impl ByteRange {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}
impl From<ByteRange> for SourceSpan {
fn from(range: ByteRange) -> Self {
let byte_range = range.start..range.end;
byte_range.into()
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EdgeId {
from: ElementPath,
to: ElementPath,
ordinal: u32,
}
impl EdgeId {
pub fn new(from: ElementPath, to: ElementPath, ordinal: u32) -> Self {
Self { from, to, ordinal }
}
pub(crate) fn next_free_in(
edges: &BTreeMap<EdgeId, Edge>,
from: ElementPath,
to: ElementPath,
) -> Self {
let parallel_ids = Self::every_parallel_id(from, to);
let first_parallel_id = parallel_ids.start();
let mut parallel_edges = edges.range(parallel_ids.clone());
let last_parallel_edge = parallel_edges.next_back();
let ordinal = last_parallel_edge.map_or(0, |(id, _edge)| id.ordinal() + 1);
first_parallel_id.parallel(ordinal)
}
pub(crate) fn every_parallel_id(from: ElementPath, to: ElementPath) -> RangeInclusive<Self> {
let first_parallel_id = Self::new(from, to, 0);
let last_parallel_id = first_parallel_id.parallel(u32::MAX);
first_parallel_id..=last_parallel_id
}
pub fn home_level(&self) -> ElementPath {
self.from.lowest_common_ancestor(&self.to)
}
fn parallel(&self, ordinal: u32) -> Self {
let from = self.from.clone();
let to = self.to.clone();
Self { from, to, ordinal }
}
pub fn joins_element_to_itself_or_its_lineage(&self) -> bool {
self.from.shares_lineage_with(&self.to)
}
pub fn from(&self) -> &ElementPath {
&self.from
}
pub fn to(&self) -> &ElementPath {
&self.to
}
pub fn ordinal(&self) -> u32 {
self.ordinal
}
}
impl fmt::Display for EdgeId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"({} -> {})[{}]",
self.from, self.to, self.ordinal
)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Edge {
pub(crate) label: Option<String>,
pub(crate) description: Option<String>,
pub(crate) style: BTreeMap<String, String>,
pub(crate) traveler: Option<Traveler>,
pub(crate) badge: bool,
pub(crate) span: Option<ByteRange>,
}
impl Edge {
pub fn new() -> Self {
Self::default()
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
let label = label.into();
self.label = Some(label);
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
let description = description.into();
self.description = Some(description);
self
}
pub fn with_style(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let key = key.into();
let value = value.into();
self.style.insert(key, value);
self
}
pub fn with_traveler(mut self, traveler: Traveler) -> Self {
self.traveler = Some(traveler);
self
}
pub fn with_badge(mut self) -> Self {
self.badge = true;
self
}
pub fn with_span(mut self, span: ByteRange) -> Self {
self.span = Some(span);
self
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn style(&self) -> &BTreeMap<String, String> {
&self.style
}
pub fn traveler(&self) -> Option<Traveler> {
self.traveler
}
pub fn is_badge(&self) -> bool {
self.badge
}
pub fn span(&self) -> Option<ByteRange> {
self.span
}
pub fn source_span(&self) -> Option<SourceSpan> {
self.span.map(SourceSpan::from)
}
}