use crate::output::path;
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Comment {
elements: Vec<Element>,
}
impl Comment {
pub fn new() -> Self {
Self::default()
}
pub fn plain<S: ToString>(mut self, text: S) -> Self {
self.push(Element::Span(text.to_string().into()));
self
}
pub fn link<S: ToString>(mut self, text: S, path: path::PathBuf) -> Self {
self.push(Element::Span(Span {
text: text.to_string(),
link: Some(Link::Path(path)),
}));
self
}
pub fn url<S: ToString, U: ToString>(mut self, text: S, url: U) -> Self {
self.push(Element::Span(Span {
text: text.to_string(),
link: Some(Link::Url(url.to_string())),
}));
self
}
pub fn nl(mut self) -> Self {
self.push(Element::NewLine);
self
}
pub fn lo(mut self) -> Self {
self.push(Element::ListOpen);
self
}
pub fn li(mut self) -> Self {
self.push(Element::ListNext);
self
}
pub fn lc(mut self) -> Self {
self.push(Element::ListClose);
self
}
pub fn push(&mut self, element: Element) {
match self.elements.pop() {
None => self.elements.push(element),
Some(Element::Span(s1)) => {
if let Element::Span(s2) = element {
let (s1, maybe_s2) = merge_spans(s1, s2);
self.elements.push(Element::Span(s1));
if let Some(s2) = maybe_s2 {
self.elements.push(Element::Span(s2));
}
} else {
self.elements.push(Element::Span(s1));
self.elements.push(element);
}
}
Some(Element::NewLine) => {
if matches!(element, Element::Span(_)) {
self.elements.push(Element::NewLine);
}
self.elements.push(element);
}
Some(Element::ListOpen) => {
self.elements.push(Element::ListOpen);
if !matches!(element, Element::ListNext) {
self.elements.push(element);
}
}
Some(Element::ListNext) => {
self.elements.push(Element::ListNext);
if !matches!(element, Element::ListNext) {
self.elements.push(element);
}
}
Some(Element::ListClose) => {
self.elements.push(Element::ListClose);
if !matches!(element, Element::NewLine) {
self.elements.push(element);
}
}
}
}
pub fn extend(&mut self, other: Comment) {
let mut it = other.elements.into_iter();
if let Some(element) = it.next() {
self.push(element);
}
self.elements.extend(it);
}
pub fn elements(&self) -> &[Element] {
&self.elements
}
}
impl std::fmt::Display for Comment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut indent = 0;
for element in self.elements.iter() {
match element {
Element::Span(span) => span.fmt(f),
Element::NewLine => write!(f, "\n\n{: >1$}", "", indent),
Element::ListOpen => {
indent += 3;
write!(f, "\n\n{: >1$}", "- ", indent)
}
Element::ListNext => {
write!(f, "\n\n{: >1$}", "- ", indent)
}
Element::ListClose => {
indent -= 3;
write!(f, "\n\n{: >1$}", "", indent)
}
}?;
}
Ok(())
}
}
impl From<String> for Comment {
fn from(text: String) -> Self {
Self {
elements: vec![Element::Span(text.into())],
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Element {
Span(Span),
NewLine,
ListOpen,
ListNext,
ListClose,
}
#[derive(Clone, Debug, PartialEq, Default, Eq)]
pub struct Brief {
spans: Vec<Span>,
}
impl Brief {
pub fn new() -> Self {
Self::default()
}
pub fn plain<S: ToString>(mut self, text: S) -> Self {
self.push(text.to_string().into());
self
}
pub fn link<S: ToString>(mut self, text: S, path: path::PathBuf) -> Self {
self.push(Span {
text: text.to_string(),
link: Some(Link::Path(path)),
});
self
}
pub fn url<S: ToString, U: ToString>(mut self, text: S, url: U) -> Self {
self.push(Span {
text: text.to_string(),
link: Some(Link::Url(url.to_string())),
});
self
}
pub fn push(&mut self, span: Span) {
if let Some(s1) = self.spans.pop() {
let s2 = span;
let (s1, maybe_s2) = merge_spans(s1, s2);
self.spans.push(s1);
if let Some(s2) = maybe_s2 {
self.spans.push(s2);
}
} else {
self.spans.push(span);
}
}
pub fn extend(&mut self, other: Brief) {
let mut it = other.spans.into_iter();
if let Some(element) = it.next() {
self.push(element);
}
self.spans.extend(it);
}
pub fn spans(&self) -> &[Span] {
&self.spans
}
}
impl std::fmt::Display for Brief {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for span in self.spans.iter() {
span.fmt(f)?;
}
Ok(())
}
}
impl From<String> for Brief {
fn from(text: String) -> Self {
Self {
spans: vec![text.into()],
}
}
}
impl From<Brief> for Comment {
fn from(brief: Brief) -> Self {
Self {
elements: brief.spans.into_iter().map(Element::Span).collect(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Span {
pub text: String,
pub link: Option<Link>,
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.text)
}
}
impl From<String> for Span {
fn from(text: String) -> Self {
Span { text, link: None }
}
}
fn merge_spans(mut a: Span, b: Span) -> (Span, Option<Span>) {
if b.text.is_empty() {
return (a, None);
}
if !a.text.ends_with(' ') && !b.text.starts_with(' ') {
a.text.push(' ');
}
if a.link == b.link {
a.text += &b.text;
return (a, None);
}
(a, Some(b))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Link {
Path(path::PathBuf),
Url(String),
}