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

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/
.cache/

46
CMakeLists.txt Normal file
View 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
View 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;
};

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

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

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

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

View 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
View file

@ -0,0 +1,8 @@
_cl() {
local comp=`CL_SUGGESTION="" ${words[1]} "$words"`
read -A line <<< $comp
compadd -o nosort -- $line
}
compdef _cl cl