27#include <wx/mimetype.h>
29#include <wx/stdpaths.h>
38#include "wx/tokenzr.h"
43#include <wx/wfstream.h>
45#include <wx/zipstrm.h>
46#include <wx/tarstrm.h>
47#include <wx/zstream.h>
52#include <system_error>
53#include <unordered_set>
60 if( !
string.StartsWith( wxT(
"\"" ) ) )
62 string.Prepend ( wxT(
"\"" ) );
63 string.Append ( wxT(
"\"" ) );
77 if( wxFileExists( fullFileName ) )
82 wxFileName buildDir(
Pgm().GetExecutablePath(), shortname );
85 if( !buildDir.GetDirs().IsEmpty()
86 && buildDir.GetDirs().Last().Lower().EndsWith( wxT(
".app" ) ) )
88 buildDir.RemoveLastDir();
91 buildDir.RemoveLastDir();
93 buildDir.AppendDir( shortname );
95 buildDir.AppendDir( shortname.BeforeLast(
'.' ) );
98 if( buildDir.GetDirs().Last() ==
"pl_editor" )
100 buildDir.RemoveLastDir();
101 buildDir.AppendDir(
"pagelayout_editor" );
105 buildDir.AppendDir( shortname + wxT(
".app" ) );
106 buildDir.AppendDir( wxT(
"Contents" ) );
107 buildDir.AppendDir( wxT(
"MacOS" ) );
110 if( wxFileExists( buildDir.GetFullPath() ) )
111 return buildDir.GetFullPath();
116 if(
Pgm().IsKicadEnvVariableDefined() )
120 if( wxFileExists( fullFileName ) )
124#if defined( __WINDOWS__ )
131 const static wxChar* possibilities[] = {
132#if defined( __WXMAC__ )
134 wxT(
"Contents/Applications/pcbnew.app/Contents/MacOS/" ),
135 wxT(
"Contents/Applications/eeschema.app/Contents/MacOS/" ),
136 wxT(
"Contents/Applications/gerbview.app/Contents/MacOS/" ),
137 wxT(
"Contents/Applications/pcb_calculator.app/Contents/MacOS/" ),
138 wxT(
"Contents/Applications/pl_editor.app/Contents/MacOS/" ),
141 wxT(
"/usr/local/bin/" ),
142 wxT(
"/usr/local/kicad/bin/" ),
147 for(
unsigned i=0; i<
arrayDim(possibilities); ++i )
150 fullFileName = possibilities[i] + shortname;
156 if( wxFileExists( fullFileName ) )
166int ExecuteFile(
const wxString& aEditorName,
const wxString& aFileName, wxProcess* aCallback,
169 wxString fullEditorName;
170 std::vector<wxString> params;
174 bool inSingleQuotes =
false;
175 bool inDoubleQuotes =
false;
180 if( !param.IsEmpty() )
182 params.push_back( param );
187 for( wxUniChar ch : aEditorName )
194 inSingleQuotes =
false;
202 else if( inDoubleQuotes )
207 inDoubleQuotes =
false;
214 else if( ch ==
'\'' )
217 inSingleQuotes =
true;
222 inDoubleQuotes =
true;
239 fullEditorName = params[0];
241 params.erase( params.begin() );
247 fullEditorName = aEditorName;
250 if( wxFileExists( fullEditorName ) )
252 std::vector<const wchar_t*> args;
254 args.emplace_back( fullEditorName.wc_str() );
256 if( !params.empty() )
258 for(
const wxString& p : params )
259 args.emplace_back( p.wc_str() );
262 if( !aFileName.IsEmpty() )
263 args.emplace_back( aFileName.wc_str() );
265 args.emplace_back(
nullptr );
267 return wxExecute(
const_cast<wchar_t**
>( args.data() ), wxEXEC_ASYNC, aCallback );
271 msg.Printf(
_(
"Command '%s' could not be found." ), fullEditorName );
281 wxGetEnvMap( &env.env );
284 wxString binPath = wxFileName( wxStandardPaths::Get().GetExecutablePath() ).GetPath();
285 bool foundPath =
false;
287 for(
auto& entry : env.env )
289 if( entry.first.CmpNoCase( wxS(
"PATH" ) ) == 0 )
291 entry.second = binPath + wxS(
';' ) + entry.second;
298 env.env[
"PATH"] = binPath;
307 wxString shellCmd = wxS(
"cmd.exe /d /s /c \"" ) + aCommand + wxS(
"\"" );
309 return static_cast<int>( wxExecute( shellCmd, wxEXEC_SYNC, aProcess, &env ) );
314 wxWCharBuffer shell = wxString( wxS(
"/bin/sh" ) ).wc_str();
315 wxWCharBuffer
flag = wxString( wxS(
"-c" ) ).wc_str();
316 wxWCharBuffer command = aCommand.wc_str();
318 const wchar_t* argv[] = { shell.data(),
flag.data(), command.data(),
nullptr };
320 return static_cast<int>( wxExecute( argv, wxEXEC_SYNC, aProcess ) );
328 wxString filename = file;
332 if(
Pgm().UseSystemPdfBrowser() )
336 msg.Printf(
_(
"Unable to find a PDF viewer for '%s'." ), filename );
343 const wchar_t* args[3];
346 args[1] = filename.wc_str();
349 if( wxExecute(
const_cast<wchar_t**
>( args ) ) == -1 )
351 msg.Printf(
_(
"Problem while running the PDF viewer '%s'." ), args[0] );
361void KiCopyFile(
const wxString& aSrcPath,
const wxString& aDestPath, wxString& aErrors )
363 if( !wxCopyFile( aSrcPath, aDestPath ) )
367 if( !aErrors.IsEmpty() )
370 msg.Printf(
_(
"Cannot copy file '%s'." ), aDestPath );
389 std::function<
bool(
const std::string& token, wxString& value )> aCallback,
392 bool success =
false;
402 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
404 std::string token = node->GetChild( 0 )->GetSymbol();
405 SEXPR::SEXPR_STRING* pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
406 SEXPR::SEXPR_SYMBOL* symNode = dynamic_cast<SEXPR::SEXPR_SYMBOL*>( node->GetChild( 1 ) );
410 path = pathNode->m_value;
412 path = symNode->m_value;
414 if( aCallback( token, path ) )
417 pathNode->m_value = path;
419 symNode->m_value = path;
429 formatter.
Print(
"%s", sexpr->AsString( 0 ).c_str() );
431 success = formatter.
Finish();
442 if( !aErrors.empty() )
443 aErrors += wxS(
"\n" );
445 msg.Printf(
_(
"Cannot copy file '%s'." ), aDestPath );
453 return wxT(
"\"" ) + fn.GetFullPath( format ) + wxT(
"\"" );
459 namespace fs = std::filesystem;
461 std::string rmDir = aFileName.ToStdString();
463 if( rmDir.length() < 3 )
466 *aErrors =
_(
"Invalid directory name, cannot remove root" );
471 if( !fs::exists( rmDir ) )
474 *aErrors = wxString::Format(
_(
"Directory '%s' does not exist" ), aFileName );
479 fs::path
path( rmDir );
481 if( !fs::is_directory(
path ) )
484 *aErrors = wxString::Format(
_(
"'%s' is not a directory" ), aFileName );
491 fs::remove_all(
path );
493 catch(
const fs::filesystem_error& e )
496 *aErrors = wxString::Format(
_(
"Error removing directory '%s': %s" ), aFileName, e.what() );
506 const std::vector<wxString>& aPathsWithOverwriteDisallowed, wxString& aErrors )
508 wxDir dir( aSourceDir );
510 if( !dir.IsOpened() )
512 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), aSourceDir );
513 aErrors += wxT(
"\n" );
517 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
519 aErrors += wxString::Format(
_(
"Could not create destination directory: %s" ), aDestDir );
520 aErrors += wxT(
"\n" );
525 bool cont = dir.GetFirst( &filename );
529 wxString sourcePath = dir.GetNameWithSep() + filename;
530 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
532 if( wxFileName::DirExists( sourcePath ) )
535 if( !
CopyDirectory( sourcePath, destPath, aPathsWithOverwriteDisallowed, aErrors ) )
541 if(
alg::contains( aPathsWithOverwriteDisallowed, sourcePath ) && wxFileExists( destPath ) )
545 else if( !wxCopyFile( sourcePath, destPath ) )
547 aErrors += wxString::Format(
_(
"Could not copy file: %s to %s" ), sourcePath, destPath );
552 cont = dir.GetNext( &filename );
560 wxString& aErrors, std::vector<wxString>& aPathsWritten )
563 wxFileName sourceFn( aSourcePath );
564 wxString sourcePath = sourceFn.GetFullPath();
565 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
566 wxString baseDestDir = aDestDir;
569 [&](
const wxString& src,
const wxString& dest ) ->
bool
571 if( wxCopyFile( src, dest, aAllowOverwrites ) )
573 aPathsWritten.push_back( dest );
577 aErrors += wxString::Format(
_(
"Could not copy file: %s to %s" ), src, dest );
578 aErrors += wxT(
"\n" );
582 auto processEntries =
583 [&](
const wxString& srcDir,
const wxString& pattern,
const wxString& destDir ) ->
bool
587 if( !dir.IsOpened() )
589 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), srcDir );
590 aErrors += wxT(
"\n" );
598 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
602 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
603 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
605 if( !filename.Matches( wxT(
"~*.lck" ) ) && !filename.Matches( wxT(
"*.lck" ) ) )
607 if( wxFileName::DirExists( entrySrc ) )
612 aErrors += wxString::Format(
_(
"Could not copy directory: %s to %s" ),
613 entrySrc, entryDest );
614 aErrors += wxT(
"\n" );
622 if( !performCopy( entrySrc, entryDest ) )
629 cont = dir.GetNext( &filename );
636 if( isSourceDirectory )
638 wxString sourceDirName = sourceFn.GetFullName();
639 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
643 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
645 aErrors += wxString::Format(
_(
"Could not create destination directory: %s" ), baseDestDir );
646 aErrors += wxT(
"\n" );
652 if( !isSourceDirectory )
654 const wxString fileName = sourceFn.GetFullName();
657 if( fileName.Contains(
'*' ) || fileName.Contains(
'?' ) )
659 const wxString dirPath = sourceFn.GetPath();
661 if( !wxFileName::DirExists( dirPath ) )
663 aErrors += wxString::Format(
_(
"Source directory does not exist: %s" ), dirPath );
664 aErrors += wxT(
"\n" );
670 return processEntries( dirPath, fileName, baseDestDir );
674 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
678 return processEntries( sourcePath, wxEmptyString, baseDestDir );
683 const wxString& aParentDir )
685 wxDir dir( aSourceDir );
687 if( !dir.IsOpened() )
689 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), aSourceDir );
695 bool cont = dir.GetFirst( &filename );
699 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
700 wxString zipPath = aParentDir + filename;
702 if( wxFileName::DirExists( sourcePath ) )
705 aZip.PutNextDirEntry( zipPath +
"/" );
714 aZip.PutNextEntry( zipPath );
715 wxFFileInputStream fileStream( sourcePath );
717 if( !fileStream.IsOk() )
719 aErrors += wxString::Format(
_(
"Could not read file: %s" ), sourcePath );
723 aZip.Write( fileStream );
726 cont = dir.GetNext( &filename );
744bool collectArchiveSources(
const wxString& aSourceDir,
const wxString& aParent,
747 wxDir dir( aSourceDir );
749 if( !dir.IsOpened() )
753 bool cont = dir.GetFirst( &
name, wxEmptyString, wxDIR_DEFAULT );
757 wxString
path = wxFileName( aSourceDir,
name ).GetFullPath();
758 wxString relative = aParent.IsEmpty() ?
name : aParent + wxS(
"/" ) +
name;
759 bool isDirectory = wxDirExists(
path );
760 wxString key = relative + ( isDirectory ? wxS(
"/" ) : wxS(
"" ) );
761 aSources.push_back( {
path, relative, key, isDirectory } );
769 cont = dir.GetNext( &
name );
776bool writeArchiveContents( wxArchiveOutputStream& aArchive,
const std::vector<ARCHIVE_SOURCE>& aSources )
778 for(
const ARCHIVE_SOURCE&
source : aSources )
780 if(
source.m_isDirectory )
782 if( !aArchive.PutNextDirEntry(
source.m_name ) )
788 wxFFileInputStream input(
source.m_path );
789 wxFileOffset expectedLength = input.IsOk() ? input.GetLength() : wxInvalidOffset;
791 if( !input.IsOk() || !aArchive.PutNextEntry(
source.m_name, wxDateTime::Now(), expectedLength ) )
794 input.Read( aArchive );
796 if( input.TellI() != expectedLength || !aArchive.IsOk() || aArchive.GetLastError() != wxSTREAM_NO_ERROR
797 || !aArchive.CloseEntry() )
809 if( !aOut.IsOk() || !wxDirExists( aSourceDir ) )
813 std::vector<ARCHIVE_SOURCE> sources;
815 if( !guard.
IsRooted() || !collectArchiveSources( aSourceDir, aTopDir, guard, sources ) )
818 std::sort( sources.begin(), sources.end(), [](
const ARCHIVE_SOURCE& a,
const ARCHIVE_SOURCE& b )
820 return a.m_key < b.m_key;
825 wxZlibOutputStream gzip( aOut, -1, wxZLIB_GZIP );
826 wxTarOutputStream archive( gzip, wxTAR_PAX, wxConvUTF8 );
827 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
828 && writeArchiveContents( archive, sources );
829 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
830 bool archiveClosed = archive.Close();
831 bool gzipClosed = gzip.Close();
832 return ok && archiveOk && archiveClosed && gzipClosed && aOut.IsOk();
835 wxZipOutputStream archive( aOut );
836 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
837 && writeArchiveContents( archive, sources );
838 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
839 bool archiveClosed = archive.Close();
840 return ok && archiveOk && archiveClosed && aOut.IsOk();
845 ARCHIVE_FORMAT aFormat,
const wxString& aTopDir, wxString* aError,
855 *aError = error.IsEmpty() ?
_(
"Cannot create temporary archive file" ) : error;
871 std::unique_ptr<wxOutputStream> output = aOpen ? aOpen( file )
872 : std::make_unique<wxFFileOutputStream>( file );
877 catch(
const std::exception& exception )
880 error = wxString::FromUTF8( exception.what() );
884 bool streamError = file.Error();
885 FILE* raw = file.Detach();
887 bool closed = fclose( raw ) == 0;
888 ok = writeOk && !streamError && flushed && closed;
890 if( !writeOk || streamError )
892 if( error.IsEmpty() )
893 error =
_(
"Write error" );
895 else if( !flushed || !closed )
897 error =
_(
"Cannot flush or close temporary file" );
905 if( !ok && error.IsEmpty() )
906 error =
_(
"Cannot replace archive" );
909 if( !ok && wxFileExists( tempPath ) )
911 if( !wxRemoveFile( tempPath ) )
912 error +=
_(
"\nCannot remove temporary archive file" );
922std::filesystem::path
ToFsPath(
const wxString& aPath )
925 return std::filesystem::path( std::wstring( aPath.wc_str() ) );
927 return std::filesystem::path( aPath.utf8_string() );
935 return wxString( aPath.wstring() );
937 std::u8string utf8 = aPath.u8string();
938 return wxString::FromUTF8(
reinterpret_cast<const char*
>( utf8.data() ), utf8.size() );
945 if( wxDirExists(
m_path ) )
946 wxFileName::Rmdir(
m_path, wxPATH_RMDIR_RECURSIVE );
947 else if( wxFileExists(
m_path ) )
954 m_path = wxFileName::CreateTempFileName( aPrefix );
963 if( !tempFile.
MakeFile( aPrefix ) || !wxRemoveFile( tempFile.
Path() ) )
972 if( !wxFileName::Mkdir(
path, aPermissions, 0 ) )
987std::filesystem::path canonicalPath(
const std::filesystem::path& aPath )
990 std::filesystem::path canon = std::filesystem::weakly_canonical( aPath, ec );
992 return ec ? std::filesystem::path() : canon;
999bool isAncestorOrSame(
const std::filesystem::path& aAncestor,
1000 const std::filesystem::path& aDescendant )
1002 auto a = aAncestor.begin();
1003 auto d = aDescendant.begin();
1005 for( ; a != aAncestor.end() && d != aDescendant.end(); ++a, ++d )
1011 return a == aAncestor.end();
1016class LOOP_SAFE_COLLECTOR :
public wxDirTraverser
1019 LOOP_SAFE_COLLECTOR( wxArrayString& aOutput,
const wxString& aRoot,
bool aCollectFiles,
1020 bool aCollectDirs ) :
1021 m_output( aOutput ),
1023 m_collectFiles( aCollectFiles ),
1024 m_collectDirs( aCollectDirs )
1028 wxDirTraverseResult OnFile(
const wxString& aFilename )
override
1030 if( m_collectFiles )
1031 m_output.Add( aFilename );
1033 return wxDIR_CONTINUE;
1036 wxDirTraverseResult OnDir(
const wxString& aDirname )
override
1038 if( !m_guard.ShouldDescend( aDirname ) )
1039 return wxDIR_IGNORE;
1042 m_output.Add( aDirname );
1044 return wxDIR_CONTINUE;
1048 wxArrayString& m_output;
1049 DIR_LOOP_GUARD m_guard;
1050 bool m_collectFiles;
1055void traverseLoopSafe(
const wxString& aRoot, wxArrayString& aOutput,
bool aCollectFiles,
1056 bool aCollectDirs,
const wxString& aFileSpec,
int aFlags )
1060 if( !dir.IsOpened() )
1063 LOOP_SAFE_COLLECTOR collector( aOutput, aRoot, aCollectFiles, aCollectDirs );
1064 dir.Traverse( collector, aFileSpec, aFlags );
1083 const std::filesystem::path raw =
ToFsPath( aDir );
1084 std::filesystem::path key;
1090 const std::filesystem::path canon = canonicalPath( raw );
1095 if( !
m_root.empty() && !isAncestorOrSame(
m_root, canon ) )
1105 const bool isLink = std::filesystem::is_symlink( raw, ec );
1109 const std::filesystem::path canon = canonicalPath( raw );
1114 if( !
m_root.empty() && isAncestorOrSame( canon,
m_root ) )
1121 key = raw.lexically_normal();
1125 return m_visited.insert( key.generic_string() ).second;
1135 traverseLoopSafe( aRoot, aFiles,
true,
false, aFileSpec, aFlags | wxDIR_FILES | wxDIR_DIRS );
1141 traverseLoopSafe( aRoot, aDirs,
false,
true, wxEmptyString, aFlags | wxDIR_DIRS );
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
bool ShouldDescend(const wxString &aDir)
std::unordered_set< std::string > m_visited
DIR_LOOP_GUARD(const wxString &aRoot, DIR_LOOP_POLICY aPolicy=DIR_LOOP_POLICY::CONFINE_TO_ROOT)
std::filesystem::path m_root
static bool RunningFromBuildDir()
True when KiCad runs from its build tree or KICAD_RUN_FROM_BUILD_DIR is set.
virtual const wxString & GetKicadEnvVariable() const
virtual void ReadPdfBrowserInfos()
Read the PDF browser choice from the common configuration.
virtual const wxString & GetPdfBrowserName() const
virtual const wxString & GetExecutablePath() const
bool MakeDirectory(const wxString &aPrefix, int aPermissions=wxS_DIR_DEFAULT)
Create an empty directory with a unique name after aPrefix.
bool MakeFile(const wxString &aPrefix)
Create an empty file with a unique name after aPrefix.
SCOPED_TEMP_PATH()=default
const wxString & Path() const
std::unique_ptr< SEXPR > ParseFromFile(const std::string &aFilename)
size_t GetNumberOfChildren() const
SEXPR * GetChild(size_t aIndex) const
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
This file is part of the common library.
wxString FromFsPath(const std::filesystem::path &aPath)
bool WriteDirectoryArchive(const wxString &aSourceDir, wxOutputStream &aOut, ARCHIVE_FORMAT aFormat, const wxString &aTopDir)
Write directory entries in sorted archive path order, using the archive root when aTopDir is empty.
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
int ExecuteCommandThroughShell(const wxString &aCommand, wxProcess *aProcess)
Run a user-supplied command line through the platform shell so glob expansion, pipes,...
void CopySexprFile(const wxString &aSrcPath, const wxString &aDestPath, std::function< bool(const std::string &token, wxString &value)> aCallback, wxString &aErrors)
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
void CollectSubdirsLoopSafe(const wxString &aRoot, wxArrayString &aDirs, int aFlags)
Recursively collect every subdirectory under aRoot using the same loop detection as CollectFilesLoopS...
void CollectFilesLoopSafe(const wxString &aRoot, wxArrayString &aFiles, const wxString &aFileSpec, int aFlags)
Recursively collect every file under aRoot, deduplicating subdirectories by their resolved path.
std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
bool CopyDirectory(const wxString &aSourceDir, const wxString &aDestDir, const std::vector< wxString > &aPathsWithOverwriteDisallowed, wxString &aErrors)
Copy a directory and its contents to another directory.
bool AddDirectoryToZip(wxZipOutputStream &aZip, const wxString &aSourceDir, wxString &aErrors, const wxString &aParentDir)
Add a directory and its contents to a zip file.
std::function< std::unique_ptr< wxOutputStream >(wxFFile &)> ARCHIVE_STREAM_FACTORY
KICOMMON_API std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
PGM_BASE & Pgm()
The global program "get" accessor.