otter_base/
html.rs

1// Copyright 2020-2021 Ian Jackson and contributors to Otter
2// SPDX-License-Identifier: AGPL-3.0-or-later
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6
7#[derive(Clone,Serialize,Default,Deserialize,Hash,Eq,Ord,PartialEq,PartialOrd)]
8#[serde(transparent)]
9pub struct Html(String);
10
11impl Debug for HtmlStr {
12  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
13    const MAX: usize = 23;
14    if self.len() < MAX {
15      write!(f, "<{}>", &self.0)
16    } else {
17      let lim = (MAX-3 ..).into_iter()
18        .find(|&i| self.0.is_char_boundary(i)).unwrap();
19      write!(f, "<{}>...", &self.0[0..lim])
20    }
21  }
22}
23
24impl AsRef<HtmlStr> for Html {
25  fn as_ref(&self) -> &HtmlStr { HtmlStr::from_html_str(&self.0) }
26}
27impl AsRef<HtmlStr> for HtmlStr {
28  fn as_ref(&self) -> &HtmlStr { self }
29}
30impl AsRef<HtmlStr> for HtmlLit {
31  fn as_ref(&self) -> &HtmlStr { HtmlStr::from_html_str(self.0) }
32}
33impl Deref for Html {
34  type Target = HtmlStr;
35  fn deref(&self) -> &HtmlStr { HtmlStr::from_html_str(&self.0) }
36}
37impl Deref for HtmlLit {
38  type Target = HtmlStr;
39  fn deref(&self) -> &HtmlStr { HtmlStr::from_html_str(self.0) }
40}
41
42impl Debug for Html {
43  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
44    Debug::fmt(self.as_ref(), f)
45  }
46}
47
48#[derive(Hash,Eq,Ord,PartialEq,PartialOrd)]
49#[repr(transparent)]
50pub struct HtmlStr(str);
51
52#[derive(Hash,Eq,Ord,PartialEq,PartialOrd)]
53#[derive(Serialize,Deserialize)]
54#[serde(transparent)]
55pub struct HtmlLit(&'static str);
56
57impl Default for &'static HtmlStr {
58  fn default() -> Self { HtmlStr::from_html_str("") }
59}
60
61impl From<HtmlLit> for &'static HtmlStr {
62  fn from(l: HtmlLit) -> &'static HtmlStr { HtmlStr::from_html_str(l.0) }
63}
64
65pub trait HtmlFormat<'e> {
66  type Encoded: Display;
67  fn html_format<'f: 'e>(&'f self) -> Self::Encoded;
68}
69
70impl From<HtmlLit> for Html {
71  fn from(l: HtmlLit) -> Html { Html(l.0.into()) }
72}
73impl From<&HtmlStr> for Html {
74  fn from(l: &HtmlStr) -> Html { Html(l.0.into()) }
75}
76
77impl<'e, T> HtmlFormat<'e> for &'e T where T: HtmlFormat<'e> + ?Sized {
78  type Encoded = T::Encoded;
79  fn html_format<'f: 'e>(&'f self) -> Self::Encoded {
80    <T as HtmlFormat>::html_format(self)
81  }
82}
83
84#[derive(Debug,Copy,Clone,Ord,PartialOrd,Eq,PartialEq,Hash)]
85pub struct IsHtmlFormatted<T:Display>(pub T);
86impl<'e, T:Display+'e> HtmlFormat<'e> for IsHtmlFormatted<T> {
87  type Encoded = &'e T;
88  fn html_format<'f: 'e>(&'f self) -> Self::Encoded { &self.0 }
89}
90
91impl Html {
92  pub fn new() -> Self { default() }
93  pub fn from_txt(s: &str) -> Self {
94    Html(htmlescape::encode_minimal(s))
95  }
96
97  pub fn from_html_string(s: String) -> Self { Html(s) }
98  pub fn as_html_string_mut(&mut self) -> &mut String { &mut self.0 }
99  pub fn into_html_string(self) -> String { self.0 }
100
101  pub const fn lit(s: &'static str) -> HtmlLit { HtmlLit(s) }
102}
103
104impl HtmlStr {
105  pub fn from_html_str<'s>(s: &'s str) -> &'s Self {
106    let s = unsafe { mem::transmute::<&'s str, &'s HtmlStr>(s) };
107    s
108  }
109  pub fn len(&self) -> usize { self.0.len() }
110  pub fn as_html_str(&self) -> &str { &self.0 }
111}
112
113#[ext(pub, name=HtmlIteratorExt, supertraits=Iterator)]
114impl<T:Iterator> T {
115  fn hjoin<'i,'j, I,J>(self, j: &'j J) -> Html
116  where
117    Self: Iterator<Item=&'i I>,
118    I: AsRef<HtmlStr> + 'i,
119    J: AsRef<HtmlStr>,
120  {
121    let j: &HtmlStr = j.as_ref();
122    Html::from_html_string(
123      izip!(
124        iter::once("").chain(iter::repeat(j.as_html_str())),
125        self.map(|h| h.as_ref().as_html_str()),
126      )
127        .map(|(a,b)| iter::once(a).chain(iter::once(b)))
128        .flatten()
129        .collect::<String>()
130    )
131  }
132}
133  
134impl Borrow<HtmlStr> for Html {
135  fn borrow<'b>(&'b self) -> &'b HtmlStr {
136    HtmlStr::from_html_str(&self.0)
137  }
138}
139impl Borrow<HtmlStr> for HtmlLit {
140  fn borrow<'b>(&'b self) -> &'static HtmlStr {
141    HtmlStr::from_html_str(self.0)
142  }
143}
144
145impl ToOwned for HtmlStr {
146  type Owned = Html;
147  fn to_owned(&self) -> Html { Html(self.0.to_owned()) }
148}
149
150#[ext(pub, name=HtmlFormatRef)]
151impl<'e, T: HtmlFormat<'e>> T {
152  fn to_html(&'e self) -> Html { hformat!("{}", *self) }
153}
154
155impl<'e> HtmlFormat<'e> for Html {
156  type Encoded = &'e str;
157  fn html_format<'f: 'e>(&'f self) -> Self::Encoded { &self.0 }
158}
159impl<'e> HtmlFormat<'e> for HtmlStr {
160  type Encoded = &'e str;
161  fn html_format<'f: 'e>(&'f self) -> Self::Encoded { &self.0 }
162}
163impl<'e> HtmlFormat<'e> for HtmlLit {
164  type Encoded = &'static str;
165  fn html_format<'f: 'e>(&'f self) -> Self::Encoded { self.0 }
166}
167impl<'e> HtmlFormat<'e> for str {
168  type Encoded = String;
169  fn html_format<'f: 'e>(&'f self) -> Self::Encoded {
170    htmlescape::encode_minimal(self)
171  }
172}
173impl<'e> HtmlFormat<'e> for String {
174  type Encoded = String;
175  fn html_format<'f: 'e>(&'f self) -> Self::Encoded {
176    htmlescape::encode_minimal(self)
177  }
178}
179
180hformat_as_display!{ usize u8 u16 u32 u64
181                     isize i8 i16 i32 i64
182                     f32 f64 }
183
184#[macro_export]
185macro_rules! hformat_as_display {
186  ( $( $t:ty )* ) => {
187    $(
188      impl<'e> $crate::html::HtmlFormat<'e> for $t {
189        type Encoded = &'e $t;
190        fn html_format<'f: 'e>(&'f self) -> Self::Encoded { self }
191      }
192    )*
193  }
194}
195
196#[macro_export]
197macro_rules! hformat {
198  ( $f:tt $(,$( $v:expr )?)* ) => {
199    Html::from_html_string(
200      format!(
201        $f  $(,$(
202          $crate::html::HtmlFormat::html_format(&$v)
203        )?)*
204      )
205    )
206  }
207}
208
209#[macro_export]
210macro_rules! hwrite {
211  ( $o:expr, $f:tt $(,$( $v:expr )?)* ) => {
212    write!(
213      $crate::html::Html::as_html_string_mut($o),
214      $f $(,$(
215        $crate::html::HtmlFormat::html_format(&$v)
216      )?)*
217    )
218  }
219}