Skip to main content

sqlparser/
display_utils.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Utilities for formatting SQL AST nodes with pretty printing support.
19//!
20//! The module provides formatters that implement the `Display` trait with support
21//! for both regular (`{}`) and pretty (`{:#}`) formatting modes. Pretty printing
22//! adds proper indentation and line breaks to make SQL statements more readable.
23
24use core::fmt::{self, Display, Write};
25
26/// A wrapper around a value that adds an indent to the value when displayed with {:#}.
27pub(crate) struct Indent<T>(pub T);
28
29const INDENT: &str = "  ";
30
31impl<T> Display for Indent<T>
32where
33    T: Display,
34{
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        if f.alternate() {
37            f.write_str(INDENT)?;
38            Indent(f).write_fmt(format_args!("{0:#}", self.0))write!(Indent(f), "{:#}", self.0)
39        } else {
40            self.0.fmt(f)
41        }
42    }
43}
44
45/// Adds an indent to the inner writer
46impl<T> Write for Indent<T>
47where
48    T: Write,
49{
50    fn write_str(&mut self, s: &str) -> fmt::Result {
51        self.0.write_str(s)?;
52        // Our NewLine and SpaceOrNewline utils always print individual newlines as a single-character string.
53        if s == "\n" {
54            self.0.write_str(INDENT)?;
55        }
56        Ok(())
57    }
58}
59
60/// A value that inserts a newline when displayed with {:#}, but not when displayed with {}.
61pub(crate) struct NewLine;
62
63impl Display for NewLine {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        if f.alternate() {
66            f.write_char('\n')
67        } else {
68            Ok(())
69        }
70    }
71}
72
73/// A value that inserts a space when displayed with {}, but a newline when displayed with {:#}.
74pub(crate) struct SpaceOrNewline;
75
76impl Display for SpaceOrNewline {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        if f.alternate() {
79            f.write_char('\n')
80        } else {
81            f.write_char(' ')
82        }
83    }
84}
85
86/// A value that displays a comma-separated list of values.
87/// When pretty-printed (using {:#}), it displays each value on a new line.
88pub(crate) struct DisplayCommaSeparated<'a, T: fmt::Display>(pub(crate) &'a [T]);
89
90impl<T: fmt::Display> fmt::Display for DisplayCommaSeparated<'_, T> {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        let mut first = true;
93        for t in self.0 {
94            if !first {
95                f.write_char(',')?;
96                SpaceOrNewline.fmt(f)?;
97            }
98            first = false;
99            t.fmt(f)?;
100        }
101        Ok(())
102    }
103}
104
105/// Displays a whitespace, followed by a comma-separated list that is indented when pretty-printed.
106pub(crate) fn indented_list<T: fmt::Display>(f: &mut fmt::Formatter, items: &[T]) -> fmt::Result {
107    SpaceOrNewline.fmt(f)?;
108    Indent(DisplayCommaSeparated(items)).fmt(f)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_indent() {
117        struct TwoLines;
118
119        impl Display for TwoLines {
120            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121                f.write_str("line 1")?;
122                SpaceOrNewline.fmt(f)?;
123                f.write_str("line 2")
124            }
125        }
126
127        let indent = Indent(TwoLines);
128        assert_eq!(
129            indent.to_string(),
130            TwoLines.to_string(),
131            "Only the alternate form should be indented"
132        );
133        assert_eq!(format!("{:#}", indent), "  line 1\n  line 2");
134    }
135}