Skip to main content

graphql_tools/parser/
format.rs

1//! Formatting graphql
2use std::default::Default;
3
4use super::common::Directive;
5
6#[derive(Debug, PartialEq)]
7pub(crate) struct Formatter<'a> {
8    buf: String,
9    style: &'a Style,
10    indent: u32,
11}
12
13/// A configuration of formatting style
14///
15/// Currently we only have indentation configured, other things might be
16/// added later (such as minification).
17#[derive(Debug, PartialEq, Clone)]
18pub struct Style {
19    indent: u32,
20    multiline_arguments: bool,
21}
22
23impl Default for Style {
24    fn default() -> Style {
25        Style {
26            indent: 2,
27            multiline_arguments: false,
28        }
29    }
30}
31
32impl Style {
33    /// Change the number of spaces used for indentation
34    pub fn indent(&mut self, indent: u32) -> &mut Self {
35        self.indent = indent;
36        self
37    }
38
39    /// Set whether to add new lines between arguments
40    pub fn multiline_arguments(&mut self, multiline_arguments: bool) -> &mut Self {
41        self.multiline_arguments = multiline_arguments;
42        self
43    }
44}
45
46pub(crate) trait Displayable {
47    fn display(&self, f: &mut Formatter);
48}
49
50impl<'a> Formatter<'a> {
51    pub fn new(style: &'a Style) -> Formatter<'a> {
52        Formatter {
53            buf: String::with_capacity(1024),
54            style,
55            indent: 0,
56        }
57    }
58
59    pub fn with_buf(style: &'a Style, buf: String) -> Formatter<'a> {
60        Formatter {
61            buf,
62            style,
63            indent: 0,
64        }
65    }
66
67    pub fn indent(&mut self) {
68        for _ in 0..self.indent {
69            self.buf.push(' ');
70        }
71    }
72
73    pub fn endline(&mut self) {
74        self.buf.push('\n');
75    }
76
77    pub fn start_argument_block(&mut self, open_char: char) {
78        self.buf.push(open_char);
79        if self.style.multiline_arguments {
80            self.inc_indent();
81        }
82    }
83
84    pub fn end_argument_block(&mut self, close_char: char) {
85        if self.style.multiline_arguments {
86            self.endline();
87            self.dec_indent();
88            self.indent();
89        }
90        self.buf.push(close_char);
91    }
92
93    pub fn start_argument(&mut self) {
94        if self.style.multiline_arguments {
95            self.endline();
96            self.indent();
97        }
98    }
99
100    pub fn deliniate_argument(&mut self) {
101        self.buf.push(',');
102        if !self.style.multiline_arguments {
103            self.buf.push(' ');
104        }
105    }
106
107    pub fn start_block(&mut self) {
108        self.buf.push('{');
109        self.endline();
110        self.inc_indent();
111    }
112
113    pub fn end_block(&mut self) {
114        self.dec_indent();
115        self.indent();
116        self.buf.push('}');
117        self.endline();
118    }
119
120    pub fn margin(&mut self) {
121        if !self.buf.is_empty() {
122            self.buf.push('\n');
123        }
124    }
125
126    pub fn write(&mut self, s: &str) {
127        self.buf.push_str(s);
128    }
129
130    pub fn into_string(self) -> String {
131        self.buf
132    }
133
134    pub fn write_quoted(&mut self, s: &str) {
135        let mut has_newline = false;
136        let mut has_nonprintable = false;
137        for c in s.chars() {
138            match c {
139                '\n' => has_newline = true,
140                '\r' | '\t' | '\u{0020}'..='\u{FFFF}' => {}
141                _ => has_nonprintable = true,
142            }
143        }
144        if !has_newline || has_nonprintable {
145            use std::fmt::Write;
146            self.buf.push('"');
147            for c in s.chars() {
148                match c {
149                    '\r' => self.write(r"\r"),
150                    '\n' => self.write(r"\n"),
151                    '\t' => self.write(r"\t"),
152                    '"' => self.write("\\\""),
153                    '\\' => self.write(r"\\"),
154                    '\u{0020}'..='\u{FFFF}' => self.buf.push(c),
155                    _ => write!(&mut self.buf, "\\u{:04}", c as u32).unwrap(),
156                }
157            }
158            self.buf.push('"');
159        } else {
160            self.buf.push_str(r#"""""#);
161            self.endline();
162            self.indent += self.style.indent;
163            for line in s.lines() {
164                if !line.trim().is_empty() {
165                    self.indent();
166                    self.write(&line.replace(r#"""""#, r#"\""""#));
167                }
168                self.endline();
169            }
170            self.indent -= self.style.indent;
171            self.indent();
172            self.buf.push_str(r#"""""#);
173        }
174    }
175
176    fn inc_indent(&mut self) {
177        self.indent += self.style.indent;
178    }
179
180    fn dec_indent(&mut self) {
181        self.indent = self
182            .indent
183            .checked_sub(self.style.indent)
184            .expect("negative indent");
185    }
186}
187
188pub(crate) fn format_directives<'a, T>(dirs: &[Directive<'a, T>], f: &mut Formatter)
189where
190    T: super::common::Text<'a>,
191{
192    for dir in dirs {
193        f.write(" ");
194        dir.display(f);
195    }
196}
197
198macro_rules! impl_display {
199    ($( $typ: ident, )+) => {
200        $(
201            impl fmt::Display for $typ {
202                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203                    f.write_str(&to_string(self))
204                }
205            }
206        )+
207    };
208
209    ('a $($typ: ident, )+) => {
210        $(
211            impl<'a, T> fmt::Display for $typ<'a, T>
212                where T: Text<'a>,
213            {
214                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215                    f.write_str(&to_string(self))
216                }
217            }
218        )+
219    };
220}