use serde::{Deserialize, Serialize};
use std::{
cmp::{max, min, Ordering},
collections::{BTreeSet, HashSet},
convert::TryFrom,
fmt::{self, Display, Formatter},
hash::Hash,
};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum Error {
InvalidId(String),
WrongNodeType {
expected: String,
actual: Node,
},
WrongChildType {
expected: String,
actual: Child,
},
ChildrenMismatch(Vec<Child>),
}
impl Error {
fn wrong_node_type(expected: &str, actual: &Node) -> Self {
Error::WrongNodeType {
expected: String::from(expected),
actual: actual.clone(),
}
}
fn wrong_child_type(expected: &str, actual: &Child) -> Self {
Error::WrongChildType {
expected: String::from(expected),
actual: actual.clone(),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Id(pub(crate) Uuid);
impl Id {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn root() -> Self {
Self(Uuid::nil())
}
}
impl Default for Id {
fn default() -> Self {
Self::new()
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Id> for String {
fn from(id: Id) -> Self {
format!("{}", id)
}
}
impl Ord for Id {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Id {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl TryFrom<&str> for Id {
type Error = Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
match Uuid::parse_str(value) {
Ok(uuid) => Ok(Id(uuid)),
Err(e) => Err(Error::InvalidId(e.to_string())),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(try_from = "String")]
pub struct Line(String);
impl TryFrom<String> for Line {
type Error = String;
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
if s.contains('\n') {
Err(format!(
"Text nodes must not contain newlines, but found \"{}\"",
s
))
} else {
Ok(Line(s))
}
}
}
impl Line {
pub fn is_blank(&self) -> bool {
self.0.trim().is_empty()
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Node {
Text(Line),
List(Layout, Vec<Child>),
Styled(Styles, Box<Child>),
}
impl Node {
pub fn text(s: impl Into<String>) -> Self {
let s = s.into();
if s.contains('\n') {
let lines = s
.split('\n')
.map(|l| Child::Eager(Node::Text(Line(String::from(l)))))
.collect();
Node::List(Layout::Page, lines)
} else {
Node::Text(Line(s))
}
}
pub fn list(layout: Layout, children: Vec<impl Into<Child>>) -> Self {
Node::List(layout, children.into_iter().map(|c| c.into()).collect())
}
pub fn styled(styles: impl Into<Styles>, child: impl Into<Child>) -> Self {
let styles = styles.into();
match (styles.is_empty(), child.into()) {
(true, Child::Eager(node)) => node,
(_, child) => Node::Styled(styles, Box::new(child)),
}
}
pub fn split(self) -> (Node, Vec<Child>) {
match self {
Node::List(l, children) => (Node::List(l, vec![]), children.into_iter().collect()),
Node::Styled(s, child) => (
Node::styled(s, Node::List(Layout::Chain, vec![])),
vec![*child],
),
Node::Text(t) => (Node::Text(t), vec![]),
}
}
pub fn with(self, mut children: Vec<Child>) -> Result<Self> {
match self {
Node::Text(_) => {
if children.is_empty() {
Ok(self)
} else {
Err(Error::ChildrenMismatch(children))
}
}
Node::List(layout, _) => Ok(Node::List(layout, children)),
Node::Styled(styles, _) => {
if children.len() != 1 {
Err(Error::ChildrenMismatch(children))
} else {
Ok(Node::Styled(styles, Box::new(children.pop().unwrap())))
}
}
}
}
pub fn children(&self) -> Vec<&Child> {
match self {
Node::List(_, children) => children.iter().collect(),
Node::Styled(_, child) => vec![child],
_ => Vec::new(),
}
}
pub fn child(&self) -> Result<&Child> {
match self {
Node::List(_, children) if children.len() == 1 => Ok(children.iter().next().unwrap()),
Node::Styled(_, child) => Ok(child),
_ => Err(Error::ChildrenMismatch(
self.children().into_iter().cloned().collect(),
)),
}
}
pub fn is_atom(&self) -> bool {
match &self {
Node::Text(_) => true,
Node::List(_, _) => false,
Node::Styled(_, _) => false,
}
}
pub fn is_collection(&self) -> bool {
!self.is_atom()
}
pub fn str(&self) -> Result<&str> {
match self {
Node::Text(Line(t)) => Ok(t.as_str()),
_ => Err(Error::wrong_node_type("Text", self)),
}
}
pub fn layout(&self) -> Result<Layout> {
match self {
Node::List(layout, _) => Ok(*layout),
_ => Err(Error::wrong_node_type("List", self)),
}
}
pub fn styles(&self) -> Result<&Styles> {
match self {
Node::Styled(styles, _) => Ok(styles),
_ => Err(Error::wrong_node_type("Styled", self)),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Layout {
Chain,
Page,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Styles {
Block(BTreeSet<BlockStyle>),
Span(BTreeSet<SpanStyle>),
}
impl Styles {
fn is_empty(&self) -> bool {
match self {
Styles::Block(s) => s.is_empty(),
Styles::Span(s) => s.is_empty(),
}
}
}
impl From<BlockStyle> for Styles {
fn from(style: BlockStyle) -> Self {
let mut styles = BTreeSet::new();
styles.insert(style);
Self::Block(styles)
}
}
impl From<SpanStyle> for Styles {
fn from(style: SpanStyle) -> Self {
let mut styles = BTreeSet::new();
styles.insert(style);
Self::Span(styles)
}
}
impl From<BTreeSet<BlockStyle>> for Styles {
fn from(styles: BTreeSet<BlockStyle>) -> Self {
Self::Block(styles)
}
}
impl From<BTreeSet<SpanStyle>> for Styles {
fn from(styles: BTreeSet<SpanStyle>) -> Self {
Self::Span(styles)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum SpanStyle {
Bold,
Italic,
Struck,
Mono,
Marked,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum BlockStyle {
Heading,
List,
Quote,
Aside,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Child {
Lazy(Id),
Eager(Node),
}
impl Child {
pub fn id(&self) -> Result<Id> {
match self {
Self::Lazy(id) => Ok(*id),
_ => Err(Error::wrong_child_type("Lazy", self)),
}
}
pub fn node(&self) -> Result<&Node> {
match self {
Self::Eager(n) => Ok(n),
_ => Err(Error::wrong_child_type("Eager", self)),
}
}
}
impl From<Node> for Child {
fn from(n: Node) -> Self {
Self::Eager(n)
}
}
impl From<Id> for Child {
fn from(id: Id) -> Self {
Self::Lazy(id)
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Parent {
pub id: Id,
pub index: u32,
}
impl Parent {
pub fn new(id: Id, index: u32) -> Self {
Self { id, index }
}
}
pub type Parents = HashSet<Parent>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Overlap {
pub id: Id,
a: u8,
b: u8,
intersection: u8,
}
impl Overlap {
pub(crate) fn new(id: Id, source_count: u32, match_count: u32, intersection: u32) -> Self {
let max_count = max(source_count, match_count);
Overlap {
id,
a: (255 * source_count / max_count) as u8,
b: (255 * match_count / max_count) as u8,
intersection: (255 * intersection / max_count) as u8,
}
}
pub fn source_size(&self) -> f32 {
self.a as f32 / 255.0
}
pub fn match_size(&self) -> f32 {
self.b as f32 / 255.0
}
pub fn intersection_size(&self) -> f32 {
self.intersection as f32 / 255.0
}
pub fn score(&self) -> f32 {
self.intersection as f32 / (min(self.a, self.b) as f32)
}
pub(crate) fn reverse(&self, id: Id) -> Self {
Self {
id,
a: self.b,
b: self.a,
intersection: self.intersection,
}
}
}
impl PartialOrd for Overlap {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Overlap {
fn cmp(&self, other: &Self) -> Ordering {
self.intersection
.cmp(&other.intersection)
.reverse()
.then(self.a.cmp(&other.a).reverse())
.then(self.b.cmp(&other.b).reverse())
}
}