KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lib_table_base.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) 2010-2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
5 * Copyright (C) 2012 Wayne Stambaugh <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26
27#include <wx/debug.h>
28#include <wx/filename.h>
29#include <set>
30#include <common.h>
31#include <kiface_base.h>
32#include <lib_table_base.h>
33#include <lib_table_lexer.h>
34#include <macros.h>
35#include <string_utils.h>
36#include <build_version.h>
37
38#define OPT_SEP '|'
39
40
41using namespace LIB_TABLE_T;
42
43
44std::unique_ptr<LINE_READER> FILE_LIB_TABLE_IO::GetReader( const wxString& aURI ) const
45{
46 const wxFileName fn( aURI );
47
48 if( !fn.IsOk() || !fn.IsFileReadable() )
49 return nullptr;
50
51 return std::make_unique<FILE_LINE_READER>( aURI );
52}
53
54
55bool FILE_LIB_TABLE_IO::CanSaveToUri( const wxString& aURI ) const
56{
57 const wxFileName fn( aURI );
58
59 if( !fn.IsOk() )
60 return false;
61
62 return fn.IsFileWritable();
63}
64
65
66bool FILE_LIB_TABLE_IO::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 ) const
67{
68 // Avoid comparing filenames as wxURIs
69 if( aURI1.Find( "://" ) != wxNOT_FOUND )
70 {
71 // found as full path
72 return aURI1 == aURI2;
73 }
74 else
75 {
76 const wxFileName fn1( aURI1 );
77 const wxFileName fn2( aURI2 );
78
79 // This will also test if the file is a symlink so if we are comparing
80 // a symlink to the same real file, the comparison will be true. See
81 // wxFileName::SameAs() in the wxWidgets source.
82
83 // found as full path and file name
84 return fn1 == fn2;
85 }
86}
87
88
89std::unique_ptr<OUTPUTFORMATTER> FILE_LIB_TABLE_IO::GetWriter( const wxString& aURI ) const
90{
91 const wxFileName fn( aURI );
92 return std::make_unique<FILE_OUTPUTFORMATTER>( aURI );
93}
94
95
97{
98 return aRow.clone();
99}
100
101
102void LIB_TABLE_ROW::setProperties( std::map<std::string, UTF8>* aProperties )
103{
104 properties.reset( aProperties );
105}
106
107
108void LIB_TABLE_ROW::SetFullURI( const wxString& aFullURI )
109{
110 uri_user = aFullURI;
111}
112
113
114const wxString LIB_TABLE_ROW::GetFullURI( bool aSubstituted ) const
115{
116 if( aSubstituted )
117 {
118 return ExpandEnvVarSubstitutions( uri_user, nullptr );
119 }
120
121 return uri_user;
122}
123
124
125void LIB_TABLE_ROW::Format( OUTPUTFORMATTER* out, int nestLevel ) const
126{
127 // In KiCad, we save path and file names using the Unix notation (separator = '/')
128 // So ensure separator is always '/' is saved URI string
129 wxString uri = GetFullURI();
130 uri.Replace( '\\', '/' );
131
132 wxString extraOptions;
133
134 if( !GetIsEnabled() )
135 extraOptions += "(disabled)";
136
137 if( !GetIsVisible() )
138 extraOptions += "(hidden)";
139
140 out->Print( nestLevel, "(lib (name %s)(type %s)(uri %s)(options %s)(descr %s)%s)\n",
141 out->Quotew( GetNickName() ).c_str(),
142 out->Quotew( GetType() ).c_str(),
143 out->Quotew( uri ).c_str(),
144 out->Quotew( GetOptions() ).c_str(),
145 out->Quotew( GetDescr() ).c_str(),
146 extraOptions.ToStdString().c_str() );
147}
148
149
151{
152 return nickName == r.nickName
153 && uri_user == r.uri_user
154 && options == r.options
156 && enabled == r.enabled
157 && visible == r.visible;
158}
159
160
161void LIB_TABLE_ROW::SetOptions( const wxString& aOptions )
162{
163 options = aOptions;
164
165 // set PROPERTIES* from options
167}
168
169
170LIB_TABLE::LIB_TABLE( LIB_TABLE* aFallBackTable, std::unique_ptr<LIB_TABLE_IO> aTableIo ) :
171 m_io( std::move( aTableIo ) ), m_fallBack( aFallBackTable ), m_version( 0 )
172{
173 // If not given, use the default file-based I/O.
174 if( !m_io )
175 {
176 m_io = std::make_unique<FILE_LIB_TABLE_IO>();
177 }
178
179 // not copying fall back, simply search aFallBackTable separately
180 // if "nickName not found".
181}
182
183
185{
186 // *fallBack is not owned here.
187}
188
189
191{
192 m_rows.clear();
193 m_rowsMap.clear();
194}
195
196
197bool LIB_TABLE::IsEmpty( bool aIncludeFallback )
198{
199 if( !aIncludeFallback || !m_fallBack )
200 return m_rows.empty();
201
202 return m_rows.empty() && m_fallBack->IsEmpty( true );
203}
204
205
206const wxString LIB_TABLE::GetDescription( const wxString& aNickname )
207{
208 // Use "no exception" form of find row and ignore disabled flag.
209 const LIB_TABLE_ROW* row = findRow( aNickname );
210
211 if( row )
212 return row->GetDescr();
213 else
214 return wxEmptyString;
215}
216
217
218bool LIB_TABLE::HasLibrary( const wxString& aNickname, bool aCheckEnabled ) const
219{
220 const LIB_TABLE_ROW* row = findRow( aNickname, aCheckEnabled );
221
222 if( row == nullptr )
223 return false;
224
225 return true;
226}
227
228
229bool LIB_TABLE::HasLibraryWithPath( const wxString& aPath ) const
230{
231 for( const LIB_TABLE_ROW& row : m_rows )
232 {
233 if( row.GetFullURI() == aPath )
234 return true;
235 }
236
237 return false;
238}
239
240
241wxString LIB_TABLE::GetFullURI( const wxString& aNickname, bool aExpandEnvVars ) const
242{
243 const LIB_TABLE_ROW* row = findRow( aNickname, true );
244
245 wxString retv;
246
247 if( row )
248 retv = row->GetFullURI( aExpandEnvVars );
249
250 return retv;
251}
252
253
254LIB_TABLE_ROW* LIB_TABLE::findRow( const wxString& aNickName, bool aCheckIfEnabled ) const
255{
256 LIB_TABLE_ROW* row = nullptr;
257 LIB_TABLE* cur = (LIB_TABLE*) this;
258
259 do
260 {
261 std::shared_lock<std::shared_mutex> lock( cur->m_mutex );
262
263 if( cur->m_rowsMap.count( aNickName ) )
264 row = &*cur->m_rowsMap.at( aNickName );
265
266 if( row )
267 {
268 if( !aCheckIfEnabled || row->GetIsEnabled() )
269 return row;
270 else
271 return nullptr; // We found it, but it's disabled
272 }
273
274 // Repeat, this time looking for names that were "fixed" by legacy versions because
275 // the old Eeschema file format didn't support spaces in tokens.
276 for( const std::pair<const wxString, LIB_TABLE_ROWS_ITER>& entry : cur->m_rowsMap )
277 {
278 wxString legacyLibName = entry.first;
279 legacyLibName.Replace( " ", "_" );
280
281 if( legacyLibName == aNickName )
282 {
283 row = &*entry.second;
284
285 if( !aCheckIfEnabled || row->GetIsEnabled() )
286 return row;
287 }
288 }
289
290 // not found, search fall back table(s), if any
291 } while( ( cur = cur->m_fallBack ) != nullptr );
292
293 return nullptr; // not found
294}
295
296
297const LIB_TABLE_ROW* LIB_TABLE::FindRowByURI( const wxString& aURI )
298{
299 LIB_TABLE* cur = this;
300
301 do
302 {
303 for( unsigned i = 0; i < cur->m_rows.size(); i++ )
304 {
305 const wxString tmp = cur->m_rows[i].GetFullURI( true );
306
307 if( m_io->UrisAreEquivalent( tmp, aURI ) )
308 return &cur->m_rows[i];
309 }
310
311 // not found, search fall back table(s), if any
312 } while( ( cur = cur->m_fallBack ) != nullptr );
313
314 return nullptr; // not found
315}
316
317
318std::vector<wxString> LIB_TABLE::GetLogicalLibs()
319{
320 // Only return unique logical library names. Use std::set::insert() to quietly reject any
321 // duplicates (usually due to encountering a duplicate nickname in a fallback table).
322
323 std::set<wxString> unique;
324 std::vector<wxString> ret;
325 const LIB_TABLE* cur = this;
326
327 do
328 {
329 for( const LIB_TABLE_ROW& row : cur->m_rows )
330 {
331 if( row.GetIsEnabled() )
332 unique.insert( row.GetNickName() );
333 }
334
335 } while( ( cur = cur->m_fallBack ) != nullptr );
336
337 ret.reserve( unique.size() );
338
339 // return a sorted, unique set of nicknames in a std::vector<wxString> to caller
340 for( std::set< wxString >::const_iterator it = unique.begin(); it!=unique.end(); ++it )
341 ret.push_back( *it );
342
343 // We want to allow case-sensitive duplicates but sort by case-insensitive ordering
344 std::sort( ret.begin(), ret.end(),
345 []( const wxString& lhs, const wxString& rhs )
346 {
347 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
348 } );
349
350 return ret;
351}
352
353
354bool LIB_TABLE::InsertRow( LIB_TABLE_ROW* aRow, bool doReplace )
355{
356 std::lock_guard<std::shared_mutex> lock( m_mutex );
357
358 doInsertRow( aRow, doReplace );
359 reindex();
360
361 return true;
362}
363
364
365bool LIB_TABLE::doInsertRow( LIB_TABLE_ROW* aRow, bool doReplace )
366{
367 auto it = m_rowsMap.find( aRow->GetNickName() );
368
369 if( it != m_rowsMap.end() )
370 {
371 if( !doReplace )
372 return false;
373
374 m_rows.replace( it->second, aRow );
375 }
376 else
377 {
378 m_rows.push_back( aRow );
379 }
380
381 aRow->SetParent( this );
382 reindex();
383 return true;
384}
385
386
388{
389 std::lock_guard<std::shared_mutex> lock( m_mutex );
390
391 bool found = false;
392 auto it = m_rowsMap.find( aRow->GetNickName() );
393
394 if( it != m_rowsMap.end() )
395 {
396 if( &*it->second == aRow )
397 {
398 found = true;
399 m_rows.erase( it->second );
400 }
401 }
402
403 if( !found )
404 {
405 // Bookkeeping got messed up...
406 for( int i = (int)m_rows.size() - 1; i >= 0; --i )
407 {
408 if( &m_rows[i] == aRow )
409 {
410 m_rows.erase( m_rows.begin() + i );
411 found = true;
412 break;
413 }
414 }
415 }
416
417 if( found )
418 reindex();
419
420 return found;
421}
422
423
424bool LIB_TABLE::ReplaceRow( size_t aIndex, LIB_TABLE_ROW* aRow )
425{
426 std::lock_guard<std::shared_mutex> lock( m_mutex );
427
428 if( aIndex >= m_rows.size() )
429 return false;
430
431 m_rowsMap.erase( m_rows[aIndex].GetNickName() );
432
433 m_rows.replace( aIndex, aRow );
434 reindex();
435 return true;
436}
437
438
439bool LIB_TABLE::ChangeRowOrder( size_t aIndex, int aOffset )
440{
441 std::lock_guard<std::shared_mutex> lock( m_mutex );
442
443 if( aIndex >= m_rows.size() )
444 return false;
445
446 int newPos = static_cast<int>( aIndex ) + aOffset;
447
448 if( newPos < 0 || newPos > static_cast<int>( m_rows.size() ) - 1 )
449 return false;
450
451 auto element = m_rows.release( m_rows.begin() + aIndex );
452
453 m_rows.insert( m_rows.begin() + newPos, element.release() );
454 reindex();
455
456 return true;
457}
458
459
461{
462 std::lock_guard<std::shared_mutex> lock( m_mutex );
463
464 clear();
465 m_rows.transfer( m_rows.end(), aRowsList.begin(), aRowsList.end(), aRowsList );
466
467 reindex();
468}
469
470
472{
473 m_rowsMap.clear();
474
475 for( LIB_TABLE_ROWS_ITER it = m_rows.begin(); it != m_rows.end(); ++it )
476 {
477 it->SetParent( this );
478 m_rowsMap[it->GetNickName()] = it;
479 }
480}
481
482
484{
485 bool table_updated = false;
486
487 for( LIB_TABLE_ROW& row : m_rows )
488 {
489 bool row_updated = false;
490 wxString uri = row.GetFullURI( true );
491
492 // If the uri still has a variable in it, that means that the user does not have
493 // these vars defined. We update the old vars to the current versions on load
494
495 static wxString fmtStr = wxS( "${KICAD%d_" );
496 int version = 0;
497 std::tie(version, std::ignore, std::ignore) = GetMajorMinorPatchTuple();
498
499 for( int ii = 5; ii < version - 1; ++ii )
500 {
501 row_updated |= ( uri.Replace( wxString::Format( fmtStr, ii ),
502 wxString::Format( fmtStr, version ), false ) > 0 );
503 }
504
505 if( row_updated )
506 {
507 row.SetFullURI( uri );
508 table_updated = true;
509 }
510 }
511
512 return table_updated;
513}
514
515
516void LIB_TABLE::Load( const wxString& aFileName )
517{
518 std::lock_guard<std::shared_mutex> lock( m_mutex );
519 clear();
520
521 std::unique_ptr<LINE_READER> reader = m_io->GetReader( aFileName );
522
523 // It's OK if footprint library tables are missing.
524 if( reader )
525 {
526 LIB_TABLE_LEXER lexer( reader.get() );
527
528 Parse( &lexer );
529
530 if( m_version != 7 && migrate() && m_io->CanSaveToUri( aFileName ) )
531 Save( aFileName );
532
533 reindex();
534 }
535}
536
537
538void LIB_TABLE::Save( const wxString& aFileName ) const
539{
540 std::unique_ptr<OUTPUTFORMATTER> sf = m_io->GetWriter( aFileName );
541
542 if( !sf )
543 {
544 THROW_IO_ERROR( wxString::Format( _( "Failed to get writer for %s" ), aFileName ) );
545 }
546
547 // Force the lib table version to 7 before saving
548 m_version = 7;
549 Format( sf.get(), 0 );
550}
551
552
553std::map<std::string, UTF8>* LIB_TABLE::ParseOptions( const std::string& aOptionsList )
554{
555 if( aOptionsList.size() )
556 {
557 const char* cp = &aOptionsList[0];
558 const char* end = cp + aOptionsList.size();
559
560 std::map<std::string, UTF8> props;
561 std::string pair;
562
563 // Parse all name=value pairs
564 while( cp < end )
565 {
566 pair.clear();
567
568 // Skip leading white space.
569 while( cp < end && isspace( *cp ) )
570 ++cp;
571
572 // Find the end of pair/field
573 while( cp < end )
574 {
575 if( *cp == '\\' && cp + 1 < end && cp[1] == OPT_SEP )
576 {
577 ++cp; // skip the escape
578 pair += *cp++; // add the separator
579 }
580 else if( *cp == OPT_SEP )
581 {
582 ++cp; // skip the separator
583 break; // process the pair
584 }
585 else
586 {
587 pair += *cp++;
588 }
589 }
590
591 // stash the pair
592 if( pair.size() )
593 {
594 // first equals sign separates 'name' and 'value'.
595 size_t eqNdx = pair.find( '=' );
596
597 if( eqNdx != pair.npos )
598 {
599 std::string name = pair.substr( 0, eqNdx );
600 std::string value = pair.substr( eqNdx + 1 );
601 props[name] = value;
602 }
603 else
604 {
605 props[pair] = ""; // property is present, but with no value.
606 }
607 }
608 }
609
610 if( props.size() )
611 return new std::map<std::string, UTF8>( props );
612 }
613
614 return nullptr;
615}
616
617
618UTF8 LIB_TABLE::FormatOptions( const std::map<std::string, UTF8>* aProperties )
619{
620 UTF8 ret;
621
622 if( aProperties )
623 {
624 for( std::map<std::string, UTF8>::const_iterator it = aProperties->begin();
625 it != aProperties->end(); ++it )
626 {
627 const std::string& name = it->first;
628
629 const UTF8& value = it->second;
630
631 if( ret.size() )
632 ret += OPT_SEP;
633
634 ret += name;
635
636 // the separation between name and value is '='
637 if( value.size() )
638 {
639 ret += '=';
640
641 for( std::string::const_iterator si = value.begin(); si != value.end(); ++si )
642 {
643 // escape any separator in the value.
644 if( *si == OPT_SEP )
645 ret += '\\';
646
647 ret += *si;
648 }
649 }
650 }
651 }
652
653 return ret;
654}
const char * name
Definition: DXF_plotter.cpp:59
const std::tuple< int, int, int > & GetMajorMinorPatchTuple()
Get the build version numbers as a tuple.
bool CanSaveToUri(const wxString &aURI) const override
Check if the given URI is writable.
bool UrisAreEquivalent(const wxString &aURI1, const wxString &aURI2) const override
Compare two URIs for equivalence.
std::unique_ptr< LINE_READER > GetReader(const wxString &aURI) const override
Create a reader for the given URI.
std::unique_ptr< OUTPUTFORMATTER > GetWriter(const wxString &aURI) const override
Save the given table to the given URI.
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
void SetFullURI(const wxString &aFullURI)
Change the full URI for the library.
bool visible
Whether the LIB_TABLE_ROW is visible in choosers.
std::unique_ptr< std::map< std::string, UTF8 > > properties
const wxString & GetOptions() const
Return the options string, which may hold a password or anything else needed to instantiate the under...
wxString nickName
const wxString & GetDescr() const
Return the description of the library referenced by this row.
wxString options
wxString uri_user
what user entered from UI or loaded from disk
virtual const wxString GetType() const =0
Return the type of library represented by this row.
wxString description
void Format(OUTPUTFORMATTER *out, int nestLevel) const
Serialize this object as utf8 text to an OUTPUTFORMATTER, and tries to make it look good using multip...
bool enabled
Whether the LIB_TABLE_ROW is enabled.
void SetParent(LIB_TABLE *aParent)
const wxString & GetNickName() const
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
LIB_TABLE_ROW * clone() const
bool operator==(const LIB_TABLE_ROW &r) const
bool GetIsEnabled() const
void SetOptions(const wxString &aOptions)
Change the library options strings.
void setProperties(std::map< std::string, UTF8 > *aProperties)
bool GetIsVisible() const
Manage LIB_TABLE_ROW records (rows), and can be searched based on library nickname.
bool ReplaceRow(size_t aIndex, LIB_TABLE_ROW *aRow)
Replaces the Nth row with the given new row.
std::shared_mutex m_mutex
Mutex to protect access to the rows vector.
const wxString GetDescription(const wxString &aNickname)
std::vector< wxString > GetLogicalLibs()
Return the logical library names, all of them that are pertinent to a look up done on this LIB_TABLE.
static std::map< std::string, UTF8 > * ParseOptions(const std::string &aOptionsList)
Parses aOptionsList and places the result into a #PROPERTIES object which is returned.
static UTF8 FormatOptions(const std::map< std::string, UTF8 > *aProperties)
Returns a list of options from the aProperties parameter.
std::map< wxString, LIB_TABLE_ROWS_ITER > m_rowsMap
this is a non-owning index into the LIB_TABLE_ROWS table
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library table.
int m_version
Versioning to handle importing old tables.
bool InsertRow(LIB_TABLE_ROW *aRow, bool doReplace=false)
Adds aRow if it does not already exist or if doReplace is true.
void TransferRows(LIB_TABLE_ROWS &aRowsList)
Takes ownership of another list of rows; the original list will be freed.
LIB_TABLE_ROWS m_rows
Owning set of rows.
virtual ~LIB_TABLE()
bool doInsertRow(LIB_TABLE_ROW *aRow, bool doReplace=false)
Performs the mechanics of inserting a row, but without locking or reindexing.
bool migrate()
Updates the env vars from older version of KiCad, provided they do not currently resolve to anything.
void Load(const wxString &aFileName)
Load the library table using the path defined by aFileName aFallBackTable.
bool HasLibraryWithPath(const wxString &aPath) const
Test for the existence of aPath in the library table.
virtual void Format(OUTPUTFORMATTER *aOutput, int aIndentLevel) const =0
Generate the table in s-expression format to aOutput with an indentation level of aIndentLevel.
bool RemoveRow(const LIB_TABLE_ROW *aRow)
Removes a row from the table and frees the pointer.
LIB_TABLE(LIB_TABLE *aFallBackTable=nullptr, std::unique_ptr< LIB_TABLE_IO > aTableIo=nullptr)
Build a library table by pre-pending this table fragment in front of aFallBackTable.
LIB_TABLE * m_fallBack
wxString GetFullURI(const wxString &aLibNickname, bool aExpandEnvVars=true) const
Return the full URI of the library mapped to aLibNickname.
bool IsEmpty(bool aIncludeFallback=true)
Return true if the table is empty.
virtual void Parse(LIB_TABLE_LEXER *aLexer)=0
Parse the #LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW objec...
std::unique_ptr< LIB_TABLE_IO > m_io
const LIB_TABLE_ROW * FindRowByURI(const wxString &aURI)
bool ChangeRowOrder(size_t aIndex, int aOffset)
Moves a row within the table.
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
LIB_TABLE_ROW * findRow(const wxString &aNickname, bool aCheckIfEnabled=false) const
Return a LIB_TABLE_ROW if aNickname is found in this table or in any chained fallBack table fragment,...
An interface used to output 8 bit text in a convenient way.
Definition: richio.h:322
std::string Quotew(const wxString &aWrapee) const
Definition: richio.cpp:545
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition: richio.cpp:460
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition: utf8.h:72
std::string::const_iterator begin() const
Definition: utf8.h:197
std::string::size_type size() const
Definition: utf8.h:111
std::string::const_iterator end() const
Definition: utf8.h:198
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:351
The common library.
#define OPT_SEP
options separator character
#define _(s)
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
LIB_TABLE_ROW * new_clone(const LIB_TABLE_ROW &aRow)
Allows boost pointer containers to make clones of the data stored in them.
boost::ptr_vector< LIB_TABLE_ROW > LIB_TABLE_ROWS
LIB_TABLE_ROWS::iterator LIB_TABLE_ROWS_ITER
This file contains miscellaneous commonly used macros and functions.
STL namespace.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:398