You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

641 lines
16 KiB

  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 <dick@softplc.com>
  5. * Copyright (C) 2012 Wayne Stambaugh <stambaughw@gmail.com>
  6. * Copyright (C) 2012-2022 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. #include <wx/debug.h>
  26. #include <wx/filename.h>
  27. #include <set>
  28. #include <common.h>
  29. #include <kiface_base.h>
  30. #include <lib_table_base.h>
  31. #include <lib_table_lexer.h>
  32. #include <macros.h>
  33. #include <string_utils.h>
  34. #define OPT_SEP '|' ///< options separator character
  35. using namespace LIB_TABLE_T;
  36. std::unique_ptr<LINE_READER> FILE_LIB_TABLE_IO::GetReader( const wxString& aURI ) const
  37. {
  38. const wxFileName fn( aURI );
  39. if( !fn.IsOk() || !fn.IsFileReadable() )
  40. return nullptr;
  41. return std::make_unique<FILE_LINE_READER>( aURI );
  42. }
  43. bool FILE_LIB_TABLE_IO::CanSaveToUri( const wxString& aURI ) const
  44. {
  45. const wxFileName fn( aURI );
  46. if( !fn.IsOk() )
  47. return false;
  48. return fn.IsFileWritable();
  49. }
  50. bool FILE_LIB_TABLE_IO::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 ) const
  51. {
  52. // Avoid comparing filenames as wxURIs
  53. if( aURI1.Find( "://" ) != wxNOT_FOUND )
  54. {
  55. // found as full path
  56. return aURI1 == aURI2;
  57. }
  58. else
  59. {
  60. const wxFileName fn1( aURI1 );
  61. const wxFileName fn2( aURI2 );
  62. // This will also test if the file is a symlink so if we are comparing
  63. // a symlink to the same real file, the comparison will be true. See
  64. // wxFileName::SameAs() in the wxWidgets source.
  65. // found as full path and file name
  66. return fn1 == fn2;
  67. }
  68. }
  69. std::unique_ptr<OUTPUTFORMATTER> FILE_LIB_TABLE_IO::GetWriter( const wxString& aURI ) const
  70. {
  71. const wxFileName fn( aURI );
  72. return std::make_unique<FILE_OUTPUTFORMATTER>( aURI );
  73. }
  74. LIB_TABLE_ROW* new_clone( const LIB_TABLE_ROW& aRow )
  75. {
  76. return aRow.clone();
  77. }
  78. void LIB_TABLE_ROW::setProperties( std::map<std::string, UTF8>* aProperties )
  79. {
  80. properties.reset( aProperties );
  81. }
  82. void LIB_TABLE_ROW::SetFullURI( const wxString& aFullURI )
  83. {
  84. uri_user = aFullURI;
  85. }
  86. const wxString LIB_TABLE_ROW::GetFullURI( bool aSubstituted ) const
  87. {
  88. if( aSubstituted )
  89. {
  90. return ExpandEnvVarSubstitutions( uri_user, nullptr );
  91. }
  92. return uri_user;
  93. }
  94. void LIB_TABLE_ROW::Format( OUTPUTFORMATTER* out, int nestLevel ) const
  95. {
  96. // In Kicad, we save path and file names using the Unix notation (separator = '/')
  97. // So ensure separator is always '/' is saved URI string
  98. wxString uri = GetFullURI();
  99. uri.Replace( '\\', '/' );
  100. wxString extraOptions;
  101. if( !GetIsEnabled() )
  102. extraOptions += "(disabled)";
  103. if( !GetIsVisible() )
  104. extraOptions += "(hidden)";
  105. out->Print( nestLevel, "(lib (name %s)(type %s)(uri %s)(options %s)(descr %s)%s)\n",
  106. out->Quotew( GetNickName() ).c_str(),
  107. out->Quotew( GetType() ).c_str(),
  108. out->Quotew( uri ).c_str(),
  109. out->Quotew( GetOptions() ).c_str(),
  110. out->Quotew( GetDescr() ).c_str(),
  111. extraOptions.ToStdString().c_str() );
  112. }
  113. bool LIB_TABLE_ROW::operator==( const LIB_TABLE_ROW& r ) const
  114. {
  115. return nickName == r.nickName
  116. && uri_user == r.uri_user
  117. && options == r.options
  118. && description == r.description
  119. && enabled == r.enabled
  120. && visible == r.visible;
  121. }
  122. void LIB_TABLE_ROW::SetOptions( const wxString& aOptions )
  123. {
  124. options = aOptions;
  125. // set PROPERTIES* from options
  126. setProperties( LIB_TABLE::ParseOptions( TO_UTF8( aOptions ) ) );
  127. }
  128. LIB_TABLE::LIB_TABLE( LIB_TABLE* aFallBackTable, std::unique_ptr<LIB_TABLE_IO> aTableIo ) :
  129. m_io( std::move( aTableIo ) ), m_fallBack( aFallBackTable ), m_version( 0 )
  130. {
  131. // If not given, use the default file-based I/O.
  132. if( !m_io )
  133. {
  134. m_io = std::make_unique<FILE_LIB_TABLE_IO>();
  135. }
  136. // not copying fall back, simply search aFallBackTable separately
  137. // if "nickName not found".
  138. }
  139. LIB_TABLE::~LIB_TABLE()
  140. {
  141. // *fallBack is not owned here.
  142. }
  143. void LIB_TABLE::clear()
  144. {
  145. m_rows.clear();
  146. m_rowsMap.clear();
  147. }
  148. bool LIB_TABLE::IsEmpty( bool aIncludeFallback )
  149. {
  150. if( !aIncludeFallback || !m_fallBack )
  151. return m_rows.empty();
  152. return m_rows.empty() && m_fallBack->IsEmpty( true );
  153. }
  154. const wxString LIB_TABLE::GetDescription( const wxString& aNickname )
  155. {
  156. // Use "no exception" form of find row and ignore disabled flag.
  157. const LIB_TABLE_ROW* row = findRow( aNickname );
  158. if( row )
  159. return row->GetDescr();
  160. else
  161. return wxEmptyString;
  162. }
  163. bool LIB_TABLE::HasLibrary( const wxString& aNickname, bool aCheckEnabled ) const
  164. {
  165. const LIB_TABLE_ROW* row = findRow( aNickname, aCheckEnabled );
  166. if( row == nullptr )
  167. return false;
  168. return true;
  169. }
  170. bool LIB_TABLE::HasLibraryWithPath( const wxString& aPath ) const
  171. {
  172. for( const LIB_TABLE_ROW& row : m_rows )
  173. {
  174. if( row.GetFullURI() == aPath )
  175. return true;
  176. }
  177. return false;
  178. }
  179. wxString LIB_TABLE::GetFullURI( const wxString& aNickname, bool aExpandEnvVars ) const
  180. {
  181. const LIB_TABLE_ROW* row = findRow( aNickname, true );
  182. wxString retv;
  183. if( row )
  184. retv = row->GetFullURI( aExpandEnvVars );
  185. return retv;
  186. }
  187. LIB_TABLE_ROW* LIB_TABLE::findRow( const wxString& aNickName, bool aCheckIfEnabled ) const
  188. {
  189. LIB_TABLE_ROW* row = nullptr;
  190. LIB_TABLE* cur = (LIB_TABLE*) this;
  191. do
  192. {
  193. std::shared_lock<std::shared_mutex> lock( cur->m_mutex );
  194. if( cur->m_rowsMap.count( aNickName ) )
  195. row = &*cur->m_rowsMap.at( aNickName );
  196. if( row )
  197. {
  198. if( !aCheckIfEnabled || row->GetIsEnabled() )
  199. return row;
  200. else
  201. return nullptr; // We found it, but it's disabled
  202. }
  203. // Repeat, this time looking for names that were "fixed" by legacy versions because
  204. // the old eeschema file format didn't support spaces in tokens.
  205. for( const std::pair<const wxString, LIB_TABLE_ROWS_ITER>& entry : cur->m_rowsMap )
  206. {
  207. wxString legacyLibName = entry.first;
  208. legacyLibName.Replace( " ", "_" );
  209. if( legacyLibName == aNickName )
  210. {
  211. row = &*entry.second;
  212. if( !aCheckIfEnabled || row->GetIsEnabled() )
  213. return row;
  214. }
  215. }
  216. // not found, search fall back table(s), if any
  217. } while( ( cur = cur->m_fallBack ) != nullptr );
  218. return nullptr; // not found
  219. }
  220. const LIB_TABLE_ROW* LIB_TABLE::FindRowByURI( const wxString& aURI )
  221. {
  222. LIB_TABLE* cur = this;
  223. do
  224. {
  225. for( unsigned i = 0; i < cur->m_rows.size(); i++ )
  226. {
  227. const wxString tmp = cur->m_rows[i].GetFullURI( true );
  228. if( m_io->UrisAreEquivalent( tmp, aURI ) )
  229. return &cur->m_rows[i];
  230. }
  231. // not found, search fall back table(s), if any
  232. } while( ( cur = cur->m_fallBack ) != nullptr );
  233. return nullptr; // not found
  234. }
  235. std::vector<wxString> LIB_TABLE::GetLogicalLibs()
  236. {
  237. // Only return unique logical library names. Use std::set::insert() to quietly reject any
  238. // duplicates (usually due to encountering a duplicate nickname in a fallback table).
  239. std::set<wxString> unique;
  240. std::vector<wxString> ret;
  241. const LIB_TABLE* cur = this;
  242. do
  243. {
  244. for( const LIB_TABLE_ROW& row : cur->m_rows )
  245. {
  246. if( row.GetIsEnabled() )
  247. unique.insert( row.GetNickName() );
  248. }
  249. } while( ( cur = cur->m_fallBack ) != nullptr );
  250. ret.reserve( unique.size() );
  251. // return a sorted, unique set of nicknames in a std::vector<wxString> to caller
  252. for( std::set< wxString >::const_iterator it = unique.begin(); it!=unique.end(); ++it )
  253. ret.push_back( *it );
  254. // We want to allow case-sensitive duplicates but sort by case-insensitive ordering
  255. std::sort( ret.begin(), ret.end(),
  256. []( const wxString& lhs, const wxString& rhs )
  257. {
  258. return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
  259. } );
  260. return ret;
  261. }
  262. bool LIB_TABLE::InsertRow( LIB_TABLE_ROW* aRow, bool doReplace )
  263. {
  264. std::lock_guard<std::shared_mutex> lock( m_mutex );
  265. doInsertRow( aRow, doReplace );
  266. reindex();
  267. return true;
  268. }
  269. bool LIB_TABLE::doInsertRow( LIB_TABLE_ROW* aRow, bool doReplace )
  270. {
  271. auto it = m_rowsMap.find( aRow->GetNickName() );
  272. if( it != m_rowsMap.end() )
  273. {
  274. if( !doReplace )
  275. return false;
  276. m_rows.replace( it->second, aRow );
  277. }
  278. else
  279. {
  280. m_rows.push_back( aRow );
  281. }
  282. aRow->SetParent( this );
  283. reindex();
  284. return true;
  285. }
  286. bool LIB_TABLE::RemoveRow( const LIB_TABLE_ROW* aRow )
  287. {
  288. std::lock_guard<std::shared_mutex> lock( m_mutex );
  289. bool found = false;
  290. auto it = m_rowsMap.find( aRow->GetNickName() );
  291. if( it != m_rowsMap.end() )
  292. {
  293. if( &*it->second == aRow )
  294. {
  295. found = true;
  296. m_rows.erase( it->second );
  297. }
  298. }
  299. if( !found )
  300. {
  301. // Bookkeeping got messed up...
  302. for( int i = (int)m_rows.size() - 1; i >= 0; --i )
  303. {
  304. if( &m_rows[i] == aRow )
  305. {
  306. m_rows.erase( m_rows.begin() + i );
  307. found = true;
  308. break;
  309. }
  310. }
  311. }
  312. if( found )
  313. reindex();
  314. return found;
  315. }
  316. bool LIB_TABLE::ReplaceRow( size_t aIndex, LIB_TABLE_ROW* aRow )
  317. {
  318. std::lock_guard<std::shared_mutex> lock( m_mutex );
  319. if( aIndex >= m_rows.size() )
  320. return false;
  321. m_rowsMap.erase( m_rows[aIndex].GetNickName() );
  322. m_rows.replace( aIndex, aRow );
  323. reindex();
  324. return true;
  325. }
  326. bool LIB_TABLE::ChangeRowOrder( size_t aIndex, int aOffset )
  327. {
  328. std::lock_guard<std::shared_mutex> lock( m_mutex );
  329. if( aIndex >= m_rows.size() )
  330. return false;
  331. int newPos = static_cast<int>( aIndex ) + aOffset;
  332. if( newPos < 0 || newPos > static_cast<int>( m_rows.size() ) - 1 )
  333. return false;
  334. auto element = m_rows.release( m_rows.begin() + aIndex );
  335. m_rows.insert( m_rows.begin() + newPos, element.release() );
  336. reindex();
  337. return true;
  338. }
  339. void LIB_TABLE::TransferRows( LIB_TABLE_ROWS& aRowsList )
  340. {
  341. std::lock_guard<std::shared_mutex> lock( m_mutex );
  342. clear();
  343. m_rows.transfer( m_rows.end(), aRowsList.begin(), aRowsList.end(), aRowsList );
  344. reindex();
  345. }
  346. void LIB_TABLE::reindex()
  347. {
  348. m_rowsMap.clear();
  349. for( LIB_TABLE_ROWS_ITER it = m_rows.begin(); it != m_rows.end(); ++it )
  350. {
  351. it->SetParent( this );
  352. m_rowsMap[it->GetNickName()] = it;
  353. }
  354. }
  355. bool LIB_TABLE::migrate()
  356. {
  357. bool table_updated = false;
  358. for( LIB_TABLE_ROW& row : m_rows )
  359. {
  360. bool row_updated = false;
  361. wxString uri = row.GetFullURI( true );
  362. // If the uri still has a variable in it, that means that the user does not have
  363. // these vars defined. We update the old vars to the KICAD7 versions on load
  364. row_updated |= ( uri.Replace( wxS( "${KICAD5_" ), wxS( "${KICAD7_" ), false ) > 0 );
  365. row_updated |= ( uri.Replace( wxS( "${KICAD6_" ), wxS( "${KICAD7_" ), false ) > 0 );
  366. if( row_updated )
  367. {
  368. row.SetFullURI( uri );
  369. table_updated = true;
  370. }
  371. }
  372. return table_updated;
  373. }
  374. void LIB_TABLE::Load( const wxString& aFileName )
  375. {
  376. std::lock_guard<std::shared_mutex> lock( m_mutex );
  377. clear();
  378. std::unique_ptr<LINE_READER> reader = m_io->GetReader( aFileName );
  379. // It's OK if footprint library tables are missing.
  380. if( reader )
  381. {
  382. LIB_TABLE_LEXER lexer( reader.get() );
  383. Parse( &lexer );
  384. if( m_version != 7 && migrate() && m_io->CanSaveToUri( aFileName ) )
  385. Save( aFileName );
  386. reindex();
  387. }
  388. }
  389. void LIB_TABLE::Save( const wxString& aFileName ) const
  390. {
  391. std::unique_ptr<OUTPUTFORMATTER> sf = m_io->GetWriter( aFileName );
  392. if( !sf )
  393. {
  394. THROW_IO_ERROR( wxString::Format( _( "Failed to get writer for %s" ), aFileName ) );
  395. }
  396. // Force the lib table version to 7 before saving
  397. m_version = 7;
  398. Format( sf.get(), 0 );
  399. }
  400. std::map<std::string, UTF8>* LIB_TABLE::ParseOptions( const std::string& aOptionsList )
  401. {
  402. if( aOptionsList.size() )
  403. {
  404. const char* cp = &aOptionsList[0];
  405. const char* end = cp + aOptionsList.size();
  406. std::map<std::string, UTF8> props;
  407. std::string pair;
  408. // Parse all name=value pairs
  409. while( cp < end )
  410. {
  411. pair.clear();
  412. // Skip leading white space.
  413. while( cp < end && isspace( *cp ) )
  414. ++cp;
  415. // Find the end of pair/field
  416. while( cp < end )
  417. {
  418. if( *cp == '\\' && cp + 1 < end && cp[1] == OPT_SEP )
  419. {
  420. ++cp; // skip the escape
  421. pair += *cp++; // add the separator
  422. }
  423. else if( *cp == OPT_SEP )
  424. {
  425. ++cp; // skip the separator
  426. break; // process the pair
  427. }
  428. else
  429. {
  430. pair += *cp++;
  431. }
  432. }
  433. // stash the pair
  434. if( pair.size() )
  435. {
  436. // first equals sign separates 'name' and 'value'.
  437. size_t eqNdx = pair.find( '=' );
  438. if( eqNdx != pair.npos )
  439. {
  440. std::string name = pair.substr( 0, eqNdx );
  441. std::string value = pair.substr( eqNdx + 1 );
  442. props[name] = value;
  443. }
  444. else
  445. {
  446. props[pair] = ""; // property is present, but with no value.
  447. }
  448. }
  449. }
  450. if( props.size() )
  451. return new std::map<std::string, UTF8>( props );
  452. }
  453. return nullptr;
  454. }
  455. UTF8 LIB_TABLE::FormatOptions( const std::map<std::string, UTF8>* aProperties )
  456. {
  457. UTF8 ret;
  458. if( aProperties )
  459. {
  460. for( std::map<std::string, UTF8>::const_iterator it = aProperties->begin(); it != aProperties->end(); ++it )
  461. {
  462. const std::string& name = it->first;
  463. const UTF8& value = it->second;
  464. if( ret.size() )
  465. ret += OPT_SEP;
  466. ret += name;
  467. // the separation between name and value is '='
  468. if( value.size() )
  469. {
  470. ret += '=';
  471. for( std::string::const_iterator si = value.begin(); si != value.end(); ++si )
  472. {
  473. // escape any separator in the value.
  474. if( *si == OPT_SEP )
  475. ret += '\\';
  476. ret += *si;
  477. }
  478. }
  479. }
  480. }
  481. return ret;
  482. }