KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_template.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) 2012 Brian Sidebotham <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU 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
22#include <wx/bitmap.h>
23#include <wx/dir.h>
24#include <wx/ffile.h>
25#include <wx/txtstrm.h>
26#include <wx/wfstream.h>
27#include <wx/log.h>
28#include <wx/textfile.h>
29#include <unordered_map>
30
31#include <kiplatform/io.h>
33#include <wx_filename.h>
34#include "project_template.h"
35
36
37#define SEP wxFileName::GetPathSeparator()
38
39
40PROJECT_TEMPLATE::PROJECT_TEMPLATE( const wxString& aPath )
41{
42 m_basePath = wxFileName::DirName( aPath );
43 m_metaPath = wxFileName::DirName( aPath + SEP + METADIR );
44 m_metaHtmlFile = wxFileName::FileName( aPath + SEP + METADIR + SEP + METAFILE_INFO_HTML );
45 m_metaIconFile = wxFileName::FileName( aPath + SEP + METADIR + SEP + METAFILE_ICON );
46
47 m_title = wxEmptyString;
48
49 // Test the project template requirements to make sure aPath is a valid template structure.
50 if( !wxFileName::DirExists( m_basePath.GetPath() ) )
51 {
52 // Error, the path doesn't exist!
53 m_error.Printf( _( "Could not open the template path '%s'" ), aPath );
54 }
55 else if( !wxFileName::DirExists( m_metaPath.GetPath() ) )
56 {
57 // Error, the meta information directory doesn't exist!
58 m_error.Printf( _( "Could not find the expected 'meta' directory at '%s'" ), m_metaPath.GetPath() );
59 }
60 else if( !wxFileName::FileExists( m_metaHtmlFile.GetFullPath() ) )
61 {
62 // Error, the meta information directory doesn't contain the informational html file!
63 m_error.Printf( _( "Could not find the expected meta HTML file at '%s'" ), m_metaHtmlFile.GetFullPath() );
64 }
65
66 if( m_title.IsEmpty() )
68
69 // Try to load an icon
70 if( !wxFileName::FileExists( m_metaIconFile.GetFullPath() ) )
71 m_metaIcon = &wxNullBitmap;
72 else
73 m_metaIcon = new wxBitmap( m_metaIconFile.GetFullPath(), wxBITMAP_TYPE_PNG );
74}
75
76
77class FILE_TRAVERSER : public wxDirTraverser
78{
79public:
80 FILE_TRAVERSER( std::vector<wxFileName>& files, const wxString& exclude ) :
81 m_files( files ),
82 m_exclude( exclude )
83 {
84 }
85
86 virtual wxDirTraverseResult OnFile( const wxString& filename ) override
87 {
88 wxFileName fn( filename );
89 wxString path( fn.GetPathWithSep() );
90
92
93 if( IsIgnored( path, fn.GetFullName(), false ) )
94 return wxDIR_CONTINUE;
95
96 bool exclude = fn.GetName().Contains( "fp-info-cache" )
97 || fn.GetName().StartsWith( FILEEXT::LockFilePrefix );
98
99 if( !exclude )
100 m_files.emplace_back( wxFileName( filename ) );
101
102 return wxDIR_CONTINUE;
103 }
104
105 virtual wxDirTraverseResult OnDir( const wxString& dirname ) override
106 {
107 wxFileName dir( dirname );
108 wxString parent = dir.GetPathWithSep();
109
110 EnsureGitFiles( parent );
111
112 if( dir.GetFullName() == wxT( ".git" ) || IsIgnored( parent, dir.GetFullName(), true )
113 || dirname.StartsWith( m_exclude ) || dirname.EndsWith( "-backups" ) )
114 {
115 return wxDIR_IGNORE;
116 }
117
118 m_files.emplace_back( wxFileName::DirName( dirname ) );
119 EnsureGitFiles( dirname + wxFileName::GetPathSeparator() );
120 return wxDIR_CONTINUE;
121 }
122
123private:
124 void EnsureGitFiles( const wxString& path )
125 {
126 if( m_gitIgnores.find( path ) != m_gitIgnores.end() )
127 return;
128
129 wxString gitignore = path + wxT( ".gitignore" );
130
131 if( wxFileExists( gitignore ) )
132 {
133 wxFileInputStream input( gitignore );
134 wxTextInputStream text( input, wxT( "\x9" ), wxConvUTF8 );
135
136 while( input.IsOk() && !input.Eof() )
137 {
138 wxString line = text.ReadLine();
139
140 line.Trim().Trim( false );
141
142 if( line.IsEmpty() || line.StartsWith( wxT( "#" ) ) )
143 continue;
144
145 m_gitIgnores[path].push_back( line );
146 }
147
148 m_files.emplace_back( wxFileName( gitignore ) );
149 }
150 else
151 {
152 m_gitIgnores[path] = {};
153 }
154
155 wxString gitattributes = path + wxT( ".gitattributes" );
156
157 if( wxFileExists( gitattributes ) )
158 m_files.emplace_back( wxFileName( gitattributes ) );
159 }
160
161 bool IsIgnored( const wxString& path, const wxString& name, bool isDir )
162 {
163 auto it = m_gitIgnores.find( path );
164
165 if( it == m_gitIgnores.end() )
166 return false;
167
168 for( const wxString& pattern : it->second )
169 {
170 bool dirOnly = pattern.EndsWith( wxT( "/" ) );
171 wxString pat = dirOnly ? pattern.substr( 0, pattern.length() - 1 ) : pattern;
172
173 if( dirOnly && !isDir )
174 continue;
175
176 if( wxMatchWild( pat, name ) )
177 return true;
178 }
179
180 return false;
181 }
182
183 std::vector<wxFileName>& m_files;
184 wxString m_exclude;
185 std::unordered_map<wxString, std::vector<wxString>> m_gitIgnores;
186};
187
188
189std::vector<wxFileName> PROJECT_TEMPLATE::GetFileList()
190{
191 std::vector<wxFileName> files;
192 FILE_TRAVERSER sink( files, m_metaPath.GetPath() );
193 wxDir dir( m_basePath.GetPath() );
194
195 dir.Traverse( sink, wxEmptyString, ( wxDIR_FILES | wxDIR_DIRS ) );
196 return files;
197}
198
199
201{
202 return m_basePath.GetDirs()[m_basePath.GetDirCount() - 1];
203}
204
205
209
210
212{
213 return m_metaHtmlFile;
214}
215
216
218{
219 return m_metaIcon;
220}
221
222
223size_t PROJECT_TEMPLATE::GetDestinationFiles( const wxFileName& aNewProjectPath, std::vector<wxFileName>& aDestFiles )
224{
225 std::vector<wxFileName> srcFiles = GetFileList();
226
227 // Find the template file name base. this is the name of the .pro template file
228 wxString basename;
229 bool multipleProjectFilesFound = false;
230
231 for( wxFileName& file : srcFiles )
232 {
233 if( file.GetExt() == FILEEXT::ProjectFileExtension || file.GetExt() == FILEEXT::LegacyProjectFileExtension )
234 {
235 if( !basename.IsEmpty() && basename != file.GetName() )
236 multipleProjectFilesFound = true;
237
238 basename = file.GetName();
239 }
240 }
241
242 if( multipleProjectFilesFound )
243 basename = GetPrjDirName();
244
245 for( wxFileName& srcFile : srcFiles )
246 {
247 // Replace the template path
248 wxFileName destFile = srcFile;
249
250 // Replace the template filename with the project filename for the new project creation
251 wxString name = destFile.GetName();
252 name.Replace( basename, aNewProjectPath.GetName() );
253 destFile.SetName( name );
254
255 // Replace the template path with the project path, also renaming any subdirectories
256 // that contain the template basename.
257 wxString path = destFile.GetPathWithSep();
258 path.Replace( m_basePath.GetPathWithSep(), aNewProjectPath.GetPathWithSep() );
259 path.Replace( SEP + basename + SEP, SEP + aNewProjectPath.GetName() + SEP );
260 path.Replace( SEP + basename + wxS( "-" ), SEP + aNewProjectPath.GetName() + wxS( "-" ) );
261 destFile.SetPath( path );
262
263 aDestFiles.push_back( destFile );
264 }
265
266 return aDestFiles.size();
267}
268
269
270bool PROJECT_TEMPLATE::CreateProject( wxFileName& aNewProjectPath, wxString* aErrorMsg )
271{
272 // CreateProject copy the files from template to the new project folder and renames files
273 // which have the same name as the template .kicad_pro file
274 bool result = true;
275
276 std::vector<wxFileName> srcFiles = GetFileList();
277
278 // Find the template file name base. this is the name of the .kicad_pro (or .pro) template
279 // file
280 wxString basename;
281 bool multipleProjectFilesFound = false;
282
283 for( wxFileName& file : srcFiles )
284 {
285 if( file.GetExt() == FILEEXT::ProjectFileExtension || file.GetExt() == FILEEXT::LegacyProjectFileExtension )
286 {
287 if( !basename.IsEmpty() && basename != file.GetName() )
288 multipleProjectFilesFound = true;
289
290 basename = file.GetName();
291 }
292 }
293
294 if( multipleProjectFilesFound )
295 basename = GetPrjDirName();
296
297 for( wxFileName& srcFile : srcFiles )
298 {
299 // Replace the template path
300 wxFileName destFile = srcFile;
301
302 // Replace the template filename with the project filename for the new project creation
303 wxString currname = destFile.GetName();
304
305 if( destFile.GetExt() == FILEEXT::DrawingSheetFileExtension )
306 {
307 // Don't rename drawing sheet definitions; they're often shared
308 }
309 else if( destFile.GetName().EndsWith( "-cache" ) || destFile.GetName().EndsWith( "-rescue" ) )
310 {
311 currname.Replace( basename, aNewProjectPath.GetName() );
312 }
313 else if( destFile.GetExt() == FILEEXT::LegacySymbolDocumentFileExtension
314 || destFile.GetExt() == FILEEXT::LegacySymbolLibFileExtension
315 // Footprint libraries are directories not files, so GetExt() won't work
316 || destFile.GetPath().EndsWith( '.' + FILEEXT::KiCadFootprintLibPathExtension ) )
317 {
318 // Don't rename project-specific libraries. This will break the library tables and
319 // cause broken links in the schematic/pcb.
320 }
321 else
322 {
323 currname.Replace( basename, aNewProjectPath.GetName() );
324 }
325
326 destFile.SetName( currname );
327
328 // Replace the template path with the project path for the new project creation,
329 // also renaming any subdirectories that contain the template basename.
330 wxString destpath = destFile.GetPathWithSep();
331 destpath.Replace( m_basePath.GetPathWithSep(), aNewProjectPath.GetPathWithSep() );
332 destpath.Replace( SEP + basename + SEP, SEP + aNewProjectPath.GetName() + SEP );
333 destpath.Replace( SEP + basename + wxS( "-" ), SEP + aNewProjectPath.GetName() + wxS( "-" ) );
334
335 // Check to see if the path already exists, if not attempt to create it here.
336 if( !wxFileName::DirExists( destpath ) )
337 {
338 if( !wxFileName::Mkdir( destpath, 0777, wxPATH_MKDIR_FULL ) )
339 {
340 if( aErrorMsg )
341 {
342 if( !aErrorMsg->empty() )
343 *aErrorMsg += "\n";
344
345 wxString msg;
346
347 msg.Printf( _( "Cannot create folder '%s'." ), destpath );
348 *aErrorMsg += msg;
349 }
350
351 continue;
352 }
353 }
354
355 destFile.SetPath( destpath );
356
357 if( srcFile.FileExists() )
358 {
359 if( !wxCopyFile( srcFile.GetFullPath(), destFile.GetFullPath() ) )
360 {
361 if( aErrorMsg )
362 {
363 if( !aErrorMsg->empty() )
364 *aErrorMsg += "\n";
365
366 wxString msg;
367
368 msg.Printf( _( "Cannot copy file '%s'." ), destFile.GetFullPath() );
369 *aErrorMsg += msg;
370 }
371
372 result = false;
373 }
374 else
375 {
376 KIPLATFORM::IO::MakeWriteable( destFile.GetFullPath() );
377 }
378 }
379 }
380
381 return result;
382}
383
384
386{
387 if( !GetHtmlFile().IsFileReadable() )
388 return &m_title;
389
390 if( m_title == wxEmptyString )
391 {
392 wxFFileInputStream input( GetHtmlFile().GetFullPath() );
393 wxString separator( wxT( "\x9" ) );
394 wxTextInputStream text( input, separator, wxConvUTF8 );
395
396 int start = 0;
397 int finish = 0;
398 bool done = false;
399 bool hasStart = false;
400
401 while( input.IsOk() && !input.Eof() && !done )
402 {
403 wxString line = text.ReadLine();
404 wxString upperline = line.Clone().Upper();
405
406 start = upperline.Find( wxT( "<TITLE>" ) );
407 finish = upperline.Find( wxT( "</TITLE>" ) );
408 int length = finish - start - 7;
409
410 // find the opening tag
411 if( start != wxNOT_FOUND )
412 {
413 if( finish != wxNOT_FOUND )
414 {
415 m_title = line( start + 7, length );
416 done = true;
417 }
418 else
419 {
420 m_title = line.Mid( start + 7 );
421 hasStart = true;
422 }
423 }
424 else
425 {
426 if( finish != wxNOT_FOUND )
427 {
428 m_title += line.SubString( 0, finish - 1 );
429 done = true;
430 }
431 else if( hasStart )
432 m_title += line;
433 }
434 }
435
436 // Remove line endings
437 m_title.Replace( wxT( "\r" ), wxT( "" ) );
438 m_title.Replace( wxT( "\n" ), wxT( "" ) );
439
440 m_title.Trim( false ); // Trim from left
441 m_title.Trim(); // Trim from right
442 }
443
444 return &m_title;
445}
446
447
448wxFileName EnsureDefaultProjectTemplate( const wxString& aBaseDir )
449{
450 if( aBaseDir.IsEmpty() )
451 return wxFileName();
452
453 wxFileName templatePath;
454 templatePath.AssignDir( aBaseDir );
455 templatePath.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
456 templatePath.AppendDir( wxT( "default" ) );
457
458 if( !templatePath.DirExists() && !templatePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
459 return wxFileName();
460
461 wxFileName metaDir = templatePath;
462 metaDir.AppendDir( METADIR );
463
464 if( !metaDir.DirExists() && !metaDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
465 return wxFileName();
466
467 wxFileName infoFile = metaDir;
468 infoFile.SetFullName( METAFILE_INFO_HTML );
469
470 if( !infoFile.FileExists() )
471 {
472 wxFFile info( infoFile.GetFullPath(), wxT( "w" ) );
473
474 if( !info.IsOpened() )
475 return wxFileName();
476
477 info.Write( wxT( "<html><head><title>Default</title></head><body>"
478 "<h3>Default KiCad project template.</h3></body></html>" ) );
479 info.Close();
480 }
481
482 wxFileName proFile = templatePath;
483 proFile.SetFullName( wxT( "default.kicad_pro" ) );
484
485 if( !proFile.FileExists() )
486 {
487 wxFFile proj( proFile.GetFullPath(), wxT( "w" ) );
488
489 if( !proj.IsOpened() )
490 return wxFileName();
491
492 proj.Write( wxT( "{\n \"meta\": {\n \"version\": 1\n }\n}\n" ) );
493 proj.Close();
494 }
495
496 if( infoFile.FileExists() && proFile.FileExists() )
497 return templatePath;
498
499 return wxFileName();
500}
const char * name
virtual wxDirTraverseResult OnDir(const wxString &dirname) override
FILE_TRAVERSER(std::vector< wxFileName > &files, const wxString &exclude)
void EnsureGitFiles(const wxString &path)
virtual wxDirTraverseResult OnFile(const wxString &filename) override
std::unordered_map< wxString, std::vector< wxString > > m_gitIgnores
std::vector< wxFileName > & m_files
bool IsIgnored(const wxString &path, const wxString &name, bool isDir)
wxBitmap * GetIcon()
Get the 64px^2 icon for the project template.
size_t GetDestinationFiles(const wxFileName &aNewProjectPath, std::vector< wxFileName > &aDestFiles)
Fetch the list of destination files to be copied when the new project is created.
wxFileName m_metaHtmlFile
PROJECT_TEMPLATE(const wxString &aPath)
Create a new project instance from aPath.
std::vector< wxFileName > GetFileList()
Get a vector list of filenames for the template.
~PROJECT_TEMPLATE()
Non-virtual destructor (so no derived classes)
wxFileName GetHtmlFile()
Get the full Html filename for the project template.
wxString * GetTitle()
Get the title of the project (extracted from the html title tag)
bool CreateProject(wxFileName &aNewProjectPath, wxString *aErrorMsg=nullptr)
Copies and renames all template files to create a new project.
wxFileName m_metaIconFile
wxString GetPrjDirName()
Get the dir name of the project template (i.e.
#define _(s)
static const std::string ProjectFileExtension
static const std::string LegacyProjectFileExtension
static const std::string LegacySymbolLibFileExtension
static const std::string LockFilePrefix
static const std::string DrawingSheetFileExtension
static const std::string LegacySymbolDocumentFileExtension
static const std::string KiCadFootprintLibPathExtension
#define SEP()
bool MakeWriteable(const wxString &aFilePath)
Ensures that a file has write permissions.
Definition unix/io.cpp:78
wxFileName EnsureDefaultProjectTemplate(const wxString &aBaseDir)
Seed the built-in "default" project template under aBaseDir, creating the directory tree and minimal ...
#define METAFILE_ICON
An optional png icon, exactly 64px x 64px which is used in the template selector if present.
#define METADIR
A directory which contains information about the project template and does not get copied.
#define METAFILE_INFO_HTML
A required html formatted file which contains information about the project template.
std::string path
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35