KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kicad_io_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <fmt/format.h>
21
22#include <kiid.h>
24#include <richio.h>
25#include <string_utils.h>
26
27namespace KICAD_FORMAT {
28
29void FormatBool( OUTPUTFORMATTER* aOut, int aNestLevel, const wxString& aKey, bool aValue,
30 char aSuffix )
31{
32 aOut->Print( aNestLevel, "(%ls %s)%c", aKey.wc_str(), aValue ? "yes" : "no", aSuffix );
33}
34
35
36void FormatUuid( OUTPUTFORMATTER* aOut, const KIID& aUuid, char aSuffix )
37{
38 if( aSuffix )
39 aOut->Print( 0, "(uuid \"%s\")%c", TO_UTF8( aUuid.AsString() ), aSuffix );
40 else
41 aOut->Print( 0, "(uuid \"%s\")", TO_UTF8( aUuid.AsString() ) );
42
43}
44
45/*
46 * Formatting rules:
47 * - All extra (non-indentation) whitespace is trimmed
48 * - Indentation is one tab
49 * - Starting a new list (open paren) starts a new line with one deeper indentation
50 * - Lists with no inner lists go on a single line
51 * - End of multi-line lists (close paren) goes on a single line at same indentation as its start
52 *
53 * For example:
54 * (first
55 * (second
56 * (third list)
57 * (another list)
58 * )
59 * (fifth)
60 * (sixth thing with lots of tokens
61 * (and a sub list)
62 * )
63 * )
64 */
65void Prettify( std::string& aSource, char aQuoteChar )
66{
67 // Configuration
68 const char indentChar = '\t';
69 const int indentSize = 1;
70
71 // In order to visually compress PCB files, it is helpful to special-case long lists of (xy ...)
72 // lists, which we allow to exist on a single line until we reach column 99.
73 const int xySpecialCaseColumnLimit = 99;
74
75 // If whitespace occurs inside a list after this threshold, it will be converted into a newline
76 // and the indentation will be increased. This is mainly used for image and group objects,
77 // which contain potentially long sets of string tokens within a single list.
78 const int consecutiveTokenWrapThreshold = 72;
79
80 std::string formatted;
81 formatted.reserve( aSource.length() );
82
83 auto cursor = aSource.begin();
84 auto seek = cursor;
85
86 int listDepth = 0;
87 char lastNonWhitespace = 0;
88 bool inQuote = false;
89 bool hasInsertedSpace = false;
90 bool inMultiLineList = false;
91 bool inXY = false;
92 int column = 0;
93 int backslashCount = 0; // Count of successive backslash read since any other char
94
95 auto isWhitespace = []( const char aChar )
96 {
97 return ( aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r' );
98 };
99
100 auto nextNonWhitespace =
101 [&]( std::string::iterator aIt )
102 {
103 seek = aIt;
104
105 while( seek != aSource.end() && isWhitespace( *seek ) )
106 seek++;
107
108 if( seek == aSource.end() )
109 return (char)0;
110
111 return *seek;
112 };
113
114 auto isXY =
115 [&]( std::string::iterator aIt )
116 {
117 seek = aIt;
118
119 if( ++seek == aSource.end() || *seek != 'x' )
120 return false;
121
122 if( ++seek == aSource.end() || *seek != 'y' )
123 return false;
124
125 if( ++seek == aSource.end() || *seek != ' ' )
126 return false;
127
128 return true;
129 };
130
131 while( cursor != aSource.end() )
132 {
133 char next = nextNonWhitespace( cursor );
134
135 if( isWhitespace( *cursor ) && !inQuote )
136 {
137 if( !hasInsertedSpace // Only permit one space between chars
138 && listDepth > 0 // Do not permit spaces in outer list
139 && lastNonWhitespace != '(' // Remove extra space after start of list
140 && next != ')' // Remove extra space before end of list
141 && next != '(' ) // Remove extra space before newline
142 {
143 if( inXY || column < consecutiveTokenWrapThreshold )
144 {
145 // Note that we only insert spaces here, no matter what kind of whitespace is in
146 // the input. Newlines will be inserted as needed by the logic below.
147 formatted.push_back( ' ' );
148 column++;
149 }
150 else
151 {
152 formatted += fmt::format( "\n{}",
153 std::string( listDepth * indentSize, indentChar ) );
154 column = listDepth * indentSize;
155 inMultiLineList = true;
156 }
157
158 hasInsertedSpace = true;
159 }
160 }
161 else
162 {
163 hasInsertedSpace = false;
164
165 if( *cursor == '(' && !inQuote )
166 {
167 bool currentIsXY = isXY( cursor );
168
169 if( listDepth == 0 )
170 {
171 formatted.push_back( '(' );
172 column++;
173 }
174 else if( inXY && currentIsXY && column < xySpecialCaseColumnLimit )
175 {
176 // List-of-points special case
177 formatted += " (";
178 column += 2;
179 inXY = true;
180 }
181 else
182 {
183 formatted += fmt::format( "\n{}(",
184 std::string( listDepth * indentSize, indentChar ) );
185 column = listDepth * indentSize + 1;
186 }
187
188 inXY = currentIsXY;
189 listDepth++;
190 }
191 else if( *cursor == ')' && !inQuote )
192 {
193 if( listDepth > 0 )
194 listDepth--;
195
196 if( lastNonWhitespace == ')' || inMultiLineList )
197 {
198 formatted += fmt::format( "\n{})",
199 std::string( listDepth * indentSize, indentChar ) );
200 column = listDepth * indentSize + 1;
201 inMultiLineList = false;
202 }
203 else
204 {
205 formatted.push_back( ')' );
206 column++;
207 }
208 }
209 else
210 {
211 // The output formatter escapes double-quotes (like \")
212 // But a corner case is a sequence like \\"
213 // therefore a '\' is attached to a '"' if a odd number of '\' is detected
214 if( *cursor == '\\' )
215 backslashCount++;
216 else if( *cursor == aQuoteChar && ( backslashCount & 1 ) == 0 )
217 inQuote = !inQuote;
218
219 if( *cursor != '\\' )
220 backslashCount = 0;
221
222 formatted.push_back( *cursor );
223 column++;
224 }
225
226 lastNonWhitespace = *cursor;
227 }
228
229 ++cursor;
230 }
231
232 // newline required at end of line / file for POSIX compliance. Keeps git diffs clean.
233 formatted += '\n';
234
235 aSource = std::move( formatted );
236}
237
238} // namespace KICAD_FORMAT
Definition: kiid.h:49
wxString AsString() const
Definition: kiid.cpp:257
An interface used to output 8 bit text in a convenient way.
Definition: richio.h:322
int PRINTF_FUNC Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition: richio.cpp:458
void FormatBool(OUTPUTFORMATTER *aOut, int aNestLevel, const wxString &aKey, bool aValue, char aSuffix)
Writes a boolean to the formatter, in the style (aKey [yes|no])
void Prettify(std::string &aSource, char aQuoteChar)
void FormatUuid(OUTPUTFORMATTER *aOut, const KIID &aUuid, char aSuffix)
CITER next(CITER it)
Definition: ptree.cpp:126
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:391