init project

This commit is contained in:
Timofey Khoruzhii 2023-04-16 00:23:12 +03:00
commit 821ea7eed0
16 changed files with 950 additions and 0 deletions

154
src/lua/loader.cpp Normal file
View 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
View 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
View 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);
}

View 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();
}

View 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;
}

View 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();
}