From 407d30f8537f9919a31f80bafebc35192106db63 Mon Sep 17 00:00:00 2001 From: joeyrp Date: Mon, 9 Oct 2023 20:01:45 -0400 Subject: [PATCH] changed default MSVC version to 22 --- .gitignore | 116 +-- CMakeLists.txt | 48 +- LICENSE | 12 +- cgenConfig.h.in | 6 +- docs/Todo.todo | 4 +- scripts/build.bat | 32 +- scripts/clean.bat | 26 +- scripts/cmconfig.bat | 14 +- src/main.cpp | 1533 ++++++++++++++++++------------------ src/utils/CmdArgParser.cpp | 534 ++++++------- src/utils/CmdArgParser.h | 228 +++--- src/utils/StringManip.cpp | 354 ++++----- src/utils/StringManip.h | 114 +-- 13 files changed, 1524 insertions(+), 1497 deletions(-) diff --git a/.gitignore b/.gitignore index 543ffac..8dc363a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,59 +1,59 @@ -######################## VSCODE IGNORES -.vscode/ - - -######################## C++ IGNORES -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# other -*.log -*.zip -*.ini -test_data/test_save.xml - -######################## CMAKE IGNORES -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps - -######################## BUILD IGNORES +######################## VSCODE IGNORES +.vscode/ + + +######################## C++ IGNORES +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# other +*.log +*.zip +*.ini +test_data/test_save.xml + +######################## CMAKE IGNORES +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +######################## BUILD IGNORES build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a8c6f7b..6df9ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,25 @@ -cmake_minimum_required(VERSION 3.16.3) - -# Set the project name and version -project(cgen VERSION 1.0.0) - -# specify the C++ standard -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -configure_file(cgenConfig.h.in cgenConfig.h) - -# specify the project source files -set( CGEN_SRC - src/main.cpp - src/utils/CmdArgParser.cpp - src/utils/StringManip.cpp -) - -# add the executable -add_executable(${PROJECT_NAME} ${CGEN_SRC}) - -target_include_directories(${PROJECT_NAME} - PUBLIC "${PROJECT_BINARY_DIR}" - PUBLIC src +cmake_minimum_required(VERSION 3.16.3) + +# Set the project name and version +project(cgen VERSION 1.0.5) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +configure_file(cgenConfig.h.in cgenConfig.h) + +# specify the project source files +set( CGEN_SRC + src/main.cpp + src/utils/CmdArgParser.cpp + src/utils/StringManip.cpp +) + +# add the executable +add_executable(${PROJECT_NAME} ${CGEN_SRC}) + +target_include_directories(${PROJECT_NAME} + PUBLIC "${PROJECT_BINARY_DIR}" + PUBLIC src ) \ No newline at end of file diff --git a/LICENSE b/LICENSE index 39a989f..2396485 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ -Copyright 2021 Joseph R Pollack - -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. - +Copyright 2021 Joseph R Pollack + +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. \ No newline at end of file diff --git a/cgenConfig.h.in b/cgenConfig.h.in index 9904f21..1170bec 100644 --- a/cgenConfig.h.in +++ b/cgenConfig.h.in @@ -1,4 +1,4 @@ -// the configured options and settings for Lunarium -#define cgen_VERSION_MAJOR @cgen_VERSION_MAJOR@ -#define cgen_VERSION_MINOR @cgen_VERSION_MINOR@ +// the configured options and settings for Lunarium +#define cgen_VERSION_MAJOR @cgen_VERSION_MAJOR@ +#define cgen_VERSION_MINOR @cgen_VERSION_MINOR@ #define cgen_VERSION_PATCH @cgen_VERSION_PATCH@ \ No newline at end of file diff --git a/docs/Todo.todo b/docs/Todo.todo index 75d3cbb..7758c09 100644 --- a/docs/Todo.todo +++ b/docs/Todo.todo @@ -1,3 +1,3 @@ - -✔ @high Add an option to change the compiler/generator (vs version, clang, etc) @done(22-04-18 17:58) + +✔ @high Add an option to change the compiler/generator (vs version, clang, etc) @done(22-04-18 17:58) ☐ Add library source directories to the root CMake project include directories \ No newline at end of file diff --git a/scripts/build.bat b/scripts/build.bat index 6653dd0..3516115 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -1,17 +1,17 @@ -@echo off -REM This script expects to be run from the parent directory -REM ex. scripts/build.bat - -IF not exist build/ ( - echo This script needs to be run from the directory above build/ - goto END -) - - -IF "%~1" == "r" ( - cmake --build build/ --target ALL_BUILD --config Release -) ELSE ( - cmake --build build/ --target ALL_BUILD --config Debug -) - +@echo off +REM This script expects to be run from the parent directory +REM ex. scripts/build.bat + +IF not exist build/ ( + echo This script needs to be run from the directory above build/ + goto END +) + + +IF "%~1" == "r" ( + cmake --build build/ --target ALL_BUILD --config Release +) ELSE ( + cmake --build build/ --target ALL_BUILD --config Debug +) + :END \ No newline at end of file diff --git a/scripts/clean.bat b/scripts/clean.bat index a6f7e69..6f2cf6d 100644 --- a/scripts/clean.bat +++ b/scripts/clean.bat @@ -1,14 +1,14 @@ - -@echo off - -IF not exist build/ ( - echo This script needs to be run from the directory above build/ - goto END -) - -echo Removing the build directory -del /s /q build -rd /s /q build -mkdir build - + +@echo off + +IF not exist build/ ( + echo This script needs to be run from the directory above build/ + goto END +) + +echo Removing the build directory +del /s /q build +rd /s /q build +mkdir build + :END \ No newline at end of file diff --git a/scripts/cmconfig.bat b/scripts/cmconfig.bat index 02b2938..ea13b17 100644 --- a/scripts/cmconfig.bat +++ b/scripts/cmconfig.bat @@ -1,7 +1,7 @@ -@echo off -REM This script expects to be run from the parent directory -REM ex. scripts/cmconfig.bat -@echo off - - -cmake -Wno-dev -B build/ -S . -G "Visual Studio 17 2022" -A x64 +@echo off +REM This script expects to be run from the parent directory +REM ex. scripts/cmconfig.bat +@echo off + + +cmake -Wno-dev -B build/ -S . -G "Visual Studio 17 2022" -A x64 diff --git a/src/main.cpp b/src/main.cpp index 200f132..f3d0585 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,754 +1,781 @@ -/****************************************************************************** -* File - main.cpp -* Author - Joey Pollack -* Date - 2021/12/03 (y/m/d) -* Mod Date - 2021/12/03 (y/m/d) -* Description - Generates a new C++ project with CMake -******************************************************************************/ - -// TODO: Generate a clean.sh script for linux - -#include -#include - -#include -#include -#include -#include -#include - -int GenerateLibCMakeFile(std::string name, std::filesystem::path location); -int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector& libraries); -int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location); -void CleanUp(std::filesystem::path root); - -typedef jpUtils::CmdArgParser::ArgDesc ArgDesc; - -int main(int argc, char** argv) -{ - jpUtils::CmdArgParser argParser(argc, argv, "Generates a new C++ project with CMake."); - - // Required args - argParser.AddArg(ArgDesc("ProjectName", false, 1, "The name of the project to generate.")); - - // Options - argParser.AddArg(ArgDesc("-h", true, 0, "Displays this help messasge.")); - argParser.AddArg(ArgDesc("-v", true, 0, "Verbose - Print extra info while running.")); - argParser.AddArg(ArgDesc("-p", true, 1, "Platform - Can be one of: win, linux, both. Default is win.")); - argParser.AddArg(ArgDesc("-l", true, 1, "License - Generate an MIT license file with the given name.")); - argParser.AddArg(ArgDesc("-d", true, 1, "Path to the location to generate the project at. Defaults to the current dirctory.")); - argParser.AddArg(ArgDesc("-i", true, 0, "Generate a .gitignore file")); - argParser.AddArg(ArgDesc("-m", true, 1, "Specify the CMake version to require. Default is 3.16.3")); - argParser.AddArg(ArgDesc("-c", true, 1, "Specify the C++ version to require. Default is 17")); - argParser.AddArg(ArgDesc("-s", true, 1, "Specify the visual studio version to use (17, 19, 22). Default is 19")); - argParser.AddArg(ArgDesc("-a", true, 1, "Add library projects, separated by ',' (ex: -a first,second,third)")); - argParser.AddArg(ArgDesc("-x", true, 1, "Add executable submodule projects, separated by ',' (ex: -x first,second,third)")); - argParser.AddArg(ArgDesc("-r", true, 0, "Remove the root project executable")); - argParser.AddArg(ArgDesc("-gs", true, 0, "Add git subsystem updating to the main cmake file.")); - - bool result = argParser.Parse(); - - if (!result) - { - if (argParser.ContainsOption("-h")) - { - std::cout << argParser.GetUsageText() << std::endl; - return 0; - } - else - { - std::cout << argParser.GetErrorMessage(); - std::cout << "\n" << argParser.GetUsageText() << std::endl; - return 1; - } - } - //////////////////////////////////////////////////////////// - // Parse the submodules/libraries - //////////////////////////////////////////////////////////// - - // Libraries - std::vector libraries; - if (argParser.ContainsOption("-a")) - { - std::string list = argParser.GetOptionValue("-a").values[0]; - std::string current_name; - for (int i = 0; i < list.size(); i++) - { - if (list[i] == ',') - { - libraries.push_back(current_name); - current_name.clear(); - } - else if (i + 1 == list.size()) - { - current_name += list[i]; - libraries.push_back(current_name); - current_name.clear(); - } - else - { - current_name += list[i]; - } - } - } - - // Submodules - std::vector submods; - if (argParser.ContainsOption("-x")) - { - std::string list = argParser.GetOptionValue("-x").values[0]; - std::string current_name; - for (int i = 0; i < list.size(); i++) - { - if (list[i] == ',') - { - submods.push_back(current_name); - current_name.clear(); - } - else if (i + 1 == list.size()) - { - current_name += list[i]; - submods.push_back(current_name); - current_name.clear(); - } - else - { - current_name += list[i]; - } - } - } - - - //////////////////////////////////////////////////////////// - // Generate directory structure - //////////////////////////////////////////////////////////// - std::filesystem::path root = std::filesystem::current_path(); - if (argParser.ContainsOption("-d")) - { - if (std::filesystem::exists(argParser.GetOptionValue("-d").values[0])) - { - root = argParser.GetOptionValue("-d").values[0]; - } - } - - std::cout << "\nGenerating project at location: " << root.string().c_str(); - std::string project_name = argParser.GetPositionalArg(0).values[0]; - root /= std::filesystem::path(project_name); - if (!std::filesystem::create_directory(root)) - { - std::cout << "\nCould not create directory: " << root.string().c_str(); - return 1; - } - - if (!std::filesystem::create_directory(root / "build")) - { - std::cout << "\nCould not create build directory"; - return 1; - } - - if (!std::filesystem::create_directory(root / "scripts")) - { - std::cout << "\nCould not create scripts directory"; - return 1; - } - - if (!std::filesystem::create_directory(root / "src")) - { - std::cout << "\nCould not create src directory"; - return 1; - } - - // Generate folders/files for any submodules/libraries - std::filesystem::path src = root / "src"; - for (int i = 0; i < libraries.size(); i++) - { - std::filesystem::path location = src / libraries[i]; - if (!std::filesystem::create_directory(location)) - { - std::cout << "\nCould not create library directory: " << location.c_str(); - return 1; - } - - if (GenerateLibCMakeFile(libraries[i], location) > 0) - return 1; - } - - src = root / "src"; - for (int i = 0; i < submods.size(); i++) - { - std::filesystem::path location = src / submods[i]; - if (!std::filesystem::create_directory(location)) - { - std::cout << "\nCould not create submodule directory: " << location.c_str(); - return 1; - } - - if (GenerateSubmodCMakeFile(submods[i], location, libraries) > 0) - return 1; - } - - - - //////////////////////////////////////////////////////////// - // Generate CMake files - //////////////////////////////////////////////////////////// - - std::filesystem::path cmake_file = root / std::filesystem::path("CMakeLists.txt"); - std::ofstream ofs(cmake_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); - - std::string cmversion = "3.16.3"; - if (argParser.ContainsOption("-m")) - { - cmversion = argParser.GetOptionValue("-m").values[0]; - } - - std::string cversion = "17"; - if (argParser.ContainsOption("-c")) - { - cversion = argParser.GetOptionValue("-c").values[0]; - } - - ofs << "cmake_minimum_required(VERSION " << cmversion << ")"; - - ofs << "\n\n# set the project name and version"; - ofs << "\nproject(" << project_name << " VERSION 0.1.0)"; - - ofs << "\n\n# specify the C++ standard"; - ofs << "\nset(CMAKE_CXX_STANDARD " << cversion << ")"; - ofs << "\nset(CMAKE_CXX_STANDARD_REQUIRED True)"; - - ofs << "\n\n# setup target output directories"; - ofs << "\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)"; - ofs << "\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)"; - ofs << "\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)"; - - ofs << "\n\nconfigure_file(" << project_name << "Config.h.in " << project_name << "Config.h)"; - - // remove root executable if the option is set - if (!argParser.ContainsOption("-r")) - { - ofs << "\n\n# specify the project source files"; - ofs << "\nset( " << jpUtils::StringManip::ToUpper(project_name) << "_SRC"; - ofs << "\n\tsrc/main.cpp"; - ofs << "\n)"; - - ofs << "\n\n# add the executable"; - ofs << "\nadd_executable(${PROJECT_NAME} ${" << jpUtils::StringManip::ToUpper(project_name) << "_SRC})"; - - ofs << "\n\ntarget_include_directories(${PROJECT_NAME}"; - ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; - ofs << "\n\tPUBLIC src"; - ofs << "\n)"; - - if (argParser.ContainsOption("-gs")) - { - ofs << "\n\n# DOWNLOAD ALL SUBMODULES"; - ofs << "\nfind_package(Git QUIET)"; - ofs << "\nif(GIT_FOUND AND EXISTS \"${PROJECT_SOURCE_DIR}/.git\")"; - ofs << "\n# Update submodules as needed"; - ofs << "\n option(GIT_SUBMODULE \"Check submodules during build\" ON)"; - ofs << "\n if(GIT_SUBMODULE)"; - ofs << "\n message(STATUS \"Submodule update\")"; - ofs << "\n execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive"; - ofs << "\n WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}"; - ofs << "\n RESULT_VARIABLE GIT_SUBMOD_RESULT)"; - ofs << "\n if(NOT GIT_SUBMOD_RESULT EQUAL \"0\")"; - ofs << "\n message(FATAL_ERROR \"git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules\")"; - ofs << "\n endif()"; - ofs << "\n endif()"; - ofs << "\nendif()"; - ofs << "\n"; - ofs << "\n# CHECK THAT ALL SUBMODULES EXIST"; - ofs << "\n#if(NOT EXISTS \"${PROJECT_SOURCE_DIR}/external//CMakeLists.txt\")"; - ofs << "\n# message(FATAL_ERROR \" submodule was not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.\")"; - ofs << "\n#endif()"; - } - - // include the libraries and submodules - ofs << "\n\n# add subdirectories here"; - for (int i = 0; i < libraries.size(); i++) - { - ofs << "\nadd_subdirectory(src/" << libraries[i].c_str() << ")"; - } - - for (int i = 0; i < submods.size(); i++) - { - ofs << "\nadd_subdirectory(src/" << submods[i].c_str() << ")"; - } - - // Link to any libs that exist or add space to link to submodules added through git - if (libraries.size() > 0) - { - - ofs << "\n\ntarget_link_directories(${PROJECT_NAME} "; - for (int i = 0; i < libraries.size(); i++) - { - ofs << "\n\tPRIVATE ../" << libraries[i].c_str(); - } - ofs << ")"; - - ofs << "\n\ntarget_link_libraries(${PROJECT_NAME} "; - for (int i = 0; i < libraries.size(); i++) - { - ofs << " " << libraries[i].c_str(); - } - ofs << ")"; - } - else if (argParser.ContainsOption("-gs")) - { - ofs << "\n\n# target_link_directories(${PROJECT_NAME} )"; - ofs << "\n\n# target_link_libraries(${PROJECT_NAME} )"; - } - - } - - ofs.close(); - ofs.clear(); - - //////////////////////////////////////////////////////////// - // Generate Config file - //////////////////////////////////////////////////////////// - - std::string config_name = project_name; - config_name += "Config.h.in"; - std::filesystem::path config_file = root / config_name; - ofs = std::ofstream(config_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the Config file: " << config_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating Config file: " << config_file.string().c_str(); - - ofs << "#define " << project_name << "_VERSION_MAJOR @" << project_name << "_VERSION_MAJOR@"; - ofs << "\n#define " << project_name << "_VERSION_MINOR @" << project_name << "_VERSION_MINOR@"; - ofs << "\n#define " << project_name << "_VERSION_PATCH @" << project_name << "_VERSION_PATCH@"; - - ofs.close(); - ofs.clear(); - - //////////////////////////////////////////////////////////// - // Generate License file - //////////////////////////////////////////////////////////// - - if (argParser.ContainsOption("-l")) - { - std::filesystem::path license_file = root / "LICENSE"; - ofs = std::ofstream(license_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the License file: " << license_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating License file: " << license_file.string().c_str(); - - time_t now = time(0); - tm *gmtm = gmtime(&now); - - - ofs << "Copyright " << gmtm->tm_year + 1900 <<" " << argParser.GetOptionValue("-l").values[0]; - ofs << "\n\nPermission 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:"; - ofs << "\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; - ofs << "\n\nTHE 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."; - - ofs.close(); - ofs.clear(); - } - - //////////////////////////////////////////////////////////// - // Generate .gitignore file - //////////////////////////////////////////////////////////// - - if (argParser.ContainsOption("-i")) - { - std::filesystem::path ignore_file = root / ".gitignore"; - ofs = std::ofstream(ignore_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the .gitignore file: " << ignore_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating .gitignore file: " << ignore_file.string().c_str(); - - ofs << "######################## VSCODE IGNORES"; - ofs << "\n.vscode/"; - ofs << "\n"; - ofs << "\n"; - ofs << "\n######################## C++ IGNORES"; - ofs << "\n# Prerequisites"; - ofs << "\n*.d"; - ofs << "\n"; - ofs << "\n# Compiled Object files"; - ofs << "\n*.slo"; - ofs << "\n*.lo"; - ofs << "\n*.o"; - ofs << "\n*.obj"; - ofs << "\n"; - ofs << "\n# Precompiled Headers"; - ofs << "\n*.gch"; - ofs << "\n*.pch"; - ofs << "\n"; - ofs << "\n# Compiled Dynamic libraries"; - ofs << "\n*.so"; - ofs << "\n*.dylib"; - ofs << "\n*.dll"; - ofs << "\n"; - ofs << "\n# Fortran module files"; - ofs << "\n*.mod"; - ofs << "\n*.smod"; - ofs << "\n"; - ofs << "\n# Compiled Static libraries"; - ofs << "\n*.lai"; - ofs << "\n*.la"; - ofs << "\n*.a"; - ofs << "\n*.lib"; - ofs << "\n"; - ofs << "\n# Executables"; - ofs << "\n*.exe"; - ofs << "\n*.out"; - ofs << "\n*.app"; - ofs << "\n"; - ofs << "\n# other"; - ofs << "\n*.log"; - ofs << "\n*.zip"; - ofs << "\n*.ini"; - ofs << "\n"; - ofs << "\n######################## CMAKE IGNORES"; - ofs << "\nCMakeLists.txt.user"; - ofs << "\nCMakeCache.txt"; - ofs << "\nCMakeFiles"; - ofs << "\nCMakeScripts"; - ofs << "\nTesting"; - ofs << "\nMakefile"; - ofs << "\ncmake_install.cmake"; - ofs << "\ninstall_manifest.txt"; - ofs << "\ncompile_commands.json"; - ofs << "\nCTestTestfile.cmake"; - ofs << "\n_deps"; - ofs << "\n"; - ofs << "\n######################## BUILD IGNORES"; - ofs << "\nbuild/"; - - ofs.close(); - ofs.clear(); - } - - //////////////////////////////////////////////////////////// - // Generate Script files - //////////////////////////////////////////////////////////// - - bool have_platform_option = argParser.ContainsOption("-p"); - bool gen_windows = (!have_platform_option || (argParser.GetOptionValue("-p").values[0] == "win" || argParser.GetOptionValue("-p").values[0] == "both")); - bool gen_linux = (have_platform_option && (argParser.GetOptionValue("-p").values[0] == "linux" || argParser.GetOptionValue("-p").values[0] == "both")); - - std::string vs_version = "\"Visual Studio 16 2019\""; - if (argParser.ContainsOption("-s")) - { - if (argParser.GetOptionValue("-s").values[0] == "17") - vs_version = "\"Visual Studio 15 2017\""; - - if (argParser.GetOptionValue("-s").values[0] == "22") - vs_version = "\"Visual Studio 17 2022\""; - } - - std::filesystem::path script_dir = root / "scripts"; - - if (gen_windows) - { - // CONFIG - std::filesystem::path config_file = script_dir / "config.bat"; - ofs = std::ofstream(config_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the config script file: " << config_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating config script file: " << config_file.string().c_str(); - - ofs << "@echo off"; - ofs << "\nREM This script expects to be run from the parent directory"; - ofs << "\nREM ex. scripts/cmconfig.bat"; - ofs << "\n@echo off"; - ofs << "\n\ncmake -Wno-dev -B build/ -S . -G " << vs_version.c_str() << " -A x64"; - ofs.close(); - ofs.clear(); - - // BUILD - std::filesystem::path build_file = script_dir / "build.bat"; - ofs = std::ofstream(build_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the build script file: " << build_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating build script file: " << build_file.string().c_str(); - - ofs << "@echo off"; - ofs << "\nSETLOCAL ENABLEDELAYEDEXPANSION"; - ofs << "\nREM This script expects to be run from the parent directory"; - ofs << "\nREM ex. scripts/build.bat"; - - ofs << "\n\nREM Sets the escape char. see for info: "; - ofs << "\nREM https://stackoverflow.com/questions/55891318/how-to-echo-with-different-colors-in-the-windows-command-line-inside-a-for-loop"; - ofs << "\nfor /F %%a in ('echo prompt $E ^| cmd') do set \"ESC=%%a\""; - - ofs << "\n\nIF not exist build/ ("; - ofs << "\n\techo This script needs to be run from the directory above build/"; - ofs << "\n\tgoto END"; - ofs << "\n)"; - - ofs << "\nset \"RELEASE=\""; - - ofs << "\n\nIf \"%~1\" == \"r\" set \"RELEASE=1\""; - - ofs << "\n\nIF defined RELEASE ("; - ofs << "\ncmake --build build/ --target ALL_BUILD --config Release"; - - ofs << "\n\nSET BUILD_ERRORLEVEL=!ERRORLEVEL!"; - ofs << "\nIF NOT \"!BUILD_ERRORLEVEL!\"==\"0\" ("; - ofs << "\n\techo %ESC%[91mBUILD FAILED!%ESC%[0m %BUILD_ERRORLEVEL%"; - ofs << "\n\tEXIT /B !BUILD_ERRORLEVEL!"; - ofs << "\n)"; - - ofs << "\n\nxcopy /y test_data\\engine_state.xml build\\Release\\"; - ofs << "\n) ELSE ("; - ofs << "\ncmake --build build/ --target ALL_BUILD --config Debug"; - - ofs << "\n\nSET BUILD_ERRORLEVEL=!ERRORLEVEL!"; - ofs << "\nIF NOT \"!BUILD_ERRORLEVEL!\"==\"0\" ("; - ofs << "\n\techo %ESC%[91mBUILD FAILED!%ESC%[0m %BUILD_ERRORLEVEL%"; - ofs << "\n\tEXIT /B !BUILD_ERRORLEVEL!"; - ofs << "\n)"; - - ofs << "\n\n)"; - - ofs << "\n\necho %ESC%[92mBUILD SUCCEEDED!%ESC%[0m"; - - ofs << "\n\n:END"; - ofs << "\nENDLOCAL "; - ofs.close(); - ofs.clear(); - - // CLEAN - std::filesystem::path clean_file = script_dir / "clean.bat"; - ofs = std::ofstream(clean_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the clean script file: " << clean_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating clean script file: " << clean_file.string().c_str(); - - ofs << "@echo off"; - ofs << "\n\nIF not exist build/ ("; - ofs << "\n\techo This script needs to be run from the directory above build/"; - ofs << "\n\tgoto END"; - ofs << "\n)"; - ofs << "\n\necho Removing the build directory"; - ofs << "\ndel /s /q build"; - ofs << "\nrd /s /q build"; - ofs << "\nmkdir build"; - ofs << "\n\n:END"; - ofs.close(); - ofs.clear(); - } - - if (gen_linux) - { - // CONFIG - std::filesystem::path config_file = script_dir / "config.sh"; - ofs = std::ofstream(config_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the config script file: " << config_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating config script file: " << config_file.string().c_str(); - - ofs << "#! /bin/sh"; - ofs << "\n# This script expects to be run from the parent directory"; - ofs << "\n# ex. scripts/cmconfig.sh"; - ofs << "\n\ncmake -S . -B build/"; - ofs.close(); - ofs.clear(); - - // BUILD - std::filesystem::path build_file = script_dir / "build.sh"; - ofs = std::ofstream(build_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the build script file: " << build_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating build script file: " << build_file.string().c_str(); - - ofs << "#! /bin/sh"; - ofs << "\n# This script expects to be run from the parent directory"; - ofs << "\n# ex. scripts/build.sh"; - ofs << "\n\ncmake -C build/"; - ofs.close(); - ofs.clear(); - } - - //////////////////////////////////////////////////////////// - // Generate Main.cpp files - //////////////////////////////////////////////////////////// - - // Check if there is a root project executable - if (!argParser.ContainsOption("-r")) - { - if (GenerateMainFile(project_name, project_name, root / "src/main.cpp") > 0) - return 1; - } - - - // Generate Main files for all libraries/submodules - for (int i = 0; i < libraries.size(); i++) - { - std::filesystem::path location = root / "src"; - location /= libraries[i]; - if (GenerateMainFile(project_name, libraries[i], location / "main.cpp") > 0) - return 1; - } - - // Generate Main files for all libraries/submodules - for (int i = 0; i < submods.size(); i++) - { - std::filesystem::path location = root / "src"; - location /= submods[i]; - if (GenerateMainFile(project_name, submods[i], location / "main.cpp") > 0) - return 1; - } - - std::cout << "\n\nNew project generated!"; - - return 0; -} - -int GenerateLibCMakeFile(std::string name, std::filesystem::path location) -{ - std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt"); - std::ofstream ofs(cmake_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); - - - ofs << "\n\nset(MODULE_NAME " << name << ")"; - - ofs << "\n# specify the libraries source files"; - ofs << "\nset( " << name << "_SRC"; - ofs << "\n\tmain.cpp"; - ofs << "\n)"; - - - ofs << "\n\n# add the library"; - ofs << "\nadd_library(${MODULE_NAME} ${" << name << "_SRC})"; - - ofs << "\n\ntarget_include_directories(${MODULE_NAME}"; - ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; - ofs << "\n)"; - - - ofs.close(); - ofs.clear(); - - return 0; -} - -int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector& libraries) -{ - std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt"); - std::ofstream ofs(cmake_file.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); - return 1; - } - - std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); - - ofs << "\n\nset(MODULE_NAME " << name << ")"; - - ofs << "\n# specify the libraries source files"; - ofs << "\nset( " << name << "_SRC"; - ofs << "\n\tmain.cpp"; - ofs << "\n)"; - - - ofs << "\n\n# add the executable"; - ofs << "\nadd_executable(${MODULE_NAME} ${" << name << "_SRC})"; - - ofs << "\n\ntarget_include_directories(${MODULE_NAME}"; - ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; - for (int i = 0; i < libraries.size(); i++) - { - ofs << "\n\tPUBLIC ../" << libraries[i].c_str(); - } - - ofs << "\n)"; - - // link to libs - ofs << "\n\ntarget_link_directories(${MODULE_NAME} "; - for (int i = 0; i < libraries.size(); i++) - { - ofs << "\n\tPRIVATE ../" << libraries[i].c_str(); - } - ofs << "\n)"; - - ofs << "\n\ntarget_link_libraries(${MODULE_NAME}"; - for (int i = 0; i < libraries.size(); i++) - { - ofs << " " << libraries[i].c_str(); - } - ofs << ")"; - - ofs.close(); - ofs.clear(); - - return 0; -} - -int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location) -{ - std::ofstream ofs = std::ofstream(location.string().c_str()); - if (!ofs.is_open()) - { - std::cout << "\nCould not generate the config script file: " << location.string().c_str(); - return 1; - } - - std::cout << "\nGenerating main.cpp file: " << location.string().c_str(); - - ofs << "\n\n#include "; - ofs << "\n#include <" << project_name << "Config.h>"; - ofs << "\n\nint main(int argc, char** argv)"; - ofs << "\n{"; - ofs << "\n\tstd::cout << \"Hello, World!\";"; - ofs << "\n\tstd::cout << \"\\nThis is " << mod_name << " version: \" << " << project_name << "_VERSION_MAJOR << \".\" << " << project_name << "_VERSION_MINOR << \".\" << " << project_name << "_VERSION_PATCH;"; - ofs << "\n\treturn 0;"; - ofs << "\n}"; - ofs.close(); - ofs.clear(); - - return 0; +/****************************************************************************** +* File - main.cpp +* Author - Joey Pollack +* Date - 2021/12/03 (y/m/d) +* Mod Date - 2021/12/03 (y/m/d) +* Description - Generates a new C++ project with CMake +******************************************************************************/ + +// TODO: Generate a clean.sh script for linux + +#include +#include + +#include +#include +#include +#include +#include + +int GenerateLibCMakeFile(std::string name, std::filesystem::path location); +int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector& libraries); +int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location); +void CleanUp(std::filesystem::path root); + +typedef jpUtils::CmdArgParser::ArgDesc ArgDesc; + +int main(int argc, char** argv) +{ + jpUtils::CmdArgParser argParser(argc, argv, "Generates a new C++ project with CMake."); + + // Required args + argParser.AddArg(ArgDesc("ProjectName", false, 1, "The name of the project to generate.")); + + // Options + argParser.AddArg(ArgDesc("-h", true, 0, "Displays this help messasge.")); + argParser.AddArg(ArgDesc("-v", true, 0, "Verbose - Print extra info while running.")); + argParser.AddArg(ArgDesc("-p", true, 1, "Platform - Can be one of: win, linux, both. Default is win.")); + argParser.AddArg(ArgDesc("-l", true, 1, "License - Generate an MIT license file with the given name.")); + argParser.AddArg(ArgDesc("-d", true, 1, "Path to the location to generate the project at. Defaults to the current dirctory.")); + argParser.AddArg(ArgDesc("-i", true, 0, "Generate a .gitignore file")); + argParser.AddArg(ArgDesc("-m", true, 1, "Specify the CMake version to require. Default is 3.16.3")); + argParser.AddArg(ArgDesc("-c", true, 1, "Specify the C++ version to require. Default is 17")); + argParser.AddArg(ArgDesc("-s", true, 1, "Specify the visual studio version to use (17, 19, 22). Default is 22")); + argParser.AddArg(ArgDesc("-a", true, 1, "Add library projects, separated by ',' (ex: -a first,second,third)")); + argParser.AddArg(ArgDesc("-x", true, 1, "Add executable submodule projects, separated by ',' (ex: -x first,second,third)")); + argParser.AddArg(ArgDesc("-r", true, 0, "Remove the root project executable")); + argParser.AddArg(ArgDesc("-gs", true, 0, "Add git subsystem updating to the main cmake file.")); + + bool result = argParser.Parse(); + + if (!result) + { + if (argParser.ContainsOption("-h")) + { + std::cout << argParser.GetUsageText() << std::endl; + return 0; + } + else + { + std::cout << argParser.GetErrorMessage(); + std::cout << "\n" << argParser.GetUsageText() << std::endl; + return 1; + } + } + //////////////////////////////////////////////////////////// + // Parse the submodules/libraries + //////////////////////////////////////////////////////////// + + // Libraries + std::vector libraries; + if (argParser.ContainsOption("-a")) + { + std::string list = argParser.GetOptionValue("-a").values[0]; + std::string current_name; + for (int i = 0; i < list.size(); i++) + { + if (list[i] == ',') + { + libraries.push_back(current_name); + current_name.clear(); + } + else if (i + 1 == list.size()) + { + current_name += list[i]; + libraries.push_back(current_name); + current_name.clear(); + } + else + { + current_name += list[i]; + } + } + } + + // Submodules + std::vector submods; + if (argParser.ContainsOption("-x")) + { + std::string list = argParser.GetOptionValue("-x").values[0]; + std::string current_name; + for (int i = 0; i < list.size(); i++) + { + if (list[i] == ',') + { + submods.push_back(current_name); + current_name.clear(); + } + else if (i + 1 == list.size()) + { + current_name += list[i]; + submods.push_back(current_name); + current_name.clear(); + } + else + { + current_name += list[i]; + } + } + } + + + //////////////////////////////////////////////////////////// + // Generate directory structure + //////////////////////////////////////////////////////////// + std::filesystem::path root = std::filesystem::current_path(); + if (argParser.ContainsOption("-d")) + { + if (std::filesystem::exists(argParser.GetOptionValue("-d").values[0])) + { + root = argParser.GetOptionValue("-d").values[0]; + } + } + + std::cout << "\nGenerating project at location: " << root.string().c_str(); + std::string project_name = argParser.GetPositionalArg(0).values[0]; + root /= std::filesystem::path(project_name); + if (!std::filesystem::create_directory(root)) + { + std::cout << "\nCould not create directory: " << root.string().c_str(); + return 1; + } + + if (!std::filesystem::create_directory(root / "build")) + { + std::cout << "\nCould not create build directory"; + return 1; + } + + if (!std::filesystem::create_directory(root / "scripts")) + { + std::cout << "\nCould not create scripts directory"; + return 1; + } + + if (!std::filesystem::create_directory(root / "src")) + { + std::cout << "\nCould not create src directory"; + return 1; + } + + // Generate folders/files for any submodules/libraries + std::filesystem::path src = root / "src"; + for (int i = 0; i < libraries.size(); i++) + { + std::filesystem::path location = src / libraries[i]; + if (!std::filesystem::create_directory(location)) + { + std::cout << "\nCould not create library directory: " << location.c_str(); + return 1; + } + + if (GenerateLibCMakeFile(libraries[i], location) > 0) + return 1; + } + + src = root / "src"; + for (int i = 0; i < submods.size(); i++) + { + std::filesystem::path location = src / submods[i]; + if (!std::filesystem::create_directory(location)) + { + std::cout << "\nCould not create submodule directory: " << location.c_str(); + return 1; + } + + if (GenerateSubmodCMakeFile(submods[i], location, libraries) > 0) + return 1; + } + + + + //////////////////////////////////////////////////////////// + // Generate CMake files + //////////////////////////////////////////////////////////// + + std::filesystem::path cmake_file = root / std::filesystem::path("CMakeLists.txt"); + std::ofstream ofs(cmake_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); + + std::string cmversion = "3.16.3"; + if (argParser.ContainsOption("-m")) + { + cmversion = argParser.GetOptionValue("-m").values[0]; + } + + std::string cversion = "17"; + if (argParser.ContainsOption("-c")) + { + cversion = argParser.GetOptionValue("-c").values[0]; + } + + ofs << "cmake_minimum_required(VERSION " << cmversion << ")"; + + ofs << "\n\n# set the project name and version"; + ofs << "\nproject(" << project_name << " VERSION 0.1.0)"; + + ofs << "\n\n# specify the C++ standard"; + ofs << "\nset(CMAKE_CXX_STANDARD " << cversion << ")"; + ofs << "\nset(CMAKE_CXX_STANDARD_REQUIRED True)"; + + ofs << "\n\n# setup target output directories"; + ofs << "\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)"; + ofs << "\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)"; + ofs << "\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)"; + + ofs << "\n\nconfigure_file(" << project_name << "Config.h.in " << project_name << "Config.h)"; + + // remove root executable if the option is set + if (!argParser.ContainsOption("-r")) + { + ofs << "\n\n# specify the project source files"; + ofs << "\nset( " << jpUtils::StringManip::ToUpper(project_name) << "_SRC"; + ofs << "\n\tsrc/main.cpp"; + ofs << "\n)"; + + ofs << "\n\n# add the executable"; + ofs << "\nadd_executable(${PROJECT_NAME} ${" << jpUtils::StringManip::ToUpper(project_name) << "_SRC})"; + + ofs << "\n\ntarget_include_directories(${PROJECT_NAME}"; + ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; + ofs << "\n\tPUBLIC src"; + ofs << "\n)"; + + if (argParser.ContainsOption("-gs")) + { + ofs << "\n\n# DOWNLOAD ALL SUBMODULES"; + ofs << "\nfind_package(Git QUIET)"; + ofs << "\nif(GIT_FOUND AND EXISTS \"${PROJECT_SOURCE_DIR}/.git\")"; + ofs << "\n# Update submodules as needed"; + ofs << "\n option(GIT_SUBMODULE \"Check submodules during build\" ON)"; + ofs << "\n if(GIT_SUBMODULE)"; + ofs << "\n message(STATUS \"Submodule update\")"; + ofs << "\n execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive"; + ofs << "\n WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}"; + ofs << "\n RESULT_VARIABLE GIT_SUBMOD_RESULT)"; + ofs << "\n if(NOT GIT_SUBMOD_RESULT EQUAL \"0\")"; + ofs << "\n message(FATAL_ERROR \"git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules\")"; + ofs << "\n endif()"; + ofs << "\n endif()"; + ofs << "\nendif()"; + ofs << "\n"; + ofs << "\n# CHECK THAT ALL SUBMODULES EXIST"; + ofs << "\n#if(NOT EXISTS \"${PROJECT_SOURCE_DIR}/external//CMakeLists.txt\")"; + ofs << "\n# message(FATAL_ERROR \" submodule was not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.\")"; + ofs << "\n#endif()"; + } + + // include the libraries and submodules + ofs << "\n\n# add subdirectories here"; + for (int i = 0; i < libraries.size(); i++) + { + ofs << "\nadd_subdirectory(src/" << libraries[i].c_str() << ")"; + } + + for (int i = 0; i < submods.size(); i++) + { + ofs << "\nadd_subdirectory(src/" << submods[i].c_str() << ")"; + } + + // Link to any libs that exist or add space to link to submodules added through git + if (libraries.size() > 0) + { + + ofs << "\n\ntarget_link_directories(${PROJECT_NAME} "; + for (int i = 0; i < libraries.size(); i++) + { + ofs << "\n\tPRIVATE ../" << libraries[i].c_str(); + } + ofs << ")"; + + ofs << "\n\ntarget_link_libraries(${PROJECT_NAME} "; + for (int i = 0; i < libraries.size(); i++) + { + ofs << " " << libraries[i].c_str(); + } + ofs << ")"; + } + else if (argParser.ContainsOption("-gs")) + { + ofs << "\n\n# target_link_directories(${PROJECT_NAME} )"; + ofs << "\n\n# target_link_libraries(${PROJECT_NAME} )"; + } + + } + + ofs.close(); + ofs.clear(); + + //////////////////////////////////////////////////////////// + // Generate Config file + //////////////////////////////////////////////////////////// + + std::string config_name = project_name; + config_name += "Config.h.in"; + std::filesystem::path config_file = root / config_name; + ofs = std::ofstream(config_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the Config file: " << config_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating Config file: " << config_file.string().c_str(); + + ofs << "#define " << project_name << "_VERSION_MAJOR @" << project_name << "_VERSION_MAJOR@"; + ofs << "\n#define " << project_name << "_VERSION_MINOR @" << project_name << "_VERSION_MINOR@"; + ofs << "\n#define " << project_name << "_VERSION_PATCH @" << project_name << "_VERSION_PATCH@"; + + ofs.close(); + ofs.clear(); + + //////////////////////////////////////////////////////////// + // Generate License file + //////////////////////////////////////////////////////////// + + if (argParser.ContainsOption("-l")) + { + std::filesystem::path license_file = root / "LICENSE"; + ofs = std::ofstream(license_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the License file: " << license_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating License file: " << license_file.string().c_str(); + + time_t now = time(0); + tm *gmtm = gmtime(&now); + + + ofs << "Copyright " << gmtm->tm_year + 1900 <<" " << argParser.GetOptionValue("-l").values[0]; + ofs << "\n\nPermission 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:"; + ofs << "\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; + ofs << "\n\nTHE 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."; + + ofs.close(); + ofs.clear(); + } + + //////////////////////////////////////////////////////////// + // Generate .gitignore file + //////////////////////////////////////////////////////////// + + if (argParser.ContainsOption("-i")) + { + std::filesystem::path ignore_file = root / ".gitignore"; + ofs = std::ofstream(ignore_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the .gitignore file: " << ignore_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating .gitignore file: " << ignore_file.string().c_str(); + + ofs << "######################## VSCODE IGNORES"; + ofs << "\n.vscode/"; + ofs << "\n"; + ofs << "\n"; + ofs << "\n######################## C++ IGNORES"; + ofs << "\n# Prerequisites"; + ofs << "\n*.d"; + ofs << "\n"; + ofs << "\n# Compiled Object files"; + ofs << "\n*.slo"; + ofs << "\n*.lo"; + ofs << "\n*.o"; + ofs << "\n*.obj"; + ofs << "\n"; + ofs << "\n# Precompiled Headers"; + ofs << "\n*.gch"; + ofs << "\n*.pch"; + ofs << "\n"; + ofs << "\n# Compiled Dynamic libraries"; + ofs << "\n*.so"; + ofs << "\n*.dylib"; + ofs << "\n*.dll"; + ofs << "\n"; + ofs << "\n# Fortran module files"; + ofs << "\n*.mod"; + ofs << "\n*.smod"; + ofs << "\n"; + ofs << "\n# Compiled Static libraries"; + ofs << "\n*.lai"; + ofs << "\n*.la"; + ofs << "\n*.a"; + ofs << "\n*.lib"; + ofs << "\n"; + ofs << "\n# Executables"; + ofs << "\n*.exe"; + ofs << "\n*.out"; + ofs << "\n*.app"; + ofs << "\n"; + ofs << "\n# other"; + ofs << "\n*.log"; + ofs << "\n*.zip"; + ofs << "\n*.ini"; + ofs << "\n"; + ofs << "\n######################## CMAKE IGNORES"; + ofs << "\nCMakeLists.txt.user"; + ofs << "\nCMakeCache.txt"; + ofs << "\nCMakeFiles"; + ofs << "\nCMakeScripts"; + ofs << "\nTesting"; + ofs << "\nMakefile"; + ofs << "\ncmake_install.cmake"; + ofs << "\ninstall_manifest.txt"; + ofs << "\ncompile_commands.json"; + ofs << "\nCTestTestfile.cmake"; + ofs << "\n_deps"; + ofs << "\n"; + ofs << "\n######################## BUILD IGNORES"; + ofs << "\nbuild/"; + + ofs.close(); + ofs.clear(); + } + + //////////////////////////////////////////////////////////// + // Generate Script files + //////////////////////////////////////////////////////////// + + bool have_platform_option = argParser.ContainsOption("-p"); + bool gen_windows = (!have_platform_option || (argParser.GetOptionValue("-p").values[0] == "win" || argParser.GetOptionValue("-p").values[0] == "both")); + bool gen_linux = (have_platform_option && (argParser.GetOptionValue("-p").values[0] == "linux" || argParser.GetOptionValue("-p").values[0] == "both")); + + std::string vs_version = "\"Visual Studio 17 2022\""; + if (argParser.ContainsOption("-s")) + { + if (argParser.GetOptionValue("-s").values[0] == "17") + vs_version = "\"Visual Studio 15 2017\""; + + if (argParser.GetOptionValue("-s").values[0] == "19") + vs_version = "\"Visual Studio 16 2019\""; + + if (argParser.GetOptionValue("-s").values[0] == "22") + vs_version = "\"Visual Studio 17 2022\""; + } + + std::filesystem::path script_dir = root / "scripts"; + + if (gen_windows) + { + // CONFIG + std::filesystem::path config_file = script_dir / "config.bat"; + ofs = std::ofstream(config_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the config script file: " << config_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating config script file: " << config_file.string().c_str(); + + ofs << "@echo off"; + ofs << "\nREM This script expects to be run from the parent directory"; + ofs << "\nREM ex. scripts/config.bat"; + ofs << "\n@echo off"; + + ofs << "\nset options="; + ofs << "\nset build_type="; + ofs << "\n"; + ofs << "\nIF \"%1\"==\"\" goto command"; + ofs << "\ngoto loop"; + + ofs << "\n:command"; + ofs << "\nIF \"%build_type%\"==\"\" ("; + ofs << "\n set build_type=-DCMAKE_BUILD_TYPE=Debug"; + ofs << "\n echo Build mode is Debug"; + ofs << "\n)"; + ofs << "\necho using options %options%"; + ofs << "\ncmake -Wno-dev %build_type% %options% -B build/ -S . -G " << vs_version.c_str() << " -A x64"; + ofs << "\ngoto exit"; + + ofs << "\n"; + ofs << "\n:loop"; + ofs << "\n"; + + ofs << "\nIF \"%1\"==\"-c\" ("; + ofs << "\n call scripts\\clean.bat"; + ofs << "\n goto loop_condition"; + ofs << "\n)"; + ofs << "\n"; + ofs << "\nIF \"%1\"==\"-r\" ("; + ofs << "\n echo Build mode is release"; + ofs << "\n set build_type=-DCMAKE_BUILD_TYPE=Release"; + ofs << "\n goto loop_condition"; + ofs << "\n)"; + ofs << "\n"; + ofs << "\nset options=%options%-D%1=ON "; + ofs << "\n"; + ofs << "\n:loop_condition"; + ofs << "\nshift"; + ofs << "\nIF \"%1\"==\"\" goto command"; + ofs << "\ngoto loop"; + ofs << "\n"; + ofs << "\n:exit"; + + + //ofs << "\n\ncmake -Wno-dev -B build/ -S . -G " << vs_version.c_str() << " -A x64"; + ofs.close(); + ofs.clear(); + + // BUILD + std::filesystem::path build_file = script_dir / "build.bat"; + ofs = std::ofstream(build_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the build script file: " << build_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating build script file: " << build_file.string().c_str(); + + ofs << "@echo off"; + ofs << "\nSETLOCAL ENABLEDELAYEDEXPANSION"; + ofs << "\nREM This script expects to be run from the parent directory"; + ofs << "\nREM ex. scripts/build.bat"; + + ofs << "\n\nREM Sets the escape char. see for info: "; + ofs << "\nREM https://stackoverflow.com/questions/55891318/how-to-echo-with-different-colors-in-the-windows-command-line-inside-a-for-loop"; + ofs << "\nfor /F %%a in ('echo prompt $E ^| cmd') do set \"ESC=%%a\""; + + ofs << "\n\nIF not exist build/ ("; + ofs << "\n\techo This script needs to be run from the directory above build/"; + ofs << "\n\tgoto END"; + ofs << "\n)"; + + + ofs << "\ncmake --build build/ --target ALL_BUILD"; + ofs << "\n\nSET BUILD_ERRORLEVEL=!ERRORLEVEL!"; + ofs << "\nIF NOT \"!BUILD_ERRORLEVEL!\"==\"0\" ("; + ofs << "\n\techo %ESC%[91mBUILD FAILED!%ESC%[0m %BUILD_ERRORLEVEL%"; + ofs << "\n\tEXIT /B !BUILD_ERRORLEVEL!"; + ofs << "\n)"; + + ofs << "\n\necho %ESC%[92mBUILD SUCCEEDED!%ESC%[0m"; + + ofs << "\n\n:END"; + ofs << "\nENDLOCAL "; + ofs.close(); + ofs.clear(); + + // CLEAN + std::filesystem::path clean_file = script_dir / "clean.bat"; + ofs = std::ofstream(clean_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the clean script file: " << clean_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating clean script file: " << clean_file.string().c_str(); + + ofs << "@echo off"; + ofs << "\n\nIF not exist build/ ("; + ofs << "\n\techo This script needs to be run from the directory above build/"; + ofs << "\n\tgoto END"; + ofs << "\n)"; + ofs << "\n\necho Removing the build directory"; + ofs << "\ndel /s /q build"; + ofs << "\nrd /s /q build"; + ofs << "\nmkdir build"; + ofs << "\n\n:END"; + ofs.close(); + ofs.clear(); + } + + if (gen_linux) + { + // CONFIG + std::filesystem::path config_file = script_dir / "config.sh"; + ofs = std::ofstream(config_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the config script file: " << config_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating config script file: " << config_file.string().c_str(); + + ofs << "#! /bin/sh"; + ofs << "\n# This script expects to be run from the parent directory"; + ofs << "\n# ex. scripts/cmconfig.sh"; + ofs << "\n\ncmake -S . -B build/"; + ofs.close(); + ofs.clear(); + + // BUILD + std::filesystem::path build_file = script_dir / "build.sh"; + ofs = std::ofstream(build_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the build script file: " << build_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating build script file: " << build_file.string().c_str(); + + ofs << "#! /bin/sh"; + ofs << "\n# This script expects to be run from the parent directory"; + ofs << "\n# ex. scripts/build.sh"; + ofs << "\n\ncmake -C build/"; + ofs.close(); + ofs.clear(); + } + + //////////////////////////////////////////////////////////// + // Generate Main.cpp files + //////////////////////////////////////////////////////////// + + // Check if there is a root project executable + if (!argParser.ContainsOption("-r")) + { + if (GenerateMainFile(project_name, project_name, root / "src/main.cpp") > 0) + return 1; + } + + + // Generate Main files for all libraries/submodules + for (int i = 0; i < libraries.size(); i++) + { + std::filesystem::path location = root / "src"; + location /= libraries[i]; + if (GenerateMainFile(project_name, libraries[i], location / "main.cpp") > 0) + return 1; + } + + // Generate Main files for all libraries/submodules + for (int i = 0; i < submods.size(); i++) + { + std::filesystem::path location = root / "src"; + location /= submods[i]; + if (GenerateMainFile(project_name, submods[i], location / "main.cpp") > 0) + return 1; + } + + std::cout << "\n\nNew project generated!"; + + return 0; +} + +int GenerateLibCMakeFile(std::string name, std::filesystem::path location) +{ + std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt"); + std::ofstream ofs(cmake_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); + + + ofs << "\n\nset(MODULE_NAME " << name << ")"; + + ofs << "\n# specify the libraries source files"; + ofs << "\nset( " << name << "_SRC"; + ofs << "\n\tmain.cpp"; + ofs << "\n)"; + + + ofs << "\n\n# add the library"; + ofs << "\nadd_library(${MODULE_NAME} ${" << name << "_SRC})"; + + ofs << "\n\ntarget_include_directories(${MODULE_NAME}"; + ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; + ofs << "\n)"; + + + ofs.close(); + ofs.clear(); + + return 0; +} + +int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector& libraries) +{ + std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt"); + std::ofstream ofs(cmake_file.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str(); + return 1; + } + + std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str(); + + ofs << "\n\nset(MODULE_NAME " << name << ")"; + + ofs << "\n# specify the libraries source files"; + ofs << "\nset( " << name << "_SRC"; + ofs << "\n\tmain.cpp"; + ofs << "\n)"; + + + ofs << "\n\n# add the executable"; + ofs << "\nadd_executable(${MODULE_NAME} ${" << name << "_SRC})"; + + ofs << "\n\ntarget_include_directories(${MODULE_NAME}"; + ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\""; + for (int i = 0; i < libraries.size(); i++) + { + ofs << "\n\tPUBLIC ../" << libraries[i].c_str(); + } + + ofs << "\n)"; + + // link to libs + ofs << "\n\ntarget_link_directories(${MODULE_NAME} "; + for (int i = 0; i < libraries.size(); i++) + { + ofs << "\n\tPRIVATE ../" << libraries[i].c_str(); + } + ofs << "\n)"; + + ofs << "\n\ntarget_link_libraries(${MODULE_NAME}"; + for (int i = 0; i < libraries.size(); i++) + { + ofs << " " << libraries[i].c_str(); + } + ofs << ")"; + + ofs.close(); + ofs.clear(); + + return 0; +} + +int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location) +{ + std::ofstream ofs = std::ofstream(location.string().c_str()); + if (!ofs.is_open()) + { + std::cout << "\nCould not generate the config script file: " << location.string().c_str(); + return 1; + } + + std::cout << "\nGenerating main.cpp file: " << location.string().c_str(); + + ofs << "\n\n#include "; + ofs << "\n#include <" << project_name << "Config.h>"; + ofs << "\n\nint main(int argc, char** argv)"; + ofs << "\n{"; + ofs << "\n\tstd::cout << \"Hello, World!\";"; + ofs << "\n\tstd::cout << \"\\nThis is " << mod_name << " version: \" << " << project_name << "_VERSION_MAJOR << \".\" << " << project_name << "_VERSION_MINOR << \".\" << " << project_name << "_VERSION_PATCH;"; + ofs << "\n\treturn 0;"; + ofs << "\n}"; + ofs.close(); + ofs.clear(); + + return 0; } \ No newline at end of file diff --git a/src/utils/CmdArgParser.cpp b/src/utils/CmdArgParser.cpp index 970dd72..dbd7267 100644 --- a/src/utils/CmdArgParser.cpp +++ b/src/utils/CmdArgParser.cpp @@ -1,268 +1,268 @@ -/****************************************************************************** -* File - CmdArgParser.cpp -* Author - Joey Pollack -* Date - 2018/09/27 (y/m/d) -* Mod Date - 2018/10/15 (y/m/d) -* Description - Parses the program's command line arguments and makes -* them easily available. -* -* Code should work on windows and *nix -******************************************************************************/ - -#include "CmdArgParser.h" -#include "StringManip.h" - -namespace jpUtils -{ - const CmdArgParser::Argument CmdArgParser::InvalidArg; - - CmdArgParser::CmdArgParser(int argc, char ** argv, std::string programDesc, char prefixChar) - : mArgc(argc), mArgv(argv), mPrefixChar(prefixChar), mUsageText(""), mErrorMessage(""), mProgramDesc(programDesc) - { - // TODO: Add -h arg? - } - - bool CmdArgParser::ContainsOption(const char * option) const - { - return mOptionalArgs.find(option) != mOptionalArgs.end(); - } - - const CmdArgParser::Argument& CmdArgParser::GetPositionalArg(int pos) const - { - if (pos < 0 || pos >= (signed)mPositionalArgs.size()) - return CmdArgParser::InvalidArg; - - return mPositionalArgs[pos]; - } - - const std::vector CmdArgParser::GetAllPosArgs() const - { - return mPositionalArgs; - } - - const CmdArgParser::Argument& CmdArgParser::GetOptionValue(const char * option) const - { - auto iter = mOptionalArgs.find(std::string(option)); - - if (iter == mOptionalArgs.end()) - return CmdArgParser::InvalidArg; - - return iter->second; - } - - bool CmdArgParser::Parse() - { - mOptionalArgs.clear(); - mPositionalArgs.clear(); - - std::vector tempPosValues; - - mErrorMessage = ""; - for (int i = 1; i < mArgc; i++) - { - // If this is an optional arg - if (mArgv[i][0] == mPrefixChar) - { - // Optional args must occur before or after all positional args - if (mPositionalArgs.size() != 0 && mPositionalArgs.size() < mExpectedPosArgs.size()) - { - mErrorMessage += "\nMissing Required Arguments"; - return false; - } - - std::string temp = mArgv[i]; - auto iter = mExpectedOptArgs.find(temp); - if (iter == mExpectedOptArgs.end()) - { - mErrorMessage += "\nUknown Argument: " + std::string(mArgv[i]); - continue; // Non fatal error - } - - const ArgDesc& arg = (ArgDesc)(*iter).second; - - if (mOptionalArgs.find(arg.name) != mOptionalArgs.end()) - { - mErrorMessage += "\nDuplicate Argument: " + std::string(mArgv[i]); - return false; - } - - Argument theArg; - theArg.name = arg.name; - - for (int j = 0; j < arg.numExpectedValues; j++) - { - i++; - if (i >= mArgc) - { - mErrorMessage += "\nNot enough values given for option argument: " + theArg.name; - return false; - } - - if (mArgv[i][0] == mPrefixChar) - { - mErrorMessage += "\nNot enough values given for option argument: " + theArg.name; - return false; - } - - theArg.values.push_back(std::string(mArgv[i])); - } - - mOptionalArgs.insert(std::pair(theArg.name, theArg)); - } - // If this is a positional arg - else - { - tempPosValues.push_back(std::string(mArgv[i])); - } - } - - //if (mPositionalArgs.size() > mExpectedPosArgs.size()) - //{ - // mErrorMessage += "\nToo many Arguments given"; - // return true; // non fatal error - //} - - // Split the positional args into lists based on how many values are expected for each - int numArgs = mExpectedPosArgs.size(); - int numValues = tempPosValues.size(); - int minNeededValues = 0; - int used = 0; - - // find the min number of values needed to satisfy all arg requirements - for (int i = 0; i < (signed)mExpectedPosArgs.size(); i++) - { - if (mExpectedPosArgs[i].numExpectedValues > 0) - minNeededValues += mExpectedPosArgs[i].numExpectedValues; - - if (mExpectedPosArgs[i].numExpectedValues == -1) - minNeededValues += 1; - } - - if (numValues < minNeededValues) - { - // TODO: Could add logic to print the name of the missing args - mErrorMessage += "\nNot enough arguments"; - return false; - } - - for (int i = 0; i < (signed)mExpectedPosArgs.size(); i++) - { - Argument arg; - arg.name = mExpectedPosArgs[i].name; - - // if -1 this arg will accept 1 or more values available - if (-1 == mExpectedPosArgs[i].numExpectedValues) - { - // How many values can be put in this arg list while still - // having enough left over to fill the rest of the args? - // numUsable should always be at least 1 - int numUsable = (tempPosValues.size() - used) - (minNeededValues - 1); - -#ifdef _DEBUG - if (numUsable < 1) - { - throw "numUsable is < 1!"; - } -#endif // _DEBUG - - // used is the index of the next available value in tempPosValues - // numUsable + used is the index past the last value in - // tempPosValues that will fit here - int endIdx = (numUsable + used); - for (int j = used; j < endIdx; j++) - { - arg.values.push_back(tempPosValues[j]); - used++; - } - } - else - { - int endIdx = (mExpectedPosArgs[i].numExpectedValues + used); - for (int j = used; j < endIdx; j++) - { - arg.values.push_back(tempPosValues[j]); - used++; - } - } - - mPositionalArgs.push_back(arg); - } - - return true; - } - - std::string CmdArgParser::GetErrorMessage() const - { - return mErrorMessage; - } - - bool CmdArgParser::AddArg(ArgDesc desc) - { - // With new args the usage text needs to be regenerated - // Making it empty will force it to be regenerated when GetUsageText() is called - mUsageText = ""; - - // argument names must not contain spaces - // so remove anything after the first space - if (desc.name.length() > 0) - desc.name = StringManip::Split(desc.name)[0]; - - if (desc.optional) - { - if (mExpectedOptArgs.find(desc.name) != mExpectedOptArgs.end()) - return false; - - mExpectedOptArgs.insert(std::pair(desc.name, desc)); - } - else - { - mExpectedPosArgs.push_back(desc); - } - - return true; - } - - - std::string CmdArgParser::GetUsageText() - { - if ("" == mUsageText) - GenerateUsageText(); - - return mUsageText; - } - - - void CmdArgParser::GenerateUsageText() - { - // TODO: GenerateUsageText() - mUsageText = ""; - - std::string programName = StringManip::GetFileNameFromPath(std::string(mArgv[0]), false); - - mUsageText = programName; - mUsageText += ": " + mProgramDesc; - mUsageText += "\n\nUsage: "; - mUsageText += programName; - mUsageText += " [OPTIONS] "; - - for (EPITER iter = mExpectedPosArgs.begin(); iter != mExpectedPosArgs.end(); iter++) - { - mUsageText += (*iter).name + " "; - } - - mUsageText += "\n\nOPTIONS:"; - for (EOITER iter = mExpectedOptArgs.begin(); iter != mExpectedOptArgs.end(); iter++) - { - mUsageText += "\n\t" + (*iter).first; - mUsageText += " " + (*iter).second.helpText; - } - - mUsageText += "\n\nREQUIRED ARGS:"; - for (EPITER iter = mExpectedPosArgs.begin(); iter != mExpectedPosArgs.end(); iter++) - { - mUsageText += "\n\t" + (*iter).name + ": " + (*iter).helpText; - } - - // ... - } +/****************************************************************************** +* File - CmdArgParser.cpp +* Author - Joey Pollack +* Date - 2018/09/27 (y/m/d) +* Mod Date - 2018/10/15 (y/m/d) +* Description - Parses the program's command line arguments and makes +* them easily available. +* +* Code should work on windows and *nix +******************************************************************************/ + +#include "CmdArgParser.h" +#include "StringManip.h" + +namespace jpUtils +{ + const CmdArgParser::Argument CmdArgParser::InvalidArg; + + CmdArgParser::CmdArgParser(int argc, char ** argv, std::string programDesc, char prefixChar) + : mArgc(argc), mArgv(argv), mPrefixChar(prefixChar), mUsageText(""), mErrorMessage(""), mProgramDesc(programDesc) + { + // TODO: Add -h arg? + } + + bool CmdArgParser::ContainsOption(const char * option) const + { + return mOptionalArgs.find(option) != mOptionalArgs.end(); + } + + const CmdArgParser::Argument& CmdArgParser::GetPositionalArg(int pos) const + { + if (pos < 0 || pos >= (signed)mPositionalArgs.size()) + return CmdArgParser::InvalidArg; + + return mPositionalArgs[pos]; + } + + const std::vector CmdArgParser::GetAllPosArgs() const + { + return mPositionalArgs; + } + + const CmdArgParser::Argument& CmdArgParser::GetOptionValue(const char * option) const + { + auto iter = mOptionalArgs.find(std::string(option)); + + if (iter == mOptionalArgs.end()) + return CmdArgParser::InvalidArg; + + return iter->second; + } + + bool CmdArgParser::Parse() + { + mOptionalArgs.clear(); + mPositionalArgs.clear(); + + std::vector tempPosValues; + + mErrorMessage = ""; + for (int i = 1; i < mArgc; i++) + { + // If this is an optional arg + if (mArgv[i][0] == mPrefixChar) + { + // Optional args must occur before or after all positional args + if (mPositionalArgs.size() != 0 && mPositionalArgs.size() < mExpectedPosArgs.size()) + { + mErrorMessage += "\nMissing Required Arguments"; + return false; + } + + std::string temp = mArgv[i]; + auto iter = mExpectedOptArgs.find(temp); + if (iter == mExpectedOptArgs.end()) + { + mErrorMessage += "\nUknown Argument: " + std::string(mArgv[i]); + continue; // Non fatal error + } + + const ArgDesc& arg = (ArgDesc)(*iter).second; + + if (mOptionalArgs.find(arg.name) != mOptionalArgs.end()) + { + mErrorMessage += "\nDuplicate Argument: " + std::string(mArgv[i]); + return false; + } + + Argument theArg; + theArg.name = arg.name; + + for (int j = 0; j < arg.numExpectedValues; j++) + { + i++; + if (i >= mArgc) + { + mErrorMessage += "\nNot enough values given for option argument: " + theArg.name; + return false; + } + + if (mArgv[i][0] == mPrefixChar) + { + mErrorMessage += "\nNot enough values given for option argument: " + theArg.name; + return false; + } + + theArg.values.push_back(std::string(mArgv[i])); + } + + mOptionalArgs.insert(std::pair(theArg.name, theArg)); + } + // If this is a positional arg + else + { + tempPosValues.push_back(std::string(mArgv[i])); + } + } + + //if (mPositionalArgs.size() > mExpectedPosArgs.size()) + //{ + // mErrorMessage += "\nToo many Arguments given"; + // return true; // non fatal error + //} + + // Split the positional args into lists based on how many values are expected for each + int numArgs = mExpectedPosArgs.size(); + int numValues = tempPosValues.size(); + int minNeededValues = 0; + int used = 0; + + // find the min number of values needed to satisfy all arg requirements + for (int i = 0; i < (signed)mExpectedPosArgs.size(); i++) + { + if (mExpectedPosArgs[i].numExpectedValues > 0) + minNeededValues += mExpectedPosArgs[i].numExpectedValues; + + if (mExpectedPosArgs[i].numExpectedValues == -1) + minNeededValues += 1; + } + + if (numValues < minNeededValues) + { + // TODO: Could add logic to print the name of the missing args + mErrorMessage += "\nNot enough arguments"; + return false; + } + + for (int i = 0; i < (signed)mExpectedPosArgs.size(); i++) + { + Argument arg; + arg.name = mExpectedPosArgs[i].name; + + // if -1 this arg will accept 1 or more values available + if (-1 == mExpectedPosArgs[i].numExpectedValues) + { + // How many values can be put in this arg list while still + // having enough left over to fill the rest of the args? + // numUsable should always be at least 1 + int numUsable = (tempPosValues.size() - used) - (minNeededValues - 1); + +#ifdef _DEBUG + if (numUsable < 1) + { + throw "numUsable is < 1!"; + } +#endif // _DEBUG + + // used is the index of the next available value in tempPosValues + // numUsable + used is the index past the last value in + // tempPosValues that will fit here + int endIdx = (numUsable + used); + for (int j = used; j < endIdx; j++) + { + arg.values.push_back(tempPosValues[j]); + used++; + } + } + else + { + int endIdx = (mExpectedPosArgs[i].numExpectedValues + used); + for (int j = used; j < endIdx; j++) + { + arg.values.push_back(tempPosValues[j]); + used++; + } + } + + mPositionalArgs.push_back(arg); + } + + return true; + } + + std::string CmdArgParser::GetErrorMessage() const + { + return mErrorMessage; + } + + bool CmdArgParser::AddArg(ArgDesc desc) + { + // With new args the usage text needs to be regenerated + // Making it empty will force it to be regenerated when GetUsageText() is called + mUsageText = ""; + + // argument names must not contain spaces + // so remove anything after the first space + if (desc.name.length() > 0) + desc.name = StringManip::Split(desc.name)[0]; + + if (desc.optional) + { + if (mExpectedOptArgs.find(desc.name) != mExpectedOptArgs.end()) + return false; + + mExpectedOptArgs.insert(std::pair(desc.name, desc)); + } + else + { + mExpectedPosArgs.push_back(desc); + } + + return true; + } + + + std::string CmdArgParser::GetUsageText() + { + if ("" == mUsageText) + GenerateUsageText(); + + return mUsageText; + } + + + void CmdArgParser::GenerateUsageText() + { + // TODO: GenerateUsageText() + mUsageText = ""; + + std::string programName = StringManip::GetFileNameFromPath(std::string(mArgv[0]), false); + + mUsageText = programName; + mUsageText += ": " + mProgramDesc; + mUsageText += "\n\nUsage: "; + mUsageText += programName; + mUsageText += " [OPTIONS] "; + + for (EPITER iter = mExpectedPosArgs.begin(); iter != mExpectedPosArgs.end(); iter++) + { + mUsageText += (*iter).name + " "; + } + + mUsageText += "\n\nOPTIONS:"; + for (EOITER iter = mExpectedOptArgs.begin(); iter != mExpectedOptArgs.end(); iter++) + { + mUsageText += "\n\t" + (*iter).first; + mUsageText += " " + (*iter).second.helpText; + } + + mUsageText += "\n\nREQUIRED ARGS:"; + for (EPITER iter = mExpectedPosArgs.begin(); iter != mExpectedPosArgs.end(); iter++) + { + mUsageText += "\n\t" + (*iter).name + ": " + (*iter).helpText; + } + + // ... + } } \ No newline at end of file diff --git a/src/utils/CmdArgParser.h b/src/utils/CmdArgParser.h index 0ae58d3..6b973ab 100644 --- a/src/utils/CmdArgParser.h +++ b/src/utils/CmdArgParser.h @@ -1,115 +1,115 @@ -/****************************************************************************** -* File - CmdArgParser.h -* Author - Joey Pollack -* Date - 2018/09/27 (y/m/d) -* Mod Date - 2018/10/15 (y/m/d) -* Description - Parses the program's command line arguments and makes -* them easily available. -* -* Code should work on windows and *nix -******************************************************************************/ - -#ifndef CMD_ARG_PARSER_H_ -#define CMD_ARG_PARSER_H_ - -#include -#include -#include - -namespace jpUtils -{ - class CmdArgParser - { - - // TODO: Possibly add error codes - // TODO: Add support for --arguments - - public: - struct Argument - { - std::string name = "INVALID"; - std::vector values; - - bool operator==(const Argument& rhs) - { - return this->name == rhs.name; - } - - }; - - static const Argument InvalidArg; - - struct ArgDesc - { - std::string name; - bool optional; - - // -1 for 1 or more args (if optional, 0 or more args) - int numExpectedValues; - std::string helpText; - - ArgDesc(std::string _name, bool _optional, int _numExpectedValues, std::string _helpText) - : name(_name), optional(_optional), numExpectedValues(_numExpectedValues), helpText(_helpText) - { - } - }; - - private: // currently unused - enum ValueType { VTYPE_STRING, VTYPE_INT, VTYPE_DOUBLE, VTYPE_BOOL, VTYPE_INVALID }; - - public: - - CmdArgParser(int argc, char** argv, std::string programDesc, char prefixChar = '-'); - - bool Parse(); - - // If the returned string is not empty errors occured when processing - // the arguments. The string contains the generated error messages. - std::string GetErrorMessage() const; - - // Returns false if the arg cant be added - this would probably - // happen if a given arg already exists - bool AddArg(ArgDesc desc); - - std::string GetUsageText(); - - bool ContainsOption(const char* option) const; - const CmdArgParser::Argument& GetPositionalArg(int pos) const; - const std::vector GetAllPosArgs() const; - const Argument& GetOptionValue(const char* option) const; - - private: - - int mArgc; - char** mArgv; - - std::string mUsageText; - std::string mErrorMessage; - std::string mProgramDesc; - - // can be set to '-' or '/' - char mPrefixChar; - - // Expected Positional Arguments - std::vector mExpectedPosArgs; - typedef std::vector::iterator EPITER; - - // Positional args found - std::vector mPositionalArgs; - typedef std::vector::iterator PITER; - - // Expected Optional args - std::map mExpectedOptArgs; - typedef std::map::iterator EOITER; - - // Optional args found - std::map mOptionalArgs; - typedef std::map::iterator OITER; - - private: // HELPER METHODS - - void GenerateUsageText(); - }; - -} +/****************************************************************************** +* File - CmdArgParser.h +* Author - Joey Pollack +* Date - 2018/09/27 (y/m/d) +* Mod Date - 2018/10/15 (y/m/d) +* Description - Parses the program's command line arguments and makes +* them easily available. +* +* Code should work on windows and *nix +******************************************************************************/ + +#ifndef CMD_ARG_PARSER_H_ +#define CMD_ARG_PARSER_H_ + +#include +#include +#include + +namespace jpUtils +{ + class CmdArgParser + { + + // TODO: Possibly add error codes + // TODO: Add support for --arguments + + public: + struct Argument + { + std::string name = "INVALID"; + std::vector values; + + bool operator==(const Argument& rhs) + { + return this->name == rhs.name; + } + + }; + + static const Argument InvalidArg; + + struct ArgDesc + { + std::string name; + bool optional; + + // -1 for 1 or more args (if optional, 0 or more args) + int numExpectedValues; + std::string helpText; + + ArgDesc(std::string _name, bool _optional, int _numExpectedValues, std::string _helpText) + : name(_name), optional(_optional), numExpectedValues(_numExpectedValues), helpText(_helpText) + { + } + }; + + private: // currently unused + enum ValueType { VTYPE_STRING, VTYPE_INT, VTYPE_DOUBLE, VTYPE_BOOL, VTYPE_INVALID }; + + public: + + CmdArgParser(int argc, char** argv, std::string programDesc, char prefixChar = '-'); + + bool Parse(); + + // If the returned string is not empty errors occured when processing + // the arguments. The string contains the generated error messages. + std::string GetErrorMessage() const; + + // Returns false if the arg cant be added - this would probably + // happen if a given arg already exists + bool AddArg(ArgDesc desc); + + std::string GetUsageText(); + + bool ContainsOption(const char* option) const; + const CmdArgParser::Argument& GetPositionalArg(int pos) const; + const std::vector GetAllPosArgs() const; + const Argument& GetOptionValue(const char* option) const; + + private: + + int mArgc; + char** mArgv; + + std::string mUsageText; + std::string mErrorMessage; + std::string mProgramDesc; + + // can be set to '-' or '/' + char mPrefixChar; + + // Expected Positional Arguments + std::vector mExpectedPosArgs; + typedef std::vector::iterator EPITER; + + // Positional args found + std::vector mPositionalArgs; + typedef std::vector::iterator PITER; + + // Expected Optional args + std::map mExpectedOptArgs; + typedef std::map::iterator EOITER; + + // Optional args found + std::map mOptionalArgs; + typedef std::map::iterator OITER; + + private: // HELPER METHODS + + void GenerateUsageText(); + }; + +} #endif // CMD_ARG_PARSER_H_ \ No newline at end of file diff --git a/src/utils/StringManip.cpp b/src/utils/StringManip.cpp index fc41d5f..e712870 100644 --- a/src/utils/StringManip.cpp +++ b/src/utils/StringManip.cpp @@ -1,178 +1,178 @@ -/****************************************************************************** -* File - StringManip.cpp -* Author - Joey Pollack -* Date - 2018/10/12 (y/m/d) -* Mod Date - 2018/10/12 (y/m/d) -* Description - Functions for working with std::string objects -* -******************************************************************************/ - -#include "StringManip.h" - -#include -#include - -namespace jpUtils -{ - - std::string StringManip::ToUpper(std::string str) - { - std::string result = ""; - for (int i = 0; i < (signed)str.length(); i++) - { - if (str[i] >= 97 && str[i] <= 122) - { - result += str[i] - 32; - } - else - { - result += str[i]; - } - } - - return result; - } - - std::string StringManip::ToLower(std::string str) - { - std::string result = ""; - for (int i = 0; i < (signed)str.length(); i++) - { - if (str[i] >= 65 && str[i] <= 90) - { - result += str[i] + 32; - } - else - { - result += str[i]; - } - } - - return result; - } - - std::vector StringManip::Split(std::string str, char delim, int maxSplits) - { - std::vector splits; - - int startPos = 0; - for (int i = 0; i < (signed)str.length(); i++) - { - if (str[i] == delim) - { - if (startPos == i) - continue; - - std::string s = str.substr(startPos, i - startPos); - splits.push_back(s); - startPos = i + 1; - - if (maxSplits > 0 && (signed)splits.size() >= (maxSplits - 1)) - { - break; - } - } - } - - std::string s = str.substr(startPos); - splits.push_back(s); - return splits; - } - - std::string StringManip::Trim(std::string str, std::string delims) - { - str = TrimStart(str, delims); - str = TrimEnd(str, delims); - return str; - } - - - std::string StringManip::TrimStart(std::string str, std::string delims) - { - bool isDelim = true; - while (isDelim && str.length() > 0) - { - isDelim = false; - for (int i = 0; i < (signed)delims.length(); i++) - { - if (str[0] == delims[i]) - { - isDelim = true; - break; - } - } - - if (isDelim) - str.erase(str.begin()); - } - - return str; - } - - - std::string StringManip::TrimEnd(std::string str, std::string delims) - { - bool isDelim = true; - while (isDelim && str.length() > 0) - { - isDelim = false; - for (int i = 0; i < (signed)delims.length(); i++) - { - if (*(str.rbegin()) == delims[i]) - { - isDelim = true; - break; - } - } - - if (isDelim) - str.erase((str.rbegin() + 1).base()); - } - - return str; - } - - std::string StringManip::GetFileNameFromPath(std::string path, bool includeExt) - { - if (path.find("\\") != std::string::npos || path.find("/") != std::string::npos) - { - path.erase(path.begin(), std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '\\' || ch == '/'; }).base()); - } - - if (!includeExt && path.find(".") != std::string::npos) - { - path.erase(std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '.'; }).base(), path.end()); - path.erase(path.begin() + path.size() - 1); - } - - return path; - } - - std::string StringManip::TrimFileNameFromPath(std::string path) - { - path.erase(std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '\\' || ch == '/'; }).base(), path.end()); - return path; - } - - std::string StringManip::GetFileExtension(std::string filename) - { - filename.erase(filename.begin(), std::find_if(filename.begin(), filename.end(), [](int ch) { return ch == '.'; })); - filename.erase(filename.begin()); - return filename; - } - - - int StringManip::AsInt(std::string value) - { - return atoi(value.c_str()); - } - double StringManip::AsDouble(std::string value) - { - return atof(value.c_str()); - } - bool StringManip::AsBool(std::string value) - { - return value == std::string("true"); - } - +/****************************************************************************** +* File - StringManip.cpp +* Author - Joey Pollack +* Date - 2018/10/12 (y/m/d) +* Mod Date - 2018/10/12 (y/m/d) +* Description - Functions for working with std::string objects +* +******************************************************************************/ + +#include "StringManip.h" + +#include +#include + +namespace jpUtils +{ + + std::string StringManip::ToUpper(std::string str) + { + std::string result = ""; + for (int i = 0; i < (signed)str.length(); i++) + { + if (str[i] >= 97 && str[i] <= 122) + { + result += str[i] - 32; + } + else + { + result += str[i]; + } + } + + return result; + } + + std::string StringManip::ToLower(std::string str) + { + std::string result = ""; + for (int i = 0; i < (signed)str.length(); i++) + { + if (str[i] >= 65 && str[i] <= 90) + { + result += str[i] + 32; + } + else + { + result += str[i]; + } + } + + return result; + } + + std::vector StringManip::Split(std::string str, char delim, int maxSplits) + { + std::vector splits; + + int startPos = 0; + for (int i = 0; i < (signed)str.length(); i++) + { + if (str[i] == delim) + { + if (startPos == i) + continue; + + std::string s = str.substr(startPos, i - startPos); + splits.push_back(s); + startPos = i + 1; + + if (maxSplits > 0 && (signed)splits.size() >= (maxSplits - 1)) + { + break; + } + } + } + + std::string s = str.substr(startPos); + splits.push_back(s); + return splits; + } + + std::string StringManip::Trim(std::string str, std::string delims) + { + str = TrimStart(str, delims); + str = TrimEnd(str, delims); + return str; + } + + + std::string StringManip::TrimStart(std::string str, std::string delims) + { + bool isDelim = true; + while (isDelim && str.length() > 0) + { + isDelim = false; + for (int i = 0; i < (signed)delims.length(); i++) + { + if (str[0] == delims[i]) + { + isDelim = true; + break; + } + } + + if (isDelim) + str.erase(str.begin()); + } + + return str; + } + + + std::string StringManip::TrimEnd(std::string str, std::string delims) + { + bool isDelim = true; + while (isDelim && str.length() > 0) + { + isDelim = false; + for (int i = 0; i < (signed)delims.length(); i++) + { + if (*(str.rbegin()) == delims[i]) + { + isDelim = true; + break; + } + } + + if (isDelim) + str.erase((str.rbegin() + 1).base()); + } + + return str; + } + + std::string StringManip::GetFileNameFromPath(std::string path, bool includeExt) + { + if (path.find("\\") != std::string::npos || path.find("/") != std::string::npos) + { + path.erase(path.begin(), std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '\\' || ch == '/'; }).base()); + } + + if (!includeExt && path.find(".") != std::string::npos) + { + path.erase(std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '.'; }).base(), path.end()); + path.erase(path.begin() + path.size() - 1); + } + + return path; + } + + std::string StringManip::TrimFileNameFromPath(std::string path) + { + path.erase(std::find_if(path.rbegin(), path.rend(), [](int ch) { return ch == '\\' || ch == '/'; }).base(), path.end()); + return path; + } + + std::string StringManip::GetFileExtension(std::string filename) + { + filename.erase(filename.begin(), std::find_if(filename.begin(), filename.end(), [](int ch) { return ch == '.'; })); + filename.erase(filename.begin()); + return filename; + } + + + int StringManip::AsInt(std::string value) + { + return atoi(value.c_str()); + } + double StringManip::AsDouble(std::string value) + { + return atof(value.c_str()); + } + bool StringManip::AsBool(std::string value) + { + return value == std::string("true"); + } + } \ No newline at end of file diff --git a/src/utils/StringManip.h b/src/utils/StringManip.h index 4686469..29b863f 100644 --- a/src/utils/StringManip.h +++ b/src/utils/StringManip.h @@ -1,58 +1,58 @@ -/****************************************************************************** -* File - StringManip.h -* Author - Joey Pollack -* Date - 2018/10/12 (y/m/d) -* Mod Date - 2018/10/12 (y/m/d) -* Description - Functions for working with std::string objects -* -******************************************************************************/ - -#ifndef STRING_MANIP_H_ -#define STRING_MANIP_H_ - -#include -#include - - -namespace jpUtils -{ - class StringManip - { - public: - - // Generic string helpers - // =========================================================== - static std::string ToUpper(std::string str); - static std::string ToLower(std::string str); - - // Splits the string based on the given delimiter, - // maxSplit = -1 for unlimited number of splits - static std::vector Split(std::string str, char delim = ' ', int maxSplits = -1); - - // Trim given delimiters from start and end of string - static std::string Trim(std::string str, std::string delims = " \r\n\t"); - - // Trim given delimiters from start of string - static std::string TrimStart(std::string str, std::string delims = " \r\n\t"); - - // Trim given delimiters from end of string - static std::string TrimEnd(std::string str, std::string delims); - - - // File path helpers - // =========================================================== - static std::string GetFileNameFromPath(std::string path, bool includeExt = true); - static std::string TrimFileNameFromPath(std::string path); - static std::string GetFileExtension(std::string filename); - - // Type Conversion - // =========================================================== - static int AsInt(std::string value); - static double AsDouble(std::string value); - static bool AsBool(std::string value); - - }; - -} - +/****************************************************************************** +* File - StringManip.h +* Author - Joey Pollack +* Date - 2018/10/12 (y/m/d) +* Mod Date - 2018/10/12 (y/m/d) +* Description - Functions for working with std::string objects +* +******************************************************************************/ + +#ifndef STRING_MANIP_H_ +#define STRING_MANIP_H_ + +#include +#include + + +namespace jpUtils +{ + class StringManip + { + public: + + // Generic string helpers + // =========================================================== + static std::string ToUpper(std::string str); + static std::string ToLower(std::string str); + + // Splits the string based on the given delimiter, + // maxSplit = -1 for unlimited number of splits + static std::vector Split(std::string str, char delim = ' ', int maxSplits = -1); + + // Trim given delimiters from start and end of string + static std::string Trim(std::string str, std::string delims = " \r\n\t"); + + // Trim given delimiters from start of string + static std::string TrimStart(std::string str, std::string delims = " \r\n\t"); + + // Trim given delimiters from end of string + static std::string TrimEnd(std::string str, std::string delims); + + + // File path helpers + // =========================================================== + static std::string GetFileNameFromPath(std::string path, bool includeExt = true); + static std::string TrimFileNameFromPath(std::string path); + static std::string GetFileExtension(std::string filename); + + // Type Conversion + // =========================================================== + static int AsInt(std::string value); + static double AsDouble(std::string value); + static bool AsBool(std::string value); + + }; + +} + #endif // STRING_MANIP_H_ \ No newline at end of file