#![deny(missing_docs)]
use chrono::prelude::*;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::iter::repeat;
use std::str::FromStr;
use std::string::ToString;
pub mod api;
mod raw;
mod error;
pub use error::ErrorKind;
use error::Result;
pub mod prelude {
pub use super::ChartColor;
pub use super::ChartElement;
pub use super::ChartLane;
pub use super::ChartShape;
pub use super::ElementData;
pub use super::Napchart;
pub use super::RemoteNapchart;
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ChartShape {
Circle,
Wide,
Line,
}
impl Default for ChartShape {
fn default() -> Self {
Self::Circle
}
}
impl ToString for ChartShape {
fn to_string(&self) -> String {
match self {
ChartShape::Circle => String::from("circle"),
ChartShape::Wide => String::from("wide"),
ChartShape::Line => String::from("line"),
}
}
}
impl FromStr for ChartShape {
type Err = ErrorKind;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"circle" => Self::Circle,
"wide" => Self::Wide,
"line" => Self::Line,
_ => return Err(ErrorKind::InvalidChartShape(s.to_string())),
})
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ChartColor {
Red,
Blue,
Brown,
Green,
Gray,
Yellow,
Purple,
Pink,
}
impl Default for ChartColor {
fn default() -> Self {
Self::Red
}
}
impl ToString for ChartColor {
fn to_string(&self) -> String {
match self {
ChartColor::Red => String::from("red"),
ChartColor::Blue => String::from("blue"),
ChartColor::Brown => String::from("brown"),
ChartColor::Green => String::from("green"),
ChartColor::Gray => String::from("gray"),
ChartColor::Yellow => String::from("yellow"),
ChartColor::Purple => String::from("purple"),
ChartColor::Pink => String::from("pink"),
}
}
}
impl FromStr for ChartColor {
type Err = ErrorKind;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"red" => ChartColor::Red,
"blue" => ChartColor::Blue,
"brown" => ChartColor::Brown,
"green" => ChartColor::Green,
"gray" => ChartColor::Gray,
"yellow" => ChartColor::Yellow,
"purple" => ChartColor::Purple,
"pink" => ChartColor::Pink,
_ => return Err(ErrorKind::InvalidChartColor(s.to_string())),
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Napchart {
pub shape: ChartShape,
lanes: Vec<ChartLane>,
pub color_tags: HashMap<ChartColor, String>,
}
impl Napchart {
pub fn add_lane(&mut self) -> &mut ChartLane {
self.lanes.push(ChartLane::default());
self.lanes.last_mut().unwrap()
}
pub fn get_lane(&self, i: usize) -> Option<&ChartLane> {
self.lanes.get(i)
}
pub fn get_lane_mut(&mut self, i: usize) -> Option<&mut ChartLane> {
self.lanes.get_mut(i)
}
pub fn remove_lane(&mut self, i: usize) -> Option<ChartLane> {
if i < self.lanes.len() {
Some(self.lanes.remove(i))
} else {
None
}
}
pub fn lanes_len(&self) -> usize {
self.lanes.len()
}
pub fn upload(&self) -> api::UploadBuilder {
api::UploadBuilder::new(self)
}
}
impl Napchart {
pub fn shape(self, shape: ChartShape) -> Self {
Self { shape, ..self }
}
pub fn lanes(self, count: usize) -> Self {
Self {
lanes: repeat(ChartLane::default()).take(count).collect(),
..self
}
}
}
#[derive(Debug)]
pub struct RemoteNapchart {
pub chartid: String,
pub title: Option<String>,
pub description: Option<String>,
pub username: Option<String>,
pub last_updated: DateTime<Utc>,
pub is_snapshot: bool,
pub public_link: Option<String>,
pub chart: Napchart,
}
impl RemoteNapchart {
pub fn semantic_eq(&self, other: &Self) -> bool {
self.title == other.title
&& self.description == other.description
&& self.username == other.username
&& self.is_snapshot == other.is_snapshot
&& self.chart == other.chart
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChartLane {
pub locked: bool,
elements: Vec<ChartElement>,
}
impl ChartLane {
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn elems_len(&self) -> usize {
self.elements.len()
}
pub fn add_element(&mut self, start: u16, end: u16) -> Result<&mut ChartElement> {
assert!(start <= 1440);
assert!(end <= 1440);
let mut elems: Vec<(u16, u16, usize)> = Vec::new();
for (i, e) in self.elements.iter().enumerate() {
if e.start < e.end {
elems.push((e.start, e.end, i));
} else {
elems.push((e.start, 1440, i));
elems.push((0, e.end, i));
}
}
for e in elems.into_iter() {
if (start >= e.0 && start < e.1) || (end > e.0 && end <= e.1) {
let e = &self.elements[e.2];
return Err(ErrorKind::ElementOverlap((start, end), (e.start, e.end)));
}
}
self.elements.push(ChartElement {
start,
end,
..Default::default()
});
Ok(self.elements.last_mut().unwrap())
}
pub fn elems_iter(&self) -> std::slice::Iter<ChartElement> {
self.elements.iter()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChartElement {
start: u16,
end: u16,
pub data: ElementData,
}
impl ChartElement {
pub fn get_position(&self) -> (u16, u16) {
(self.start, self.end)
}
pub fn text<T: ToString>(&mut self, text: T) -> &mut Self {
self.data.text = Some(text.to_string());
self
}
pub fn color(&mut self, color: ChartColor) -> &mut Self {
self.data.color = color;
self
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ElementData {
pub text: Option<String>,
pub color: ChartColor,
}
impl TryFrom<Napchart> for raw::ChartSchema {
type Error = ErrorKind;
fn try_from(chart: Napchart) -> Result<raw::ChartSchema> {
Ok(raw::ChartSchema {
lanes: chart.lanes.len(),
shape: chart.shape.to_string(),
lanesConfig: chart
.lanes
.iter()
.map(|l| raw::LaneConfig { locked: l.locked })
.enumerate()
.collect(),
elements: chart
.lanes
.into_iter()
.enumerate()
.flat_map(|(i, l)| l.elements.into_iter().zip(repeat(i)))
.map(|(e, i)| raw::ChartElement {
start: e.start,
end: e.end,
lane: i,
text: e.data.text,
color: e.data.color.to_string(),
})
.collect(),
colorTags: chart
.color_tags
.into_iter()
.map(|(color, tag)| raw::ColorTag {
tag,
color: color.to_string(),
})
.collect(),
})
}
}
impl TryFrom<raw::ChartCreationReturn> for RemoteNapchart {
type Error = ErrorKind;
fn try_from(raw: raw::ChartCreationReturn) -> Result<RemoteNapchart> {
Ok(RemoteNapchart {
chartid: raw.chartDocument.chartid,
title: raw
.chartDocument
.title
.and_then(|t| if t.is_empty() { None } else { Some(t) }),
description: raw.chartDocument.description.and_then(|d| {
if d.is_empty() {
None
} else {
Some(d)
}
}),
username: if &raw.chartDocument.username == "anonymous" {
None
} else {
Some(raw.chartDocument.username)
},
last_updated: raw.chartDocument.lastUpdated.parse()?,
is_snapshot: raw.chartDocument.isSnapshot,
public_link: if raw.publicLink.is_empty() {
None
} else {
Some(raw.publicLink)
},
chart: Napchart {
shape: raw.chartDocument.chartData.shape.parse()?,
lanes: {
let mut vec = Vec::with_capacity(raw.chartDocument.chartData.lanes);
for i in 0..raw.chartDocument.chartData.lanes {
vec.push(ChartLane {
locked: raw
.chartDocument
.chartData
.lanesConfig
.get(&i)
.map(|c| c.locked)
.unwrap_or(false),
elements: vec![],
});
}
for e in raw.chartDocument.chartData.elements.into_iter() {
let lane = &mut vec.get_mut(e.lane).map(|l| &mut l.elements).ok_or(
ErrorKind::InvalidLane(e.lane, raw.chartDocument.chartData.lanes),
)?;
lane.push(ChartElement {
start: e.start,
end: e.end,
data: ElementData {
text: e.text.and_then(
|d| {
if d.is_empty() {
None
} else {
Some(d)
}
},
),
color: e.color.parse()?,
},
});
}
vec
},
color_tags: raw
.chartDocument
.chartData
.colorTags
.into_iter()
.filter_map(|tag| tag.color.parse().ok().map(|color| (color, tag.tag.clone())))
.collect(),
},
})
}
}