radiant: introduce transformpath lib and make use of it to transform engine path illwieckz/transformpath 189/head
authorThomas Debesse <dev@illwieckz.net>
Fri, 26 Nov 2021 00:21:23 +0000 (01:21 +0100)
committerThomas Debesse <dev@illwieckz.net>
Fri, 26 Nov 2021 00:21:23 +0000 (01:21 +0100)
libs/CMakeLists.txt
libs/transformpath/CMakeLists.txt [new file with mode: 0644]
libs/transformpath/transformpath.cpp [new file with mode: 0644]
libs/transformpath/transformpath.h [new file with mode: 0644]
radiant/CMakeLists.txt
radiant/mainframe.cpp

index a306996a019d2c2384435702261d9aa25d74a03d..f0c157f530b4abddf77028280930bf3a6b3cdfdc 100644 (file)
@@ -29,6 +29,7 @@ add_subdirectory(signal)
 add_subdirectory(splines)
 add_subdirectory(stream)
 add_subdirectory(string)
+add_subdirectory(transformpath)
 add_subdirectory(xml)
 
 add_library(libs
diff --git a/libs/transformpath/CMakeLists.txt b/libs/transformpath/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5d3d0aa
--- /dev/null
@@ -0,0 +1,3 @@
+add_library(transformpath STATIC
+        transformpath.cpp transformpath.h
+        )
diff --git a/libs/transformpath/transformpath.cpp b/libs/transformpath/transformpath.cpp
new file mode 100644 (file)
index 0000000..67e482e
--- /dev/null
@@ -0,0 +1,443 @@
+/* SPDX-License-Identifier: MIT License
+
+Copyright © 2021 Thomas “illwieckz” Debesse
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE. */
+
+/* transformpath: transform file path based on keywords.
+
+This is not an environment variable parser, this only supports
+a set of keywords using common environment name syntax when it
+exists to make the strings easier to read.
+
+Supported substitution keywords,
+Windows:
+
+- %HOMEPATH%
+- %USERPROFILE%
+- %ProgramFiles%
+- %ProgramFiles(x86)%
+- %ProgramW6432%
+- %APPDATA%
+- [CSIDL_MYDOCUMENTS]
+
+Supported substitution keywords,
+Linux, FreeBSD, macOS:
+
+- ~
+- ${HOME}
+
+Supported substitution keywords,
+Linux, FreeBSD, other XDG systems:
+
+- ${XDG_CONFIG_HOME}
+- ${XDG_DATA_HOME}
+
+Examples,
+game engine directories:
+
+- Windows: %ProgramFiles%\Unvanquished
+- Linux: ${XDG_DATA_HOME}/unvanquished/base
+- macOS: ${HOME}/Games/Unvanquished
+
+Examples,
+game home directories:
+
+- Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished
+- Linux: ${XDG_DATA_HOME}/unvanquished
+- macOS: ${HOME}/Application Data/Unvanquished
+
+*/
+
+#include "globaldefs.h"
+#include "stringio.h"
+
+#include <cstring>
+#include <string>
+
+#if GDEF_OS_WINDOWS
+#include <windows.h>
+#include <iostream>
+#include <shlobj.h>
+#pragma comment(lib, "shell32.lib")
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+
+#if GDEF_OS_WINDOWS
+static std::string getUserProfilePath();
+#endif // !GDEF_OS_WINDOWS
+
+static std::string getUserName()
+{
+#if GDEF_OS_WINDOWS
+       std::string path( getenv( "USERNAME" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%USERNAME\% not found.\n";
+
+       return "";
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       std::string path( getenv( "USERNAME" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${USERNAME} not found, guessing…\n";
+
+       path = std::string( getenv( "LOGNAME" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${LOGNAME} not found, guessing…\n";
+
+       path = std::string( getenv( "USER" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${USER} not found.\n";
+
+       return "";
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+}
+
+static std::string getHomePath()
+{
+#if GDEF_OS_WINDOWS
+       std::string path( getenv( "HOMEPATH" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%HOMEPATH\% not found, guessing…\n";
+
+       std::string path1 = getUserProfilePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1;
+       }
+
+       globalErrorStream() << "\%HOMEPATH\% not found.\n";
+
+       return "";
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       // Get the path environment variable.
+       std::string path( getenv( "HOME" ) );
+
+       // Look up path directory in password database.
+       if( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${HOME} not found, guessing…\n";
+
+       static char     buf[ 4096 ];
+       struct passwd pw, *pwp;
+
+       if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 )
+       {
+               return std::string( pw.pw_dir );
+       }
+
+       globalErrorStream() << "${HOME} not found, guessing…\n";
+
+       std::string path1( "/home/" );
+
+       std::string path2 = getUserName();
+
+       if ( ! path2.empty() )
+       {
+               return path1 + path2;
+       }
+
+       globalErrorStream() << "${HOME} not found…\n";
+
+       return "";
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+}
+
+#if GDEF_OS_WINDOWS
+static std::string getSystemDrive()
+{
+       std::string path( getenv( "SYSTEMDRIVE" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%SYSTEMDRIVE\% not found, guessing…\n";
+
+       return "C:";
+}
+
+static std::string getUserProfilePath()
+{
+       std::string path( getenv( "USERPROFILE" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%USERPROFILE\% not found, guessing…\n";
+
+       std::string path1 = getSystemDrive();
+       std::string path2 = getUserName();
+
+       if ( ! path2.empty() )
+       {
+               return path1 + "\\Users\\" + path2;
+       }
+
+       globalErrorStream() << "\%USERPROFILE\% not found.\n";
+
+       return "";
+}
+
+static std::string getProgramFilesPath()
+{
+       std::string path( getenv( "ProgramFiles" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%ProgramFiles\% not found, guessing…\n";
+
+       std::string path1 = getSystemDrive();
+       return path1 + "\\Program Files";
+}
+
+static std::string getProgramFilesX86Path()
+{
+       std::string path( getenv( "ProgramFiles(x86)" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%ProgramFiles(x86)\% not found, guessing…\n";
+
+       return getProgramFilesPath();
+}
+
+static std::string getProgramW6432Path()
+{
+       std::string path( getenv( "ProgramW6432" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%ProgramW6432\% not found, guessing…\n";
+
+       return getProgramFilesPath();
+}
+
+static std::string getAppDataPath()
+{
+       std::string path( getenv( "APPDATA" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "\%APPDATA\% not found, guessing…\n";
+
+       std::string path1 = getUserProfilePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "\\AppData\\Roaming";
+       }
+
+       globalErrorStream() << "\%APPDATA\% not found.\n";
+
+       return std::string( "" );
+}
+
+/* TODO: see also qFOLDERID_SavedGames in mainframe.cpp,
+HomePaths_Realise and other things like that,
+they look to be game paths, not NetRadiant paths. */
+
+static std::string getMyDocumentsPath()
+{
+       CHAR path[ MAX_PATH ];
+       HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
+
+       if ( result == S_OK )
+       {
+               return std::string( path );
+       }
+
+       globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               path1 += "\\Documents";
+               
+               return path1;
+       }
+
+       globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n";
+
+       return "";
+}
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_XDG
+static std::string getXdgConfigHomePath()
+{
+       /* FIXME: we may want to rely on g_get_user_config_dir()
+       provided by GLib. */
+       std::string path ( getenv( "XDG_CONFIG_HOME" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       // This is not an error.
+       // globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "/.config";
+       }
+
+       globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n";
+
+       return "";
+}
+
+static std::string getXdgDataHomePath()
+{
+       std::string path ( getenv( "XDG_DATA_HOME" ) );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       // This is not an error.
+       // globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "/.local/share";
+       }
+
+       globalErrorStream() << "${XDG_DATA_HOME} not found.\n";
+
+       return "";
+}
+#endif // GDEF_OS_XDG
+
+struct pathTransformer_t
+{
+       std::string pattern;
+       std::string ( *function )();
+};
+
+static const pathTransformer_t pathTransformers[] =
+{
+#if GDEF_OS_WINDOWS
+       { "\%HOMEPATH\%", getHomePath },
+       { "\%USERPROFILE\%", getUserProfilePath },
+       { "\%ProgramFiles\%", getProgramFilesPath },
+       { "\%ProgramFiles(x86)\%", getProgramFilesX86Path },
+       { "\%ProgramW6432\%", getProgramW6432Path },
+       { "\%APPDATA\%", getAppDataPath },
+       { "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath },
+#endif // GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       { "~", getHomePath },
+       { "${HOME}", getHomePath },
+#endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+
+#if GDEF_OS_XDG
+       { "${XDG_CONFIG_HOME}", getXdgConfigHomePath },
+       { "${XDG_DATA_HOME}", getXdgDataHomePath },
+#endif // GDEF_OS_XDG
+};
+
+/* If no transformation succeeds, the path will be returned untransformed. */
+std::string transformPath( std::string transformedPath )
+{
+       for ( const pathTransformer_t &pathTransformer : pathTransformers )
+       {
+               if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 )
+               {
+                       globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n";
+
+                       std::string path = pathTransformer.function();
+
+                       if ( ! path.empty() )
+                       {
+                               transformedPath.replace( 0, pathTransformer.pattern.length(), path );
+
+                               globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n";
+
+                               return transformedPath;
+                       }
+
+                       break;
+               }
+       }
+
+       globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n";
+
+       return transformedPath;
+}
diff --git a/libs/transformpath/transformpath.h b/libs/transformpath/transformpath.h
new file mode 100644 (file)
index 0000000..172971a
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT License
+
+Copyright © 2021 Thomas “illwieckz” Debesse
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE. */
+
+/* marker */
+#ifndef TRANSFORMPATH_H
+#define TRANSFORMPATH_H
+
+#include <string>
+
+std::string transformPath( std::string transformedPath );
+
+#endif // TRANSFORMPATH_H
index 12ee1dfa8bd8c7a736328b21aaf4126dd1119888..f93b00cf1329a8662fb132c1cd897372a38808a9 100644 (file)
@@ -123,6 +123,7 @@ target_link_libraries(${RADIANT_BASENAME}
     splines
     stream
     string
+    transformpath
     uilib
     xmllib
 )
index 74ec1904c977d6fdb52741b311bfbb6bfc9c99e0..d058fe5475ba44ec36b963be5a9172673bc0f45e 100644 (file)
@@ -3472,9 +3472,9 @@ void Layout_registerPreferencesPage(){
        PreferencesDialog_addInterfacePage( makeCallbackF(Layout_constructPage) );
 }
 
-
 #include "preferencesystem.h"
 #include "stringio.h"
+#include "transformpath/transformpath.h"
 
 void MainFrame_Construct(){
        GlobalCommands_insert( "OpenManual", makeCallbackF(OpenHelpURL), Accelerator( GDK_KEY_F1 ) );
@@ -3631,9 +3631,11 @@ void MainFrame_Construct(){
 #error "unknown platform"
 #endif
                ;
+
                StringOutputStream path( 256 );
                path << DirectoryCleaned( g_pGameDescription->getRequiredKeyValue( ENGINEPATH_ATTRIBUTE ) );
-               g_strEnginePath = path.c_str();
+
+               g_strEnginePath = transformPath( path.c_str() ).c_str();
        }
 
        GlobalPreferenceSystem().registerPreference( "EnginePath", make_property_string( g_strEnginePath ) );