1use super::provenance::Provenance;
8use std::fmt::Write;
9
10impl Provenance {
11 #[must_use]
16 pub fn to_json(&self) -> String {
17 let fields = fields(self);
18 let mut output = String::from("{");
19 for (position, (key, value)) in fields.iter().enumerate() {
20 if position != 0 {
21 output.push(',');
22 }
23 json_string(&mut output, key);
24 output.push(':');
25 json_string(&mut output, value);
26 }
27 output.push('}');
28 output
29 }
30
31 #[must_use]
34 pub fn entries(&self) -> Vec<(String, String)> {
35 fields(self)
36 }
37
38 #[must_use]
43 pub fn to_mmcif(&self) -> String {
44 let fields = fields(self);
45 let mut output = String::from(
46 "data_molframe_provenance\n#\nloop_\n_molframe_provenance.key\n_molframe_provenance.value\n",
47 );
48 for (key, value) in fields {
49 cif_value(&mut output, &key);
50 output.push(' ');
51 cif_value(&mut output, &value);
52 output.push('\n');
53 }
54 output.push_str("#\n");
55 output
56 }
57}
58
59fn fields(record: &Provenance) -> Vec<(String, String)> {
60 let policy = &record.policy;
61 let mut result = vec![
62 ("molframe_version", record.molframe_version.to_owned()),
63 ("input_source", record.input_source.to_string()),
64 ("input_sha256", optional_display(record.input_digest)),
65 ("policy_fingerprint", record.policy_fingerprint.to_string()),
66 ("profile", optional_profile(record)),
67 (
68 "schema_version",
69 optional_version(record.schema_version.as_ref()),
70 ),
71 (
72 "component_version",
73 optional_version(record.component_version.as_ref()),
74 ),
75 ("timestamp", optional_text(record.timestamp.as_deref())),
76 ("policy.assembly", format!("{:?}", policy.assembly)),
77 ("policy.model", format!("{:?}", policy.model)),
78 ("policy.altloc", format!("{:?}", policy.altloc)),
79 ("policy.identifiers", format!("{:?}", policy.identifiers)),
80 (
81 "policy.missing_atoms",
82 format!("{:?}", policy.missing_atoms),
83 ),
84 ("policy.hydrogens", format!("{:?}", policy.hydrogens)),
85 (
86 "policy.atom_equivalence",
87 format!("{:?}", policy.atom_equivalence),
88 ),
89 ("policy.symmetry", format!("{:?}", policy.symmetry)),
90 ("policy.alignment", format!("{:?}", policy.alignment)),
91 ("policy.precision", format!("{:?}", policy.precision)),
92 ("policy.periodic", format!("{:?}", policy.periodic)),
93 ("policy.vdw_radii", format!("{:?}", policy.vdw_radii)),
94 ("policy.contact_def", format!("{:?}", policy.contact_def)),
95 (
96 "policy.float_tolerance.relative",
97 policy.float_tolerance.relative.to_string(),
98 ),
99 (
100 "policy.float_tolerance.absolute",
101 policy.float_tolerance.absolute.to_string(),
102 ),
103 ]
104 .into_iter()
105 .map(|(key, value)| (key.to_owned(), value))
106 .collect::<Vec<_>>();
107 if let Some(algorithm) = &record.algorithm {
108 result.push(("algorithm.name".to_owned(), algorithm.name().to_owned()));
109 result.push((
110 "algorithm.version".to_owned(),
111 algorithm.version().to_owned(),
112 ));
113 }
114 result.extend(
115 record
116 .parameters
117 .iter()
118 .map(|(name, value)| (format!("parameter.{name}"), value.serialised())),
119 );
120 result
121}
122
123fn optional_display(value: Option<impl std::fmt::Display>) -> String {
124 match value {
125 Some(value) => value.to_string(),
126 None => ".".to_owned(),
127 }
128}
129
130fn optional_profile(record: &Provenance) -> String {
131 match record.profile {
132 Some(profile) => profile.as_str().to_owned(),
133 None => ".".to_owned(),
134 }
135}
136
137fn optional_version(value: Option<&super::provenance::DictionaryVersion>) -> String {
138 match value {
139 Some(value) => value.as_str().to_owned(),
140 None => ".".to_owned(),
141 }
142}
143
144fn optional_text(value: Option<&str>) -> String {
145 match value {
146 Some(value) => value.to_owned(),
147 None => ".".to_owned(),
148 }
149}
150
151pub fn push_json_string(output: &mut String, value: &str) {
153 json_string(output, value);
154}
155
156fn json_string(output: &mut String, value: &str) {
157 output.push('"');
158 for character in value.chars() {
159 match character {
160 '"' => output.push_str("\\\""),
161 '\\' => output.push_str("\\\\"),
162 '\n' => output.push_str("\\n"),
163 '\r' => output.push_str("\\r"),
164 '\t' => output.push_str("\\t"),
165 control if control.is_control() => {
166 let _ = write!(output, "\\u{:04x}", u32::from(control));
167 }
168 ordinary => output.push(ordinary),
169 }
170 }
171 output.push('"');
172}
173
174fn cif_value(output: &mut String, value: &str) {
175 if bare_cif(value) {
176 output.push_str(value);
177 } else if !value.contains('\'') && !value.contains('\n') {
178 output.push('\'');
179 output.push_str(value);
180 output.push('\'');
181 } else if !value.contains('"') && !value.contains('\n') {
182 output.push('"');
183 output.push_str(value);
184 output.push('"');
185 } else {
186 output.push_str("\n;");
187 output.push_str(value);
188 output.push_str("\n;");
189 }
190}
191
192fn bare_cif(value: &str) -> bool {
193 !value.is_empty()
194 && value != "."
195 && value != "?"
196 && !value.chars().any(char::is_whitespace)
197 && !value.starts_with(['#', '$', ';', '_', '\'', '"'])
198 && !value.eq_ignore_ascii_case("loop_")
199 && !value.to_ascii_lowercase().starts_with("data_")
200 && !value.to_ascii_lowercase().starts_with("save_")
201}
202
203#[cfg(test)]
204#[path = "serialise_tests.rs"]
205mod tests;