Skip to main content

substrate_data/forms/text/comments/
mod.rs

1//! Text with its comments kept as data — what a file becomes in the binary
2//! form, which has no comments: a comment above an object (or above the
3//! entry holding it) becomes its `comment`; one above any other entry
4//! becomes the map's `$<key>`; one above a list's item that isn't an
5//! object joins the list's nearest map's `comment`.
6
7use super::{Spans, SyntaxError, parse};
8use crate::model::value::{Map, Value};
9use crate::schema::{COMMENT_KEY, META_PREFIX};
10
11/// `text`'s value, its comments in it (see the module docs).
12///
13/// # Errors
14///
15/// [`SyntaxError`] when the text doesn't parse.
16pub fn with_comments(text: &str) -> Result<Value, SyntaxError> {
17    let doc = parse(text)?;
18    let mut value = doc.value;
19    let lead = comments(&text[..doc.spans.span.start as usize]);
20    let mut stray = Vec::new();
21    attach(&mut value, &doc.spans, text, &mut stray);
22    let mut notes: Vec<String> = lead.into_iter().collect();
23    notes.extend(stray);
24    if let (Value::Map(m), false) = (&mut value, notes.is_empty()) {
25        note(m, COMMENT_KEY, &notes.join("\n"));
26    }
27    Ok(value)
28}
29
30/// Moves the comments inside `v` (spanned by `s`) into it; the ones that
31/// have no object to go to are added to `stray`.
32fn attach(v: &mut Value, s: &Spans, text: &str, stray: &mut Vec<String>) {
33    let inner = s.span.start as usize + 1;
34    match v {
35        Value::Map(m) => {
36            let mut before = inner;
37            let mut entries: Vec<(String, Value)> = std::mem::take(m).into_iter().collect();
38            let mut out = Map::with_capacity(entries.len());
39            let mut own = Vec::new();
40            for ((key, child), cs) in entries.iter_mut().zip(&s.children) {
41                let start = cs.key.unwrap_or(cs.span).start as usize;
42                let said = comments(&text[before..start]);
43                before = cs.span.end as usize;
44                attach(child, cs, text, &mut own);
45                match (said, &mut *child) {
46                    (Some(c), Value::Map(cm)) => note(cm, COMMENT_KEY, &c),
47                    (Some(c), _) => {
48                        out.insert(format!("{META_PREFIX}{key}"), Value::Str(c));
49                    }
50                    (None, _) => {}
51                }
52                out.insert(key.clone(), std::mem::take(child));
53            }
54            own.extend(comments(
55                &text[before..(s.span.end as usize).saturating_sub(1).max(before)],
56            ));
57            *m = out;
58            if !own.is_empty() {
59                note(m, COMMENT_KEY, &own.join("\n"));
60            }
61        }
62        Value::List(items) => {
63            let mut before = inner;
64            for (item, cs) in items.iter_mut().zip(&s.children) {
65                let said = comments(&text[before..cs.span.start as usize]);
66                before = cs.span.end as usize;
67                attach(item, cs, text, stray);
68                match (said, &mut *item) {
69                    (Some(c), Value::Map(im)) => note(im, COMMENT_KEY, &c),
70                    (Some(c), _) => stray.push(c),
71                    (None, _) => {}
72                }
73            }
74        }
75        _ => {}
76    }
77}
78
79/// Adds `text` to `m`'s note `key` (first, before what it said already).
80fn note(m: &mut Map, key: &str, text: &str) {
81    let joined = match m.get(key).and_then(Value::as_str) {
82        Some(had) => format!("{text}\n{had}"),
83        None => text.to_string(),
84    };
85    let rest: Vec<(String, Value)> = std::mem::take(m)
86        .into_iter()
87        .filter(|(k, _)| k != key)
88        .collect();
89    m.insert(key, Value::Str(joined));
90    for (k, v) in rest {
91        m.insert(k, v);
92    }
93}
94
95/// The comments in `gap` (text between values), their markers and
96/// indentation gone, joined by lines; `None` if there are none.
97fn comments(gap: &str) -> Option<String> {
98    let mut lines = Vec::new();
99    let mut rest = gap;
100    while let Some(at) = rest.find('/') {
101        let after = &rest[at + 1..];
102        if let Some(line) = after.strip_prefix('/') {
103            let end = line.find('\n').unwrap_or(line.len());
104            lines.push(line[..end].trim().to_string());
105            rest = &line[end..];
106        } else if let Some(body) = after.strip_prefix('*') {
107            let end = body.find("*/").unwrap_or(body.len());
108            lines.extend(
109                body[..end]
110                    .lines()
111                    .map(|l| l.trim().trim_start_matches('*').trim().to_string())
112                    .filter(|l| !l.is_empty()),
113            );
114            rest = body.get(end + 2..).unwrap_or_default();
115        } else {
116            rest = after;
117        }
118    }
119    (!lines.is_empty()).then(|| lines.join("\n"))
120}
121
122#[cfg(test)]
123mod tests;