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
8 changes: 7 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ set(CMAKE_DEBUG_POSTFIX d)

set(SOURCES
cppIni.cpp

Entry.cpp
File.cpp
Section.cpp
)

set(API_HEADERS
Expand All @@ -12,7 +16,9 @@ set(API_HEADERS
LIST(TRANSFORM API_HEADERS PREPEND ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME}/)

set(HEADERS

Entry.h
File.h
Section.h
)

add_library(${PROJECT_NAME} ${SOURCES} ${API_HEADERS} ${HEADERS})
Expand Down
11 changes: 11 additions & 0 deletions src/Entry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "Entry.h"
#include "Section.h"

auto Entry::fqKey() const -> std::string
{
if (m_parent == nullptr) {
return m_key;
}

return m_parent->fqTitle() + "." + m_key;
}
105 changes: 105 additions & 0 deletions src/Entry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <string>

class Section;

/// \brief The Entry class
/// \details An entry is a key-value pair. The key is a string and the value can be any type. It has a parent Section
/// held by a (non-owning) pointer.
/// \note The value is stored as a string.
/// \note The value is copied into the Entry and not moved.
/// \note The parent Section is a pointer to the Section object that contains this Entry.
class Entry {
public:
constexpr Entry() = default; ///< Default constructor
virtual ~Entry() = default; ///< Destructor

Entry(const Entry& other) = default; ///< Copy constructor

template<class T>
constexpr Entry(std::string_view key, T value, Section* parent = nullptr); ///< Constructor with key, value and parent Section pointer (default nullptr)

auto key() const -> std::string_view { return m_key; } ///< Key as std::string_view

template<class T> auto value() const -> T; ///< Value as type T
auto data() const -> std::string_view { return m_data; } ///< Value as std::string_view
constexpr auto parent() const -> const Section* { return m_parent; } ///< Parent Section
auto fqKey() const -> std::string; ///< Fully qualified key (e.g. "Section1.Section2.Key")

auto setKey(std::string_view key) -> void { m_key = key; } ///< Set the key
template<class T> auto setData(T value) -> void; ///< Set the value

// FIXME Why does GCC 11.4.0 not like constexpr string comparison?
auto operator==(const Entry& other) const -> bool = default; ///< Equality operator
auto operator!=(const Entry& other) const -> bool = default; ///< Inequality operator

auto operator=(const Entry& other) -> Entry& = default; ///< Copy assignment operator
auto operator=(Entry&& other) -> Entry& = default; ///< Move assignment operator

private:
std::string m_key {};
std::string m_data {};
Section* m_parent {nullptr};
};

template<class T>
constexpr Entry::Entry(std::string_view key, T value, Section* parent)
: m_key(key)
, m_parent(parent)
{
m_data = std::to_string(value);
}

template<>
inline Entry::Entry(std::string_view key, std::string value, Section* parent)
: m_key(key)
, m_data(std::move(value))
, m_parent(parent)
{
}

template<>
inline Entry::Entry(std::string_view key, std::string_view value, Section* parent)
: m_key(key)
, m_data(std::string(value))
, m_parent(parent)
{
}

template<>
inline Entry::Entry(std::string_view key, const char* value, Section* parent)
: m_key(key)
, m_data(std::string(value))
, m_parent(parent)
{
}

template<class T>
auto Entry::setData(T value) -> void
{
m_data = std::to_string(value);
}

template<>
inline auto Entry::setData(std::string value) -> void
{
m_data = std::move(value);
}

template<> inline auto Entry::value<char>() const -> char { return std::stoi(m_data); }
template<> inline auto Entry::value<short>() const -> short { return std::stoi(m_data); }
template<> inline auto Entry::value<int>() const -> int { return std::stoi(m_data); }
template<> inline auto Entry::value<long>() const -> long { return std::stol(m_data); }
template<> inline auto Entry::value<long long>() const -> long long { return std::stoll(m_data); }
template<> inline auto Entry::value<unsigned char>() const -> unsigned char { return std::stoull(m_data); }
template<> inline auto Entry::value<unsigned short>() const -> unsigned short { return std::stoull(m_data); }
template<> inline auto Entry::value<unsigned int>() const -> unsigned int { return std::stoull(m_data); }
template<> inline auto Entry::value<unsigned long>() const -> unsigned long { return std::stoull(m_data); }
template<> inline auto Entry::value<unsigned long long>() const -> unsigned long long { return std::stoull(m_data); }
template<> inline auto Entry::value<float>() const -> float { return std::stof(m_data); }
template<> inline auto Entry::value<double>() const -> double { return std::stod(m_data); }
template<> inline auto Entry::value<long double>() const -> long double { return std::stold(m_data); }
template<> inline auto Entry::value<std::string>() const -> std::string { return m_data; }
template<> inline auto Entry::value<std::string_view>() const -> std::string_view { return m_data; }
template<> inline auto Entry::value<const char*>() const -> const char* { return data().data(); }
84 changes: 84 additions & 0 deletions src/File.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "File.h"

#include <fstream>
#include <stdexcept>

/// \param filename The filename of the file to open.
File::File(std::string_view filename)
: m_filename{filename}
{
open();
}

/// \param filename The filename of the file to open.
File File::open(std::string_view filename)
{
return File{filename};
}

/// \throws std::runtime_error if the file cannot be opened.
void File::open()
{
if (m_filename.empty()) {
throw std::runtime_error{"Filename is empty"};
}

parse();
}

/// \param title The title of the Section to find.
/// \returns A pointer to the Section if found, nullptr otherwise.
auto File::findSection(std::string_view title) const -> const Section*
{
for (const auto& section : m_sections) {
if (section.fqTitle() == title) {
return &section;
}
}

return nullptr;
}

/// \param name The name of the Entry to find.
/// \returns A pointer to the Entry if found, nullptr otherwise.
/// \see Section::findEntry
auto File::findEntry(std::string_view name) const -> const Entry*
{
const Section* section = nullptr;

if (name.find('.')) {
section = findSection(name.substr(0, name.find_last_of('.')));
}

if (section) {
return section->findEntry(name.substr(name.find_last_of('.') + 1));
}

return nullptr;
}

/// \details This function is called by the constructor. It should not be called directly.
/// \throws std::runtime_error if the file cannot be opened.
/// \see File::open for the public function.
auto File::parse() -> void
{
auto content = std::ifstream{m_filename};

for (std::string line; std::getline(content, line);) {
const std::string_view lineView{line};

if (lineView.empty()) {
continue;
}

if (line[0] == '[') {
if (line.find('.') != std::string::npos) {
m_sections.emplace_back(lineView.substr(lineView.find_last_of('.') + 1, lineView.find_last_of(']') - 1), &m_sections.back());
} else {
m_sections.emplace_back(lineView.substr(1, lineView.find_last_of(']') - 1));
}
} else {
m_sections.back().createEntry(lineView.substr(0, lineView.find('=')), lineView.substr(lineView.find('=') + 1));
}
}
}
32 changes: 32 additions & 0 deletions src/File.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "Section.h"

#include <vector>

/// \brief Represents a file on disk.
/// A file is a collection of Sections.
class File {
public:
explicit File(std::string_view filename); ///< Constructor.
virtual ~File() = default; ///< Destructor.

static File open(std::string_view filename); ///< Open a file. Throws if the file cannot be opened.
void open(); ///< Open the file. Throws if the file cannot be opened.

auto findSection(std::string_view title) const -> const Section*; ///< Find a Section by title.
auto findEntry(std::string_view name) const -> const Entry*; ///< Find an Entry by name.

constexpr auto sections() const -> const auto& { return m_sections; }

auto operator==(const File& other) const -> bool = default; ///< Equality operator.
auto operator!=(const File& other) const -> bool = default; ///< Inequality operator.

private:
void parse(); ///< Parse the file.

private:
std::string m_filename{};

std::vector<Section> m_sections{};
};
57 changes: 57 additions & 0 deletions src/Section.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "Section.h"

#include <algorithm>

Section::Section(std::string_view title, Section* parent)
: m_title(title)
, m_parent(parent)
{

}

/// \note The entry is moved into the vector
/// \arg entry The Entry to add
auto Section::addEntry(Entry entry) -> void
{
m_entries.insert(std::make_pair(entry.key(), std::move(entry)));
}

/// If the Section is a top-level Section, the title is returned.
/// Otherwise, the title is prefixed with the parent's fully qualified title and a dot.
/// \returns The fully qualified title of the Section.
auto Section::fqTitle() const -> std::string
{
if (m_parent == nullptr) {
return m_title;
}

return m_parent->fqTitle() + "." + m_title;
}

/// \param name The name of the Entry to find.
/// \returns A pointer to the Entry if found, nullptr otherwise.
auto Section::findEntry(std::string_view name) const -> const Entry*
{
const auto entry = m_entries.find(std::string(name));

if (entry != m_entries.cend()) {
return &entry->second;
}

return nullptr;
}

/// \details Two Sections are equal if they have the same title and the same entries. Their parents are not compared.
/// \param other The Section to compare to.
/// \returns true if the Sections are equal, false otherwise.
auto Section::operator==(const Section& other) const -> bool
{
if (m_title != other.m_title) {
return false;
}

return std::ranges::all_of(m_entries, [&other](const auto& entry) {
const auto otherEntry = other.findEntry(entry.first);
return otherEntry and entry.second.data() == otherEntry->data();
});
}
44 changes: 44 additions & 0 deletions src/Section.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#include "Entry.h"

#include <unordered_map>

/// \brief Represents a section in a configuration file
/// \details A section is a collection of Entry objects with a title (e.g. [Section]) in a configuration file
/// \note A section has a title and a list of Entry objects
class Section {
public:
explicit Section(std::string_view title, Section* parent = nullptr); ///< Constructor with title

auto title() const -> std::string_view { return m_title; } ///< Title as std::string_view
auto fqTitle() const -> std::string; ///< Fully qualified title (e.g. "Section1.Section2")

constexpr auto parent() const -> const Section* { return m_parent; } ///< Parent Section
auto isSubsection() const -> bool { return m_parent != nullptr; } ///< Returns true if this Section is a subsection

auto addEntry(Entry entry) -> void; ///< Add an Entry object to the section

template<class T>
auto createEntry(std::string_view key, T value) -> void; ///< Create an Entry object in place and add it to the section

constexpr auto entries() const -> const auto& { return m_entries; } ///< List of Entry objects

auto findEntry(std::string_view name) const -> const Entry*; ///< Find an Entry object by name

auto operator==(const Section& other) const -> bool; ///< Equality operator
auto operator!=(const Section& other) const -> bool = default; ///< Inequality operator
private:
std::string m_title;
std::unordered_map<std::string, Entry> m_entries;
Section *m_parent {nullptr};
};

/// \details The parameters are forwarded to the Entry constructor and a pointer to this Section object is added as the parent
/// \arg key The key of the Entry
/// \arg value The value of the Entry
template<class T>
auto Section::createEntry(std::string_view key, T value) -> void
{
m_entries.emplace(key, Entry{key, value, this});
}
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ find_package(doctest REQUIRED)

set(TEST_SOURCES
ConstructionTest.cpp
EntryTest.cpp
FileTest.cpp
SectionTest.cpp
)

add_executable(${PROJECT_NAME}_tests ${TEST_SOURCES})
target_link_libraries(${PROJECT_NAME}_tests doctest::doctest cppIni)
target_compile_definitions(${PROJECT_NAME}_tests
PUBLIC
DOCTEST_CONFIG_NO_MULTITHREADING
DOCTEST_CONFIG_USE_STD_HEADERS
WORKING_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
)

target_include_directories(${PROJECT_NAME}
Expand Down
Loading