use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum GraphKind {
Directed,
Undirected,
}
impl GraphKind {
pub const fn as_keyword(&self) -> &'static str {
match *self {
GraphKind::Directed => "digraph",
GraphKind::Undirected => "graph",
}
}
pub const fn as_edge_op(&self) -> &'static str {
match *self {
GraphKind::Directed => "->",
GraphKind::Undirected => "--",
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum RankDir {
TopBottom,
LeftRight,
BottomTop,
RightLeft,
}
impl RankDir {
pub const fn as_static_str(self) -> &'static str {
match self {
RankDir::TopBottom => "TB",
RankDir::LeftRight => "LR",
RankDir::BottomTop => "BT",
RankDir::RightLeft => "RL",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IdError {
EmptyName,
InvalidStartChar(char),
InvalidChar(char),
}
impl std::error::Error for IdError {}
impl std::fmt::Display for IdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IdError::EmptyName => write!(f, "Id cannot be empty"),
IdError::InvalidStartChar(c) => write!(f, "Id cannot begin with '{c}'"),
IdError::InvalidChar(c) => write!(f, "Id cannot contain '{c}'"),
}
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Id<'a> {
pub(crate) name: Cow<'a, str>,
}
impl<'a> std::ops::Deref for Id<'a> {
type Target = Cow<'a, str>;
fn deref(&self) -> &Self::Target {
&self.name
}
}
impl<'a> Id<'a> {
pub fn new(name: impl Into<Cow<'a, str>>) -> Result<Id<'a>, IdError> {
let name = name.into();
match name.chars().next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
Some(c) => return Err(IdError::InvalidStartChar(c)),
None => return Err(IdError::EmptyName),
}
if let Some(c) = name
.chars()
.find(|c| !(c.is_ascii_alphanumeric() || *c == '_'))
{
return Err(IdError::InvalidChar(c));
}
Ok(Id { name })
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub enum Text<'a> {
Label(Cow<'a, str>),
Esc(Cow<'a, str>),
Html(Cow<'a, str>),
}
impl<'a> Text<'a> {
pub fn label(s: impl Into<Cow<'a, str>>) -> Self {
Self::Label(s.into())
}
pub fn esc(s: impl Into<Cow<'a, str>>) -> Self {
Self::Esc(s.into())
}
pub fn html(s: impl Into<Cow<'a, str>>) -> Self {
Self::Html(s.into())
}
pub fn to_escaped_string(&self) -> String {
match self {
Self::Label(s) => format!("\"{}\"", s.escape_default()),
Self::Esc(s) => format!("\"{}\"", Text::escape_str(s)),
Self::Html(s) => format!("<{s}>"),
}
}
pub fn into_inner(self) -> Cow<'a, str> {
match self {
Text::Label(s) | Text::Esc(s) | Text::Html(s) => s,
}
}
pub(crate) fn escape_char(c: char, mut f: impl FnMut(char)) {
match c {
'\\' => f(c),
_ => {
for c in c.escape_default() {
f(c)
}
}
}
}
pub(crate) fn escape_str(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
Text::escape_char(c, |c| out.push(c));
}
out
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Style {
None,
Solid,
Dashed,
Dotted,
Bold,
Rounded,
Diagonals,
Filled,
Striped,
Wedged,
}
impl Style {
pub const fn as_static_str(self) -> &'static str {
match self {
Style::None => "",
Style::Solid => "solid",
Style::Dashed => "dashed",
Style::Dotted => "dotted",
Style::Bold => "bold",
Style::Rounded => "rounded",
Style::Diagonals => "diagonals",
Style::Filled => "filled",
Style::Striped => "striped",
Style::Wedged => "wedged",
}
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum ShapeFill {
Open,
Filled,
}
impl ShapeFill {
pub const fn as_static_str(self) -> &'static str {
match self {
ShapeFill::Open => "o",
ShapeFill::Filled => "",
}
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum Side {
Left,
Right,
Both,
}
impl Side {
pub const fn as_static_str(self) -> &'static str {
match self {
Side::Left => "l",
Side::Right => "r",
Side::Both => "",
}
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum ArrowVertex {
None,
Normal(ShapeFill, Side),
Box(ShapeFill, Side),
Crow(Side),
Curve(Side),
ICurve(ShapeFill, Side),
Diamond(ShapeFill, Side),
Dot(ShapeFill),
Inv(ShapeFill, Side),
Tee(Side),
Vee(Side),
}
impl ArrowVertex {
pub fn none() -> ArrowVertex {
ArrowVertex::None
}
pub fn normal() -> ArrowVertex {
ArrowVertex::Normal(ShapeFill::Filled, Side::Both)
}
pub fn boxed() -> ArrowVertex {
ArrowVertex::Box(ShapeFill::Filled, Side::Both)
}
pub fn crow() -> ArrowVertex {
ArrowVertex::Crow(Side::Both)
}
pub fn curve() -> ArrowVertex {
ArrowVertex::Curve(Side::Both)
}
pub fn icurve() -> ArrowVertex {
ArrowVertex::ICurve(ShapeFill::Filled, Side::Both)
}
pub fn diamond() -> ArrowVertex {
ArrowVertex::Diamond(ShapeFill::Filled, Side::Both)
}
pub fn dot() -> ArrowVertex {
ArrowVertex::Dot(ShapeFill::Filled)
}
pub fn inv() -> ArrowVertex {
ArrowVertex::Inv(ShapeFill::Filled, Side::Both)
}
pub fn tee() -> ArrowVertex {
ArrowVertex::Tee(Side::Both)
}
pub fn vee() -> ArrowVertex {
ArrowVertex::Vee(Side::Both)
}
pub fn to_dot_string(&self) -> String {
let mut res = String::new();
match *self {
Self::Box(fill, side)
| Self::ICurve(fill, side)
| Self::Diamond(fill, side)
| Self::Inv(fill, side)
| Self::Normal(fill, side) => {
res.push_str(fill.as_static_str());
match side {
Side::Left | Side::Right => res.push_str(side.as_static_str()),
Side::Both => {}
};
}
Self::Dot(fill) => res.push_str(fill.as_static_str()),
Self::Crow(side) | Self::Curve(side) | Self::Tee(side) | Self::Vee(side) => {
match side {
Side::Left | Side::Right => res.push_str(side.as_static_str()),
Side::Both => {}
}
}
Self::None => {}
};
match *self {
Self::None => res.push_str("none"),
Self::Normal(_, _) => res.push_str("normal"),
Self::Box(_, _) => res.push_str("box"),
Self::Crow(_) => res.push_str("crow"),
Self::Curve(_) => res.push_str("curve"),
Self::ICurve(_, _) => res.push_str("icurve"),
Self::Diamond(_, _) => res.push_str("diamond"),
Self::Dot(_) => res.push_str("dot"),
Self::Inv(_, _) => res.push_str("inv"),
Self::Tee(_) => res.push_str("tee"),
Self::Vee(_) => res.push_str("vee"),
};
res
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Arrow {
pub arrows: Vec<ArrowVertex>,
}
impl Arrow {
pub fn is_default(&self) -> bool {
self.arrows.is_empty()
}
pub fn none() -> Arrow {
Arrow {
arrows: vec![ArrowVertex::None],
}
}
pub fn normal() -> Arrow {
Arrow {
arrows: vec![ArrowVertex::normal()],
}
}
pub fn to_dot_string(&self) -> String {
let mut cow = String::new();
for arrow in &self.arrows {
cow.push_str(&arrow.to_dot_string());
}
cow
}
}
impl Default for Arrow {
fn default() -> Arrow {
Arrow { arrows: vec![] }
}
}
impl From<ArrowVertex> for Arrow {
fn from(vertex: ArrowVertex) -> Self {
Arrow {
arrows: vec![vertex],
}
}
}
macro_rules! impl_arrow_from_vertex_array {
( $($n:literal)+ ) => {
$(
impl From<[ArrowVertex; $n]> for Arrow {
fn from(shape: [ArrowVertex; $n]) -> Arrow {
Arrow {
arrows: shape.to_vec(),
}
}
}
)+
};
}
impl_arrow_from_vertex_array!(1 2 3 4);
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum CompassPoint {
North,
NorthEast,
East,
SouthEast,
South,
SouthWest,
West,
NorthWest,
Center,
}
impl CompassPoint {
pub const fn as_static_str(&self) -> &'static str {
use CompassPoint as C;
match self {
C::North => ":n",
C::NorthEast => ":ne",
C::East => ":e",
C::SouthEast => ":se",
C::South => ":s",
C::SouthWest => ":sw",
C::West => ":w",
C::NorthWest => ":nw",
C::Center => ":c",
}
}
}