textlane_core/models/
smartypants.rs1use serde::Deserialize;
2use serde::de::{Deserializer, Error};
3
4#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
6#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7pub struct QuoteCharacters {
8 pub double: String,
9 pub single: String,
10}
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub enum Backticks {
15 Off,
16 #[default]
17 On,
18 All,
19}
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub enum Dashes {
24 Off,
25 #[default]
26 On,
27 Oldschool,
28 Inverted,
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
33pub enum Ellipses {
34 Off,
35 #[default]
36 On,
37 Spaced,
38 Unspaced,
39}
40
41#[derive(Deserialize)]
42#[serde(untagged)]
43enum Switch {
44 Flag(bool),
45 Name(String),
46}
47
48fn read_mode<'de, D: Deserializer<'de>>(
50 deserializer: D,
51 names: &[&str],
52) -> Result<Option<Result<bool, usize>>, D::Error> {
53 match Option::<Switch>::deserialize(deserializer)? {
54 None => Ok(None),
55 Some(Switch::Flag(flag)) => Ok(Some(Ok(flag))),
56 Some(Switch::Name(name)) => match names.iter().position(|known| *known == name) {
57 Some(index) => Ok(Some(Err(index))),
58 None => Err(D::Error::custom(format!(
59 "unknown variant `{name}`, expected a boolean or one of {names:?}"
60 ))),
61 },
62 }
63}
64
65pub fn read_switch<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
67 Ok(Option::<bool>::deserialize(deserializer)? != Some(false))
68}
69
70impl<'de> Deserialize<'de> for Backticks {
71 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
72 Ok(match read_mode(deserializer, &["all"])? {
73 Some(Ok(false)) => Backticks::Off,
74 Some(Err(_)) => Backticks::All,
75 _ => Backticks::On,
76 })
77 }
78}
79
80impl<'de> Deserialize<'de> for Dashes {
81 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
82 Ok(match read_mode(deserializer, &["oldschool", "inverted"])? {
83 Some(Ok(false)) => Dashes::Off,
84 Some(Err(0)) => Dashes::Oldschool,
85 Some(Err(_)) => Dashes::Inverted,
86 _ => Dashes::On,
87 })
88 }
89}
90
91impl<'de> Deserialize<'de> for Ellipses {
92 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
93 Ok(match read_mode(deserializer, &["spaced", "unspaced"])? {
94 Some(Ok(false)) => Ellipses::Off,
95 Some(Err(0)) => Ellipses::Spaced,
96 Some(Err(_)) => Ellipses::Unspaced,
97 _ => Ellipses::On,
98 })
99 }
100}
101
102#[cfg(feature = "schema")]
103fn write_mode(names: &[&str]) -> schemars::Schema {
104 schemars::json_schema!({ "anyOf": [{ "type": "boolean" }, { "enum": names }] })
105}
106
107#[cfg(feature = "schema")]
108impl schemars::JsonSchema for Backticks {
109 fn schema_name() -> std::borrow::Cow<'static, str> {
110 "Backticks".into()
111 }
112
113 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
114 write_mode(&["all"])
115 }
116}
117
118#[cfg(feature = "schema")]
119impl schemars::JsonSchema for Dashes {
120 fn schema_name() -> std::borrow::Cow<'static, str> {
121 "Dashes".into()
122 }
123
124 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
125 write_mode(&["oldschool", "inverted"])
126 }
127}
128
129#[cfg(feature = "schema")]
130impl schemars::JsonSchema for Ellipses {
131 fn schema_name() -> std::borrow::Cow<'static, str> {
132 "Ellipses".into()
133 }
134
135 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
136 write_mode(&["spaced", "unspaced"])
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct Typography {
143 pub quotes: bool,
144 pub ellipses: Ellipses,
145 pub backticks: Backticks,
146 pub dashes: Dashes,
147 pub opening_quotes: Option<QuoteCharacters>,
148 pub closing_quotes: Option<QuoteCharacters>,
149}