KiCad PCB EDA Suite
filename_resolver.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) 2015-2020 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include <fstream>
25 #include <mutex>
26 #include <sstream>
27 
28 #include <wx/filename.h>
29 #include <wx/log.h>
30 #include <wx/msgdlg.h>
31 #include <pgm_base.h>
32 #include <trace_helpers.h>
33 
34 #include "common.h"
35 #include "filename_resolver.h"
36 
37 // configuration file version
38 #define CFGFILE_VERSION 1
39 #define RESOLVER_CONFIG wxT( "3Dresolver.cfg" )
40 
41 // flag bits used to track different one-off messages to users
42 #define ERRFLG_ALIAS (1)
43 #define ERRFLG_RELPATH (2)
44 #define ERRFLG_ENVPATH (4)
45 
46 #define MASK_3D_RESOLVER "3D_RESOLVER"
47 
48 static std::mutex mutex_resolver;
49 
50 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
51 
52 
54  m_pgm( nullptr ),
55  m_project( nullptr )
56 {
57  m_errflags = 0;
58 }
59 
60 
61 bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
62 {
63  if( aConfigDir.empty() )
64  return false;
65 
66  wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
67 
68  cfgdir.Normalize();
69 
70  if( !cfgdir.DirExists() )
71  return false;
72 
73  m_configDir = cfgdir.GetPath();
75 
76  return true;
77 }
78 
79 
80 bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
81 {
82  m_project = aProject;
83 
84  if( !aProject )
85  return false;
86 
87  wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
88 
89  projdir.Normalize();
90 
91  if( !projdir.DirExists() )
92  return false;
93 
94  m_curProjDir = projdir.GetPath();
95 
96  if( flgChanged )
97  *flgChanged = false;
98 
99  if( m_paths.empty() )
100  {
101  SEARCH_PATH al;
102  al.m_Alias = "${KIPRJMOD}";
103  al.m_Pathvar = "${KIPRJMOD}";
104  al.m_Pathexp = m_curProjDir;
105  m_paths.push_back( al );
106 
107  if( flgChanged )
108  *flgChanged = true;
109  }
110  else
111  {
112  if( m_paths.front().m_Pathexp.Cmp( m_curProjDir ) )
113  {
114  m_paths.front().m_Pathexp = m_curProjDir;
115 
116  if( flgChanged )
117  *flgChanged = true;
118  }
119  else
120  {
121  return true;
122  }
123  }
124 
125 #ifdef DEBUG
126  {
127  std::ostringstream ostr;
128  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
129  ostr << " * [INFO] changed project dir to ";
130  ostr << m_paths.front().m_Pathexp.ToUTF8();
131  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
132  }
133 #endif
134 
135  return true;
136 }
137 
138 
140 {
141  return m_curProjDir;
142 }
143 
144 
146 {
147  m_pgm = aBase;
148 
149  if( !m_pgm || m_paths.empty() )
150  return;
151 
152  // recreate the path list
153  m_paths.clear();
154  createPathList();
155 }
156 
157 
159 {
160  if( !m_paths.empty() )
161  return true;
162 
163  wxString kmod;
164 
165  // add an entry for the default search path; at this point
166  // we cannot set a sensible default so we use an empty string.
167  // the user may change this later with a call to SetProjectDir()
168 
169  SEARCH_PATH lpath;
170  lpath.m_Alias = "${KIPRJMOD}";
171  lpath.m_Pathvar = "${KIPRJMOD}";
172  lpath.m_Pathexp = m_curProjDir;
173  m_paths.push_back( lpath );
174  wxFileName fndummy;
175  wxUniChar psep = fndummy.GetPathSeparator();
176  std::list< wxString > epaths;
177 
178  if( GetKicadPaths( epaths ) )
179  {
180  for( const wxString& curr_path : epaths )
181  {
182  wxString pathVal = ExpandEnvVarSubstitutions( curr_path, m_project );
183 
184  if( pathVal.empty() )
185  {
186  lpath.m_Pathexp.clear();
187  }
188  else
189  {
190  fndummy.Assign( pathVal, "" );
191  fndummy.Normalize();
192  lpath.m_Pathexp = fndummy.GetFullPath();
193  }
194 
195  lpath.m_Alias = curr_path;
196  lpath.m_Pathvar = curr_path;
197 
198  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
199  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
200 
201  m_paths.push_back( lpath );
202  }
203  }
204 
205  if( !m_configDir.empty() )
206  readPathList();
207 
208  if( m_paths.empty() )
209  return false;
210 
211 #ifdef DEBUG
212  wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
213  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
214 
215  while( sPL != m_paths.end() )
216  {
217  wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_Alias.GetData(),
218  (*sPL).m_Pathexp.GetData() );
219  ++sPL;
220  }
221 #endif
222 
223  return true;
224 }
225 
226 
227 bool FILENAME_RESOLVER::UpdatePathList( const std::vector< SEARCH_PATH >& aPathList )
228 {
229  wxUniChar envMarker( '$' );
230 
231  while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
232  m_paths.pop_back();
233 
234  for( const SEARCH_PATH& path : aPathList )
235  addPath( path );
236 
237  return writePathList();
238 }
239 
240 
241 wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
242 {
243  std::lock_guard<std::mutex> lock( mutex_resolver );
244 
245  if( aFileName.empty() )
246  return wxEmptyString;
247 
248  if( m_paths.empty() )
249  createPathList();
250 
251  // first attempt to use the name as specified:
252  wxString tname = aFileName;
253 
254  #ifdef _WIN32
255  // translate from KiCad's internal UNIX-like path to MSWin paths
256  tname.Replace( wxT( "/" ), wxT( "\\" ) );
257  #endif
258 
259  // Note: variable expansion must be performed using a threadsafe
260  // wrapper for the getenv() system call. If we allow the
261  // wxFileName::Normalize() routine to perform expansion then
262  // we will have a race condition since wxWidgets does not assure
263  // a threadsafe wrapper for getenv().
264  tname = ExpandEnvVarSubstitutions( tname, m_project );
265 
266  wxFileName tmpFN( tname );
267 
268  // in the case of absolute filenames we don't store a map item
269  if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
270  && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
271  {
272  tmpFN.Normalize();
273 
274  if( tmpFN.FileExists() )
275  return tmpFN.GetFullPath();
276 
277  return wxEmptyString;
278  }
279 
280  // this case covers full paths, leading expanded vars, and paths
281  // relative to the current working directory (which is not necessarily
282  // the current project directory)
283  if( tmpFN.FileExists() )
284  {
285  tmpFN.Normalize();
286  tname = tmpFN.GetFullPath();
287 
288  // special case: if a path begins with ${ENV_VAR} but is not in the
289  // resolver's path list then add it.
290  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
291  checkEnvVarPath( aFileName );
292 
293  return tname;
294  }
295 
296  // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the
297  // file either does not exist or the ENV_VAR is not defined
298  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
299  {
300  if( !( m_errflags & ERRFLG_ENVPATH ) )
301  {
303  wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
304  errmsg.append( "\n" );
305  errmsg.append( tname );
306  errmsg.append( "\n" );
307  wxLogTrace( tracePathsAndFiles, errmsg );
308  }
309 
310  return wxEmptyString;
311  }
312 
313  // at this point aFileName is:
314  // a. an aliased shortened name or
315  // b. cannot be determined
316 
317  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
318  std::list< SEARCH_PATH >::const_iterator ePL = m_paths.end();
319 
320  // check the path relative to the current project directory;
321  // note: this is not necessarily the same as the current working
322  // directory, which has already been checked. This case accounts
323  // for partial paths which do not contain ${KIPRJMOD}.
324  // This check is performed before checking the path relative to
325  // ${KICAD6_3DMODEL_DIR} so that users can potentially override a model
326  // within ${KICAD6_3DMODEL_DIR}
327  if( !sPL->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
328  {
329  tmpFN.Assign( sPL->m_Pathexp, "" );
330  wxString fullPath = tmpFN.GetPathWithSep() + tname;
331 
332  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
333 
334  if( wxFileName::FileExists( fullPath ) )
335  {
336  tmpFN.Assign( fullPath );
337  tmpFN.Normalize();
338  tname = tmpFN.GetFullPath();
339  return tname;
340  }
341 
342  }
343 
344  // check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
345  if( !tname.StartsWith( ":" ) )
346  {
347  wxFileName fpath;
348  wxString fullPath( "${KICAD6_3DMODEL_DIR}" );
349  fullPath.Append( fpath.GetPathSeparator() );
350  fullPath.Append( tname );
351  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
352  fpath.Assign( fullPath );
353 
354  if( fpath.Normalize() && fpath.FileExists() )
355  {
356  tname = fpath.GetFullPath();
357  return tname;
358  }
359 
360  }
361 
362  // ${ENV_VAR} paths have already been checked; skip them
363  while( sPL != ePL && ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
364  ++sPL;
365 
366  // at this point the filename must contain an alias or else it is invalid
367  wxString alias; // the alias portion of the short filename
368  wxString relpath; // the path relative to the alias
369 
370  if( !SplitAlias( tname, alias, relpath ) )
371  {
372  if( !( m_errflags & ERRFLG_RELPATH ) )
373  {
374  // this can happen if the file was intended to be relative to
375  // ${KICAD6_3DMODEL_DIR} but ${KICAD6_3DMODEL_DIR} not set or incorrect.
377  wxString errmsg = "[3D File Resolver] No such path";
378  errmsg.append( "\n" );
379  errmsg.append( tname );
380  errmsg.append( "\n" );
381  wxLogTrace( tracePathsAndFiles, errmsg );
382  }
383 
384  return wxEmptyString;
385  }
386 
387  while( sPL != ePL )
388  {
389  if( !sPL->m_Alias.Cmp( alias ) && !sPL->m_Pathexp.empty() )
390  {
391  wxFileName fpath( wxFileName::DirName( sPL->m_Pathexp ) );
392  wxString fullPath = fpath.GetPathWithSep() + relpath;
393 
394  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
395 
396  if( wxFileName::FileExists( fullPath ) )
397  {
398  wxFileName tmp( fullPath );
399 
400  if( tmp.Normalize() )
401  tname = tmp.GetFullPath();
402 
403  return tname;
404  }
405  }
406 
407  ++sPL;
408  }
409 
410  if( !( m_errflags & ERRFLG_ALIAS ) )
411  {
413  wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
414  errmsg.append( "\n" );
415  errmsg.append( tname.substr( 1 ) );
416  errmsg.append( "\n" );
417  wxLogTrace( tracePathsAndFiles, errmsg );
418  }
419 
420  return wxEmptyString;
421 }
422 
423 
425 {
426  if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
427  return false;
428 
429  std::lock_guard<std::mutex> lock( mutex_resolver );
430 
431  SEARCH_PATH tpath = aPath;
432 
433  #ifdef _WIN32
434  while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
435  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
436  #else
437  while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
438  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
439  #endif
440 
441  wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
442 
443  path.Normalize();
444 
445  if( !path.DirExists() )
446  {
447  // suppress the message if the missing pathvar is the
448  // legacy KICAD6_3DMODEL_DIR variable
449  if( aPath.m_Pathvar.compare( wxT( "${KICAD6_3DMODEL_DIR}" ) ) )
450  {
451  wxString msg = _( "The given path does not exist" );
452  msg.append( wxT( "\n" ) );
453  msg.append( tpath.m_Pathvar );
454  wxMessageBox( msg, _( "3D model search path" ) );
455  }
456 
457  tpath.m_Pathexp.clear();
458  }
459  else
460  {
461  tpath.m_Pathexp = path.GetFullPath();
462 
463 #ifdef _WIN32
464  while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
465  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
466 #else
467  while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
468  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
469 #endif
470  }
471 
472  std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
473  std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
474 
475  while( sPL != ePL )
476  {
477  if( !tpath.m_Alias.Cmp( sPL->m_Alias ) )
478  {
479  wxString msg = _( "Alias: " );
480  msg.append( tpath.m_Alias );
481  msg.append( wxT( "\n" ) );
482  msg.append( _( "This path:" ) + wxS( " " ) );
483  msg.append( tpath.m_Pathvar );
484  msg.append( wxT( "\n" ) );
485  msg.append( _( "Existing path:" ) + wxS( " " ) );
486  msg.append( sPL->m_Pathvar );
487  wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
488 
489  return false;
490  }
491 
492  ++sPL;
493  }
494 
495  m_paths.push_back( tpath );
496  return true;
497 }
498 
499 
501 {
502  if( m_configDir.empty() )
503  {
504  std::ostringstream ostr;
505  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
506  wxString errmsg = "3D configuration directory is unknown";
507  ostr << " * " << errmsg.ToUTF8();
508  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
509  return false;
510  }
511 
512  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
513  cfgpath.Normalize();
514  wxString cfgname = cfgpath.GetFullPath();
515 
516  size_t nitems = m_paths.size();
517 
518  std::ifstream cfgFile;
519  std::string cfgLine;
520 
521  if( !wxFileName::Exists( cfgname ) )
522  {
523  std::ostringstream ostr;
524  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
525  wxString errmsg = "no 3D configuration file";
526  ostr << " * " << errmsg.ToUTF8() << " '";
527  ostr << cfgname.ToUTF8() << "'";
528  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
529  return false;
530  }
531 
532  cfgFile.open( cfgname.ToUTF8() );
533 
534  if( !cfgFile.is_open() )
535  {
536  std::ostringstream ostr;
537  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
538  wxString errmsg = "Could not open configuration file";
539  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
540  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
541  return false;
542  }
543 
544  int lineno = 0;
545  SEARCH_PATH al;
546  size_t idx;
547  int vnum = 0; // version number
548 
549  while( cfgFile.good() )
550  {
551  cfgLine.clear();
552  std::getline( cfgFile, cfgLine );
553  ++lineno;
554 
555  if( cfgLine.empty() )
556  {
557  if( cfgFile.eof() )
558  break;
559 
560  continue;
561  }
562 
563  if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
564  {
565  // extract the version number and parse accordingly
566  if( cfgLine.size() > 2 )
567  {
568  std::istringstream istr;
569  istr.str( cfgLine.substr( 2 ) );
570  istr >> vnum;
571  }
572 
573  continue;
574  }
575 
576  idx = 0;
577 
578  if( !getHollerith( cfgLine, idx, al.m_Alias ) )
579  continue;
580 
581  // never add on KICAD6_3DMODEL_DIR from a config file
582  if( !al.m_Alias.Cmp( wxT( "KICAD6_3DMODEL_DIR" ) ) )
583  continue;
584 
585  if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
586  continue;
587 
588  if( !getHollerith( cfgLine, idx, al.m_Description ) )
589  continue;
590 
591  addPath( al );
592  }
593 
594  cfgFile.close();
595 
596  if( vnum < CFGFILE_VERSION )
597  writePathList();
598 
599  return( m_paths.size() != nitems );
600 }
601 
602 
604 {
605  if( m_configDir.empty() )
606  {
607  std::ostringstream ostr;
608  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
609  wxString errmsg = _( "3D configuration directory is unknown" );
610  ostr << " * " << errmsg.ToUTF8();
611  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
612  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
613 
614  return false;
615  }
616 
617  // skip all ${ENV_VAR} alias names
618  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
619 
620  while( sPL != m_paths.end() &&
621  ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
622  ++sPL;
623 
624  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
625  wxString cfgname = cfgpath.GetFullPath();
626  std::ofstream cfgFile;
627 
628  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
629 
630  if( !cfgFile.is_open() )
631  {
632  std::ostringstream ostr;
633  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
634  wxString errmsg = _( "Could not open configuration file" );
635  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
636  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
637  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
638 
639  return false;
640  }
641 
642  cfgFile << "#V" << CFGFILE_VERSION << "\n";
643  std::string tstr;
644 
645  while( sPL != m_paths.end() )
646  {
647  tstr = sPL->m_Alias.ToUTF8();
648  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
649  tstr = sPL->m_Pathvar.ToUTF8();
650  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
651  tstr = sPL->m_Description.ToUTF8();
652  cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
653  ++sPL;
654  }
655 
656  bool bad = cfgFile.bad();
657  cfgFile.close();
658 
659  if( bad )
660  {
661  wxMessageBox( _( "Problems writing configuration file" ),
662  _( "Write 3D search path list" ) );
663 
664  return false;
665  }
666 
667  return true;
668 }
669 
670 
671 void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
672 {
673  bool useParen = false;
674 
675  if( aPath.StartsWith( "$(" ) )
676  useParen = true;
677  else if( !aPath.StartsWith( "${" ) )
678  return;
679 
680  size_t pEnd;
681 
682  if( useParen )
683  pEnd = aPath.find( ")" );
684  else
685  pEnd = aPath.find( "}" );
686 
687  if( pEnd == wxString::npos )
688  return;
689 
690  wxString envar = aPath.substr( 0, pEnd + 1 );
691 
692  // check if the alias exists; if not then add it to the end of the
693  // env var section of the path list
694  auto sPL = m_paths.begin();
695  auto ePL = m_paths.end();
696 
697  while( sPL != ePL )
698  {
699  if( sPL->m_Alias == envar )
700  return;
701 
702  if( !sPL->m_Alias.StartsWith( "${" ) )
703  break;
704 
705  ++sPL;
706  }
707 
708  SEARCH_PATH lpath;
709  lpath.m_Alias = envar;
710  lpath.m_Pathvar = lpath.m_Alias;
711  wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
712 
713  wxUniChar psep = tmpFN.GetPathSeparator();
714  tmpFN.Normalize();
715 
716  if( !tmpFN.DirExists() )
717  return;
718 
719  lpath.m_Pathexp = tmpFN.GetFullPath();
720 
721  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
722  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
723 
724  if( lpath.m_Pathexp.empty() )
725  return;
726 
727  m_paths.insert( sPL, lpath );
728 }
729 
730 
731 wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
732 {
733  wxString fname = aFullPathName;
734 
735  if( m_paths.empty() )
736  createPathList();
737 
738  std::lock_guard<std::mutex> lock( mutex_resolver );
739 
740  std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
741  size_t idx;
742 
743  while( sL != m_paths.end() )
744  {
745  // undefined paths do not participate in the
746  // file name shortening procedure
747  if( sL->m_Pathexp.empty() )
748  {
749  ++sL;
750  continue;
751  }
752 
753  wxFileName fpath;
754 
755  // in the case of aliases, ensure that we use the most recent definition
756  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
757  {
758  wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
759 
760  if( tpath.empty() )
761  {
762  ++sL;
763  continue;
764  }
765 
766  fpath.Assign( tpath, wxT( "" ) );
767  }
768  else
769  {
770  fpath.Assign( sL->m_Pathexp, wxT( "" ) );
771  }
772 
773  wxString fps = fpath.GetPathWithSep();
774  wxString tname;
775 
776  idx = fname.find( fps );
777 
778  if( idx == 0 )
779  {
780  fname = fname.substr( fps.size() );
781 
782  #ifdef _WIN32
783  // ensure only the '/' separator is used in the internal name
784  fname.Replace( wxT( "\\" ), wxT( "/" ) );
785  #endif
786 
787  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
788  {
789  // old style ENV_VAR
790  tname = sL->m_Alias;
791  tname.Append( "/" );
792  tname.append( fname );
793  }
794  else
795  {
796  // new style alias
797  tname = ":";
798  tname.append( sL->m_Alias );
799  tname.append( ":" );
800  tname.append( fname );
801  }
802 
803  return tname;
804  }
805 
806  ++sL;
807  }
808 
809 #ifdef _WIN32
810  // it is strange to convert an MSWin full path to use the
811  // UNIX separator but this is done for consistency and can
812  // be helpful even when transferring project files from
813  // MSWin to *NIX.
814  fname.Replace( wxT( "\\" ), wxT( "/" ) );
815 #endif
816 
817  return fname;
818 }
819 
820 
821 
822 const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths() const
823 {
824  return &m_paths;
825 }
826 
827 
828 bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
829  wxString& anAlias, wxString& aRelPath ) const
830 {
831  anAlias.clear();
832  aRelPath.clear();
833 
834  if( !aFileName.StartsWith( wxT( ":" ) ) )
835  return false;
836 
837  size_t tagpos = aFileName.find( wxT( ":" ), 1 );
838 
839  if( wxString::npos == tagpos || 1 == tagpos )
840  return false;
841 
842  if( tagpos + 1 >= aFileName.length() )
843  return false;
844 
845  anAlias = aFileName.substr( 1, tagpos - 1 );
846  aRelPath = aFileName.substr( tagpos + 1 );
847 
848  return true;
849 }
850 
851 
852 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
853 {
854  aResult.clear();
855 
856  if( aIndex >= aString.size() )
857  {
858  std::ostringstream ostr;
859  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
860  wxString errmsg = "bad Hollerith string on line";
861  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
862  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
863 
864  return false;
865  }
866 
867  size_t i2 = aString.find( '"', aIndex );
868 
869  if( std::string::npos == i2 )
870  {
871  std::ostringstream ostr;
872  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
873  wxString errmsg = "missing opening quote mark in config file";
874  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
875  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
876 
877  return false;
878  }
879 
880  ++i2;
881 
882  if( i2 >= aString.size() )
883  {
884  std::ostringstream ostr;
885  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
886  wxString errmsg = "invalid entry (unexpected end of line)";
887  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
888  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
889 
890  return false;
891  }
892 
893  std::string tnum;
894 
895  while( aString[i2] >= '0' && aString[i2] <= '9' )
896  tnum.append( 1, aString[i2++] );
897 
898  if( tnum.empty() || aString[i2++] != ':' )
899  {
900  std::ostringstream ostr;
901  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
902  wxString errmsg = "bad Hollerith string on line";
903  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
904  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
905 
906  return false;
907  }
908 
909  std::istringstream istr;
910  istr.str( tnum );
911  size_t nchars;
912  istr >> nchars;
913 
914  if( (i2 + nchars) >= aString.size() )
915  {
916  std::ostringstream ostr;
917  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
918  wxString errmsg = "invalid entry (unexpected end of line)";
919  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
920  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
921 
922  return false;
923  }
924 
925  if( nchars > 0 )
926  {
927  aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
928  i2 += nchars;
929  }
930 
931  if( i2 >= aString.size() || aString[i2] != '"' )
932  {
933  std::ostringstream ostr;
934  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
935  wxString errmsg = "missing closing quote mark in config file";
936  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
937  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
938 
939  return false;
940  }
941 
942  aIndex = i2 + 1;
943  return true;
944 }
945 
946 
947 bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias ) const
948 {
949  // Rules:
950  // 1. The generic form of an aliased 3D relative path is:
951  // ALIAS:relative/path
952  // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
953  // 3. The relative path must be a valid relative path for the platform
954  hasAlias = false;
955 
956  if( aFileName.empty() )
957  return false;
958 
959  wxString filename = aFileName;
960  size_t pos0 = aFileName.find( ':' );
961 
962  // ensure that the file separators suit the current platform
963  #ifdef __WINDOWS__
964  filename.Replace( wxT( "/" ), wxT( "\\" ) );
965 
966  // if we see the :\ pattern then it must be a drive designator
967  if( pos0 != wxString::npos )
968  {
969  size_t pos1 = filename.find( wxT( ":\\" ) );
970 
971  if( pos1 != wxString::npos && ( pos1 != pos0 || pos1 != 1 ) )
972  return false;
973 
974  // if we have a drive designator then we have no alias
975  if( pos1 != wxString::npos )
976  pos0 = wxString::npos;
977  }
978  #else
979  filename.Replace( wxT( "\\" ), wxT( "/" ) );
980  #endif
981 
982  // names may not end with ':'
983  if( pos0 == aFileName.length() -1 )
984  return false;
985 
986  if( pos0 != wxString::npos )
987  {
988  // ensure the alias component is not empty
989  if( pos0 == 0 )
990  return false;
991 
992  wxString lpath = filename.substr( 0, pos0 );
993 
994  // check the alias for restricted characters
995  if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
996  return false;
997 
998  hasAlias = true;
999  }
1000 
1001  return true;
1002 }
1003 
1004 
1005 bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths ) const
1006 {
1007  paths.clear();
1008 
1009  if( !m_pgm )
1010  return false;
1011 
1012  bool hasKisys3D = false;
1013 
1014 
1015  // iterate over the list of internally defined ENV VARs
1016  // and add them to the paths list
1019 
1020  while( mS != mE )
1021  {
1022  // filter out URLs, template directories, and known system paths
1023  if( mS->first == wxString( "KICAD_PTEMPLATES" )
1024  || mS->first == wxString( "KICAD6_FOOTPRINT_DIR" ) )
1025  {
1026  ++mS;
1027  continue;
1028  }
1029 
1030  if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
1031  {
1032  ++mS;
1033  continue;
1034  }
1035 
1036  wxString tmp( "${" );
1037  tmp.Append( mS->first );
1038  tmp.Append( "}" );
1039  paths.push_back( tmp );
1040 
1041  if( tmp == "${KICAD6_3DMODEL_DIR}" )
1042  hasKisys3D = true;
1043 
1044  ++mS;
1045  }
1046 
1047  if( !hasKisys3D )
1048  paths.emplace_back("${KICAD6_3DMODEL_DIR}" );
1049 
1050  return true;
1051 }
#define CFGFILE_VERSION
std::list< SEARCH_PATH > m_paths
Container for project specific data.
Definition: project.h:62
#define ERRFLG_ENVPATH
bool readPathList(void)
Read a list of path names from a configuration file.
Container for data for KiCad programs.
Definition: pgm_base.h:91
bool UpdatePathList(const std::vector< SEARCH_PATH > &aPathList)
Clear the current path list and substitutes the given path list and update the path configuration fil...
bool SplitAlias(const wxString &aFileName, wxString &anAlias, wxString &aRelPath) const
Return true if the given name contains an alias and populates the string anAlias with the alias and a...
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
bool writePathList(void)
Write the current path list to a configuration file.
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...
static bool getHollerith(const std::string &aString, size_t &aIndex, wxString &aResult)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:273
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:123
bool ValidateFileName(const wxString &aFileName, bool &hasAlias) const
Returns true if the given path is a valid aliased relative path.
wxString m_Alias
#define ERRFLG_ALIAS
bool addPath(const SEARCH_PATH &aPath)
Check that a path is valid and adds it to the search list.
wxString ShortenPath(const wxString &aFullPathName)
Produce a relative path based on the existing search directories or returns the same path if the path...
wxString m_Description
#define ERRFLG_RELPATH
wxString m_Pathvar
const std::list< SEARCH_PATH > * GetPaths() const
Return a pointer to the internal path list; the items in:load.
wxLogTrace helper definitions.
wxString ResolvePath(const wxString &aFileName)
Determines the full path of the given file name.
#define RESOLVER_CONFIG
see class PGM_BASE
static std::mutex mutex_resolver
#define _(s)
Definition: 3d_actions.cpp:33
bool SetProject(PROJECT *aProject, bool *flgChanged=nullptr)
Set the current KiCad project directory as the first entry in the model path list.
The common library.
bool Set3DConfigDir(const wxString &aConfigDir)
Set the user's configuration directory for 3D models.
bool GetKicadPaths(std::list< wxString > &paths) const
Return a list of path environment variables local to KiCad.
void checkEnvVarPath(const wxString &aPath)
Check the ${ENV_VAR} component of a path and adds it to the resolver's path list if it is not yet in ...
void SetProgramBase(PGM_BASE *aBase)
Set a pointer to the application's PGM_BASE instance used to extract the local env vars.
std::map< wxString, ENV_VAR_ITEM >::const_iterator ENV_VAR_MAP_CITER
wxString GetProjectDir() const
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition: pgm_base.cpp:645
#define MASK_3D_RESOLVER
wxString m_Pathexp