use serde::Serialize;
use crate::graph::inlines::Inline;
#[derive(Clone, Copy, Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum NodeTypes {
Block,
Inline,
String,
}
#[derive(Serialize, Clone, Debug)]
pub struct Header {
pub title: Vec<Inline>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authors: Option<Vec<Author>>,
pub location: Vec<Location>,
}
impl Default for Header {
fn default() -> Self {
Self::new()
}
}
impl Header {
pub fn new() -> Self {
Header {
title: vec![],
authors: None,
location: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.title.is_empty() && self.authors.is_none()
}
pub fn consolidate(&mut self) {
if let Some(last_inline) = self.title.last_mut() {
self.location = Location::reconcile(self.location.clone(), last_inline.locations())
}
}
pub fn title(&self) -> Vec<Inline> {
self.title.clone()
}
pub fn document_id(&self) -> String {
let mut id = String::new();
for inline in self.title() {
id.push_str(&inline.extract_values_to_string());
}
id = id
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
id.to_lowercase().to_string()
}
}
#[derive(Serialize, Clone, Debug)]
pub struct Author {
fullname: String,
initials: String,
firstname: String,
middlename: String,
lastname: String,
address: String,
}
#[derive(Serialize, Clone, PartialEq, Eq, Debug)]
pub struct Location {
pub line: usize, pub col: usize, #[serde(skip_serializing_if = "Vec::is_empty")]
pub file: Vec<String>,
}
impl Default for Location {
fn default() -> Self {
Location {
line: 1,
col: 1,
file: vec![],
}
}
}
impl Ord for Location {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.line == other.line {
self.col.cmp(&other.col)
} else {
self.line.cmp(&other.line)
}
}
}
impl PartialOrd for Location {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Location {
pub fn new(line: usize, col: usize, file: Vec<String>) -> Self {
Location { line, col, file }
}
pub fn col(&self) -> usize {
self.col
}
pub fn reconcile(mut start: Vec<Location>, other: Vec<Location>) -> Vec<Location> {
if !other.is_empty() {
start.extend(other);
start.sort();
if start.len() > 1 {
start.drain(1..start.len() - 1);
}
}
start
}
pub fn destructure_inline_locations(locations: Vec<Location>) -> (usize, usize, usize) {
let mut line: usize = 0;
let mut startcol: usize = 0;
let mut endcol: usize = 0;
for (idx, location) in locations.iter().enumerate() {
if idx == 0 {
line = location.line;
startcol = location.col;
}
endcol = location.col
}
(line, startcol, endcol)
}
}
#[cfg(test)]
mod tests {
use crate::graph::inlines::{Inline, InlineLiteral, InlineLiteralName};
use super::*;
#[test]
fn reconcile_locations() {
let start = vec![Location::new(1, 1, vec![]), Location::new(2, 4, vec![])];
let other = vec![Location::new(1, 1, vec![]), Location::new(4, 5, vec![])];
assert_eq!(
vec![Location::new(1, 1, vec![]), Location::new(4, 5, vec![])],
Location::reconcile(start, other)
)
}
#[test]
fn header_to_document_id() {
let mut header = Header::new();
header.title.push(Inline::InlineLiteral(InlineLiteral::new(
InlineLiteralName::Text,
String::from("Foo With Space"),
vec![],
)));
assert_eq!(header.document_id(), "foo_with_space");
}
#[test]
fn header_to_empty_document_id() {
let header = Header::new();
assert_eq!(header.document_id(), "");
}
}