KiCad PCB EDA Suite
Loading...
Searching...
No Matches
library_table.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 * @author Jon Evans <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <boost/lexical_cast.hpp>
22
23#include <cstdint>
24#include <limits>
25
26#include <kiplatform/io.h>
29#include <richio.h>
30#include <string_utils.h>
31#include <trace_helpers.h>
32#include <wx_filename.h>
33#include <xnode.h>
34#include <ki_exception.h>
36
37#include <wx/buffer.h>
38#include <wx/ffile.h>
39
40
41const wxString LIBRARY_TABLE_ROW::TABLE_TYPE_NAME = wxT( "Table" );
42
43
45{
46 return m_scope == aOther.m_scope
47 && m_nickname == aOther.m_nickname
48 && m_uri == aOther.m_uri
49 && m_type == aOther.m_type
50 && m_options == aOther.m_options
51 && m_description == aOther.m_description
52 && m_disabled == aOther.m_disabled
53 && m_hidden == aOther.m_hidden;
54}
55
56
57std::map<std::string, UTF8> LIBRARY_TABLE_ROW::GetOptionsMap() const
58{
60}
61
62
63LIBRARY_TABLE::LIBRARY_TABLE( const wxFileName &aPath, LIBRARY_TABLE_SCOPE aScope, LIBRARY_TABLE_TYPE aExpectedType ) :
64 m_scope( aScope )
65{
67
68 wxFileName fn( aPath );
70 m_path = fn.GetAbsolutePath();
71
72 if( !fn.FileExists() )
73 {
74 m_ok = false;
75 m_errorDescription = wxString::Format( _( "The library table path '%s' does not exist" ),
76 fn.GetFullPath() );
77 return;
78 }
79
80 if( fn.GetSize() < 1 )
81 {
82 // blank files are treated as "new" and "ok" as we want to allow the user to start
83 // fleshing them out and save
84 m_ok = true;
85 m_type = aExpectedType;
86 return;
87 }
88
89 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> ir = parser.Parse( m_path.ToStdString() );
90
91 if( ir.has_value() )
92 {
93 if( aExpectedType != LIBRARY_TABLE_TYPE::UNINITIALIZED && ir->type != aExpectedType )
94 {
95 m_ok = false;
96 m_errorDescription = _( "The library table is of wrong type" );
97 return;
98 }
99
100 m_ok = initFromIR( *ir );
101 }
102 else
103 {
104 m_ok = false;
105 m_errorDescription = ir.error().description;
106 }
107}
108
109
110LIBRARY_TABLE::LIBRARY_TABLE( bool aFromClipboard, const wxString &aBuffer, LIBRARY_TABLE_SCOPE aScope ) :
111 m_path( wxEmptyString ),
112 m_scope( aScope )
113{
115
116 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> ir = parser.ParseBuffer( aBuffer.ToStdString() );
117
118 if( ir.has_value() )
119 {
120 m_ok = initFromIR( *ir );
121 }
122 else
123 {
124 m_ok = false;
125 m_errorDescription = ir.error().description;
126 }
127}
128
129
130bool LIBRARY_TABLE::operator==( const LIBRARY_TABLE& aOther ) const
131{
132 return m_path == aOther.m_path
133 && m_scope == aOther.m_scope
134 && m_type == aOther.m_type
135 && m_version == aOther.m_version
136 && m_rows == aOther.m_rows;
137}
138
139
141{
142 m_type = aIR.type;
143
144 try
145 {
146 m_version = boost::lexical_cast<int>( aIR.version );
147 }
148 catch( const boost::bad_lexical_cast & )
149 {
150 m_version = std::nullopt;
151 }
152
153 for( const LIBRARY_TABLE_ROW_IR& row : aIR.rows )
154 addRowFromIR( row );
155
156 return true;
157}
158
159
161{
163
164 row.m_nickname = wxString::FromUTF8( aIR.nickname );
165 row.m_uri = wxString::FromUTF8( aIR.uri );
166 row.m_type = wxString::FromUTF8( aIR.type );
167 row.m_options = wxString::FromUTF8( aIR.options );
168 row.m_description = wxString::FromUTF8( aIR.description );
169 row.m_hidden = aIR.hidden;
170 row.m_disabled = aIR.disabled;
171 row.m_ok = true;
172 row.m_scope = m_scope;
173
174 m_rows.emplace_back( row );
175 return true;
176}
177
178
180{
181 static const std::map<LIBRARY_TABLE_TYPE, wxString> types = {
182 { LIBRARY_TABLE_TYPE::SYMBOL, "sym_lib_table" },
183 { LIBRARY_TABLE_TYPE::FOOTPRINT, "fp_lib_table" },
184 { LIBRARY_TABLE_TYPE::DESIGN_BLOCK, "design_block_lib_table" }
185 };
186
187 if( !types.contains( Type() ) )
188 {
189 THROW_IO_ERROR( "Unknown library table type: " + std::to_string( static_cast<int>( Type() ) ) );
190 }
191
192 XNODE self( wxXML_ELEMENT_NODE, types.at( Type() ) );
193
194 // TODO(JE) library tables - version management?
195 self.AddAttribute( "version", 7 );
196
197 for( const LIBRARY_TABLE_ROW& row : Rows() )
198 {
199 wxString uri = row.URI();
200 uri.Replace( '\\', '/' );
201
202 XNODE* rowNode = new XNODE( wxXML_ELEMENT_NODE, "lib" );
203 rowNode->AddAttribute( "name", row.Nickname() );
204 rowNode->AddAttribute( "type", row.Type() );
205 rowNode->AddAttribute( "uri", uri );
206 rowNode->AddAttribute( "options", row.Options() );
207 rowNode->AddAttribute( "descr", row.Description() );
208
209 if( row.Disabled() )
210 rowNode->AddChild( new XNODE( wxXML_ELEMENT_NODE, "disabled" ) );
211
212 if( row.Hidden() )
213 rowNode->AddChild( new XNODE( wxXML_ELEMENT_NODE, "hidden" ) );
214
215 self.AddChild( rowNode );
216 }
217
218 self.Format( aOutput );
219}
220
221
223{
224 LIBRARY_TABLE_ROW row = {};
225
226 row.SetScope( m_scope );
227 row.SetOk();
228
229 return row;
230}
231
232
234{
235 return Rows().emplace_back( MakeRow() );
236}
237
238
239bool LIBRARY_TABLE::HasRow( const wxString& aNickname ) const
240{
241 for( const LIBRARY_TABLE_ROW& row : m_rows )
242 {
243 if( row.Nickname() == aNickname )
244 return true;
245 }
246
247 return false;
248}
249
250
251bool LIBRARY_TABLE::HasRowWithURI( const wxString& aUri, const PROJECT& aProject,
252 bool aSubstituted ) const
253{
254 for( const LIBRARY_TABLE_ROW& row : m_rows )
255 {
256 if( !aSubstituted && row.URI() == aUri )
257 return true;
258
259 if( aSubstituted && LIBRARY_MANAGER::ExpandURI( row.URI(), aProject ) == aUri )
260 return true;
261 }
262
263 return false;
264}
265
266
267std::optional<LIBRARY_TABLE_ROW*> LIBRARY_TABLE::Row( const wxString& aNickname )
268{
269 for( LIBRARY_TABLE_ROW& row : m_rows )
270 {
271 if( row.Nickname() == aNickname )
272 return &row;
273 }
274
275 return std::nullopt;
276}
277
278
279std::optional<const LIBRARY_TABLE_ROW*> LIBRARY_TABLE::Row( const wxString& aNickname ) const
280{
281 for( const LIBRARY_TABLE_ROW& row : m_rows )
282 {
283 if( row.Nickname() == aNickname )
284 return &row;
285 }
286
287 return std::nullopt;
288}
289
290
292{
293 if( m_path.IsEmpty() )
294 return false;
295
296 wxFileName fn( m_path );
297
298 return fn.FileExists() && !fn.IsFileWritable();
299}
300
301
303{
304 if( IsReadOnly() )
305 {
306 return tl::unexpected( LIBRARY_ERROR(
307 wxString::Format( _( "Library table '%s' is read-only" ), Path() ) ) );
308 }
309
310 wxLogTrace( traceLibraries, "Saving %s", Path() );
311 wxFileName fn( Path() );
312 // This should already be normalized, but just in case...
313 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
314
315 // Global user data with no other recovery path: keep a rotating .bak sibling so the
316 // user can recover from logical corruption outside our fsync window.
317 wxFFile existing;
318
319 if( fn.FileExists() && existing.Open( fn.GetFullPath(), wxT( "rb" ) ) )
320 {
321 wxFileOffset rawLen = existing.Length();
322
323 if( rawLen >= 0
324 && static_cast<uint64_t>( rawLen ) <= std::numeric_limits<size_t>::max() )
325 {
326 size_t len = static_cast<size_t>( rawLen );
327 wxMemoryBuffer buf;
328 void* dst = len > 0 ? buf.GetWriteBuf( len ) : nullptr;
329
330 if( len == 0 || existing.Read( dst, len ) == len )
331 {
332 buf.SetDataLen( len );
333 existing.Close();
334
335 wxString bakPath = fn.GetFullPath() + wxT( ".bak" );
336 wxString bakError;
337
338 if( !KIPLATFORM::IO::AtomicWriteFile( bakPath, buf.GetData(), len, &bakError ) )
339 {
340 // Non-fatal: the original is still on disk and the atomic save below
341 // is safe.
342 wxLogTrace( traceLibraries,
343 "Could not rotate library table backup to '%s': %s", bakPath,
344 bakError );
345 }
346 }
347 }
348 }
349
350 try
351 {
352 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( fn.GetFullPath(), KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
353 Format( &formatter );
354 formatter.Finish();
355 }
356 catch( IO_ERROR& e )
357 {
358 wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() );
359 return tl::unexpected( LIBRARY_ERROR( e.Problem(), e.Where() ) );
360 }
361
362 return LIBRARY_RESULT<void>();
363}
364
365
366#define OPT_SEP '|'
367
368std::map<std::string, UTF8> LIBRARY_TABLE::ParseOptions( const std::string& aOptionsList )
369{
370 std::map<std::string, UTF8> props;
371
372 if( aOptionsList.size() )
373 {
374 const char* cp = &aOptionsList[0];
375 const char* end = cp + aOptionsList.size();
376
377 std::string pair;
378
379 // Parse all name=value pairs
380 while( cp < end )
381 {
382 pair.clear();
383
384 // Skip leading white space.
385 while( cp < end && isspace( *cp ) )
386 ++cp;
387
388 // Find the end of pair/field
389 while( cp < end )
390 {
391 if( *cp == '\\' && cp + 1 < end && cp[1] == OPT_SEP )
392 {
393 ++cp; // skip the escape
394 pair += *cp++; // add the separator
395 }
396 else if( *cp == OPT_SEP )
397 {
398 ++cp; // skip the separator
399 break; // process the pair
400 }
401 else
402 {
403 pair += *cp++;
404 }
405 }
406
407 // stash the pair
408 if( pair.size() )
409 {
410 // first equals sign separates 'name' and 'value'.
411 size_t eqNdx = pair.find( '=' );
412
413 if( eqNdx != pair.npos )
414 {
415 std::string name = pair.substr( 0, eqNdx );
416 std::string value = pair.substr( eqNdx + 1 );
417 props[name] = value;
418 }
419 else
420 {
421 props[pair] = ""; // property is present, but with no value.
422 }
423 }
424 }
425 }
426
427 return props;
428}
429
430
431UTF8 LIBRARY_TABLE::FormatOptions( const std::map<std::string, UTF8>* aProperties )
432{
433 UTF8 ret;
434
435 if( aProperties )
436 {
437 for( std::map<std::string, UTF8>::const_iterator it = aProperties->begin();
438 it != aProperties->end(); ++it )
439 {
440 const std::string& name = it->first;
441
442 const UTF8& value = it->second;
443
444 if( ret.size() )
445 ret += OPT_SEP;
446
447 ret += name;
448
449 // the separation between name and value is '='
450 if( value.size() )
451 {
452 ret += '=';
453
454 for( std::string::const_iterator si = value.begin(); si != value.end(); ++si )
455 {
456 // escape any separator in the value.
457 if( *si == OPT_SEP )
458 ret += '\\';
459
460 ret += *si;
461 }
462 }
463 }
464 }
465
466 return ret;
467}
const char * name
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const wxString Problem() const
what was the problem?
virtual const wxString Where() const
where did the Problem() occur?
static wxString ExpandURI(const wxString &aShortURI, const PROJECT &aProject)
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > ParseBuffer(const std::string &aBuffer)
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > Parse(const std::filesystem::path &aPath)
LIBRARY_TABLE_ROW()=default
void SetOk(bool aOk=true)
bool operator==(const LIBRARY_TABLE_ROW &aOther) const
std::map< std::string, UTF8 > GetOptionsMap() const
static const wxString TABLE_TYPE_NAME
void SetScope(LIBRARY_TABLE_SCOPE aScope)
LIBRARY_TABLE_SCOPE m_scope
LIBRARY_RESULT< void > Save()
bool operator==(const LIBRARY_TABLE &aOther) const
void Format(OUTPUTFORMATTER *aOutput) const
bool IsReadOnly() const
Returns true if the underlying file exists but is not writable.
static std::map< std::string, UTF8 > ParseOptions(const std::string &aOptionsList)
LIBRARY_TABLE_TYPE Type() const
LIBRARY_TABLE_TYPE m_type
What type of content this table contains (footprint, symbol, design block, etc)
std::optional< LIBRARY_TABLE_ROW * > Row(const wxString &aNickname)
LIBRARY_TABLE_ROW & InsertRow()
Builds a new row and inserts it at the end of the table; returning a reference to the row.
static UTF8 FormatOptions(const std::map< std::string, UTF8 > *aProperties)
const wxString & Path() const
wxString m_path
The full path to the file this table was parsed from, if any.
wxString m_errorDescription
LIBRARY_TABLE_ROW MakeRow() const
Builds a new row that is suitable for this table (does not insert it)
bool HasRow(const wxString &aNickname) const
bool HasRowWithURI(const wxString &aUri, const PROJECT &aProject, bool aSubstituted=false) const
Returns true if the given (fully-expanded) URI exists as a library in this table.
const std::deque< LIBRARY_TABLE_ROW > & Rows() const
LIBRARY_TABLE(const wxFileName &aPath, LIBRARY_TABLE_SCOPE aScope, LIBRARY_TABLE_TYPE aExpectedType=LIBRARY_TABLE_TYPE::UNINITIALIZED)
Creates a library table from a file on disk.
std::optional< int > m_version
The format version, if present in the parsed file.
std::deque< LIBRARY_TABLE_ROW > m_rows
bool addRowFromIR(const LIBRARY_TABLE_ROW_IR &aIR)
LIBRARY_TABLE_SCOPE m_scope
bool initFromIR(const LIBRARY_TABLE_IR &aIR)
An interface used to output 8 bit text in a convenient way.
Definition richio.h:294
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
std::string::const_iterator begin() const
Definition utf8.h:215
std::string::size_type size() const
Definition utf8.h:112
std::string::const_iterator end() const
Definition utf8.h:216
static void ResolvePossibleSymlinks(wxFileName &aFilename)
An extension of wxXmlNode that can format its contents as KiCad-style s-expressions.
Definition xnode.h:67
void Format(OUTPUTFORMATTER *out) const
Write this object as UTF8 out to an OUTPUTFORMATTER as an S-expression.
Definition xnode.cpp:109
void AddAttribute(const wxString &aName, const wxString &aValue) override
Definition xnode.cpp:88
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
#define OPT_SEP
options separator character
tl::expected< ResultType, LIBRARY_ERROR > LIBRARY_RESULT
LIBRARY_TABLE_TYPE
LIBRARY_TABLE_SCOPE
bool AtomicWriteFile(const wxString &aTargetPath, const void *aData, size_t aSize, wxString *aError=nullptr)
Writes aData to aTargetPath via a sibling temp file, fsyncs the data and directory,...
The intermediate representation that a library table is parsed into.
std::vector< LIBRARY_TABLE_ROW_IR > rows
LIBRARY_TABLE_TYPE type
VECTOR2I end
wxLogTrace helper definitions.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35