diff --git a/CMakeLists.txt b/CMakeLists.txt index 952f8b9..ae92603 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,27 @@ cmake_minimum_required(VERSION 3.14) project(clippy_terminal) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) 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) install(TARGETS clippy_terminal DESTINATION bin) + +include(FetchContent) +FetchContent_Declare( + cppshell + GIT_REPOSITORY https://gitlab.com/Onyad/cppshell + GIT_TAG origin/main +) +FetchContent_MakeAvailable(cppshell) + +FetchContent_Declare( + rang + GIT_REPOSITORY https://github.com/agauniyal/rang.git + GIT_TAG origin/master +) +FetchContent_MakeAvailable(rang) diff --git a/src/clippy/clippy.cpp b/src/clippy/clippy.cpp new file mode 100644 index 0000000..1b9d064 --- /dev/null +++ b/src/clippy/clippy.cpp @@ -0,0 +1,91 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +void Clippy::Run(const std::vector& args) { + if (auto result = TryExecuteClippyCommand(args); result) { + result->Execute(); + return; + } + if (auto result = GetScriptTarget(args); result) { + result->Execute(); + return; + } + + std::cout << rang::bg::red << rang::style::bold << "Unsupported parameters { "; + 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; +} + +Clippy::TargetPtr Clippy::TryExecuteClippyCommand( + const std::vector& args) { + using namespace utils::parametres; + using namespace clippy::targets; + 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) { + std::cout << args[i] << (i + 1 == args.size() ? "" : ",") << " "; + } + std::cout << "}" << std::endl; + + return std::make_unique(); + } + + if (CheckPatternParametres(args, Parameter::Skip, "cfg", + Parameter::Anything)) { + LoadProjects(); + auto p = projects_->GetCurrentProject(); + + if (p.has_value()) { + return std::make_unique(p->GetConfig()); + } else { + return std::make_unique(projects_.value()); + } + } + + if (CheckPatternParametres(args, Parameter::Skip, "crt", + Parameter::Anything)) { + LoadProjects(); + return std::make_unique(projects_.value()); + } + + return nullptr; +} + +Clippy::TargetPtr Clippy::GetScriptTarget( + const std::vector& args) { + LoadProjects(); + auto p = projects_->GetCurrentProject(); + + if (!p.has_value() || args.size() < 2) { + return nullptr; + } + + auto cfg = p->GetConfig(); + auto result = cfg.GetTarget(args[1]); + + if (result && enable_aliases_) { + result->PushFront("shopt -s expand_aliases"); + } + + return result; +} + +void Clippy::LoadProjects() { + if (!projects_.has_value()) { + projects_.emplace(utils::GetProjectDirectory() / "projects"); + } +} diff --git a/src/clippy/clippy.hpp b/src/clippy/clippy.hpp new file mode 100644 index 0000000..7dc83f0 --- /dev/null +++ b/src/clippy/clippy.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include +#include +#include + +class Clippy { + public: + Clippy(bool enable_aliases = true) : enable_aliases_(enable_aliases) {} + + void Run(const std::vector& args); + + using TargetPtr = std::unique_ptr; + private: + TargetPtr TryExecuteClippyCommand(const std::vector& args); + TargetPtr GetScriptTarget(const std::vector& args); + + void LoadProjects(); + + bool enable_aliases_; + std::optional projects_; +}; diff --git a/src/clippy/config.cpp b/src/clippy/config.cpp new file mode 100644 index 0000000..1940174 --- /dev/null +++ b/src/clippy/config.cpp @@ -0,0 +1,56 @@ +#include +#include + +#include +#include + +std::string Strip(std::string s) { + while (!s.empty() && std::isspace(s.back())) { + s.pop_back(); + } + std::reverse(s.begin(), s.end()); + while (!s.empty() && std::isspace(s.back())) { + s.pop_back(); + } + std::reverse(s.begin(), s.end()); + + return s; +} + +std::unique_ptr Config::GetTarget( + const std::string& target) { + std::ifstream in(path_); + + std::string current; + std::vector target_commands; + target_commands.emplace_back("cd " + initial_directory_); + + bool target_begin = false; + + while (std::getline(in, current)) { + if (current == target + ":") { + target_begin = true; + continue; + } + + if (current.empty()) { + continue; + } + + if (!std::isspace(current[0]) && target_begin) { + target_begin = false; + } + + if (target_begin) { + 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) { + return nullptr; + } + + return std::make_unique(target_commands); +} diff --git a/src/clippy/config.hpp b/src/clippy/config.hpp new file mode 100644 index 0000000..01f9b27 --- /dev/null +++ b/src/clippy/config.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include + +namespace clippy::targets { +class Target; +class RunShellScript; +} + +class Config { + public: + Config(std::string path, std::string initial_directory) + : path_(std::move(path)), initial_directory_(initial_directory) {} + + void Edit() { utils::OpenEditor(path_); } + + std::unique_ptr GetTarget(const std::string& target); + + private: + std::string path_; + std::string initial_directory_; +}; diff --git a/src/clippy/project_list.cpp b/src/clippy/project_list.cpp new file mode 100644 index 0000000..e2ad9f3 --- /dev/null +++ b/src/clippy/project_list.cpp @@ -0,0 +1,110 @@ +#include +#include +#include + +#include +#include +#include +#include + +Config ProjectList::GetNewConfig( + const std::filesystem::path& config_directory) { + std::lock_guard guard(lock_file_); + + LoadWithouLock(); + + std::mt19937 rnd(std::chrono::system_clock::now().time_since_epoch().count()); + + 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; + + 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); + + return {path_to_config, std::filesystem::current_path()}; +} + +void ProjectList::Load() { + std::lock_guard guard(lock_file_); + + LoadWithouLock(); +} + +void ProjectList::LoadWithouLock() { + std::ifstream in(path_); + + Project current; + + while (in >> current.root_project >> current.configuration_file) { + projects_.push_back(current); + } +} + +std::optional ProjectList::GetCurrentProject() { + auto current_path = std::filesystem::current_path(); + + std::optional result; + auto UpdateResult = [&result](Project p) { + if (!result.has_value()) { + result = p; + } else { + result = std::max(p, result.value()); + } + }; + + for (auto& project : projects_) { + auto prefix_current_path = current_path; + + if (project.root_project == "/") { + UpdateResult(project); + continue; + } + + while (prefix_current_path != "/") { + if (prefix_current_path == project.root_project) { + UpdateResult(project); + } + prefix_current_path = prefix_current_path.parent_path(); + } + } + + return result; +} diff --git a/src/clippy/project_list.hpp b/src/clippy/project_list.hpp new file mode 100644 index 0000000..5cefefe --- /dev/null +++ b/src/clippy/project_list.hpp @@ -0,0 +1,45 @@ +#pragma once +#include + +#include + +#include +#include +#include + +struct Project { + Project() {} + + Project(std::filesystem::path root_project, std::filesystem::path configuration_file) + : root_project(root_project), configuration_file(configuration_file) {} + + std::strong_ordering operator<=>(const Project&) const = default; + + Config GetConfig() { return Config{configuration_file, root_project}; } + + std::filesystem::path root_project; + std::filesystem::path configuration_file; +}; + +class ProjectList { + public: + ProjectList(std::filesystem::path path) : path_(std::move(path)), lock_file_(path_) { + Load(); + } + + Config GetNewConfig(const std::filesystem::path& config_directory); + + const std::vector& GetProjects() const { return projects_; } + + std::optional GetCurrentProject(); + + private: + void Load(); + void LoadWithouLock(); + + private: + std::filesystem::path path_; + utils::filesystem::LockFile lock_file_; + + std::vector projects_; +}; diff --git a/src/clippy/target.hpp b/src/clippy/target.hpp new file mode 100644 index 0000000..48fab11 --- /dev/null +++ b/src/clippy/target.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace clippy::targets { +class Target { + public: + virtual void Execute() = 0; + virtual ~Target() = default; +}; + +class EmptyTarget : public Target { + public: + void Execute() override {} + ~EmptyTarget() override {} +}; + +class OpenProjectConfig : public Target { + public: + OpenProjectConfig(Config config) : config_(config) {} + + void Execute() override { config_.Edit(); } + ~OpenProjectConfig() override {} + + private: + Config config_; +}; + +class CreateProjectConfig : public Target { + public: + CreateProjectConfig(ProjectList& projects) : projects_(projects) {} + + void Execute() override { + auto scripts_path = utils::GetProjectDirectory() / "scripts"; + std::filesystem::create_directories(scripts_path); + + auto config = projects_.GetNewConfig(scripts_path); + config.Edit(); + } + ~CreateProjectConfig() override {} + + private: + ProjectList& projects_; +}; + +class RunShellScript : public Target { + public: + template