From 1df359175ead4b6fc1940a4250fa76f28c23b05b Mon Sep 17 00:00:00 2001 From: Timofey Khoruzhii Date: Sun, 30 Oct 2022 10:47:27 +0300 Subject: [PATCH 1/8] Add include; fix background color in error --- src/clippy/clippy.cpp | 2 +- src/clippy/target.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clippy/clippy.cpp b/src/clippy/clippy.cpp index 1b9d064..9bd4fb2 100644 --- a/src/clippy/clippy.cpp +++ b/src/clippy/clippy.cpp @@ -25,7 +25,7 @@ void Clippy::Run(const std::vector& args) { for (size_t i = 1; i < args.size(); ++i) { std::cout << args[i] << (i + 1 == args.size() ? "" : ",") << " "; } - std::cout << "}" << std::endl << rang::bg::reset << rang::style::reset; + std::cout << "}" << rang::bg::reset << rang::style::reset << std::endl; } Clippy::TargetPtr Clippy::TryExecuteClippyCommand( diff --git a/src/clippy/target.hpp b/src/clippy/target.hpp index 48fab11..996b03f 100644 --- a/src/clippy/target.hpp +++ b/src/clippy/target.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include From 86a13a170589a83f1205ea606d42fcd00dbb3560 Mon Sep 17 00:00:00 2001 From: Timofey Khoruzhii Date: Sun, 30 Oct 2022 11:00:04 +0300 Subject: [PATCH 2/8] Fix bug find target --- src/clippy/config.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/clippy/config.cpp b/src/clippy/config.cpp index 1940174..e7499e8 100644 --- a/src/clippy/config.cpp +++ b/src/clippy/config.cpp @@ -25,11 +25,13 @@ std::unique_ptr Config::GetTarget( std::vector target_commands; target_commands.emplace_back("cd " + initial_directory_); - bool target_begin = false; + bool target_exists = false; + bool in_target = false; while (std::getline(in, current)) { if (current == target + ":") { - target_begin = true; + target_exists = true; + in_target = true; continue; } @@ -37,18 +39,18 @@ std::unique_ptr Config::GetTarget( continue; } - if (!std::isspace(current[0]) && target_begin) { - target_begin = false; + if (!std::isspace(current[0])) { + in_target = false; } - if (target_begin) { + if (in_target) { target_commands.emplace_back(Strip(std::move(current))); } else if (current[0] == '!') { target_commands.emplace_back(current.substr(1, current.size() - 1)); } } - if (!target_begin) { + if (!target_exists) { return nullptr; } From dbd34e0817327997fe79b8651615e5675e2baa92 Mon Sep 17 00:00:00 2001 From: Timofey Khoruzhii Date: Wed, 28 Dec 2022 14:56:09 +0300 Subject: [PATCH 3/8] add json lib --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae92603..25f0169 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB_RECURSE SOURCES_FILES src/*) add_executable(clippy_terminal ${SOURCES_FILES}) target_include_directories(clippy_terminal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(clippy_terminal cppshell rang) +target_link_libraries(clippy_terminal cppshell rang jsoncons) install(TARGETS clippy_terminal DESTINATION bin) @@ -26,3 +26,11 @@ FetchContent_Declare( GIT_TAG origin/master ) FetchContent_MakeAvailable(rang) + +FetchContent_Declare( + jsoncons + GIT_REPOSITORY https://github.com/danielaparker/jsoncons + GIT_TAG origin/master +) +set(JSONCONS_BUILD_TESTS OFF) +FetchContent_MakeAvailable(jsoncons) From a2186366b81d5639e5c048f41e4c75e64f7a0ee9 Mon Sep 17 00:00:00 2001 From: Timofey Khoruzhii Date: Wed, 28 Dec 2022 14:58:14 +0300 Subject: [PATCH 4/8] move to json; add name for projects --- src/clippy/clippy.cpp | 42 ++++++++++++---- src/clippy/project_list.cpp | 98 ++++++++++++++++++++++++++++++------- src/clippy/project_list.hpp | 39 +++++++++++++-- src/clippy/target.hpp | 43 +++++++++++++--- src/main.cpp | 4 ++ src/utils/filesystem.hpp | 22 +++++++++ 6 files changed, 210 insertions(+), 38 deletions(-) create mode 100644 src/utils/filesystem.hpp diff --git a/src/clippy/clippy.cpp b/src/clippy/clippy.cpp index 9bd4fb2..d67f08b 100644 --- a/src/clippy/clippy.cpp +++ b/src/clippy/clippy.cpp @@ -28,12 +28,10 @@ void Clippy::Run(const std::vector& args) { std::cout << "}" << rang::bg::reset << rang::style::reset << std::endl; } -Clippy::TargetPtr Clippy::TryExecuteClippyCommand( - const std::vector& args) { +Clippy::TargetPtr Clippy::TryExecuteClippyCommand(const std::vector& args) { using namespace utils::parametres; using namespace clippy::targets; - if (CheckPatternParametres(args, Parameter::Skip, "help", - Parameter::Anything)) { + if (CheckPatternParametres(args, Parameter::Skip, "help", Parameter::Anything)) { std::cout << "Hello I'm clippy" << std::endl; std::cout << "Parametres: { "; for (size_t i = 0; i < args.size(); ++i) { @@ -44,8 +42,7 @@ Clippy::TargetPtr Clippy::TryExecuteClippyCommand( return std::make_unique(); } - if (CheckPatternParametres(args, Parameter::Skip, "cfg", - Parameter::Anything)) { + if (CheckPatternParametres(args, Parameter::Skip, "cfg", Parameter::Anything)) { LoadProjects(); auto p = projects_->GetCurrentProject(); @@ -56,17 +53,42 @@ Clippy::TargetPtr Clippy::TryExecuteClippyCommand( } } - if (CheckPatternParametres(args, Parameter::Skip, "crt", - Parameter::Anything)) { + if (CheckPatternParametres(args, Parameter::Skip, "crt", Parameter::Anything)) { LoadProjects(); return std::make_unique(projects_.value()); } + if (CheckPatternParametres(args, Parameter::Skip, "prj", Parameter::Anything)) { + if (args.size() != 3) { + return nullptr; + } + std::string name_project = args[2]; + + LoadProjects(); + + for (auto& project : projects_->GetProjects()) { + if (project.name == name_project) { + std::cout << "Find: " << project.root_project << " " << project.configuration_file << std::endl; + } + } + } + + if (CheckPatternParametres(args, Parameter::Skip, "list", Parameter::Nothing)) { + LoadProjects(); + + return std::make_unique(projects_.value()); + } + + if (CheckPatternParametres(args, Parameter::Skip, "setname", Parameter::Anything)) { + LoadProjects(); + + return std::make_unique(projects_.value(), args[2]); + } + return nullptr; } -Clippy::TargetPtr Clippy::GetScriptTarget( - const std::vector& args) { +Clippy::TargetPtr Clippy::GetScriptTarget(const std::vector& args) { LoadProjects(); auto p = projects_->GetCurrentProject(); diff --git a/src/clippy/project_list.cpp b/src/clippy/project_list.cpp index e2ad9f3..f8f6c3a 100644 --- a/src/clippy/project_list.cpp +++ b/src/clippy/project_list.cpp @@ -1,17 +1,22 @@ #include #include +#include +#include #include +#include #include #include #include #include -Config ProjectList::GetNewConfig( - const std::filesystem::path& config_directory) { +#include +#include + +Config ProjectList::GetNewConfig(const std::filesystem::path& config_directory) { std::lock_guard guard(lock_file_); - LoadWithouLock(); + LoadWithoutLock(); std::mt19937 rnd(std::chrono::system_clock::now().time_since_epoch().count()); @@ -52,24 +57,21 @@ Config ProjectList::GetNewConfig( } auto path_to_config = config_directory / filename; - - std::ofstream out(path_, std::ios::app); - out << std::filesystem::current_path() << " " << path_to_config << std::endl; - out.close(); - projects_.emplace_back(std::filesystem::current_path(), path_to_config); + SaveConfig(); + return {path_to_config, std::filesystem::current_path()}; } void ProjectList::Load() { std::lock_guard guard(lock_file_); - LoadWithouLock(); + LoadWithoutLock(); } -void ProjectList::LoadWithouLock() { - std::ifstream in(path_); +void ProjectList::OldLoadConfig(const std::string& data) { + std::stringstream in(data); Project current; @@ -78,15 +80,77 @@ void ProjectList::LoadWithouLock() { } } -std::optional ProjectList::GetCurrentProject() { +void ProjectList::SaveConfig() { + jsoncons::json result; + + result["version"] = "0.1"; + result["projects"] = std::vector(); + + for (auto& project : projects_) { + jsoncons::json current; + current["path_to_config"] = std::string(project.configuration_file); + current["path_root_project"] = std::string(project.root_project); + + std::cout << (bool)project.name << std::endl; + if (project.name && !project.name.value().empty()) { + current["name"] = project.name.value(); + } + + result["projects"].emplace_back(current); + } + + std::ofstream out(path_); + out << result.to_string(); +} + +void ProjectList::LoadConfig(const jsoncons::json& data) { + if (data["version"] != "0.1") { + throw std::logic_error("unsupported version config"); + } + + Project current; + for (auto& project : data["projects"].array_range()) { + current.configuration_file = project["path_to_config"].as(); + current.root_project = project["path_root_project"].as(); + if (project.contains("name")) { + current.name = project["name"].as(); + } else { + current.name = std::nullopt; + } + + projects_.emplace_back(current); + } +} + +void ProjectList::LoadWithoutLock() { + auto data = utils::LoadFile(path_); + + try { + LoadConfig(jsoncons::json::parse(data)); + } catch (...) { + std::cout << "I can't read project lists. Try fix it?"; + + std::string result; + std::getline(std::cin, result); + + if (result == "y") { + OldLoadConfig(data); + SaveConfig(); + } + } +} + +Project* ProjectList::GetCurrentProject_() { auto current_path = std::filesystem::current_path(); - std::optional result; - auto UpdateResult = [&result](Project p) { - if (!result.has_value()) { - result = p; + Project* result = nullptr; + auto UpdateResult = [&result](Project& p) { + if (!result) { + result = &p; } else { - result = std::max(p, result.value()); + if (*result > p) { + result = &p; + } } }; diff --git a/src/clippy/project_list.hpp b/src/clippy/project_list.hpp index 5cefefe..bf6094f 100644 --- a/src/clippy/project_list.hpp +++ b/src/clippy/project_list.hpp @@ -1,12 +1,15 @@ #pragma once #include +#include #include #include #include #include +#include + struct Project { Project() {} @@ -15,10 +18,13 @@ struct Project { std::strong_ordering operator<=>(const Project&) const = default; - Config GetConfig() { return Config{configuration_file, root_project}; } + Config GetConfig() { + return Config{configuration_file, root_project}; + } std::filesystem::path root_project; std::filesystem::path configuration_file; + std::optional name; }; class ProjectList { @@ -29,13 +35,38 @@ class ProjectList { Config GetNewConfig(const std::filesystem::path& config_directory); - const std::vector& GetProjects() const { return projects_; } + const std::vector& GetProjects() const { + return projects_; + } - std::optional GetCurrentProject(); + std::optional GetCurrentProject() { + auto* p = GetCurrentProject_(); + if (p) { + return *p; + } else { + return std::nullopt; + } + } + + void SetNameCurrentProject(const std::string& name) { + auto* p = GetCurrentProject_(); + if (p) { + p->name = name; + SaveConfig(); + } else { + throw std::logic_error("Not exists current project"); + } + } private: + Project* GetCurrentProject_(); + + void OldLoadConfig(const std::string&); + void SaveConfig(); + void Load(); - void LoadWithouLock(); + void LoadConfig(const jsoncons::json&); + void LoadWithoutLock(); private: std::filesystem::path path_; diff --git a/src/clippy/target.hpp b/src/clippy/target.hpp index 996b03f..a8a46fa 100644 --- a/src/clippy/target.hpp +++ b/src/clippy/target.hpp @@ -32,7 +32,9 @@ class OpenProjectConfig : public Target { public: OpenProjectConfig(Config config) : config_(config) {} - void Execute() override { config_.Edit(); } + void Execute() override { + config_.Edit(); + } ~OpenProjectConfig() override {} private: @@ -76,15 +78,13 @@ class RunShellScript : public Target { cppshell::Shell s("bash", tmp_path); for (auto& command : commands_) { - std::cout << rang::fg::green << rang::style::bold << rang::bgB::blue << "->" << rang::fg::reset - << rang::bg::reset << " " << command << rang::style::reset << std::endl; + std::cout << rang::fg::green << rang::style::bold << rang::bgB::blue << "->" << rang::fg::reset << rang::bg::reset + << " " << command << rang::style::reset << std::endl; s.Execute(command); if (int err = s.GetExitCodeLastCommand(); err != 0) { - std::cout << rang::fg::red << rang::style::bold - << "Command exit with code " << err << rang::fg::reset + std::cout << rang::fg::red << rang::style::bold << "Command exit with code " << err << rang::fg::reset << rang::style::reset << std::endl; - std::cout << rang::fg::blue << rang::style::bold - << "Continue execution? [Y/n] " << rang::fg::reset + std::cout << rang::fg::blue << rang::style::bold << "Continue execution? [Y/n] " << rang::fg::reset << rang::style::reset; std::string result; @@ -103,4 +103,33 @@ class RunShellScript : public Target { private: std::deque commands_; }; + +class PrintListProjects : public Target { + public: + PrintListProjects(ProjectList& projects) : projects_(projects) {} + + void Execute() override { + for (auto& project : projects_.GetProjects()) { + std::cout << std::setw(10) << project.name.value_or("-") << " " << std::setw(50) << project.root_project << " " + << std::setw(70) << project.configuration_file << std::endl; + } + } + + private: + ProjectList& projects_; +}; + +class SetNameProject : public Target { + public: + SetNameProject(ProjectList& projects, std::string new_name) + : projects_(projects), new_name_(std::move(new_name)) {} + + void Execute() override { + projects_.SetNameCurrentProject(new_name_); + } + + private: + ProjectList& projects_; + std::string new_name_; +}; } // namespace clippy::targets diff --git a/src/main.cpp b/src/main.cpp index 267527e..239901f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,10 @@ #include +#include + +#include + int main(int argc, char* argv[]) { std::signal(SIGPIPE, SIG_IGN); diff --git a/src/utils/filesystem.hpp b/src/utils/filesystem.hpp new file mode 100644 index 0000000..e65acd2 --- /dev/null +++ b/src/utils/filesystem.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace utils { +inline std::string LoadFile(const std::filesystem::path& file) { + std::ifstream in(file); + + std::string result; + std::string tmp; + + while (std::getline(in, tmp)) { + result += tmp; + result += "\n"; + } + + return result; +} + +} // namespace utils From 75eb05bd1ae241f270781d6b801133dd9244ad65 Mon Sep 17 00:00:00 2001 From: Timofey Khoruzhii Date: Mon, 9 Jan 2023 11:42:28 +0300 Subject: [PATCH 5/8] upd --- CMakeLists.txt | 11 ++++- src/clippy/clippy.cpp | 28 +++++++------ src/clippy/project_list.cpp | 83 ++++++++++++++++--------------------- src/clippy/project_list.hpp | 34 +++++++++++++++ src/clippy/target.hpp | 62 +++++++++++++++++++++++---- src/utils/filesystem.hpp | 49 +++++++++++++++++++++- src/utils/random.hpp | 12 ++++++ 7 files changed, 206 insertions(+), 73 deletions(-) create mode 100644 src/utils/random.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 25f0169..60e2ec1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB_RECURSE SOURCES_FILES src/*) add_executable(clippy_terminal ${SOURCES_FILES}) target_include_directories(clippy_terminal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(clippy_terminal cppshell rang jsoncons) +target_link_libraries(clippy_terminal cppshell tmuxub rang jsoncons) install(TARGETS clippy_terminal DESTINATION bin) @@ -20,6 +20,13 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cppshell) +FetchContent_Declare( + tmuxub + GIT_REPOSITORY https://gitlab.com/Onyad/tmuxub + GIT_TAG origin/main +) +FetchContent_MakeAvailable(tmuxub) + FetchContent_Declare( rang GIT_REPOSITORY https://github.com/agauniyal/rang.git @@ -27,10 +34,10 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(rang) +set(JSONCONS_BUILD_TESTS OFF CACHE INTERNAL "Turn off tests") FetchContent_Declare( jsoncons GIT_REPOSITORY https://github.com/danielaparker/jsoncons GIT_TAG origin/master ) -set(JSONCONS_BUILD_TESTS OFF) FetchContent_MakeAvailable(jsoncons) diff --git a/src/clippy/clippy.cpp b/src/clippy/clippy.cpp index d67f08b..b1c8b94 100644 --- a/src/clippy/clippy.cpp +++ b/src/clippy/clippy.cpp @@ -39,6 +39,8 @@ Clippy::TargetPtr Clippy::TryExecuteClippyCommand(const std::vector } std::cout << "}" << std::endl; + std::cout << "You can use:\n cfg\n crt\n prj\n list\n setname\n loadcfg\n op" << std::endl; + return std::make_unique(); } @@ -59,18 +61,8 @@ Clippy::TargetPtr Clippy::TryExecuteClippyCommand(const std::vector } if (CheckPatternParametres(args, Parameter::Skip, "prj", Parameter::Anything)) { - if (args.size() != 3) { - return nullptr; - } - std::string name_project = args[2]; - - LoadProjects(); - - for (auto& project : projects_->GetProjects()) { - if (project.name == name_project) { - std::cout << "Find: " << project.root_project << " " << project.configuration_file << std::endl; - } - } + // TODO description current project or project by name + return nullptr; } if (CheckPatternParametres(args, Parameter::Skip, "list", Parameter::Nothing)) { @@ -85,6 +77,18 @@ Clippy::TargetPtr Clippy::TryExecuteClippyCommand(const std::vector return std::make_unique(projects_.value(), args[2]); } + if (CheckPatternParametres(args, Parameter::Skip, "loadcfg", Parameter::Nothing)) { + LoadProjects(); + + return std::make_unique(projects_.value()); + } + + if (CheckPatternParametres(args, Parameter::Skip, "op", Parameter::Anything)) { + LoadProjects(); + + return std::make_unique(projects_.value(), args[2]); + } + return nullptr; } diff --git a/src/clippy/project_list.cpp b/src/clippy/project_list.cpp index f8f6c3a..31f628c 100644 --- a/src/clippy/project_list.cpp +++ b/src/clippy/project_list.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -18,45 +20,8 @@ Config ProjectList::GetNewConfig(const std::filesystem::path& config_directory) LoadWithoutLock(); - std::mt19937 rnd(std::chrono::system_clock::now().time_since_epoch().count()); + auto path_to_config = utils::filesystem::GenerateFile(config_directory); - auto RandomSymbol = [&rnd]() { - int n = rnd() % (26 + 26 + 10); - if (n < 26) { - return 'a' + n; - } else if (n < 26 + 26) { - return 'A' + n - 26; - } else { - return '0' + n - 26 - 26; - } - }; - - auto GenerateFilename = [&rnd, &RandomSymbol]() { - std::string filename; - for (size_t i = 0; i < 6; ++i) { - filename += RandomSymbol(); - } - - return filename; - }; - - auto filename = GenerateFilename(); - while (true) { - bool exist_config = false; - for (auto& project : projects_) { - if (filename == project.configuration_file.filename()) { - exist_config = true; - break; - } - } - if (!exist_config) { - break; - } - - filename = GenerateFilename(); - } - - auto path_to_config = config_directory / filename; projects_.emplace_back(std::filesystem::current_path(), path_to_config); SaveConfig(); @@ -80,6 +45,13 @@ void ProjectList::OldLoadConfig(const std::string& data) { } } +template +void UpdateField(jsoncons::json& data, const std::string& field, std::optional member) { + if (member) { + data[field] = static_cast(member.value()); + } +} + void ProjectList::SaveConfig() { jsoncons::json result; @@ -91,10 +63,8 @@ void ProjectList::SaveConfig() { current["path_to_config"] = std::string(project.configuration_file); current["path_root_project"] = std::string(project.root_project); - std::cout << (bool)project.name << std::endl; - if (project.name && !project.name.value().empty()) { - current["name"] = project.name.value(); - } + UpdateField(current, "name", project.name); + UpdateField(current, "open_script", project.open_script); result["projects"].emplace_back(current); } @@ -103,6 +73,11 @@ void ProjectList::SaveConfig() { out << result.to_string(); } +template +std::optional GetOptionalField(const jsoncons::json& data, std::string field) { + return data.contains(field) ? std::make_optional(data[field].as()) : std::nullopt; +} + void ProjectList::LoadConfig(const jsoncons::json& data) { if (data["version"] != "0.1") { throw std::logic_error("unsupported version config"); @@ -112,18 +87,17 @@ void ProjectList::LoadConfig(const jsoncons::json& data) { for (auto& project : data["projects"].array_range()) { current.configuration_file = project["path_to_config"].as(); current.root_project = project["path_root_project"].as(); - if (project.contains("name")) { - current.name = project["name"].as(); - } else { - current.name = std::nullopt; - } + + current.name = GetOptionalField(project, "name"); + current.open_script = GetOptionalField(project, "open_script"); projects_.emplace_back(current); } } void ProjectList::LoadWithoutLock() { - auto data = utils::LoadFile(path_); + projects_.clear(); + auto data = utils::filesystem::LoadFile(path_); try { LoadConfig(jsoncons::json::parse(data)); @@ -172,3 +146,16 @@ Project* ProjectList::GetCurrentProject_() { return result; } + +Project* ProjectList::GetProjectByName_(const std::string& name) { + Project* result = nullptr; + for (auto& project : projects_) { + if (!project.name) { + continue; + } + if (project.name == name) { + result = &project; + } + } + return result; +} diff --git a/src/clippy/project_list.hpp b/src/clippy/project_list.hpp index bf6094f..b65d4db 100644 --- a/src/clippy/project_list.hpp +++ b/src/clippy/project_list.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,6 +10,9 @@ #include #include +#include "utils/config_path.hpp" +#include "utils/editor.hpp" +#include "utils/filesystem.hpp" struct Project { Project() {} @@ -25,6 +29,7 @@ struct Project { std::filesystem::path root_project; std::filesystem::path configuration_file; std::optional name; + std::optional open_script; }; class ProjectList { @@ -58,8 +63,37 @@ class ProjectList { } } + std::filesystem::path GetCurrentLoadConfig() { + auto* p = GetCurrentProject_(); + if (p) { + if (p->open_script) { + return p->open_script.value(); + } else { + auto scripts_path = utils::GetProjectDirectory() / "scripts"; + { + std::lock_guard lock(lock_file_); + p->open_script = utils::filesystem::GenerateFile(scripts_path); + SaveConfig(); + } + return p->open_script.value(); + } + } else { + throw std::logic_error("Not exists current project"); + } + } + + std::optional GetProjectByName(const std::string& name) { + auto* p = GetProjectByName_(name); + if (p) { + return *p; + } else { + return std::nullopt; + } + } + private: Project* GetCurrentProject_(); + Project* GetProjectByName_(const std::string&); void OldLoadConfig(const std::string&); void SaveConfig(); diff --git a/src/clippy/target.hpp b/src/clippy/target.hpp index a8a46fa..d2f9b7e 100644 --- a/src/clippy/target.hpp +++ b/src/clippy/target.hpp @@ -14,6 +14,9 @@ #include #include #include +#include "utils/filesystem.hpp" + +#include namespace clippy::targets { class Target { @@ -61,7 +64,8 @@ class CreateProjectConfig : public Target { class RunShellScript : public Target { public: template