Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ set(VersionString "0.0.5")

project(walng CXX)

include(cmake/CMakeUtils.cmake)

if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
Expand All @@ -12,5 +14,7 @@ add_subdirectory(deps EXCLUDE_FROM_ALL)

set(TargetName walng)

enable_testing()

add_subdirectory(code)
add_subdirectory(man)
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ Download base16 (or base24) scheme you like from https://github.com/tinted-themi
run walng:

```sh
curl -O -L https://raw.githubusercontent.com/tinted-theming/schemes/refs/heads/spec-0.11/base16/terracotta.yaml && ./walng --theme-file terracotta.yaml
curl -O -L https://raw.githubusercontent.com/tinted-theming/schemes/refs/heads/spec-0.11/base16/terracotta.yaml && ./walng --theme terracotta.yaml

```

or

```sh
walng --theme-url https://raw.githubusercontent.com/tinted-theming/schemes/refs/heads/spec-0.11/base16/terracotta.yaml
walng --theme https://raw.githubusercontent.com/tinted-theming/schemes/refs/heads/spec-0.11/base16/terracotta.yaml

```

Expand Down
49 changes: 49 additions & 0 deletions cmake/CMakeUtils.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
include(CMakeParseArguments)

function(CMakeUtilsRemoveMatchesFromList)
set(options)
set(oneValueArgs)
set(multiValueArgs MATCHES)
cmake_parse_arguments(TQ_PARSED "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

foreach (TQ_LIST ${TQ_PARSED_UNPARSED_ARGUMENTS})
foreach (TQ_ENTRY ${${TQ_LIST}})
foreach (TQ_MATCH ${TQ_PARSED_MATCHES})
if (${TQ_ENTRY} MATCHES ${TQ_MATCH})
list(REMOVE_ITEM ${TQ_LIST} ${TQ_ENTRY})
endif()
endforeach()
endforeach()
set(${TQ_LIST} ${${TQ_LIST}} PARENT_SCOPE)
endforeach()
endfunction()

function(CMakeUtilsAddTestsFromSourceList)
set(options)
set(oneValueArgs PREFIX)
set(multiValueArgs LINK_LIBS COMPILE_OPTIONS COMPILE_DEFINITIONS COMPILE_FEATURES)

cmake_parse_arguments(TQ_PARSED "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

foreach (TQ_LIST ${TQ_PARSED_UNPARSED_ARGUMENTS})
foreach (TQ_ENTRY ${${TQ_LIST}})
if (${TQ_ENTRY} MATCHES ".*_test.cpp")
string(REGEX REPLACE "^.*\/(.*)_test\.cpp$" "\\1" testName "${TQ_ENTRY}")
set(testName "${TQ_PARSED_PREFIX}-${testName}-test")

add_executable(${testName} ${TQ_ENTRY})
target_compile_features(${testName} PRIVATE ${TQ_PARSED_COMPILE_FEATURES})
target_compile_options(${testName} PRIVATE ${TQ_PARSED_COMPILE_OPTIONS})
target_compile_definitions(${testName} PRIVATE ${TQ_PARSED_COMPILE_DEFINITIONS})
target_link_libraries(${testName} PRIVATE ${TQ_PARSED_LINK_LIBS})

add_test(${testName} ${testName})
endif()
endforeach()
endforeach()
endfunction()

function(CMakeUtilsExcludeTestsFromSourceList TQ_LIST)
list(FILTER ${TQ_LIST} EXCLUDE REGEX ".*_test.cpp")
set(${TQ_LIST} ${${TQ_LIST}} PARENT_SCOPE)
endfunction()
14 changes: 8 additions & 6 deletions code/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
add_executable(${TargetName})

target_compile_features(${TargetName}
PRIVATE cxx_std_23)

set_target_properties(${TargetName}
PROPERTIES
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF)

target_compile_options(${TargetName}
PRIVATE
-Wall -Wextra -Wattributes -Wpedantic -Wstrict-aliasing -Wcast-align -g
-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/=
)

target_compile_definitions(${TargetName}
PRIVATE -DWALNG_VERSION="${VersionString}"
)

target_link_libraries(${TargetName}
PRIVATE cxxopts::cxxopts yaml-cpp::yaml-cpp 3rdparty::inja CURL::libcurl_static)

file(GLOB_RECURSE Sources "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
file(GLOB_RECURSE Headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h")

CMakeUtilsAddTestsFromSourceList(Sources
PREFIX ${TargetName}
COMPILE_FEATURES cxx_std_23
COMPILE_OPTIONS -Wall -Wextra -g
LINK_LIBS doctest::doctest_with_main CURL::libcurl_static)

CMakeUtilsExcludeTestsFromSourceList(Sources)

target_sources(${TargetName} PUBLIC ${Headers} ${Sources})

file(COPY config.yaml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

include(GNUInstallDirs)

install(TARGETS ${TargetName} DESTINATION ${CMAKE_INSTALL_BINDIR})
66 changes: 29 additions & 37 deletions code/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "Request.h"
#include "generate.h"
#include "net/download.h"
#include "utils.h"
#include "version.h"

Expand All @@ -25,10 +26,10 @@ std::expected<std::filesystem::path, std::system_error> extractFileNameFromUrl(s
});
}

std::expected<void, std::system_error> writeFile(std::filesystem::path const& path, std::string_view content) noexcept {
std::expected<void, std::error_code> writeFile(std::filesystem::path const& path, std::string_view content) noexcept {
FILE* file = ::fopen(path.c_str(), "w");
if (!file) {
return std::unexpected(std::system_error(errno, std::system_category(), "failed to open file for writting"));
return std::unexpected(std::error_code(errno, std::system_category()));
}

// TODO: check result
Expand All @@ -38,41 +39,35 @@ std::expected<void, std::system_error> writeFile(std::filesystem::path const& pa
return {};
}

std::expected<std::filesystem::path, std::system_error> downloadFileOrGetFromCache(std::string const& url) noexcept {
using namespace walng;

auto filename = extractFileNameFromUrl(url);
if (!filename) {
return std::unexpected(filename.error());
}

auto request = Request();
if (auto const rc = request.prepare(url); !rc) {
return std::unexpected(rc.error());
}

auto response = request.perform();
/// Download file. Return error or path where content stored
std::expected<std::filesystem::path, std::system_error> downloadFile(std::string const& url) noexcept {
auto response = walng::net::download(url);
if (!response) {
return std::unexpected(response.error());
}

auto cachePath = getCachePath();
if (response->code != 200) {
return std::unexpected(std::error_code(ENOENT, std::system_category()));
}

auto cachePath = walng::getCachePath();
if (!cachePath.has_value()) {
return std::unexpected(cachePath.error());
}

auto const themesPath = cachePath.value() / "themes";
std::error_code ec;
create_directories(themesPath, ec);
if (ec) {
return std::unexpected(std::system_error(ec, "can't create themes cache dir"));
return std::unexpected(ec);
}

auto const themeFilePath = themesPath / *filename;
if (auto const rc = writeFile(themeFilePath, response->body()); !rc) {
auto themeFilePath = themesPath / response->filename;
if (auto const rc = writeFile(themeFilePath, response->content); !rc) {
return std::unexpected(response.error());
}

return themeFilePath;
return {std::move(themeFilePath)};
}

int main(int argc, char* argv[]) {
Expand All @@ -82,8 +77,7 @@ int main(int argc, char* argv[]) {
// clang-format off
options.add_options()
("config", "path to config file", cxxopts::value<std::string>(), "PATH")
("theme-file", "path to theme file", cxxopts::value<std::string>(), "PATH")
("theme-url", "url to theme file", cxxopts::value<std::string>(), "URL")
("theme", "path or url to theme file", cxxopts::value<std::string>(), "PATH or URL")
("help", "prints the help and exit")
("version", "prints the version and exit")
;
Expand All @@ -107,26 +101,24 @@ int main(int argc, char* argv[]) {
}
auto configPath = walng::getConfigPath();
if (!configPath.has_value()) {
throw configPath.error();
throw std::system_error(configPath.error());
}
return configPath.value() / "config.yaml";
}();

if (result.count("theme-file") > 0) {
if (result.count("theme-url") > 0) {
throw std::runtime_error("argument `--theme-file` and `--theme-url` can't be set simultaneously");
}
std::filesystem::path const themePath = result["theme-file"].as<std::string>();
walng::generate(configPath, themePath);
} else if (result.count("theme-url") > 0) {
std::string const& themeUrl = result["theme-url"].as<std::string>();
auto const downloadResult = downloadFileOrGetFromCache(themeUrl);
if (!downloadResult) {
throw downloadResult.error();
if (result.count("theme") == 0) {
throw std::runtime_error("argument `--theme` should be set");
}
auto const& theme = result["theme"].as<std::string>();

if (theme.starts_with("http://") || theme.starts_with("https://")) {
if (auto rc = downloadFile(theme); rc) {
walng::generate(configPath, *rc);
} else {
throw std::system_error(rc.error());
}
walng::generate(configPath, downloadResult.value());
} else {
throw std::runtime_error("argument `--theme-file` or `--theme-url` should be set");
walng::generate(configPath, theme);
}

} catch (std::exception const& e) {
Expand Down
117 changes: 117 additions & 0 deletions code/net/CurlEasyHandle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) Sergey Kovalevich <inndie@gmail.com>
// SPDX-License-Identifier: AGPL-3.0

#pragma once

#include <expected>
#include <utility>

#include <curl/curl.h>

#include "error.h"

namespace walng::net {

class CurlEasyHandle {
private:
CURL* handle_ = nullptr;

public:
CurlEasyHandle(CurlEasyHandle const&) = delete;
CurlEasyHandle& operator=(CurlEasyHandle const&) = delete;

CurlEasyHandle() = default;

CurlEasyHandle(CurlEasyHandle&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}

~CurlEasyHandle() noexcept {
if (handle_) {
::curl_easy_cleanup(handle_);
}
}

CurlEasyHandle& operator=(CurlEasyHandle&& other) noexcept {
if (this != &other) {
this->~CurlEasyHandle();
new (this) CurlEasyHandle(std::move(other));
}
return *this;
}

explicit CurlEasyHandle(CURL* handle) noexcept : handle_(handle) {}

/// Create and init CurlEasyHandle
static std::expected<CurlEasyHandle, std::error_code> create() noexcept {
auto handle = ::curl_easy_init();
if (!handle) [[unlikely]] {
return std::unexpected(make_error_code(CurlInitError::EasyInit));
}
return {CurlEasyHandle(handle)};
}

/// Return true on handle valid
[[nodiscard]] operator bool() const noexcept {
return handle_ != nullptr;
}

/// Wrapper around @c curl_easy_reset
void reset() noexcept {
::curl_easy_reset(handle_);
}

/// Wrapper around @c curl_easy_setopt
template <typename T>
std::expected<void, std::error_code> setOption(CURLoption opt, T&& value) noexcept {
if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
return this->setOptionImpl(opt, value.c_str());
} else if constexpr (std::is_same_v<std::decay_t<T>, std::string_view>) {
return this->setOptionImpl(opt, std::string(value).c_str());
} else {
return this->setOptionImpl(opt, std::forward<T>(value));
}
}

/// Wrapper around @c curl_easy_getinfo
template <typename T>
std::expected<T, std::error_code> info(CURLINFO info) const {
if constexpr (std::is_same_v<T, std::string>) {
return this->infoImpl<char const*>(info).transform([](char const* value) {
return std::string(value);
});
} else if constexpr (std::is_same_v<T, std::string_view>) {
return this->infoImpl<char const*>(info).transform([](char const* value) {
return std::string_view(value);
});
} else {
return this->infoImpl<T>(info);
}
}

/// Wrapper around @c curl_easy_perform
std::expected<void, std::error_code> perform() noexcept {
if (auto const rc = ::curl_easy_perform(handle_); rc != CURLE_OK) [[unlikely]] {
return std::unexpected(make_error_code(rc));
}
return {};
}

private:
template <typename T>
std::expected<void, std::error_code> setOptionImpl(CURLoption opt, T&& value) noexcept {
if (auto const rc = ::curl_easy_setopt(handle_, opt, std::forward<T>(value)); rc != CURLE_OK) [[unlikely]] {
return std::unexpected(make_error_code(rc));
}
return {};
}

template <typename T>
std::expected<T, std::error_code> infoImpl(CURLINFO info) const {
T result;
if (auto const rc = ::curl_easy_getinfo(handle_, info, &result); rc != CURLE_OK) [[unlikely]] {
return std::unexpected(make_error_code(rc));
}
return {result};
}
};

} // namespace walng::net
26 changes: 26 additions & 0 deletions code/net/CurlEasyHandle_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Sergey Kovalevich <inndie@gmail.com>
// SPDX-License-Identifier: AGPL-3.0

#include <doctest/doctest.h>

#include "CurlEasyHandle.h"

namespace walng::net {

TEST_CASE("CurlEasyHandle: move-semantic") {
CurlEasyHandle handle;
CHECK(!handle);
CHECK(!handle.setOption(CURLOPT_FOLLOWLOCATION, 1U));

auto result = CurlEasyHandle::create();
CHECK(result);
CHECK(*result);

handle = std::move(*result);
CHECK(handle);
CHECK(!result.value());

CHECK(handle.setOption(CURLOPT_FOLLOWLOCATION, 1U));
}

} // namespace walng::net
Loading