From patchwork Thu Oct 17 12:52:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21657 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 8B912C32FD for ; Thu, 17 Oct 2024 12:52:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F30416538C; Thu, 17 Oct 2024 14:52:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZKpRyAlc"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6E84B65387 for ; Thu, 17 Oct 2024 14:52:33 +0200 (CEST) Received: from ideasonboard.com (unknown [5.77.64.27]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 072DA827; Thu, 17 Oct 2024 14:50:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729169450; bh=XXHKXPzAfJiudNvP4pazjj/DWrTK6xt/dq4EJHQaHDM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZKpRyAlcb1O+I4wvvPyF0LlZvbz+QWh+ONS//iUhgJWLO86sP1/u8bKiflwhafwV3 mKI9TNy08/NZ9stvmdNOpsD/E73vqdIh6FBQJFzOmjMsBi29BIFpnYgFHyHYQpbjku EDWNJPrLAD+7apulThUJjyViucK7MzLXfcOiPNWY= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC v2 3/4] libcamera: Implement YamlEmitter Date: Thu, 17 Oct 2024 14:52:18 +0200 Message-ID: <20241017125220.60567-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241017125220.60567-1-jacopo.mondi@ideasonboard.com> References: <20241017125220.60567-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 --- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_emitter.h | 162 ++++++++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 362 ++++++++++++++++++++++ 4 files changed, 526 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..e4a0e3b440a5 --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,162 @@ +/* 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 std::unique_ptr 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(); + + std::unique_ptr file_; + yaml_event_t event_; + Emitter emitter_; +}; + +class YamlOutput +{ +public: + virtual ~YamlOutput() {}; + + YamlOutput(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + } + + YamlScalar scalar(); + std::unique_ptr dict(); + std::unique_ptr list(); + +protected: + YamlOutput(YamlEmitter *emitter) + : emitter_(emitter) + { + } + + int emitScalar(std::string_view scalar); + int emitMappingStart(); + int emitMappingEnd(); + int emitSequenceStart(); + int emitSequenceEnd(); + + YamlEmitter *emitter_; + yaml_event_t event_; +}; + +class YamlRoot : public YamlOutput +{ +public: + ~YamlRoot(); + + std::unique_ptr list(); + std::unique_ptr dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlEmitter; + + YamlRoot(YamlEmitter *emitter) + : YamlOutput(emitter) + { + emitterRoot_ = std::unique_ptr(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(YamlList &&other) = default; + ~YamlList(); + + std::unique_ptr list(); + std::unique_ptr dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlList(YamlEmitter *emitter); +}; + +class YamlDict : public YamlOutput +{ +public: + YamlDict(YamlDict &&other) = default; + ~YamlDict(); + + std::unique_ptr list(std::string_view key); + std::unique_ptr 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..f2aaa1c1c1a6 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,362 @@ +/* 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 + */ +std::unique_ptr YamlEmitter::root(std::string_view path) +{ + YamlEmitter *emitter = new YamlEmitter(); + + std::string filePath(path); + emitter->file_ = std::make_unique(filePath); + emitter->file_->open(File::OpenModeFlag::WriteOnly); + + emitter->init(); + + YamlRoot *root = new YamlRoot(emitter); + return std::unique_ptr(root); +} + +/** + * \brief Emit a yaml event + */ +int YamlEmitter::emit() +{ + return emitter_.emit(&event_); +} + +void YamlEmitter::init() +{ + emitter_.init(file_.get()); + + 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_); +} + +std::unique_ptr YamlOutput::dict() +{ + YamlDict dict(emitter_); + + return std::make_unique(std::move(dict)); +} + +std::unique_ptr YamlOutput::list() +{ + YamlList list(emitter_); + + return std::make_unique(std::move(list)); +} + +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() +{ + yaml_document_end_event_initialize(emitter_->event(), 0); + emitterRoot_->emit(); + + yaml_stream_end_event_initialize(emitter_->event()); + emitterRoot_->emit(); +} + +std::unique_ptr YamlRoot::dict() +{ + emitMappingStart(); + + return YamlOutput::dict(); +} + +std::unique_ptr 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 (emitter_) + emitSequenceEnd(); +} + +void YamlList::scalar(std::string_view scalar) +{ + emitScalar(scalar); +} + +std::unique_ptr YamlList::list() +{ + emitSequenceStart(); + + return YamlOutput::list(); +} + +std::unique_ptr 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 (emitter_) + emitMappingEnd(); +} + +std::unique_ptr YamlDict::list(std::string_view key) +{ + emitScalar(key); + emitSequenceStart(); + + return YamlOutput::list(); +} + +std::unique_ptr 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 */