Merge branch 'dev' into 'main'

Dev

See merge request Onyad/clippy-terminal!1
This commit is contained in:
Timofey Khoruzhii 2022-10-30 07:33:21 +00:00
commit fc53f9bfb1
16 changed files with 655 additions and 4 deletions

View file

@ -2,11 +2,27 @@ cmake_minimum_required(VERSION 3.14)
project(clippy_terminal) project(clippy_terminal)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
file(GLOB_RECURSE SOURCES_FILES src/*) file(GLOB_RECURSE SOURCES_FILES src/*)
add_executable(clippy_terminal ${SOURCES_FILES}) add_executable(clippy_terminal ${SOURCES_FILES})
target_include_directories(clippy_terminal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(clippy_terminal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(clippy_terminal cppshell rang)
install(TARGETS clippy_terminal DESTINATION bin) 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)

91
src/clippy/clippy.cpp Normal file
View file

@ -0,0 +1,91 @@
#include <clippy/clippy.hpp>
#include <clippy/target.hpp>
#include <utils/parametres.hpp>
#include <utils/config_path.hpp>
#include <iostream>
#include <memory>
#include <vector>
#include <filesystem>
#include <rang.hpp>
void Clippy::Run(const std::vector<std::string>& 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<std::string>& 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<EmptyTarget>();
}
if (CheckPatternParametres(args, Parameter::Skip, "cfg",
Parameter::Anything)) {
LoadProjects();
auto p = projects_->GetCurrentProject();
if (p.has_value()) {
return std::make_unique<OpenProjectConfig>(p->GetConfig());
} else {
return std::make_unique<CreateProjectConfig>(projects_.value());
}
}
if (CheckPatternParametres(args, Parameter::Skip, "crt",
Parameter::Anything)) {
LoadProjects();
return std::make_unique<CreateProjectConfig>(projects_.value());
}
return nullptr;
}
Clippy::TargetPtr Clippy::GetScriptTarget(
const std::vector<std::string>& 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");
}
}

25
src/clippy/clippy.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <clippy/project_list.hpp>
#include <clippy/target.hpp>
#include <vector>
#include <string>
#include <memory>
class Clippy {
public:
Clippy(bool enable_aliases = true) : enable_aliases_(enable_aliases) {}
void Run(const std::vector<std::string>& args);
using TargetPtr = std::unique_ptr<clippy::targets::Target>;
private:
TargetPtr TryExecuteClippyCommand(const std::vector<std::string>& args);
TargetPtr GetScriptTarget(const std::vector<std::string>& args);
void LoadProjects();
bool enable_aliases_;
std::optional<ProjectList> projects_;
};

56
src/clippy/config.cpp Normal file
View file

@ -0,0 +1,56 @@
#include <clippy/config.hpp>
#include <clippy/target.hpp>
#include <fstream>
#include <algorithm>
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<clippy::targets::RunShellScript> Config::GetTarget(
const std::string& target) {
std::ifstream in(path_);
std::string current;
std::vector<std::string> 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<clippy::targets::RunShellScript>(target_commands);
}

25
src/clippy/config.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <utils/editor.hpp>
#include <string>
#include <memory>
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<clippy::targets::RunShellScript> GetTarget(const std::string& target);
private:
std::string path_;
std::string initial_directory_;
};

110
src/clippy/project_list.cpp Normal file
View file

@ -0,0 +1,110 @@
#include <clippy/project_list.hpp>
#include <filesystem>
#include <utils/lock_file.hpp>
#include <fstream>
#include <mutex>
#include <random>
#include <chrono>
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<Project> ProjectList::GetCurrentProject() {
auto current_path = std::filesystem::current_path();
std::optional<Project> 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;
}

View file

@ -0,0 +1,45 @@
#pragma once
#include <clippy/config.hpp>
#include <utils/lock_file.hpp>
#include <vector>
#include <filesystem>
#include <optional>
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<Project>& GetProjects() const { return projects_; }
std::optional<Project> GetCurrentProject();
private:
void Load();
void LoadWithouLock();
private:
std::filesystem::path path_;
utils::filesystem::LockFile lock_file_;
std::vector<Project> projects_;
};

105
src/clippy/target.hpp Normal file
View file

@ -0,0 +1,105 @@
#pragma once
#include <clippy/project_list.hpp>
#include <clippy/config.hpp>
#include <utils/editor.hpp>
#include <utils/config_path.hpp>
#include <cppshell/shell.hpp>
#include <rang.hpp>
#include <iostream>
#include <ranges>
#include <functional>
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 <template <typename, typename...> class C, typename... Params>
RunShellScript(const C<std::string, Params...>& commands) : commands_(commands.begin(), commands.end()) {}
void PushBack(const std::string& command) {
commands_.push_back(command);
}
void PushFront(const std::string& command) {
commands_.push_front(command);
}
void Execute() override {
auto tmp_path = utils::GetProjectDirectory() / "tmp";
std::filesystem::create_directories(tmp_path);
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;
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
<< rang::style::reset << std::endl;
std::cout << rang::fg::blue << rang::style::bold
<< "Continue execution? [Y/n] " << rang::fg::reset
<< rang::style::reset;
std::string result;
std::getline(std::cin, result);
if (result == "" || result == "y") {
continue;
} else {
return;
}
}
}
}
~RunShellScript() override {}
private:
std::deque<std::string> commands_;
};
} // namespace clippy::targets

View file

@ -1,5 +1,17 @@
#include <iostream> #include <clippy/clippy.hpp>
#include <utils/parametres.hpp>
int main() { #include <vector>
std::cout << "Hello I'm clippy" << std::endl; #include <string>
#include <csignal>
int main(int argc, char* argv[]) {
std::signal(SIGPIPE, SIG_IGN);
using namespace utils::parametres;
auto params = ParseInputParameters(argc, argv);
Clippy clippy;
clippy.Run(params);
} }

20
src/utils/config_path.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <vector>
#include <ranges>
#include <filesystem>
namespace utils {
inline std::filesystem::path GetProjectDirectory() {
namespace fs = std::filesystem;
using std::filesystem::path;
path config_path =
std::string(std::getenv("HOME")) + "/.local/share/clippy-terminal/";
fs::create_directories(config_path);
return config_path;
}
} // namespace utils

22
src/utils/editor.cpp Normal file
View file

@ -0,0 +1,22 @@
#include <utils/editor.hpp>
#include <utils/config_path.hpp>
#include <cppshell/shell.hpp>
namespace utils {
void OpenEditor(const std::string& file) {
std::string editors[] = {"nvim", "vim", "vi"};
auto tmp_path = utils::GetProjectDirectory() / "tmp";
std::filesystem::create_directories(tmp_path);
cppshell::Shell shell("bash", tmp_path);
for (auto& editor : editors) {
shell.Execute(editor + " " + file);
if (shell.GetExitCodeLastCommand() == 0) {
break;
}
}
}
} // namespace utils

7
src/utils/editor.hpp Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace utils {
void OpenEditor(const std::string& file);
}

18
src/utils/lock_file.cpp Normal file
View file

@ -0,0 +1,18 @@
#include <utils/lock_file.hpp>
#include <unistd.h>
#include <sys/file.h>
namespace utils::filesystem {
void LockFile::Lock() {
fd_ = open(path_.data(), O_RDWR | O_CREAT, 0666);
while (flock(fd_, LOCK_EX) != 0) {
}
}
void LockFile::Unlock() {
while (flock(fd_, LOCK_UN) != 0) {
}
close(fd_);
}
} // namespace utils::filesystem

19
src/utils/lock_file.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace utils::filesystem {
class LockFile {
public:
LockFile(std::string path) : path_(std::move(path) + ".lock") {}
void Lock();
void lock() { Lock(); }
void Unlock();
void unlock() { Unlock(); }
private:
std::string path_;
int fd_ = -1;
};
} // namespace utils::filesystem

14
src/utils/parametres.cpp Normal file
View file

@ -0,0 +1,14 @@
#include <utils/parametres.hpp>
namespace utils::parametres {
std::vector<std::string> ParseInputParameters(int argc, char* argv[]) {
std::vector<std::string> result;
result.reserve(argc);
for (size_t i = 0; i < argc; ++i) {
result.emplace_back(argv[i]);
}
return result;
}
} // namespace utils

66
src/utils/parametres.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include <vector>
#include <string>
#include <stdexcept>
namespace utils::parametres {
enum class Parameter { Skip, Nothing, Anything };
namespace detail {
template <size_t index, typename... Args>
bool CheckPatternParametresImpl(const std::vector<std::string>& args,
Parameter t) {
if (t == Parameter::Nothing) {
return index == args.size();
} else if (t == Parameter::Anything) {
return true;
}
return false;
}
template <size_t index, typename T, typename... Args,
std::enable_if_t<!std::is_same_v<T, Parameter>, bool> = true>
bool CheckPatternParametresImpl(const std::vector<std::string>& args, T&& head,
Args&&... pattern);
template <size_t index, typename T, typename... Args,
std::enable_if_t<std::is_same_v<T, Parameter>, bool> = true>
bool CheckPatternParametresImpl(const std::vector<std::string>& args, T&& p,
Args&&... pattern) {
if (index >= args.size()) {
return false;
}
if (p != Parameter::Skip) {
throw std::logic_error("Unsupported parameter");
}
return CheckPatternParametresImpl<index + 1, Args...>(
args, std::forward<Args>(pattern)...);
}
template <size_t index, typename T, typename... Args,
std::enable_if_t<!std::is_same_v<T, Parameter>, bool>>
bool CheckPatternParametresImpl(const std::vector<std::string>& args, T&& head,
Args&&... pattern) {
if (index >= args.size()) {
return false;
}
if (args[index] != head) {
return false;
}
return CheckPatternParametresImpl<index + 1, Args...>(
args, std::forward<Args>(pattern)...);
}
} // namespace detail
std::vector<std::string> ParseInputParameters(int argc, char* argv[]);
template <typename... Args>
bool CheckPatternParametres(const std::vector<std::string>& args,
Args&&... pattern) {
return detail::CheckPatternParametresImpl<0, Args...>(
args, std::forward<Args>(pattern)...);
}
} // namespace utils