From patchwork Mon Oct 21 09:49: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: 21714 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 A5C42C330A for ; Mon, 21 Oct 2024 09:50:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8C94965397; Mon, 21 Oct 2024 11:50:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nrSV49VJ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6492A6538B for ; Mon, 21 Oct 2024 11:50:14 +0200 (CEST) Received: from ideasonboard.com (mob-5-90-62-78.net.vodafone.it [5.90.62.78]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E8CD18B9; Mon, 21 Oct 2024 11:48:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729504108; bh=pTrILWZAB7I/LxeI6m+s+r3ui6Hzh4dg15Q4S9zZCIc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nrSV49VJjsp0FnPSsiBUrX3EPcWSmb1x0ezNVTzOt9XVNgmMQVC4D680i4l5ngrn+ dQ4nHd+sdnghUwWLLbXe5SZgIwqsMOatjoHKbc75GR/zQRgUESkSZiLR6IyRsMXmFv GqWtoCwe6wWowalUzSaVxTU60bgjaeKlko3h/Gyg= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC v3 3/4] libcamera: Implement YamlEmitter Date: Mon, 21 Oct 2024 11:49:51 +0200 Message-ID: <20241021094955.26991-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241021094955.26991-1-jacopo.mondi@ideasonboard.com> References: <20241021094955.26991-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" Signed-off-by: Jacopo Mondi --- Documentation/Doxyfile-internal.in | 2 + include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_emitter.h | 183 +++++++++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 360 ++++++++++++++++++++++ 5 files changed, 547 insertions(+) create mode 100644 include/libcamera/internal/yaml_emitter.h create mode 100644 src/libcamera/yaml_emitter.cpp diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in index cf9825537866..e0ba64da1bef 100644 --- a/Documentation/Doxyfile-internal.in +++ b/Documentation/Doxyfile-internal.in @@ -21,9 +21,11 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \ @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \ + @TOP_SRCDIR@/include/libcamera/internal/yaml_emitter.h \ @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \ @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \ @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \ + @TOP_SRCDIR@/src/libcamera/yaml_emitter.cpp \ @TOP_SRCDIR@/src/libcamera/pipeline/ \ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ 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..bcbb574061cc --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,183 @@ +/* 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(std::string_view path); + + int emit(); + yaml_event_t *event() { return &event_; } + +private: + LIBCAMERA_DISABLE_COPY(YamlEmitter) + + class Emitter + { + public: + ~Emitter(); + + void init(File *file); + + int emit(yaml_event_t *event); + + private: + void logError(); + + yaml_emitter_t emitter_; + }; + + YamlEmitter() = default; + + void init(); + + File file_; + yaml_event_t event_; + Emitter emitter_; +}; + +class YamlOutput +{ +public: + virtual ~YamlOutput() {}; + + YamlOutput() = default; + + YamlOutput(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + } + + bool initialized() { return !!emitter_; } + + YamlScalar scalar(); + YamlDict dict(); + YamlList list(); + +protected: + 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(); + + YamlEmitter *emitter_ = nullptr; + yaml_event_t event_; +}; + +class YamlRoot : public YamlOutput +{ +public: + YamlRoot() = default; + YamlRoot(YamlRoot &&other) = default; + ~YamlRoot(); + + YamlRoot &operator=(YamlRoot &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + 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; + + void operator=(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlScalar(YamlEmitter *emitter); +}; + +class YamlList : public YamlOutput +{ +public: + YamlList() = default; + YamlList(YamlList &&other) = 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 &&other) = 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..9542ad2d9c18 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,360 @@ +/* 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 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. + * + * 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(data); + + Span 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 YamlEmitter + * + * Yaml Emitter entry point. Allows to create a YamlRoot object that users + * can populate. + */ + +YamlEmitter::~YamlEmitter() +{ + yaml_event_delete(&event_); +} + +/** + * \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 + */ +YamlRoot YamlEmitter::root(std::string_view path) +{ + std::unique_ptr emitter{ new YamlEmitter() }; + + std::string filePath(path); + emitter->file_.setFileName(filePath); + emitter->file_.open(File::OpenModeFlag::WriteOnly); + + emitter->init(); + + return YamlRoot(std::move(emitter)); +} + +/** + * \brief Emit a yaml event + */ +int YamlEmitter::emit() +{ + return emitter_.emit(&event_); +} + +void YamlEmitter::init() +{ + emitter_.init(&file_); + + yaml_stream_start_event_initialize(&event_, YAML_UTF8_ENCODING); + emitter_.emit(&event_); + + yaml_document_start_event_initialize(&event_, NULL, NULL, NULL, 0); + emitter_.emit(&event_); +} + +/** + * \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(yaml_event_t *event) +{ + int ret = yaml_emitter_emit(&emitter_, 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. + */ + +YamlScalar YamlOutput::scalar() +{ + return YamlScalar(emitter_); +} + +YamlDict YamlOutput::dict() +{ + return YamlDict(emitter_); +} + +YamlList YamlOutput::list() +{ + return YamlList(emitter_); +} + +int YamlOutput::emitScalar(std::string_view scalar) +{ + const unsigned char *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(); +} + +int YamlOutput::emitMappingStart() +{ + yaml_mapping_start_event_initialize(emitter_->event(), NULL, NULL, + true, YAML_BLOCK_MAPPING_STYLE); + return emitter_->emit(); +} + +int YamlOutput::emitMappingEnd() +{ + yaml_mapping_end_event_initialize(emitter_->event()); + return emitter_->emit(); +} + +int YamlOutput::emitSequenceStart() +{ + yaml_sequence_start_event_initialize(emitter_->event(), NULL, NULL, true, + YAML_BLOCK_SEQUENCE_STYLE); + return emitter_->emit(); +} + +int YamlOutput::emitSequenceEnd() +{ + yaml_sequence_end_event_initialize(emitter_->event()); + return emitter_->emit(); +} + +/** + * \class YamlRoot + * + * Yaml root node. A root node can be populated with a scalar, a list or a dict. + */ + +YamlRoot::~YamlRoot() +{ + if (!initialized()) + return; + + yaml_document_end_event_initialize(emitter_->event(), 0); + emitterRoot_->emit(); + + yaml_stream_end_event_initialize(emitter_->event()); + emitterRoot_->emit(); +} + +YamlDict YamlRoot::dict() +{ + emitMappingStart(); + + return YamlOutput::dict(); +} + +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=(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 (initialized()) + emitSequenceEnd(); +} + +void YamlList::scalar(std::string_view scalar) +{ + emitScalar(scalar); +} + +YamlList YamlList::list() +{ + emitSequenceStart(); + + return YamlOutput::list(); +} + +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 (initialized()) + emitMappingEnd(); +} + +YamlList YamlDict::list(std::string_view key) +{ + emitScalar(key); + emitSequenceStart(); + + return YamlOutput::list(); +} + +YamlDict YamlDict::dict(std::string_view key) +{ + emitScalar(key); + emitMappingStart(); + + return YamlOutput::dict(); +} + +YamlScalar YamlDict::operator[](std::string_view key) +{ + emitScalar(key); + + return YamlOutput::scalar(); +} + +} /* namespace libcamera */