molframe_cif/write/
value.rs1use crate::document::CifValue;
4use std::fmt::{self, Display, Formatter, Write};
5
6#[must_use]
8pub fn render_value(value: &CifValue) -> String {
9 match value {
10 CifValue::Inapplicable => ".".to_owned(),
11 CifValue::Unknown => "?".to_owned(),
12 CifValue::Integer(value) => value.to_string(),
13 CifValue::Float(value) => value.to_string(),
14 CifValue::Text(value) => quote_text(value),
15 }
16}
17
18#[must_use]
20pub fn quote_text(text: &str) -> String {
21 let mut rendered = String::new();
22 write_quoted(&mut rendered, text);
23 rendered
24}
25
26pub fn write_quoted(output: &mut impl Write, text: &str) {
32 let _ = write_quoted_to(output, text);
33}
34
35pub(crate) struct Quoted<'a>(&'a str);
36
37pub(crate) const fn quoted(text: &str) -> Quoted<'_> {
38 Quoted(text)
39}
40
41impl Display for Quoted<'_> {
42 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
43 write_quoted_to(formatter, self.0)
44 }
45}
46
47fn write_quoted_to(output: &mut impl Write, text: &str) -> fmt::Result {
48 if text.contains('\n') || (text.contains('\'') && text.contains('"')) {
49 output.write_str("\n;")?;
50 output.write_str(text)?;
51 return output.write_str("\n;");
52 }
53 if !needs_quoting(text) {
54 return output.write_str(text);
55 }
56 if text.contains('\'') {
57 output.write_char('"')?;
58 output.write_str(text)?;
59 output.write_char('"')
60 } else {
61 output.write_char('\'')?;
62 output.write_str(text)?;
63 output.write_char('\'')
64 }
65}
66
67pub(crate) fn needs_quoting(text: &str) -> bool {
68 text.is_empty()
69 || text.chars().any(char::is_whitespace)
70 || matches!(text, "." | "?")
71 || text.starts_with(['_', '#', '\'', '"', '[', ']', '$', ';'])
72 || ["loop_", "stop_", "global_"]
73 .iter()
74 .any(|keyword| text.eq_ignore_ascii_case(keyword))
75 || text.get(..5).is_some_and(|prefix| {
76 prefix.eq_ignore_ascii_case("data_") || prefix.eq_ignore_ascii_case("save_")
77 })
78}
79
80#[cfg(test)]
81#[path = "value_tests.rs"]
82mod tests;