[RFC 3/4] libcamera: Implement YamlEmitter
Jacopo Mondi
jacopo.mondi at ideasonboard.com
Mon Oct 14 11:59:35 CEST 2024
Signed-off-by: Jacopo Mondi <jacopo.mondi at ideasonboard.com>
---
include/libcamera/internal/meson.build | 1 +
include/libcamera/internal/yaml_emitter.h | 172 +++++++++
src/libcamera/meson.build | 1 +
src/libcamera/yaml_emitter.cpp | 427 ++++++++++++++++++++++
4 files changed, 601 insertions(+)
create mode 100644 include/libcamera/internal/yaml_emitter.h
create mode 100644 src/libcamera/yaml_emitter.cpp
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 1c5eef9cab80..7533b075fde2 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -41,6 +41,7 @@ libcamera_internal_headers = files([
'v4l2_pixelformat.h',
'v4l2_subdevice.h',
'v4l2_videodevice.h',
+ 'yaml_emitter.h',
'yaml_parser.h',
])
diff --git a/include/libcamera/internal/yaml_emitter.h b/include/libcamera/internal/yaml_emitter.h
new file mode 100644
index 000000000000..3fe05f97de70
--- /dev/null
+++ b/include/libcamera/internal/yaml_emitter.h
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board Oy
+ *
+ * libcamera YAML emitter helper
+ */
+
+#pragma once
+
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/file.h>
+#include <libcamera/orientation.h>
+
+#include <yaml.h>
+
+namespace libcamera {
+
+class YamlDict;
+class YamlEvent;
+class YamlList;
+class YamlRoot;
+class YamlScalar;
+
+class YamlEmitter final
+{
+public:
+ ~YamlEmitter() = default;
+
+ static std::unique_ptr<YamlRoot> root(std::string_view path);
+
+ int emit(YamlEvent *event);
+
+private:
+ LIBCAMERA_DISABLE_COPY(YamlEmitter)
+
+ class Emitter
+ {
+ public:
+ Emitter() = default;
+ ~Emitter();
+
+ void init(File *file);
+
+ int emit(YamlEvent *event);
+
+ private:
+ void logError();
+
+ yaml_emitter_t emitter_;
+ };
+
+ YamlEmitter() = default;
+
+ void init();
+
+ std::unique_ptr<File> file_;
+ Emitter emitter_;
+};
+
+class YamlOutput
+{
+public:
+ virtual ~YamlOutput() = default;
+
+ void close()
+ {
+ closed_ = true;
+ }
+
+ std::unique_ptr<YamlScalar> scalar();
+ std::unique_ptr<YamlDict> dict();
+ std::unique_ptr<YamlList> list();
+
+protected:
+ YamlOutput(YamlEmitter *emitter)
+ : emitter_(emitter), closed_(false)
+ {
+ }
+
+ int emitScalar(std::string_view scalar);
+ int emitMappingStart();
+ int emitMappingEnd();
+ int emitSequenceStart();
+ int emitSequenceEnd();
+
+ YamlEmitter *emitter_;
+
+ bool closed_;
+};
+
+class YamlRoot : public YamlOutput
+{
+public:
+ ~YamlRoot();
+ void close() {}
+
+ std::unique_ptr<YamlList> list();
+ std::unique_ptr<YamlDict> dict();
+ void scalar(std::string_view scalar);
+
+private:
+ friend class YamlEmitter;
+
+ YamlRoot(YamlEmitter *emitter)
+ : YamlOutput(emitter)
+ {
+ emitterRoot_ = std::unique_ptr<YamlEmitter>(emitter);
+ }
+
+ std::unique_ptr<YamlEmitter> emitterRoot_;
+};
+
+class YamlScalar : public YamlOutput
+{
+public:
+ ~YamlScalar() = default;
+
+ void close() {}
+
+ void operator=(const Orientation &orientation);
+ void operator=(std::string_view scalar);
+
+private:
+ friend class YamlOutput;
+
+ YamlScalar(YamlEmitter *emitter);
+};
+
+class YamlList : public YamlOutput
+{
+public:
+ ~YamlList();
+
+ void close();
+
+ std::unique_ptr<YamlList> list();
+ std::unique_ptr<YamlDict> dict();
+ void scalar(std::string_view scalar);
+
+private:
+ friend class YamlOutput;
+
+ YamlList(YamlEmitter *emitter);
+};
+
+class YamlDict : public YamlOutput,
+ private std::unordered_map<std::string_view,
+ std::unique_ptr<YamlScalar>>
+{
+public:
+ using Map = std::unordered_map<std::string_view, YamlScalar>;
+
+ ~YamlDict();
+
+ void close();
+
+ std::unique_ptr<YamlList> list(std::string_view key);
+ std::unique_ptr<YamlDict> dict(std::string_view key);
+
+ YamlScalar &operator[](const Map::key_type &key);
+
+private:
+ friend class YamlOutput;
+
+ YamlDict(YamlEmitter *emitter);
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index aa9ab0291854..5b8af4103085 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -51,6 +51,7 @@ libcamera_internal_sources = files([
'v4l2_pixelformat.cpp',
'v4l2_subdevice.cpp',
'v4l2_videodevice.cpp',
+ 'yaml_emitter.cpp',
'yaml_parser.cpp',
])
diff --git a/src/libcamera/yaml_emitter.cpp b/src/libcamera/yaml_emitter.cpp
new file mode 100644
index 000000000000..1f7651ffcb24
--- /dev/null
+++ b/src/libcamera/yaml_emitter.cpp
@@ -0,0 +1,427 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board Oy
+ *
+ * libcamera YAML emitter helper
+ */
+
+#include "libcamera/internal/yaml_emitter.h"
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file yaml_emitter.h
+ * \brief A YAML emitter helper
+ *
+ * The YAML Emitter helpers allows users to emit output in YAML format.
+ *
+ * To emit YAML users of this classes should create a root node with
+ *
+ * \code
+ std::string filePath("...");
+ auto root = YamlEmitter::root(filePath);
+ \endcode
+ *
+ * A YamlRoot implements RAII-style handling of YAML sequence and document
+ * events handling. Creating a YamlRoot emits the STREAM_START and DOC_START
+ * events. Once a YamlRoot gets deleted the DOC_SEND and STREAM_END events
+ * gets automatically deleted.
+ *
+ * Once a root node has been created it is possible to populate it with
+ * scalars, list or dictionaries.
+ *
+ * YamlList and YamlDict can only be created wrapped in unique_ptr<> instances,
+ * to implement a RAII-style handling of YAML of lists and dictionaries.
+ * Creating a YamlList and a YamlDict emits the YAML sequence and mapping start
+ * events. Once an instance gets deleted, the sequence and mapping gets
+ * automatically emitted. If a list of dictionary needs to be closed before
+ * it gets deleted, it can be explicitly closed with the close() function.
+ *
+ * A YamlScalar is a simpler object that can be assigned to different types
+ * to emit them as strings.
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(YamlEmitter)
+
+namespace {
+
+int yamlWrite(void *data, unsigned char *buffer, size_t size)
+{
+ File *file = static_cast<File *>(data);
+
+ Span<unsigned char> buf{ buffer, size };
+ ssize_t ret = file->write(buf);
+ if (ret < 0) {
+ ret = errno;
+ LOG(YamlEmitter, Error) << "Write error : " << strerror(ret);
+ return 0;
+ }
+
+ return 1;
+}
+
+} /* namespace */
+
+/**
+ * \class YamlEvent
+ *
+ * Class that represents a yaml_event that automatically cleans-up on
+ * destruction.
+ */
+class YamlEvent
+{
+public:
+ static std::unique_ptr<YamlEvent> create();
+
+ const yaml_event_t *event() const
+ {
+ return &event_;
+ }
+
+ yaml_event_t *event()
+ {
+ return &event_;
+ }
+
+private:
+ YamlEvent() = default;
+
+ yaml_event_t event_;
+};
+
+std::unique_ptr<YamlEvent> YamlEvent::create()
+{
+ struct Deleter : std::default_delete<YamlEvent> {
+ void operator()(YamlEvent *yamlEvent)
+ {
+ yaml_event_delete(yamlEvent->event());
+ }
+ };
+
+ return std::unique_ptr<YamlEvent>(new YamlEvent(), Deleter());
+}
+
+/**
+ * \class YamlEmitter
+ *
+ * Yaml Emitter entry point. Allows to create a YamlRoot object that users
+ * can populate.
+ */
+
+/**
+ * \brief Create a YamlRoot that applications can start populating with YamlOutput
+ * \param[in] path The YAML output file path
+ * \return A unique pointer to a YamlRoot
+ */
+std::unique_ptr<YamlRoot> YamlEmitter::root(std::string_view path)
+{
+ YamlEmitter *emitter = new YamlEmitter();
+
+ std::string filePath(path);
+ emitter->file_ = std::make_unique<File>(filePath);
+ emitter->file_->open(File::OpenModeFlag::WriteOnly);
+
+ emitter->init();
+
+ YamlRoot *root = new YamlRoot(emitter);
+ return std::unique_ptr<YamlRoot>(root);
+}
+
+/**
+ * \brief Emit a YamlEvent
+ */
+int YamlEmitter::emit(YamlEvent *event)
+{
+ return emitter_.emit(event);
+}
+
+void YamlEmitter::init()
+{
+ emitter_.init(file_.get());
+
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+
+ yaml_stream_start_event_initialize(event->event(), YAML_UTF8_ENCODING);
+ emitter_.emit(event.get());
+
+ yaml_document_start_event_initialize(event->event(), NULL, NULL,
+ NULL, 0);
+ emitter_.emit(event.get());
+}
+
+/**
+ * \class YamlEmitter::Emitter
+ *
+ * yaml_emitter_t wrapper. Initialize the yaml_emitter_t on creation allows
+ * YamlOutput classes to emit events.
+ */
+
+YamlEmitter::Emitter::~Emitter()
+{
+ yaml_emitter_delete(&emitter_);
+}
+
+void YamlEmitter::Emitter::init(File *file)
+{
+ yaml_emitter_initialize(&emitter_);
+ yaml_emitter_set_output(&emitter_, yamlWrite, file);
+}
+
+void YamlEmitter::Emitter::logError()
+{
+ switch (emitter_.error) {
+ case YAML_MEMORY_ERROR:
+ LOG(YamlEmitter, Error)
+ << "Memory error: Not enough memory for emitting";
+ break;
+
+ case YAML_WRITER_ERROR:
+ LOG(YamlEmitter, Error)
+ << "Writer error: " << emitter_.problem;
+ break;
+
+ case YAML_EMITTER_ERROR:
+ LOG(YamlEmitter, Error)
+ << "Emitter error: " << emitter_.problem;
+ break;
+
+ default:
+ LOG(YamlEmitter, Error) << "Internal problem";
+ break;
+ }
+}
+
+int YamlEmitter::Emitter::emit(YamlEvent *event)
+{
+ int ret = yaml_emitter_emit(&emitter_, event->event());
+ if (!ret) {
+ logError();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * \class YamlOutput
+ *
+ * The YamlOutput base class. From this class YamlScalar, YamlList and YamlDict
+ * are derived.
+ *
+ * The YamlOutput class provides functions to create a scalar, a list or a
+ * dictionary.
+ *
+ * The class cannot be instantiated directly by applications.
+ */
+
+std::unique_ptr<YamlScalar> YamlOutput::scalar()
+{
+ return std::unique_ptr<YamlScalar>(new YamlScalar(emitter_));
+}
+
+std::unique_ptr<YamlDict> YamlOutput::dict()
+{
+ return std::unique_ptr<YamlDict>(new YamlDict(emitter_));
+}
+
+std::unique_ptr<YamlList> YamlOutput::list()
+{
+ return std::unique_ptr<YamlList>(new YamlList(emitter_));
+}
+
+int YamlOutput::emitScalar(std::string_view scalar)
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+
+ const unsigned char *value = reinterpret_cast<const unsigned char *>
+ (scalar.data());
+ yaml_scalar_event_initialize(event->event(), NULL, NULL, value,
+ scalar.length(), true, false,
+ YAML_PLAIN_SCALAR_STYLE);
+ return emitter_->emit(event.get());
+}
+
+int YamlOutput::emitMappingStart()
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+ yaml_mapping_start_event_initialize(event->event(), NULL, NULL,
+ true, YAML_BLOCK_MAPPING_STYLE);
+ return emitter_->emit(event.get());
+}
+
+int YamlOutput::emitMappingEnd()
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+ yaml_mapping_end_event_initialize(event->event());
+ return emitter_->emit(event.get());
+}
+
+int YamlOutput::emitSequenceStart()
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+ yaml_sequence_start_event_initialize(event->event(), NULL, NULL, true,
+ YAML_BLOCK_SEQUENCE_STYLE);
+ return emitter_->emit(event.get());
+}
+
+int YamlOutput::emitSequenceEnd()
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+ yaml_sequence_end_event_initialize(event->event());
+ return emitter_->emit(event.get());
+}
+
+/**
+ * \class YamlRoot
+ *
+ * Yaml root node. A root node can be populated with a scalar, a list or a dict.
+ */
+
+YamlRoot::~YamlRoot()
+{
+ std::unique_ptr<YamlEvent> event = YamlEvent::create();
+
+ yaml_document_end_event_initialize(event->event(), 0);
+ emitterRoot_->emit(event.get());
+
+ yaml_stream_end_event_initialize(event->event());
+ emitterRoot_->emit(event.get());
+}
+
+std::unique_ptr<YamlDict> YamlRoot::dict()
+{
+ emitMappingStart();
+
+ return YamlOutput::dict();
+}
+
+std::unique_ptr<YamlList> YamlRoot::list()
+{
+ emitSequenceStart();
+
+ return YamlOutput::list();
+}
+
+/**
+ * \class YamlScalar
+ *
+ * A YamlScalar can be assigned to an std::string_view and other libcamera
+ * types to emit them as YAML scalars.
+ */
+
+YamlScalar::YamlScalar(YamlEmitter *emitter)
+ : YamlOutput(emitter)
+{
+}
+
+void YamlScalar::operator=(const libcamera::Orientation &orientation)
+{
+ std::stringstream o;
+ o << orientation;
+
+ emitScalar(o.str());
+}
+
+void YamlScalar::operator=(std::string_view scalar)
+{
+ emitScalar(scalar);
+}
+
+/**
+ * \class YamlList
+ *
+ * A YamlList can be populated with scalar and allows to create other lists
+ * and dictionaries.
+ */
+
+YamlList::YamlList(YamlEmitter *emitter)
+ : YamlOutput(emitter)
+{
+}
+
+YamlList::~YamlList()
+{
+ if (!closed_)
+ close();
+}
+
+void YamlList::close()
+{
+ emitSequenceEnd();
+ YamlOutput::close();
+}
+
+void YamlList::scalar(std::string_view scalar)
+{
+ emitScalar(scalar);
+}
+
+std::unique_ptr<YamlList> YamlList::list()
+{
+ emitSequenceStart();
+
+ return YamlOutput::list();
+}
+
+std::unique_ptr<YamlDict> YamlList::dict()
+{
+ emitMappingStart();
+
+ return YamlOutput::dict();
+}
+
+/**
+ * \class YamlDict
+ *
+ * A YamlDict derives an unordered_map that maps strings to YAML scalar.
+ *
+ * A YamlDict can be populated with scalars using operator[] and allows to
+ * create other lists and dictionaries.
+ */
+
+YamlDict::YamlDict(YamlEmitter *emitter)
+ : YamlOutput(emitter)
+{
+}
+
+YamlDict::~YamlDict()
+{
+ if (!closed_)
+ close();
+
+ clear();
+}
+
+void YamlDict::close()
+{
+ emitMappingEnd();
+ YamlOutput::close();
+}
+
+std::unique_ptr<YamlList> YamlDict::list(std::string_view key)
+{
+ emitScalar(key);
+ emitSequenceStart();
+
+ return YamlOutput::list();
+}
+
+std::unique_ptr<YamlDict> YamlDict::dict(std::string_view key)
+{
+ emitScalar(key);
+ emitMappingStart();
+
+ return YamlOutput::dict();
+}
+
+YamlScalar &YamlDict::operator[](const YamlDict::Map::key_type &key)
+{
+ emplace(key, YamlOutput::scalar());
+ emitScalar(key);
+
+ return *at(key);
+}
+
+} /* namespace libcamera */
--
2.46.2
More information about the libcamera-devel
mailing list