From patchwork Wed Nov 6 17:58:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21819 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 995F9C324E for ; Wed, 6 Nov 2024 17:59:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 26A9B65435; Wed, 6 Nov 2024 18:59:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wPsOxB7Y"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 019F665432 for ; Wed, 6 Nov 2024 18:59:10 +0100 (CET) Received: from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it [93.61.96.190]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7BAE6C80; Wed, 6 Nov 2024 18:59:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1730915941; bh=jFTln8oGrWCy1Ik8GlmMhkgfS8bgoZ1LY5c5xZI3Pgs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wPsOxB7YSXKNoLRvO7tIzOA21QkEg9uTT09a0WAmtKg8Su+g/6zRdYcehJ+QvPShH GZT+ryWZwAwFnC305Z+zjUG80Pl8jiLTs7yNhgOVlwD2KmqAb98N0JiSWVl5WkINXu KZUZR/NH4+6tb2Ft2w/5OuOfK6X7k3qYeq70/ebc= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH 1/3] libcamera: Implement YamlEmitter Date: Wed, 6 Nov 2024 18:58:51 +0100 Message-ID: <20241106175901.83960-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241106175901.83960-1-jacopo.mondi@ideasonboard.com> References: <20241106175901.83960-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Implement a helpers to allow outputting text in YAML format. The class of helpers allows to create list and dictionaries and emit scalar values starting from a YamlRoot object initialized with a file path. Signed-off-by: Jacopo Mondi --- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_emitter.h | 164 ++++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 577 ++++++++++++++++++++++ 4 files changed, 743 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..78196a7f147f --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * libcamera YAML emitter helper + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace libcamera { + +class YamlDict; +class YamlEvent; +class YamlList; +class YamlRoot; +class YamlScalar; + +class YamlEmitter final +{ +public: + ~YamlEmitter(); + + static YamlRoot root(const std::string &path); + +private: + friend class YamlOutput; + friend class YamlRoot; + + LIBCAMERA_DISABLE_COPY(YamlEmitter) + + YamlEmitter(const std::string &path); + + void logError(); + void init(); + int emit(); + + File file_; + yaml_event_t event_; + yaml_emitter_t emitter_; +}; + +class YamlOutput +{ +public: + bool valid() const { return !!emitter_; } + +protected: + YamlOutput() = default; + + YamlOutput(YamlEmitter *emitter) + : emitter_(emitter) + { + } + + YamlOutput &operator=(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + + return *this; + } + + int emitScalar(std::string_view scalar); + int emitMappingStart(); + int emitMappingEnd(); + int emitSequenceStart(); + int emitSequenceEnd(); + + YamlScalar scalar(); + YamlDict dict(); + YamlList list(); + + YamlEmitter *emitter_ = nullptr; + yaml_event_t event_; + +private: + LIBCAMERA_DISABLE_COPY(YamlOutput) +}; + +class YamlRoot : public YamlOutput +{ +public: + YamlRoot() = default; + ~YamlRoot(); + + YamlRoot &operator=(YamlRoot &&other) = default; + + YamlList list(); + YamlDict dict(); + +private: + LIBCAMERA_DISABLE_COPY(YamlRoot); + + friend class YamlEmitter; + + YamlRoot(std::unique_ptr emitter) + : YamlOutput(emitter.get()), emitterRoot_(std::move(emitter)) + { + } + + std::unique_ptr emitterRoot_; +}; + +class YamlScalar : public YamlOutput +{ +public: + YamlScalar() = default; + ~YamlScalar() = default; + + void operator=(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlScalar(YamlEmitter *emitter); +}; + +class YamlList : public YamlOutput +{ +public: + YamlList() = default; + ~YamlList(); + + YamlList &operator=(YamlList &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlList(YamlEmitter *emitter); +}; + +class YamlDict : public YamlOutput +{ +public: + YamlDict() = default; + ~YamlDict(); + + YamlDict &operator=(YamlDict &&other) = default; + + YamlList list(std::string_view key); + YamlDict dict(std::string_view key); + + YamlScalar operator[](std::string_view 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..fa3de91ce988 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,577 @@ +/* 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 + +/** + * \file yaml_emitter.h + * \brief A YAML emitter helper + * + * The YAML emitter helpers allow users to emit output in YAML format. + * + * To emit YAML users of the these helper classes create a root node with + * + * \code + std::string filePath("..."); + auto root = YamlEmitter::root(filePath); + \endcode + * + * and start populating it with dictionaries and lists with the YamlRoot::dict() + * and YamlRoot::list() functions. + * + * The classes part of this file implement RAII-style handling of YAML + * events. By creating a YamlList and YamlDict instance the associated YAML + * sequence start and mapping start events are emitted and once the instances + * gets destroyed the corresponding sequence end and mapping end events are + * emitted. + * + * From an initialized YamlRoot instance is possible to create YAML list and + * dictionaries. + * + * \code + YamlDict dict = root.dict(); + YamlList list = root.list(); + \endcode + * + * YamlDict instances can be populated with scalars associated with a key + * + * \code + dict["key"] = "value"; + \endcode + * + * and it is possible to create lists and dictionaries, associated with a key + * + * \code + YamlDict subDict = dict.dict("newDict"); + YamlList subList = dict.list("newList"); + \endcode + * + * YamlList instances can be populated with scalar elements + * + * \code + list.scalar("x"); + list.scalar("y"); + \endcode + * + * and with dictionaries and lists too + * + * \code + YamlDict subDict = list.dict(); + YamlList subList = list.list(); + \endcode + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(YamlEmitter) + +namespace { + +int yamlWrite(void *data, unsigned char *buffer, size_t size) +{ + File *file = static_cast(data); + + Span buf{ buffer, size }; + ssize_t ret = file->write(buf); + if (ret < 0) { + LOG(YamlEmitter, Error) << "Write error : " << strerror(ret); + return 0; + } + + return 1; +} + +} /* namespace */ + +/** + * \class YamlEmitter + * + * YAML helper classes entry point. This class allows to create a YamlRoot + * instances, using the YamlEmitter::root() function, that users can populate + * with lists, dictionaries and scalars. + */ + +YamlEmitter::YamlEmitter(const std::string &path) +{ + std::string filePath(path); + file_.setFileName(filePath); + file_.open(File::OpenModeFlag::WriteOnly); +} + +/** + * \brief Destroy the YamlEmitter + */ +YamlEmitter::~YamlEmitter() +{ + yaml_event_delete(&event_); + yaml_emitter_delete(&emitter_); +} + +/** + * \brief Create an initialized instance of YamlRoot + * \param[in] path The YAML output file path + * + * Create an initialized instance of the YamlRoot class that users can start + * using and populating with scalers, lists and dictionaries. + * + * \return An initialized YamlRoot instance + */ +YamlRoot YamlEmitter::root(const std::string &path) +{ + std::unique_ptr emitter{ new YamlEmitter(path) }; + + emitter->init(); + + return YamlRoot(std::move(emitter)); +} + +void YamlEmitter::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; + } +} + +void YamlEmitter::init() +{ + yaml_emitter_initialize(&emitter_); + yaml_emitter_set_output(&emitter_, yamlWrite, &file_); + + yaml_stream_start_event_initialize(&event_, YAML_UTF8_ENCODING); + emit(); + + yaml_document_start_event_initialize(&event_, NULL, NULL, NULL, 0); + emit(); +} + +int YamlEmitter::emit() +{ + int ret = yaml_emitter_emit(&emitter_, &event_); + if (!ret) { + logError(); + return -EINVAL; + } + + return 0; +} + +/** + * \class YamlOutput + * + * The YamlOutput base class. From this class are derived the YamlScalar, + * YamlList and YamlDict classes which are meant to be used by users of the + * YAML emitter helpers. + * + * The YamlOutput base class provides functions to create YAML lists and + * dictionaries and to populate them. + * + * Instances of this class cannot be instantiated directly by applications. + */ + +/** + * \fn YamlOutput::valid() + * \brief Check if a YamlOutput instance has been correctly initialized + * \return True if the instance has been initialized, false otherwise + */ + +/** + * \fn YamlOutput::YamlOutput(YamlEmitter *emitter) + * \brief Create a YamlOutput instance with an associated emitter + * \param[in] emitter The YAML emitter + */ + +/** + * \fn YamlOutput &YamlOutput::operator=(YamlOutput &&other) + * \brief The move-assignment operator + * \param[in] other The instance to be moved + */ + +/** + * \brief Emit \a scalar as a YAML scalar + * \param[in] scalar The element to emit + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitScalar(std::string_view scalar) +{ + if (!valid()) + return -EINVAL; + + const yaml_char_t *value = reinterpret_cast + (scalar.data()); + yaml_scalar_event_initialize(&emitter_->event_, NULL, NULL, value, + scalar.length(), true, false, + YAML_PLAIN_SCALAR_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the mapping start YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitMappingStart() +{ + if (!valid()) + return -EINVAL; + + yaml_mapping_start_event_initialize(&emitter_->event_, NULL, NULL, + true, YAML_BLOCK_MAPPING_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the mapping end YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitMappingEnd() +{ + if (!valid()) + return -EINVAL; + + yaml_mapping_end_event_initialize(&emitter_->event_); + return emitter_->emit(); +} + +/** + * \brief Emit the sequence start YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitSequenceStart() +{ + if (!valid()) + return -EINVAL; + + yaml_sequence_start_event_initialize(&emitter_->event_, NULL, NULL, + true, YAML_BLOCK_SEQUENCE_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the sequence end YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitSequenceEnd() +{ + if (!valid()) + return -EINVAL; + + yaml_sequence_end_event_initialize(&emitter_->event_); + return emitter_->emit(); +} + +/** + * \brief Create a scalar instance + * \return An instance of YamlScalar + */ +YamlScalar YamlOutput::scalar() +{ + return YamlScalar(emitter_); +} + +/** + * \brief Create a dictionary instance + * \return An instance of YamlDict + */ +YamlDict YamlOutput::dict() +{ + return YamlDict(emitter_); +} + +/** + * \brief Create a list instance + * \return An instance of YamlList + */ +YamlList YamlOutput::list() +{ + return YamlList(emitter_); +} + +/** + * \var YamlOutput::emitter_ + * \brief The emitter used by this YamlObject to output YAML events + */ + +/** + * \var YamlOutput::event_ + * \brief The YAML event used by this YamlObject + */ + +/** + * \class YamlRoot + * + * The YAML root node. A valid YamlRoot instance can only be created using the + * YamlEmitter::root() function. The typical initialization pattern of users of + * this class is similar to the one in the following example: + * + * \code + class YamlUser + { + public: + YamlUser(); + + private: + YamlRool root_; + }; + + YamlUser::YamlUser() + { + root_ = YamlEmitter::root("/path/to/yaml/file.yml"); + } + \endcode + * + * A YamlRoot element can be populated with list and dictionaries. + */ + +/** + * \fn YamlRoot::YamlRoot() + * \brief Construct a YamlRoot instance without initializing it + * + * A YamlRoot instance can be created in non-initialized state typically to be + * stored as a class member by the users of this class. In order to start using + * and populating the YamlRoot instance a valid and initialized instance, + * created using the YamlEmitter::root() function, has to be move-assigned to + * the non-initialized instance. + * + * \code + YamlRoot root; + + root = YamlEmitter::root("/path/to/yaml/file.yml"); + \endcode + */ + +/** + * \brief Delete a YamlRoot + */ +YamlRoot::~YamlRoot() +{ + if (!valid()) + return; + + yaml_document_end_event_initialize(&emitter_->event_, 0); + emitterRoot_->emit(); + + yaml_stream_end_event_initialize(&emitter_->event_); + emitterRoot_->emit(); +} + +/** + * \fn YamlRoot &YamlRoot::operator=(YamlRoot &&other) + * \brief Move assignment operator + * + * Move-assign a YamlRoot instance. This function is typically used to assign an + * initialized instance returned by YamlEmitter::root() to a non-initialized + * one. + * + * \return A reference to this instance of YamlRoot + */ + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlRoot::dict() +{ + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlRoot::list() +{ + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \class YamlScalar + * + * A YamlScalar can be assigned to an std::string_view to emit them as YAML + * elements. + */ + +/** + * \brief Create a YamlScalar instance + */ +YamlScalar::YamlScalar(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Emit \a scalar as a YAML scalar + * \param[in] scalar The element to emit in the YAML output + */ +void YamlScalar::operator=(std::string_view scalar) +{ + emitScalar(scalar); +} + +/** + * \class YamlList + * + * A YamlList can be populated with scalars and allows to create nested lists + * and dictionaries. + */ + +/** + * \brief Create a YamlList + */ +YamlList::YamlList(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Destroy a YamlList instance + */ +YamlList::~YamlList() +{ + emitSequenceEnd(); +} + +/** + * \fn YamlList &YamlList::operator=(YamlList &&other) + * \brief Move-assignment operator + * \param[inout] other The instance to move + */ + +/** + * \brief Append \a scalar to the list + * \param[in] scalar The element to append to the list + */ +void YamlList::scalar(std::string_view scalar) +{ + emitScalar(scalar); +} + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlList::list() +{ + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlList::dict() +{ + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \class YamlDict + * + * A YamlDict can be populated with scalars using operator[] and allows to + * create other lists and dictionaries associated with a key. + */ + +/** + * \fn YamlDict::YamlDict() + * \brief Create a non-initialized instance of a YamlDict + */ + +YamlDict::YamlDict(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Destroy a YamlDict instance + */ +YamlDict::~YamlDict() +{ + emitMappingEnd(); +} + +/** + * \fn YamlDict &YamlDict::operator=(YamlDict &&other) + * \brief Move-assignment operator + * \param[inout] other The instance to move + */ + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlDict::list(std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlDict::dict(std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \brief Create a scalar associated with \a key in the dictionary + * \param[in] key The key associated with the newly created scalar + * \return A YamlScalar that application can use to output text + */ +YamlScalar YamlDict::operator[](std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + return YamlOutput::scalar(); +} + +} /* namespace libcamera */