Verified Commit d7d93aba authored by Jonah Brüchert's avatar Jonah Brüchert 🌳

Move first modules over to cpp

parent d043d564
Pipeline #33099 passed with stage
in 2 minutes and 10 seconds
......@@ -198,7 +198,6 @@ class RepositoryConfig:
"""
Set the workflow to one of RepositoryConfig.Workflow
"""
# Make sure not to corrupt the config file with invalid numbers
if not isinstance(workflow, Workflow):
raise TypeError()
......
from typing import List, Tuple
class Lotto:
def __init__(self, superzahl: int, zahlen):
self.zahlen = zahlen
self.superzahl = superzahl
if __name__ == "__main__":
lotto = Lotto(2, (1,2,3,4,5,6));
\ No newline at end of file
......@@ -69,7 +69,6 @@ class RepositoryConnection:
Utils.log(LogType.Error, "Failed to connect to GitLab")
sys.exit(1)
print(Utils.str_id_for_url(Utils.normalize_url(repository)))
self._remote_project = self._connection.projects.get(
Utils.str_id_for_url(Utils.normalize_url(repository))
)
......
add_executable(git-lab main.cpp fork.cpp workflow.cpp login.cpp feature.cpp)
add_executable(git-lab
main.cpp
fork.cpp
workflow.cpp
login.cpp
feature.cpp
repositoryconnection.cpp
snippet.cpp
utils.cpp)
target_link_libraries(git-lab pybind11::embed)
......
......@@ -2,6 +2,8 @@
#include <iostream>
#include "utils.h"
using namespace pybind11::literals;
void Feature::run(const std::string &start, const std::string &name)
......@@ -16,26 +18,24 @@ void Feature::run(const std::string &start, const std::string &name)
Feature::Feature()
: m_utils(py::module::import("lab.utils").attr("Utils"))
, m_repo(m_utils.attr("get_cwd_repo")())
, m_repo(Utils::get_cwd_repo())
, m_git(m_repo.attr("git"))
{
}
void Feature::checkout(const std::string &start, const std::string &name) const
{
auto logType = py::module::import("lab.utils").attr("LogType");
try {
if (m_repo.attr("refs").contains(name)) {
m_git.attr("checkout")(name);
m_utils.attr("log")(logType.attr("Info"), "Switched to branch '" + name + "'");
Utils::log(LogType::Info, "Switched to branch '" + name + "'");
} else {
m_git.attr("checkout")(start, "b"_a=name);
m_utils.attr("log")(logType.attr("Info"), "Switched to a new branch '" + name + "'");
Utils::log(LogType::Info, "Switched to a new branch '" + name + "'");
}
} catch (const py::error_already_set &git_error) {
m_utils.attr("log")(logType.attr("Error"), git_error.value().attr("stderr").attr("strip")());
Utils::log(LogType::Error, git_error.value().attr("stderr").attr("strip")().cast<std::string>());
}
}
......
......@@ -6,6 +6,8 @@
#include "workflow.h"
#include "login.h"
#include "feature.h"
#include "snippet.h"
#include "utils.h"
namespace py = pybind11;
......@@ -68,8 +70,8 @@ int main(int argc, char* argv[]) {
issues_parser->add_flag("--project", issues_project, "Show all project issues and not only the one you authored");
issues_parser->add_flag("--web", issues_web, "open on web browser");
std::string snippet_title;
std::string snippet_filename;
std::optional<std::string> snippet_title;
std::optional<std::string> snippet_filename;
snippet_parser->add_option("--title", snippet_title, "Add a custom title");
snippet_parser->add_option("filename", snippet_filename, "File name to upload");
......@@ -128,8 +130,7 @@ int main(int argc, char* argv[]) {
py::module issues = py::module::import("lab.issues");
issues.attr("run")(issue_id, issues_opened, issues_closed, issues_assigned, issues_project, issues_web);
} else if (parser.got_subcommand(snippet_parser)) {
py::module snippet = py::module::import("lab.snippet");
snippet.attr("run")(snippet_filename, snippet_filename);
Snippet::run(snippet_filename, snippet_title);
} else if (parser.got_subcommand(workflow_parser)) {
Workflow::run(workflow_fork, workflow_workbranch);
} else if (parser.got_subcommand(fork_parser)) {
......
#include "repositoryconnection.h"
#include <iostream>
#include "utils.h"
using namespace pybind11::literals;
RepositoryConnection::RepositoryConnection()
: m_config(py::module::import("lab.config").attr("Config")())
, m_local_repo(Utils::get_cwd_repo())
{
auto utilsModule = py::module::import("lab.utils");
auto utils = utilsModule.attr("Utils");
py::object origin;
try {
origin = m_local_repo.attr("remote")("name"_a="origin");
} catch (const py::error_already_set &valueError) {
Utils::log(LogType::Error, "No origin remote exists");
std::exit(1);
}
std::string repository = (*origin.attr("urls").begin()).cast<std::string>();
std::string gitlab_url = Utils::gitlab_instance_url(repository);
std::string gitlab_hostname;
auto urlparse = py::module::import("urllib.parse").attr("urlparse");
if (const auto parseResult = urlparse(gitlab_url); !parseResult.is_none()) {
gitlab_hostname = parseResult.attr("hostname").cast<std::string>();
} else {
Utils::log(LogType::Error, "Failed to detect GitLab hostname");
std::exit(1);
}
auto optional_token = py_cast_optional<std::string>(m_config.attr("token")(gitlab_hostname));
if (!optional_token) {
Utils::log(LogType::Error, "No authentication token found. ");
std::cout << "Please create a token with the api and write_repository scopes on " + gitlab_url + "/profile/personal_access_tokens.";
std::cout << R"(Afterwards use "git lab login --host )" << gitlab_hostname << R"( --token t0k3n")";
std::exit(1);
}
login(gitlab_url, *optional_token);
if (m_connection.is_none()) {
Utils::log(LogType::Error, "Failed to connect to GitLab");
std::exit(1);
}
m_remote_project = m_connection.attr("projects").attr("get")(Utils::str_id_for_url(Utils::normalize_url(repository)));
}
void RepositoryConnection::login(const std::string &instance_url, const std::string &token)
{
try {
m_connection = py::module::import("gitlab").attr("Gitlab")(instance_url, "private_token"_a=token);
m_connection.attr("auth")();
} catch (const py::error_already_set &error) {
Utils::log(LogType::Error, "Could not log into GitLab: " + instance_url);
std::exit(1);
}
}
#pragma once
#include <pybind11/embed.h>
namespace py = pybind11;
class RepositoryConnection
{
public:
RepositoryConnection();
private:
void login(const std::string &instance_url, const std::string &token);
py::object m_config; /* Config */
protected:
py::object m_connection; /* Gitlab */
py::object m_local_repo; /* Repo */
py::object m_remote_project; /* Project */
};
#include "snippet.h"
#include <map>
#include <iostream>
#include <fstream>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include "utils.h"
Snippet::Snippet() : RepositoryConnection()
{
}
void Snippet::run(const std::optional<std::string> &filename, const std::optional<std::string> &title)
{
Snippet snippet;
std::string content;
if (filename) {
auto file = std::ifstream(*filename);
if (!file.is_open()) {
Utils::log(LogType::Error, "Failed to open file" + *filename);
std::exit(1);
}
for (std::string line; std::getline(file, line); ) {
content += line;
}
} else {
std::cin >> content;
}
snippet.paste(filename.value_or("stdin"), content, title.value_or("Empty title"));
}
void Snippet::paste(const std::string &file_name, const std::string &content, const std::string &title)
{
std::map<std::string, std::string> options {
{"title", title},
{"file_name", file_name},
{"content", content},
{"visibility", "public"}
};
auto snippet = m_connection.attr("snippets").attr("create")(options);
Utils::log(LogType::Info, "Created snippet at " + snippet.attr("web_url").cast<std::string>());
std::cout << "You can access it raw at " << snippet.attr("raw_url").cast<std::string>();
}
#pragma once
#include <optional>
#include <string>
#include "repositoryconnection.h"
class Snippet : RepositoryConnection
{
public:
Snippet();
static void run(const std::optional<std::string> &filename, const std::optional<std::string> &title);
void paste(const std::string &file_name, const std::string &content, const std::string &title);
};
#include "utils.h"
#include <iostream>
#include <filesystem>
#include <unistd.h>
#include <sys/wait.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <spawn.h>
#include <pybind11/embed.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
namespace py = pybind11;
Utils::Utils()
{
}
void Utils::log(LogType type, const std::string &message)
{
std::string prefix = TextFormatting::bold;
switch (type) {
case LogType::Info:
prefix += "Info";
break;
case LogType::Warning:
prefix += TextFormatting::yellow + "Warning" + TextFormatting::end;
break;
case LogType::Error:
prefix += TextFormatting::red + "Error" + TextFormatting::end;
break;
}
prefix += TextFormatting::end;
if (prefix.size() > 0) {
prefix += ": ";
}
std::cout << prefix << message << std::endl;
}
std::string Utils::normalize_url(const std::string &url)
{
auto parse_result = py::module::import("urllib.parse").attr("urlparse")(url);
// url is already fine
if (!parse_result.attr("scheme").cast<std::string>().empty())
return url;
if (url.find("@") != 0 && url.find(":") != 0) {
return "ssh://" + string_replace(url, ":", "/");
}
Utils::log(LogType::Error, "Invalid url " + url);
std::exit(0);
}
std::string Utils::ssh_url_from_http(const std::string &url)
{
return string_replace(string_replace(url, "https://", "ssh://git@"), "http://", "ssh://git@");
}
std::string Utils::str_id_for_url(const std::string &url)
{
std::string normalized_url = normalize_url(url);
py::object parse_result = py::module::import("urllib.parse").attr("urlparse")(string_replace(normalized_url, ".git", ""));
const std::string path = parse_result.attr("path").cast<std::string>();
std::string repository_path(path.begin() + 1, path.end());
return py::module::import("urllib.parse").attr("quote_plus")(repository_path).cast<std::string>();
}
std::string Utils::gitlab_instance_url(const std::string &repository_url)
{
py::object repository_url_parse = py::module::import("urllib.parse").attr("urlparse")(repository_url);
const auto scheme = py_cast_optional<std::string>(repository_url_parse.attr("scheme"));
const auto hostname = py_cast_optional<std::string>(repository_url_parse.attr("hostname"));
const auto path = py_cast_optional<std::string>(repository_url_parse.attr("path"));
// Valid url
if (scheme && path) {
// If the repository is using some kind of http, can know whether to use http or https
if ((*scheme).find("http") != std::string::npos) {
if (!(*hostname).empty()) {
return (*scheme) + "://" + *hostname;
}
}
// else assume https
// redirects don't work according to
// https://python-gitlab.readthedocs.io/en/stable/api-usage.html.
if (hostname) {
return "https://" + *hostname;
}
}
// non valid url (probably scp syntax)
if (repository_url.find("@") != std::string::npos && repository_url.find(":") != std::string::npos) {
// create url in form of ssh://git@github.com/KDE/kaidan
py::object ssh_repository_parse = py::module::import("urllib.parse").attr("urlparse")("ssh://" + string_replace(repository_url, ":", "/"));
if (!ssh_repository_parse.attr("hostname").cast<std::string>().empty()) {
return "https://" + ssh_repository_parse.attr("hostname").cast<std::string>();
}
}
// If everything failed, exit
Utils::log(LogType::Error, "Failed to detect GitLab instance url");
std::exit(1);
}
void Utils::xdg_open(const std::string &path)
{
run_process({"xdg-open", path});
}
bool Utils::ask_bool(const std::string &question)
{
std::cout << question + " [y/n] ";
std::string answer;
std::getline(std::cin, answer);
return answer == "y";
}
std::optional<std::string> Utils::find_dotgit(const std::filesystem::path &search_path)
{
// Check if search path has a directory called .git
const auto it = std::filesystem::directory_iterator(search_path);
bool found = std::any_of(std::filesystem::begin(it), std::filesystem::end(it), [](const std::filesystem::directory_entry &entry) {
return entry.path().filename() == ".git";
});
if (found) {
return search_path;
}
// Search the parent directory
const auto parent_dir = search_path.parent_path();
// Stop if no parent directory exists
if (parent_dir == search_path) {
return std::nullopt;
}
return find_dotgit(parent_dir);
}
pybind11::object Utils::get_cwd_repo()
{
const auto path = Utils::find_dotgit(std::filesystem::current_path());
if (path) {
try {
return py::module::import("git").attr("Repo")(*path);
} catch (const py::error_already_set &error) {
// Move on to error reporting
}
}
Utils::log(LogType::Error, "Current directory is not a git repository");
std::exit(1);
}
std::vector<std::string> Utils::editor()
{
auto repo = Utils::get_cwd_repo();
auto config = repo.attr("config_reader")();
std::string editor = config.attr("get_value")("core", "editor", "").cast<std::string>();
if (editor.empty()) {
if (const auto enveditor = get_environment_variable("EDITOR")) {
editor = *enveditor;
} else if (const auto enveditor = get_environment_variable("VISUAL")) {
editor = *enveditor;
} else if (bool exists = run_process({"which", "editor"}); exists) {
editor = "editor";
} else {
editor = "vi";
}
}
const auto split_string = py::module::import("shlex").attr("split")(editor).cast<py::list>();
std::vector<std::string> command;
std::transform(split_string.begin(), split_string.end(), std::back_inserter(command), [](const py::handle &item) {
return item.cast<std::string>();
});
return command;
}
std::string Utils::string_replace(std::string string, const std::string &from, const std::string &to) {
size_t start_pos = string.find(from);
// Not found
if(start_pos == std::string::npos)
return string;
string.replace(start_pos, from.length(), to);
return string;
}
int run_process(const std::vector<std::string> &command) {
// TODO proper implementation
return py::module::import("subprocess").attr("call")(command).cast<int>();
}
std::optional<std::string> get_environment_variable(const std::string &variable)
{
const auto value = std::getenv(variable.c_str());
if (value == nullptr) {
return std::nullopt;
}
return std::string(value);
}
#pragma once
#include <string>
#include <filesystem>
#include <pybind11/pytypes.h>
namespace py = pybind11;
namespace TextFormatting {
static std::string purple = "\033[0;95m";
static std::string cyan = "\033[0;36m";
static std::string darkcyan = "\033[0;96m";
static std::string blue = "\033[0;34m";
static std::string green = "\033[0;32m";
static std::string yellow = "\033[0;33m";
static std::string red = "\033[0;31m";
static std::string lightred = "\033[1;31m";
static std::string bold = "\033[1m";
static std::string underline = "\033[4m";
static std::string end = "\033[0m";
}
template<class T>
std::optional<T> py_cast_optional(const pybind11::object &&source) {
if (source.is_none()) {
return std::nullopt;
}
return source.cast<T>();
}
int run_process(const std::vector<std::string> &command);
std::optional<std::string> get_environment_variable(const std::string &variable);
enum LogType {
Info,
Warning,
Error
};
class Utils
{
public:
Utils();
static void log(LogType type, const std::string &message);
static std::string normalize_url(const std::string &url);
static std::string ssh_url_from_http(const std::string &url);
static std::string str_id_for_url(const std::string &url);
static std::string gitlab_instance_url(const std::string &repository_url);
static void xdg_open(const std::string &path);
static bool ask_bool(const std::string &question);
static std::optional<std::string> find_dotgit(const std::filesystem::path &root_path);
static py::object get_cwd_repo();
static std::vector<std::string> editor();
private:
static std::string string_replace(std::string string, const std::string &from, const std::string &to);
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment