Skip to main content

worktree_io/config/
ser.rs

1use std::fmt::Write as _;
2
3use super::Config;
4
5/// Wrap a string value in TOML basic-string quotes, escaping special characters.
6///
7/// When the value contains newlines, a TOML multiline basic string (`"""..."""`) is
8/// used so that hook scripts remain human-readable in the config file.
9fn toml_quoted(s: &str) -> String {
10    if s.contains('\n') {
11        let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
12        return format!("\"\"\"\n{escaped}\"\"\"");
13    }
14    let escaped = s
15        .replace('\\', "\\\\")
16        .replace('"', "\\\"")
17        .replace('\r', "\\r")
18        .replace('\t', "\\t");
19    format!("\"{escaped}\"")
20}
21
22impl Config {
23    /// Serialize the config to a TOML string with inline documentation comments.
24    ///
25    /// Each section header and field is preceded by a `#` comment that matches
26    /// the doc-comment on the corresponding struct field.  The resulting string
27    /// round-trips cleanly through [`toml::from_str`].
28    #[must_use]
29    pub fn to_toml_with_comments(&self) -> String {
30        let mut out = String::new();
31
32        // Website header comment
33        out.push_str("# runner \u{2014} https://worktree.io\n\n");
34
35        // [editor] ------------------------------------------------------------
36        out.push_str("# Editor configuration.\n");
37        out.push_str("[editor]\n");
38        if let Some(cmd) = &self.editor.command {
39            out.push_str("# Command to launch the editor, e.g. \"code .\" or \"nvim .\"\n");
40            writeln!(out, "command = {}", toml_quoted(cmd)).unwrap();
41        }
42        out.push('\n');
43
44        // [open] --------------------------------------------------------------
45        out.push_str("# Workspace open behavior.\n");
46        out.push_str("[open]\n");
47        out.push_str("# Whether to launch the configured editor when opening a workspace.\n");
48        writeln!(out, "editor = {}", self.open.editor).unwrap();
49        out.push('\n');
50
51        // [hooks] -------------------------------------------------------------
52        out.push_str("# Hook scripts run around the open command.\n");
53        out.push_str("[hooks]\n");
54        if let Some(pre) = &self.hooks.pre_open {
55            out.push_str("# Script run before opening the workspace.\n");
56            writeln!(out, "\"pre:open\" = {}", toml_quoted(pre)).unwrap();
57        }
58        if let Some(post) = &self.hooks.post_open {
59            out.push_str("# Script run after opening the workspace.\n");
60            writeln!(out, "\"post:open\" = {}", toml_quoted(post)).unwrap();
61        }
62
63        out
64    }
65}
66
67#[cfg(test)]
68#[path = "ser_tests.rs"]
69mod ser_tests;