init project
This commit is contained in:
commit
821ea7eed0
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
.cache/
|
46
CMakeLists.txt
Normal file
46
CMakeLists.txt
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Set the minimum required version of CMake
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(Cl3 VERSION 1.0)
|
||||
|
||||
# Set the C++ standard to C++20
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
# Enable "compile_commands.json" output for use with various tools
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Add the Catch2 library
|
||||
find_package(Catch2 REQUIRED)
|
||||
find_package(yaml-cpp REQUIRED)
|
||||
find_package(Lua 5.1 REQUIRED)
|
||||
|
||||
# Add all source files to a single target
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
list(FILTER SOURCES EXCLUDE REGEX ".*main.cpp$")
|
||||
add_executable(cl ${SOURCES} src/main.cpp)
|
||||
target_include_directories(cl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${LUA_INCLUDE_DIR})
|
||||
target_link_libraries(cl PRIVATE yaml-cpp tmuxub ${LUA_LIBRARIES})
|
||||
target_compile_options(cl PRIVATE -g)
|
||||
|
||||
# Add all test files to a single target
|
||||
file(GLOB_RECURSE TEST_SOURCES "tests/*.cpp")
|
||||
add_executable(test_cl ${TEST_SOURCES} ${SOURCES})
|
||||
target_link_libraries(test_cl PRIVATE Catch2::Catch2WithMain yaml-cpp ${LUA_LIBRARIES})
|
||||
target_include_directories(test_cl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CATCH_INCLUDE_DIRS} ${LUA_INCLUDE_DIR})
|
||||
|
||||
# Enable testing
|
||||
enable_testing()
|
||||
|
||||
# Add the tests to the test suite
|
||||
include(Catch)
|
||||
catch_discover_tests(test_cl)
|
||||
add_test(NAME TestCl COMMAND test_cl)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
tmuxub
|
||||
GIT_REPOSITORY https://gitlab.com/Onyad/tmuxub
|
||||
GIT_TAG origin/main
|
||||
)
|
||||
FetchContent_MakeAvailable(tmuxub)
|
28
include/lua/loader.hpp
Normal file
28
include/lua/loader.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include <lua.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class LuaLoader {
|
||||
public:
|
||||
LuaLoader();
|
||||
|
||||
~LuaLoader();
|
||||
|
||||
void AddLuaPath(const std::string&);
|
||||
void AddCPath(const std::string&);
|
||||
void Call(const std::string& lua_module, const std::string& function);
|
||||
void LoadToGlobal(const std::string&);
|
||||
|
||||
std::vector<std::string> GetListScripts(const std::string&) const;
|
||||
|
||||
private:
|
||||
void Import(const std::string&, const std::string&, int);
|
||||
void LoadImpl(const std::string&);
|
||||
|
||||
void ForEachByStringArray(std::function<void(const std::string&)>);
|
||||
void ParseRequire();
|
||||
void ParseImport();
|
||||
|
||||
lua_State* L;
|
||||
};
|
32
include/projects/project.hpp
Normal file
32
include/projects/project.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class Project {
|
||||
public:
|
||||
Project(const std::string& path, const std::string& path_to_scripts, const std::string& name);
|
||||
Project(const Project&) = delete;
|
||||
Project(Project&&) = default;
|
||||
|
||||
const std::filesystem::path& GetPath() const;
|
||||
const std::filesystem::path& GetPathToScripts() const;
|
||||
const std::string& GetName() const;
|
||||
bool IsSubDirectory(const std::filesystem::path&) const;
|
||||
|
||||
void SetPath(const std::string& path);
|
||||
void SetPathToScripts(const std::string& path_to_scripts);
|
||||
void SetName(const std::string& name);
|
||||
|
||||
void PrintInfo() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
std::filesystem::path path_to_scripts_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
Project CreateProject();
|
||||
Project CreateProject(const std::string& name);
|
24
include/projects/project_list.hpp
Normal file
24
include/projects/project_list.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include "projects/project.hpp"
|
||||
|
||||
class ProjectList {
|
||||
public:
|
||||
ProjectList();
|
||||
|
||||
const std::deque<Project>& GetProjects();
|
||||
void AddProject(Project&&);
|
||||
|
||||
const Project& GetProject(
|
||||
const std::filesystem::path& path = std::filesystem::current_path()) const;
|
||||
void LoadFromYaml(const std::string& file_path);
|
||||
void SaveToYaml(const std::string& file_path) const;
|
||||
|
||||
private:
|
||||
std::deque<Project> projects_;
|
||||
};
|
54
include/utils/command_parser.hpp
Normal file
54
include/utils/command_parser.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
enum class PatternType { FixedWord, AnyWord, AnyWords };
|
||||
|
||||
struct Pattern {
|
||||
PatternType type;
|
||||
std::string fixed_word;
|
||||
};
|
||||
|
||||
using Callback = std::variant<std::function<void(const std::vector<std::string>&)>,
|
||||
std::function<bool(const std::vector<std::string>&)>>;
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
void AddRule(const std::vector<Pattern>& pattern, const auto& callback,
|
||||
bool find_in_suggestion = true) {
|
||||
if constexpr (std::is_same_v<
|
||||
decltype(callback(std::declval<const std::vector<std::string>&>())), void>) {
|
||||
AddRuleImpl(pattern, std::function<void(const std::vector<std::string>&)>(callback),
|
||||
find_in_suggestion);
|
||||
} else {
|
||||
AddRuleImpl(pattern, std::function<bool(const std::vector<std::string>&)>(callback),
|
||||
find_in_suggestion);
|
||||
}
|
||||
}
|
||||
void Parse(int argc, char* argv[]) const;
|
||||
std::vector<std::string> GetPossibleAdditions(int argc, char* argv[]) const;
|
||||
std::vector<std::string> GetPossibleAdditions(const std::vector<std::string>&) const;
|
||||
|
||||
private:
|
||||
void AddRuleImpl(const std::vector<Pattern>& pattern, const Callback& callback,
|
||||
bool find_in_suggestion);
|
||||
bool Match(const std::vector<Pattern>& pattern, const std::vector<std::string>& args) const;
|
||||
|
||||
private:
|
||||
struct Rule {
|
||||
Rule(const std::vector<Pattern>& pattern, Callback callback, bool find_in_suggestion)
|
||||
: pattern(pattern), callback(callback), find_in_suggestion(find_in_suggestion) {}
|
||||
std::vector<Pattern> pattern;
|
||||
Callback callback;
|
||||
bool find_in_suggestion;
|
||||
};
|
||||
|
||||
std::vector<Rule> rules_;
|
||||
};
|
||||
|
||||
std::vector<Pattern> MakePattern(
|
||||
std::initializer_list<std::variant<const char*, PatternType>> elements);
|
6
include/utils/generate_project_directory.hpp
Normal file
6
include/utils/generate_project_directory.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
std::string generate_project_directory(const std::string& project_name);
|
17
include/utils/local.hpp
Normal file
17
include/utils/local.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
inline std::filesystem::path GetLocalPath() {
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (home_dir == nullptr) {
|
||||
std::cerr << "Home directory was not declared" << std::endl;
|
||||
throw std::runtime_error("Was not found home directory");
|
||||
}
|
||||
std::filesystem::path result = std::filesystem::path(home_dir) / ".local/share/cl3";
|
||||
std::filesystem::create_directories(result);
|
||||
|
||||
return result;
|
||||
}
|
154
src/lua/loader.cpp
Normal file
154
src/lua/loader.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <lua/loader.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
LuaLoader::LuaLoader() : L(luaL_newstate()) {
|
||||
luaL_openlibs(L);
|
||||
AddLuaPath("./?.lua");
|
||||
AddCPath("./?.so");
|
||||
AddCPath("./?.dylib");
|
||||
}
|
||||
|
||||
void LuaLoader::AddLuaPath(const std::string& new_path) {
|
||||
lua_getglobal(L, "package");
|
||||
lua_getfield(L, -1, "path");
|
||||
lua_pushstring(L, (std::string(";") + new_path).data());
|
||||
lua_concat(L, 2);
|
||||
lua_setfield(L, -2, "path");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
void LuaLoader::AddCPath(const std::string& new_path) {
|
||||
lua_getglobal(L, "package");
|
||||
lua_getfield(L, -1, "cpath");
|
||||
lua_pushstring(L, (std::string(";") + new_path).data());
|
||||
lua_concat(L, 2);
|
||||
lua_setfield(L, -2, "cpath");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
void LuaLoader::LoadImpl(const std::string& name) {
|
||||
lua_getglobal(L, "require");
|
||||
lua_pushstring(L, name.data());
|
||||
|
||||
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
|
||||
std::cerr << "Error call script: " << name.data() << std::endl;
|
||||
std::cerr << lua_tostring(L, -1) << std::endl;
|
||||
throw std::logic_error("Error call script: " + name);
|
||||
}
|
||||
|
||||
ParseRequire();
|
||||
ParseImport();
|
||||
}
|
||||
|
||||
void LuaLoader::LoadToGlobal(const std::string& name) {
|
||||
LoadImpl(name);
|
||||
lua_setglobal(L, name.data());
|
||||
}
|
||||
|
||||
std::vector<std::string> LuaLoader::GetListScripts(const std::string& lua_module) const {
|
||||
std::vector<std::string> result;
|
||||
|
||||
lua_getglobal(L, lua_module.data());
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (!lua_isfunction(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
result.push_back(lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LuaLoader::ForEachByStringArray(std::function<void(const std::string&)> callback) {
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lua_isstring(L, -1)) {
|
||||
callback(lua_tostring(L, -1));
|
||||
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
} else if (lua_istable(L, -1)) {
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (!lua_isstring(L, -1)) {
|
||||
throw std::logic_error("Error in for each by string array");
|
||||
}
|
||||
|
||||
callback(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::logic_error("Error in parse requires ");
|
||||
}
|
||||
|
||||
void LuaLoader::ParseRequire() {
|
||||
if (!lua_istable(L, -1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, "require");
|
||||
|
||||
ForEachByStringArray(
|
||||
[this](const std::string& new_module_name) { LoadToGlobal(new_module_name); });
|
||||
}
|
||||
|
||||
void LuaLoader::Import(const std::string& name, const std::string& field, int table) {
|
||||
LoadImpl(name);
|
||||
lua_getfield(L, -1, field.data());
|
||||
lua_setfield(L, table, field.data());
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
void LuaLoader::ParseImport() {
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, "import");
|
||||
|
||||
ForEachByStringArray([this, table = lua_absindex(L, -2)](const std::string& new_module_name) {
|
||||
int dot_position = new_module_name.find(".");
|
||||
|
||||
if (dot_position == std::string::npos) {
|
||||
throw std::logic_error("Can't find dot in import");
|
||||
}
|
||||
|
||||
Import(new_module_name.substr(0, dot_position), new_module_name.substr(dot_position + 1),
|
||||
table);
|
||||
});
|
||||
}
|
||||
|
||||
void LuaLoader::Call(const std::string& lua_module, const std::string& function) {
|
||||
lua_getglobal(L, lua_module.data());
|
||||
lua_getfield(L, -1, function.data());
|
||||
|
||||
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
|
||||
std::cout << "Error in call " << lua_module << "." << function << std::endl;
|
||||
std::cerr << lua_tostring(L, -1) << std::endl;
|
||||
throw std::logic_error("Error in call " + lua_module + "." + function);
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
LuaLoader::~LuaLoader() {
|
||||
lua_close(L);
|
||||
}
|
142
src/main.cpp
Normal file
142
src/main.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include "utils/command_parser.hpp"
|
||||
#include "projects/project_list.hpp"
|
||||
#include "utils/local.hpp"
|
||||
#include "lua/loader.hpp"
|
||||
|
||||
#include <tmuxub/tmuxub.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void OpenFileInEditor(const std::filesystem::path& filename) {
|
||||
const char* editor = getenv("EDITOR");
|
||||
|
||||
if (!editor) {
|
||||
editor = "vim";
|
||||
}
|
||||
|
||||
auto str = filename.string();
|
||||
|
||||
std::vector<const char*> argv = {editor, str.data(), nullptr};
|
||||
execvp(editor, const_cast<char* const*>(argv.data()));
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
ProjectList projects;
|
||||
projects.LoadFromYaml(GetLocalPath() / "projects.yml");
|
||||
|
||||
LuaLoader lua;
|
||||
lua.AddLuaPath((GetLocalPath() / "globals/") / "?.lua");
|
||||
lua.AddCPath((GetLocalPath() / "globals/") / "?.so");
|
||||
|
||||
Parser parser;
|
||||
|
||||
try {
|
||||
const auto& project = projects.GetProject();
|
||||
lua.AddLuaPath(project.GetPathToScripts() / "?.lua");
|
||||
|
||||
std::vector<fs::path> scripts;
|
||||
for (auto entry : fs::directory_iterator(project.GetPathToScripts())) {
|
||||
auto filename = entry.path().filename().string();
|
||||
if (filename.ends_with(".lua")) {
|
||||
scripts.push_back(filename.substr(0, filename.size() - 4));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto script_name : scripts) {
|
||||
lua.LoadToGlobal(script_name);
|
||||
|
||||
if (script_name == "main") {
|
||||
auto all_scripts = lua.GetListScripts("main");
|
||||
|
||||
for (auto& script : all_scripts) {
|
||||
parser.AddRule(MakePattern({script.data()}), [script, &lua, &project](auto& args) {
|
||||
fs::current_path(project.GetPath());
|
||||
lua.Call("main", script);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
auto all_scripts = lua.GetListScripts(script_name);
|
||||
|
||||
for (auto& script : all_scripts) {
|
||||
parser.AddRule(MakePattern({script_name.string().data(), script.data()}),
|
||||
[script, &lua, &project, script_name](auto& args) {
|
||||
std::filesystem::current_path(project.GetPath());
|
||||
lua.Call(script_name, script);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::find(scripts.begin(), scripts.end(), "main") != scripts.end()) {
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
for (auto& project : projects.GetProjects()) {
|
||||
parser.AddRule(MakePattern({".", "open", project.GetName().data()}), [&project](auto& args) {
|
||||
tmuxub::Tmux t(GetLocalPath() / "tmuxsocket");
|
||||
|
||||
if (t.HasSession(project.GetName())) {
|
||||
t.ConnectToSession(project.GetName());
|
||||
t.Exec();
|
||||
} else {
|
||||
fs::current_path(project.GetPath());
|
||||
t.CreateSession(project.GetName());
|
||||
t.Exec();
|
||||
}
|
||||
|
||||
std::cout << "try open: " << project.GetName() << std::endl;
|
||||
});
|
||||
}
|
||||
|
||||
parser.AddRule(MakePattern({"help"}), [](auto& args) { std::cout << "It's help" << std::endl; });
|
||||
|
||||
parser.AddRule(MakePattern({".", "new"}),
|
||||
[&projects](auto& args) { projects.AddProject(CreateProject()); });
|
||||
|
||||
parser.AddRule(MakePattern({".", "new", PatternType::AnyWord}),
|
||||
[&projects](auto& args) { projects.AddProject(CreateProject(args[3])); });
|
||||
|
||||
parser.AddRule(MakePattern({"."}), [&projects](auto& args) {
|
||||
const auto& project = projects.GetProject();
|
||||
OpenFileInEditor((project.GetPathToScripts() / "main.lua").string());
|
||||
});
|
||||
|
||||
parser.AddRule(MakePattern({".", "global", PatternType::AnyWord}), [&projects](auto& args) {
|
||||
OpenFileInEditor(GetLocalPath() / "globals" / (args[3] + ".lua"));
|
||||
});
|
||||
|
||||
parser.AddRule(MakePattern({".", "config"}), [&projects](auto& args) {
|
||||
const auto& project = projects.GetProject();
|
||||
OpenFileInEditor((project.GetPathToScripts() / "main.lua").string());
|
||||
});
|
||||
|
||||
parser.AddRule(MakePattern({".", "config", PatternType::AnyWord}), [&projects](auto& args) {
|
||||
const auto& project = projects.GetProject();
|
||||
OpenFileInEditor(project.GetPathToScripts() / (args[3] + ".lua"));
|
||||
});
|
||||
|
||||
parser.AddRule(MakePattern({".", "projects"}),
|
||||
[](auto& args) { OpenFileInEditor(GetLocalPath() / "projects.yml"); });
|
||||
|
||||
parser.AddRule(MakePattern({PatternType::AnyWords}),
|
||||
[](auto& args) { std::cout << "Unknown command" << std::endl; });
|
||||
|
||||
if (getenv("CL_SUGGESTION") != nullptr) {
|
||||
auto suggestions = parser.GetPossibleAdditions(argc, argv);
|
||||
int x = 0;
|
||||
for (auto& suggestion : suggestions) {
|
||||
x++;
|
||||
if (x == 7) {
|
||||
break;
|
||||
}
|
||||
std::cout << suggestion << " ";
|
||||
}
|
||||
} else {
|
||||
parser.Parse(argc, argv);
|
||||
|
||||
projects.SaveToYaml(GetLocalPath() / "projects.yml");
|
||||
}
|
||||
}
|
59
src/projects/project.cpp
Normal file
59
src/projects/project.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#include "projects/project.hpp"
|
||||
#include "utils/local.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
Project::Project(const std::string& path, const std::string& path_to_scripts,
|
||||
const std::string& name)
|
||||
: path_(path), path_to_scripts_(path_to_scripts), name_(name) {}
|
||||
|
||||
const fs::path& Project::GetPath() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
const fs::path& Project::GetPathToScripts() const {
|
||||
return path_to_scripts_;
|
||||
}
|
||||
|
||||
const std::string& Project::GetName() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
bool Project::IsSubDirectory(const fs::path& path) const {
|
||||
return path.string().starts_with(path_.string());
|
||||
}
|
||||
|
||||
void Project::SetPath(const std::string& path) {
|
||||
path_ = path;
|
||||
}
|
||||
|
||||
void Project::SetPathToScripts(const std::string& path_to_scripts) {
|
||||
path_to_scripts_ = path_to_scripts;
|
||||
}
|
||||
|
||||
void Project::SetName(const std::string& name) {
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
void Project::PrintInfo() const {
|
||||
std::cout << "Project Name: " << name_ << "\n";
|
||||
std::cout << "Path: " << path_ << "\n";
|
||||
std::cout << "Path to Scripts: " << path_to_scripts_ << "\n";
|
||||
}
|
||||
|
||||
Project CreateProject() {
|
||||
fs::path current_path = fs::current_path();
|
||||
return CreateProject(current_path.filename().string());
|
||||
}
|
||||
|
||||
Project CreateProject(const std::string& name) {
|
||||
fs::path current_path = fs::current_path();
|
||||
std::string path = current_path.string();
|
||||
|
||||
std::string path_to_scripts = GetLocalPath() / "projects" / name;
|
||||
fs::create_directories(path_to_scripts);
|
||||
|
||||
return Project(path, path_to_scripts, name);
|
||||
}
|
78
src/projects/project_list.cpp
Normal file
78
src/projects/project_list.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include "projects/project_list.hpp"
|
||||
|
||||
ProjectList::ProjectList() {}
|
||||
|
||||
const std::deque<Project>& ProjectList::GetProjects() {
|
||||
return projects_;
|
||||
}
|
||||
|
||||
void ProjectList::AddProject(Project&& project) {
|
||||
bool exists =
|
||||
std::find_if(projects_.begin(), projects_.end(), [&project](const Project& another) {
|
||||
return another.GetName() == project.GetName();
|
||||
}) != projects_.end();
|
||||
|
||||
if (exists) {
|
||||
throw std::logic_error("Project with same name exists");
|
||||
}
|
||||
|
||||
projects_.push_back(std::move(project));
|
||||
}
|
||||
|
||||
const Project& ProjectList::GetProject(const std::filesystem::path& path) const {
|
||||
size_t index = 0, prefix = 0;
|
||||
for (size_t i = 0; i < projects_.size(); ++i) {
|
||||
const auto& project = projects_[i];
|
||||
if (project.IsSubDirectory(path)) {
|
||||
if (prefix < project.GetPath().string().size()) {
|
||||
index = i;
|
||||
prefix = project.GetPath().string().size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix == 0) {
|
||||
throw std::logic_error("Project not exists");
|
||||
}
|
||||
|
||||
return projects_[index];
|
||||
}
|
||||
|
||||
void ProjectList::LoadFromYaml(const std::string& file_path) {
|
||||
try {
|
||||
YAML::Node yaml = YAML::LoadFile(file_path);
|
||||
for (const auto& project : yaml["projects"]) {
|
||||
std::string path = project["path"].as<std::string>();
|
||||
std::string path_to_scripts = project["path_to_scripts"].as<std::string>();
|
||||
std::string name = project["name"].as<std::string>();
|
||||
projects_.emplace_back(path, path_to_scripts, name);
|
||||
}
|
||||
} catch (YAML::Exception& e) {
|
||||
std::cerr << "Error loading YAML file: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectList::SaveToYaml(const std::string& file_path) const {
|
||||
YAML::Emitter yaml;
|
||||
yaml << YAML::BeginMap;
|
||||
yaml << YAML::Key << "projects";
|
||||
yaml << YAML::Value << YAML::BeginSeq;
|
||||
for (const auto& project : projects_) {
|
||||
yaml << YAML::BeginMap;
|
||||
yaml << YAML::Key << "path";
|
||||
yaml << YAML::Value << project.GetPath();
|
||||
yaml << YAML::Key << "path_to_scripts";
|
||||
yaml << YAML::Value << project.GetPathToScripts();
|
||||
yaml << YAML::Key << "name";
|
||||
yaml << YAML::Value << project.GetName();
|
||||
yaml << YAML::EndMap;
|
||||
}
|
||||
yaml << YAML::EndSeq;
|
||||
yaml << YAML::EndMap;
|
||||
|
||||
std::ofstream output(file_path);
|
||||
output << yaml.c_str();
|
||||
output.close();
|
||||
}
|
167
src/utils/command_parser.cpp
Normal file
167
src/utils/command_parser.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include "utils/command_parser.hpp"
|
||||
#include <deque>
|
||||
|
||||
void Parser::AddRuleImpl(const std::vector<Pattern>& pattern, const Callback& callback,
|
||||
bool find_in_suggestion) {
|
||||
rules_.emplace_back(pattern, callback, find_in_suggestion);
|
||||
}
|
||||
|
||||
void Parser::Parse(int argc, char* argv[]) const {
|
||||
std::vector<std::string> args(argv, argv + argc);
|
||||
|
||||
for (const auto& [pattern, callback, _] : rules_) {
|
||||
if (Match(pattern, args)) {
|
||||
if (callback.index() == 0) {
|
||||
std::get<0>(callback)(args);
|
||||
return;
|
||||
} else {
|
||||
if (std::get<1>(callback)(args)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Parser::GetPossibleAdditions(int argc, char* argv[]) const {
|
||||
if (argc != 2) {
|
||||
throw std::logic_error("argc should be equal 2");
|
||||
}
|
||||
|
||||
size_t last = 0;
|
||||
std::string s = argv[1];
|
||||
|
||||
std::vector<std::string> input;
|
||||
|
||||
for (size_t i = 0; i < s.length(); ++i) {
|
||||
if (s[i] == ' ') {
|
||||
input.push_back(s.substr(last, i - last));
|
||||
last = i + 1;
|
||||
} else if (i + 1 == s.length()) {
|
||||
input.push_back(s.substr(last, i - last + 1));
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(input, [](auto x) { return x.empty(); });
|
||||
|
||||
if (s.back() == ' ') {
|
||||
input.emplace_back();
|
||||
}
|
||||
|
||||
return GetPossibleAdditions(input);
|
||||
};
|
||||
|
||||
std::vector<std::string> Parser::GetPossibleAdditions(const std::vector<std::string>& input) const {
|
||||
std::vector<std::string> suggestions;
|
||||
|
||||
for (const auto& [pattern, callback, check] : rules_) {
|
||||
if (!check) {
|
||||
continue;
|
||||
}
|
||||
size_t arg_index = 1;
|
||||
size_t pattern_index = 0;
|
||||
bool continue_outer_loop = false;
|
||||
|
||||
while (arg_index <= input.size() && pattern_index < pattern.size()) {
|
||||
switch (pattern[pattern_index].type) {
|
||||
case PatternType::FixedWord:
|
||||
if (arg_index == input.size()) {
|
||||
suggestions.push_back(pattern[pattern_index].fixed_word);
|
||||
continue_outer_loop = true;
|
||||
}else if (arg_index + 1 == input.size() &&
|
||||
pattern[pattern_index].fixed_word.starts_with(input[arg_index])) {
|
||||
suggestions.push_back(pattern[pattern_index].fixed_word);
|
||||
continue_outer_loop = true;
|
||||
} else if (input[arg_index] == pattern[pattern_index].fixed_word) {
|
||||
++arg_index;
|
||||
++pattern_index;
|
||||
} else {
|
||||
continue_outer_loop = true;
|
||||
}
|
||||
break;
|
||||
case PatternType::AnyWord:
|
||||
++arg_index;
|
||||
++pattern_index;
|
||||
break;
|
||||
case PatternType::AnyWords:
|
||||
++pattern_index;
|
||||
|
||||
if (pattern_index == pattern.size()) {
|
||||
arg_index = input.size();
|
||||
} else {
|
||||
while (arg_index <= input.size() &&
|
||||
(arg_index == input.size() ||
|
||||
input[arg_index] != pattern[pattern_index].fixed_word)) {
|
||||
if (arg_index == input.size()) {
|
||||
suggestions.push_back(pattern[pattern_index].fixed_word);
|
||||
}
|
||||
++arg_index;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (continue_outer_loop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// std::sort(suggestions.begin(), suggestions.end());
|
||||
// suggestions.erase(std::unique(suggestions.begin(), suggestions.end()), suggestions.end());
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
bool Parser::Match(const std::vector<Pattern>& pattern,
|
||||
const std::vector<std::string>& args) const {
|
||||
size_t arg_index = 1;
|
||||
size_t pattern_index = 0;
|
||||
|
||||
while (arg_index < args.size() && pattern_index < pattern.size()) {
|
||||
switch (pattern[pattern_index].type) {
|
||||
case PatternType::FixedWord:
|
||||
if (args[arg_index] == pattern[pattern_index].fixed_word) {
|
||||
++arg_index;
|
||||
++pattern_index;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case PatternType::AnyWord:
|
||||
++arg_index;
|
||||
++pattern_index;
|
||||
break;
|
||||
case PatternType::AnyWords:
|
||||
++pattern_index;
|
||||
if (pattern_index == pattern.size()) {
|
||||
arg_index = args.size();
|
||||
} else {
|
||||
while (arg_index < args.size() && args[arg_index] != pattern[pattern_index].fixed_word) {
|
||||
++arg_index;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern_index < pattern.size() && pattern[pattern_index].type == PatternType::AnyWords) {
|
||||
pattern_index++;
|
||||
}
|
||||
|
||||
return arg_index == args.size() && pattern_index == pattern.size();
|
||||
}
|
||||
|
||||
std::vector<Pattern> MakePattern(
|
||||
std::initializer_list<std::variant<const char*, PatternType>> elements) {
|
||||
std::vector<Pattern> pattern;
|
||||
|
||||
for (const auto& element : elements) {
|
||||
if (std::holds_alternative<const char*>(element)) {
|
||||
pattern.push_back({PatternType::FixedWord, std::get<const char*>(element)});
|
||||
} else {
|
||||
pattern.push_back({std::get<PatternType>(element), ""});
|
||||
}
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
29
src/utils/gererate_project_directory.cpp
Normal file
29
src/utils/gererate_project_directory.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "utils/generate_project_directory.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string generate_project_directory(const std::string& project_name) {
|
||||
const char* home_dir = std::getenv("HOME");
|
||||
if (!home_dir) {
|
||||
throw std::runtime_error("Could not get HOME environment variable");
|
||||
}
|
||||
fs::path base_dir(home_dir);
|
||||
|
||||
fs::path app_subdir(".local/share/cl3/projets");
|
||||
fs::path app_dir = base_dir / app_subdir;
|
||||
|
||||
if (!fs::exists(app_dir)) {
|
||||
fs::create_directory(app_dir);
|
||||
}
|
||||
|
||||
fs::path new_dir_path = app_dir / project_name;
|
||||
|
||||
if (fs::exists(new_dir_path)) {
|
||||
throw std::runtime_error("Project exists");
|
||||
}
|
||||
|
||||
fs::create_directory(new_dir_path);
|
||||
|
||||
return new_dir_path.string();
|
||||
}
|
104
tests/utils/command_parser.cpp
Normal file
104
tests/utils/command_parser.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include "utils/command_parser.hpp"
|
||||
|
||||
TEST_CASE("Parser tests", "[parser]") {
|
||||
SECTION("Fixed word pattern") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command"}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv1[] = {"./program", "command"};
|
||||
parser.Parse(2, const_cast<char**>(argv1));
|
||||
REQUIRE(callbackCalled);
|
||||
}
|
||||
|
||||
SECTION("Any word pattern") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command", PatternType::AnyWord}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv2[] = {"./program", "command", "any_word"};
|
||||
parser.Parse(3, const_cast<char**>(argv2));
|
||||
REQUIRE(callbackCalled);
|
||||
}
|
||||
|
||||
SECTION("Any words pattern") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command", PatternType::AnyWords}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv3[] = {"./program", "command", "word1", "word2", "word3"};
|
||||
parser.Parse(5, const_cast<char**>(argv3));
|
||||
REQUIRE(callbackCalled);
|
||||
}
|
||||
|
||||
SECTION("Mixed pattern") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command", PatternType::AnyWord, "option", PatternType::AnyWords}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv4[] = {"./program", "command", "any_word", "option", "word1", "word2", "word3"};
|
||||
parser.Parse(7, const_cast<char**>(argv4));
|
||||
REQUIRE(callbackCalled);
|
||||
}
|
||||
|
||||
SECTION("Non-matching pattern") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command", "option", PatternType::AnyWords}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv5[] = {"./program", "other_command", "option", "word1", "word2", "word3"};
|
||||
parser.Parse(6, const_cast<char**>(argv5));
|
||||
REQUIRE(!callbackCalled);
|
||||
}
|
||||
|
||||
SECTION("Multiple rules with different matches") {
|
||||
Parser parser;
|
||||
bool callbackCalled1 = false;
|
||||
bool callbackCalled2 = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command1", PatternType::AnyWords}),
|
||||
[&callbackCalled1](const std::vector<std::string>& args) { callbackCalled1 = true; });
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command2", PatternType::AnyWords}),
|
||||
[&callbackCalled2](const std::vector<std::string>& args) { callbackCalled2 = true; });
|
||||
|
||||
const char* argv6[] = {"./program", "command1", "word1", "word2", "word3"};
|
||||
parser.Parse(5, const_cast<char**>(argv6));
|
||||
REQUIRE(callbackCalled1);
|
||||
REQUIRE(!callbackCalled2);
|
||||
const char* argv7[] = {"./program", "command2", "word1", "word2", "word3"};
|
||||
parser.Parse(5, const_cast<char**>(argv7));
|
||||
REQUIRE(callbackCalled2);
|
||||
}
|
||||
|
||||
SECTION("No matching rule") {
|
||||
Parser parser;
|
||||
bool callbackCalled = false;
|
||||
|
||||
parser.AddRule(
|
||||
MakePattern({"command", "option", PatternType::AnyWords}),
|
||||
[&callbackCalled](const std::vector<std::string>& args) { callbackCalled = true; });
|
||||
|
||||
const char* argv8[] = {"./program", "other_command", "other_option", "word1", "word2", "word3"};
|
||||
parser.Parse(6, const_cast<char**>(argv8));
|
||||
REQUIRE(!callbackCalled);
|
||||
}
|
||||
}
|
8
zsh_autocomplete/_cl
Executable file
8
zsh_autocomplete/_cl
Executable file
|
@ -0,0 +1,8 @@
|
|||
_cl() {
|
||||
local comp=`CL_SUGGESTION="" ${words[1]} "$words"`
|
||||
|
||||
read -A line <<< $comp
|
||||
compadd -o nosort -- $line
|
||||
}
|
||||
|
||||
compdef _cl cl
|
Loading…
Reference in a new issue