utils: Initial Commit

This commit is contained in:
Vaxry 2024-06-08 19:37:15 +02:00
parent cbc7c2df31
commit bf73356d39
17 changed files with 1025 additions and 0 deletions

65
.clang-format Normal file
View file

@ -0,0 +1,65 @@
---
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
AllowShortEnumsOnASingleLine: false
BraceWrapping:
AfterEnum: false
AlignConsecutiveDeclarations: AcrossEmptyLines
NamespaceIndentation: All

57
.github/workflows/arch.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Build & Test (Arch)
on: [push, pull_request, workflow_dispatch]
jobs:
gcc:
name: "Arch: Build and Test (gcc)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Get required pkgs
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
- name: Build hyprutils with gcc
run: |
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install ./build
- name: Run tests
run: |
cd ./build && ctest --output-on-failure
clang:
name: "Arch: Build and Test (clang)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Get required pkgs
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
- name: Build hyprutils with clang
run: |
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install ./build
- name: Run tests
run: |
cd ./build && ctest --output-on-failure

4
.gitignore vendored
View file

@ -30,3 +30,7 @@
*.exe *.exe
*.out *.out
*.app *.app
build/
.vscode/
.cache/

63
CMakeLists.txt Normal file
View file

@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.19)
set(HYPRUTILS_VERSION "0.1.0")
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
project(hyprutils
VERSION ${HYPRUTILS_VERSION}
DESCRIPTION "A library and toolkit for the Hyprland cursor format"
)
include(CTest)
include(GNUInstallDirs)
set(PREFIX ${CMAKE_INSTALL_PREFIX})
set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprutils.pc.in hyprutils.pc @ONLY)
set(CMAKE_CXX_STANDARD 23)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprutils in Debug")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprutils in Release")
endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
add_library(hyprutils SHARED ${SRCFILES})
target_include_directories( hyprutils
PUBLIC "./include"
PRIVATE "./src"
)
set_target_properties(hyprutils PROPERTIES
VERSION ${hyprutils_VERSION}
SOVERSION 0
PUBLIC_HEADER include/hyprutils/hyprutils.hpp include/hyprutils/hyprutils.h include/hyprutils/shared.h
)
# tests
add_custom_target(tests)
add_executable(hyprutils_memory "tests/memory.cpp")
target_link_libraries(hyprutils_memory PRIVATE hyprutils)
add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory")
add_dependencies(tests hyprutils_memory)
add_executable(hyprutils_string "tests/string.cpp")
target_link_libraries(hyprutils_string PRIVATE hyprutils)
add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string")
add_dependencies(tests hyprutils_string)
add_executable(hyprutils_signal "tests/signal.cpp")
target_link_libraries(hyprutils_signal PRIVATE hyprutils)
add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal")
add_dependencies(tests hyprutils_signal)
# Installation
install(TARGETS hyprutils)
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

10
hyprutils.pc.in Normal file
View file

@ -0,0 +1,10 @@
prefix=@PREFIX@
includedir=@INCLUDE@
libdir=@LIBDIR@
Name: hyprutils
URL: https://github.com/hyprwm/hyprutils
Description: Hyprland utilities library used across the ecosystem
Version: @HYPRUTILS_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lhyprutils

View file

@ -0,0 +1,302 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
/*
This is a custom impl of std::shared_ptr.
It is not thread-safe like the STL one,
but Hyprland is single-threaded anyways.
It differs a bit from how the STL one works,
namely in the fact that it keeps the T* inside the
control block, and that you can still make a CWeakPtr
or deref an existing one inside the destructor.
*/
namespace Hyprutils {
namespace Memory {
namespace CSharedPointer_ {
class impl_base {
public:
virtual ~impl_base(){};
virtual void inc() noexcept = 0;
virtual void dec() noexcept = 0;
virtual void incWeak() noexcept = 0;
virtual void decWeak() noexcept = 0;
virtual unsigned int ref() noexcept = 0;
virtual unsigned int wref() noexcept = 0;
virtual void destroy() noexcept = 0;
virtual bool destroying() noexcept = 0;
virtual bool dataNonNull() noexcept = 0;
};
template <typename T>
class impl : public impl_base {
public:
impl(T* data) noexcept : _data(data) {
;
}
/* strong refcount */
unsigned int _ref = 0;
/* weak refcount */
unsigned int _weak = 0;
T* _data = nullptr;
friend void swap(impl*& a, impl*& b) {
impl* tmp = a;
a = b;
b = tmp;
}
/* if the destructor was called,
creating shared_ptrs is no longer valid */
bool _destroying = false;
void _destroy() {
if (!_data || _destroying)
return;
// first, we destroy the data, but keep the pointer.
// this way, weak pointers will still be able to
// reference and use, but no longer create shared ones.
_destroying = true;
__deleter(_data);
// now, we can reset the data and call it a day.
_data = nullptr;
_destroying = false;
}
std::default_delete<T> __deleter{};
//
virtual void inc() noexcept {
_ref++;
}
virtual void dec() noexcept {
_ref--;
}
virtual void incWeak() noexcept {
_weak++;
}
virtual void decWeak() noexcept {
_weak--;
}
virtual unsigned int ref() noexcept {
return _ref;
}
virtual unsigned int wref() noexcept {
return _weak;
}
virtual void destroy() noexcept {
_destroy();
}
virtual bool destroying() noexcept {
return _destroying;
}
virtual bool dataNonNull() noexcept {
return _data;
}
virtual ~impl() {
destroy();
}
};
};
template <typename T>
class CSharedPointer {
public:
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CSharedPointer<T>&, X>::value, CSharedPointer&>::type;
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
/* creates a new shared pointer managing a resource
avoid calling. Could duplicate ownership. Prefer makeShared */
explicit CSharedPointer(T* object) noexcept {
impl_ = new CSharedPointer_::impl<T>(object);
increment();
}
/* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>>
CSharedPointer(const CSharedPointer<U>& ref) noexcept {
impl_ = ref.impl_;
increment();
}
CSharedPointer(const CSharedPointer& ref) noexcept {
impl_ = ref.impl_;
increment();
}
template <typename U, typename = isConstructible<U>>
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
CSharedPointer(CSharedPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
/* allows weakPointer to create from an impl */
CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept {
impl_ = implementation;
increment();
}
/* creates an empty shared pointer with no implementation */
CSharedPointer() noexcept {
; // empty
}
/* creates an empty shared pointer with no implementation */
CSharedPointer(std::nullptr_t) noexcept {
; // empty
}
~CSharedPointer() {
// we do not decrement here,
// because we want to preserve the pointer
// in case this is the last owner.
if (impl_ && impl_->ref() == 1)
destroyImpl();
else
decrement();
}
template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if (impl_ == rhs.impl_)
return *this;
decrement();
impl_ = rhs.impl_;
increment();
return *this;
}
CSharedPointer& operator=(const CSharedPointer& rhs) {
if (impl_ == rhs.impl_)
return *this;
decrement();
impl_ = rhs.impl_;
increment();
return *this;
}
template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}
CSharedPointer& operator=(CSharedPointer&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}
operator bool() const {
return impl_ && impl_->dataNonNull();
}
bool operator==(const CSharedPointer& rhs) const {
return impl_ == rhs.impl_;
}
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
}
bool operator<(const CSharedPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
}
T* operator->() const {
return get();
}
T& operator*() const {
return *get();
}
void reset() {
decrement();
impl_ = nullptr;
}
T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
}
unsigned int strongRef() const {
return impl_ ? impl_->ref() : 0;
}
CSharedPointer_::impl_base* impl_ = nullptr;
private:
/*
no-op if there is no impl_
may delete the stored object if ref == 0
may delete and reset impl_ if ref == 0 and weak == 0
*/
void decrement() {
if (!impl_)
return;
impl_->dec();
// if ref == 0, we can destroy impl
if (impl_->ref() == 0)
destroyImpl();
}
/* no-op if there is no impl_ */
void increment() {
if (!impl_)
return;
impl_->inc();
}
/* destroy the pointed-to object
if able, will also destroy impl */
void destroyImpl() {
// destroy the impl contents
impl_->destroy();
// check for weak refs, if zero, we can also delete impl_
if (impl_->wref() == 0) {
delete impl_;
impl_ = nullptr;
}
}
};
template <typename U, typename... Args>
static CSharedPointer<U> makeShared(Args&&... args) {
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
}
}
}
template <typename T>
struct std::hash<Hyprutils::Memory::CSharedPointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};

View file

@ -0,0 +1,192 @@
#pragma once
#include "./SharedPtr.hpp"
/*
This is a Hyprland implementation of std::weak_ptr.
See SharedPtr.hpp for more info on how it's different.
*/
namespace Hyprutils {
namespace Memory {
template <typename T>
class CWeakPointer {
public:
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CWeakPointer<T>&, X>::value, CWeakPointer&>::type;
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
/* create a weak ptr from a reference */
template <typename U, typename = isConstructible<U>>
CWeakPointer(const CSharedPointer<U>& ref) noexcept {
if (!ref.impl_)
return;
impl_ = ref.impl_;
incrementWeak();
}
/* create a weak ptr from another weak ptr */
template <typename U, typename = isConstructible<U>>
CWeakPointer(const CWeakPointer<U>& ref) noexcept {
if (!ref.impl_)
return;
impl_ = ref.impl_;
incrementWeak();
}
CWeakPointer(const CWeakPointer& ref) noexcept {
if (!ref.impl_)
return;
impl_ = ref.impl_;
incrementWeak();
}
template <typename U, typename = isConstructible<U>>
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
CWeakPointer(CWeakPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
/* create a weak ptr from another weak ptr with assignment */
template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CWeakPointer<U>& rhs) {
if (impl_ == rhs.impl_)
return *this;
decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}
CWeakPointer<T>& operator=(const CWeakPointer& rhs) {
if (impl_ == rhs.impl_)
return *this;
decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}
/* create a weak ptr from a shared ptr with assignment */
template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_)
return *this;
decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}
/* create an empty weak ptr */
CWeakPointer() {
;
}
~CWeakPointer() {
decrementWeak();
}
/* expired MAY return true even if the pointer is still stored.
the situation would be e.g. self-weak pointer in a destructor.
for pointer validity, use valid() */
bool expired() const {
return !impl_ || !impl_->dataNonNull() || impl_->destroying();
}
/* this means the pointed-to object is not yet deleted and can still be
referenced, but it might be in the process of being deleted.
check !expired() if you want to check whether it's valid and
assignable to a SP. */
bool valid() const {
return impl_ && impl_->dataNonNull();
}
void reset() {
decrementWeak();
impl_ = nullptr;
}
CSharedPointer<T> lock() const {
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
return {};
return CSharedPointer<T>(impl_);
}
/* this returns valid() */
operator bool() const {
return valid();
}
bool operator==(const CWeakPointer<T>& rhs) const {
return impl_ == rhs.impl_;
}
bool operator==(const CSharedPointer<T>& rhs) const {
return impl_ == rhs.impl_;
}
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
}
bool operator<(const CWeakPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
}
T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
}
T* operator->() const {
return get();
}
CSharedPointer_::impl_base* impl_ = nullptr;
private:
/* no-op if there is no impl_ */
void decrementWeak() {
if (!impl_)
return;
impl_->decWeak();
// we need to check for ->destroying,
// because otherwise we could destroy here
// and have a shared_ptr destroy the same thing
// later (in situations where we have a weak_ptr to self)
if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) {
delete impl_;
impl_ = nullptr;
}
}
/* no-op if there is no impl_ */
void incrementWeak() {
if (!impl_)
return;
impl_->incWeak();
}
};
}
}
template <typename T>
struct std::hash<Hyprutils::Memory::CWeakPointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CWeakPointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};

View file

@ -0,0 +1,44 @@
#pragma once
#include <any>
#include <functional>
#include <hyprutils/memory/SharedPtr.hpp>
namespace Hyprutils {
namespace Signal {
class CSignal;
class CSignalListener {
public:
CSignalListener(std::function<void(std::any)> handler);
CSignalListener(CSignalListener&&) = delete;
CSignalListener(CSignalListener&) = delete;
CSignalListener(const CSignalListener&) = delete;
CSignalListener(const CSignalListener&&) = delete;
void emit(std::any data);
private:
std::function<void(std::any)> m_fHandler;
};
typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener;
class CStaticSignalListener {
public:
CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner);
CStaticSignalListener(CStaticSignalListener&&) = delete;
CStaticSignalListener(CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&&) = delete;
void emit(std::any data);
private:
void* m_pOwner = nullptr;
std::function<void(void*, std::any)> m_fHandler;
};
}
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <functional>
#include <any>
#include <vector>
#include <memory>
#include <hyprutils/memory/WeakPtr.hpp>
#include "./Listener.hpp"
namespace Hyprutils {
namespace Signal {
class CSignal {
public:
void emit(std::any data = {});
//
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler);
// this is for static listeners. They die with this signal.
// TODO: can we somehow rid of the void* data and make it a custom this?
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner);
private:
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners;
};
}
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace Hyprutils {
namespace String {
// trims beginning and end of whitespace characters
std::string trim(const std::string& in);
bool isNumber(const std::string& str, bool allowfloat = false);
};
};

22
src/signal/Listener.cpp Normal file
View file

@ -0,0 +1,22 @@
#include <hyprutils/signal/Listener.hpp>
using namespace Hyprutils::Signal;
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) {
;
}
void Hyprutils::Signal::CSignalListener::emit(std::any data) {
if (!m_fHandler)
return;
m_fHandler(data);
}
Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner) : m_pOwner(owner), m_fHandler(handler) {
;
}
void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) {
m_fHandler(m_pOwner, data);
}

58
src/signal/Signal.cpp Normal file
View file

@ -0,0 +1,58 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <algorithm>
using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
void Hyprutils::Signal::CSignal::emit(std::any data) {
bool dirty = false;
std::vector<SP<CSignalListener>> listeners;
for (auto& l : m_vListeners) {
if (l.expired()) {
dirty = true;
continue;
}
listeners.emplace_back(l.lock());
}
std::vector<CStaticSignalListener*> statics;
for (auto& l : m_vStaticListeners) {
statics.emplace_back(l.get());
}
for (auto& l : listeners) {
// if there is only one lock, it means the event is only held by the listeners
// vector and was removed during our iteration
if (l.strongRef() == 1) {
dirty = true;
continue;
}
l->emit(data);
}
for (auto& l : statics) {
l->emit(data);
}
// release SPs
listeners.clear();
if (dirty)
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
}
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
CHyprSignalListener listener = makeShared<CSignalListener>(handler);
m_vListeners.emplace_back(listener);
return listener;
}
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner));
}

58
src/string/String.cpp Normal file
View file

@ -0,0 +1,58 @@
#include <algorithm>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
std::string Hyprutils::String::trim(const std::string& in) {
if (in.empty())
return in;
int countBefore = 0;
while (countBefore < in.length() && std::isspace(in.at(countBefore))) {
countBefore++;
}
int countAfter = 0;
while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) {
countAfter++;
}
std::string result = in.substr(countBefore, in.length() - countBefore - countAfter);
return result;
}
bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (str.empty())
return false;
for (size_t i = 0; i < str.length(); ++i) {
const char& c = str.at(i);
if (i == 0 && str.at(i) == '-') {
// only place where we allow -
continue;
}
if (!isdigit(c)) {
if (!allowfloat)
return false;
if (c != '.')
return false;
if (i == 0)
return false;
if (str.at(0) == '-')
return false;
continue;
}
}
if (str.back() == '.')
return false;
return true;
}

29
tests/memory.cpp Normal file
View file

@ -0,0 +1,29 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
int ret = 0;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
WP<int> weak = intPtr;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*weak.get(), 10);
EXPECT(weak.expired(), false);
intPtr = {};
EXPECT(weak.expired(), true);
return ret;
}

20
tests/shared.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <iostream>
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}

30
tests/signal.cpp Normal file
View file

@ -0,0 +1,30 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"
using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;
int main(int argc, char** argv, char** envp) {
int ret = 0;
CSignal signal;
int data = 0;
auto listener = signal.registerListener([&] (std::any d) {
data = 1;
});
signal.emit();
EXPECT(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT(data, 0);
return ret;
}

33
tests/string.cpp Normal file
View file

@ -0,0 +1,33 @@
#include <hyprutils/string/String.hpp>
#include "shared.hpp"
using namespace Hyprutils::String;
int main(int argc, char** argv, char** envp) {
int ret = 0;
EXPECT(trim(" a "), "a");
EXPECT(trim(" a a "), "a a");
EXPECT(trim("a"), "a");
EXPECT(trim(" "), "");
EXPECT(isNumber("99214123434"), true);
EXPECT(isNumber("-35252345234"), true);
EXPECT(isNumber("---3423--432"), false);
EXPECT(isNumber("s---3423--432"), false);
EXPECT(isNumber("---3423--432s"), false);
EXPECT(isNumber("1s"), false);
EXPECT(isNumber(""), false);
EXPECT(isNumber("--0"), false);
EXPECT(isNumber("abc"), false);
EXPECT(isNumber("0.0", true), true);
EXPECT(isNumber("0.2", true), true);
EXPECT(isNumber("0.", true), false);
EXPECT(isNumber(".0", true), false);
EXPECT(isNumber("", true), false);
EXPECT(isNumber("vvss", true), false);
EXPECT(isNumber("0.9999s", true), false);
EXPECT(isNumber("s0.9999", true), false);
return ret;
}