Caitlyn is a header-only versatile general-purpose C++ library designed to enhance productivity for everyday tasks.
- Unicode String Support
- Text Formatting
- File Management
- Serializing
- Error Handling
- Numeric
- Type Traits
- Unit Testing
- Utilities
Supported Platforms: Linux, Windows, macOS
Supported Standard: C++11 or later
First, navigate to your project root and create an external
directory to hold
third-party libraries. Next, clone the Caitlyn repository into this folder.
Open your terminal and run the following commands:
mkdir external
cd external
git clone https://github.com/szawrowski/caitlyn.git
Add the necessary configurations to the CMakeLists.txt
file to link the
Caitlyn library to your project.
Use the following template as a reference:
cmake_minimum_required(VERSION 3.10)
project(ProjectName)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Specify the path to external dependencies
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/external/caitlyn")
# Find and include the Caitlyn package configuration
find_package(Caitlyn CONFIG REQUIRED)
# Create an executable target from main.cpp
add_executable(${PROJECT_NAME} main.cpp)
# Link the executable with Caitlyn library
target_link_libraries(${PROJECT_NAME} PRIVATE Caitlyn::Caitlyn)
Replace ProjectName
with the actual name of your project and ensure main.cpp
is the source file for your main executable.
Run the simple program.
#include <caitlyn/io>
int main() {
cait::println("Hello, world!");
return 0;
}
Basic string enhanced with correct UTF-8 operations.
Usage
#include <caitlyn/io>
#include <caitlyn/string>
int main() {
cait::string_t data = "Hello, 世界! 🙂";
cait::println("String: {}", data);
cait::println("Substring (from 7): {}", data.substr(7));
cait::println("Char count: {}", data.size());
cait::println("Byte count: {}", data.byte_count());
cait::println("Starts with H? {}", data.starts_with("H"));
cait::println("Ends with 🙂? {}", data.ends_with("🙂"));
cait::println("Contains '世界'? {}", data.contains("世界"));
cait::println("Contains 'some text'? {}", data.contains("some text"));
cait::println("Char at position 0: {}", data[0]);
cait::println("Char at position 7: {}", data.at(7));
for (auto&& elem : data) {
if (elem == "🙂") {
elem = "🍉";
}
}
cait::println("Updated: {}", data);
return 0;
}
To correctly display UTF-8 characters in Windows, you must call the
set_windows_utf8_encode
function at the entry point.
int main() {
cait::set_windows_utf8_encode();
// ...
}
Output:
String: Hello, 世界! 🙂
Substring (from 7): 世界! 🙂
Char count: 12
Byte count: 19
Starts with H? true
Ends with 🙂? true
Contains '世界'? true
Contains 'some text'? false
Char at position 0: H
Char at position 7: 世
Updated: Hello, 世界! 🍉
text_t
: - String builder type.format
: - Universal string formatter.
Usage
#include <caitlyn/io>
#include <caitlyn/text>
int main() {
const auto first = "Hello"_str;
const auto second = "world"_str;
const auto data = cait::format("{}, {}!", first, second);
// Alignment
cait::println("L: '{:<25}'", data);
cait::println("C: '{:^25}'", data);
cait::println("R: '{:>25}'\n", data);
// // Precision
cait::println("Floating: {:.4f}", 64.932698);
cait::println("Decimal: {:d}\n", 6427123266375693);
// // Filling
cait::println("Line: {:-<24}");
cait::println("Fill: {:*^24}\n", "TEXT");
auto content = cait::make_text("Text: ");
content.append("Lorem ipsum dolor sit amet, ");
content.append_line("consectetur adipiscing elit...");
content.append_line(data);
cait::println(content);
return 0;
}
Output:
L: 'Hello, world! '
C: ' Hello, world! '
R: ' Hello, world!'
Floating: 64.9326
Decimal: 6427123266375693
Line: ------------------------
Fill: **********TEXT**********
Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit...
Hello, world!
file_t
: Universal file handler.
Usage
- Write text to file
#include <caitlyn/file>
int main() {
const auto some = "Lorem ipsum dolor sit amet,"_str;
const auto other = "consectetur adipiscing elit..."_str;
const auto unicode = "Hello, 世界!"_str;
auto file = "somefile.txt"_file;
file.write("{} {}\n{}", some, other, unicode);
file.close();
return 0;
}
- File (somefile.txt)
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
Hello, 世界!
- Read lines from file
#include <caitlyn/io>
int main() {
auto file = "somefile.txt"_file;
while (file) {
cait::println("{}", file.read_line());
}
file.close();
return 0;
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
Hello, 世界!
The JSON format is crucial for web development, API integration, and any applications that need efficient data exchange in a structured format.
json_t
: Provides comprehensive support for JSON handling. Facilitates parsing, generating, and manipulating JSON data structures. Enables easy serialization of complex data into JSON format for storage or transmission, and deserialization of JSON back into native data structures. Offers efficient methods for encoding and decoding JSON, handling nested objects and arrays, ensuring compatibility across various platforms and systems.
Usage
- Brackets operator
#include <caitlyn/io>
#include <caitlyn/serializing>
int main() {
auto config = cait::make_json();
config["name"] = cait::json::make_object();
config["name"]["first"] = "John";
config["name"]["last"] = "Doe";
config["age"] = 30;
config["address"] = cait::json::make_object();
config["address"]["street"] = "123 Main St";
config["address"]["city"] = "Anytown";
config["address"]["zip"] = "12345";
config["phone_numbers"] = cait::json::make_array("555-1234", "555-5678");
cait::println(config);
return 0;
}
- Native JSON
#include <caitlyn/io>
#include <caitlyn/serializing>
int main() {
const auto config = json_str(
{
"name": {
"first": "John",
"last": "Doe"
},
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"zip": "12345"
},
"phone_numbers": [
"555-1234",
"555-5678"
]
}
);
cait::println(config);
return 0;
}
- Optimized output
config.str();
- Structured output
// Pass true and optional indent width (2 by default)
config.str(true);
config.str(true, 2);
Output
{"name":{"first":"John","last":"Doe"},"age":30,"address":{"street":"123 Main St","city":"Anytown","zip":"12345"},"phone_numbers":["555-1234","555-5678"]}
{
"name": {
"first": "John",
"last": "Doe"
},
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"zip": "12345"
},
"phone_numbers": [
"555-1234",
"555-5678"
]
}
Handling errors without standard exceptions.
-
result_t
: Represents a type to encapsulate the result of an operation that may succeed or fail, along with an associated error type. It provides a type-safe way to handle both successful outcomes and errors without relying on exceptions. -
error_t
: Represents error types used in conjunction withresult_t
for detailed error reporting and handling within operations. It provides a structured way to categorize and manage errors that occur during computations or operations.
Usage
#include <caitlyn/error>
#include <caitlyn/io>
enum class MathError {
kDivideByZero
};
auto Divide(const double lhs, const double rhs)
-> cait::result_t<double, MathError> {
if (lhs == 0 || rhs == 0) {
return cait::make_error(MathError::kDivideByZero);
}
return lhs / rhs;
}
int main() {
const auto result = Divide(64, 4);
if (result) {
cait::println("64 / 4 = {}", result.get());
} else {
if (result.get_error() == MathError::kDivideByZero) {
cait::eprintln("Error: divide by zero");
}
}
return 0;
}
Arbitrary-precision types provides tools for working with numbers of any size
or precision, unrestricted by standard data types like int
or float
.
It enables performing arithmetic operations, comparisons, and other mathematical
computations with high precision, avoiding data loss due to type limitations.
Such types are often used in applications requiring high-precision calculations,
such as financial applications, scientific research, or cryptography.
pwrint_t
: Integral type of arbitrary lengthpwrnum_t
: Floating point type with arbitrary precision
Usage
- Integral
#include <caitlyn/io>
#include <caitlyn/numeric>
int main() {
const auto a = "47011878636176761032731633812398273982371829"_pwrint;
const auto b = "10218827321893782973821793709217371273"_pwrint;
const auto result = a * b;
cait::println(result);
return 0;
}
480406269860917721318957511814148894618259818296995209585410018969574705029068317
- Floating point
#include <caitlyn/io>
#include <caitlyn/numeric>
int main() {
const auto a = "182.81278920101871298728193797392737812737"_pwrnum;
const auto b = "7.8827318902910380293782646543821795732418"_pwrnum;
const auto result = a * b;
cait::println(result);
return 0;
}
1441.64203387923303265813084431780163079588042340079866748019604087803446244208066
required_t < CONDITION, OPTIONAL_RETURN_TYPE >
The required_t
type trait provides a more elegant alternative to
std::enable_if
for enforcing template constraints in C++.
- CONDITION: A boolean value that determines whether the template is enabled.
- OPTIONAL_RETURN_TYPE: The type to return if the condition is met.
Defaults to
void
Usage
Use SFINAE with a template default type parameter.
Function template Add
accepts two parameters of type T
and returns
their sum. It uses required_t
to enforce that T
is an integral type,
as a template default type parameter.
#include <caitlyn/traits>
template <typename T, typename = cait::required_t<cait::is_integral<T>()>>
T Add(const T lhs, const T rhs) {
return lhs + rhs;
}
Use SFINAE as a return type constraint.
Function template Add
uses required_t
directly in the return type.
This enforces that T
must be an integral type.
#include <caitlyn/traits>
template <typename T>
cait::required_t<cait::is_integral<T>(), T>
Add(const T lhs, const T rhs) {
return lhs + rhs;
}
Use SFINAE as a trailing return type constraint.
Function template Add
uses a trailing return type to apply the required_t
constraint. This ensures that T
is an integral type.
#include <caitlyn/traits>
template <typename T>
auto Add(const T lhs, const T rhs)
-> cait::required_t<cait::is_integral<T>(), T> {
return lhs + rhs;
}
Caitlyn includes a lightweight testing component for unit testing within your projects.
- Test Definition: Define tests using macros like
TEST
for standalone tests andTEST_F
for fixture-based tests. - Dynamic Test Registration: Tests are dynamically registered using a central registry for easy discovery and execution.
- Assertions: Includes assertion macros (
ASSERT_TRUE
,ASSERT_EQ
, etc.) for validating expected behaviors. - Detailed Reporting: Reports detailed information on passed and failed tests, aiding in debugging.
Usage
#include <caitlyn/testing>
TEST(MathTests, TestAddition) {
ASSERT_EQ(2 + 3, 5);
}
TEST(MathTests, TestFailure) {
ASSERT_EQ_PRINTABLE(2 + 2, 5);
}
template <typename T>
struct MathFixture {
T a{};
T b{};
MathFixture() {}
~MathFixture() {}
void SetValues(const T& x, const T& y) {
a = x;
b = y;
}
};
TEST_F(MathFixture<int>, TestFixtureAddition) {
SetValues(1, 2);
ASSERT_EQ(a + b, 3);
}
int main() {
return cait::test::registry_t::instance().run_all();
}
Output
[==========] Running 3 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 tests from MathFixture<int>
[ RUN ] MathFixture<int>.TestFixtureAddition
[ OK ] MathFixture<int>.TestFixtureAddition (0 ms)
[----------] 1 tests from MathFixture<int> (0 ms total)
[----------] 2 tests from MathTests
[ RUN ] MathTests.TestAddition
[ OK ] MathTests.TestAddition (0 ms)
[ RUN ] MathTests.TestFailure
[ FAIL ] MathTests.TestFailure (0 ms)
[ INFO ] Assertion failed: 2 + 2 == 5 (4 != 5)
[----------] 2 tests from MathTests (0 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 2 test cases ran. (4 ms total)
[ PASSED ] 2 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] MathTests.TestFailure
1 FAILED TEST
- Get keys and values from the
std::map
usingget_map_keys
andget_map_values
#include <caitlyn/containers>
#include <caitlyn/io>
#include <caitlyn/string>
int main() {
const std::map<int, std::string> numbers{
{1, "One"}, {2, "Two"}, {3, "Three"}, {4, "Four"}, {5, "Five"}};
for (auto& key : cait::get_map_keys(numbers)) {
cait::print("{} ", key);
}
cait::println();
for (auto& value : cait::get_map_values(numbers)) {
cait::print("{} ", value);
}
return 0;
}
1 2 3 4 5
One Two Three Four Five