feat(streamer): add ffmpeg encoder and mcap recording
This commit is contained in:
+141
-46
@@ -6,31 +6,86 @@ set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
option(ENABLE_GSTREAMER_LEGACY "Build the optional legacy GStreamer backend" ON)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(cppzmq QUIET)
|
||||
if (
|
||||
NOT cvmmap-core_DIR
|
||||
AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/../cv-mmap/build/core/cvmmap-coreConfig.cmake"
|
||||
AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/../cv-mmap/build/core/cvmmap-coreTargets.cmake")
|
||||
set(cvmmap-core_DIR "${CMAKE_CURRENT_LIST_DIR}/../cv-mmap/build/core")
|
||||
endif()
|
||||
if (cvmmap-core_DIR)
|
||||
find_package(cvmmap-core CONFIG QUIET)
|
||||
endif()
|
||||
find_package(ZeroMQ QUIET)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/CMakeLists.txt")
|
||||
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/lib/CLI11" "${CMAKE_CURRENT_BINARY_DIR}/vendor/cli11")
|
||||
endif()
|
||||
|
||||
find_package(cppzmq QUIET)
|
||||
find_package(cvmmap-core CONFIG REQUIRED)
|
||||
find_package(ZeroMQ QUIET)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavcodec libavutil libswscale)
|
||||
pkg_check_modules(PROTOBUF_PKG QUIET IMPORTED_TARGET protobuf)
|
||||
pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET libzstd)
|
||||
pkg_check_modules(LZ4 REQUIRED IMPORTED_TARGET liblz4)
|
||||
|
||||
pkg_check_modules(GSTREAMER
|
||||
IMPORTED_TARGET
|
||||
gstreamer-1.0>=1.14
|
||||
gstreamer-video-1.0>=1.14
|
||||
gstreamer-app-1.0>=1.14)
|
||||
|
||||
if (NOT GSTREAMER_FOUND)
|
||||
message(FATAL_ERROR
|
||||
"GStreamer development packages are required for cvmmap-streamer. "
|
||||
"Install pkg-config modules: gstreamer-1.0>=1.14, gstreamer-video-1.0>=1.14, "
|
||||
"and gstreamer-app-1.0>=1.14.")
|
||||
if (NOT TARGET cvmmap::client)
|
||||
set(CVMMAP_LOCAL_ROOT "${CMAKE_CURRENT_LIST_DIR}/../cv-mmap")
|
||||
set(CVMMAP_LOCAL_BUILD "${CVMMAP_LOCAL_ROOT}/build/core")
|
||||
if (
|
||||
EXISTS "${CVMMAP_LOCAL_ROOT}/core/include/cvmmap/client.hpp"
|
||||
AND EXISTS "${CVMMAP_LOCAL_BUILD}/libcvmmap_client.a"
|
||||
AND EXISTS "${CVMMAP_LOCAL_BUILD}/libcvmmap_ipc.a"
|
||||
AND EXISTS "${CVMMAP_LOCAL_BUILD}/libcvmmap_target.a")
|
||||
add_library(cvmmap::client INTERFACE IMPORTED)
|
||||
set_target_properties(cvmmap::client PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CVMMAP_LOCAL_ROOT}/core/include"
|
||||
INTERFACE_LINK_LIBRARIES "${CVMMAP_LOCAL_BUILD}/libcvmmap_client.a;${CVMMAP_LOCAL_BUILD}/libcvmmap_ipc.a;${CVMMAP_LOCAL_BUILD}/libcvmmap_target.a")
|
||||
else()
|
||||
message(FATAL_ERROR "cvmmap::client target is unavailable and local cv-mmap build artifacts were not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CVMMAP_STREAMER_HAS_GSTREAMER 0)
|
||||
if (ENABLE_GSTREAMER_LEGACY)
|
||||
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-1.0>=1.14 gstreamer-video-1.0>=1.14 gstreamer-app-1.0>=1.14)
|
||||
if (GSTREAMER_FOUND AND TARGET PkgConfig::GSTREAMER)
|
||||
set(CVMMAP_STREAMER_HAS_GSTREAMER 1)
|
||||
else()
|
||||
message(WARNING "GStreamer legacy backend disabled because required GStreamer packages were not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(cvmmap_streamer_foxglove_proto STATIC)
|
||||
protobuf_generate(
|
||||
TARGET cvmmap_streamer_foxglove_proto
|
||||
LANGUAGE cpp
|
||||
PROTOS "${CMAKE_CURRENT_LIST_DIR}/proto/foxglove/CompressedVideo.proto"
|
||||
IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}/proto")
|
||||
add_library(cvmmap_streamer_protobuf INTERFACE)
|
||||
target_include_directories(cvmmap_streamer_foxglove_proto
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
${Protobuf_INCLUDE_DIRS})
|
||||
target_include_directories(cvmmap_streamer_protobuf
|
||||
INTERFACE
|
||||
${Protobuf_INCLUDE_DIRS})
|
||||
if (TARGET protobuf::libprotobuf)
|
||||
target_link_libraries(cvmmap_streamer_protobuf INTERFACE protobuf::libprotobuf)
|
||||
elseif (TARGET Protobuf::libprotobuf)
|
||||
target_link_libraries(cvmmap_streamer_protobuf INTERFACE Protobuf::libprotobuf)
|
||||
else()
|
||||
target_link_libraries(cvmmap_streamer_protobuf INTERFACE ${Protobuf_LIBRARIES})
|
||||
endif()
|
||||
if (TARGET PkgConfig::PROTOBUF_PKG)
|
||||
target_link_libraries(cvmmap_streamer_protobuf INTERFACE PkgConfig::PROTOBUF_PKG)
|
||||
endif()
|
||||
target_link_libraries(cvmmap_streamer_foxglove_proto PUBLIC cvmmap_streamer_protobuf)
|
||||
|
||||
add_library(cvmmap_streamer_common STATIC
|
||||
src/ipc/help.cpp
|
||||
src/config/runtime_config.cpp
|
||||
@@ -41,63 +96,78 @@ add_library(cvmmap_streamer_common STATIC
|
||||
src/metrics/latency_tracker.cpp
|
||||
src/pipeline/pipeline_runtime.cpp
|
||||
src/protocol/rtmp_publisher.cpp
|
||||
src/protocol/rtp_publisher.cpp)
|
||||
src/protocol/rtp_publisher.cpp
|
||||
src/encode/encoder_backend.cpp
|
||||
src/encode/ffmpeg_encoder_backend.cpp
|
||||
src/encode/gstreamer_legacy_backend.cpp
|
||||
src/record/protobuf_descriptor.cpp
|
||||
src/record/mcap_record_sink.cpp)
|
||||
|
||||
target_include_directories(cvmmap_streamer_common
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
set(CVMAP_STREAMER_LINK_DEPS Threads::Threads)
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/tomlplusplus/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/mcap/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
target_compile_definitions(cvmmap_streamer_common
|
||||
PUBLIC
|
||||
CVMMAP_STREAMER_HAS_GSTREAMER=${CVMMAP_STREAMER_HAS_GSTREAMER})
|
||||
|
||||
set(CVMMAP_STREAMER_LINK_DEPS
|
||||
Threads::Threads
|
||||
cvmmap_streamer_foxglove_proto
|
||||
PkgConfig::FFMPEG
|
||||
PkgConfig::ZSTD
|
||||
PkgConfig::LZ4
|
||||
cvmmap::client)
|
||||
|
||||
if (TARGET cppzmq::cppzmq)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS cppzmq::cppzmq)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS cppzmq::cppzmq)
|
||||
elseif (TARGET cppzmq)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS cppzmq)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS cppzmq)
|
||||
endif()
|
||||
|
||||
if (TARGET ZeroMQ::libzmq)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS ZeroMQ::libzmq)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS ZeroMQ::libzmq)
|
||||
elseif (TARGET ZeroMQ::ZeroMQ)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS ZeroMQ::ZeroMQ)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS ZeroMQ::ZeroMQ)
|
||||
endif()
|
||||
|
||||
if (TARGET ZeroMQ::cppzmq)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS ZeroMQ::cppzmq)
|
||||
elseif (TARGET cppzmq::cppzmq)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS cppzmq::cppzmq)
|
||||
endif()
|
||||
|
||||
|
||||
if (NOT TARGET PkgConfig::GSTREAMER)
|
||||
message(FATAL_ERROR
|
||||
"GStreamer packages were detected but PkgConfig::GSTREAMER target is unavailable. "
|
||||
"Please ensure GStreamer development toolchain is correctly installed.")
|
||||
endif()
|
||||
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS PkgConfig::GSTREAMER)
|
||||
|
||||
|
||||
if (TARGET spdlog::spdlog)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog::spdlog)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS spdlog::spdlog)
|
||||
elseif (TARGET spdlog)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS spdlog)
|
||||
endif()
|
||||
|
||||
if (TARGET CLI11::CLI11)
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS CLI11::CLI11)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS CLI11::CLI11)
|
||||
endif()
|
||||
|
||||
list(APPEND CVMAP_STREAMER_LINK_DEPS cvmmap::client)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS cvmmap_streamer_protobuf)
|
||||
if (TARGET PkgConfig::PROTOBUF_PKG)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS PkgConfig::PROTOBUF_PKG)
|
||||
endif()
|
||||
|
||||
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMAP_STREAMER_LINK_DEPS})
|
||||
if (CVMMAP_STREAMER_HAS_GSTREAMER)
|
||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS PkgConfig::GSTREAMER)
|
||||
endif()
|
||||
|
||||
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMMAP_STREAMER_LINK_DEPS})
|
||||
|
||||
function(add_cvmmap_binary target source)
|
||||
add_executable(${target} ${source} ${ARGN})
|
||||
target_include_directories(${target}
|
||||
PRIVATE
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/tomlplusplus/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/mcap/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||
target_link_libraries(${target}
|
||||
PRIVATE
|
||||
cvmmap_streamer_common)
|
||||
cvmmap_streamer_common)
|
||||
set_target_properties(${target} PROPERTIES OUTPUT_NAME "${target}")
|
||||
endfunction()
|
||||
|
||||
@@ -105,3 +175,28 @@ add_cvmmap_binary(cvmmap_streamer src/main_streamer.cpp)
|
||||
add_cvmmap_binary(rtp_receiver_tester src/testers/rtp_receiver_tester.cpp)
|
||||
add_cvmmap_binary(rtmp_stub_tester src/testers/rtmp_stub_tester.cpp)
|
||||
add_cvmmap_binary(ipc_snapshot_tester src/testers/ipc_snapshot_tester.cpp)
|
||||
|
||||
add_executable(mcap_reader_tester src/testers/mcap_reader_tester.cpp)
|
||||
target_include_directories(mcap_reader_tester
|
||||
PRIVATE
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/lib/mcap/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||
target_link_libraries(mcap_reader_tester
|
||||
PRIVATE
|
||||
cvmmap_streamer_foxglove_proto
|
||||
PkgConfig::ZSTD
|
||||
PkgConfig::LZ4)
|
||||
if (TARGET spdlog::spdlog)
|
||||
target_link_libraries(mcap_reader_tester PRIVATE spdlog::spdlog)
|
||||
elseif (TARGET spdlog)
|
||||
target_link_libraries(mcap_reader_tester PRIVATE spdlog)
|
||||
endif()
|
||||
if (TARGET CLI11::CLI11)
|
||||
target_link_libraries(mcap_reader_tester PRIVATE CLI11::CLI11)
|
||||
endif()
|
||||
target_link_libraries(mcap_reader_tester PRIVATE cvmmap_streamer_protobuf)
|
||||
if (TARGET PkgConfig::PROTOBUF_PKG)
|
||||
target_link_libraries(mcap_reader_tester PRIVATE PkgConfig::PROTOBUF_PKG)
|
||||
endif()
|
||||
|
||||
@@ -25,9 +25,34 @@ enum class RtmpMode {
|
||||
Domestic,
|
||||
};
|
||||
|
||||
enum class EncoderBackendType {
|
||||
Auto,
|
||||
FFmpeg,
|
||||
GStreamerLegacy,
|
||||
};
|
||||
|
||||
enum class EncoderDeviceType {
|
||||
Auto,
|
||||
Nvidia,
|
||||
Software,
|
||||
};
|
||||
|
||||
enum class McapCompression {
|
||||
None,
|
||||
Lz4,
|
||||
Zstd,
|
||||
};
|
||||
|
||||
struct InputConfig {
|
||||
std::string shm_name{"cvmmap_default"};
|
||||
std::string zmq_endpoint{"ipc:///tmp/cvmmap_default"};
|
||||
std::string uri{"cvmmap://default"};
|
||||
};
|
||||
|
||||
struct EncoderConfig {
|
||||
EncoderBackendType backend{EncoderBackendType::Auto};
|
||||
EncoderDeviceType device{EncoderDeviceType::Auto};
|
||||
CodecType codec{CodecType::H264};
|
||||
std::uint32_t gop{30};
|
||||
std::uint32_t b_frames{0};
|
||||
};
|
||||
|
||||
struct RtmpOutputConfig {
|
||||
@@ -50,10 +75,20 @@ struct OutputsConfig {
|
||||
RtpOutputConfig rtp{};
|
||||
};
|
||||
|
||||
struct McapRecordConfig {
|
||||
bool enabled{false};
|
||||
std::string path{"capture.mcap"};
|
||||
std::string topic{"/camera/video"};
|
||||
std::string frame_id{"camera"};
|
||||
McapCompression compression{McapCompression::Zstd};
|
||||
};
|
||||
|
||||
struct RecordConfig {
|
||||
McapRecordConfig mcap{};
|
||||
};
|
||||
|
||||
struct LatencyConfig {
|
||||
std::size_t queue_size{1};
|
||||
std::uint32_t gop{30};
|
||||
std::uint32_t b_frames{0};
|
||||
bool realtime_sync{true};
|
||||
bool force_idr_on_reset{true};
|
||||
std::uint32_t ingest_max_frames{0};
|
||||
@@ -66,8 +101,9 @@ struct LatencyConfig {
|
||||
struct RuntimeConfig {
|
||||
InputConfig input{};
|
||||
RunMode run_mode{RunMode::Pipeline};
|
||||
CodecType codec{CodecType::H264};
|
||||
EncoderConfig encoder{};
|
||||
OutputsConfig outputs{};
|
||||
RecordConfig record{};
|
||||
LatencyConfig latency{};
|
||||
|
||||
static RuntimeConfig defaults();
|
||||
@@ -76,6 +112,9 @@ struct RuntimeConfig {
|
||||
std::string_view to_string(CodecType codec);
|
||||
std::string_view to_string(RunMode mode);
|
||||
std::string_view to_string(RtmpMode mode);
|
||||
std::string_view to_string(EncoderBackendType backend);
|
||||
std::string_view to_string(EncoderDeviceType device);
|
||||
std::string_view to_string(McapCompression compression);
|
||||
|
||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv);
|
||||
std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &config);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "cvmmap_streamer/config/runtime_config.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace cvmmap_streamer::encode {
|
||||
|
||||
struct EncodedAccessUnit {
|
||||
CodecType codec{CodecType::H264};
|
||||
std::uint64_t source_timestamp_ns{0};
|
||||
std::uint64_t stream_pts_ns{0};
|
||||
bool keyframe{false};
|
||||
std::vector<std::uint8_t> annexb_bytes{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "cvmmap_streamer/config/runtime_config.hpp"
|
||||
#include "cvmmap_streamer/encode/encoded_access_unit.hpp"
|
||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace cvmmap_streamer::encode {
|
||||
|
||||
struct RawVideoFrame {
|
||||
ipc::FrameInfo info{};
|
||||
std::uint64_t source_timestamp_ns{0};
|
||||
std::span<const std::uint8_t> bytes{};
|
||||
};
|
||||
|
||||
class EncoderBackend {
|
||||
public:
|
||||
virtual ~EncoderBackend() = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::string_view backend_name() const = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool using_hardware() const = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::expected<void, std::string> init(const RuntimeConfig &config, const ipc::FrameInfo &frame_info) = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::expected<void, std::string> poll() = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::expected<void, std::string> push_frame(const RawVideoFrame &frame) = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::expected<std::vector<EncodedAccessUnit>, std::string> drain() = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual std::expected<std::vector<EncodedAccessUnit>, std::string> flush() = 0;
|
||||
|
||||
virtual void shutdown() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::unique_ptr<EncoderBackend>, std::string> make_encoder_backend(const RuntimeConfig &config);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "cvmmap_streamer/config/runtime_config.hpp"
|
||||
#include "cvmmap_streamer/encode/encoded_access_unit.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace cvmmap_streamer::record {
|
||||
|
||||
class McapRecordSink {
|
||||
public:
|
||||
McapRecordSink() = default;
|
||||
~McapRecordSink();
|
||||
|
||||
McapRecordSink(const McapRecordSink &) = delete;
|
||||
McapRecordSink &operator=(const McapRecordSink &) = delete;
|
||||
|
||||
McapRecordSink(McapRecordSink &&other) noexcept;
|
||||
McapRecordSink &operator=(McapRecordSink &&other) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static std::expected<McapRecordSink, std::string> create(const RuntimeConfig &config);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_access_unit(const encode::EncodedAccessUnit &access_unit);
|
||||
|
||||
[[nodiscard]]
|
||||
bool is_open() const;
|
||||
|
||||
[[nodiscard]]
|
||||
std::string_view path() const;
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
struct State;
|
||||
State *state_{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mcap::internal {
|
||||
|
||||
/**
|
||||
* Compute CRC32 lookup tables as described at:
|
||||
* https://github.com/komrad36/CRC#option-6-1-byte-tabular
|
||||
*
|
||||
* An iteration of CRC computation can be performed on 8 bits of input at once. By pre-computing a
|
||||
* table of the values of CRC(?) for all 2^8 = 256 possible byte values, during the final
|
||||
* computation we can replace a loop over 8 bits with a single lookup in the table.
|
||||
*
|
||||
* For further speedup, we can also pre-compute the values of CRC(?0) for all possible bytes when a
|
||||
* zero byte is appended. Then we can process two bytes of input at once by computing CRC(AB) =
|
||||
* CRC(A0) ^ CRC(B), using one lookup in the CRC(?0) table and one lookup in the CRC(?) table.
|
||||
*
|
||||
* The same technique applies for any number of bytes to be processed at once, although the speed
|
||||
* improvements diminish.
|
||||
*
|
||||
* @param Polynomial The binary representation of the polynomial to use (reversed, i.e. most
|
||||
* significant bit represents x^0).
|
||||
* @param NumTables The number of bytes of input that will be processed at once.
|
||||
*/
|
||||
template <size_t Polynomial, size_t NumTables>
|
||||
struct CRC32Table {
|
||||
private:
|
||||
std::array<uint32_t, 256 * NumTables> table = {};
|
||||
|
||||
public:
|
||||
constexpr CRC32Table() {
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
uint32_t r = i;
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
r = ((r & 1) * Polynomial) ^ (r >> 1);
|
||||
table[i] = r;
|
||||
}
|
||||
for (size_t i = 256; i < table.size(); i++) {
|
||||
uint32_t value = table[i - 256];
|
||||
table[i] = table[value & 0xff] ^ (value >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t operator[](size_t index) const {
|
||||
return table[index];
|
||||
}
|
||||
};
|
||||
|
||||
inline uint32_t getUint32LE(const std::byte* data) {
|
||||
return (uint32_t(data[0]) << 0) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) |
|
||||
(uint32_t(data[3]) << 24);
|
||||
}
|
||||
|
||||
static constexpr CRC32Table<0xedb88320, 8> CRC32_TABLE;
|
||||
|
||||
/**
|
||||
* Initialize a CRC32 to all 1 bits.
|
||||
*/
|
||||
static constexpr uint32_t CRC32_INIT = 0xffffffff;
|
||||
|
||||
/**
|
||||
* Update a streaming CRC32 calculation.
|
||||
*
|
||||
* For performance, this implementation processes the data 8 bytes at a time, using the algorithm
|
||||
* presented at: https://github.com/komrad36/CRC#option-9-8-byte-tabular
|
||||
*/
|
||||
inline uint32_t crc32Update(const uint32_t prev, const std::byte* const data, const size_t length) {
|
||||
// Process bytes one by one until we reach the proper alignment.
|
||||
uint32_t r = prev;
|
||||
size_t offset = 0;
|
||||
for (; (uintptr_t(data + offset) & alignof(uint32_t)) != 0 && offset < length; offset++) {
|
||||
r = CRC32_TABLE[(r ^ uint8_t(data[offset])) & 0xff] ^ (r >> 8);
|
||||
}
|
||||
if (offset == length) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Process 8 bytes (2 uint32s) at a time.
|
||||
size_t remainingBytes = length - offset;
|
||||
for (; remainingBytes >= 8; offset += 8, remainingBytes -= 8) {
|
||||
r ^= getUint32LE(data + offset);
|
||||
uint32_t r2 = getUint32LE(data + offset + 4);
|
||||
r = CRC32_TABLE[0 * 256 + ((r2 >> 24) & 0xff)] ^ CRC32_TABLE[1 * 256 + ((r2 >> 16) & 0xff)] ^
|
||||
CRC32_TABLE[2 * 256 + ((r2 >> 8) & 0xff)] ^ CRC32_TABLE[3 * 256 + ((r2 >> 0) & 0xff)] ^
|
||||
CRC32_TABLE[4 * 256 + ((r >> 24) & 0xff)] ^ CRC32_TABLE[5 * 256 + ((r >> 16) & 0xff)] ^
|
||||
CRC32_TABLE[6 * 256 + ((r >> 8) & 0xff)] ^ CRC32_TABLE[7 * 256 + ((r >> 0) & 0xff)];
|
||||
}
|
||||
|
||||
// Process any remaining bytes one by one.
|
||||
for (; offset < length; offset++) {
|
||||
r = CRC32_TABLE[(r ^ uint8_t(data[offset])) & 0xff] ^ (r >> 8);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Finalize a CRC32 by inverting the output value. */
|
||||
inline uint32_t crc32Final(uint32_t crc) {
|
||||
return crc ^ 0xffffffff;
|
||||
}
|
||||
|
||||
} // namespace mcap::internal
|
||||
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mcap {
|
||||
|
||||
/**
|
||||
* @brief Status codes for MCAP readers and writers.
|
||||
*/
|
||||
enum class StatusCode {
|
||||
Success = 0,
|
||||
NotOpen,
|
||||
InvalidSchemaId,
|
||||
InvalidChannelId,
|
||||
FileTooSmall,
|
||||
ReadFailed,
|
||||
MagicMismatch,
|
||||
InvalidFile,
|
||||
InvalidRecord,
|
||||
InvalidOpCode,
|
||||
InvalidChunkOffset,
|
||||
InvalidFooter,
|
||||
DecompressionFailed,
|
||||
DecompressionSizeMismatch,
|
||||
UnrecognizedCompression,
|
||||
OpenFailed,
|
||||
MissingStatistics,
|
||||
InvalidMessageReadOptions,
|
||||
NoMessageIndexesAvailable,
|
||||
UnsupportedCompression,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wraps a status code and string message carrying additional context.
|
||||
*/
|
||||
struct [[nodiscard]] Status {
|
||||
StatusCode code;
|
||||
std::string message;
|
||||
|
||||
Status()
|
||||
: code(StatusCode::Success) {}
|
||||
|
||||
Status(StatusCode _code)
|
||||
: code(_code) {
|
||||
switch (code) {
|
||||
case StatusCode::Success:
|
||||
break;
|
||||
case StatusCode::NotOpen:
|
||||
message = "not open";
|
||||
break;
|
||||
case StatusCode::InvalidSchemaId:
|
||||
message = "invalid schema id";
|
||||
break;
|
||||
case StatusCode::InvalidChannelId:
|
||||
message = "invalid channel id";
|
||||
break;
|
||||
case StatusCode::FileTooSmall:
|
||||
message = "file too small";
|
||||
break;
|
||||
case StatusCode::ReadFailed:
|
||||
message = "read failed";
|
||||
break;
|
||||
case StatusCode::MagicMismatch:
|
||||
message = "magic mismatch";
|
||||
break;
|
||||
case StatusCode::InvalidFile:
|
||||
message = "invalid file";
|
||||
break;
|
||||
case StatusCode::InvalidRecord:
|
||||
message = "invalid record";
|
||||
break;
|
||||
case StatusCode::InvalidOpCode:
|
||||
message = "invalid opcode";
|
||||
break;
|
||||
case StatusCode::InvalidChunkOffset:
|
||||
message = "invalid chunk offset";
|
||||
break;
|
||||
case StatusCode::InvalidFooter:
|
||||
message = "invalid footer";
|
||||
break;
|
||||
case StatusCode::DecompressionFailed:
|
||||
message = "decompression failed";
|
||||
break;
|
||||
case StatusCode::DecompressionSizeMismatch:
|
||||
message = "decompression size mismatch";
|
||||
break;
|
||||
case StatusCode::UnrecognizedCompression:
|
||||
message = "unrecognized compression";
|
||||
break;
|
||||
case StatusCode::OpenFailed:
|
||||
message = "open failed";
|
||||
break;
|
||||
case StatusCode::MissingStatistics:
|
||||
message = "missing statistics";
|
||||
break;
|
||||
case StatusCode::InvalidMessageReadOptions:
|
||||
message = "message read options conflict";
|
||||
break;
|
||||
case StatusCode::NoMessageIndexesAvailable:
|
||||
message = "file has no message indices";
|
||||
break;
|
||||
case StatusCode::UnsupportedCompression:
|
||||
message = "unsupported compression";
|
||||
break;
|
||||
default:
|
||||
message = "unknown";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Status(StatusCode _code, const std::string& _message)
|
||||
: code(_code)
|
||||
, message(_message) {}
|
||||
|
||||
bool ok() const {
|
||||
return code == StatusCode::Success;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcap
|
||||
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <cstring>
|
||||
|
||||
// Do not compile on systems with non-8-bit bytes
|
||||
static_assert(std::numeric_limits<unsigned char>::digits == 8);
|
||||
|
||||
namespace mcap {
|
||||
|
||||
namespace internal {
|
||||
|
||||
constexpr uint64_t MinHeaderLength = /* magic bytes */ sizeof(Magic) +
|
||||
/* opcode */ 1 +
|
||||
/* record length */ 8 +
|
||||
/* profile length */ 4 +
|
||||
/* library length */ 4;
|
||||
constexpr uint64_t FooterLength = /* opcode */ 1 +
|
||||
/* record length */ 8 +
|
||||
/* summary start */ 8 +
|
||||
/* summary offset start */ 8 +
|
||||
/* summary crc */ 4 +
|
||||
/* magic bytes */ sizeof(Magic);
|
||||
|
||||
inline std::string ToHex(uint8_t byte) {
|
||||
std::string result{2, '\0'};
|
||||
result[0] = "0123456789ABCDEF"[(uint8_t(byte) >> 4) & 0x0F];
|
||||
result[1] = "0123456789ABCDEF"[uint8_t(byte) & 0x0F];
|
||||
return result;
|
||||
}
|
||||
inline std::string ToHex(std::byte byte) {
|
||||
return ToHex(uint8_t(byte));
|
||||
}
|
||||
|
||||
inline std::string to_string(const std::string& arg) {
|
||||
return arg;
|
||||
}
|
||||
inline std::string to_string(std::string_view arg) {
|
||||
return std::string(arg);
|
||||
}
|
||||
inline std::string to_string(const char* arg) {
|
||||
return std::string(arg);
|
||||
}
|
||||
template <typename... T>
|
||||
[[nodiscard]] inline std::string StrCat(T&&... args) {
|
||||
using mcap::internal::to_string;
|
||||
using std::to_string;
|
||||
return ("" + ... + to_string(std::forward<T>(args)));
|
||||
}
|
||||
|
||||
inline uint32_t KeyValueMapSize(const KeyValueMap& map) {
|
||||
size_t size = 0;
|
||||
for (const auto& [key, value] : map) {
|
||||
size += 4 + key.size() + 4 + value.size();
|
||||
}
|
||||
return (uint32_t)(size);
|
||||
}
|
||||
|
||||
inline const std::string CompressionString(Compression compression) {
|
||||
switch (compression) {
|
||||
case Compression::None:
|
||||
default:
|
||||
return std::string{};
|
||||
case Compression::Lz4:
|
||||
return "lz4";
|
||||
case Compression::Zstd:
|
||||
return "zstd";
|
||||
}
|
||||
}
|
||||
|
||||
inline uint16_t ParseUint16(const std::byte* data) {
|
||||
return uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
}
|
||||
|
||||
inline uint32_t ParseUint32(const std::byte* data) {
|
||||
return uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) |
|
||||
(uint32_t(data[3]) << 24);
|
||||
}
|
||||
|
||||
inline Status ParseUint32(const std::byte* data, uint64_t maxSize, uint32_t* output) {
|
||||
if (maxSize < 4) {
|
||||
const auto msg = StrCat("cannot read uint32 from ", maxSize, " bytes");
|
||||
return Status{StatusCode::InvalidRecord, msg};
|
||||
}
|
||||
*output = ParseUint32(data);
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline uint64_t ParseUint64(const std::byte* data) {
|
||||
return uint64_t(data[0]) | (uint64_t(data[1]) << 8) | (uint64_t(data[2]) << 16) |
|
||||
(uint64_t(data[3]) << 24) | (uint64_t(data[4]) << 32) | (uint64_t(data[5]) << 40) |
|
||||
(uint64_t(data[6]) << 48) | (uint64_t(data[7]) << 56);
|
||||
}
|
||||
|
||||
inline Status ParseUint64(const std::byte* data, uint64_t maxSize, uint64_t* output) {
|
||||
if (maxSize < 8) {
|
||||
const auto msg = StrCat("cannot read uint64 from ", maxSize, " bytes");
|
||||
return Status{StatusCode::InvalidRecord, msg};
|
||||
}
|
||||
*output = ParseUint64(data);
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline Status ParseStringView(const std::byte* data, uint64_t maxSize, std::string_view* output) {
|
||||
uint32_t size = 0;
|
||||
if (auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
|
||||
const auto msg = StrCat("cannot read string size: ", status.message);
|
||||
return Status{StatusCode::InvalidRecord, msg};
|
||||
}
|
||||
if (uint64_t(size) > (maxSize - 4)) {
|
||||
const auto msg = StrCat("string size ", size, " exceeds remaining bytes ", (maxSize - 4));
|
||||
return Status(StatusCode::InvalidRecord, msg);
|
||||
}
|
||||
*output = std::string_view(reinterpret_cast<const char*>(data + 4), size);
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline Status ParseString(const std::byte* data, uint64_t maxSize, std::string* output) {
|
||||
uint32_t size = 0;
|
||||
if (auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (uint64_t(size) > (maxSize - 4)) {
|
||||
const auto msg = StrCat("string size ", size, " exceeds remaining bytes ", (maxSize - 4));
|
||||
return Status(StatusCode::InvalidRecord, msg);
|
||||
}
|
||||
*output = std::string(reinterpret_cast<const char*>(data + 4), size);
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline Status ParseByteArray(const std::byte* data, uint64_t maxSize, ByteArray* output) {
|
||||
uint32_t size = 0;
|
||||
if (auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (uint64_t(size) > (maxSize - 4)) {
|
||||
const auto msg = StrCat("byte array size ", size, " exceeds remaining bytes ", (maxSize - 4));
|
||||
return Status(StatusCode::InvalidRecord, msg);
|
||||
}
|
||||
output->resize(size);
|
||||
// output->data() may return nullptr if 'output' is empty, but memcpy() does not accept nullptr.
|
||||
// 'output' will be empty only if the 'size' is equal to 0.
|
||||
if (size > 0) {
|
||||
std::memcpy(output->data(), data + 4, size);
|
||||
}
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline Status ParseKeyValueMap(const std::byte* data, uint64_t maxSize, KeyValueMap* output) {
|
||||
uint32_t sizeInBytes = 0;
|
||||
if (auto status = ParseUint32(data, maxSize, &sizeInBytes); !status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (sizeInBytes > (maxSize - 4)) {
|
||||
const auto msg =
|
||||
StrCat("key-value map size ", sizeInBytes, " exceeds remaining bytes ", (maxSize - 4));
|
||||
return Status(StatusCode::InvalidRecord, msg);
|
||||
}
|
||||
|
||||
// Account for the byte size prefix in sizeInBytes to make the bounds checking
|
||||
// below simpler
|
||||
sizeInBytes += 4;
|
||||
|
||||
output->clear();
|
||||
uint64_t pos = 4;
|
||||
while (pos < sizeInBytes) {
|
||||
std::string_view key;
|
||||
if (auto status = ParseStringView(data + pos, sizeInBytes - pos, &key); !status.ok()) {
|
||||
const auto msg = StrCat("cannot read key-value map key at pos ", pos, ": ", status.message);
|
||||
return Status{StatusCode::InvalidRecord, msg};
|
||||
}
|
||||
pos += 4 + key.size();
|
||||
std::string_view value;
|
||||
if (auto status = ParseStringView(data + pos, sizeInBytes - pos, &value); !status.ok()) {
|
||||
const auto msg = StrCat("cannot read key-value map value for key \"", key, "\" at pos ", pos,
|
||||
": ", status.message);
|
||||
return Status{StatusCode::InvalidRecord, msg};
|
||||
}
|
||||
pos += 4 + value.size();
|
||||
output->emplace(key, value);
|
||||
}
|
||||
return StatusCode::Success;
|
||||
}
|
||||
|
||||
inline std::string MagicToHex(const std::byte* data) {
|
||||
return internal::ToHex(data[0]) + internal::ToHex(data[1]) + internal::ToHex(data[2]) +
|
||||
internal::ToHex(data[3]) + internal::ToHex(data[4]) + internal::ToHex(data[5]) +
|
||||
internal::ToHex(data[6]) + internal::ToHex(data[7]);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace mcap
|
||||
@@ -0,0 +1,303 @@
|
||||
// Adapted from <https://github.com/ekg/intervaltree/blob/master/IntervalTree.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace mcap::internal {
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
class Interval {
|
||||
public:
|
||||
Scalar start;
|
||||
Scalar stop;
|
||||
Value value;
|
||||
Interval(const Scalar& s, const Scalar& e, const Value& v)
|
||||
: start(std::min(s, e))
|
||||
, stop(std::max(s, e))
|
||||
, value(v) {}
|
||||
};
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
Value intervalStart(const Interval<Scalar, Value>& i) {
|
||||
return i.start;
|
||||
}
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
Value intervalStop(const Interval<Scalar, Value>& i) {
|
||||
return i.stop;
|
||||
}
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
std::ostream& operator<<(std::ostream& out, const Interval<Scalar, Value>& i) {
|
||||
out << "Interval(" << i.start << ", " << i.stop << "): " << i.value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class Scalar, class Value>
|
||||
class IntervalTree {
|
||||
public:
|
||||
using interval = Interval<Scalar, Value>;
|
||||
using interval_vector = std::vector<interval>;
|
||||
|
||||
struct IntervalStartCmp {
|
||||
bool operator()(const interval& a, const interval& b) {
|
||||
return a.start < b.start;
|
||||
}
|
||||
};
|
||||
|
||||
struct IntervalStopCmp {
|
||||
bool operator()(const interval& a, const interval& b) {
|
||||
return a.stop < b.stop;
|
||||
}
|
||||
};
|
||||
|
||||
IntervalTree()
|
||||
: left(nullptr)
|
||||
, right(nullptr)
|
||||
, center(Scalar(0)) {}
|
||||
|
||||
~IntervalTree() = default;
|
||||
|
||||
std::unique_ptr<IntervalTree> clone() const {
|
||||
return std::unique_ptr<IntervalTree>(new IntervalTree(*this));
|
||||
}
|
||||
|
||||
IntervalTree(const IntervalTree& other)
|
||||
: intervals(other.intervals)
|
||||
, left(other.left ? other.left->clone() : nullptr)
|
||||
, right(other.right ? other.right->clone() : nullptr)
|
||||
, center(other.center) {}
|
||||
|
||||
IntervalTree& operator=(IntervalTree&&) = default;
|
||||
IntervalTree(IntervalTree&&) = default;
|
||||
|
||||
IntervalTree& operator=(const IntervalTree& other) {
|
||||
center = other.center;
|
||||
intervals = other.intervals;
|
||||
left = other.left ? other.left->clone() : nullptr;
|
||||
right = other.right ? other.right->clone() : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IntervalTree(interval_vector&& ivals, std::size_t depth = 16, std::size_t minbucket = 64,
|
||||
std::size_t maxbucket = 512, Scalar leftextent = 0, Scalar rightextent = 0)
|
||||
: left(nullptr)
|
||||
, right(nullptr) {
|
||||
--depth;
|
||||
const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(), IntervalStopCmp());
|
||||
const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||
if (!ivals.empty()) {
|
||||
center = (minmaxStart.first->start + minmaxStop.second->stop) / 2;
|
||||
}
|
||||
if (leftextent == 0 && rightextent == 0) {
|
||||
// sort intervals by start
|
||||
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||
} else {
|
||||
assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp()));
|
||||
}
|
||||
if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket)) {
|
||||
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||
intervals = std::move(ivals);
|
||||
assert(is_valid().first);
|
||||
return;
|
||||
} else {
|
||||
Scalar leftp = 0;
|
||||
Scalar rightp = 0;
|
||||
|
||||
if (leftextent || rightextent) {
|
||||
leftp = leftextent;
|
||||
rightp = rightextent;
|
||||
} else {
|
||||
leftp = ivals.front().start;
|
||||
rightp = std::max_element(ivals.begin(), ivals.end(), IntervalStopCmp())->stop;
|
||||
}
|
||||
|
||||
interval_vector lefts;
|
||||
interval_vector rights;
|
||||
|
||||
for (typename interval_vector::const_iterator i = ivals.begin(); i != ivals.end(); ++i) {
|
||||
const interval& cur = *i;
|
||||
if (cur.stop < center) {
|
||||
lefts.push_back(cur);
|
||||
} else if (cur.start > center) {
|
||||
rights.push_back(cur);
|
||||
} else {
|
||||
assert(cur.start <= center);
|
||||
assert(center <= cur.stop);
|
||||
intervals.push_back(cur);
|
||||
}
|
||||
}
|
||||
|
||||
if (!lefts.empty()) {
|
||||
left.reset(new IntervalTree(std::move(lefts), depth, minbucket, maxbucket, leftp, center));
|
||||
}
|
||||
if (!rights.empty()) {
|
||||
right.reset(
|
||||
new IntervalTree(std::move(rights), depth, minbucket, maxbucket, center, rightp));
|
||||
}
|
||||
}
|
||||
assert(is_valid().first);
|
||||
}
|
||||
|
||||
// Call f on all intervals near the range [start, stop]:
|
||||
template <class UnaryFunction>
|
||||
void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
if (!intervals.empty() && !(stop < intervals.front().start)) {
|
||||
for (auto& i : intervals) {
|
||||
f(i);
|
||||
}
|
||||
}
|
||||
if (left && start <= center) {
|
||||
left->visit_near(start, stop, f);
|
||||
}
|
||||
if (right && stop >= center) {
|
||||
right->visit_near(start, stop, f);
|
||||
}
|
||||
}
|
||||
|
||||
// Call f on all intervals crossing pos
|
||||
template <class UnaryFunction>
|
||||
void visit_overlapping(const Scalar& pos, UnaryFunction f) const {
|
||||
visit_overlapping(pos, pos, f);
|
||||
}
|
||||
|
||||
// Call f on all intervals overlapping [start, stop]
|
||||
template <class UnaryFunction>
|
||||
void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
auto filterF = [&](const interval& cur) {
|
||||
if (cur.stop >= start && cur.start <= stop) {
|
||||
// Only apply f if overlapping
|
||||
f(cur);
|
||||
}
|
||||
};
|
||||
visit_near(start, stop, filterF);
|
||||
}
|
||||
|
||||
// Call f on all intervals contained within [start, stop]
|
||||
template <class UnaryFunction>
|
||||
void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
auto filterF = [&](const interval& cur) {
|
||||
if (start <= cur.start && cur.stop <= stop) {
|
||||
f(cur);
|
||||
}
|
||||
};
|
||||
visit_near(start, stop, filterF);
|
||||
}
|
||||
|
||||
interval_vector find_overlapping(const Scalar& start, const Scalar& stop) const {
|
||||
interval_vector result;
|
||||
visit_overlapping(start, stop, [&](const interval& cur) {
|
||||
result.emplace_back(cur);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
interval_vector find_contained(const Scalar& start, const Scalar& stop) const {
|
||||
interval_vector result;
|
||||
visit_contained(start, stop, [&](const interval& cur) {
|
||||
result.push_back(cur);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
if (left && !left->empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!intervals.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (right && !right->empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class UnaryFunction>
|
||||
void visit_all(UnaryFunction f) const {
|
||||
if (left) {
|
||||
left->visit_all(f);
|
||||
}
|
||||
std::for_each(intervals.begin(), intervals.end(), f);
|
||||
if (right) {
|
||||
right->visit_all(f);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<Scalar, Scalar> extent() const {
|
||||
struct Extent {
|
||||
std::pair<Scalar, Scalar> x{std::numeric_limits<Scalar>::max(),
|
||||
std::numeric_limits<Scalar>::min()};
|
||||
void operator()(const interval& cur) {
|
||||
x.first = std::min(x.first, cur.start);
|
||||
x.second = std::max(x.second, cur.stop);
|
||||
}
|
||||
};
|
||||
Extent extent;
|
||||
|
||||
visit_all([&](const interval& cur) {
|
||||
extent(cur);
|
||||
});
|
||||
return extent.x;
|
||||
}
|
||||
|
||||
// Check all constraints.
|
||||
// If first is false, second is invalid.
|
||||
std::pair<bool, std::pair<Scalar, Scalar>> is_valid() const {
|
||||
const auto minmaxStop =
|
||||
std::minmax_element(intervals.begin(), intervals.end(), IntervalStopCmp());
|
||||
const auto minmaxStart =
|
||||
std::minmax_element(intervals.begin(), intervals.end(), IntervalStartCmp());
|
||||
|
||||
std::pair<bool, std::pair<Scalar, Scalar>> result = {
|
||||
true, {std::numeric_limits<Scalar>::max(), std::numeric_limits<Scalar>::min()}};
|
||||
if (!intervals.empty()) {
|
||||
result.second.first = std::min(result.second.first, minmaxStart.first->start);
|
||||
result.second.second = std::min(result.second.second, minmaxStop.second->stop);
|
||||
}
|
||||
if (left) {
|
||||
auto valid = left->is_valid();
|
||||
result.first &= valid.first;
|
||||
result.second.first = std::min(result.second.first, valid.second.first);
|
||||
result.second.second = std::min(result.second.second, valid.second.second);
|
||||
if (!result.first) {
|
||||
return result;
|
||||
}
|
||||
if (valid.second.second >= center) {
|
||||
result.first = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (right) {
|
||||
auto valid = right->is_valid();
|
||||
result.first &= valid.first;
|
||||
result.second.first = std::min(result.second.first, valid.second.first);
|
||||
result.second.second = std::min(result.second.second, valid.second.second);
|
||||
if (!result.first) {
|
||||
return result;
|
||||
}
|
||||
if (valid.second.first <= center) {
|
||||
result.first = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp())) {
|
||||
result.first = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
interval_vector intervals;
|
||||
std::unique_ptr<IntervalTree> left;
|
||||
std::unique_ptr<IntervalTree> right;
|
||||
Scalar center;
|
||||
};
|
||||
|
||||
} // namespace mcap::internal
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "reader.hpp"
|
||||
#include "writer.hpp"
|
||||
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
||||
namespace mcap::internal {
|
||||
|
||||
// Helper for writing compile-time exhaustive variant visitors.
|
||||
template <class>
|
||||
inline constexpr bool always_false_v = false;
|
||||
|
||||
/**
|
||||
* @brief A job to read a specific message at offset `offset` from the decompressed chunk
|
||||
* stored in `chunkReaderIndex`. A timestamp is provided to order this job relative to other jobs.
|
||||
*/
|
||||
struct ReadMessageJob {
|
||||
Timestamp timestamp;
|
||||
RecordOffset offset;
|
||||
size_t chunkReaderIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A job to decompress the chunk starting at `chunkStartOffset`. The message indices
|
||||
* starting directly after the chunk record and ending at `messageIndexEndOffset` will be used to
|
||||
* find specific messages within the chunk.
|
||||
*/
|
||||
struct DecompressChunkJob {
|
||||
Timestamp messageStartTime;
|
||||
Timestamp messageEndTime;
|
||||
ByteOffset chunkStartOffset;
|
||||
ByteOffset messageIndexEndOffset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A union of jobs that an indexed MCAP reader executes.
|
||||
*/
|
||||
using ReadJob = std::variant<ReadMessageJob, DecompressChunkJob>;
|
||||
|
||||
/**
|
||||
* @brief A priority queue of jobs for an indexed MCAP reader to execute.
|
||||
*/
|
||||
struct ReadJobQueue {
|
||||
private:
|
||||
bool reverse_ = false;
|
||||
std::vector<ReadJob> heap_;
|
||||
|
||||
/**
|
||||
* @brief return the timestamp key that should be used to compare jobs.
|
||||
*/
|
||||
static Timestamp TimeComparisonKey(const ReadJob& job, bool reverse) {
|
||||
Timestamp result = 0;
|
||||
std::visit(
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, ReadMessageJob>) {
|
||||
result = arg.timestamp;
|
||||
} else if constexpr (std::is_same_v<T, DecompressChunkJob>) {
|
||||
if (reverse) {
|
||||
result = arg.messageEndTime;
|
||||
} else {
|
||||
result = arg.messageStartTime;
|
||||
}
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "non-exhaustive visitor!");
|
||||
}
|
||||
},
|
||||
job);
|
||||
return result;
|
||||
}
|
||||
static RecordOffset PositionComparisonKey(const ReadJob& job, bool reverse) {
|
||||
RecordOffset result;
|
||||
std::visit(
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, ReadMessageJob>) {
|
||||
result = arg.offset;
|
||||
} else if constexpr (std::is_same_v<T, DecompressChunkJob>) {
|
||||
if (reverse) {
|
||||
result.offset = arg.messageIndexEndOffset;
|
||||
} else {
|
||||
result.offset = arg.chunkStartOffset;
|
||||
}
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "non-exhaustive visitor!");
|
||||
}
|
||||
},
|
||||
job);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool CompareForward(const ReadJob& a, const ReadJob& b) {
|
||||
auto aTimestamp = TimeComparisonKey(a, false);
|
||||
auto bTimestamp = TimeComparisonKey(b, false);
|
||||
if (aTimestamp == bTimestamp) {
|
||||
return PositionComparisonKey(a, false) > PositionComparisonKey(b, false);
|
||||
}
|
||||
return aTimestamp > bTimestamp;
|
||||
}
|
||||
|
||||
static bool CompareReverse(const ReadJob& a, const ReadJob& b) {
|
||||
auto aTimestamp = TimeComparisonKey(a, true);
|
||||
auto bTimestamp = TimeComparisonKey(b, true);
|
||||
if (aTimestamp == bTimestamp) {
|
||||
return PositionComparisonKey(a, true) < PositionComparisonKey(b, true);
|
||||
}
|
||||
return aTimestamp < bTimestamp;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ReadJobQueue(bool reverse)
|
||||
: reverse_(reverse) {}
|
||||
void push(DecompressChunkJob&& decompressChunkJob) {
|
||||
heap_.emplace_back(std::move(decompressChunkJob));
|
||||
if (!reverse_) {
|
||||
std::push_heap(heap_.begin(), heap_.end(), CompareForward);
|
||||
} else {
|
||||
std::push_heap(heap_.begin(), heap_.end(), CompareReverse);
|
||||
}
|
||||
}
|
||||
|
||||
void push(ReadMessageJob&& readMessageJob) {
|
||||
heap_.emplace_back(std::move(readMessageJob));
|
||||
if (!reverse_) {
|
||||
std::push_heap(heap_.begin(), heap_.end(), CompareForward);
|
||||
} else {
|
||||
std::push_heap(heap_.begin(), heap_.end(), CompareReverse);
|
||||
}
|
||||
}
|
||||
|
||||
ReadJob pop() {
|
||||
if (!reverse_) {
|
||||
std::pop_heap(heap_.begin(), heap_.end(), CompareForward);
|
||||
} else {
|
||||
std::pop_heap(heap_.begin(), heap_.end(), CompareReverse);
|
||||
}
|
||||
auto popped = heap_.back();
|
||||
heap_.pop_back();
|
||||
return popped;
|
||||
}
|
||||
|
||||
size_t len() const {
|
||||
return heap_.size();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcap::internal
|
||||
@@ -0,0 +1,743 @@
|
||||
#pragma once
|
||||
|
||||
#include "intervaltree.hpp"
|
||||
#include "read_job_queue.hpp"
|
||||
#include "types.hpp"
|
||||
#include "visibility.hpp"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace mcap {
|
||||
|
||||
enum struct ReadSummaryMethod {
|
||||
/**
|
||||
* @brief Parse the Summary section to produce seeking indexes and summary
|
||||
* statistics. If the Summary section is not present or corrupt, a failure
|
||||
* Status is returned and the seeking indexes and summary statistics are not
|
||||
* populated.
|
||||
*/
|
||||
NoFallbackScan,
|
||||
/**
|
||||
* @brief If the Summary section is missing or incomplete, allow falling back
|
||||
* to reading the file sequentially to produce seeking indexes and summary
|
||||
* statistics.
|
||||
*/
|
||||
AllowFallbackScan,
|
||||
/**
|
||||
* @brief Read the file sequentially from Header to DataEnd to produce seeking
|
||||
* indexes and summary statistics.
|
||||
*/
|
||||
ForceScan,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An abstract interface for reading MCAP data.
|
||||
*/
|
||||
struct MCAP_PUBLIC IReadable {
|
||||
virtual ~IReadable() = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the file in bytes.
|
||||
*
|
||||
* @return uint64_t The total number of bytes in the MCAP file.
|
||||
*/
|
||||
virtual uint64_t size() const = 0;
|
||||
/**
|
||||
* @brief This method is called by MCAP reader classes when they need to read
|
||||
* a portion of the file.
|
||||
*
|
||||
* @param output A pointer to a pointer to the buffer to write to. This method
|
||||
* is expected to either maintain an internal buffer, read data into it, and
|
||||
* update this pointer to point at the internal buffer, or update this
|
||||
* pointer to point directly at the source data if possible. The pointer and
|
||||
* data must remain valid and unmodified until the next call to read().
|
||||
* @param offset The offset in bytes from the beginning of the file to read.
|
||||
* @param size The number of bytes to read.
|
||||
* @return uint64_t Number of bytes actually read. This may be less than the
|
||||
* requested size if the end of the file is reached. The output pointer must
|
||||
* be readable from `output` to `output + size`. If the read fails, this
|
||||
* method should return 0.
|
||||
*/
|
||||
virtual uint64_t read(std::byte** output, uint64_t offset, uint64_t size) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IReadable implementation wrapping a FILE* pointer created by fopen()
|
||||
* and a read buffer.
|
||||
*/
|
||||
class MCAP_PUBLIC FileReader final : public IReadable {
|
||||
public:
|
||||
FileReader(std::FILE* file);
|
||||
|
||||
uint64_t size() const override;
|
||||
uint64_t read(std::byte** output, uint64_t offset, uint64_t size) override;
|
||||
|
||||
private:
|
||||
// Numeric type returned by the tell/seek operations. Necessary because long on Windows is 32
|
||||
// bits so the standard C library interfaces don't work for files larger than 2GiB.
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
typedef __int64 offset_type;
|
||||
#else
|
||||
typedef long offset_type;
|
||||
#endif
|
||||
|
||||
static_assert((offset_type)(uint64_t)std::numeric_limits<offset_type>::max() ==
|
||||
std::numeric_limits<offset_type>::max(),
|
||||
"offset_type should fit in uint64_t");
|
||||
|
||||
std::FILE* file_;
|
||||
std::vector<std::byte> buffer_;
|
||||
uint64_t size_;
|
||||
uint64_t position_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IReadable implementation wrapping a std::ifstream input file stream.
|
||||
*/
|
||||
class MCAP_PUBLIC FileStreamReader final : public IReadable {
|
||||
public:
|
||||
FileStreamReader(std::ifstream& stream);
|
||||
|
||||
uint64_t size() const override;
|
||||
uint64_t read(std::byte** output, uint64_t offset, uint64_t size) override;
|
||||
|
||||
private:
|
||||
std::ifstream& stream_;
|
||||
std::vector<std::byte> buffer_;
|
||||
uint64_t size_;
|
||||
uint64_t position_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An abstract interface for compressed readers.
|
||||
*/
|
||||
class MCAP_PUBLIC ICompressedReader : public IReadable {
|
||||
public:
|
||||
virtual ~ICompressedReader() override = default;
|
||||
|
||||
/**
|
||||
* @brief Reset the reader state, clearing any internal buffers and state, and
|
||||
* initialize with new compressed data.
|
||||
*
|
||||
* @param data Compressed data to read from.
|
||||
* @param size Size of the compressed data in bytes.
|
||||
* @param uncompressedSize Size of the data in bytes after decompression. A
|
||||
* buffer of this size will be allocated for the uncompressed data.
|
||||
*/
|
||||
virtual void reset(const std::byte* data, uint64_t size, uint64_t uncompressedSize) = 0;
|
||||
/**
|
||||
* @brief Report the current status of decompression. A StatusCode other than
|
||||
* `StatusCode::Success` after `reset()` is called indicates the decompression
|
||||
* was not successful and the reader is in an invalid state.
|
||||
*/
|
||||
virtual Status status() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A "null" compressed reader that directly passes through uncompressed
|
||||
* data. No internal buffers are allocated.
|
||||
*/
|
||||
class MCAP_PUBLIC BufferReader final : public ICompressedReader {
|
||||
public:
|
||||
void reset(const std::byte* data, uint64_t size, uint64_t uncompressedSize) override;
|
||||
uint64_t read(std::byte** output, uint64_t offset, uint64_t size) override;
|
||||
uint64_t size() const override;
|
||||
Status status() const override;
|
||||
|
||||
BufferReader() = default;
|
||||
BufferReader(const BufferReader&) = delete;
|
||||
BufferReader& operator=(const BufferReader&) = delete;
|
||||
BufferReader(BufferReader&&) = delete;
|
||||
BufferReader& operator=(BufferReader&&) = delete;
|
||||
|
||||
private:
|
||||
const std::byte* data_;
|
||||
uint64_t size_;
|
||||
};
|
||||
|
||||
#ifndef MCAP_COMPRESSION_NO_ZSTD
|
||||
/**
|
||||
* @brief ICompressedReader implementation that decompresses Zstandard
|
||||
* (https://facebook.github.io/zstd/) data.
|
||||
*/
|
||||
class MCAP_PUBLIC ZStdReader final : public ICompressedReader {
|
||||
public:
|
||||
void reset(const std::byte* data, uint64_t size, uint64_t uncompressedSize) override;
|
||||
uint64_t read(std::byte** output, uint64_t offset, uint64_t size) override;
|
||||
uint64_t size() const override;
|
||||
Status status() const override;
|
||||
|
||||
/**
|
||||
* @brief Decompresses an entire Zstd-compressed chunk into `output`.
|
||||
*
|
||||
* @param data The Zstd-compressed input chunk.
|
||||
* @param compressedSize The size of the Zstd-compressed input.
|
||||
* @param uncompressedSize The size of the data once uncompressed.
|
||||
* @param output The output vector. This will be resized to `uncompressedSize` to fit the data,
|
||||
* or 0 if the decompression encountered an error.
|
||||
* @return Status
|
||||
*/
|
||||
static Status DecompressAll(const std::byte* data, uint64_t compressedSize,
|
||||
uint64_t uncompressedSize, ByteArray* output);
|
||||
ZStdReader() = default;
|
||||
ZStdReader(const ZStdReader&) = delete;
|
||||
ZStdReader& operator=(const ZStdReader&) = delete;
|
||||
ZStdReader(ZStdReader&&) = delete;
|
||||
ZStdReader& operator=(ZStdReader&&) = delete;
|
||||
|
||||
private:
|
||||
Status status_;
|
||||
ByteArray uncompressedData_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef MCAP_COMPRESSION_NO_LZ4
|
||||
/**
|
||||
* @brief ICompressedReader implementation that decompresses LZ4
|
||||
* (https://lz4.github.io/lz4/) data.
|
||||
*/
|
||||
class MCAP_PUBLIC LZ4Reader final : public ICompressedReader {
|
||||
public:
|
||||
void reset(const std::byte* data, uint64_t size, uint64_t uncompressedSize) override;
|
||||
uint64_t read(std::byte** output, uint64_t offset, uint64_t size) override;
|
||||
uint64_t size() const override;
|
||||
Status status() const override;
|
||||
|
||||
/**
|
||||
* @brief Decompresses an entire LZ4-encoded chunk into `output`.
|
||||
*
|
||||
* @param data The LZ4-compressed input chunk.
|
||||
* @param size The size of the LZ4-compressed input.
|
||||
* @param uncompressedSize The size of the data once uncompressed.
|
||||
* @param output The output vector. This will be resized to `uncompressedSize` to fit the data,
|
||||
* or 0 if the decompression encountered an error.
|
||||
* @return Status
|
||||
*/
|
||||
Status decompressAll(const std::byte* data, uint64_t size, uint64_t uncompressedSize,
|
||||
ByteArray* output);
|
||||
LZ4Reader();
|
||||
LZ4Reader(const LZ4Reader&) = delete;
|
||||
LZ4Reader& operator=(const LZ4Reader&) = delete;
|
||||
LZ4Reader(LZ4Reader&&) = delete;
|
||||
LZ4Reader& operator=(LZ4Reader&&) = delete;
|
||||
~LZ4Reader() override;
|
||||
|
||||
private:
|
||||
void* decompressionContext_ = nullptr; // LZ4F_dctx*
|
||||
Status status_;
|
||||
const std::byte* compressedData_;
|
||||
ByteArray uncompressedData_;
|
||||
uint64_t compressedSize_;
|
||||
uint64_t uncompressedSize_;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct LinearMessageView;
|
||||
|
||||
/**
|
||||
* @brief Options for reading messages out of an MCAP file.
|
||||
*/
|
||||
struct MCAP_PUBLIC ReadMessageOptions {
|
||||
public:
|
||||
/**
|
||||
* @brief Only messages with log timestamps greater or equal to startTime will be included.
|
||||
*/
|
||||
Timestamp startTime = 0;
|
||||
/**
|
||||
* @brief Only messages with log timestamps less than endTime will be included.
|
||||
*/
|
||||
Timestamp endTime = MaxTime;
|
||||
/**
|
||||
* @brief If provided, `topicFilter` is called on all topics found in the MCAP file. If
|
||||
* `topicFilter` returns true for a given channel, messages from that channel will be included.
|
||||
* if not provided, messages from all channels are provided.
|
||||
*/
|
||||
std::function<bool(std::string_view)> topicFilter;
|
||||
enum struct ReadOrder { FileOrder, LogTimeOrder, ReverseLogTimeOrder };
|
||||
/**
|
||||
* @brief Set the expected order that messages should be returned in.
|
||||
* if readOrder == FileOrder, messages will be returned in the order they appear in the MCAP file.
|
||||
* if readOrder == LogTimeOrder, messages will be returned in ascending log time order.
|
||||
* if readOrder == ReverseLogTimeOrder, messages will be returned in descending log time order.
|
||||
*/
|
||||
ReadOrder readOrder = ReadOrder::FileOrder;
|
||||
|
||||
ReadMessageOptions(Timestamp start, Timestamp end)
|
||||
: startTime(start)
|
||||
, endTime(end) {}
|
||||
|
||||
ReadMessageOptions() = default;
|
||||
|
||||
/**
|
||||
* @brief validate the configuration.
|
||||
*/
|
||||
Status validate() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides a read interface to an MCAP file.
|
||||
*/
|
||||
class MCAP_PUBLIC McapReader final {
|
||||
public:
|
||||
~McapReader();
|
||||
|
||||
/**
|
||||
* @brief Opens an MCAP file for reading from an already constructed IReadable
|
||||
* implementation.
|
||||
*
|
||||
* @param reader An implementation of the IReader interface that provides raw
|
||||
* MCAP data.
|
||||
* @return Status StatusCode::Success on success. If a non-success Status is
|
||||
* returned, the data source is not considered open and McapReader is not
|
||||
* usable until `open()` is called and a success response is returned.
|
||||
*/
|
||||
Status open(IReadable& reader);
|
||||
/**
|
||||
* @brief Opens an MCAP file for reading from a given filename.
|
||||
*
|
||||
* @param filename Filename to open.
|
||||
* @return Status StatusCode::Success on success. If a non-success Status is
|
||||
* returned, the data source is not considered open and McapReader is not
|
||||
* usable until `open()` is called and a success response is returned.
|
||||
*/
|
||||
Status open(std::string_view filename);
|
||||
/**
|
||||
* @brief Opens an MCAP file for reading from a std::ifstream input file
|
||||
* stream.
|
||||
*
|
||||
* @param stream Input file stream to read MCAP data from.
|
||||
* @return Status StatusCode::Success on success. If a non-success Status is
|
||||
* returned, the file is not considered open and McapReader is not usable
|
||||
* until `open()` is called and a success response is returned.
|
||||
*/
|
||||
Status open(std::ifstream& stream);
|
||||
|
||||
/**
|
||||
* @brief Closes the MCAP file, clearing any internal data structures and
|
||||
* state and dropping the data source reference.
|
||||
*
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Read and parse the Summary section at the end of the MCAP file, if
|
||||
* available. This will populate internal indexes to allow for efficient
|
||||
* summarization and random access. This method will automatically be called
|
||||
* upon requesting summary data or first seek if Summary section parsing is
|
||||
* allowed by the configuration options.
|
||||
*/
|
||||
Status readSummary(
|
||||
ReadSummaryMethod method, const ProblemCallback& onProblem = [](const Status&) {});
|
||||
|
||||
/**
|
||||
* @brief Returns an iterable view with `begin()` and `end()` methods for
|
||||
* iterating Messages in the MCAP file. If a non-zero `startTime` is provided,
|
||||
* this will first parse the Summary section (by calling `readSummary()`) if
|
||||
* allowed by the configuration options and it has not been parsed yet.
|
||||
*
|
||||
* @param startTime Optional start time in nanoseconds. Messages before this
|
||||
* time will not be returned.
|
||||
* @param endTime Optional end time in nanoseconds. Messages equal to or after
|
||||
* this time will not be returned.
|
||||
*/
|
||||
LinearMessageView readMessages(Timestamp startTime = 0, Timestamp endTime = MaxTime);
|
||||
/**
|
||||
* @brief Returns an iterable view with `begin()` and `end()` methods for
|
||||
* iterating Messages in the MCAP file. If a non-zero `startTime` is provided,
|
||||
* this will first parse the Summary section (by calling `readSummary()`) if
|
||||
* allowed by the configuration options and it has not been parsed yet.
|
||||
*
|
||||
* @param onProblem A callback that will be called when a parsing error
|
||||
* occurs. Problems can either be recoverable, indicating some data could
|
||||
* not be read, or non-recoverable, stopping the iteration.
|
||||
* @param startTime Optional start time in nanoseconds. Messages before this
|
||||
* time will not be returned.
|
||||
* @param endTime Optional end time in nanoseconds. Messages equal to or after
|
||||
* this time will not be returned.
|
||||
*/
|
||||
LinearMessageView readMessages(const ProblemCallback& onProblem, Timestamp startTime = 0,
|
||||
Timestamp endTime = MaxTime);
|
||||
|
||||
/**
|
||||
* @brief Returns an iterable view with `begin()` and `end()` methods for
|
||||
* iterating Messages in the MCAP file.
|
||||
* Uses the options from `options` to select the messages that are yielded.
|
||||
*/
|
||||
LinearMessageView readMessages(const ProblemCallback& onProblem,
|
||||
const ReadMessageOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Returns starting and ending byte offsets that must be read to
|
||||
* iterate all messages in the given time range. If `readSummary()` has been
|
||||
* successfully called and the recording contains Chunk records, this range
|
||||
* will be narrowed to Chunk records that contain messages in the given time
|
||||
* range. Otherwise, this range will be the entire Data section if the Data
|
||||
* End record has been found or the entire file otherwise.
|
||||
*
|
||||
* This method is automatically used by `readMessages()`, and only needs to be
|
||||
* called directly if the caller is manually constructing an iterator.
|
||||
*
|
||||
* @param startTime Start time in nanoseconds.
|
||||
* @param endTime Optional end time in nanoseconds.
|
||||
* @return Start and end byte offsets.
|
||||
*/
|
||||
std::pair<ByteOffset, ByteOffset> byteRange(Timestamp startTime,
|
||||
Timestamp endTime = MaxTime) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the IReadable data source backing this reader.
|
||||
* Will return nullptr if the reader is not open.
|
||||
*/
|
||||
IReadable* dataSource();
|
||||
|
||||
/**
|
||||
* @brief Returns the parsed Header record, if it has been encountered.
|
||||
*/
|
||||
const std::optional<Header>& header() const;
|
||||
/**
|
||||
* @brief Returns the parsed Footer record, if it has been encountered.
|
||||
*/
|
||||
const std::optional<Footer>& footer() const;
|
||||
/**
|
||||
* @brief Returns the parsed Statistics record, if it has been encountered.
|
||||
*/
|
||||
const std::optional<Statistics>& statistics() const;
|
||||
|
||||
/**
|
||||
* @brief Returns all of the parsed Channel records. Call `readSummary()`
|
||||
* first to fully populate this data structure.
|
||||
*/
|
||||
const std::unordered_map<ChannelId, ChannelPtr> channels() const;
|
||||
/**
|
||||
* @brief Returns all of the parsed Schema records. Call `readSummary()`
|
||||
* first to fully populate this data structure.
|
||||
*/
|
||||
const std::unordered_map<SchemaId, SchemaPtr> schemas() const;
|
||||
|
||||
/**
|
||||
* @brief Look up a Channel record by channel ID. If the Channel has not been
|
||||
* encountered yet or does not exist in the file, this will return nullptr.
|
||||
*
|
||||
* @param channelId Channel ID to search for
|
||||
* @return ChannelPtr A shared pointer to a Channel record, or nullptr
|
||||
*/
|
||||
ChannelPtr channel(ChannelId channelId) const;
|
||||
/**
|
||||
* @brief Look up a Schema record by schema ID. If the Schema has not been
|
||||
* encountered yet or does not exist in the file, this will return nullptr.
|
||||
*
|
||||
* @param schemaId Schema ID to search for
|
||||
* @return SchemaPtr A shared pointer to a Schema record, or nullptr
|
||||
*/
|
||||
SchemaPtr schema(SchemaId schemaId) const;
|
||||
|
||||
/**
|
||||
* @brief Returns all of the parsed ChunkIndex records. Call `readSummary()`
|
||||
* first to fully populate this data structure.
|
||||
*/
|
||||
const std::vector<ChunkIndex>& chunkIndexes() const;
|
||||
|
||||
/**
|
||||
* @brief Returns all of the parsed MetadataIndex records. Call `readSummary()`
|
||||
* first to fully populate this data structure.
|
||||
* The multimap's keys are the `name` field from each indexed Metadata.
|
||||
*/
|
||||
const std::multimap<std::string, MetadataIndex>& metadataIndexes() const;
|
||||
|
||||
/**
|
||||
* @brief Returns all of the parsed AttachmentIndex records. Call `readSummary()`
|
||||
* first to fully populate this data structure.
|
||||
* The multimap's keys are the `name` field from each indexed Attachment.
|
||||
*/
|
||||
const std::multimap<std::string, AttachmentIndex>& attachmentIndexes() const;
|
||||
|
||||
// The following static methods are used internally for parsing MCAP records
|
||||
// and do not need to be called directly unless you are implementing your own
|
||||
// reader functionality or tests.
|
||||
|
||||
static Status ReadRecord(IReadable& reader, uint64_t offset, Record* record);
|
||||
static Status ReadFooter(IReadable& reader, uint64_t offset, Footer* footer);
|
||||
|
||||
static Status ParseHeader(const Record& record, Header* header);
|
||||
static Status ParseFooter(const Record& record, Footer* footer);
|
||||
static Status ParseSchema(const Record& record, Schema* schema);
|
||||
static Status ParseChannel(const Record& record, Channel* channel);
|
||||
static Status ParseMessage(const Record& record, Message* message);
|
||||
static Status ParseChunk(const Record& record, Chunk* chunk);
|
||||
static Status ParseMessageIndex(const Record& record, MessageIndex* messageIndex);
|
||||
static Status ParseChunkIndex(const Record& record, ChunkIndex* chunkIndex);
|
||||
static Status ParseAttachment(const Record& record, Attachment* attachment);
|
||||
static Status ParseAttachmentIndex(const Record& record, AttachmentIndex* attachmentIndex);
|
||||
static Status ParseStatistics(const Record& record, Statistics* statistics);
|
||||
static Status ParseMetadata(const Record& record, Metadata* metadata);
|
||||
static Status ParseMetadataIndex(const Record& record, MetadataIndex* metadataIndex);
|
||||
static Status ParseSummaryOffset(const Record& record, SummaryOffset* summaryOffset);
|
||||
static Status ParseDataEnd(const Record& record, DataEnd* dataEnd);
|
||||
|
||||
/**
|
||||
* @brief Converts a compression string ("", "zstd", "lz4") to the Compression enum.
|
||||
*/
|
||||
static std::optional<Compression> ParseCompression(const std::string_view compression);
|
||||
|
||||
private:
|
||||
using ChunkInterval = internal::Interval<ByteOffset, ChunkIndex>;
|
||||
friend LinearMessageView;
|
||||
|
||||
IReadable* input_ = nullptr;
|
||||
std::FILE* file_ = nullptr;
|
||||
std::unique_ptr<FileReader> fileInput_;
|
||||
std::unique_ptr<FileStreamReader> fileStreamInput_;
|
||||
std::optional<Header> header_;
|
||||
std::optional<Footer> footer_;
|
||||
std::optional<Statistics> statistics_;
|
||||
std::vector<ChunkIndex> chunkIndexes_;
|
||||
internal::IntervalTree<ByteOffset, ChunkIndex> chunkRanges_;
|
||||
std::multimap<std::string, AttachmentIndex> attachmentIndexes_;
|
||||
std::multimap<std::string, MetadataIndex> metadataIndexes_;
|
||||
std::unordered_map<SchemaId, SchemaPtr> schemas_;
|
||||
std::unordered_map<ChannelId, ChannelPtr> channels_;
|
||||
ByteOffset dataStart_ = 0;
|
||||
ByteOffset dataEnd_ = EndOffset;
|
||||
bool parsedSummary_ = false;
|
||||
|
||||
void reset_();
|
||||
Status readSummarySection_(IReadable& reader);
|
||||
Status readSummaryFromScan_(IReadable& reader);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A low-level interface for parsing MCAP-style TLV records from a data
|
||||
* source.
|
||||
*/
|
||||
struct MCAP_PUBLIC RecordReader {
|
||||
ByteOffset offset;
|
||||
ByteOffset endOffset;
|
||||
|
||||
RecordReader(IReadable& dataSource, ByteOffset startOffset, ByteOffset endOffset = EndOffset);
|
||||
|
||||
void reset(IReadable& dataSource, ByteOffset startOffset, ByteOffset endOffset);
|
||||
|
||||
std::optional<Record> next();
|
||||
|
||||
const Status& status() const;
|
||||
|
||||
ByteOffset curRecordOffset() const;
|
||||
|
||||
private:
|
||||
IReadable* dataSource_ = nullptr;
|
||||
Status status_;
|
||||
Record curRecord_;
|
||||
};
|
||||
|
||||
struct MCAP_PUBLIC TypedChunkReader {
|
||||
std::function<void(const SchemaPtr, ByteOffset)> onSchema;
|
||||
std::function<void(const ChannelPtr, ByteOffset)> onChannel;
|
||||
std::function<void(const Message&, ByteOffset)> onMessage;
|
||||
std::function<void(const Record&, ByteOffset)> onUnknownRecord;
|
||||
|
||||
TypedChunkReader();
|
||||
TypedChunkReader(const TypedChunkReader&) = delete;
|
||||
TypedChunkReader& operator=(const TypedChunkReader&) = delete;
|
||||
TypedChunkReader(TypedChunkReader&&) = delete;
|
||||
TypedChunkReader& operator=(TypedChunkReader&&) = delete;
|
||||
|
||||
void reset(const Chunk& chunk, Compression compression);
|
||||
|
||||
bool next();
|
||||
|
||||
ByteOffset offset() const;
|
||||
|
||||
const Status& status() const;
|
||||
|
||||
private:
|
||||
RecordReader reader_;
|
||||
Status status_;
|
||||
BufferReader uncompressedReader_;
|
||||
#ifndef MCAP_COMPRESSION_NO_LZ4
|
||||
LZ4Reader lz4Reader_;
|
||||
#endif
|
||||
#ifndef MCAP_COMPRESSION_NO_ZSTD
|
||||
ZStdReader zstdReader_;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mid-level interface for parsing and validating MCAP records from a
|
||||
* data source.
|
||||
*/
|
||||
struct MCAP_PUBLIC TypedRecordReader {
|
||||
std::function<void(const Header&, ByteOffset)> onHeader;
|
||||
std::function<void(const Footer&, ByteOffset)> onFooter;
|
||||
std::function<void(const SchemaPtr, ByteOffset, std::optional<ByteOffset>)> onSchema;
|
||||
std::function<void(const ChannelPtr, ByteOffset, std::optional<ByteOffset>)> onChannel;
|
||||
std::function<void(const Message&, ByteOffset, std::optional<ByteOffset>)> onMessage;
|
||||
std::function<void(const Chunk&, ByteOffset)> onChunk;
|
||||
std::function<void(const MessageIndex&, ByteOffset)> onMessageIndex;
|
||||
std::function<void(const ChunkIndex&, ByteOffset)> onChunkIndex;
|
||||
std::function<void(const Attachment&, ByteOffset)> onAttachment;
|
||||
std::function<void(const AttachmentIndex&, ByteOffset)> onAttachmentIndex;
|
||||
std::function<void(const Statistics&, ByteOffset)> onStatistics;
|
||||
std::function<void(const Metadata&, ByteOffset)> onMetadata;
|
||||
std::function<void(const MetadataIndex&, ByteOffset)> onMetadataIndex;
|
||||
std::function<void(const SummaryOffset&, ByteOffset)> onSummaryOffset;
|
||||
std::function<void(const DataEnd&, ByteOffset)> onDataEnd;
|
||||
std::function<void(const Record&, ByteOffset, std::optional<ByteOffset>)> onUnknownRecord;
|
||||
std::function<void(ByteOffset)> onChunkEnd;
|
||||
|
||||
TypedRecordReader(IReadable& dataSource, ByteOffset startOffset,
|
||||
ByteOffset endOffset = EndOffset);
|
||||
|
||||
TypedRecordReader(const TypedRecordReader&) = delete;
|
||||
TypedRecordReader& operator=(const TypedRecordReader&) = delete;
|
||||
TypedRecordReader(TypedRecordReader&&) = delete;
|
||||
TypedRecordReader& operator=(TypedRecordReader&&) = delete;
|
||||
|
||||
bool next();
|
||||
|
||||
ByteOffset offset() const;
|
||||
|
||||
const Status& status() const;
|
||||
|
||||
private:
|
||||
RecordReader reader_;
|
||||
TypedChunkReader chunkReader_;
|
||||
Status status_;
|
||||
bool parsingChunk_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Uses message indices to read messages out of an MCAP in log time order.
|
||||
* The underlying MCAP must be chunked, with a summary section and message indexes.
|
||||
* The required McapWriterOptions are:
|
||||
* - noChunking: false
|
||||
* - noMessageIndex: false
|
||||
* - noSummary: false
|
||||
*/
|
||||
struct MCAP_PUBLIC IndexedMessageReader {
|
||||
public:
|
||||
IndexedMessageReader(McapReader& reader, const ReadMessageOptions& options,
|
||||
const std::function<void(const Message&, RecordOffset)> onMessage);
|
||||
|
||||
/**
|
||||
* @brief reads the next message out of the MCAP.
|
||||
*
|
||||
* @return true if a message was found.
|
||||
* @return false if no more messages are to be read. If there was some error reading the MCAP,
|
||||
* `status()` will return a non-Success status.
|
||||
*/
|
||||
bool next();
|
||||
|
||||
/**
|
||||
* @brief gets the status of the reader.
|
||||
*
|
||||
* @return Status
|
||||
*/
|
||||
Status status() const;
|
||||
|
||||
private:
|
||||
struct ChunkSlot {
|
||||
ByteArray decompressedChunk;
|
||||
ByteOffset chunkStartOffset;
|
||||
int unreadMessages = 0;
|
||||
};
|
||||
size_t findFreeChunkSlot();
|
||||
void decompressChunk(const Chunk& chunk, ChunkSlot& slot);
|
||||
Status status_;
|
||||
McapReader& mcapReader_;
|
||||
RecordReader recordReader_;
|
||||
#ifndef MCAP_COMPRESSION_NO_LZ4
|
||||
LZ4Reader lz4Reader_;
|
||||
#endif
|
||||
ReadMessageOptions options_;
|
||||
std::unordered_set<ChannelId> selectedChannels_;
|
||||
std::function<void(const Message&, RecordOffset)> onMessage_;
|
||||
internal::ReadJobQueue queue_;
|
||||
std::vector<ChunkSlot> chunkSlots_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An iterable view of Messages in an MCAP file.
|
||||
*/
|
||||
struct MCAP_PUBLIC LinearMessageView {
|
||||
struct MCAP_PUBLIC Iterator {
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using difference_type = int64_t;
|
||||
using value_type = MessageView;
|
||||
using pointer = const MessageView*;
|
||||
using reference = const MessageView&;
|
||||
|
||||
reference operator*() const;
|
||||
pointer operator->() const;
|
||||
Iterator& operator++();
|
||||
void operator++(int);
|
||||
MCAP_PUBLIC friend bool operator==(const Iterator& a, const Iterator& b);
|
||||
MCAP_PUBLIC friend bool operator!=(const Iterator& a, const Iterator& b);
|
||||
|
||||
private:
|
||||
friend LinearMessageView;
|
||||
|
||||
Iterator() = default;
|
||||
Iterator(LinearMessageView& view);
|
||||
|
||||
class Impl {
|
||||
public:
|
||||
Impl(LinearMessageView& view);
|
||||
|
||||
Impl(const Impl&) = delete;
|
||||
Impl& operator=(const Impl&) = delete;
|
||||
Impl(Impl&&) = delete;
|
||||
Impl& operator=(Impl&&) = delete;
|
||||
|
||||
void increment();
|
||||
reference dereference() const;
|
||||
bool has_value() const;
|
||||
|
||||
LinearMessageView& view_;
|
||||
|
||||
std::optional<TypedRecordReader> recordReader_;
|
||||
std::optional<IndexedMessageReader> indexedMessageReader_;
|
||||
Message curMessage_;
|
||||
std::optional<MessageView> curMessageView_;
|
||||
|
||||
private:
|
||||
void onMessage(const Message& message, RecordOffset offset);
|
||||
};
|
||||
|
||||
bool begun_ = false;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
LinearMessageView(McapReader& mcapReader, const ProblemCallback& onProblem);
|
||||
LinearMessageView(McapReader& mcapReader, ByteOffset dataStart, ByteOffset dataEnd,
|
||||
Timestamp startTime, Timestamp endTime, const ProblemCallback& onProblem);
|
||||
LinearMessageView(McapReader& mcapReader, const ReadMessageOptions& options, ByteOffset dataStart,
|
||||
ByteOffset dataEnd, const ProblemCallback& onProblem);
|
||||
|
||||
LinearMessageView(const LinearMessageView&) = delete;
|
||||
LinearMessageView& operator=(const LinearMessageView&) = delete;
|
||||
LinearMessageView(LinearMessageView&&) = default;
|
||||
LinearMessageView& operator=(LinearMessageView&&) = delete;
|
||||
|
||||
Iterator begin();
|
||||
Iterator end();
|
||||
|
||||
private:
|
||||
McapReader& mcapReader_;
|
||||
ByteOffset dataStart_;
|
||||
ByteOffset dataEnd_;
|
||||
ReadMessageOptions readMessageOptions_;
|
||||
const ProblemCallback onProblem_;
|
||||
};
|
||||
|
||||
} // namespace mcap
|
||||
|
||||
#ifdef MCAP_IMPLEMENTATION
|
||||
# include "reader.inl"
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,407 @@
|
||||
#pragma once
|
||||
|
||||
#include "errors.hpp"
|
||||
#include "visibility.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace mcap {
|
||||
|
||||
#define MCAP_LIBRARY_VERSION "2.1.3"
|
||||
|
||||
using SchemaId = uint16_t;
|
||||
using ChannelId = uint16_t;
|
||||
using Timestamp = uint64_t;
|
||||
using ByteOffset = uint64_t;
|
||||
using KeyValueMap = std::unordered_map<std::string, std::string>;
|
||||
using ByteArray = std::vector<std::byte>;
|
||||
using ProblemCallback = std::function<void(const Status&)>;
|
||||
|
||||
constexpr char SpecVersion = '0';
|
||||
constexpr char LibraryVersion[] = MCAP_LIBRARY_VERSION;
|
||||
constexpr uint8_t Magic[] = {137, 77, 67, 65, 80, SpecVersion, 13, 10}; // "\x89MCAP0\r\n"
|
||||
constexpr uint64_t DefaultChunkSize = 1024 * 768;
|
||||
constexpr ByteOffset EndOffset = std::numeric_limits<ByteOffset>::max();
|
||||
constexpr Timestamp MaxTime = std::numeric_limits<Timestamp>::max();
|
||||
|
||||
/**
|
||||
* @brief Supported MCAP compression algorithms.
|
||||
*/
|
||||
enum struct Compression {
|
||||
None,
|
||||
Lz4,
|
||||
Zstd,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compression level to use when compression is enabled. Slower generally
|
||||
* produces smaller files, at the expense of more CPU time. These levels map to
|
||||
* different internal settings for each compression algorithm.
|
||||
*/
|
||||
enum struct CompressionLevel {
|
||||
Fastest,
|
||||
Fast,
|
||||
Default,
|
||||
Slow,
|
||||
Slowest,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief MCAP record types.
|
||||
*/
|
||||
enum struct OpCode : uint8_t {
|
||||
Header = 0x01,
|
||||
Footer = 0x02,
|
||||
Schema = 0x03,
|
||||
Channel = 0x04,
|
||||
Message = 0x05,
|
||||
Chunk = 0x06,
|
||||
MessageIndex = 0x07,
|
||||
ChunkIndex = 0x08,
|
||||
Attachment = 0x09,
|
||||
AttachmentIndex = 0x0A,
|
||||
Statistics = 0x0B,
|
||||
Metadata = 0x0C,
|
||||
MetadataIndex = 0x0D,
|
||||
SummaryOffset = 0x0E,
|
||||
DataEnd = 0x0F,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the string representation of an OpCode.
|
||||
*/
|
||||
MCAP_PUBLIC
|
||||
constexpr std::string_view OpCodeString(OpCode opcode);
|
||||
|
||||
/**
|
||||
* @brief A generic Type-Length-Value record using a uint8 type and uint64
|
||||
* length. This is the generic form of all MCAP records.
|
||||
*/
|
||||
struct MCAP_PUBLIC Record {
|
||||
OpCode opcode;
|
||||
uint64_t dataSize;
|
||||
std::byte* data;
|
||||
|
||||
uint64_t recordSize() const {
|
||||
return sizeof(opcode) + sizeof(dataSize) + dataSize;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Appears at the beginning of every MCAP file (after the magic byte
|
||||
* sequence) and contains the recording profile (see
|
||||
* <https://github.com/foxglove/mcap/tree/main/docs/specification/profiles>) and
|
||||
* a string signature of the recording library.
|
||||
*/
|
||||
struct MCAP_PUBLIC Header {
|
||||
std::string profile;
|
||||
std::string library;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The final record in an MCAP file (before the trailing magic byte
|
||||
* sequence). Contains byte offsets from the start of the file to the Summary
|
||||
* and Summary Offset sections, along with an optional CRC of the combined
|
||||
* Summary and Summary Offset sections. A `summaryStart` and
|
||||
* `summaryOffsetStart` of zero indicates no Summary section is available.
|
||||
*/
|
||||
struct MCAP_PUBLIC Footer {
|
||||
ByteOffset summaryStart;
|
||||
ByteOffset summaryOffsetStart;
|
||||
uint32_t summaryCrc;
|
||||
|
||||
Footer() = default;
|
||||
Footer(ByteOffset _summaryStart, ByteOffset _summaryOffsetStart)
|
||||
: summaryStart(_summaryStart)
|
||||
, summaryOffsetStart(_summaryOffsetStart)
|
||||
, summaryCrc(0) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describes a schema used for message encoding and decoding and/or
|
||||
* describing the shape of messages. One or more Channel records map to a single
|
||||
* Schema.
|
||||
*/
|
||||
struct MCAP_PUBLIC Schema {
|
||||
SchemaId id;
|
||||
std::string name;
|
||||
std::string encoding;
|
||||
ByteArray data;
|
||||
|
||||
Schema() = default;
|
||||
|
||||
Schema(const std::string_view _name, const std::string_view _encoding,
|
||||
const std::string_view _data)
|
||||
: name(_name)
|
||||
, encoding(_encoding)
|
||||
, data{reinterpret_cast<const std::byte*>(_data.data()),
|
||||
reinterpret_cast<const std::byte*>(_data.data() + _data.size())} {}
|
||||
|
||||
Schema(const std::string_view _name, const std::string_view _encoding, const ByteArray& _data)
|
||||
: name(_name)
|
||||
, encoding(_encoding)
|
||||
, data{_data} {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describes a Channel that messages are written to. A Channel represents
|
||||
* a single connection from a publisher to a topic, so each topic will have one
|
||||
* Channel per publisher. Channels optionally reference a Schema, for message
|
||||
* encodings that are not self-describing (e.g. JSON) or when schema information
|
||||
* is available (e.g. JSONSchema).
|
||||
*/
|
||||
struct MCAP_PUBLIC Channel {
|
||||
ChannelId id;
|
||||
std::string topic;
|
||||
std::string messageEncoding;
|
||||
SchemaId schemaId;
|
||||
KeyValueMap metadata;
|
||||
|
||||
Channel() = default;
|
||||
|
||||
Channel(const std::string_view _topic, const std::string_view _messageEncoding,
|
||||
SchemaId _schemaId, const KeyValueMap& _metadata = {})
|
||||
: topic(_topic)
|
||||
, messageEncoding(_messageEncoding)
|
||||
, schemaId(_schemaId)
|
||||
, metadata(_metadata) {}
|
||||
};
|
||||
|
||||
using SchemaPtr = std::shared_ptr<Schema>;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
/**
|
||||
* @brief A single Message published to a Channel.
|
||||
*/
|
||||
struct MCAP_PUBLIC Message {
|
||||
ChannelId channelId;
|
||||
/**
|
||||
* @brief An optional sequence number. If non-zero, sequence numbers should be
|
||||
* unique per channel and increasing over time.
|
||||
*/
|
||||
uint32_t sequence;
|
||||
/**
|
||||
* @brief Nanosecond timestamp when this message was recorded or received for
|
||||
* recording.
|
||||
*/
|
||||
Timestamp logTime;
|
||||
/**
|
||||
* @brief Nanosecond timestamp when this message was initially published. If
|
||||
* not available, this should be set to `logTime`.
|
||||
*/
|
||||
Timestamp publishTime;
|
||||
/**
|
||||
* @brief Size of the message payload in bytes, pointed to via `data`.
|
||||
*/
|
||||
uint64_t dataSize;
|
||||
/**
|
||||
* @brief A pointer to the message payload. For readers, this pointer is only
|
||||
* valid for the lifetime of an onMessage callback or before the message
|
||||
* iterator is advanced.
|
||||
*/
|
||||
const std::byte* data = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An collection of Schemas, Channels, and Messages that supports
|
||||
* compression and indexing.
|
||||
*/
|
||||
struct MCAP_PUBLIC Chunk {
|
||||
Timestamp messageStartTime;
|
||||
Timestamp messageEndTime;
|
||||
ByteOffset uncompressedSize;
|
||||
uint32_t uncompressedCrc;
|
||||
std::string compression;
|
||||
ByteOffset compressedSize;
|
||||
const std::byte* records = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A list of timestamps to byte offsets for a single Channel. This record
|
||||
* appears after each Chunk, one per Channel that appeared in that Chunk.
|
||||
*/
|
||||
struct MCAP_PUBLIC MessageIndex {
|
||||
ChannelId channelId;
|
||||
std::vector<std::pair<Timestamp, ByteOffset>> records;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Chunk Index records are found in the Summary section, providing
|
||||
* summary information for a single Chunk and pointing to each Message Index
|
||||
* record associated with that Chunk.
|
||||
*/
|
||||
struct MCAP_PUBLIC ChunkIndex {
|
||||
Timestamp messageStartTime;
|
||||
Timestamp messageEndTime;
|
||||
ByteOffset chunkStartOffset;
|
||||
ByteOffset chunkLength;
|
||||
std::unordered_map<ChannelId, ByteOffset> messageIndexOffsets;
|
||||
ByteOffset messageIndexLength;
|
||||
std::string compression;
|
||||
ByteOffset compressedSize;
|
||||
ByteOffset uncompressedSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An Attachment is an arbitrary file embedded in an MCAP file, including
|
||||
* a name, media type, timestamps, and optional CRC. Attachment records are
|
||||
* written in the Data section, outside of Chunks.
|
||||
*/
|
||||
struct MCAP_PUBLIC Attachment {
|
||||
Timestamp logTime;
|
||||
Timestamp createTime;
|
||||
std::string name;
|
||||
std::string mediaType;
|
||||
uint64_t dataSize;
|
||||
const std::byte* data = nullptr;
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Attachment Index records are found in the Summary section, providing
|
||||
* summary information for a single Attachment.
|
||||
*/
|
||||
struct MCAP_PUBLIC AttachmentIndex {
|
||||
ByteOffset offset;
|
||||
ByteOffset length;
|
||||
Timestamp logTime;
|
||||
Timestamp createTime;
|
||||
uint64_t dataSize;
|
||||
std::string name;
|
||||
std::string mediaType;
|
||||
|
||||
AttachmentIndex() = default;
|
||||
AttachmentIndex(const Attachment& attachment, ByteOffset fileOffset)
|
||||
: offset(fileOffset)
|
||||
, length(9 +
|
||||
/* name */ 4 + attachment.name.size() +
|
||||
/* log_time */ 8 +
|
||||
/* create_time */ 8 +
|
||||
/* media_type */ 4 + attachment.mediaType.size() +
|
||||
/* data */ 8 + attachment.dataSize +
|
||||
/* crc */ 4)
|
||||
, logTime(attachment.logTime)
|
||||
, createTime(attachment.createTime)
|
||||
, dataSize(attachment.dataSize)
|
||||
, name(attachment.name)
|
||||
, mediaType(attachment.mediaType) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Statistics record is found in the Summary section, providing
|
||||
* counts and timestamp ranges for the entire file.
|
||||
*/
|
||||
struct MCAP_PUBLIC Statistics {
|
||||
uint64_t messageCount;
|
||||
uint16_t schemaCount;
|
||||
uint32_t channelCount;
|
||||
uint32_t attachmentCount;
|
||||
uint32_t metadataCount;
|
||||
uint32_t chunkCount;
|
||||
Timestamp messageStartTime;
|
||||
Timestamp messageEndTime;
|
||||
std::unordered_map<ChannelId, uint64_t> channelMessageCounts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Holds a named map of key/value strings containing arbitrary user data.
|
||||
* Metadata records are found in the Data section, outside of Chunks.
|
||||
*/
|
||||
struct MCAP_PUBLIC Metadata {
|
||||
std::string name;
|
||||
KeyValueMap metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Metadata Index records are found in the Summary section, providing
|
||||
* summary information for a single Metadata record.
|
||||
*/
|
||||
struct MCAP_PUBLIC MetadataIndex {
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
std::string name;
|
||||
|
||||
MetadataIndex() = default;
|
||||
MetadataIndex(const Metadata& metadata, ByteOffset fileOffset);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Summary Offset records are found in the Summary Offset section.
|
||||
* Records in the Summary section are grouped together, and for each record type
|
||||
* found in the Summary section, a Summary Offset references the file offset and
|
||||
* length where that type of Summary record can be found.
|
||||
*/
|
||||
struct MCAP_PUBLIC SummaryOffset {
|
||||
OpCode groupOpCode;
|
||||
ByteOffset groupStart;
|
||||
ByteOffset groupLength;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The final record in the Data section, signaling the end of Data and
|
||||
* beginning of Summary. Optionally contains a CRC of the entire Data section.
|
||||
*/
|
||||
struct MCAP_PUBLIC DataEnd {
|
||||
uint32_t dataSectionCrc;
|
||||
};
|
||||
|
||||
struct MCAP_PUBLIC RecordOffset {
|
||||
ByteOffset offset;
|
||||
std::optional<ByteOffset> chunkOffset;
|
||||
|
||||
RecordOffset() = default;
|
||||
explicit RecordOffset(ByteOffset offset_)
|
||||
: offset(offset_) {}
|
||||
RecordOffset(ByteOffset offset_, ByteOffset chunkOffset_)
|
||||
: offset(offset_)
|
||||
, chunkOffset(chunkOffset_) {}
|
||||
|
||||
bool operator==(const RecordOffset& other) const;
|
||||
bool operator>(const RecordOffset& other) const;
|
||||
|
||||
bool operator!=(const RecordOffset& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator>=(const RecordOffset& other) const {
|
||||
return ((*this == other) || (*this > other));
|
||||
}
|
||||
bool operator<(const RecordOffset& other) const {
|
||||
return !(*this >= other);
|
||||
}
|
||||
bool operator<=(const RecordOffset& other) const {
|
||||
return !(*this > other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Returned when iterating over Messages in a file, MessageView contains
|
||||
* a reference to one Message, a pointer to its Channel, and an optional pointer
|
||||
* to that Channel's Schema. The Channel pointer is guaranteed to be valid,
|
||||
* while the Schema pointer may be null if the Channel references schema_id 0.
|
||||
*/
|
||||
struct MCAP_PUBLIC MessageView {
|
||||
const Message& message;
|
||||
const ChannelPtr channel;
|
||||
const SchemaPtr schema;
|
||||
const RecordOffset messageOffset;
|
||||
|
||||
MessageView(const Message& _message, const ChannelPtr _channel, const SchemaPtr _schema,
|
||||
RecordOffset offset)
|
||||
: message(_message)
|
||||
, channel(_channel)
|
||||
, schema(_schema)
|
||||
, messageOffset(offset) {}
|
||||
};
|
||||
|
||||
} // namespace mcap
|
||||
|
||||
#ifdef MCAP_IMPLEMENTATION
|
||||
# include "types.inl"
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "internal.hpp"
|
||||
|
||||
namespace mcap {
|
||||
|
||||
constexpr std::string_view OpCodeString(OpCode opcode) {
|
||||
switch (opcode) {
|
||||
case OpCode::Header:
|
||||
return "Header";
|
||||
case OpCode::Footer:
|
||||
return "Footer";
|
||||
case OpCode::Schema:
|
||||
return "Schema";
|
||||
case OpCode::Channel:
|
||||
return "Channel";
|
||||
case OpCode::Message:
|
||||
return "Message";
|
||||
case OpCode::Chunk:
|
||||
return "Chunk";
|
||||
case OpCode::MessageIndex:
|
||||
return "MessageIndex";
|
||||
case OpCode::ChunkIndex:
|
||||
return "ChunkIndex";
|
||||
case OpCode::Attachment:
|
||||
return "Attachment";
|
||||
case OpCode::AttachmentIndex:
|
||||
return "AttachmentIndex";
|
||||
case OpCode::Statistics:
|
||||
return "Statistics";
|
||||
case OpCode::Metadata:
|
||||
return "Metadata";
|
||||
case OpCode::MetadataIndex:
|
||||
return "MetadataIndex";
|
||||
case OpCode::SummaryOffset:
|
||||
return "SummaryOffset";
|
||||
case OpCode::DataEnd:
|
||||
return "DataEnd";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
MetadataIndex::MetadataIndex(const Metadata& metadata, ByteOffset fileOffset)
|
||||
: offset(fileOffset)
|
||||
, length(9 + 4 + metadata.name.size() + 4 + internal::KeyValueMapSize(metadata.metadata))
|
||||
, name(metadata.name) {}
|
||||
|
||||
bool RecordOffset::operator==(const RecordOffset& other) const {
|
||||
if (chunkOffset != std::nullopt && other.chunkOffset != std::nullopt) {
|
||||
if (*chunkOffset != *other.chunkOffset) {
|
||||
// messages are in separate chunks, cannot be equal.
|
||||
return false;
|
||||
}
|
||||
// messages are in the same chunk, compare chunk-level offsets.
|
||||
return (offset == other.offset);
|
||||
}
|
||||
if (chunkOffset != std::nullopt || other.chunkOffset != std::nullopt) {
|
||||
// one message is in a chunk and one is not, cannot be equal.
|
||||
return false;
|
||||
}
|
||||
// neither message is in a chunk, compare file-level offsets.
|
||||
return (offset == other.offset);
|
||||
}
|
||||
|
||||
bool RecordOffset::operator>(const RecordOffset& other) const {
|
||||
if (chunkOffset != std::nullopt) {
|
||||
if (other.chunkOffset != std::nullopt) {
|
||||
if (*chunkOffset == *other.chunkOffset) {
|
||||
// messages are in the same chunk, compare chunk-level offsets.
|
||||
return (offset > other.offset);
|
||||
}
|
||||
// messages are in separate chunks, compare file-level offsets
|
||||
return (*chunkOffset > *other.chunkOffset);
|
||||
} else {
|
||||
// this message is in a chunk, other is not, compare file-level offsets.
|
||||
return (*chunkOffset > other.offset);
|
||||
}
|
||||
}
|
||||
if (other.chunkOffset != std::nullopt) {
|
||||
// other message is in a chunk, this is not, compare file-level offsets.
|
||||
return (offset > *other.chunkOffset);
|
||||
}
|
||||
// neither message is in a chunk, compare file-level offsets.
|
||||
return (offset > other.offset);
|
||||
}
|
||||
|
||||
} // namespace mcap
|
||||
@@ -0,0 +1,28 @@
|
||||
/** Defines an MCAP_PUBLIC visibility attribute macro, which is used on all public interfaces.
|
||||
* This can be defined before including `mcap.hpp` to directly control symbol visibility.
|
||||
* If not defined externally, this library attempts to export symbols from the translation unit
|
||||
* where MCAP_IMPLEMENTATION is defined, and import them anywhere else.
|
||||
*/
|
||||
#ifndef MCAP_PUBLIC
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
# ifdef MCAP_IMPLEMENTATION
|
||||
# ifdef __GNUC__
|
||||
# define MCAP_PUBLIC __attribute__((dllexport))
|
||||
# else
|
||||
# define MCAP_PUBLIC __declspec(dllexport)
|
||||
# endif
|
||||
# else
|
||||
# ifdef __GNUC__
|
||||
# define MCAP_PUBLIC __attribute__((dllimport))
|
||||
# else
|
||||
# define MCAP_PUBLIC __declspec(dllimport)
|
||||
# endif
|
||||
# endif
|
||||
#else
|
||||
# if __GNUC__ >= 4
|
||||
# define MCAP_PUBLIC __attribute__((visibility("default")))
|
||||
# else
|
||||
# define MCAP_PUBLIC
|
||||
# endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,514 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include "visibility.hpp"
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// Forward declaration
|
||||
#ifndef MCAP_COMPRESSION_NO_ZSTD
|
||||
struct ZSTD_CCtx_s;
|
||||
#endif
|
||||
|
||||
namespace mcap {
|
||||
|
||||
/**
|
||||
* @brief Configuration options for McapWriter.
|
||||
*/
|
||||
struct MCAP_PUBLIC McapWriterOptions {
|
||||
/**
|
||||
* @brief Disable CRC calculations for Chunks.
|
||||
*/
|
||||
bool noChunkCRC = false;
|
||||
/**
|
||||
* @brief Disable CRC calculations for Attachments.
|
||||
*/
|
||||
bool noAttachmentCRC = false;
|
||||
/**
|
||||
* @brief Enable CRC calculations for all records in the data section.
|
||||
*/
|
||||
bool enableDataCRC = false;
|
||||
/**
|
||||
* @brief Disable CRC calculations for the summary section.
|
||||
*/
|
||||
bool noSummaryCRC = false;
|
||||
/**
|
||||
* @brief Do not write Chunks to the file, instead writing Schema, Channel,
|
||||
* and Message records directly into the Data section.
|
||||
*/
|
||||
bool noChunking = false;
|
||||
/**
|
||||
* @brief Do not write Message Index records to the file. If
|
||||
* `noMessageIndex=true` and `noChunkIndex=false`, Chunk Index records will
|
||||
* still be written to the Summary section, providing a coarse message index.
|
||||
*/
|
||||
bool noMessageIndex = false;
|
||||
/**
|
||||
* @brief Do not write Summary or Summary Offset sections to the file, placing
|
||||
* the Footer record immediately after DataEnd. This can provide some speed
|
||||
* boost to file writing and produce smaller files, at the expense of
|
||||
* requiring a conversion process later if fast summarization or indexed
|
||||
* access is desired.
|
||||
*/
|
||||
bool noSummary = false;
|
||||
/**
|
||||
* @brief Target uncompressed Chunk payload size in bytes. Once a Chunk's
|
||||
* uncompressed data is about to exceed this size, the Chunk will be
|
||||
* compressed (if enabled) and written to disk. Note that this is a 'soft'
|
||||
* ceiling as some Chunks could exceed this size due to either indexing
|
||||
* data or when a single message is larger than `chunkSize`, in which case,
|
||||
* the Chunk will contain only this one large message.
|
||||
* This option is ignored if `noChunking=true`.
|
||||
*/
|
||||
uint64_t chunkSize = DefaultChunkSize;
|
||||
/**
|
||||
* @brief Compression algorithm to use when writing Chunks. This option is
|
||||
* ignored if `noChunking=true`.
|
||||
*/
|
||||
Compression compression = Compression::Zstd;
|
||||
/**
|
||||
* @brief Compression level to use when writing Chunks. Slower generally
|
||||
* produces smaller files, at the expense of more CPU time. These levels map
|
||||
* to different internal settings for each compression algorithm.
|
||||
*/
|
||||
CompressionLevel compressionLevel = CompressionLevel::Default;
|
||||
/**
|
||||
* @brief By default, Chunks that do not benefit from compression will be
|
||||
* written uncompressed. This option can be used to force compression on all
|
||||
* Chunks. This option is ignored if `noChunking=true`.
|
||||
*/
|
||||
bool forceCompression = false;
|
||||
/**
|
||||
* @brief The recording profile. See
|
||||
* https://mcap.dev/spec/registry#well-known-profiles
|
||||
* for more information on well-known profiles.
|
||||
*/
|
||||
std::string profile;
|
||||
/**
|
||||
* @brief A freeform string written by recording libraries. For this library,
|
||||
* the default is "libmcap {Major}.{Minor}.{Patch}".
|
||||
*/
|
||||
std::string library = "libmcap " MCAP_LIBRARY_VERSION;
|
||||
|
||||
// The following options are less commonly used, providing more fine-grained
|
||||
// control of index records and the Summary section
|
||||
|
||||
bool noRepeatedSchemas = false;
|
||||
bool noRepeatedChannels = false;
|
||||
bool noAttachmentIndex = false;
|
||||
bool noMetadataIndex = false;
|
||||
bool noChunkIndex = false;
|
||||
bool noStatistics = false;
|
||||
bool noSummaryOffsets = false;
|
||||
|
||||
McapWriterOptions(const std::string_view _profile)
|
||||
: profile(_profile) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An abstract interface for writing MCAP data.
|
||||
*/
|
||||
class MCAP_PUBLIC IWritable {
|
||||
public:
|
||||
bool crcEnabled = false;
|
||||
|
||||
IWritable() noexcept;
|
||||
virtual ~IWritable() = default;
|
||||
|
||||
/**
|
||||
* @brief Called whenever the writer needs to write data to the output MCAP
|
||||
* file.
|
||||
*
|
||||
* @param data A pointer to the data to write.
|
||||
* @param size Size of the data in bytes.
|
||||
*/
|
||||
void write(const std::byte* data, uint64_t size);
|
||||
/**
|
||||
* @brief Called when the writer is finished writing data to the output MCAP
|
||||
* file.
|
||||
*/
|
||||
virtual void end() = 0;
|
||||
/**
|
||||
* @brief Returns the current size of the file in bytes. This must be equal to
|
||||
* the sum of all `size` parameters passed to `write()`.
|
||||
*/
|
||||
virtual uint64_t size() const = 0;
|
||||
/**
|
||||
* @brief Returns the CRC32 of the uncompressed data.
|
||||
*/
|
||||
uint32_t crc();
|
||||
/**
|
||||
* @brief Resets the CRC32 calculation.
|
||||
*/
|
||||
void resetCrc();
|
||||
|
||||
/**
|
||||
* @brief flushes any buffered data to the output. This is called by McapWriter after every
|
||||
* completed chunk. Callers may also retain a reference to the writer and call flush() at their
|
||||
* own cadence. Defaults to a no-op.
|
||||
*/
|
||||
virtual void flush() {}
|
||||
|
||||
protected:
|
||||
virtual void handleWrite(const std::byte* data, uint64_t size) = 0;
|
||||
|
||||
private:
|
||||
uint32_t crc_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements the IWritable interface used by McapWriter by wrapping a
|
||||
* FILE* pointer created by fopen().
|
||||
*/
|
||||
class MCAP_PUBLIC FileWriter final : public IWritable {
|
||||
public:
|
||||
~FileWriter() override;
|
||||
|
||||
Status open(std::string_view filename);
|
||||
|
||||
void handleWrite(const std::byte* data, uint64_t size) override;
|
||||
void end() override;
|
||||
void flush() override;
|
||||
uint64_t size() const override;
|
||||
|
||||
private:
|
||||
std::FILE* file_ = nullptr;
|
||||
uint64_t size_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements the IWritable interface used by McapWriter by wrapping a
|
||||
* std::ostream stream.
|
||||
*/
|
||||
class MCAP_PUBLIC StreamWriter final : public IWritable {
|
||||
public:
|
||||
StreamWriter(std::ostream& stream);
|
||||
|
||||
void handleWrite(const std::byte* data, uint64_t size) override;
|
||||
void end() override;
|
||||
void flush() override;
|
||||
uint64_t size() const override;
|
||||
|
||||
private:
|
||||
std::ostream& stream_;
|
||||
uint64_t size_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An abstract interface for writing Chunk data. Chunk data is buffered
|
||||
* in memory and written to disk as a single record, to support optimal
|
||||
* compression and calculating the final Chunk data size.
|
||||
*/
|
||||
class MCAP_PUBLIC IChunkWriter : public IWritable {
|
||||
public:
|
||||
virtual ~IChunkWriter() override = default;
|
||||
|
||||
/**
|
||||
* @brief Called when the writer wants to close the current output Chunk.
|
||||
* After this call, `data()` and `size()` should return the data and size of
|
||||
* the compressed data.
|
||||
*/
|
||||
virtual void end() override = 0;
|
||||
/**
|
||||
* @brief Returns the size in bytes of the uncompressed data.
|
||||
*/
|
||||
virtual uint64_t size() const override = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the size in bytes of the compressed data. This will only be
|
||||
* called after `end()`.
|
||||
*/
|
||||
virtual uint64_t compressedSize() const = 0;
|
||||
/**
|
||||
* @brief Returns true if `write()` has never been called since initialization
|
||||
* or the last call to `clear()`.
|
||||
*/
|
||||
virtual bool empty() const = 0;
|
||||
/**
|
||||
* @brief Clear the internal state of the writer, discarding any input or
|
||||
* output buffers.
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* @brief Returns a pointer to the uncompressed data.
|
||||
*/
|
||||
virtual const std::byte* data() const = 0;
|
||||
/**
|
||||
* @brief Returns a pointer to the compressed data. This will only be called
|
||||
* after `end()`.
|
||||
*/
|
||||
virtual const std::byte* compressedData() const = 0;
|
||||
|
||||
protected:
|
||||
virtual void handleClear() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An in-memory IChunkWriter implementation backed by a
|
||||
* growable buffer.
|
||||
*/
|
||||
class MCAP_PUBLIC BufferWriter final : public IChunkWriter {
|
||||
public:
|
||||
void handleWrite(const std::byte* data, uint64_t size) override;
|
||||
void end() override;
|
||||
uint64_t size() const override;
|
||||
uint64_t compressedSize() const override;
|
||||
bool empty() const override;
|
||||
void handleClear() override;
|
||||
const std::byte* data() const override;
|
||||
const std::byte* compressedData() const override;
|
||||
|
||||
private:
|
||||
std::vector<std::byte> buffer_;
|
||||
};
|
||||
|
||||
#ifndef MCAP_COMPRESSION_NO_LZ4
|
||||
/**
|
||||
* @brief An in-memory IChunkWriter implementation that holds data in a
|
||||
* temporary buffer before flushing to an LZ4-compressed buffer.
|
||||
*/
|
||||
class MCAP_PUBLIC LZ4Writer final : public IChunkWriter {
|
||||
public:
|
||||
LZ4Writer(CompressionLevel compressionLevel, uint64_t chunkSize);
|
||||
|
||||
void handleWrite(const std::byte* data, uint64_t size) override;
|
||||
void end() override;
|
||||
uint64_t size() const override;
|
||||
uint64_t compressedSize() const override;
|
||||
bool empty() const override;
|
||||
void handleClear() override;
|
||||
const std::byte* data() const override;
|
||||
const std::byte* compressedData() const override;
|
||||
|
||||
private:
|
||||
std::vector<std::byte> uncompressedBuffer_;
|
||||
std::vector<std::byte> compressedBuffer_;
|
||||
CompressionLevel compressionLevel_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef MCAP_COMPRESSION_NO_ZSTD
|
||||
/**
|
||||
* @brief An in-memory IChunkWriter implementation that holds data in a
|
||||
* temporary buffer before flushing to an ZStandard-compressed buffer.
|
||||
*/
|
||||
class MCAP_PUBLIC ZStdWriter final : public IChunkWriter {
|
||||
public:
|
||||
ZStdWriter(CompressionLevel compressionLevel, uint64_t chunkSize);
|
||||
~ZStdWriter() override;
|
||||
|
||||
void handleWrite(const std::byte* data, uint64_t size) override;
|
||||
void end() override;
|
||||
uint64_t size() const override;
|
||||
uint64_t compressedSize() const override;
|
||||
bool empty() const override;
|
||||
void handleClear() override;
|
||||
const std::byte* data() const override;
|
||||
const std::byte* compressedData() const override;
|
||||
|
||||
private:
|
||||
std::vector<std::byte> uncompressedBuffer_;
|
||||
std::vector<std::byte> compressedBuffer_;
|
||||
ZSTD_CCtx_s* zstdContext_ = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Provides a write interface to an MCAP file.
|
||||
*/
|
||||
class MCAP_PUBLIC McapWriter final {
|
||||
public:
|
||||
~McapWriter();
|
||||
|
||||
/**
|
||||
* @brief Open a new MCAP file for writing and write the header.
|
||||
*
|
||||
* If the writer was already opened, this calls `close`() first to reset the state.
|
||||
* A writer may be re-used after being reset via `close`() or `terminate`().
|
||||
*
|
||||
* @param filename Filename of the MCAP file to write.
|
||||
* @param options Options for MCAP writing. `profile` is required.
|
||||
* @return A non-success status if the file could not be opened for writing.
|
||||
*/
|
||||
Status open(std::string_view filename, const McapWriterOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Open a new MCAP file for writing and write the header.
|
||||
*
|
||||
* If the writer was already opened, this calls `close`() first to reset the state.
|
||||
* A writer may be re-used after being reset via `close`() or `terminate`().
|
||||
*
|
||||
* @param writer An implementation of the IWritable interface. Output bytes
|
||||
* will be written to this object.
|
||||
* @param options Options for MCAP writing. `profile` is required.
|
||||
*/
|
||||
void open(IWritable& writer, const McapWriterOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Open a new MCAP file for writing and write the header.
|
||||
*
|
||||
* @param stream Output stream to write to.
|
||||
* @param options Options for MCAP writing. `profile` is required.
|
||||
*/
|
||||
void open(std::ostream& stream, const McapWriterOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Write the MCAP footer, flush pending writes to the output stream,
|
||||
* and reset internal state. The writer may be re-used with another call to open afterwards.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Reset internal state without writing the MCAP footer or flushing
|
||||
* pending writes. This should only be used in error cases as the output MCAP
|
||||
* file will be truncated. The writer may be re-used with another call to open afterwards.
|
||||
*/
|
||||
void terminate();
|
||||
|
||||
/**
|
||||
* @brief Add a new schema to the MCAP file and set `schema.id` to a generated
|
||||
* schema id. The schema id is used when adding channels to the file.
|
||||
*
|
||||
* Schemas are not cleared when the state is reset via `close`() or `terminate`().
|
||||
* If you're re-using a writer for multiple files in a row, the schemas only need
|
||||
* to be added once, before first use.
|
||||
*
|
||||
* This method does not de-duplicate schemas.
|
||||
*
|
||||
* @param schema Description of the schema to register. The `id` field is
|
||||
* ignored and will be set to a generated schema id.
|
||||
*/
|
||||
void addSchema(Schema& schema);
|
||||
|
||||
/**
|
||||
* @brief Add a new channel to the MCAP file and set `channel.id` to a
|
||||
* generated channel id. The channel id is used when adding messages to the
|
||||
* file.
|
||||
*
|
||||
* Channels are not cleared when the state is reset via `close`() or `terminate`().
|
||||
* If you're re-using a writer for multiple files in a row, the channels only need
|
||||
* to be added once, before first use.
|
||||
*
|
||||
* This method does not de-duplicate channels.
|
||||
*
|
||||
* @param channel Description of the channel to register. The `id` value is
|
||||
* ignored and will be set to a generated channel id.
|
||||
*/
|
||||
void addChannel(Channel& channel);
|
||||
|
||||
/**
|
||||
* @brief Write a message to the output stream.
|
||||
*
|
||||
* @param msg Message to add.
|
||||
* @return A non-zero error code on failure.
|
||||
*/
|
||||
Status write(const Message& message);
|
||||
|
||||
/**
|
||||
* @brief Write an attachment to the output stream.
|
||||
*
|
||||
* @param attachment Attachment to add. The `attachment.crc` will be
|
||||
* calculated and set if configuration options allow CRC calculation.
|
||||
* @return A non-zero error code on failure.
|
||||
*/
|
||||
Status write(Attachment& attachment);
|
||||
|
||||
/**
|
||||
* @brief Write a metadata record to the output stream.
|
||||
*
|
||||
* @param metadata Named group of key/value string pairs to add.
|
||||
* @return A non-zero error code on failure.
|
||||
*/
|
||||
Status write(const Metadata& metadata);
|
||||
|
||||
/**
|
||||
* @brief Current MCAP file-level statistics. This is written as a Statistics
|
||||
* record in the Summary section of the MCAP file.
|
||||
*/
|
||||
const Statistics& statistics() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the IWritable data destination backing this
|
||||
* writer. Will return nullptr if the writer is not open.
|
||||
*/
|
||||
IWritable* dataSink();
|
||||
|
||||
/**
|
||||
* @brief finishes the current chunk in progress and writes it to the file, if a chunk
|
||||
* is in progress.
|
||||
*/
|
||||
void closeLastChunk();
|
||||
|
||||
// The following static methods are used for serialization of records and
|
||||
// primitives to an output stream. They are not intended to be used directly
|
||||
// unless you are implementing a lower level writer or tests
|
||||
|
||||
static void writeMagic(IWritable& output);
|
||||
|
||||
static uint64_t write(IWritable& output, const Header& header);
|
||||
static uint64_t write(IWritable& output, const Footer& footer, bool crcEnabled);
|
||||
static uint64_t write(IWritable& output, const Schema& schema);
|
||||
static uint64_t write(IWritable& output, const Channel& channel);
|
||||
static uint64_t getRecordSize(const Message& message);
|
||||
static uint64_t write(IWritable& output, const Message& message);
|
||||
static uint64_t write(IWritable& output, const Attachment& attachment);
|
||||
static uint64_t write(IWritable& output, const Metadata& metadata);
|
||||
static uint64_t write(IWritable& output, const Chunk& chunk);
|
||||
static uint64_t write(IWritable& output, const MessageIndex& index);
|
||||
static uint64_t write(IWritable& output, const ChunkIndex& index);
|
||||
static uint64_t write(IWritable& output, const AttachmentIndex& index);
|
||||
static uint64_t write(IWritable& output, const MetadataIndex& index);
|
||||
static uint64_t write(IWritable& output, const Statistics& stats);
|
||||
static uint64_t write(IWritable& output, const SummaryOffset& summaryOffset);
|
||||
static uint64_t write(IWritable& output, const DataEnd& dataEnd);
|
||||
static uint64_t write(IWritable& output, const Record& record);
|
||||
|
||||
static void write(IWritable& output, const std::string_view str);
|
||||
static void write(IWritable& output, const ByteArray bytes);
|
||||
static void write(IWritable& output, OpCode value);
|
||||
static void write(IWritable& output, uint16_t value);
|
||||
static void write(IWritable& output, uint32_t value);
|
||||
static void write(IWritable& output, uint64_t value);
|
||||
static void write(IWritable& output, const std::byte* data, uint64_t size);
|
||||
static void write(IWritable& output, const KeyValueMap& map, uint32_t size = 0);
|
||||
|
||||
private:
|
||||
McapWriterOptions options_{""};
|
||||
uint64_t chunkSize_ = DefaultChunkSize;
|
||||
IWritable* output_ = nullptr;
|
||||
std::unique_ptr<FileWriter> fileOutput_;
|
||||
std::unique_ptr<StreamWriter> streamOutput_;
|
||||
std::unique_ptr<BufferWriter> uncompressedChunk_;
|
||||
#ifndef MCAP_COMPRESSION_NO_LZ4
|
||||
std::unique_ptr<LZ4Writer> lz4Chunk_;
|
||||
#endif
|
||||
#ifndef MCAP_COMPRESSION_NO_ZSTD
|
||||
std::unique_ptr<ZStdWriter> zstdChunk_;
|
||||
#endif
|
||||
std::vector<Schema> schemas_;
|
||||
std::vector<Channel> channels_;
|
||||
std::vector<AttachmentIndex> attachmentIndex_;
|
||||
std::vector<MetadataIndex> metadataIndex_;
|
||||
std::vector<ChunkIndex> chunkIndex_;
|
||||
Statistics statistics_{};
|
||||
std::unordered_set<SchemaId> writtenSchemas_;
|
||||
std::unordered_map<ChannelId, MessageIndex> currentMessageIndex_;
|
||||
Timestamp currentChunkStart_ = MaxTime;
|
||||
Timestamp currentChunkEnd_ = 0;
|
||||
Compression compression_ = Compression::None;
|
||||
uint64_t uncompressedSize_ = 0;
|
||||
bool opened_ = false;
|
||||
|
||||
IWritable& getOutput();
|
||||
IChunkWriter* getChunkWriter();
|
||||
void writeChunk(IWritable& output, IChunkWriter& chunkData);
|
||||
};
|
||||
|
||||
} // namespace mcap
|
||||
|
||||
#ifdef MCAP_IMPLEMENTATION
|
||||
# include "writer.inl"
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
include_dir = include_directories('.')
|
||||
|
||||
if not is_subproject
|
||||
install_subdir('toml++', install_dir: get_option('includedir'))
|
||||
endif
|
||||
|
||||
if not build_lib # header-only mode
|
||||
|
||||
tomlplusplus_dep = declare_dependency(include_directories: include_dir)
|
||||
|
||||
if not is_subproject
|
||||
import('pkgconfig').generate(
|
||||
name: meson.project_name(),
|
||||
description: 'Header-only TOML config file parser and serializer for C++',
|
||||
install_dir: get_option('datadir')/'pkgconfig',
|
||||
url: 'https://marzer.github.io/tomlplusplus'
|
||||
)
|
||||
endif
|
||||
|
||||
# cmake
|
||||
if get_option('generate_cmake_config') and not is_subproject and not is_devel
|
||||
cmake = import('cmake')
|
||||
# Can't use until Meson 0.62.0, see https://github.com/mesonbuild/meson/pull/9916
|
||||
# and https://github.com/marzer/tomlplusplus/issues/140
|
||||
#cmake.write_basic_package_version_file(
|
||||
# name: meson.project_name(),
|
||||
# version: meson.project_version(),
|
||||
# install_dir: get_option('datadir')/'cmake'/meson.project_name(),
|
||||
# arch_independent: true
|
||||
#)
|
||||
# In the meantime, install a pre-generated Package Version file
|
||||
configure_file(
|
||||
configuration: {'version': meson.project_version()},
|
||||
input: '..'/'cmake'/'tomlplusplusConfigVersion.cmake.meson.in',
|
||||
output: 'tomlplusplusConfigVersion.cmake',
|
||||
install_dir: get_option('datadir')/'cmake'/meson.project_name()
|
||||
)
|
||||
|
||||
cmake.configure_package_config_file(
|
||||
name: meson.project_name(),
|
||||
input: '..'/'cmake'/'tomlplusplusConfig.cmake.meson.in',
|
||||
configuration: configuration_data({'includedir': get_option('includedir')}),
|
||||
install_dir: get_option('datadir')/'cmake'/meson.project_name(),
|
||||
)
|
||||
endif
|
||||
|
||||
endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,382 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "array.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::array() noexcept
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_ARRAY_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::~array() noexcept
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_ARRAY_DESTROYED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::array(const impl::array_init_elem* b, const impl::array_init_elem* e)
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_ARRAY_CREATED;
|
||||
#endif
|
||||
|
||||
TOML_ASSERT_ASSUME(b);
|
||||
TOML_ASSERT_ASSUME(e);
|
||||
TOML_ASSERT_ASSUME(b <= e);
|
||||
|
||||
if TOML_UNLIKELY(b == e)
|
||||
return;
|
||||
|
||||
size_t cap{};
|
||||
for (auto it = b; it != e; it++)
|
||||
{
|
||||
if (it->value)
|
||||
cap++;
|
||||
}
|
||||
if TOML_UNLIKELY(!cap)
|
||||
return;
|
||||
|
||||
elems_.reserve(cap);
|
||||
for (; b != e; b++)
|
||||
{
|
||||
if (b->value)
|
||||
elems_.push_back(std::move(b->value));
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::array(const array& other) //
|
||||
: node(other)
|
||||
{
|
||||
elems_.reserve(other.elems_.size());
|
||||
for (const auto& elem : other)
|
||||
elems_.emplace_back(impl::make_node(elem));
|
||||
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_ARRAY_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::array(array && other) noexcept //
|
||||
: node(std::move(other)),
|
||||
elems_(std::move(other.elems_))
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_ARRAY_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array& array::operator=(const array& rhs)
|
||||
{
|
||||
if (&rhs != this)
|
||||
{
|
||||
node::operator=(rhs);
|
||||
elems_.clear();
|
||||
elems_.reserve(rhs.elems_.size());
|
||||
for (const auto& elem : rhs)
|
||||
elems_.emplace_back(impl::make_node(elem));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array& array::operator=(array&& rhs) noexcept
|
||||
{
|
||||
if (&rhs != this)
|
||||
{
|
||||
node::operator=(std::move(rhs));
|
||||
elems_ = std::move(rhs.elems_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::preinsertion_resize(size_t idx, size_t count)
|
||||
{
|
||||
TOML_ASSERT(idx <= elems_.size());
|
||||
TOML_ASSERT_ASSUME(count >= 1u);
|
||||
const auto old_size = elems_.size();
|
||||
const auto new_size = old_size + count;
|
||||
const auto inserting_at_end = idx == old_size;
|
||||
elems_.resize(new_size);
|
||||
if (!inserting_at_end)
|
||||
{
|
||||
for (size_t left = old_size, right = new_size - 1u; left-- > idx; right--)
|
||||
elems_[right] = std::move(elems_[left]);
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::insert_at_back(impl::node_ptr && elem)
|
||||
{
|
||||
TOML_ASSERT(elem);
|
||||
elems_.push_back(std::move(elem));
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::vector_iterator array::insert_at(const_vector_iterator pos, impl::node_ptr && elem)
|
||||
{
|
||||
return elems_.insert(pos, std::move(elem));
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool array::is_homogeneous(node_type ntype) const noexcept
|
||||
{
|
||||
if (elems_.empty())
|
||||
return false;
|
||||
|
||||
if (ntype == node_type::none)
|
||||
ntype = elems_[0]->type();
|
||||
|
||||
for (const auto& val : elems_)
|
||||
if (val->type() != ntype)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool array::is_homogeneous(node_type ntype, node * &first_nonmatch) noexcept
|
||||
{
|
||||
if (elems_.empty())
|
||||
{
|
||||
first_nonmatch = {};
|
||||
return false;
|
||||
}
|
||||
if (ntype == node_type::none)
|
||||
ntype = elems_[0]->type();
|
||||
for (const auto& val : elems_)
|
||||
{
|
||||
if (val->type() != ntype)
|
||||
{
|
||||
first_nonmatch = val.get();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool array::is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept
|
||||
{
|
||||
node* fnm = nullptr;
|
||||
const auto result = const_cast<array&>(*this).is_homogeneous(ntype, fnm);
|
||||
first_nonmatch = fnm;
|
||||
return result;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node& array::at(size_t index)
|
||||
{
|
||||
#if TOML_COMPILER_HAS_EXCEPTIONS
|
||||
|
||||
return *elems_.at(index);
|
||||
|
||||
#else
|
||||
|
||||
auto n = get(index);
|
||||
TOML_ASSERT_ASSUME(n && "element index not found in array!");
|
||||
return *n;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::reserve(size_t new_capacity)
|
||||
{
|
||||
elems_.reserve(new_capacity);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::shrink_to_fit()
|
||||
{
|
||||
elems_.shrink_to_fit();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::truncate(size_t new_size)
|
||||
{
|
||||
if (new_size < elems_.size())
|
||||
elems_.resize(new_size);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::iterator array::erase(const_iterator pos) noexcept
|
||||
{
|
||||
return iterator{ elems_.erase(const_vector_iterator{ pos }) };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array::iterator array::erase(const_iterator first, const_iterator last) noexcept
|
||||
{
|
||||
return iterator{ elems_.erase(const_vector_iterator{ first }, const_vector_iterator{ last }) };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
size_t array::total_leaf_count() const noexcept
|
||||
{
|
||||
size_t leaves{};
|
||||
for (size_t i = 0, e = elems_.size(); i < e; i++)
|
||||
{
|
||||
auto arr = elems_[i]->as_array();
|
||||
leaves += arr ? arr->total_leaf_count() : size_t{ 1 };
|
||||
}
|
||||
return leaves;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::flatten_child(array && child, size_t & dest_index) noexcept
|
||||
{
|
||||
for (size_t i = 0, e = child.size(); i < e; i++)
|
||||
{
|
||||
auto type = child.elems_[i]->type();
|
||||
if (type == node_type::array)
|
||||
{
|
||||
array& arr = *reinterpret_cast<array*>(child.elems_[i].get());
|
||||
if (!arr.empty())
|
||||
flatten_child(std::move(arr), dest_index);
|
||||
}
|
||||
else
|
||||
elems_[dest_index++] = std::move(child.elems_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array& array::flatten()&
|
||||
{
|
||||
if (elems_.empty())
|
||||
return *this;
|
||||
|
||||
bool requires_flattening = false;
|
||||
size_t size_after_flattening = elems_.size();
|
||||
for (size_t i = elems_.size(); i-- > 0u;)
|
||||
{
|
||||
auto arr = elems_[i]->as_array();
|
||||
if (!arr)
|
||||
continue;
|
||||
size_after_flattening--; // discount the array itself
|
||||
const auto leaf_count = arr->total_leaf_count();
|
||||
if (leaf_count > 0u)
|
||||
{
|
||||
requires_flattening = true;
|
||||
size_after_flattening += leaf_count;
|
||||
}
|
||||
else
|
||||
elems_.erase(elems_.cbegin() + static_cast<ptrdiff_t>(i));
|
||||
}
|
||||
|
||||
if (!requires_flattening)
|
||||
return *this;
|
||||
|
||||
elems_.reserve(size_after_flattening);
|
||||
|
||||
size_t i = 0;
|
||||
while (i < elems_.size())
|
||||
{
|
||||
auto arr = elems_[i]->as_array();
|
||||
if (!arr)
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
impl::node_ptr arr_storage = std::move(elems_[i]);
|
||||
const auto leaf_count = arr->total_leaf_count();
|
||||
if (leaf_count > 1u)
|
||||
preinsertion_resize(i + 1u, leaf_count - 1u);
|
||||
flatten_child(std::move(*arr), i); // increments i
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
array& array::prune(bool recursive)& noexcept
|
||||
{
|
||||
if (elems_.empty())
|
||||
return *this;
|
||||
|
||||
for (size_t i = elems_.size(); i-- > 0u;)
|
||||
{
|
||||
if (auto arr = elems_[i]->as_array())
|
||||
{
|
||||
if (recursive)
|
||||
arr->prune(true);
|
||||
if (arr->empty())
|
||||
elems_.erase(elems_.cbegin() + static_cast<ptrdiff_t>(i));
|
||||
}
|
||||
else if (auto tbl = elems_[i]->as_table())
|
||||
{
|
||||
if (recursive)
|
||||
tbl->prune(true);
|
||||
if (tbl->empty())
|
||||
elems_.erase(elems_.cbegin() + static_cast<ptrdiff_t>(i));
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::pop_back() noexcept
|
||||
{
|
||||
elems_.pop_back();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void array::clear() noexcept
|
||||
{
|
||||
elems_.clear();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV array::equal(const array& lhs, const array& rhs) noexcept
|
||||
{
|
||||
if (&lhs == &rhs)
|
||||
return true;
|
||||
if (lhs.elems_.size() != rhs.elems_.size())
|
||||
return false;
|
||||
for (size_t i = 0, e = lhs.elems_.size(); i < e; i++)
|
||||
{
|
||||
const auto lhs_type = lhs.elems_[i]->type();
|
||||
const node& rhs_ = *rhs.elems_[i];
|
||||
const auto rhs_type = rhs_.type();
|
||||
if (lhs_type != rhs_type)
|
||||
return false;
|
||||
|
||||
const bool equal = lhs.elems_[i]->visit(
|
||||
[&](const auto& lhs_) noexcept
|
||||
{ return lhs_ == *reinterpret_cast<std::remove_reference_t<decltype(lhs_)>*>(&rhs_); });
|
||||
if (!equal)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,97 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "forward_declarations.hpp"
|
||||
|
||||
/// \cond
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
template <typename T>
|
||||
using parse_path_callback = bool(TOML_CALLCONV*)(void*, T);
|
||||
|
||||
TOML_NODISCARD
|
||||
bool TOML_CALLCONV parse_path(std::string_view,
|
||||
void*,
|
||||
parse_path_callback<std::string_view>,
|
||||
parse_path_callback<size_t>);
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
/// \endcond
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief Returns a view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto config = toml::parse(R"(
|
||||
///
|
||||
/// [foo]
|
||||
/// bar = [ 0, 1, 2, [ 3 ], { kek = 4 } ]
|
||||
///
|
||||
/// )"sv);
|
||||
///
|
||||
/// std::cout << toml::at_path(config, "foo.bar[2]") << "\n";
|
||||
/// std::cout << toml::at_path(config, "foo.bar[3][0]") << "\n";
|
||||
/// std::cout << toml::at_path(config, "foo.bar[4].kek") << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 2
|
||||
/// 3
|
||||
/// 4
|
||||
/// \eout
|
||||
///
|
||||
///
|
||||
/// \note Keys in paths are interpreted literally, so whitespace (or lack thereof) matters:
|
||||
/// \cpp
|
||||
/// toml::at_path(config, "foo.bar") // same as config["foo"]["bar"]
|
||||
/// toml::at_path(config, "foo. bar") // same as config["foo"][" bar"]
|
||||
/// toml::at_path(config, "foo..bar") // same as config["foo"][""]["bar"]
|
||||
/// toml::at_path(config, ".foo.bar") // same as config[""]["foo"]["bar"]
|
||||
/// \ecpp
|
||||
/// <br>
|
||||
/// Additionally, TOML allows '.' (period) characters to appear in keys if they are quoted strings.
|
||||
/// This function makes no allowance for this, instead treating all period characters as sub-table delimiters.
|
||||
/// If you have periods in your table keys, first consider:
|
||||
/// 1. Not doing that
|
||||
/// 2. Using node_view::operator[] instead.
|
||||
///
|
||||
/// \param root The root node from which the path will be traversed.
|
||||
/// \param path The "TOML path" to traverse.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, std::string_view path) noexcept;
|
||||
|
||||
/// \brief Returns a const view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::at_path(node&, std::string_view)
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, std::string_view path) noexcept;
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns a view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \see #toml::at_path(node&, std::string_view)
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, std::wstring_view path);
|
||||
|
||||
/// \brief Returns a const view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \see #toml::at_path(node&, std::string_view)
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, std::wstring_view path);
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
@@ -0,0 +1,290 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "at_path.hpp"
|
||||
#include "array.hpp"
|
||||
#include "table.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#if TOML_INT_CHARCONV
|
||||
#include <charconv>
|
||||
#else
|
||||
#include <sstream>
|
||||
#endif
|
||||
TOML_ENABLE_WARNINGS;
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV parse_path(const std::string_view path,
|
||||
void* const data,
|
||||
const parse_path_callback<std::string_view> on_key,
|
||||
const parse_path_callback<size_t> on_index)
|
||||
{
|
||||
// a blank string is a valid path; it's just one component representing the "" key
|
||||
if (path.empty())
|
||||
return on_key(data, ""sv);
|
||||
|
||||
size_t pos = 0;
|
||||
const auto end = path.length();
|
||||
bool prev_was_array_indexer = false;
|
||||
bool prev_was_dot = true; // invisible root 'dot'
|
||||
|
||||
while (pos < end)
|
||||
{
|
||||
// start of an array indexer
|
||||
if (path[pos] == '[')
|
||||
{
|
||||
// find first digit in index
|
||||
size_t index_start = pos + 1u;
|
||||
while (true)
|
||||
{
|
||||
if TOML_UNLIKELY(index_start >= path.length())
|
||||
return false;
|
||||
|
||||
const auto c = path[index_start];
|
||||
if TOML_LIKELY(c >= '0' && c <= '9')
|
||||
break;
|
||||
else if (c == ' ' || c == '\t')
|
||||
index_start++;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
TOML_ASSERT(path[index_start] >= '0');
|
||||
TOML_ASSERT(path[index_start] <= '9');
|
||||
|
||||
// find end of index (first non-digit character)
|
||||
size_t index_end = index_start + 1u;
|
||||
while (true)
|
||||
{
|
||||
// if an array indexer is missing the trailing ']' at the end of the string, permissively accept it
|
||||
if TOML_UNLIKELY(index_end >= path.length())
|
||||
break;
|
||||
|
||||
const auto c = path[index_end];
|
||||
if (c >= '0' && c <= '9')
|
||||
index_end++;
|
||||
else if (c == ']' || c == ' ' || c == '\t' || c == '.' || c == '[')
|
||||
break;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
TOML_ASSERT(path[index_end - 1u] >= '0');
|
||||
TOML_ASSERT(path[index_end - 1u] <= '9');
|
||||
|
||||
// move pos to after indexer (char after closing ']' or permissively EOL/subkey '.'/next opening '[')
|
||||
pos = index_end;
|
||||
while (true)
|
||||
{
|
||||
if TOML_UNLIKELY(pos >= path.length())
|
||||
break;
|
||||
|
||||
const auto c = path[pos];
|
||||
if (c == ']')
|
||||
{
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
else if TOML_UNLIKELY(c == '.' || c == '[')
|
||||
break;
|
||||
else if (c == '\t' || c == ' ')
|
||||
pos++;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// get array index substring
|
||||
auto index_str = path.substr(index_start, index_end - index_start);
|
||||
|
||||
// parse the actual array index to an integer type
|
||||
size_t index;
|
||||
if (index_str.length() == 1u)
|
||||
index = static_cast<size_t>(index_str[0] - '0');
|
||||
else
|
||||
{
|
||||
#if TOML_INT_CHARCONV
|
||||
|
||||
auto fc_result = std::from_chars(index_str.data(), index_str.data() + index_str.length(), index);
|
||||
if (fc_result.ec != std::errc{})
|
||||
return false;
|
||||
|
||||
#else
|
||||
|
||||
std::stringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss.write(index_str.data(), static_cast<std::streamsize>(index_str.length()));
|
||||
if (!(ss >> index))
|
||||
return false;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
prev_was_dot = false;
|
||||
prev_was_array_indexer = true;
|
||||
|
||||
if (!on_index(data, index))
|
||||
return false;
|
||||
}
|
||||
|
||||
// start of a new table child
|
||||
else if (path[pos] == '.')
|
||||
{
|
||||
// a dot immediately following another dot (or at the beginning of the string) is as if we'd asked
|
||||
// for an empty child in between, e.g.
|
||||
//
|
||||
// foo..bar
|
||||
//
|
||||
// is equivalent to
|
||||
//
|
||||
// "foo".""."bar"
|
||||
//
|
||||
if (prev_was_dot && !on_key(data, ""sv))
|
||||
return false;
|
||||
|
||||
pos++;
|
||||
prev_was_dot = true;
|
||||
prev_was_array_indexer = false;
|
||||
}
|
||||
|
||||
// an errant closing ']'
|
||||
else if TOML_UNLIKELY(path[pos] == ']')
|
||||
return false;
|
||||
|
||||
// some regular subkey
|
||||
else
|
||||
{
|
||||
const auto subkey_start = pos;
|
||||
const auto subkey_len =
|
||||
impl::min(path.find_first_of(".[]"sv, subkey_start + 1u), path.length()) - subkey_start;
|
||||
const auto subkey = path.substr(subkey_start, subkey_len);
|
||||
|
||||
// a regular subkey segment immediately after an array indexer is OK if it was all whitespace, e.g.:
|
||||
//
|
||||
// "foo[0] .bar"
|
||||
// ^^ skip this
|
||||
//
|
||||
// otherwise its an error (since it would have to be preceeded by a dot)
|
||||
if (prev_was_array_indexer)
|
||||
{
|
||||
auto non_ws = subkey.find_first_not_of(" \t");
|
||||
if (non_ws == std::string_view::npos)
|
||||
{
|
||||
pos += subkey_len;
|
||||
prev_was_dot = false;
|
||||
prev_was_array_indexer = false;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
pos += subkey_len;
|
||||
prev_was_dot = false;
|
||||
prev_was_array_indexer = false;
|
||||
|
||||
if (!on_key(data, subkey))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Last character was a '.', which implies an empty string key at the end of the path
|
||||
if (prev_was_dot && !on_key(data, ""sv))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, std::string_view path) noexcept
|
||||
{
|
||||
// early-exit sanity-checks
|
||||
if (root.is_value())
|
||||
return {};
|
||||
if (auto tbl = root.as_table(); tbl && tbl->empty())
|
||||
return {};
|
||||
if (auto arr = root.as_array(); arr && arr->empty())
|
||||
return {};
|
||||
|
||||
node* current = &root;
|
||||
|
||||
static constexpr auto on_key = [](void* data, std::string_view key) noexcept -> bool
|
||||
{
|
||||
auto& curr = *static_cast<node**>(data);
|
||||
TOML_ASSERT_ASSUME(curr);
|
||||
|
||||
const auto current_table = curr->as<table>();
|
||||
if (!current_table)
|
||||
return false;
|
||||
|
||||
curr = current_table->get(key);
|
||||
return curr != nullptr;
|
||||
};
|
||||
|
||||
static constexpr auto on_index = [](void* data, size_t index) noexcept -> bool
|
||||
{
|
||||
auto& curr = *static_cast<node**>(data);
|
||||
TOML_ASSERT_ASSUME(curr);
|
||||
|
||||
const auto current_array = curr->as<array>();
|
||||
if (!current_array)
|
||||
return false;
|
||||
|
||||
curr = current_array->get(index);
|
||||
return curr != nullptr;
|
||||
};
|
||||
|
||||
if (!impl::parse_path(path, ¤t, on_key, on_index))
|
||||
current = nullptr;
|
||||
|
||||
return node_view{ current };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, std::string_view path) noexcept
|
||||
{
|
||||
return node_view<const node>{ at_path(const_cast<node&>(root), path).node() };
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, std::wstring_view path)
|
||||
{
|
||||
// these are the same top-level checks from the narrow-string version;
|
||||
// they're hoisted up here to avoid doing the wide -> narrow conversion where it would not be necessary
|
||||
// (avoids an allocation)
|
||||
if (root.is_value())
|
||||
return {};
|
||||
if (auto tbl = root.as_table(); tbl && tbl->empty())
|
||||
return {};
|
||||
if (auto arr = root.as_array(); arr && arr->empty())
|
||||
return {};
|
||||
|
||||
return at_path(root, impl::narrow(path));
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, std::wstring_view path)
|
||||
{
|
||||
return node_view<const node>{ at_path(const_cast<node&>(root), path).node() };
|
||||
}
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,468 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "forward_declarations.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A local date.
|
||||
struct TOML_TRIVIAL_ABI date
|
||||
{
|
||||
/// \brief The year component.
|
||||
uint16_t year;
|
||||
|
||||
/// \brief The month component, from 1 - 12.
|
||||
uint8_t month;
|
||||
|
||||
/// \brief The day component, from 1 - 31.
|
||||
uint8_t day;
|
||||
|
||||
/// \brief Default constructor. Does not initialize the members.
|
||||
TOML_NODISCARD_CTOR
|
||||
date() noexcept = default;
|
||||
|
||||
/// \brief Constructs a date from individual date component values.
|
||||
TOML_CONSTRAINED_TEMPLATE((impl::all_integral<Y, M, D>), typename Y, typename M, typename D)
|
||||
TOML_NODISCARD_CTOR
|
||||
constexpr date(Y y, M m, D d) noexcept //
|
||||
: year{ static_cast<uint16_t>(y) },
|
||||
month{ static_cast<uint8_t>(m) },
|
||||
day{ static_cast<uint8_t>(d) }
|
||||
{}
|
||||
|
||||
/// \brief Equality operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator==(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return lhs.year == rhs.year //
|
||||
&& lhs.month == rhs.month //
|
||||
&& lhs.day == rhs.day;
|
||||
}
|
||||
|
||||
/// \brief Inequality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator!=(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
TOML_PURE_GETTER
|
||||
static constexpr uint32_t pack(const date& d) noexcept
|
||||
{
|
||||
return (static_cast<uint32_t>(d.year) << 16) | (static_cast<uint32_t>(d.month) << 8)
|
||||
| static_cast<uint32_t>(d.day);
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Less-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) < pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Less-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<=(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) <= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) > pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>=(const date& lhs, const date& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) >= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Prints a date out to a stream as `YYYY-MM-DD` (per RFC 3339).
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// std::cout << toml::date{ 1987, 3, 16 } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 1987-03-16
|
||||
/// \eout
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const date& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief A local time-of-day.
|
||||
struct TOML_TRIVIAL_ABI time
|
||||
{
|
||||
/// \brief The hour component, from 0 - 23.
|
||||
uint8_t hour;
|
||||
|
||||
/// \brief The minute component, from 0 - 59.
|
||||
uint8_t minute;
|
||||
|
||||
/// \brief The second component, from 0 - 59.
|
||||
uint8_t second;
|
||||
|
||||
/// \brief The fractional nanoseconds component, from 0 - 999999999.
|
||||
uint32_t nanosecond;
|
||||
|
||||
/// \brief Default constructor. Does not initialize the members.
|
||||
TOML_NODISCARD_CTOR
|
||||
time() noexcept = default;
|
||||
|
||||
/// \brief Constructs a time from individual time component values.
|
||||
TOML_CONSTRAINED_TEMPLATE((impl::all_integral<H, M, S, NS>),
|
||||
typename H,
|
||||
typename M,
|
||||
typename S = uint8_t,
|
||||
typename NS = uint32_t)
|
||||
TOML_NODISCARD_CTOR
|
||||
constexpr time(H h, M m, S s = S{}, NS ns = NS{}) noexcept //
|
||||
: hour{ static_cast<uint8_t>(h) },
|
||||
minute{ static_cast<uint8_t>(m) },
|
||||
second{ static_cast<uint8_t>(s) },
|
||||
nanosecond{ static_cast<uint32_t>(ns) }
|
||||
{}
|
||||
|
||||
/// \brief Equality operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator==(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return lhs.hour == rhs.hour //
|
||||
&& lhs.minute == rhs.minute //
|
||||
&& lhs.second == rhs.second //
|
||||
&& lhs.nanosecond == rhs.nanosecond;
|
||||
}
|
||||
|
||||
/// \brief Inequality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator!=(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
TOML_PURE_GETTER
|
||||
static constexpr uint64_t pack(const time& t) noexcept
|
||||
{
|
||||
return static_cast<uint64_t>(t.hour) << 48 | static_cast<uint64_t>(t.minute) << 40
|
||||
| static_cast<uint64_t>(t.second) << 32 | static_cast<uint64_t>(t.nanosecond);
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Less-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) < pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Less-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<=(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) <= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) > pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>=(const time& lhs, const time& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) >= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Prints a time out to a stream as `HH:MM:SS.FFFFFF` (per RFC 3339).
|
||||
/// \detail \cpp
|
||||
/// std::cout << toml::time{ 10, 20, 34 } << "\n";
|
||||
/// std::cout << toml::time{ 10, 20, 34, 500000000 } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 10:20:34
|
||||
/// 10:20:34.5
|
||||
/// \eout
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const time& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief A timezone offset.
|
||||
struct TOML_TRIVIAL_ABI time_offset
|
||||
{
|
||||
/// \brief Offset from UTC+0, in minutes.
|
||||
int16_t minutes;
|
||||
|
||||
/// \brief Default constructor. Does not initialize the members.
|
||||
TOML_NODISCARD_CTOR
|
||||
time_offset() noexcept = default;
|
||||
|
||||
/// \brief Constructs a timezone offset from individual hour and minute totals.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// std::cout << toml::time_offset{ 2, 30 } << "\n";
|
||||
/// std::cout << toml::time_offset{ -2, 30 } << "\n";
|
||||
/// std::cout << toml::time_offset{ -2, -30 } << "\n";
|
||||
/// std::cout << toml::time_offset{ 0, 0 } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// +02:30
|
||||
/// -01:30
|
||||
/// -02:30
|
||||
/// Z
|
||||
/// \eout
|
||||
///
|
||||
/// \tparam H An integral type.
|
||||
/// \tparam M An integral type.
|
||||
///
|
||||
/// \param h The total hours.
|
||||
/// \param m The total minutes.
|
||||
TOML_CONSTRAINED_TEMPLATE((impl::all_integral<H, M>), typename H, typename M)
|
||||
TOML_NODISCARD_CTOR
|
||||
constexpr time_offset(H h, M m) noexcept //
|
||||
: minutes{ static_cast<int16_t>(static_cast<impl::common_signed_type<H, M>>(h)
|
||||
* impl::common_signed_type<H, M>{ 60 }
|
||||
+ static_cast<impl::common_signed_type<H, M>>(m)) }
|
||||
{}
|
||||
|
||||
/// \brief Equality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator==(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes == rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Inequality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator!=(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes != rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Less-than operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator<(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes < rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Less-than-or-equal-to operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator<=(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes <= rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Greater-than operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator>(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes > rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Greater-than-or-equal-to operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator>=(time_offset lhs, time_offset rhs) noexcept
|
||||
{
|
||||
return lhs.minutes >= rhs.minutes;
|
||||
}
|
||||
|
||||
/// \brief Prints a time_offset out to a stream as `+-HH:MM or Z` (per RFC 3339).
|
||||
/// \detail \cpp
|
||||
/// std::cout << toml::time_offset{ 2, 30 } << "\n";
|
||||
/// std::cout << toml::time_offset{ 2, -30 } << "\n";
|
||||
/// std::cout << toml::time_offset{} << "\n";
|
||||
/// std::cout << toml::time_offset{ -2, 30 } << "\n";
|
||||
/// std::cout << toml::time_offset{ -2, -30 } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// +02:30
|
||||
/// +01:30
|
||||
/// Z
|
||||
/// -01:30
|
||||
/// -02:30
|
||||
/// \eout
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const time_offset& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt);
|
||||
|
||||
/// \brief A date-time.
|
||||
struct date_time
|
||||
{
|
||||
/// \brief The date component.
|
||||
toml::date date;
|
||||
|
||||
/// \brief The time component.
|
||||
toml::time time;
|
||||
|
||||
/// \brief The timezone offset component.
|
||||
///
|
||||
/// \remarks The date_time is said to be 'local' if the offset is empty.
|
||||
optional<toml::time_offset> offset;
|
||||
|
||||
/// \brief Default constructor. Does not initialize the members.
|
||||
TOML_NODISCARD_CTOR
|
||||
date_time() noexcept = default;
|
||||
|
||||
/// \brief Constructs a local date-time.
|
||||
///
|
||||
/// \param d The date component.
|
||||
/// \param t The time component.
|
||||
TOML_NODISCARD_CTOR
|
||||
constexpr date_time(const toml::date& d, const toml::time& t) noexcept //
|
||||
: date{ d },
|
||||
time{ t },
|
||||
offset{} // TINAE - icc bugfix
|
||||
{}
|
||||
|
||||
/// \brief Constructs a local date-time.
|
||||
///
|
||||
/// \param d The date component.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit constexpr date_time(const toml::date& d) noexcept //
|
||||
: date{ d },
|
||||
time{},
|
||||
offset{} // TINAE - icc bugfix
|
||||
{}
|
||||
|
||||
/// \brief Constructs a local date-time.
|
||||
///
|
||||
/// \param t The time component.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit constexpr date_time(const toml::time& t) noexcept //
|
||||
: date{},
|
||||
time{ t },
|
||||
offset{} // TINAE - icc bugfix
|
||||
{}
|
||||
|
||||
/// \brief Constructs an offset date-time.
|
||||
///
|
||||
/// \param d The date component.
|
||||
/// \param t The time component.
|
||||
/// \param off The timezone offset.
|
||||
TOML_NODISCARD_CTOR
|
||||
constexpr date_time(const toml::date& d, const toml::time& t, const toml::time_offset& off) noexcept
|
||||
: date{ d },
|
||||
time{ t },
|
||||
offset{ off }
|
||||
{}
|
||||
|
||||
/// \brief Returns true if this date_time does not contain timezone offset information.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
constexpr bool is_local() const noexcept
|
||||
{
|
||||
return !offset.has_value();
|
||||
}
|
||||
|
||||
/// \brief Equality operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator==(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
return lhs.date == rhs.date //
|
||||
&& lhs.time == rhs.time //
|
||||
&& lhs.offset == rhs.offset;
|
||||
}
|
||||
|
||||
/// \brief Inequality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator!=(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// \brief Less-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
if (lhs.date != rhs.date)
|
||||
return lhs.date < rhs.date;
|
||||
if (lhs.time != rhs.time)
|
||||
return lhs.time < rhs.time;
|
||||
return lhs.offset < rhs.offset;
|
||||
}
|
||||
|
||||
/// \brief Less-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<=(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
if (lhs.date != rhs.date)
|
||||
return lhs.date < rhs.date;
|
||||
if (lhs.time != rhs.time)
|
||||
return lhs.time < rhs.time;
|
||||
return lhs.offset <= rhs.offset;
|
||||
}
|
||||
|
||||
/// \brief Greater-than operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator>(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than-or-equal-to operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator>=(const date_time& lhs, const date_time& rhs) noexcept
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
/// \brief Prints a date_time out to a stream in RFC 3339 format.
|
||||
/// \detail \cpp
|
||||
/// std::cout << toml::date_time{ { 1987, 3, 16 }, { 10, 20, 34 } } << "\n";
|
||||
/// std::cout << toml::date_time{ { 1987, 3, 16 }, { 10, 20, 34 }, { -2, -30 } } << "\n";
|
||||
/// std::cout << toml::date_time{ { 1987, 3, 16 }, { 10, 20, 34 }, {} } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 1987-03-16T10:20:34
|
||||
/// 1987-03-16T10:20:34-02:30
|
||||
/// 1987-03-16T10:20:34Z
|
||||
/// \eout
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const date_time& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
TOML_ABI_NAMESPACE_END; // TOML_HAS_CUSTOM_OPTIONAL_TYPE
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,200 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "forward_declarations.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "header_start.hpp"
|
||||
/// \cond
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
struct formatter_constants
|
||||
{
|
||||
format_flags mandatory_flags;
|
||||
format_flags ignored_flags;
|
||||
|
||||
std::string_view float_pos_inf;
|
||||
std::string_view float_neg_inf;
|
||||
std::string_view float_nan;
|
||||
|
||||
std::string_view bool_true;
|
||||
std::string_view bool_false;
|
||||
};
|
||||
|
||||
struct formatter_config
|
||||
{
|
||||
format_flags flags;
|
||||
std::string_view indent;
|
||||
};
|
||||
|
||||
class TOML_EXPORTED_CLASS formatter
|
||||
{
|
||||
private:
|
||||
const node* source_;
|
||||
#if TOML_ENABLE_PARSER && !TOML_EXCEPTIONS
|
||||
const parse_result* result_;
|
||||
#endif
|
||||
const formatter_constants* constants_;
|
||||
formatter_config config_;
|
||||
size_t indent_columns_;
|
||||
format_flags int_format_mask_;
|
||||
std::ostream* stream_; //
|
||||
int indent_; // these are set in attach()
|
||||
bool naked_newline_; //
|
||||
|
||||
protected:
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const node& source() const noexcept
|
||||
{
|
||||
return *source_;
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
std::ostream& stream() const noexcept
|
||||
{
|
||||
return *stream_;
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
int indent() const noexcept
|
||||
{
|
||||
return indent_;
|
||||
}
|
||||
|
||||
void indent(int level) noexcept
|
||||
{
|
||||
indent_ = level;
|
||||
}
|
||||
|
||||
void increase_indent() noexcept
|
||||
{
|
||||
indent_++;
|
||||
}
|
||||
|
||||
void decrease_indent() noexcept
|
||||
{
|
||||
indent_--;
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
size_t indent_columns() const noexcept
|
||||
{
|
||||
return indent_columns_;
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool indent_array_elements() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::indent_array_elements);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool indent_sub_tables() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::indent_sub_tables);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool literal_strings_allowed() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::allow_literal_strings);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool multi_line_strings_allowed() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::allow_multi_line_strings);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool real_tabs_in_strings_allowed() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::allow_real_tabs_in_strings);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool unicode_strings_allowed() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::allow_unicode_strings);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool terse_kvps() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::terse_key_value_pairs);
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool force_multiline_arrays() const noexcept
|
||||
{
|
||||
return !!(config_.flags & format_flags::force_multiline_arrays);
|
||||
}
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void attach(std::ostream& stream) noexcept;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void detach() noexcept;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_newline(bool force = false);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_indent();
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_unformatted(char);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_unformatted(std::string_view);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_string(std::string_view str,
|
||||
bool allow_multi_line = true,
|
||||
bool allow_bare = false,
|
||||
bool allow_literal_whitespace = true);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<std::string>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<int64_t>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<double>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<bool>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<date>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<time>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const value<date_time>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_value(const node&, node_type);
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
bool dump_failed_parse_result();
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
formatter(const node*, const parse_result*, const formatter_constants&, const formatter_config&) noexcept;
|
||||
};
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
/// \endcond
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,523 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
//# {{
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "formatter.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "value.hpp"
|
||||
#include "table.hpp"
|
||||
#include "array.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include "parse_result.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
enum class TOML_CLOSED_FLAGS_ENUM formatted_string_traits : unsigned
|
||||
{
|
||||
none,
|
||||
line_breaks = 1u << 0, // \n
|
||||
tabs = 1u << 1, // \t
|
||||
control_chars = 1u << 2, // also includes non-ascii vertical whitespace
|
||||
single_quotes = 1u << 3,
|
||||
non_bare = 1u << 4, // anything not satisfying "is bare key character"
|
||||
non_ascii = 1u << 5, // any codepoint >= 128
|
||||
|
||||
all = (non_ascii << 1u) - 1u
|
||||
};
|
||||
TOML_MAKE_FLAGS(formatted_string_traits);
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
formatter::formatter(const node* source_node,
|
||||
const parse_result* source_pr,
|
||||
const formatter_constants& constants,
|
||||
const formatter_config& config) noexcept //
|
||||
#if TOML_ENABLE_PARSER && !TOML_EXCEPTIONS
|
||||
: source_{ source_pr && *source_pr ? &source_pr->table() : source_node },
|
||||
result_{ source_pr },
|
||||
#else
|
||||
: source_{ source_pr ? source_pr : source_node },
|
||||
#endif
|
||||
constants_{ &constants },
|
||||
config_{ config }
|
||||
{
|
||||
TOML_ASSERT_ASSUME(source_);
|
||||
|
||||
config_.flags = (config_.flags | constants_->mandatory_flags) & ~constants_->ignored_flags;
|
||||
|
||||
indent_columns_ = {};
|
||||
for (auto c : config_.indent)
|
||||
indent_columns_ += c == '\t' ? 4u : 1u;
|
||||
|
||||
int_format_mask_ = config_.flags
|
||||
& (format_flags::allow_binary_integers | format_flags::allow_octal_integers
|
||||
| format_flags::allow_hexadecimal_integers);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::attach(std::ostream & stream) noexcept
|
||||
{
|
||||
indent_ = {};
|
||||
naked_newline_ = true;
|
||||
stream_ = &stream;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::detach() noexcept
|
||||
{
|
||||
stream_ = nullptr;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_newline(bool force)
|
||||
{
|
||||
if (!naked_newline_ || force)
|
||||
{
|
||||
print_to_stream(*stream_, '\n');
|
||||
naked_newline_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_indent()
|
||||
{
|
||||
for (int i = 0; i < indent_; i++)
|
||||
{
|
||||
print_to_stream(*stream_, config_.indent);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_unformatted(char c)
|
||||
{
|
||||
print_to_stream(*stream_, c);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_unformatted(std::string_view str)
|
||||
{
|
||||
print_to_stream(*stream_, str);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_string(std::string_view str,
|
||||
bool allow_multi_line,
|
||||
bool allow_bare,
|
||||
bool allow_literal_whitespace)
|
||||
{
|
||||
if (str.empty())
|
||||
{
|
||||
print_unformatted(literal_strings_allowed() ? "''"sv : "\"\""sv);
|
||||
return;
|
||||
}
|
||||
|
||||
// pre-scan the string to determine how we should output it
|
||||
formatted_string_traits traits = {};
|
||||
|
||||
if (!allow_bare)
|
||||
traits |= formatted_string_traits::non_bare;
|
||||
bool unicode_allowed = unicode_strings_allowed();
|
||||
|
||||
// ascii fast path
|
||||
if (is_ascii(str.data(), str.length()))
|
||||
{
|
||||
for (auto c : str)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\n': traits |= formatted_string_traits::line_breaks; break;
|
||||
case '\t': traits |= formatted_string_traits::tabs; break;
|
||||
case '\'': traits |= formatted_string_traits::single_quotes; break;
|
||||
default:
|
||||
{
|
||||
if TOML_UNLIKELY(is_control_character(c))
|
||||
traits |= formatted_string_traits::control_chars;
|
||||
|
||||
if (!is_ascii_bare_key_character(static_cast<char32_t>(c)))
|
||||
traits |= formatted_string_traits::non_bare;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto all_ascii_traits =
|
||||
formatted_string_traits::all & ~formatted_string_traits::non_ascii;
|
||||
if (traits == all_ascii_traits)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// unicode slow path
|
||||
else
|
||||
{
|
||||
traits |= formatted_string_traits::non_ascii;
|
||||
utf8_decoder decoder;
|
||||
|
||||
// if the unicode is malformed just treat the string as a single-line non-literal and
|
||||
// escape all non-ascii characters (to ensure round-tripping and help with diagnostics)
|
||||
const auto bad_unicode = [&]() noexcept
|
||||
{
|
||||
traits &= ~formatted_string_traits::line_breaks;
|
||||
traits |= formatted_string_traits::control_chars | formatted_string_traits::non_bare;
|
||||
unicode_allowed = false;
|
||||
};
|
||||
|
||||
for (auto c : str)
|
||||
{
|
||||
decoder(c);
|
||||
|
||||
if TOML_UNLIKELY(decoder.error())
|
||||
{
|
||||
bad_unicode();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!decoder.has_code_point())
|
||||
continue;
|
||||
|
||||
switch (decoder.codepoint)
|
||||
{
|
||||
case U'\n': traits |= formatted_string_traits::line_breaks; break;
|
||||
case U'\t': traits |= formatted_string_traits::tabs; break;
|
||||
case U'\'': traits |= formatted_string_traits::single_quotes; break;
|
||||
default:
|
||||
{
|
||||
if TOML_UNLIKELY(is_control_character(decoder.codepoint)
|
||||
|| is_non_ascii_vertical_whitespace(decoder.codepoint))
|
||||
traits |= formatted_string_traits::control_chars;
|
||||
|
||||
if (!is_bare_key_character(decoder.codepoint))
|
||||
traits |= formatted_string_traits::non_bare;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder.needs_more_input())
|
||||
bad_unicode();
|
||||
}
|
||||
|
||||
// strings with line breaks, tabs, and single-quotes can't be bare
|
||||
if (!!(traits
|
||||
& (formatted_string_traits::line_breaks | formatted_string_traits::tabs
|
||||
| formatted_string_traits::single_quotes)))
|
||||
traits |= formatted_string_traits::non_bare;
|
||||
|
||||
// if the string meets the requirements of being 'bare' we can emit a bare string
|
||||
// (bare strings are composed of letters and numbers; no whitespace, control chars, quotes, etc)
|
||||
if (!(traits & formatted_string_traits::non_bare)
|
||||
&& (!(traits & formatted_string_traits::non_ascii) || unicode_allowed))
|
||||
{
|
||||
print_unformatted(str);
|
||||
return;
|
||||
}
|
||||
const auto real_tabs_allowed = allow_literal_whitespace && real_tabs_in_strings_allowed();
|
||||
|
||||
// determine if this should be a multi-line string (triple-quotes)
|
||||
const auto multi_line = allow_literal_whitespace //
|
||||
&& allow_multi_line //
|
||||
&& multi_line_strings_allowed() //
|
||||
&& !!(traits & formatted_string_traits::line_breaks);
|
||||
|
||||
// determine if this should be a literal string (single-quotes with no escaping)
|
||||
const auto literal = literal_strings_allowed() //
|
||||
&& !(traits & formatted_string_traits::control_chars) //
|
||||
&& (!(traits & formatted_string_traits::single_quotes) || multi_line) //
|
||||
&& (!(traits & formatted_string_traits::tabs) || real_tabs_allowed) //
|
||||
&& (!(traits & formatted_string_traits::line_breaks) || multi_line) //
|
||||
&& (!(traits & formatted_string_traits::non_ascii) || unicode_allowed);
|
||||
|
||||
// literal strings (single quotes, no escape codes)
|
||||
if (literal)
|
||||
{
|
||||
const auto quot = multi_line ? R"(''')"sv : R"(')"sv;
|
||||
print_unformatted(quot);
|
||||
print_unformatted(str);
|
||||
print_unformatted(quot);
|
||||
return;
|
||||
}
|
||||
|
||||
// anything from here down is a non-literal string, so requires iteration and escaping.
|
||||
print_unformatted(multi_line ? R"(""")"sv : R"(")"sv);
|
||||
|
||||
// ascii fast path
|
||||
if (!(traits & formatted_string_traits::non_ascii))
|
||||
{
|
||||
for (auto c : str)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': print_to_stream(*stream_, R"(\")"sv); break;
|
||||
case '\\': print_to_stream(*stream_, R"(\\)"sv); break;
|
||||
case '\x7F': print_to_stream(*stream_, R"(\u007F)"sv); break;
|
||||
case '\t': print_to_stream(*stream_, real_tabs_allowed ? "\t"sv : R"(\t)"sv); break;
|
||||
case '\n': print_to_stream(*stream_, multi_line ? "\n"sv : R"(\n)"sv); break;
|
||||
default:
|
||||
{
|
||||
// control characters from lookup table
|
||||
if TOML_UNLIKELY(c >= '\x00' && c <= '\x1F')
|
||||
print_to_stream(*stream_, control_char_escapes[c]);
|
||||
|
||||
// regular characters
|
||||
else
|
||||
print_to_stream(*stream_, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unicode slow path
|
||||
else
|
||||
{
|
||||
utf8_decoder decoder;
|
||||
const char* cp_start = str.data();
|
||||
const char* cp_end = cp_start;
|
||||
for (auto c : str)
|
||||
{
|
||||
decoder(c);
|
||||
cp_end++;
|
||||
|
||||
// if the decoder encounters malformed unicode just emit raw bytes and
|
||||
if (decoder.error())
|
||||
{
|
||||
while (cp_start != cp_end)
|
||||
{
|
||||
print_to_stream(*stream_, R"(\u00)"sv);
|
||||
print_to_stream(*stream_,
|
||||
static_cast<uint8_t>(*cp_start),
|
||||
value_flags::format_as_hexadecimal,
|
||||
2);
|
||||
cp_start++;
|
||||
}
|
||||
decoder.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!decoder.has_code_point())
|
||||
continue;
|
||||
|
||||
switch (decoder.codepoint)
|
||||
{
|
||||
case U'"': print_to_stream(*stream_, R"(\")"sv); break;
|
||||
case U'\\': print_to_stream(*stream_, R"(\\)"sv); break;
|
||||
case U'\x7F': print_to_stream(*stream_, R"(\u007F)"sv); break;
|
||||
case U'\t': print_to_stream(*stream_, real_tabs_allowed ? "\t"sv : R"(\t)"sv); break;
|
||||
case U'\n': print_to_stream(*stream_, multi_line ? "\n"sv : R"(\n)"sv); break;
|
||||
default:
|
||||
{
|
||||
// control characters from lookup table
|
||||
if TOML_UNLIKELY(decoder.codepoint <= U'\x1F')
|
||||
print_to_stream(*stream_,
|
||||
control_char_escapes[static_cast<uint_least32_t>(decoder.codepoint)]);
|
||||
|
||||
// escaped unicode characters
|
||||
else if (decoder.codepoint > U'\x7F'
|
||||
&& (!unicode_allowed || is_non_ascii_vertical_whitespace(decoder.codepoint)))
|
||||
{
|
||||
if (static_cast<uint_least32_t>(decoder.codepoint) > 0xFFFFu)
|
||||
{
|
||||
print_to_stream(*stream_, R"(\U)"sv);
|
||||
print_to_stream(*stream_,
|
||||
static_cast<uint_least32_t>(decoder.codepoint),
|
||||
value_flags::format_as_hexadecimal,
|
||||
8);
|
||||
}
|
||||
else
|
||||
{
|
||||
print_to_stream(*stream_, R"(\u)"sv);
|
||||
print_to_stream(*stream_,
|
||||
static_cast<uint_least32_t>(decoder.codepoint),
|
||||
value_flags::format_as_hexadecimal,
|
||||
4);
|
||||
}
|
||||
}
|
||||
|
||||
// regular characters
|
||||
else
|
||||
print_to_stream(*stream_, cp_start, static_cast<size_t>(cp_end - cp_start));
|
||||
}
|
||||
}
|
||||
|
||||
cp_start = cp_end;
|
||||
}
|
||||
}
|
||||
|
||||
print_unformatted(multi_line ? R"(""")"sv : R"(")"sv);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<std::string>& val)
|
||||
{
|
||||
print_string(val.get());
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<int64_t>& val)
|
||||
{
|
||||
naked_newline_ = false;
|
||||
|
||||
if (*val >= 0 && !!int_format_mask_)
|
||||
{
|
||||
static constexpr auto value_flags_mask =
|
||||
value_flags::format_as_binary | value_flags::format_as_octal | value_flags::format_as_hexadecimal;
|
||||
|
||||
const auto fmt = val.flags() & value_flags_mask;
|
||||
switch (fmt)
|
||||
{
|
||||
case value_flags::format_as_binary:
|
||||
if (!!(int_format_mask_ & format_flags::allow_binary_integers))
|
||||
{
|
||||
print_to_stream(*stream_, "0b"sv);
|
||||
print_to_stream(*stream_, *val, fmt);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case value_flags::format_as_octal:
|
||||
if (!!(int_format_mask_ & format_flags::allow_octal_integers))
|
||||
{
|
||||
print_to_stream(*stream_, "0o"sv);
|
||||
print_to_stream(*stream_, *val, fmt);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case value_flags::format_as_hexadecimal:
|
||||
if (!!(int_format_mask_ & format_flags::allow_hexadecimal_integers))
|
||||
{
|
||||
print_to_stream(*stream_, "0x"sv);
|
||||
print_to_stream(*stream_, *val, fmt);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to decimal
|
||||
print_to_stream(*stream_, *val);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<double>& val)
|
||||
{
|
||||
const std::string_view* inf_nan = nullptr;
|
||||
switch (fpclassify(*val))
|
||||
{
|
||||
case fp_class::neg_inf: inf_nan = &constants_->float_neg_inf; break;
|
||||
case fp_class::pos_inf: inf_nan = &constants_->float_pos_inf; break;
|
||||
case fp_class::nan: inf_nan = &constants_->float_nan; break;
|
||||
case fp_class::ok:
|
||||
print_to_stream(*stream_,
|
||||
*val,
|
||||
value_flags::none,
|
||||
!!(config_.flags & format_flags::relaxed_float_precision));
|
||||
break;
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
if (inf_nan)
|
||||
{
|
||||
if (!!(config_.flags & format_flags::quote_infinities_and_nans))
|
||||
print_to_stream_bookended(*stream_, *inf_nan, '"');
|
||||
else
|
||||
print_to_stream(*stream_, *inf_nan);
|
||||
}
|
||||
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<bool>& val)
|
||||
{
|
||||
print_unformatted(*val ? constants_->bool_true : constants_->bool_false);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<date>& val)
|
||||
{
|
||||
if (!!(config_.flags & format_flags::quote_dates_and_times))
|
||||
print_to_stream_bookended(*stream_, *val, literal_strings_allowed() ? '\'' : '"');
|
||||
else
|
||||
print_to_stream(*stream_, *val);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<time>& val)
|
||||
{
|
||||
if (!!(config_.flags & format_flags::quote_dates_and_times))
|
||||
print_to_stream_bookended(*stream_, *val, literal_strings_allowed() ? '\'' : '"');
|
||||
else
|
||||
print_to_stream(*stream_, *val);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print(const value<date_time>& val)
|
||||
{
|
||||
if (!!(config_.flags & format_flags::quote_dates_and_times))
|
||||
print_to_stream_bookended(*stream_, *val, literal_strings_allowed() ? '\'' : '"');
|
||||
else
|
||||
print_to_stream(*stream_, *val);
|
||||
naked_newline_ = false;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void formatter::print_value(const node& val_node, node_type type)
|
||||
{
|
||||
TOML_ASSUME(type > node_type::array);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::string: print(*reinterpret_cast<const value<std::string>*>(&val_node)); break;
|
||||
case node_type::integer: print(*reinterpret_cast<const value<int64_t>*>(&val_node)); break;
|
||||
case node_type::floating_point: print(*reinterpret_cast<const value<double>*>(&val_node)); break;
|
||||
case node_type::boolean: print(*reinterpret_cast<const value<bool>*>(&val_node)); break;
|
||||
case node_type::date: print(*reinterpret_cast<const value<date>*>(&val_node)); break;
|
||||
case node_type::time: print(*reinterpret_cast<const value<time>*>(&val_node)); break;
|
||||
case node_type::date_time: print(*reinterpret_cast<const value<date_time>*>(&val_node)); break;
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_PARSER && !TOML_EXCEPTIONS
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool formatter::dump_failed_parse_result()
|
||||
{
|
||||
if (result_ && !(*result_))
|
||||
{
|
||||
stream() << result_->error();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
TOML_ATTR(const)
|
||||
bool formatter::dump_failed_parse_result()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
//# {{
|
||||
#ifdef __INTELLISENSE__
|
||||
#include "preprocessor.hpp"
|
||||
#endif
|
||||
//# }}
|
||||
#ifdef _MSC_VER
|
||||
#pragma pop_macro("min")
|
||||
#pragma pop_macro("max")
|
||||
#ifndef __clang__
|
||||
#pragma inline_recursion(off)
|
||||
#endif
|
||||
#endif
|
||||
TOML_POP_WARNINGS;
|
||||
@@ -0,0 +1,15 @@
|
||||
//# {{
|
||||
#ifdef __INTELLISENSE__
|
||||
#include "preprocessor.hpp"
|
||||
#endif
|
||||
//# }}
|
||||
TOML_PUSH_WARNINGS;
|
||||
#ifdef _MSC_VER
|
||||
#ifndef __clang__
|
||||
#pragma inline_recursion(on)
|
||||
#endif
|
||||
#pragma push_macro("min")
|
||||
#pragma push_macro("max")
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
@@ -0,0 +1,142 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "formatter.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A wrapper for printing TOML objects out to a stream as formatted JSON.
|
||||
///
|
||||
/// \availability This class is only available when #TOML_ENABLE_FORMATTERS is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto some_toml = toml::parse(R"(
|
||||
/// [fruit]
|
||||
/// apple.color = "red"
|
||||
/// apple.taste.sweet = true
|
||||
///
|
||||
/// [fruit.apple.texture]
|
||||
/// smooth = true
|
||||
/// )"sv);
|
||||
/// std::cout << toml::json_formatter{ some_toml } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// {
|
||||
/// "fruit" : {
|
||||
/// "apple" : {
|
||||
/// "color" : "red",
|
||||
/// "taste" : {
|
||||
/// "sweet" : true
|
||||
/// },
|
||||
/// "texture" : {
|
||||
/// "smooth" : true
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// \eout
|
||||
class TOML_EXPORTED_CLASS json_formatter : impl::formatter
|
||||
{
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
using base = impl::formatter;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::table&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::array&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print();
|
||||
|
||||
static constexpr impl::formatter_constants constants = {
|
||||
format_flags::quote_dates_and_times, // mandatory
|
||||
format_flags::allow_literal_strings | format_flags::allow_multi_line_strings, // ignored
|
||||
"Infinity"sv,
|
||||
"-Infinity"sv,
|
||||
"NaN"sv,
|
||||
"true"sv,
|
||||
"false"sv
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief The default flags for a json_formatter.
|
||||
static constexpr format_flags default_flags = constants.mandatory_flags //
|
||||
| format_flags::quote_infinities_and_nans //
|
||||
| format_flags::allow_unicode_strings //
|
||||
| format_flags::indentation;
|
||||
|
||||
/// \brief Constructs a JSON formatter and binds it to a TOML object.
|
||||
///
|
||||
/// \param source The source TOML object.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit json_formatter(const toml::node& source, format_flags flags = default_flags) noexcept
|
||||
: base{ &source, nullptr, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#if TOML_DOXYGEN || (TOML_ENABLE_PARSER && !TOML_EXCEPTIONS)
|
||||
|
||||
/// \brief Constructs a JSON formatter and binds it to a toml::parse_result.
|
||||
///
|
||||
/// \availability This constructor is only available when exceptions are disabled.
|
||||
///
|
||||
/// \attention Formatting a failed parse result will simply dump the error message out as-is.
|
||||
/// This will not be valid JSON, but at least gives you something to log or show up in diagnostics:
|
||||
/// \cpp
|
||||
/// std::cout << toml::json_formatter{ toml::parse("a = 'b'"sv) } // ok
|
||||
/// << "\n\n"
|
||||
/// << toml::json_formatter{ toml::parse("a = "sv) } // malformed
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
/// \out
|
||||
/// {
|
||||
/// "a" : "b"
|
||||
/// }
|
||||
///
|
||||
/// Error while parsing key-value pair: encountered end-of-file
|
||||
/// (error occurred at line 1, column 5)
|
||||
/// \eout
|
||||
/// Use the library with exceptions if you want to avoid this scenario.
|
||||
///
|
||||
/// \param result The parse result.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit json_formatter(const toml::parse_result& result, format_flags flags = default_flags) noexcept
|
||||
: base{ nullptr, &result, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as JSON.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, json_formatter& rhs)
|
||||
{
|
||||
rhs.attach(lhs);
|
||||
rhs.print();
|
||||
rhs.detach();
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as JSON (rvalue overload).
|
||||
friend std::ostream& operator<<(std::ostream& lhs, json_formatter&& rhs)
|
||||
{
|
||||
return lhs << rhs; // as lvalue
|
||||
}
|
||||
};
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,121 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
//# {{
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "json_formatter.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "table.hpp"
|
||||
#include "array.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void json_formatter::print(const toml::table& tbl)
|
||||
{
|
||||
if (tbl.empty())
|
||||
{
|
||||
print_unformatted("{}"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
print_unformatted('{');
|
||||
|
||||
if (indent_sub_tables())
|
||||
increase_indent();
|
||||
bool first = false;
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
if (first)
|
||||
print_unformatted(',');
|
||||
first = true;
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
|
||||
print_string(k.str(), false);
|
||||
if (terse_kvps())
|
||||
print_unformatted(":"sv);
|
||||
else
|
||||
print_unformatted(" : "sv);
|
||||
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
if (indent_sub_tables())
|
||||
decrease_indent();
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
|
||||
print_unformatted('}');
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void json_formatter::print(const toml::array& arr)
|
||||
{
|
||||
if (arr.empty())
|
||||
{
|
||||
print_unformatted("[]"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
print_unformatted('[');
|
||||
if (indent_array_elements())
|
||||
increase_indent();
|
||||
for (size_t i = 0; i < arr.size(); i++)
|
||||
{
|
||||
if (i > 0u)
|
||||
print_unformatted(',');
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
|
||||
auto& v = arr[i];
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
if (indent_array_elements())
|
||||
decrease_indent();
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
print_unformatted(']');
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void json_formatter::print()
|
||||
{
|
||||
if (dump_failed_parse_result())
|
||||
return;
|
||||
|
||||
switch (auto source_type = source().type())
|
||||
{
|
||||
case node_type::table: print(*reinterpret_cast<const table*>(&source())); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&source())); break;
|
||||
default: print_value(source(), source_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,335 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "source_region.hpp"
|
||||
#include "std_utility.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A key parsed from a TOML document.
|
||||
///
|
||||
/// \detail These are used as the internal keys for a toml::table: \cpp
|
||||
/// const toml::table tbl = R"(
|
||||
/// a = 1
|
||||
/// b = 2
|
||||
/// c = 3
|
||||
/// )"_toml;
|
||||
///
|
||||
/// for (auto&& [k, v] : tbl)
|
||||
/// std::cout << "key '"sv << k << "' defined at "sv << k.source() << "\n";
|
||||
/// \ecpp
|
||||
/// \out
|
||||
/// key 'a' defined at line 2, column 5
|
||||
/// key 'b' defined at line 3, column 7
|
||||
/// key 'c' defined at line 4, column 9
|
||||
/// \eout
|
||||
class key
|
||||
{
|
||||
private:
|
||||
std::string key_;
|
||||
source_region source_;
|
||||
|
||||
public:
|
||||
/// \brief Default constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
key() noexcept = default;
|
||||
|
||||
/// \brief Constructs a key from a string view and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::string_view k, source_region&& src = {}) //
|
||||
: key_{ k },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a string view and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::string_view k, const source_region& src) //
|
||||
: key_{ k },
|
||||
source_{ src }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a string and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::string&& k, source_region&& src = {}) noexcept //
|
||||
: key_{ std::move(k) },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a string and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::string&& k, const source_region& src) noexcept //
|
||||
: key_{ std::move(k) },
|
||||
source_{ src }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a c-string and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(const char* k, source_region&& src = {}) //
|
||||
: key_{ k },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a c-string view and source region.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(const char* k, const source_region& src) //
|
||||
: key_{ k },
|
||||
source_{ src }
|
||||
{}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Constructs a key from a wide string view and source region.
|
||||
///
|
||||
/// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::wstring_view k, source_region&& src = {}) //
|
||||
: key_{ impl::narrow(k) },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
/// \brief Constructs a key from a wide string and source region.
|
||||
///
|
||||
/// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit key(std::wstring_view k, const source_region& src) //
|
||||
: key_{ impl::narrow(k) },
|
||||
source_{ src }
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
/// \name String operations
|
||||
/// @{
|
||||
|
||||
/// \brief Returns a view of the key's underlying string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
std::string_view str() const noexcept
|
||||
{
|
||||
return std::string_view{ key_ };
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the key's underlying string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
/*implicit*/ operator std::string_view() const noexcept
|
||||
{
|
||||
return str();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the key's underlying string is empty.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return key_.empty();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the start of the key's underlying string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const char* data() const noexcept
|
||||
{
|
||||
return key_.data();
|
||||
}
|
||||
|
||||
/// \brief Returns the length of the key's underlying string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
size_t length() const noexcept
|
||||
{
|
||||
return key_.length();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Metadata
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the source region responsible for specifying this key during parsing.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const source_region& source() const noexcept
|
||||
{
|
||||
return source_;
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Equality and Comparison
|
||||
/// \attention These operations only compare the underlying strings; source regions are ignored for the purposes of all comparison!
|
||||
/// @{
|
||||
|
||||
/// \brief Returns true if `lhs.str() == rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator==(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ == rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() != rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator!=(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ != rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() < rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ < rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() <= rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<=(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ <= rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() > rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ > rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() >= rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>=(const key& lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs.key_ >= rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() == rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator==(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ == rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() != rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator!=(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ != rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() < rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ < rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() <= rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<=(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ <= rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() > rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ > rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs.str() >= rhs`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>=(const key& lhs, std::string_view rhs) noexcept
|
||||
{
|
||||
return lhs.key_ >= rhs;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs == rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator==(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs == rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs != rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator!=(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs != rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs < rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs < rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs <= rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator<=(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs <= rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs > rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs > rhs.key_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if `lhs >= rhs.str()`.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator>=(std::string_view lhs, const key& rhs) noexcept
|
||||
{
|
||||
return lhs >= rhs.key_;
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Iteration
|
||||
/// @{
|
||||
|
||||
/// \brief A const iterator for iterating over the characters in the key.
|
||||
using const_iterator = const char*;
|
||||
|
||||
/// \brief A const iterator for iterating over the characters in the key.
|
||||
using iterator = const_iterator;
|
||||
|
||||
/// \brief Returns an iterator to the first character in the key's backing string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return key_.data();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to one-past-the-last character in the key's backing string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return key_.data() + key_.length();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \brief Prints the key's underlying string out to the stream.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const key& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs.key_);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Metafunction for determining if a type is, or is a reference to, a toml::key.
|
||||
template <typename T>
|
||||
inline constexpr bool is_key = std::is_same_v<impl::remove_cvref<T>, toml::key>;
|
||||
|
||||
/// \brief Metafunction for determining if a type is, or is a reference to, a toml::key,
|
||||
/// or is implicitly or explicitly convertible to one.
|
||||
template <typename T>
|
||||
inline constexpr bool is_key_or_convertible = is_key<T> //
|
||||
|| impl::is_constructible_or_convertible<toml::key, T>;
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,182 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "forward_declarations.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
/// \cond
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
TOML_ATTR(returns_nonnull)
|
||||
auto* make_node_impl_specialized(T && val, [[maybe_unused]] value_flags flags)
|
||||
{
|
||||
using unwrapped_type = unwrap_node<remove_cvref<T>>;
|
||||
static_assert(!std::is_same_v<unwrapped_type, node>);
|
||||
static_assert(!is_node_view<unwrapped_type>);
|
||||
|
||||
// arrays + tables - invoke copy/move ctor
|
||||
if constexpr (is_one_of<unwrapped_type, array, table>)
|
||||
{
|
||||
return new unwrapped_type(static_cast<T&&>(val));
|
||||
}
|
||||
|
||||
// values
|
||||
else
|
||||
{
|
||||
using native_type = native_type_of<unwrapped_type>;
|
||||
using value_type = value<native_type>;
|
||||
|
||||
value_type* out;
|
||||
|
||||
// copy/move ctor
|
||||
if constexpr (std::is_same_v<remove_cvref<T>, value_type>)
|
||||
{
|
||||
out = new value_type{ static_cast<T&&>(val), flags };
|
||||
}
|
||||
|
||||
// creating from raw value
|
||||
else
|
||||
{
|
||||
static_assert(!is_wide_string<T> || TOML_ENABLE_WINDOWS_COMPAT,
|
||||
"Instantiating values from wide-character strings is only "
|
||||
"supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled.");
|
||||
|
||||
if constexpr (!is_losslessly_convertible_to_native<unwrapped_type>)
|
||||
{
|
||||
if constexpr (std::is_same_v<native_type, int64_t>)
|
||||
static_assert(always_false<T>,
|
||||
"Integral value initializers must be losslessly convertible to int64_t");
|
||||
else if constexpr (std::is_same_v<native_type, double>)
|
||||
static_assert(always_false<T>,
|
||||
"Floating-point value initializers must be losslessly convertible to double");
|
||||
else
|
||||
static_assert(
|
||||
always_false<T>,
|
||||
"Value initializers must be losslessly convertible to one of the TOML value types");
|
||||
}
|
||||
|
||||
if constexpr (is_wide_string<T>)
|
||||
{
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
out = new value_type{ narrow(static_cast<T&&>(val)) };
|
||||
#else
|
||||
static_assert(always_false<T>, "Evaluated unreachable branch!");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
out = new value_type{ static_cast<T&&>(val) };
|
||||
|
||||
if (flags != preserve_source_value_flags)
|
||||
out->flags(flags);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
auto* make_node_impl(T && val, value_flags flags = preserve_source_value_flags)
|
||||
{
|
||||
using unwrapped_type = unwrap_node<remove_cvref<T>>;
|
||||
if constexpr (std::is_same_v<unwrapped_type, node> || is_node_view<unwrapped_type>)
|
||||
{
|
||||
if constexpr (is_node_view<unwrapped_type>)
|
||||
{
|
||||
if (!val)
|
||||
return static_cast<toml::node*>(nullptr);
|
||||
}
|
||||
|
||||
return static_cast<T&&>(val).visit(
|
||||
[flags](auto&& concrete) {
|
||||
return static_cast<toml::node*>(
|
||||
make_node_impl_specialized(static_cast<decltype(concrete)&&>(concrete), flags));
|
||||
});
|
||||
}
|
||||
else
|
||||
return make_node_impl_specialized(static_cast<T&&>(val), flags);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
auto* make_node_impl(inserter<T> && val, value_flags flags = preserve_source_value_flags)
|
||||
{
|
||||
return make_node_impl(static_cast<T&&>(val.value), flags);
|
||||
}
|
||||
|
||||
template <typename T, bool = (is_node<T> || is_node_view<T> || is_value<T> || can_partially_represent_native<T>)>
|
||||
struct inserted_type_of_
|
||||
{
|
||||
using type = std::remove_pointer_t<decltype(make_node_impl(std::declval<T>()))>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct inserted_type_of_<inserter<T>, false>
|
||||
{
|
||||
using type = typename inserted_type_of_<remove_cvref<T>>::type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct inserted_type_of_<T, false>
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
node_ptr make_node(T && val, value_flags flags = preserve_source_value_flags)
|
||||
{
|
||||
return node_ptr{ make_node_impl(static_cast<T&&>(val), flags) };
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
struct emplaced_type_of_
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct emplaced_type_of_<T>
|
||||
{
|
||||
using type = std::conditional_t<is_one_of<T, node, node_view<node>, node_view<const node>>,
|
||||
void,
|
||||
typename inserted_type_of_<T>::type>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct emplaced_type_of_<inserter<T>>
|
||||
{
|
||||
using type = typename emplaced_type_of_<remove_cvref<T>>::type;
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
using emplaced_type_of = typename emplaced_type_of_<remove_cvref<T>...>::type;
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
/// \endcond
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief Metafunction for determining which node type would be constructed
|
||||
/// if an object of this type was inserted into a toml::table or toml::array.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// static_assert(std::is_same_v<toml::inserted_type_of<const char*>, toml::value<std::string>);
|
||||
/// static_assert(std::is_same_v<toml::inserted_type_of<int>, toml::value<int64_t>);
|
||||
/// static_assert(std::is_same_v<toml::inserted_type_of<float>, toml::value<double>);
|
||||
/// static_assert(std::is_same_v<toml::inserted_type_of<bool>, toml::value<bool>);
|
||||
/// \ecpp
|
||||
///
|
||||
/// \note This will return toml::node for nodes and node_views, even though a more specific node subclass
|
||||
/// would actually be inserted. There is no way around this in a compile-time metafunction.
|
||||
template <typename T>
|
||||
using inserted_type_of = POXY_IMPLEMENTATION_DETAIL(typename impl::inserted_type_of_<impl::remove_cvref<T>>::type);
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,141 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "node.hpp"
|
||||
#include "node_view.hpp"
|
||||
#include "at_path.hpp"
|
||||
#include "table.hpp"
|
||||
#include "array.hpp"
|
||||
#include "value.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node::node() noexcept = default;
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node::~node() noexcept = default;
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node::node(node && other) noexcept //
|
||||
: source_{ std::exchange(other.source_, {}) }
|
||||
{}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node::node(const node& /*other*/) noexcept
|
||||
{
|
||||
// does not copy source information - this is not an error
|
||||
//
|
||||
// see https://github.com/marzer/tomlplusplus/issues/49#issuecomment-665089577
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node& node::operator=(const node& /*rhs*/) noexcept
|
||||
{
|
||||
// does not copy source information - this is not an error
|
||||
//
|
||||
// see https://github.com/marzer/tomlplusplus/issues/49#issuecomment-665089577
|
||||
|
||||
source_ = {};
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node& node::operator=(node&& rhs) noexcept
|
||||
{
|
||||
if (&rhs != this)
|
||||
source_ = std::exchange(rhs.source_, {});
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> node::at_path(std::string_view path) noexcept
|
||||
{
|
||||
return toml::at_path(*this, path);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> node::at_path(std::string_view path) const noexcept
|
||||
{
|
||||
return toml::at_path(*this, path);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> node::at_path(const path& p) noexcept
|
||||
{
|
||||
return toml::at_path(*this, p);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> node::at_path(const path& p) const noexcept
|
||||
{
|
||||
return toml::at_path(*this, p);
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> node::at_path(std::wstring_view path)
|
||||
{
|
||||
return toml::at_path(*this, path);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> node::at_path(std::wstring_view path) const
|
||||
{
|
||||
return toml::at_path(*this, path);
|
||||
}
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> node::operator[](const path& p) noexcept
|
||||
{
|
||||
return toml::at_path(*this, p);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> node::operator[](const path& p) const noexcept
|
||||
{
|
||||
return toml::at_path(*this, p);
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV node_deep_equality(const node* lhs, const node* rhs) noexcept
|
||||
{
|
||||
// both same or both null
|
||||
if (lhs == rhs)
|
||||
return true;
|
||||
|
||||
// lhs null != rhs null or different types
|
||||
if ((!lhs != !rhs) || lhs->type() != rhs->type())
|
||||
return false;
|
||||
|
||||
return lhs->visit(
|
||||
[=](auto& l) noexcept
|
||||
{
|
||||
using concrete_type = remove_cvref<decltype(l)>;
|
||||
|
||||
return l == *(rhs->as<concrete_type>());
|
||||
});
|
||||
}
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,839 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "std_vector.hpp"
|
||||
#include "std_initializer_list.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "node.hpp"
|
||||
#include "header_start.hpp"
|
||||
TOML_DISABLE_ARITHMETIC_WARNINGS;
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A view of a node.
|
||||
///
|
||||
/// \detail A node_view is like a std::optional<toml::node&> (if such a construct were legal), with lots of
|
||||
/// toml-specific stuff built-in. It _may_ represent a node, and allows you to do many of the
|
||||
/// same operations that you'd do on nodes directly, as well as easily traversing the node tree by creating
|
||||
/// subviews (via node_view::operator[]). \cpp
|
||||
///
|
||||
/// auto tbl = toml::parse(R"(
|
||||
///
|
||||
/// title = "my hardware store"
|
||||
///
|
||||
/// [[products]]
|
||||
/// name = "Hammer"
|
||||
/// sku = 738594937
|
||||
/// keywords = [ "hammer", "construction", "build" ]
|
||||
///
|
||||
/// [[products]]
|
||||
/// name = "Nail"
|
||||
/// sku = 284758393
|
||||
/// color = "gray"
|
||||
///
|
||||
/// )"sv);
|
||||
///
|
||||
/// std::cout << tbl["title"] << "\n";
|
||||
/// std::cout << tbl["products"][0]["name"] << "\n";
|
||||
/// std::cout << tbl["products"][0]["keywords"] << "\n";
|
||||
/// std::cout << tbl["products"][0]["keywords"][2] << "\n";
|
||||
///
|
||||
/// tbl["products"][0]["keywords"].as_array()->push_back("heavy");
|
||||
/// std::cout << tbl["products"][0]["keywords"] << "\n";
|
||||
/// std::cout << "has product[2]: "sv << !!tbl["products"][2] << "\n";
|
||||
/// std::cout << "product[2]: "sv << tbl["products"][2] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// "my hardware store"
|
||||
/// "Hammer"
|
||||
/// [ "hammer", "construction", "build" ]
|
||||
/// "build"
|
||||
/// [ "hammer", "construction", "build", "heavy" ]
|
||||
/// has product[2]: false
|
||||
/// product[2]:
|
||||
/// \eout
|
||||
template <typename ViewedType>
|
||||
class TOML_TRIVIAL_ABI node_view
|
||||
{
|
||||
static_assert(impl::is_one_of<ViewedType, toml::node, const toml::node>,
|
||||
"A toml::node_view<> must wrap toml::node or const toml::node.");
|
||||
|
||||
public:
|
||||
/// \brief The node type being viewed - either `node` or `const node`.
|
||||
using viewed_type = ViewedType;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
friend class node_view;
|
||||
|
||||
mutable viewed_type* node_ = nullptr;
|
||||
|
||||
public:
|
||||
/// \brief Constructs an empty node view.
|
||||
TOML_NODISCARD_CTOR
|
||||
node_view() noexcept = default;
|
||||
|
||||
/// \brief Constructs node_view of a specific node.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit node_view(viewed_type* node) noexcept //
|
||||
: node_{ node }
|
||||
{}
|
||||
|
||||
/// \brief Constructs node_view of a specific node.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit node_view(viewed_type& node) noexcept //
|
||||
: node_{ &node }
|
||||
{}
|
||||
|
||||
/// \brief Copy constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
node_view(const node_view&) noexcept = default;
|
||||
|
||||
/// \brief Move constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
node_view(node_view&&) noexcept = default;
|
||||
|
||||
/// \brief Copy-assignment operator.
|
||||
node_view& operator=(const node_view&) & noexcept = default;
|
||||
|
||||
/// \brief Move-assignment operator.
|
||||
node_view& operator=(node_view&&) & noexcept = default;
|
||||
|
||||
/// \brief Returns true if the view references a node.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return node_ != nullptr;
|
||||
}
|
||||
|
||||
/// \brief Returns the node that's being referenced by the view.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
viewed_type* node() const noexcept
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
/// \name Type checks
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the type identifier for the viewed node.
|
||||
TOML_PURE_GETTER
|
||||
node_type type() const noexcept
|
||||
{
|
||||
return node_ ? node_->type() : node_type::none;
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::table.
|
||||
TOML_PURE_GETTER
|
||||
bool is_table() const noexcept
|
||||
{
|
||||
return node_ && node_->is_table();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::array.
|
||||
TOML_PURE_GETTER
|
||||
bool is_array() const noexcept
|
||||
{
|
||||
return node_ && node_->is_array();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_value() const noexcept
|
||||
{
|
||||
return node_ && node_->is_value();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<string>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_string() const noexcept
|
||||
{
|
||||
return node_ && node_->is_string();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<int64_t>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_integer() const noexcept
|
||||
{
|
||||
return node_ && node_->is_integer();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<double>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_floating_point() const noexcept
|
||||
{
|
||||
return node_ && node_->is_floating_point();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<int64_t> or toml::value<double>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_number() const noexcept
|
||||
{
|
||||
return node_ && node_->is_number();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<bool>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_boolean() const noexcept
|
||||
{
|
||||
return node_ && node_->is_boolean();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<date>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_date() const noexcept
|
||||
{
|
||||
return node_ && node_->is_date();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<time>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_time() const noexcept
|
||||
{
|
||||
return node_ && node_->is_time();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::value<date_time>.
|
||||
TOML_PURE_GETTER
|
||||
bool is_date_time() const noexcept
|
||||
{
|
||||
return node_ && node_->is_date_time();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a toml::array that contains only tables.
|
||||
TOML_PURE_GETTER
|
||||
bool is_array_of_tables() const noexcept
|
||||
{
|
||||
return node_ && node_->is_array_of_tables();
|
||||
}
|
||||
|
||||
/// \brief Checks if this view references a node of a specific type.
|
||||
///
|
||||
/// \tparam T A TOML node or value type.
|
||||
///
|
||||
/// \returns Returns true if the viewed node is an instance of the specified type.
|
||||
///
|
||||
/// \see toml::node::is()
|
||||
template <typename T>
|
||||
TOML_PURE_GETTER
|
||||
bool is() const noexcept
|
||||
{
|
||||
return node_ ? node_->template is<impl::unwrap_node<impl::remove_cvref<T>>>() : false;
|
||||
}
|
||||
|
||||
/// \brief Checks if the viewed node contains values/elements of only one type.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto cfg = toml::parse("arr = [ 1, 2, 3, 4.0 ]");
|
||||
///
|
||||
/// toml::node* nonmatch{};
|
||||
/// if (cfg["arr"].is_homogeneous(toml::node_type::integer, nonmatch))
|
||||
/// std::cout << "array was homogeneous"sv << "\n";
|
||||
/// else
|
||||
/// std::cout << "array was not homogeneous!\n"
|
||||
/// << "first non-match was a "sv << nonmatch->type() << " at " << nonmatch->source() << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// array was not homogeneous!
|
||||
/// first non-match was a floating-point at line 1, column 18
|
||||
/// \eout
|
||||
///
|
||||
/// \param ntype A TOML node type. <br>
|
||||
/// \conditional_return{toml::node_type::none} "is every element the same type?"
|
||||
/// \conditional_return{Anything else} "is every element one of these?"
|
||||
///
|
||||
/// \param first_nonmatch Reference to a pointer in which the address of the first non-matching element
|
||||
/// will be stored if the return value is false.
|
||||
///
|
||||
/// \returns True if the viewed node was homogeneous.
|
||||
///
|
||||
/// \remarks Always returns `false` if the view does not reference a node, or if the viewed node is
|
||||
/// an empty table or array.
|
||||
TOML_NODISCARD
|
||||
bool is_homogeneous(node_type ntype, viewed_type*& first_nonmatch) const noexcept
|
||||
{
|
||||
if (!node_)
|
||||
{
|
||||
first_nonmatch = {};
|
||||
return false;
|
||||
}
|
||||
return node_->is_homogeneous(ntype, first_nonmatch);
|
||||
}
|
||||
|
||||
/// \brief Checks if the viewed node contains values/elements of only one type.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto cfg = toml::parse("arr = [ 1, 2, 3 ]");
|
||||
/// std::cout << "homogenous: "sv << cfg["arr"].is_homogeneous(toml::node_type::none) << "\n";
|
||||
/// std::cout << "all floats: "sv << cfg["arr"].is_homogeneous(toml::node_type::floating_point) << "\n";
|
||||
/// std::cout << "all arrays: "sv << cfg["arr"].is_homogeneous(toml::node_type::array) << "\n";
|
||||
/// std::cout << "all ints: "sv << cfg["arr"].is_homogeneous(toml::node_type::integer) << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// homogeneous: true
|
||||
/// all floats: false
|
||||
/// all arrays: false
|
||||
/// all ints: true
|
||||
/// \eout
|
||||
///
|
||||
/// \param ntype A TOML node type. <br>
|
||||
/// \conditional_return{toml::node_type::none} "is every element the same type?"
|
||||
/// \conditional_return{Anything else} "is every element one of these?"
|
||||
///
|
||||
/// \returns True if the viewed node was homogeneous.
|
||||
///
|
||||
/// \remarks Always returns `false` if the view does not reference a node, or if the viewed node is
|
||||
/// an empty table or array.
|
||||
TOML_PURE_GETTER
|
||||
bool is_homogeneous(node_type ntype) const noexcept
|
||||
{
|
||||
return node_ ? node_->is_homogeneous(ntype) : false;
|
||||
}
|
||||
|
||||
/// \brief Checks if the viewed node contains values/elements of only one type.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto cfg = toml::parse("arr = [ 1, 2, 3 ]");
|
||||
/// std::cout << "homogenous: "sv << cfg["arr"].is_homogeneous() << "\n";
|
||||
/// std::cout << "all doubles: "sv << cfg["arr"].is_homogeneous<double>() << "\n";
|
||||
/// std::cout << "all arrays: "sv << cfg["arr"].is_homogeneous<toml::array>() << "\n";
|
||||
/// std::cout << "all integers: "sv << cfg["arr"].is_homogeneous<int64_t>() << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// homogeneous: true
|
||||
/// all floats: false
|
||||
/// all arrays: false
|
||||
/// all ints: true
|
||||
/// \eout
|
||||
///
|
||||
/// \tparam ElemType A TOML node or value type. <br>
|
||||
/// \conditional_return{Left as `void`} "is every element the same type?" <br>
|
||||
/// \conditional_return{Explicitly specified} "is every element a T?"
|
||||
///
|
||||
/// \returns True if the viewed node was homogeneous.
|
||||
///
|
||||
/// \remarks Always returns `false` if the view does not reference a node, or if the viewed node is
|
||||
/// an empty table or array.
|
||||
template <typename ElemType = void>
|
||||
TOML_PURE_GETTER
|
||||
bool is_homogeneous() const noexcept
|
||||
{
|
||||
return node_ ? node_->template is_homogeneous<impl::unwrap_node<impl::remove_cvref<ElemType>>>() : false;
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Type casts
|
||||
/// @{
|
||||
|
||||
/// \brief Gets a pointer to the viewed node as a more specific node type.
|
||||
///
|
||||
/// \tparam T The node type or TOML value type to cast to.
|
||||
///
|
||||
/// \returns A pointer to the node as the given type, or nullptr if it was a different type.
|
||||
///
|
||||
/// \see toml::node::as()
|
||||
template <typename T>
|
||||
TOML_PURE_GETTER
|
||||
auto* as() const noexcept
|
||||
{
|
||||
return node_ ? node_->template as<T>() : nullptr;
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::table, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_table() const noexcept
|
||||
{
|
||||
return as<table>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::array, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_array() const noexcept
|
||||
{
|
||||
return as<array>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<string>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_string() const noexcept
|
||||
{
|
||||
return as<std::string>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<int64_t>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_integer() const noexcept
|
||||
{
|
||||
return as<int64_t>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<double>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_floating_point() const noexcept
|
||||
{
|
||||
return as<double>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<bool>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_boolean() const noexcept
|
||||
{
|
||||
return as<bool>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<date>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_date() const noexcept
|
||||
{
|
||||
return as<date>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<time>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_time() const noexcept
|
||||
{
|
||||
return as<time>();
|
||||
}
|
||||
|
||||
/// \brief Returns a pointer to the viewed node as a toml::value<date_time>, if it is one.
|
||||
TOML_PURE_GETTER
|
||||
auto* as_date_time() const noexcept
|
||||
{
|
||||
return as<date_time>();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Value retrieval
|
||||
/// @{
|
||||
|
||||
/// \brief Gets the value contained by the referenced node.
|
||||
///
|
||||
/// \detail This function has 'exact' retrieval semantics; the only return value types allowed are the
|
||||
/// TOML native value types, or types that can losslessly represent a native value type (e.g.
|
||||
/// std::wstring on Windows).
|
||||
///
|
||||
/// \tparam T One of the native TOML value types, or a type capable of losslessly representing one.
|
||||
///
|
||||
/// \returns The underlying value if the node was a value of the
|
||||
/// matching type (or losslessly convertible to it), or an empty optional.
|
||||
///
|
||||
/// \see node_view::value()
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
optional<T> value_exact() const noexcept(impl::value_retrieval_is_nothrow<T>)
|
||||
{
|
||||
if (node_)
|
||||
return node_->template value_exact<T>();
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief Gets the value contained by the referenced node.
|
||||
///
|
||||
/// \detail This function has 'permissive' retrieval semantics; some value types are allowed
|
||||
/// to convert to others (e.g. retrieving a boolean as an integer), and the specified return value
|
||||
/// type can be any type where a reasonable conversion from a native TOML value exists
|
||||
/// (e.g. std::wstring on Windows). If the source value cannot be represented by
|
||||
/// the destination type, an empty optional is returned. See node::value() for examples.
|
||||
///
|
||||
/// \tparam T One of the native TOML value types, or a type capable of convertible to one.
|
||||
///
|
||||
/// \returns The underlying value if the node was a value of the matching type (or convertible to it)
|
||||
/// and within the range of the output type, or an empty optional.
|
||||
///
|
||||
/// \note If you want strict value retrieval semantics that do not allow for any type conversions,
|
||||
/// use node_view::value_exact() instead.
|
||||
///
|
||||
/// \see
|
||||
/// - node_view::value()
|
||||
/// - node_view::value_exact()
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
optional<T> value() const noexcept(impl::value_retrieval_is_nothrow<T>)
|
||||
{
|
||||
if (node_)
|
||||
return node_->template value<T>();
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief Gets the raw value contained by the referenced node, or a default.
|
||||
///
|
||||
/// \tparam T Default value type. Must be one of the native TOML value types,
|
||||
/// or convertible to it.
|
||||
/// \param default_value The default value to return if the node wasn't a value, wasn't the
|
||||
/// correct type, or no conversion was possible.
|
||||
///
|
||||
/// \returns The underlying value if the node was a value of the matching type (or convertible to it)
|
||||
/// and within the range of the output type, or the provided default.
|
||||
///
|
||||
/// \note This function has the same permissive retrieval semantics as node::value(). If you want strict
|
||||
/// value retrieval semantics that do not allow for any type conversions, use node_view::value_exact()
|
||||
/// instead.
|
||||
///
|
||||
/// \see
|
||||
/// - node_view::value()
|
||||
/// - node_view::value_exact()
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
auto value_or(T&& default_value) const noexcept(impl::value_retrieval_is_nothrow<T>)
|
||||
{
|
||||
using namespace ::toml::impl;
|
||||
|
||||
static_assert(!is_wide_string<T> || TOML_ENABLE_WINDOWS_COMPAT,
|
||||
"Retrieving values as wide-character strings is only "
|
||||
"supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled.");
|
||||
|
||||
if constexpr (is_wide_string<T>)
|
||||
{
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
if (node_)
|
||||
return node_->value_or(static_cast<T&&>(default_value));
|
||||
return std::wstring{ static_cast<T&&>(default_value) };
|
||||
|
||||
#else
|
||||
|
||||
static_assert(impl::always_false<T>, "Evaluated unreachable branch!");
|
||||
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
using value_type =
|
||||
std::conditional_t<std::is_pointer_v<std::decay_t<T>>,
|
||||
std::add_pointer_t<std::add_const_t<std::remove_pointer_t<std::decay_t<T>>>>,
|
||||
std::decay_t<T>>;
|
||||
|
||||
if (node_)
|
||||
return node_->value_or(static_cast<T&&>(default_value));
|
||||
if constexpr (std::is_pointer_v<value_type>)
|
||||
return value_type{ default_value };
|
||||
else
|
||||
return static_cast<T&&>(default_value);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Gets a raw reference to the viewed node's underlying data.
|
||||
///
|
||||
/// \warning This function is dangerous if used carelessly and **WILL** break your code if the
|
||||
/// node_view didn't reference a node, or the chosen value type doesn't match the node's
|
||||
/// actual type. In debug builds an assertion will fire when invalid accesses are attempted: \cpp
|
||||
///
|
||||
/// auto tbl = toml::parse(R"(
|
||||
/// min = 32
|
||||
/// max = 45
|
||||
/// )"sv);
|
||||
///
|
||||
/// int64_t& min_ref = tbl["min"].ref<int64_t>(); // matching type
|
||||
/// double& max_ref = tbl["max"].ref<double>(); // mismatched type, hits assert()
|
||||
/// int64_t& foo_ref = tbl["foo"].ref<int64_t>(); // nonexistent key, hits assert()
|
||||
/// \ecpp
|
||||
///
|
||||
/// \note Specifying explicit ref qualifiers acts as an explicit ref-category cast,
|
||||
/// whereas specifying explicit cv-ref qualifiers merges them with whatever
|
||||
/// the cv qualification of the viewed node is (to ensure cv-correctness is propagated), e.g.:
|
||||
/// | node_view | T | return type |
|
||||
/// |-----------------------|------------------------|------------------------------|
|
||||
/// | node_view<node> | std::string | std::string& |
|
||||
/// | node_view<node> | std::string&& | std::string&& |
|
||||
/// | node_view<const node> | volatile std::string | const volatile std::string& |
|
||||
/// | node_view<const node> | volatile std::string&& | const volatile std::string&& |
|
||||
///
|
||||
///
|
||||
/// \tparam T One of the TOML value types.
|
||||
///
|
||||
/// \returns A reference to the underlying data.
|
||||
template <typename T>
|
||||
TOML_PURE_INLINE_GETTER
|
||||
decltype(auto) ref() const noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(node_ && "toml::node_view::ref() called on a node_view that did not reference a node");
|
||||
return node_->template ref<T>();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Visitation
|
||||
/// @{
|
||||
|
||||
private:
|
||||
/// \cond
|
||||
template <typename Func>
|
||||
static constexpr bool visit_is_nothrow = noexcept(std::declval<viewed_type*>()->visit(std::declval<Func>()));
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Invokes a visitor on the viewed node based on its concrete type.
|
||||
///
|
||||
/// \remarks Has no effect if the view does not reference a node.
|
||||
///
|
||||
/// \see node::visit()
|
||||
template <typename Func>
|
||||
decltype(auto) visit(Func&& visitor) const noexcept(visit_is_nothrow<Func&&>)
|
||||
{
|
||||
using return_type = decltype(node_->visit(static_cast<Func&&>(visitor)));
|
||||
if (node_)
|
||||
return node_->visit(static_cast<Func&&>(visitor));
|
||||
if constexpr (!std::is_void_v<return_type>)
|
||||
return return_type{};
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Equality
|
||||
/// @{
|
||||
|
||||
public:
|
||||
/// \brief Returns true if the two views refer to nodes of the same type and value.
|
||||
template <typename T>
|
||||
TOML_PURE_GETTER
|
||||
friend bool operator==(const node_view& lhs, const node_view<T>& rhs) noexcept
|
||||
{
|
||||
return impl::node_deep_equality(lhs.node_, rhs.node_);
|
||||
}
|
||||
|
||||
/// \brief Returns true if the two views do not refer to nodes of the same type and value.
|
||||
template <typename T>
|
||||
TOML_PURE_GETTER
|
||||
friend bool operator!=(const node_view& lhs, const node_view<T>& rhs) noexcept
|
||||
{
|
||||
return !impl::node_deep_equality(lhs.node_, rhs.node_);
|
||||
}
|
||||
|
||||
/// \brief Returns true if the viewed node is a table with the same contents as RHS.
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs, const table& rhs) noexcept
|
||||
{
|
||||
if (lhs.node_ == &rhs)
|
||||
return true;
|
||||
const auto tbl = lhs.as<table>();
|
||||
return tbl && *tbl == rhs;
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const table&, );
|
||||
|
||||
/// \brief Returns true if the viewed node is an array with the same contents as RHS.
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs, const array& rhs) noexcept
|
||||
{
|
||||
if (lhs.node_ == &rhs)
|
||||
return true;
|
||||
const auto arr = lhs.as<array>();
|
||||
return arr && *arr == rhs;
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const array&, );
|
||||
|
||||
/// \brief Returns true if the viewed node is a value with the same value as RHS.
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs, const toml::value<T>& rhs) noexcept
|
||||
{
|
||||
if (lhs.node_ == &rhs)
|
||||
return true;
|
||||
const auto val = lhs.as<T>();
|
||||
return val && *val == rhs;
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const toml::value<T>&, template <typename T>);
|
||||
|
||||
/// \brief Returns true if the viewed node is a value with the same value as RHS.
|
||||
TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native<T>, typename T)
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs, const T& rhs) noexcept(!impl::is_wide_string<T>)
|
||||
{
|
||||
static_assert(!impl::is_wide_string<T> || TOML_ENABLE_WINDOWS_COMPAT,
|
||||
"Comparison with wide-character strings is only "
|
||||
"supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled.");
|
||||
|
||||
if constexpr (impl::is_wide_string<T>)
|
||||
{
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
return lhs == impl::narrow(rhs);
|
||||
#else
|
||||
static_assert(impl::always_false<T>, "Evaluated unreachable branch!");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto val = lhs.as<impl::native_type_of<T>>();
|
||||
return val && *val == rhs;
|
||||
}
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&,
|
||||
const T&,
|
||||
TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native<T>,
|
||||
typename T));
|
||||
|
||||
/// \brief Returns true if the viewed node is an array with the same contents as the RHS initializer list.
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs,
|
||||
const std::initializer_list<T>& rhs) noexcept(!impl::is_wide_string<T>)
|
||||
{
|
||||
const auto arr = lhs.as<array>();
|
||||
return arr && *arr == rhs;
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const std::initializer_list<T>&, template <typename T>);
|
||||
|
||||
/// \brief Returns true if the viewed node is an array with the same contents as the RHS vector.
|
||||
template <typename T>
|
||||
TOML_NODISCARD
|
||||
friend bool operator==(const node_view& lhs, const std::vector<T>& rhs) noexcept(!impl::is_wide_string<T>)
|
||||
{
|
||||
const auto arr = lhs.as<array>();
|
||||
return arr && *arr == rhs;
|
||||
}
|
||||
TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const std::vector<T>&, template <typename T>);
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Subviews
|
||||
/// @{
|
||||
|
||||
/// \brief Returns a view of the selected subnode.
|
||||
///
|
||||
/// \param key The key of the node to retrieve
|
||||
///
|
||||
/// \returns A view of the selected node if this node represented a table and it contained a
|
||||
/// value at the given key, or an empty view.
|
||||
TOML_NODISCARD
|
||||
node_view operator[](std::string_view key) const noexcept
|
||||
{
|
||||
if (auto tbl = this->as_table())
|
||||
return node_view{ tbl->get(key) };
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the selected subnode.
|
||||
///
|
||||
/// \param path A "TOML path" to the desired subnode
|
||||
///
|
||||
/// \returns A view of the selected node if this node represented a table and it contained a
|
||||
/// value at the given key, or an empty view.
|
||||
TOML_NODISCARD
|
||||
node_view operator[](const toml::path& path) const noexcept
|
||||
{
|
||||
return node_ ? node_->at_path(path) : node_view{};
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view at_path(std::string_view path) const noexcept
|
||||
{
|
||||
return node_ ? node_->at_path(path) : node_view{};
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(const toml::path&)
|
||||
TOML_NODISCARD
|
||||
node_view at_path(const toml::path& path) const noexcept
|
||||
{
|
||||
return node_ ? node_->at_path(path) : node_view{};
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns a view of the selected subnode.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \param key The key of the node to retrieve
|
||||
///
|
||||
/// \returns A view of the selected node if this node represented a table and it contained a
|
||||
/// value at the given key, or an empty view.
|
||||
TOML_NODISCARD
|
||||
node_view operator[](std::wstring_view key) const
|
||||
{
|
||||
if (auto tbl = this->as_table())
|
||||
return node_view{ tbl->get(key) };
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view at_path(std::wstring_view path) const
|
||||
{
|
||||
return node_ ? node_->at_path(path) : node_view{};
|
||||
}
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns a view of the selected subnode.
|
||||
///
|
||||
/// \param index The index of the node to retrieve
|
||||
///
|
||||
/// \returns A view of the selected node if this node represented an array and it contained a
|
||||
/// value at the given index, or an empty view.
|
||||
TOML_NODISCARD
|
||||
node_view operator[](size_t index) const noexcept
|
||||
{
|
||||
if (auto arr = this->as_array())
|
||||
return node_view{ arr->get(index) };
|
||||
return {};
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
/// \brief Prints the viewed node out to a stream.
|
||||
///
|
||||
/// \availability This operator is only available when #TOML_ENABLE_FORMATTERS is enabled.
|
||||
friend std::ostream& operator<<(std::ostream& os, const node_view& nv)
|
||||
{
|
||||
if (nv.node_)
|
||||
nv.node_->visit([&os](const auto& n) { os << n; });
|
||||
return os;
|
||||
}
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
/// \cond
|
||||
|
||||
template <typename T>
|
||||
node_view(const T&) -> node_view<const node>;
|
||||
|
||||
template <typename T>
|
||||
node_view(const T*) -> node_view<const node>;
|
||||
|
||||
template <typename T>
|
||||
node_view(T&) -> node_view<node>;
|
||||
|
||||
template <typename T>
|
||||
node_view(T*) -> node_view<node>;
|
||||
|
||||
/// \endcond
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
/// \cond
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
inline node::operator node_view<node>() noexcept
|
||||
{
|
||||
return node_view<node>{ this };
|
||||
}
|
||||
|
||||
inline node::operator node_view<const node>() const noexcept
|
||||
{
|
||||
return node_view<const node>{ this };
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
/// \endcond
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,139 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_PARSER
|
||||
|
||||
#include "std_except.hpp"
|
||||
#include "source_region.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
#if TOML_DOXYGEN || !TOML_EXCEPTIONS
|
||||
#define TOML_PARSE_ERROR_BASE
|
||||
#else
|
||||
#define TOML_PARSE_ERROR_BASE : public std::runtime_error
|
||||
#endif
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex);
|
||||
|
||||
/// \brief An error generated when parsing fails.
|
||||
///
|
||||
/// \remarks This class inherits from std::runtime_error when exceptions are enabled.
|
||||
/// The public interface is the same regardless of exception mode.
|
||||
class parse_error TOML_PARSE_ERROR_BASE
|
||||
{
|
||||
private:
|
||||
#if !TOML_EXCEPTIONS
|
||||
std::string description_;
|
||||
#endif
|
||||
source_region source_;
|
||||
|
||||
public:
|
||||
#if TOML_EXCEPTIONS
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_ATTR(nonnull)
|
||||
parse_error(const char* desc, source_region&& src) noexcept //
|
||||
: std::runtime_error{ desc },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_ATTR(nonnull)
|
||||
parse_error(const char* desc, const source_region& src) noexcept //
|
||||
: parse_error{ desc, source_region{ src } }
|
||||
{}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_ATTR(nonnull)
|
||||
parse_error(const char* desc, const source_position& position, const source_path_ptr& path = {}) noexcept
|
||||
: parse_error{ desc, source_region{ position, position, path } }
|
||||
{}
|
||||
|
||||
#else
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
parse_error(std::string&& desc, source_region&& src) noexcept //
|
||||
: description_{ std::move(desc) },
|
||||
source_{ std::move(src) }
|
||||
{}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
parse_error(std::string&& desc, const source_region& src) noexcept //
|
||||
: parse_error{ std::move(desc), source_region{ src } }
|
||||
{}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
parse_error(std::string&& desc, const source_position& position, const source_path_ptr& path = {}) noexcept
|
||||
: parse_error{ std::move(desc), source_region{ position, position, path } }
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Returns a textual description of the error.
|
||||
/// \remark The backing string is guaranteed to be null-terminated.
|
||||
TOML_NODISCARD
|
||||
std::string_view description() const noexcept
|
||||
{
|
||||
#if TOML_EXCEPTIONS
|
||||
return std::string_view{ what() };
|
||||
#else
|
||||
return description_;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// \brief Returns the region of the source document responsible for the error.
|
||||
TOML_NODISCARD
|
||||
const source_region& source() const noexcept
|
||||
{
|
||||
return source_;
|
||||
}
|
||||
|
||||
/// \brief Prints a parse_error to a stream.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// try
|
||||
/// {
|
||||
/// auto tbl = toml::parse("enabled = trUe"sv);
|
||||
/// }
|
||||
/// catch (const toml::parse_error & err)
|
||||
/// {
|
||||
/// std::cerr << "Parsing failed:\n"sv << err << "\n";
|
||||
/// }
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// Parsing failed:
|
||||
/// Encountered unexpected character while parsing boolean; expected 'true', saw 'trU'
|
||||
/// (error occurred at line 1, column 13)
|
||||
/// \eout
|
||||
///
|
||||
/// \tparam Char The output stream's underlying character type. Must be 1 byte in size.
|
||||
/// \param lhs The stream.
|
||||
/// \param rhs The parse_error.
|
||||
///
|
||||
/// \returns The input stream.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const parse_error& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs.description());
|
||||
impl::print_to_stream(lhs, "\n\t(error occurred at "sv);
|
||||
impl::print_to_stream(lhs, rhs.source());
|
||||
impl::print_to_stream(lhs, ")"sv);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#undef TOML_PARSE_ERROR_BASE
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_PARSER
|
||||
@@ -0,0 +1,499 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_DOXYGEN || (TOML_ENABLE_PARSER && !TOML_EXCEPTIONS)
|
||||
|
||||
#include "table.hpp"
|
||||
#include "parse_error.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_ABI_NAMESPACE_START(noex);
|
||||
|
||||
/// \brief The result of a parsing operation.
|
||||
///
|
||||
/// \availability <strong>This type only exists when exceptions are disabled.</strong>
|
||||
/// Otherwise parse_result is just an alias for toml::table: \cpp
|
||||
/// #if TOML_EXCEPTIONS
|
||||
/// using parse_result = table;
|
||||
/// #else
|
||||
/// class parse_result { // ...
|
||||
/// #endif
|
||||
/// \ecpp
|
||||
///
|
||||
/// \detail A parse_result is effectively a discriminated union containing either a toml::table
|
||||
/// or a toml::parse_error. Most member functions assume a particular one of these two states,
|
||||
/// and calling them when in the wrong state will cause errors (e.g. attempting to access the
|
||||
/// error object when parsing was successful). \cpp
|
||||
/// toml::parse_result result = toml::parse_file("config.toml");
|
||||
/// if (result)
|
||||
/// do_stuff_with_a_table(result); //implicitly converts to table&
|
||||
/// else
|
||||
/// std::cerr << "Parse failed:\n"sv << result.error() << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// example output:
|
||||
///
|
||||
/// Parse failed:
|
||||
/// Encountered unexpected character while parsing boolean; expected 'true', saw 'trU'
|
||||
/// (error occurred at line 1, column 13 of 'config.toml')
|
||||
/// \eout
|
||||
///
|
||||
/// Getting node_views (`operator[]`, `at_path()`) and using the iterator accessor functions (`begin()`, `end()` etc.) are
|
||||
/// unconditionally safe; when parsing fails these just return 'empty' values. A ranged-for loop on a failed
|
||||
/// parse_result is also safe since `begin()` and `end()` return the same iterator and will not lead to any
|
||||
/// dereferences and iterations.
|
||||
class parse_result
|
||||
{
|
||||
private:
|
||||
struct storage_t
|
||||
{
|
||||
static constexpr size_t size =
|
||||
(sizeof(toml::table) < sizeof(parse_error) ? sizeof(parse_error) : sizeof(toml::table));
|
||||
static constexpr size_t align =
|
||||
(alignof(toml::table) < alignof(parse_error) ? alignof(parse_error) : alignof(toml::table));
|
||||
|
||||
alignas(align) unsigned char bytes[size];
|
||||
};
|
||||
|
||||
alignas(storage_t::align) mutable storage_t storage_;
|
||||
bool err_;
|
||||
|
||||
template <typename Type>
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
static Type* get_as(storage_t& s) noexcept
|
||||
{
|
||||
return TOML_LAUNDER(reinterpret_cast<Type*>(s.bytes));
|
||||
}
|
||||
|
||||
void destroy() noexcept
|
||||
{
|
||||
if (err_)
|
||||
get_as<parse_error>(storage_)->~parse_error();
|
||||
else
|
||||
get_as<toml::table>(storage_)->~table();
|
||||
}
|
||||
|
||||
public:
|
||||
/// \brief Default constructs an 'error' result.
|
||||
TOML_NODISCARD_CTOR
|
||||
parse_result() noexcept //
|
||||
: err_{ true }
|
||||
{
|
||||
::new (static_cast<void*>(storage_.bytes)) parse_error{ std::string{}, source_region{} };
|
||||
}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit parse_result(toml::table&& tbl) noexcept //
|
||||
: err_{ false }
|
||||
{
|
||||
::new (static_cast<void*>(storage_.bytes)) toml::table{ std::move(tbl) };
|
||||
}
|
||||
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit parse_result(parse_error&& err) noexcept //
|
||||
: err_{ true }
|
||||
{
|
||||
::new (static_cast<void*>(storage_.bytes)) parse_error{ std::move(err) };
|
||||
}
|
||||
|
||||
/// \brief Move constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
parse_result(parse_result&& res) noexcept //
|
||||
: err_{ res.err_ }
|
||||
{
|
||||
if (err_)
|
||||
::new (static_cast<void*>(storage_.bytes)) parse_error{ std::move(res).error() };
|
||||
else
|
||||
::new (static_cast<void*>(storage_.bytes)) toml::table{ std::move(res).table() };
|
||||
}
|
||||
|
||||
/// \brief Move-assignment operator.
|
||||
parse_result& operator=(parse_result&& rhs) noexcept
|
||||
{
|
||||
if (err_ != rhs.err_)
|
||||
{
|
||||
destroy();
|
||||
err_ = rhs.err_;
|
||||
if (err_)
|
||||
::new (static_cast<void*>(storage_.bytes)) parse_error{ std::move(rhs).error() };
|
||||
else
|
||||
::new (static_cast<void*>(storage_.bytes)) toml::table{ std::move(rhs).table() };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (err_)
|
||||
error() = std::move(rhs).error();
|
||||
else
|
||||
table() = std::move(rhs).table();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \brief Destructor.
|
||||
~parse_result() noexcept
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
/// \name Result state
|
||||
/// @{
|
||||
|
||||
/// \brief Returns true if parsing succeeeded.
|
||||
TOML_NODISCARD
|
||||
bool succeeded() const noexcept
|
||||
{
|
||||
return !err_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if parsing failed.
|
||||
TOML_NODISCARD
|
||||
bool failed() const noexcept
|
||||
{
|
||||
return err_;
|
||||
}
|
||||
|
||||
/// \brief Returns true if parsing succeeded.
|
||||
TOML_NODISCARD
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return !err_;
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Successful parses
|
||||
/// \warning It is undefined behaviour to call these functions when the result respresents a failed parse.
|
||||
/// Check #failed(), #succeeded or #operator bool() to determine the result's state.
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the internal toml::table.
|
||||
TOML_NODISCARD
|
||||
toml::table& table() & noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(!err_);
|
||||
return *get_as<toml::table>(storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::table (rvalue overload).
|
||||
TOML_NODISCARD
|
||||
toml::table&& table() && noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(!err_);
|
||||
return static_cast<toml::table&&>(*get_as<toml::table>(storage_));
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::table (const lvalue overload).
|
||||
TOML_NODISCARD
|
||||
const toml::table& table() const& noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(!err_);
|
||||
return *get_as<const toml::table>(storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::table.
|
||||
TOML_NODISCARD
|
||||
/* implicit */ operator toml::table&() noexcept
|
||||
{
|
||||
return table();
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::table (rvalue overload).
|
||||
TOML_NODISCARD
|
||||
/* implicit */ operator toml::table&&() noexcept
|
||||
{
|
||||
return std::move(table());
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::table (const lvalue overload).
|
||||
TOML_NODISCARD
|
||||
/* implicit */ operator const toml::table&() const noexcept
|
||||
{
|
||||
return table();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Failed parses
|
||||
/// \warning It is undefined behaviour to call these functions when the result respresents a successful parse.
|
||||
/// Check #failed(), #succeeded or #operator bool() to determine the result's state.
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the internal toml::parse_error.
|
||||
TOML_NODISCARD
|
||||
parse_error& error() & noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(err_);
|
||||
return *get_as<parse_error>(storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::parse_error (rvalue overload).
|
||||
TOML_NODISCARD
|
||||
parse_error&& error() && noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(err_);
|
||||
return static_cast<parse_error&&>(*get_as<parse_error>(storage_));
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::parse_error (const lvalue overload).
|
||||
TOML_NODISCARD
|
||||
const parse_error& error() const& noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(err_);
|
||||
return *get_as<const parse_error>(storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::parse_error.
|
||||
TOML_NODISCARD
|
||||
explicit operator parse_error&() noexcept
|
||||
{
|
||||
return error();
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::parse_error (rvalue overload).
|
||||
TOML_NODISCARD
|
||||
explicit operator parse_error&&() noexcept
|
||||
{
|
||||
return std::move(error());
|
||||
}
|
||||
|
||||
/// \brief Returns the internal toml::parse_error (const lvalue overload).
|
||||
TOML_NODISCARD
|
||||
explicit operator const parse_error&() const noexcept
|
||||
{
|
||||
return error();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Iteration
|
||||
/// @{
|
||||
|
||||
/// \brief A BidirectionalIterator for iterating over key-value pairs in a wrapped toml::table.
|
||||
using iterator = table_iterator;
|
||||
|
||||
/// \brief A BidirectionalIterator for iterating over const key-value pairs in a wrapped toml::table.
|
||||
using const_iterator = const_table_iterator;
|
||||
|
||||
/// \brief Returns an iterator to the first key-value pair in the wrapped table.
|
||||
/// \remarks Always returns the same value as #end() if parsing failed.
|
||||
TOML_NODISCARD
|
||||
table_iterator begin() noexcept
|
||||
{
|
||||
return err_ ? table_iterator{} : table().begin();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to the first key-value pair in the wrapped table.
|
||||
/// \remarks Always returns the same value as #end() if parsing failed.
|
||||
TOML_NODISCARD
|
||||
const_table_iterator begin() const noexcept
|
||||
{
|
||||
return err_ ? const_table_iterator{} : table().begin();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to the first key-value pair in the wrapped table.
|
||||
/// \remarks Always returns the same value as #cend() if parsing failed.
|
||||
TOML_NODISCARD
|
||||
const_table_iterator cbegin() const noexcept
|
||||
{
|
||||
return err_ ? const_table_iterator{} : table().cbegin();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to one-past-the-last key-value pair in the wrapped table.
|
||||
TOML_NODISCARD
|
||||
table_iterator end() noexcept
|
||||
{
|
||||
return err_ ? table_iterator{} : table().end();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to one-past-the-last key-value pair in the wrapped table.
|
||||
TOML_NODISCARD
|
||||
const_table_iterator end() const noexcept
|
||||
{
|
||||
return err_ ? const_table_iterator{} : table().end();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to one-past-the-last key-value pair in the wrapped table.
|
||||
TOML_NODISCARD
|
||||
const_table_iterator cend() const noexcept
|
||||
{
|
||||
return err_ ? const_table_iterator{} : table().cend();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Node views
|
||||
/// @{
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view<node> at_path(std::string_view path) noexcept
|
||||
{
|
||||
return err_ ? node_view<node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
/// \brief Returns a const view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view<const node> at_path(std::string_view path) const noexcept
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(const toml::path&)
|
||||
TOML_NODISCARD
|
||||
node_view<node> at_path(const toml::path& path) noexcept
|
||||
{
|
||||
return err_ ? node_view<node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
/// \brief Returns a const view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::at_path(const toml::path&)
|
||||
TOML_NODISCARD
|
||||
node_view<const node> at_path(const toml::path& path) const noexcept
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view<node> at_path(std::wstring_view path)
|
||||
{
|
||||
return err_ ? node_view<node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
/// \brief Returns a const view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \see #toml::node::at_path(std::string_view)
|
||||
TOML_NODISCARD
|
||||
node_view<const node> at_path(std::wstring_view path) const
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table().at_path(path);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Returns a view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::operator[](const toml::path&)
|
||||
TOML_NODISCARD
|
||||
node_view<node> operator[](const toml::path& path) noexcept
|
||||
{
|
||||
return err_ ? node_view<node>{} : table()[path];
|
||||
}
|
||||
|
||||
/// \brief Returns a const view of the subnode matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::node::operator[](const toml::path&)
|
||||
TOML_NODISCARD
|
||||
node_view<const node> operator[](const toml::path& path) const noexcept
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table()[path];
|
||||
}
|
||||
|
||||
/// \brief Gets a node_view for the selected key-value pair in the wrapped table.
|
||||
///
|
||||
/// \param key The key used for the lookup.
|
||||
///
|
||||
/// \returns A view of the value at the given key if parsing was successful and a matching key existed,
|
||||
/// or an empty node view.
|
||||
///
|
||||
/// \see toml::node_view
|
||||
TOML_NODISCARD
|
||||
node_view<node> operator[](std::string_view key) noexcept
|
||||
{
|
||||
return err_ ? node_view<node>{} : table()[key];
|
||||
}
|
||||
|
||||
/// \brief Gets a node_view for the selected key-value pair in the wrapped table (const overload).
|
||||
///
|
||||
/// \param key The key used for the lookup.
|
||||
///
|
||||
/// \returns A view of the value at the given key if parsing was successful and a matching key existed,
|
||||
/// or an empty node view.
|
||||
///
|
||||
/// \see toml::node_view
|
||||
TOML_NODISCARD
|
||||
node_view<const node> operator[](std::string_view key) const noexcept
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table()[key];
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Gets a node_view for the selected key-value pair in the wrapped table.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \param key The key used for the lookup.
|
||||
///
|
||||
/// \returns A view of the value at the given key if parsing was successful and a matching key existed,
|
||||
/// or an empty node view.
|
||||
///
|
||||
/// \see toml::node_view
|
||||
TOML_NODISCARD
|
||||
node_view<node> operator[](std::wstring_view key)
|
||||
{
|
||||
return err_ ? node_view<node>{} : table()[key];
|
||||
}
|
||||
|
||||
/// \brief Gets a node_view for the selected key-value pair in the wrapped table (const overload).
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \param key The key used for the lookup.
|
||||
///
|
||||
/// \returns A view of the value at the given key if parsing was successful and a matching key existed,
|
||||
/// or an empty node view.
|
||||
///
|
||||
/// \see toml::node_view
|
||||
TOML_NODISCARD
|
||||
node_view<const node> operator[](std::wstring_view key) const
|
||||
{
|
||||
return err_ ? node_view<const node>{} : table()[key];
|
||||
}
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// @}
|
||||
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
/// \brief Prints the held error or table object out to a text stream.
|
||||
///
|
||||
/// \availability This operator is only available when #TOML_ENABLE_FORMATTERS is enabled.
|
||||
friend std::ostream& operator<<(std::ostream& os, const parse_result& result)
|
||||
{
|
||||
return result.err_ ? (os << result.error()) : (os << result.table());
|
||||
}
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
TOML_ABI_NAMESPACE_END;
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_PARSER && !TOML_EXCEPTIONS
|
||||
@@ -0,0 +1,390 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_PARSER
|
||||
|
||||
#include "table.hpp"
|
||||
#include "parse_result.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex);
|
||||
|
||||
/// \brief Parses a TOML document from a string view.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse("a = 3"sv);
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path = {});
|
||||
|
||||
/// \brief Parses a TOML document from a string view.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse("a = 3"sv, "foo.toml");
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path);
|
||||
|
||||
/// \brief Parses a TOML document from a file.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// toml::parse_result get_foo_toml()
|
||||
/// {
|
||||
/// return toml::parse_file("foo.toml");
|
||||
/// }
|
||||
/// \ecpp
|
||||
///
|
||||
/// \param file_path The TOML document to parse. Must be valid UTF-8.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse_file(std::string_view file_path);
|
||||
|
||||
#if TOML_HAS_CHAR8
|
||||
|
||||
/// \brief Parses a TOML document from a char8_t string view.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse(u8"a = 3"sv);
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path = {});
|
||||
|
||||
/// \brief Parses a TOML document from a char8_t string view.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse(u8"a = 3"sv, "foo.toml");
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path);
|
||||
|
||||
/// \brief Parses a TOML document from a file.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// toml::parse_result get_foo_toml()
|
||||
/// {
|
||||
/// return toml::parse_file(u8"foo.toml");
|
||||
/// }
|
||||
/// \ecpp
|
||||
///
|
||||
/// \param file_path The TOML document to parse. Must be valid UTF-8.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse_file(std::u8string_view file_path);
|
||||
|
||||
#endif // TOML_HAS_CHAR8
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a TOML document from a string view.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse("a = 3"sv, L"foo.toml");
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path);
|
||||
|
||||
/// \brief Parses a TOML document from a stream.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// std::stringstream ss;
|
||||
/// ss << "a = 3"sv;
|
||||
///
|
||||
/// auto tbl = toml::parse(ss);
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path);
|
||||
|
||||
/// \brief Parses a TOML document from a file.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// toml::parse_result get_foo_toml()
|
||||
/// {
|
||||
/// return toml::parse_file(L"foo.toml");
|
||||
/// }
|
||||
/// \ecpp
|
||||
///
|
||||
/// \param file_path The TOML document to parse. Must be valid UTF-8.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse_file(std::wstring_view file_path);
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
#if TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a TOML document from a char8_t string view.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse(u8"a = 3"sv, L"foo.toml");
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path);
|
||||
|
||||
#endif // TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a TOML document from a stream.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// std::stringstream ss;
|
||||
/// ss << "a = 3"sv;
|
||||
///
|
||||
/// auto tbl = toml::parse(ss);
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path = {});
|
||||
|
||||
/// \brief Parses a TOML document from a stream.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// std::stringstream ss;
|
||||
/// ss << "a = 3"sv;
|
||||
///
|
||||
/// auto tbl = toml::parse(ss, "foo.toml");
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The TOML document to parse. Must be valid UTF-8.
|
||||
/// \param source_path The path used to initialize each node's `source().path`.
|
||||
/// If you don't have a path (or you have no intention of using paths in diagnostics)
|
||||
/// then this parameter can safely be left blank.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path);
|
||||
|
||||
TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS
|
||||
|
||||
inline namespace literals
|
||||
{
|
||||
TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, lit_ex, lit_noex);
|
||||
|
||||
/// \brief Parses TOML data from a string literal.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// using namespace toml::literals;
|
||||
///
|
||||
/// auto tbl = "a = 3"_toml;
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param str The string data. Must be valid UTF-8.
|
||||
/// \param len The string length.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
parse_result operator""_toml(const char* str, size_t len)
|
||||
{
|
||||
return parse(std::string_view{ str, len });
|
||||
}
|
||||
|
||||
#if TOML_HAS_CHAR8
|
||||
|
||||
/// \brief Parses TOML data from a UTF-8 string literal.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// using namespace toml::literals;
|
||||
///
|
||||
/// auto tbl = u8"a = 3"_toml;
|
||||
/// std::cout << tbl["a"] << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 3
|
||||
/// \eout
|
||||
///
|
||||
/// \param str The string data. Must be valid UTF-8.
|
||||
/// \param len The string length.
|
||||
///
|
||||
/// \returns \conditional_return{With exceptions}
|
||||
/// A toml::table.
|
||||
/// \conditional_return{Without exceptions}
|
||||
/// A toml::parse_result.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
parse_result operator""_toml(const char8_t* str, size_t len)
|
||||
{
|
||||
return parse(std::u8string_view{ str, len });
|
||||
}
|
||||
|
||||
#endif // TOML_HAS_CHAR8
|
||||
|
||||
TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_PARSER
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,851 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "forward_declarations.hpp"
|
||||
#include "std_vector.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief Indicates type of path component, either a key, an index in an array, or invalid
|
||||
enum class TOML_CLOSED_ENUM path_component_type : uint8_t
|
||||
{
|
||||
key = 0x1,
|
||||
array_index = 0x2
|
||||
};
|
||||
|
||||
/// \brief Represents a single component of a complete 'TOML-path': either a key or an array index
|
||||
class TOML_EXPORTED_CLASS path_component
|
||||
{
|
||||
/// \cond
|
||||
struct storage_t
|
||||
{
|
||||
static constexpr size_t size =
|
||||
(sizeof(size_t) < sizeof(std::string) ? sizeof(std::string) : sizeof(size_t));
|
||||
static constexpr size_t align =
|
||||
(alignof(size_t) < alignof(std::string) ? alignof(std::string) : alignof(size_t));
|
||||
|
||||
alignas(align) unsigned char bytes[size];
|
||||
};
|
||||
alignas(storage_t::align) mutable storage_t value_storage_;
|
||||
|
||||
path_component_type type_;
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXPORTED_STATIC_FUNCTION
|
||||
static bool TOML_CALLCONV equal(const path_component&, const path_component&) noexcept;
|
||||
|
||||
template <typename Type>
|
||||
TOML_PURE_INLINE_GETTER
|
||||
static Type* get_as(storage_t& s) noexcept
|
||||
{
|
||||
return TOML_LAUNDER(reinterpret_cast<Type*>(s.bytes));
|
||||
}
|
||||
|
||||
static void store_key(std::string_view key, storage_t& storage_)
|
||||
{
|
||||
::new (static_cast<void*>(storage_.bytes)) std::string{ key };
|
||||
}
|
||||
|
||||
static void store_index(size_t index, storage_t& storage_) noexcept
|
||||
{
|
||||
::new (static_cast<void*>(storage_.bytes)) std::size_t{ index };
|
||||
}
|
||||
|
||||
void destroy() noexcept
|
||||
{
|
||||
if (type_ == path_component_type::key)
|
||||
get_as<std::string>(value_storage_)->~basic_string();
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
size_t& index_ref() noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(type_ == path_component_type::array_index);
|
||||
return *get_as<size_t>(value_storage_);
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
std::string& key_ref() noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(type_ == path_component_type::key);
|
||||
return *get_as<std::string>(value_storage_);
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Default constructor (creates an empty key).
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component();
|
||||
|
||||
/// \brief Constructor for a path component that is an array index
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component(size_t index) noexcept;
|
||||
|
||||
/// \brief Constructor for a path component that is a key string
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component(std::string_view key);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Constructor for a path component that is a key string
|
||||
///
|
||||
/// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component(std::wstring_view key);
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Copy constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component(const path_component& pc);
|
||||
|
||||
/// \brief Move constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component(path_component&& pc) noexcept;
|
||||
|
||||
/// \brief Copy-assignment operator.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component& operator=(const path_component& rhs);
|
||||
|
||||
/// \brief Move-assignment operator.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component& operator=(path_component&& rhs) noexcept;
|
||||
|
||||
/// \brief Assigns an array index to this path component.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component& operator=(size_t new_index) noexcept;
|
||||
|
||||
/// \brief Assigns a path key to this path component.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component& operator=(std::string_view new_key);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Assigns a path key to this path component.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path_component& operator=(std::wstring_view new_key);
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Destructor.
|
||||
~path_component() noexcept
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
/// \name Array index accessors
|
||||
/// \warning It is undefined behaviour to call these functions when the path component does not represent an array index.
|
||||
/// Check #type() to determine the component's value type.
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the array index (const lvalue overload).
|
||||
TOML_PURE_GETTER
|
||||
size_t index() const noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(type_ == path_component_type::array_index);
|
||||
return *get_as<const size_t>(value_storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the array index (const lvalue).
|
||||
TOML_PURE_INLINE_GETTER
|
||||
explicit operator size_t() const noexcept
|
||||
{
|
||||
return index();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Key accessors
|
||||
/// \warning It is undefined behaviour to call these functions when the path component does not represent a key.
|
||||
/// Check #type() to determine the component's value type.
|
||||
/// @{
|
||||
|
||||
/// \brief Returns the key string.
|
||||
TOML_PURE_GETTER
|
||||
const std::string& key() const noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(type_ == path_component_type::key);
|
||||
return *get_as<const std::string>(value_storage_);
|
||||
}
|
||||
|
||||
/// \brief Returns the key string.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
explicit operator const std::string&() const noexcept
|
||||
{
|
||||
return key();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \brief Retrieve the type of this path component, either path_component::key or path_component::array_index
|
||||
TOML_PURE_INLINE_GETTER
|
||||
path_component_type type() const noexcept
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
/// \name Equality
|
||||
/// @{
|
||||
|
||||
/// \brief Returns true if two path components represent the same key or array index.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator==(const path_component& lhs, const path_component& rhs) noexcept
|
||||
{
|
||||
return equal(lhs, rhs);
|
||||
}
|
||||
|
||||
/// \brief Returns true if two path components do not represent the same key or array index.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator!=(const path_component& lhs, const path_component& rhs) noexcept
|
||||
{
|
||||
return !equal(lhs, rhs);
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// \brief A TOML path.
|
||||
///
|
||||
/// \detail This type parses and represents a path to a TOML node. It validates
|
||||
/// the syntax of the path but does not ensure that the path refers to
|
||||
/// a valid node in any particular TOML document. If parsing fails,
|
||||
/// the object will evaluate as 'falsy', and will be empty.
|
||||
///
|
||||
/// \cpp
|
||||
/// toml::path the_path("animals.cats[1]");
|
||||
///
|
||||
/// // can use with tbl.at_path or operator[]
|
||||
/// std::cout << "second cat: " << tbl[the_path] << "\n";
|
||||
/// std::cout << "cats: " << tbl.at_path(the_path.parent_path()) << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// second cat: lion
|
||||
/// cats: ['tiger', 'lion', 'puma']
|
||||
/// \eout
|
||||
class TOML_EXPORTED_CLASS path
|
||||
{
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
std::vector<path_component> components_;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_to(std::ostream&) const;
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXPORTED_STATIC_FUNCTION
|
||||
static bool TOML_CALLCONV equal(const path&, const path&) noexcept;
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Default constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
path() noexcept = default;
|
||||
|
||||
/// \brief Construct a path by parsing from a string.
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
explicit path(std::string_view);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Construct a path by parsing from a string.
|
||||
///
|
||||
/// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD_CTOR
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
explicit path(std::wstring_view);
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Default destructor.
|
||||
~path() noexcept = default;
|
||||
|
||||
/// \brief Copy constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
path(const path&) = default;
|
||||
|
||||
/// \brief Move constructor.
|
||||
TOML_NODISCARD_CTOR
|
||||
path(path&&) noexcept = default;
|
||||
|
||||
/// \brief Returns the number of components in the path.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return components_.size();
|
||||
}
|
||||
|
||||
/// \brief Returns true if the path has one or more components.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return !components_.empty();
|
||||
}
|
||||
|
||||
/// \brief Whether (true) or not (false) the path is empty
|
||||
TOML_PURE_INLINE_GETTER
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return components_.empty();
|
||||
}
|
||||
|
||||
/// \brief Fetch a path component by index.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
path_component& operator[](size_t index) noexcept
|
||||
{
|
||||
TOML_ASSERT(index < size());
|
||||
return components_[index];
|
||||
}
|
||||
|
||||
/// \brief Fetch a path component by index (const overload).
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const path_component& operator[](size_t index) const noexcept
|
||||
{
|
||||
TOML_ASSERT(index < size());
|
||||
return components_[index];
|
||||
}
|
||||
|
||||
/// \name Assignment
|
||||
/// @{
|
||||
|
||||
/// \brief Copy-assignment operator.
|
||||
path& operator=(const path&) = default;
|
||||
|
||||
/// \brief Move-assignment operator.
|
||||
path& operator=(path&&) noexcept = default;
|
||||
|
||||
/// \brief Replaces the contents of the path by parsing from a string.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator=(std::string_view);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Replaces the contents of the path by parsing from a string.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator=(std::wstring_view);
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Replaces the contents of the path with that of another.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& assign(const path& p)
|
||||
{
|
||||
return *this = p;
|
||||
}
|
||||
|
||||
/// \brief Replaces the contents of the path with that of another.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& assign(path&& p) noexcept
|
||||
{
|
||||
return *this = std::move(p);
|
||||
}
|
||||
|
||||
/// \brief Replaces the contents of the path object by a new path
|
||||
TOML_ALWAYS_INLINE
|
||||
path& assign(std::string_view str)
|
||||
{
|
||||
return *this = str;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Replaces the contents of the path object by a new path
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& assign(std::wstring_view str)
|
||||
{
|
||||
return *this = str;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Appending
|
||||
/// @{
|
||||
|
||||
/// \brief Appends another path onto the end of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator+=(const path&);
|
||||
|
||||
/// \brief Appends another path onto the end of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator+=(path&&);
|
||||
|
||||
/// \brief Parses a path and appends it onto the end of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator+=(std::string_view);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a path and appends it onto the end of this one.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& operator+=(std::wstring_view);
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Appends another path onto the end of this one.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& append(const path& p)
|
||||
{
|
||||
return *this += p;
|
||||
}
|
||||
|
||||
/// \brief Appends another path onto the end of this one.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& append(path&& p)
|
||||
{
|
||||
return *this += std::move(p);
|
||||
}
|
||||
|
||||
/// \brief Parses a path and appends it onto the end of this one.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& append(std::string_view str)
|
||||
{
|
||||
return *this += str;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a path and appends it onto the end of this one.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_ALWAYS_INLINE
|
||||
path& append(std::wstring_view str)
|
||||
{
|
||||
return *this += str;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Prepending
|
||||
/// @{
|
||||
|
||||
/// \brief Prepends another path onto the beginning of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& prepend(const path&);
|
||||
|
||||
/// \brief Prepends another path onto the beginning of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& prepend(path&&);
|
||||
|
||||
/// \brief Parses a path and prepends it onto the beginning of this one.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& prepend(std::string_view);
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Parses a path and prepends it onto the beginning of this one.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& prepend(std::wstring_view);
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Concatenation
|
||||
/// @{
|
||||
|
||||
/// \brief Concatenates two paths.
|
||||
TOML_NODISCARD
|
||||
friend path operator+(const path& lhs, const path& rhs)
|
||||
{
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// \brief Concatenates two paths.
|
||||
TOML_NODISCARD
|
||||
friend path operator+(const path& lhs, std::string_view rhs)
|
||||
{
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// \brief Concatenates two paths.
|
||||
TOML_NODISCARD
|
||||
friend path operator+(std::string_view lhs, const path& rhs)
|
||||
{
|
||||
path result = rhs;
|
||||
result.prepend(lhs);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Concatenates two paths.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
friend path operator+(const path& lhs, std::wstring_view rhs)
|
||||
{
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// \brief Concatenates two paths.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
friend path operator+(std::wstring_view lhs, const path& rhs)
|
||||
{
|
||||
path result = rhs;
|
||||
result.prepend(lhs);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name String conversion
|
||||
/// @{
|
||||
|
||||
/// \brief Prints the string representation of a #toml::path out to a stream.
|
||||
TOML_ALWAYS_INLINE
|
||||
friend std::ostream& operator<<(std::ostream& os, const path& rhs)
|
||||
{
|
||||
rhs.print_to(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Returns a string representation of this path.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
std::string str() const;
|
||||
|
||||
/// \brief Returns a string representation of this path.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
explicit operator std::string() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns a string representation of this path.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
std::wstring wide_str() const;
|
||||
|
||||
/// \brief Returns a string representation of this path.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
explicit operator std::wstring() const
|
||||
{
|
||||
return wide_str();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Equality
|
||||
/// @{
|
||||
|
||||
/// \brief Returns whether two paths are the same.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator==(const path& lhs, const path& rhs) noexcept
|
||||
{
|
||||
return equal(lhs, rhs);
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are not the same.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend bool operator!=(const path& lhs, const path& rhs) noexcept
|
||||
{
|
||||
return !equal(lhs, rhs);
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are the same.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator==(const path& lhs, std::string_view rhs)
|
||||
{
|
||||
return lhs == path{ rhs };
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are the same.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator==(std::string_view lhs, const path& rhs)
|
||||
{
|
||||
return rhs == lhs;
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are not the same.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator!=(const path& lhs, std::string_view rhs)
|
||||
{
|
||||
return lhs != path{ rhs };
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are not the same.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator!=(std::string_view lhs, const path& rhs)
|
||||
{
|
||||
return rhs != lhs;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief Returns whether two paths are the same.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator==(const path& lhs, std::wstring_view rhs)
|
||||
{
|
||||
return lhs == path{ rhs };
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are the same.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator==(std::wstring_view lhs, const path& rhs)
|
||||
{
|
||||
return rhs == lhs;
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are not the same.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator!=(const path& lhs, std::wstring_view rhs)
|
||||
{
|
||||
return lhs != path{ rhs };
|
||||
}
|
||||
|
||||
/// \brief Returns whether two paths are not the same.
|
||||
///
|
||||
/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
friend bool operator!=(std::wstring_view lhs, const path& rhs)
|
||||
{
|
||||
return rhs != lhs;
|
||||
}
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Iteration
|
||||
/// @{
|
||||
|
||||
/// An iterator for iterating over the components in the path.
|
||||
/// \see #toml::path_component
|
||||
using iterator = std::vector<path_component>::iterator;
|
||||
|
||||
/// A const iterator for iterating over the components in the path.
|
||||
/// \see #toml::path_component
|
||||
using const_iterator = std::vector<path_component>::const_iterator;
|
||||
|
||||
/// \brief Returns an iterator to the first component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return components_.begin();
|
||||
}
|
||||
|
||||
/// \brief Returns an iterator to one-past-the-last component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
iterator end() noexcept
|
||||
{
|
||||
return components_.end();
|
||||
}
|
||||
|
||||
/// \brief Returns a const iterator to the first component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return components_.begin();
|
||||
}
|
||||
|
||||
/// \brief Returns a const iterator to one-past-the-last component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return components_.end();
|
||||
}
|
||||
|
||||
/// \brief Returns a const iterator to the first component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator cbegin() const noexcept
|
||||
{
|
||||
return components_.begin();
|
||||
}
|
||||
|
||||
/// \brief Returns a const iterator to one-past-the-last component in the path.
|
||||
/// \see #toml::path_component
|
||||
TOML_PURE_INLINE_GETTER
|
||||
const_iterator cend() const noexcept
|
||||
{
|
||||
return components_.end();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// \name Subpaths and Truncation
|
||||
/// @{
|
||||
|
||||
/// \brief Erases the contents of the path.
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void clear() noexcept;
|
||||
|
||||
/// \brief Removes the number of terminal path components specified by n
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path& truncate(size_t n);
|
||||
|
||||
/// \brief Returns a toml::path object which has had n terminal path components removed
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path truncated(size_t n) const;
|
||||
|
||||
/// \brief Returns a toml::path object representing the path of the parent node
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path parent() const;
|
||||
|
||||
/// \brief Returns a toml::path object representing terminal n-parts of a TOML path
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path leaf(size_t n = 1) const;
|
||||
|
||||
/// \brief Returns a toml::path object that is a specified subpath of the current path, representing the
|
||||
/// range of path components from [start, end).
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path subpath(const_iterator start, const_iterator end) const;
|
||||
|
||||
/// \brief Returns a toml::path object that is a specified subpath of the current path, representing the
|
||||
/// range of path components with indexes from [start, start + length].
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
path subpath(size_t start, size_t length) const;
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
inline namespace literals
|
||||
{
|
||||
/// \brief Parses a TOML path from a string literal.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// using namespace toml::literals;
|
||||
///
|
||||
/// auto path = "main.settings.devices[2]"_tpath;
|
||||
/// std::cout << path.parent_path() << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// main.settings.devices
|
||||
/// \eout
|
||||
///
|
||||
/// \param str The string data.
|
||||
/// \param len The string length.
|
||||
///
|
||||
/// \returns A #toml::path generated from the string literal.
|
||||
TOML_NODISCARD
|
||||
TOML_ALWAYS_INLINE
|
||||
path operator""_tpath(const char* str, size_t len)
|
||||
{
|
||||
return path(std::string_view{ str, len });
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Returns a view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto config = toml::parse(R"(
|
||||
///
|
||||
/// [foo]
|
||||
/// bar = [ 0, 1, 2, [ 3 ], { kek = 4 } ]
|
||||
///
|
||||
/// )"sv);
|
||||
///
|
||||
/// toml::path path1("foo.bar[2]");
|
||||
/// toml::path path2("foo.bar[4].kek");
|
||||
/// std::cout << toml::at_path(config, path1) << "\n";
|
||||
/// std::cout << toml::at_path(config, path1.parent_path()) << "\n";
|
||||
/// std::cout << toml::at_path(config, path2) << "\n";
|
||||
/// std::cout << toml::at_path(config, path2.parent_path()) << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// 2
|
||||
/// [ 0, 1, 2, [ 3 ], { kek = 4 } ]
|
||||
/// 4
|
||||
/// { kek = 4 }
|
||||
/// \eout
|
||||
///
|
||||
///
|
||||
/// \note Keys in paths are interpreted literally, so whitespace (or lack thereof) matters:
|
||||
/// \cpp
|
||||
/// toml::at_path(config, toml::path("foo.bar")) // same as config["foo"]["bar"]
|
||||
/// toml::at_path(config, toml::path("foo. bar")) // same as config["foo"][" bar"]
|
||||
/// toml::at_path(config, toml::path("foo..bar")) // same as config["foo"][""]["bar"]
|
||||
/// toml::at_path(config, toml::path(".foo.bar")) // same as config[""]["foo"]["bar"]
|
||||
/// \ecpp
|
||||
/// <br>
|
||||
/// Additionally, TOML allows '.' (period) characters to appear in keys if they are quoted strings.
|
||||
/// This function makes no allowance for this, instead treating all period characters as sub-table delimiters.
|
||||
///
|
||||
/// \param root The root node from which the path will be traversed.
|
||||
/// \param path The "TOML path" to traverse.
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, const toml::path& path) noexcept;
|
||||
|
||||
/// \brief Returns a const view of the node matching a fully-qualified "TOML path".
|
||||
///
|
||||
/// \see #toml::at_path(node&, const toml::path& path)
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, const toml::path& path) noexcept;
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,523 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "path.hpp"
|
||||
#include "at_path.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#if TOML_INT_CHARCONV
|
||||
#include <charconv>
|
||||
#endif
|
||||
#include <sstream>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
#include "header_start.hpp"
|
||||
|
||||
//#=====================================================================================================================
|
||||
//# toml::path_component
|
||||
//#=====================================================================================================================
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component() //
|
||||
: type_{ path_component_type::key }
|
||||
{
|
||||
store_key("", value_storage_);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component(size_t index) noexcept //
|
||||
: type_(path_component_type::array_index)
|
||||
{
|
||||
store_index(index, value_storage_);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component(std::string_view key) //
|
||||
: type_(path_component_type::key)
|
||||
{
|
||||
store_key(key, value_storage_);
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component(std::wstring_view key) //
|
||||
: path_component(impl::narrow(key))
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component(const path_component& pc) //
|
||||
: type_{ pc.type_ }
|
||||
{
|
||||
if (type_ == path_component_type::array_index)
|
||||
store_index(pc.index(), value_storage_);
|
||||
else
|
||||
store_key(pc.key(), value_storage_);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component::path_component(path_component && pc) noexcept //
|
||||
: type_{ pc.type_ }
|
||||
{
|
||||
if (type_ == path_component_type::array_index)
|
||||
store_index(pc.index_ref(), value_storage_);
|
||||
else
|
||||
store_key(std::move(pc.key_ref()), value_storage_);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component& path_component::operator=(const path_component& rhs)
|
||||
{
|
||||
if (type_ != rhs.type_)
|
||||
{
|
||||
destroy();
|
||||
|
||||
type_ = rhs.type_;
|
||||
if (type_ == path_component_type::array_index)
|
||||
store_index(rhs.index(), value_storage_);
|
||||
else
|
||||
store_key(rhs.key(), value_storage_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type_ == path_component_type::array_index)
|
||||
index_ref() = rhs.index();
|
||||
else
|
||||
key_ref() = rhs.key();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component& path_component::operator=(path_component&& rhs) noexcept
|
||||
{
|
||||
if (type_ != rhs.type_)
|
||||
{
|
||||
destroy();
|
||||
|
||||
type_ = rhs.type_;
|
||||
if (type_ == path_component_type::array_index)
|
||||
store_index(rhs.index(), value_storage_);
|
||||
else
|
||||
store_key(std::move(rhs.key_ref()), value_storage_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type_ == path_component_type::array_index)
|
||||
index_ref() = rhs.index();
|
||||
else
|
||||
key_ref() = std::move(rhs.key_ref());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV path_component::equal(const path_component& lhs, const path_component& rhs) noexcept
|
||||
{
|
||||
// Different comparison depending on contents
|
||||
if (lhs.type_ != rhs.type_)
|
||||
return false;
|
||||
|
||||
if (lhs.type_ == path_component_type::array_index)
|
||||
return lhs.index() == rhs.index();
|
||||
else // path_component_type::key
|
||||
return lhs.key() == rhs.key();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component& path_component::operator=(size_t new_index) noexcept
|
||||
{
|
||||
// If currently a key, string will need to be destroyed regardless
|
||||
destroy();
|
||||
|
||||
type_ = path_component_type::array_index;
|
||||
store_index(new_index, value_storage_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component& path_component::operator=(std::string_view new_key)
|
||||
{
|
||||
if (type_ == path_component_type::key)
|
||||
key_ref() = new_key;
|
||||
else
|
||||
{
|
||||
type_ = path_component_type::key;
|
||||
store_key(new_key, value_storage_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path_component& path_component::operator=(std::wstring_view new_key)
|
||||
{
|
||||
if (type_ == path_component_type::key)
|
||||
key_ref() = impl::narrow(new_key);
|
||||
else
|
||||
{
|
||||
type_ = path_component_type::key;
|
||||
store_key(impl::narrow(new_key), value_storage_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
//#=====================================================================================================================
|
||||
//# toml::path
|
||||
//#=====================================================================================================================
|
||||
|
||||
TOML_ANON_NAMESPACE_START
|
||||
{
|
||||
TOML_INTERNAL_LINKAGE
|
||||
bool parse_path_into(std::string_view path_str, std::vector<path_component> & components)
|
||||
{
|
||||
using components_type = std::remove_reference_t<decltype(components)>;
|
||||
|
||||
const auto original_size = components.size();
|
||||
|
||||
static constexpr auto on_key = [](void* data, std::string_view key) -> bool
|
||||
{
|
||||
auto& comps = *static_cast<components_type*>(data);
|
||||
comps.emplace_back(key);
|
||||
return true;
|
||||
};
|
||||
|
||||
static constexpr auto on_index = [](void* data, size_t index) -> bool
|
||||
{
|
||||
auto& comps = *static_cast<components_type*>(data);
|
||||
comps.emplace_back(index);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!impl::parse_path(path_str, &components, on_key, on_index))
|
||||
{
|
||||
components.resize(original_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TOML_ANON_NAMESPACE_END;
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void path::print_to(std::ostream & os) const
|
||||
{
|
||||
bool root = true;
|
||||
for (const auto& component : components_)
|
||||
{
|
||||
if (component.type() == path_component_type::key) // key
|
||||
{
|
||||
if (!root)
|
||||
impl::print_to_stream(os, '.');
|
||||
impl::print_to_stream(os, component.key());
|
||||
}
|
||||
else if (component.type() == path_component_type::array_index) // array
|
||||
{
|
||||
impl::print_to_stream(os, '[');
|
||||
impl::print_to_stream(os, component.index());
|
||||
impl::print_to_stream(os, ']');
|
||||
}
|
||||
root = false;
|
||||
}
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV path::equal(const path& lhs, const path& rhs) noexcept
|
||||
{
|
||||
return lhs.components_ == rhs.components_;
|
||||
}
|
||||
|
||||
//#=== constructors =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path::path(std::string_view str) //
|
||||
{
|
||||
TOML_ANON_NAMESPACE::parse_path_into(str, components_);
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path::path(std::wstring_view str) //
|
||||
: path(impl::narrow(str))
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
//#=== assignment =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator=(std::string_view rhs)
|
||||
{
|
||||
components_.clear();
|
||||
TOML_ANON_NAMESPACE::parse_path_into(rhs, components_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator=(std::wstring_view rhs)
|
||||
{
|
||||
return assign(impl::narrow(rhs));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//#=== appending =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator+=(const path& rhs)
|
||||
{
|
||||
components_.insert(components_.cend(), rhs.begin(), rhs.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator+=(path&& rhs)
|
||||
{
|
||||
components_.insert(components_.end(),
|
||||
std::make_move_iterator(rhs.components_.begin()),
|
||||
std::make_move_iterator(rhs.components_.end()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator+=(std::string_view str)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::parse_path_into(str, components_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::operator+=(std::wstring_view str)
|
||||
{
|
||||
return *this += impl::narrow(str);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//#=== prepending =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::prepend(const path& source)
|
||||
{
|
||||
components_.insert(components_.begin(), source.components_.begin(), source.components_.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::prepend(path && source)
|
||||
{
|
||||
components_.insert(components_.begin(),
|
||||
std::make_move_iterator(source.components_.begin()),
|
||||
std::make_move_iterator(source.components_.end()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::prepend(std::string_view source)
|
||||
{
|
||||
return prepend(path{ source });
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::prepend(std::wstring_view source)
|
||||
{
|
||||
return prepend(impl::narrow(source));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//#=== string conversion =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
std::string path::str() const
|
||||
{
|
||||
if (empty())
|
||||
return "";
|
||||
|
||||
std::ostringstream ss;
|
||||
print_to(ss);
|
||||
return std::move(ss).str();
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
std::wstring path::wide_str() const
|
||||
{
|
||||
return impl::widen(str());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//#=== equality and comparison =================================================
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void path::clear() noexcept
|
||||
{
|
||||
components_.clear();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path& path::truncate(size_t n)
|
||||
{
|
||||
n = n > components_.size() ? components_.size() : n;
|
||||
|
||||
auto it_end = components_.end();
|
||||
components_.erase(it_end - static_cast<int>(n), it_end);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path path::truncated(size_t n) const
|
||||
{
|
||||
path truncated_path{};
|
||||
|
||||
n = n > components_.size() ? components_.size() : n;
|
||||
|
||||
// Copy all components except one
|
||||
// Need at least two path components to have a parent, since if there is
|
||||
// only one path component, the parent is the root/null path ""
|
||||
truncated_path.components_.insert(truncated_path.components_.begin(),
|
||||
components_.begin(),
|
||||
components_.end() - static_cast<int>(n));
|
||||
|
||||
return truncated_path;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path path::parent() const
|
||||
{
|
||||
return truncated(1);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path path::leaf(size_t n) const
|
||||
{
|
||||
path leaf_path{};
|
||||
|
||||
n = n > components_.size() ? components_.size() : n;
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
leaf_path.components_.insert(leaf_path.components_.begin(),
|
||||
components_.end() - static_cast<int>(n),
|
||||
components_.end());
|
||||
}
|
||||
|
||||
return leaf_path;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path path::subpath(std::vector<path_component>::const_iterator start,
|
||||
std::vector<path_component>::const_iterator end) const
|
||||
{
|
||||
if (start >= end)
|
||||
return {};
|
||||
|
||||
path subpath;
|
||||
subpath.components_.insert(subpath.components_.begin(), start, end);
|
||||
return subpath;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
path path::subpath(size_t start, size_t length) const
|
||||
{
|
||||
return subpath(begin() + static_cast<int>(start), begin() + static_cast<int>(start + length));
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
//#=====================================================================================================================
|
||||
//# at_path() overloads for toml::path
|
||||
//#=====================================================================================================================
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<node> TOML_CALLCONV at_path(node & root, const toml::path& path) noexcept
|
||||
{
|
||||
// early-exit sanity-checks
|
||||
if (root.is_value())
|
||||
return {};
|
||||
if (auto tbl = root.as_table(); tbl && tbl->empty())
|
||||
return {};
|
||||
if (auto arr = root.as_array(); arr && arr->empty())
|
||||
return {};
|
||||
|
||||
node* current = &root;
|
||||
|
||||
for (const auto& component : path)
|
||||
{
|
||||
auto type = component.type();
|
||||
if (type == path_component_type::array_index)
|
||||
{
|
||||
const auto current_array = current->as<array>();
|
||||
if (!current_array)
|
||||
return {}; // not an array, using array index doesn't work
|
||||
|
||||
current = current_array->get(component.index());
|
||||
}
|
||||
else if (type == path_component_type::key)
|
||||
{
|
||||
const auto current_table = current->as<table>();
|
||||
if (!current_table)
|
||||
return {};
|
||||
|
||||
current = current_table->get(component.key());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error: invalid component
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!current)
|
||||
return {}; // not found
|
||||
}
|
||||
|
||||
return node_view{ current };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node_view<const node> TOML_CALLCONV at_path(const node& root, const toml::path& path) noexcept
|
||||
{
|
||||
return node_view<const node>{ at_path(const_cast<node&>(root), path).node() };
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,129 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "std_string.hpp"
|
||||
#include "forward_declarations.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
// Q: "why does print_to_stream() exist? why not just use ostream::write(), ostream::put() etc?"
|
||||
// A: - I'm using <charconv> to format numerics. Faster and locale-independent.
|
||||
// - I can (potentially) avoid forcing users to drag in <sstream> and <iomanip>.
|
||||
// - Strings in C++. Honestly.
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
TOML_ATTR(nonnull)
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const char*, size_t);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, std::string_view);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const std::string&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, char);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, signed char, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, signed short, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, signed int, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, signed long, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, signed long long, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, unsigned char, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, unsigned short, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, unsigned int, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, unsigned long, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, unsigned long long, value_flags = {}, size_t min_digits = 0);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, float, value_flags = {}, bool relaxed_precision = false);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, double, value_flags = {}, bool relaxed_precision = false);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, bool);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const toml::date&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const toml::time&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const toml::time_offset&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const toml::date_time&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const source_position&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const source_region&);
|
||||
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const array&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const table&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<std::string>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<int64_t>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<double>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<bool>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<date>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<time>&);
|
||||
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
void TOML_CALLCONV print_to_stream(std::ostream&, const value<date_time>&);
|
||||
|
||||
#endif
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void print_to_stream_bookended(std::ostream & stream, const T& val, const U& bookend)
|
||||
{
|
||||
print_to_stream(stream, bookend);
|
||||
print_to_stream(stream, val);
|
||||
print_to_stream(stream, bookend);
|
||||
}
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,495 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "print_to_stream.hpp"
|
||||
#include "source_region.hpp"
|
||||
#include "date_time.hpp"
|
||||
#include "toml_formatter.hpp"
|
||||
#include "value.hpp"
|
||||
#include "array.hpp"
|
||||
#include "table.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <ostream>
|
||||
#if TOML_INT_CHARCONV || TOML_FLOAT_CHARCONV
|
||||
#include <charconv>
|
||||
#endif
|
||||
#if !TOML_INT_CHARCONV || !TOML_FLOAT_CHARCONV
|
||||
#include <sstream>
|
||||
#endif
|
||||
#if !TOML_INT_CHARCONV
|
||||
#include <iomanip>
|
||||
#endif
|
||||
TOML_ENABLE_WARNINGS;
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_ANON_NAMESPACE_START
|
||||
{
|
||||
template <typename T>
|
||||
inline constexpr size_t charconv_buffer_length = 0;
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<int8_t> = 4; // strlen("-128")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<int16_t> = 6; // strlen("-32768")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<int32_t> = 11; // strlen("-2147483648")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<int64_t> = 20; // strlen("-9223372036854775808")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<uint8_t> = 3; // strlen("255")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<uint16_t> = 5; // strlen("65535")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<uint32_t> = 10; // strlen("4294967295")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<uint64_t> = 20; // strlen("18446744073709551615")
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<float> = 64;
|
||||
|
||||
template <>
|
||||
inline constexpr size_t charconv_buffer_length<double> = 64;
|
||||
|
||||
template <typename T>
|
||||
TOML_INTERNAL_LINKAGE
|
||||
void print_integer_to_stream(std::ostream & stream, T val, value_flags format = {}, size_t min_digits = 0)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
if (!min_digits)
|
||||
min_digits = 1;
|
||||
|
||||
for (size_t i = 0; i < min_digits; i++)
|
||||
stream.put('0');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr auto value_flags_mask =
|
||||
value_flags::format_as_binary | value_flags::format_as_octal | value_flags::format_as_hexadecimal;
|
||||
format &= value_flags_mask;
|
||||
|
||||
int base = 10;
|
||||
if (format != value_flags::none && val > T{})
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case value_flags::format_as_binary: base = 2; break;
|
||||
case value_flags::format_as_octal: base = 8; break;
|
||||
case value_flags::format_as_hexadecimal: base = 16; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
#if TOML_INT_CHARCONV
|
||||
|
||||
char buf[(sizeof(T) * CHAR_BIT)];
|
||||
const auto res = std::to_chars(buf, buf + sizeof(buf), val, base);
|
||||
const auto len = static_cast<size_t>(res.ptr - buf);
|
||||
for (size_t i = len; i < min_digits; i++)
|
||||
stream.put('0');
|
||||
if (base == 16)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
if (buf[i] >= 'a')
|
||||
buf[i] -= 32;
|
||||
}
|
||||
impl::print_to_stream(stream, buf, len);
|
||||
|
||||
#else
|
||||
|
||||
using unsigned_type = std::conditional_t<(sizeof(T) > sizeof(unsigned)), std::make_unsigned_t<T>, unsigned>;
|
||||
using cast_type = std::conditional_t<std::is_signed_v<T>, std::make_signed_t<unsigned_type>, unsigned_type>;
|
||||
|
||||
if (base == 2)
|
||||
{
|
||||
const auto len = sizeof(T) * CHAR_BIT;
|
||||
for (size_t i = len; i < min_digits; i++)
|
||||
stream.put('0');
|
||||
|
||||
bool found_one = false;
|
||||
const auto v = static_cast<unsigned_type>(val);
|
||||
unsigned_type mask = unsigned_type{ 1 } << (len - 1u);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
if ((v & mask))
|
||||
{
|
||||
stream.put('1');
|
||||
found_one = true;
|
||||
}
|
||||
else if (found_one)
|
||||
stream.put('0');
|
||||
mask >>= 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss << std::uppercase << std::setbase(base);
|
||||
if (min_digits)
|
||||
ss << std::setfill('0') << std::setw(static_cast<int>(min_digits));
|
||||
ss << static_cast<cast_type>(val);
|
||||
const auto str = std::move(ss).str();
|
||||
impl::print_to_stream(stream, str);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TOML_INTERNAL_LINKAGE
|
||||
void print_floating_point_to_stream(std::ostream & stream,
|
||||
T val,
|
||||
value_flags format,
|
||||
[[maybe_unused]] bool relaxed_precision)
|
||||
{
|
||||
switch (impl::fpclassify(val))
|
||||
{
|
||||
case impl::fp_class::neg_inf: impl::print_to_stream(stream, "-inf"sv); break;
|
||||
|
||||
case impl::fp_class::pos_inf: impl::print_to_stream(stream, "inf"sv); break;
|
||||
|
||||
case impl::fp_class::nan: impl::print_to_stream(stream, "nan"sv); break;
|
||||
|
||||
case impl::fp_class::ok:
|
||||
{
|
||||
static constexpr auto needs_decimal_point = [](auto&& s) noexcept
|
||||
{
|
||||
for (auto c : s)
|
||||
if (c == '.' || c == 'E' || c == 'e')
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
#if TOML_FLOAT_CHARCONV
|
||||
|
||||
const auto hex = !!(format & value_flags::format_as_hexadecimal);
|
||||
char buf[charconv_buffer_length<T>];
|
||||
auto res = hex ? std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::hex)
|
||||
: std::to_chars(buf, buf + sizeof(buf), val);
|
||||
auto str = std::string_view{ buf, static_cast<size_t>(res.ptr - buf) };
|
||||
|
||||
char buf2[charconv_buffer_length<T>];
|
||||
if (!hex && relaxed_precision)
|
||||
{
|
||||
res = std::to_chars(buf2, buf2 + sizeof(buf2), val, std::chars_format::general, 6);
|
||||
const auto str2 = std::string_view{ buf2, static_cast<size_t>(res.ptr - buf2) };
|
||||
if (str2.length() < str.length())
|
||||
str = str2;
|
||||
}
|
||||
|
||||
impl::print_to_stream(stream, str);
|
||||
if (!hex && needs_decimal_point(str))
|
||||
toml::impl::print_to_stream(stream, ".0"sv);
|
||||
|
||||
#else
|
||||
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
if (!relaxed_precision)
|
||||
ss.precision(std::numeric_limits<T>::max_digits10);
|
||||
if (!!(format & value_flags::format_as_hexadecimal))
|
||||
ss << std::hexfloat;
|
||||
ss << val;
|
||||
const auto str = std::move(ss).str();
|
||||
impl::print_to_stream(stream, str);
|
||||
if (!(format & value_flags::format_as_hexadecimal) && needs_decimal_point(str))
|
||||
impl::print_to_stream(stream, ".0"sv);
|
||||
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
TOML_ANON_NAMESPACE_END;
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
TOML_ATTR(nonnull)
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const char* val, size_t len)
|
||||
{
|
||||
stream.write(val, static_cast<std::streamsize>(len));
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, std::string_view val)
|
||||
{
|
||||
stream.write(val.data(), static_cast<std::streamsize>(val.length()));
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const std::string& val)
|
||||
{
|
||||
stream.write(val.data(), static_cast<std::streamsize>(val.length()));
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, char val)
|
||||
{
|
||||
stream.put(val);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, signed char val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, signed short val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, signed int val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, signed long val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream,
|
||||
signed long long val,
|
||||
value_flags format,
|
||||
size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, unsigned char val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, unsigned short val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, unsigned int val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, unsigned long val, value_flags format, size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream,
|
||||
unsigned long long val,
|
||||
value_flags format,
|
||||
size_t min_digits)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_integer_to_stream(stream, val, format, min_digits);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, float val, value_flags format, bool relaxed_precision)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, double val, value_flags format, bool relaxed_precision)
|
||||
{
|
||||
TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, bool val)
|
||||
{
|
||||
print_to_stream(stream, val ? "true"sv : "false"sv);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const toml::date& val)
|
||||
{
|
||||
print_to_stream(stream, val.year, {}, 4);
|
||||
stream.put('-');
|
||||
print_to_stream(stream, val.month, {}, 2);
|
||||
stream.put('-');
|
||||
print_to_stream(stream, val.day, {}, 2);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const toml::time& val)
|
||||
{
|
||||
print_to_stream(stream, val.hour, {}, 2);
|
||||
stream.put(':');
|
||||
print_to_stream(stream, val.minute, {}, 2);
|
||||
stream.put(':');
|
||||
print_to_stream(stream, val.second, {}, 2);
|
||||
if (val.nanosecond && val.nanosecond <= 999999999u)
|
||||
{
|
||||
stream.put('.');
|
||||
auto ns = val.nanosecond;
|
||||
size_t digits = 9u;
|
||||
while (ns % 10u == 0u)
|
||||
{
|
||||
ns /= 10u;
|
||||
digits--;
|
||||
}
|
||||
print_to_stream(stream, ns, {}, digits);
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const toml::time_offset& val)
|
||||
{
|
||||
if (!val.minutes)
|
||||
{
|
||||
stream.put('Z');
|
||||
return;
|
||||
}
|
||||
|
||||
auto mins = static_cast<int>(val.minutes);
|
||||
if (mins < 0)
|
||||
{
|
||||
stream.put('-');
|
||||
mins = -mins;
|
||||
}
|
||||
else
|
||||
stream.put('+');
|
||||
const auto hours = mins / 60;
|
||||
if (hours)
|
||||
{
|
||||
print_to_stream(stream, static_cast<unsigned int>(hours), {}, 2);
|
||||
mins -= hours * 60;
|
||||
}
|
||||
else
|
||||
print_to_stream(stream, "00"sv);
|
||||
stream.put(':');
|
||||
print_to_stream(stream, static_cast<unsigned int>(mins), {}, 2);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const toml::date_time& val)
|
||||
{
|
||||
print_to_stream(stream, val.date);
|
||||
stream.put('T');
|
||||
print_to_stream(stream, val.time);
|
||||
if (val.offset)
|
||||
print_to_stream(stream, *val.offset);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const source_position& val)
|
||||
{
|
||||
print_to_stream(stream, "line "sv);
|
||||
print_to_stream(stream, val.line);
|
||||
print_to_stream(stream, ", column "sv);
|
||||
print_to_stream(stream, val.column);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const source_region& val)
|
||||
{
|
||||
print_to_stream(stream, val.begin);
|
||||
if (val.begin != val.end)
|
||||
{
|
||||
print_to_stream(stream, " to "sv);
|
||||
print_to_stream(stream, val.end);
|
||||
}
|
||||
if (val.path)
|
||||
{
|
||||
print_to_stream(stream, " of '"sv);
|
||||
print_to_stream(stream, *val.path);
|
||||
stream.put('\'');
|
||||
}
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const array& arr)
|
||||
{
|
||||
stream << toml_formatter{ arr };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const table& tbl)
|
||||
{
|
||||
stream << toml_formatter{ tbl };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<std::string>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<int64_t>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<double>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<bool>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<date>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<time>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void TOML_CALLCONV print_to_stream(std::ostream & stream, const value<date_time>& val)
|
||||
{
|
||||
stream << toml_formatter{ val };
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,35 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_SIMD
|
||||
|
||||
#if defined(__SSE2__) \
|
||||
|| (defined(_MSC_VER) && (defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)))
|
||||
#define TOML_HAS_SSE2 1
|
||||
#endif
|
||||
|
||||
#if defined(__SSE4_1__) || (defined(_MSC_VER) && (defined(__AVX__) || defined(__AVX2__)))
|
||||
#define TOML_HAS_SSE4_1 1
|
||||
#endif
|
||||
|
||||
#endif // TOML_ENABLE_SIMD
|
||||
|
||||
#ifndef TOML_HAS_SSE2
|
||||
#define TOML_HAS_SSE2 0
|
||||
#endif
|
||||
#ifndef TOML_HAS_SSE4_1
|
||||
#define TOML_HAS_SSE4_1 0
|
||||
#endif
|
||||
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#if TOML_HAS_SSE4_1
|
||||
#include <smmintrin.h>
|
||||
#endif
|
||||
#if TOML_HAS_SSE2
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,296 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "std_optional.hpp"
|
||||
#include "std_string.hpp"
|
||||
#include "forward_declarations.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief The integer type used to tally line numbers and columns.
|
||||
using source_index = uint32_t;
|
||||
|
||||
/// \brief A pointer to a shared string resource containing a source path.
|
||||
using source_path_ptr = std::shared_ptr<const std::string>;
|
||||
|
||||
/// \brief A source document line-and-column pair.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto table = toml::parse_file("config.toml"sv);
|
||||
/// std::cout << "The node 'description' was defined at "sv
|
||||
/// << table.get("description")->source().begin()
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// The value 'description' was defined at line 7, column 15
|
||||
/// \eout
|
||||
///
|
||||
/// \remarks toml++'s parser is unicode-aware insofar as it knows how to handle
|
||||
/// non-ASCII whitespace and newline characters, but it doesn't give much thought
|
||||
/// to combining marks, grapheme clusters vs. characters, et cetera.
|
||||
/// If a TOML document contains lots of codepoints outside of the ASCII range
|
||||
/// you may find that your source_positions don't match those given by a text editor
|
||||
/// (typically the line numbers will be accurate but column numbers will be too high).
|
||||
/// <strong>This is not an error.</strong> I've chosen this behaviour as a deliberate trade-off
|
||||
/// between parser complexity and correctness.
|
||||
struct TOML_TRIVIAL_ABI source_position
|
||||
{
|
||||
/// \brief The line number.
|
||||
/// \remarks Valid line numbers start at 1.
|
||||
source_index line;
|
||||
|
||||
/// \brief The column number.
|
||||
/// \remarks Valid column numbers start at 1.
|
||||
source_index column;
|
||||
|
||||
/// \brief Returns true if both line and column numbers are non-zero.
|
||||
TOML_PURE_GETTER
|
||||
explicit constexpr operator bool() const noexcept
|
||||
{
|
||||
return line > source_index{} //
|
||||
&& column > source_index{};
|
||||
}
|
||||
|
||||
/// \brief Equality operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator==(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return lhs.line == rhs.line //
|
||||
&& lhs.column == rhs.column;
|
||||
}
|
||||
|
||||
/// \brief Inequality operator.
|
||||
TOML_PURE_INLINE_GETTER
|
||||
friend constexpr bool operator!=(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
TOML_PURE_GETTER
|
||||
static constexpr uint64_t pack(const source_position& pos) noexcept
|
||||
{
|
||||
return static_cast<uint64_t>(pos.line) << 32 | static_cast<uint64_t>(pos.column);
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief Less-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) < pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Less-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator<=(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) <= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) > pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Greater-than-or-equal-to operator.
|
||||
TOML_PURE_GETTER
|
||||
friend constexpr bool operator>=(const source_position& lhs, const source_position& rhs) noexcept
|
||||
{
|
||||
return pack(lhs) >= pack(rhs);
|
||||
}
|
||||
|
||||
/// \brief Prints a source_position to a stream.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse("bar = 42"sv);
|
||||
///
|
||||
/// std::cout << "The value for 'bar' was found on "sv
|
||||
/// << tbl.get("bar")->source().begin()
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// The value for 'bar' was found on line 1, column 7
|
||||
/// \eout
|
||||
///
|
||||
/// \param lhs The stream.
|
||||
/// \param rhs The source_position.
|
||||
///
|
||||
/// \returns The input stream.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const source_position& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief A source document region.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse_file("config.toml"sv);
|
||||
/// if (auto server = tbl.get("server"))
|
||||
/// {
|
||||
/// std::cout << "begin: "sv << server->source().begin << "\n";
|
||||
/// std::cout << "end: "sv << server->source().end << "\n";
|
||||
/// std::cout << "path: "sv << *server->source().path << "\n";
|
||||
/// }
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// begin: line 3, column 1
|
||||
/// end: line 3, column 22
|
||||
/// path: config.toml
|
||||
/// \eout
|
||||
///
|
||||
/// \remarks toml++'s parser is unicode-aware insofar as it knows how to handle
|
||||
/// non-ASCII whitespace and newline characters, but it doesn't give much thought
|
||||
/// to combining marks, grapheme clusters vs. characters, et cetera.
|
||||
/// If a TOML document contains lots of codepoints outside of the ASCII range
|
||||
/// you may find that your source_positions don't match those given by a text editor
|
||||
/// (typically the line numbers will be accurate but column numbers will be too high).
|
||||
/// <strong>This is not an error.</strong> I've chosen this behaviour as a deliberate trade-off
|
||||
/// between parser complexity and correctness.
|
||||
struct source_region
|
||||
{
|
||||
/// \brief The beginning of the region (inclusive).
|
||||
source_position begin;
|
||||
|
||||
/// \brief The end of the region (exclusive).
|
||||
source_position end;
|
||||
|
||||
/// \brief The path to the corresponding source document.
|
||||
///
|
||||
/// \remarks This will be `nullptr` if no path was provided to toml::parse().
|
||||
source_path_ptr path;
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \brief The path to the corresponding source document as a wide-string.
|
||||
///
|
||||
/// \availability This function is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
|
||||
///
|
||||
/// \remarks This will return an empty optional if no path was provided to toml::parse().
|
||||
TOML_NODISCARD
|
||||
optional<std::wstring> wide_path() const
|
||||
{
|
||||
if (!path || path->empty())
|
||||
return {};
|
||||
return { impl::widen(*path) };
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Prints a source_region to a stream.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::parse("bar = 42", "config.toml");
|
||||
///
|
||||
/// std::cout << "The value for 'bar' was found on "sv
|
||||
/// << tbl.get("bar")->source()
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// The value for 'bar' was found on line 1, column 7 of 'config.toml'
|
||||
/// \eout
|
||||
///
|
||||
/// \param lhs The stream.
|
||||
/// \param rhs The source_position.
|
||||
///
|
||||
/// \returns The input stream.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, const source_region& rhs)
|
||||
{
|
||||
impl::print_to_stream(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Returns the line at the specified line number, from the specified document.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto doc = "alpha = 1\nbeta = 2"sv;
|
||||
/// auto second_line = toml::get_line(doc, 2);
|
||||
///
|
||||
/// std::cout << "The second line says \"" << second_line.value() << "\"\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// The second line says "beta = 2"
|
||||
/// \eout
|
||||
///
|
||||
/// \param doc The document.
|
||||
/// \param line_num The line number (1-based).
|
||||
///
|
||||
/// \returns The specified line, excluding any possible trailing carriage return or line feed character.
|
||||
/// \remarks Returns an empty `optional` when the specified line number is out of range, i.e., when
|
||||
/// the line number is zero or greater than the total number of lines of the specified document.
|
||||
TOML_NODISCARD
|
||||
constexpr optional<std::string_view> get_line(std::string_view doc, source_index line_num) noexcept
|
||||
{
|
||||
if (line_num == 0)
|
||||
{
|
||||
// Invalid line number. Should be greater than zero.
|
||||
return {};
|
||||
}
|
||||
|
||||
// The position of the first character of the specified line.
|
||||
const auto begin_of_line = [doc, line_num]() -> std::size_t
|
||||
{
|
||||
if (line_num == 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto num_chars_of_doc = doc.size();
|
||||
std::size_t current_line_num{ 1 };
|
||||
|
||||
for (std::size_t i{}; i < num_chars_of_doc; ++i)
|
||||
{
|
||||
if (doc[i] == '\n')
|
||||
{
|
||||
++current_line_num;
|
||||
|
||||
if (current_line_num == line_num)
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::string_view::npos;
|
||||
}();
|
||||
|
||||
if (begin_of_line >= doc.size())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (const auto end_of_line = doc.find('\n', begin_of_line); end_of_line != std::string_view::npos)
|
||||
{
|
||||
const auto num_chars_of_line = end_of_line - begin_of_line;
|
||||
|
||||
// Trim an optional trailing carriage return.
|
||||
return doc.substr(begin_of_line,
|
||||
((num_chars_of_line > 0) && (doc[end_of_line - 1] == '\r')) ? num_chars_of_line - 1
|
||||
: num_chars_of_line);
|
||||
}
|
||||
|
||||
// Return the last line. Apparently this doc has no trailing line break character at the end.
|
||||
return doc.substr(begin_of_line);
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,12 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#if TOML_EXCEPTIONS
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,10 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <initializer_list>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,11 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,18 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <new>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
|
||||
#if (!defined(__apple_build_version__) && TOML_CLANG >= 8) || TOML_GCC >= 7 || TOML_ICC >= 1910 || TOML_MSVC >= 1914
|
||||
#define TOML_LAUNDER(x) __builtin_launder(x)
|
||||
#elif defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
|
||||
#define TOML_LAUNDER(x) std::launder(x)
|
||||
#else
|
||||
#define TOML_LAUNDER(x) x
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#if !TOML_HAS_CUSTOM_OPTIONAL_TYPE
|
||||
#include <optional>
|
||||
#endif
|
||||
TOML_ENABLE_WARNINGS;
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
#if TOML_HAS_CUSTOM_OPTIONAL_TYPE
|
||||
|
||||
template <typename T>
|
||||
using optional = TOML_OPTIONAL_TYPE<T>;
|
||||
|
||||
#else
|
||||
|
||||
/// \brief The 'optional' type used throughout the library.
|
||||
///
|
||||
/// \remarks By default this will be an alias for std::optional, but you can change the optional type
|
||||
/// used by the library by defining #TOML_OPTIONAL_TYPE.
|
||||
template <typename T>
|
||||
using optional = std::optional<T>;
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
@@ -0,0 +1,53 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
|
||||
#if TOML_DOXYGEN \
|
||||
|| (defined(__cpp_char8_t) && __cpp_char8_t >= 201811 && defined(__cpp_lib_char8_t) \
|
||||
&& __cpp_lib_char8_t >= 201907)
|
||||
#define TOML_HAS_CHAR8 1
|
||||
#else
|
||||
#define TOML_HAS_CHAR8 0
|
||||
#endif
|
||||
|
||||
/// \cond
|
||||
|
||||
namespace toml // non-abi namespace; this is not an error
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
using namespace std::string_view_literals;
|
||||
}
|
||||
|
||||
#if TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
std::string narrow(std::wstring_view);
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
std::wstring widen(std::string_view);
|
||||
|
||||
#if TOML_HAS_CHAR8
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXPORTED_FREE_FUNCTION
|
||||
std::wstring widen(std::u8string_view);
|
||||
|
||||
#endif
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#endif // TOML_ENABLE_WINDOWS_COMPAT
|
||||
|
||||
/// \endcond
|
||||
@@ -0,0 +1,99 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#if TOML_WINDOWS
|
||||
#include "std_string.hpp"
|
||||
#ifndef _WINDOWS_
|
||||
#if TOML_INCLUDE_WINDOWS_H
|
||||
#include <Windows.h>
|
||||
#else
|
||||
|
||||
extern "C" __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int CodePage,
|
||||
unsigned long dwFlags,
|
||||
const wchar_t* lpWideCharStr,
|
||||
int cchWideChar,
|
||||
char* lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
const char* lpDefaultChar,
|
||||
int* lpUsedDefaultChar);
|
||||
|
||||
extern "C" __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage,
|
||||
unsigned long dwFlags,
|
||||
const char* lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
wchar_t* lpWideCharStr,
|
||||
int cchWideChar);
|
||||
|
||||
#endif // TOML_INCLUDE_WINDOWS_H
|
||||
#endif // _WINDOWS_
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
std::string narrow(std::wstring_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return {};
|
||||
|
||||
std::string s;
|
||||
const auto len =
|
||||
::WideCharToMultiByte(65001, 0, str.data(), static_cast<int>(str.length()), nullptr, 0, nullptr, nullptr);
|
||||
if (len)
|
||||
{
|
||||
s.resize(static_cast<size_t>(len));
|
||||
::WideCharToMultiByte(65001,
|
||||
0,
|
||||
str.data(),
|
||||
static_cast<int>(str.length()),
|
||||
s.data(),
|
||||
len,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
std::wstring widen(std::string_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return {};
|
||||
|
||||
std::wstring s;
|
||||
const auto len = ::MultiByteToWideChar(65001, 0, str.data(), static_cast<int>(str.length()), nullptr, 0);
|
||||
if (len)
|
||||
{
|
||||
s.resize(static_cast<size_t>(len));
|
||||
::MultiByteToWideChar(65001, 0, str.data(), static_cast<int>(str.length()), s.data(), len);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#if TOML_HAS_CHAR8
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
std::wstring widen(std::u8string_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return {};
|
||||
|
||||
return widen(std::string_view{ reinterpret_cast<const char*>(str.data()), str.length() });
|
||||
}
|
||||
|
||||
#endif // TOML_HAS_CHAR8
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_WINDOWS
|
||||
@@ -0,0 +1,10 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <utility>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,10 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <variant>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
@@ -0,0 +1,11 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
TOML_DISABLE_WARNINGS;
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
TOML_ENABLE_WARNINGS;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,318 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "table.hpp"
|
||||
#include "node_view.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::table() noexcept
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_TABLE_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::~table() noexcept
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_TABLE_DESTROYED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::table(const impl::table_init_pair* b, const impl::table_init_pair* e)
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_TABLE_CREATED;
|
||||
#endif
|
||||
|
||||
TOML_ASSERT_ASSUME(b);
|
||||
TOML_ASSERT_ASSUME(e);
|
||||
TOML_ASSERT_ASSUME(b <= e);
|
||||
|
||||
if TOML_UNLIKELY(b == e)
|
||||
return;
|
||||
|
||||
for (; b != e; b++)
|
||||
{
|
||||
if (!b->value) // empty node_views
|
||||
continue;
|
||||
|
||||
map_.insert_or_assign(std::move(b->key), std::move(b->value));
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::table(const table& other) //
|
||||
: node(other),
|
||||
inline_{ other.inline_ }
|
||||
{
|
||||
for (auto&& [k, v] : other.map_)
|
||||
map_.emplace_hint(map_.end(), k, impl::make_node(*v));
|
||||
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_TABLE_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::table(table && other) noexcept //
|
||||
: node(std::move(other)),
|
||||
map_{ std::move(other.map_) },
|
||||
inline_{ other.inline_ }
|
||||
{
|
||||
#if TOML_LIFETIME_HOOKS
|
||||
TOML_TABLE_CREATED;
|
||||
#endif
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table& table::operator=(const table& rhs)
|
||||
{
|
||||
if (&rhs != this)
|
||||
{
|
||||
node::operator=(rhs);
|
||||
map_.clear();
|
||||
for (auto&& [k, v] : rhs.map_)
|
||||
map_.emplace_hint(map_.end(), k, impl::make_node(*v));
|
||||
inline_ = rhs.inline_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table& table::operator=(table&& rhs) noexcept
|
||||
{
|
||||
if (&rhs != this)
|
||||
{
|
||||
node::operator=(std::move(rhs));
|
||||
map_ = std::move(rhs.map_);
|
||||
inline_ = rhs.inline_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool table::is_homogeneous(node_type ntype) const noexcept
|
||||
{
|
||||
if (map_.empty())
|
||||
return false;
|
||||
|
||||
if (ntype == node_type::none)
|
||||
ntype = map_.cbegin()->second->type();
|
||||
|
||||
for (auto&& [k, v] : map_)
|
||||
{
|
||||
TOML_UNUSED(k);
|
||||
if (v->type() != ntype)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool table::is_homogeneous(node_type ntype, node * &first_nonmatch) noexcept
|
||||
{
|
||||
if (map_.empty())
|
||||
{
|
||||
first_nonmatch = {};
|
||||
return false;
|
||||
}
|
||||
if (ntype == node_type::none)
|
||||
ntype = map_.cbegin()->second->type();
|
||||
for (const auto& [k, v] : map_)
|
||||
{
|
||||
TOML_UNUSED(k);
|
||||
if (v->type() != ntype)
|
||||
{
|
||||
first_nonmatch = v.get();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TOML_NODISCARD
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool table::is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept
|
||||
{
|
||||
node* fnm = nullptr;
|
||||
const auto result = const_cast<table&>(*this).is_homogeneous(ntype, fnm);
|
||||
first_nonmatch = fnm;
|
||||
return result;
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node* table::get(std::string_view key) noexcept
|
||||
{
|
||||
if (auto it = map_.find(key); it != map_.end())
|
||||
return it->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
node& table::at(std::string_view key)
|
||||
{
|
||||
auto n = get(key);
|
||||
|
||||
#if TOML_COMPILER_HAS_EXCEPTIONS
|
||||
|
||||
if (!n)
|
||||
{
|
||||
auto err = "key '"s;
|
||||
err.append(key);
|
||||
err.append("' not found in table"sv);
|
||||
throw std::out_of_range{ err };
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
TOML_ASSERT_ASSUME(n && "key not found in table!");
|
||||
|
||||
#endif
|
||||
|
||||
return *n;
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::map_iterator table::get_lower_bound(std::string_view key) noexcept
|
||||
{
|
||||
return map_.lower_bound(key);
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::iterator table::find(std::string_view key) noexcept
|
||||
{
|
||||
return iterator{ map_.find(key) };
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::const_iterator table::find(std::string_view key) const noexcept
|
||||
{
|
||||
return const_iterator{ map_.find(key) };
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::map_iterator table::erase(const_map_iterator pos) noexcept
|
||||
{
|
||||
return map_.erase(pos);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::map_iterator table::erase(const_map_iterator begin, const_map_iterator end) noexcept
|
||||
{
|
||||
return map_.erase(begin, end);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
size_t table::erase(std::string_view key) noexcept
|
||||
{
|
||||
if (auto it = map_.find(key); it != map_.end())
|
||||
{
|
||||
map_.erase(it);
|
||||
return size_t{ 1 };
|
||||
}
|
||||
return size_t{};
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table& table::prune(bool recursive)& noexcept
|
||||
{
|
||||
if (map_.empty())
|
||||
return *this;
|
||||
|
||||
for (auto it = map_.begin(); it != map_.end();)
|
||||
{
|
||||
if (auto arr = it->second->as_array())
|
||||
{
|
||||
if (recursive)
|
||||
arr->prune(true);
|
||||
|
||||
if (arr->empty())
|
||||
{
|
||||
it = map_.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (auto tbl = it->second->as_table())
|
||||
{
|
||||
if (recursive)
|
||||
tbl->prune(true);
|
||||
|
||||
if (tbl->empty())
|
||||
{
|
||||
it = map_.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void table::clear() noexcept
|
||||
{
|
||||
map_.clear();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
table::map_iterator table::insert_with_hint(const_iterator hint, key && k, impl::node_ptr && v)
|
||||
{
|
||||
return map_.emplace_hint(const_map_iterator{ hint }, std::move(k), std::move(v));
|
||||
}
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool TOML_CALLCONV table::equal(const table& lhs, const table& rhs) noexcept
|
||||
{
|
||||
if (&lhs == &rhs)
|
||||
return true;
|
||||
if (lhs.map_.size() != rhs.map_.size())
|
||||
return false;
|
||||
|
||||
for (auto l = lhs.map_.begin(), r = rhs.map_.begin(), e = lhs.map_.end(); l != e; l++, r++)
|
||||
{
|
||||
if (l->first != r->first)
|
||||
return false;
|
||||
|
||||
const auto lhs_type = l->second->type();
|
||||
const node& rhs_ = *r->second;
|
||||
const auto rhs_type = rhs_.type();
|
||||
if (lhs_type != rhs_type)
|
||||
return false;
|
||||
|
||||
const bool equal = l->second->visit(
|
||||
[&](const auto& lhs_) noexcept
|
||||
{ return lhs_ == *reinterpret_cast<std::remove_reference_t<decltype(lhs_)>*>(&rhs_); });
|
||||
if (!equal)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,153 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "std_vector.hpp"
|
||||
#include "formatter.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A wrapper for printing TOML objects out to a stream as formatted TOML.
|
||||
///
|
||||
/// \availability This class is only available when #TOML_ENABLE_FORMATTERS is enabled.
|
||||
///
|
||||
/// \remarks You generally don't need to create an instance of this class explicitly; the stream
|
||||
/// operators of the TOML node types already print themselves out using this formatter.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto tbl = toml::table{
|
||||
/// { "description", "This is some TOML, yo." },
|
||||
/// { "fruit", toml::array{ "apple", "orange", "pear" } },
|
||||
/// { "numbers", toml::array{ 1, 2, 3, 4, 5 } },
|
||||
/// { "table", toml::table{ { "foo", "bar" } } }
|
||||
/// };
|
||||
///
|
||||
/// // these two lines are equivalent:
|
||||
/// std::cout << toml::toml_formatter{ tbl } << "\n";
|
||||
/// std::cout << tbl << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// description = "This is some TOML, yo."
|
||||
/// fruit = ["apple", "orange", "pear"]
|
||||
/// numbers = [1, 2, 3, 4, 5]
|
||||
///
|
||||
/// [table]
|
||||
/// foo = "bar"
|
||||
/// \eout
|
||||
class TOML_EXPORTED_CLASS toml_formatter : impl::formatter
|
||||
{
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
using base = impl::formatter;
|
||||
std::vector<const key*> key_path_;
|
||||
bool pending_table_separator_ = false;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_pending_table_separator();
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const key&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_inline(const toml::table&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::array&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::table&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print();
|
||||
|
||||
static constexpr impl::formatter_constants constants = { format_flags::none, // mandatory
|
||||
format_flags::none, // ignored
|
||||
"inf"sv,
|
||||
"-inf"sv,
|
||||
"nan"sv,
|
||||
"true"sv,
|
||||
"false"sv };
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief The default flags for a toml_formatter.
|
||||
static constexpr format_flags default_flags = constants.mandatory_flags //
|
||||
| format_flags::allow_literal_strings //
|
||||
| format_flags::allow_multi_line_strings //
|
||||
| format_flags::allow_unicode_strings //
|
||||
| format_flags::allow_real_tabs_in_strings //
|
||||
| format_flags::allow_binary_integers //
|
||||
| format_flags::allow_octal_integers //
|
||||
| format_flags::allow_hexadecimal_integers //
|
||||
| format_flags::indentation;
|
||||
|
||||
/// \brief Constructs a TOML formatter and binds it to a TOML object.
|
||||
///
|
||||
/// \param source The source TOML object.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit toml_formatter(const toml::node& source, format_flags flags = default_flags) noexcept
|
||||
: base{ &source, nullptr, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#if TOML_DOXYGEN || (TOML_ENABLE_PARSER && !TOML_EXCEPTIONS)
|
||||
|
||||
/// \brief Constructs a TOML formatter and binds it to a toml::parse_result.
|
||||
///
|
||||
/// \availability This constructor is only available when exceptions are disabled.
|
||||
///
|
||||
/// \attention Formatting a failed parse result will simply dump the error message out as-is.
|
||||
/// This will not be valid TOML, but at least gives you something to log or show up in diagnostics:
|
||||
/// \cpp
|
||||
/// std::cout << toml::toml_formatter{ toml::parse("a = 'b'"sv) } // ok
|
||||
/// << "\n\n"
|
||||
/// << toml::toml_formatter{ toml::parse("a = "sv) } // malformed
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
/// \out
|
||||
/// a = 'b'
|
||||
///
|
||||
/// Error while parsing key-value pair: encountered end-of-file
|
||||
/// (error occurred at line 1, column 5)
|
||||
/// \eout
|
||||
/// Use the library with exceptions if you want to avoid this scenario.
|
||||
///
|
||||
/// \param result The parse result.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit toml_formatter(const toml::parse_result& result, format_flags flags = default_flags) noexcept
|
||||
: base{ nullptr, &result, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as formatted TOML.
|
||||
friend std::ostream& operator<<(std::ostream& lhs, toml_formatter& rhs)
|
||||
{
|
||||
rhs.attach(lhs);
|
||||
rhs.key_path_.clear();
|
||||
rhs.print();
|
||||
rhs.detach();
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as formatted TOML (rvalue overload).
|
||||
friend std::ostream& operator<<(std::ostream& lhs, toml_formatter&& rhs)
|
||||
{
|
||||
return lhs << rhs; // as lvalue
|
||||
}
|
||||
};
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,405 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
//# {{
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "toml_formatter.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "value.hpp"
|
||||
#include "table.hpp"
|
||||
#include "array.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include "header_start.hpp"
|
||||
TOML_DISABLE_ARITHMETIC_WARNINGS;
|
||||
|
||||
TOML_ANON_NAMESPACE_START
|
||||
{
|
||||
TOML_INTERNAL_LINKAGE
|
||||
size_t toml_formatter_count_inline_columns(const node& node, size_t line_wrap_cols) noexcept
|
||||
{
|
||||
switch (node.type())
|
||||
{
|
||||
case node_type::table:
|
||||
{
|
||||
auto& tbl = *reinterpret_cast<const table*>(&node);
|
||||
if (tbl.empty())
|
||||
return 2u; // "{}"
|
||||
size_t weight = 3u; // "{ }"
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
weight += k.length() + toml_formatter_count_inline_columns(v, line_wrap_cols) + 2u; // + ", "
|
||||
if (weight >= line_wrap_cols)
|
||||
break;
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
case node_type::array:
|
||||
{
|
||||
auto& arr = *reinterpret_cast<const array*>(&node);
|
||||
if (arr.empty())
|
||||
return 2u; // "[]"
|
||||
size_t weight = 3u; // "[ ]"
|
||||
for (auto& elem : arr)
|
||||
{
|
||||
weight += toml_formatter_count_inline_columns(elem, line_wrap_cols) + 2u; // + ", "
|
||||
if (weight >= line_wrap_cols)
|
||||
break;
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
case node_type::string:
|
||||
{
|
||||
// todo: proper utf8 decoding?
|
||||
// todo: tab awareness?
|
||||
auto& str = (*reinterpret_cast<const value<std::string>*>(&node)).get();
|
||||
return str.length() + 2u; // + ""
|
||||
}
|
||||
|
||||
case node_type::integer:
|
||||
{
|
||||
auto val = (*reinterpret_cast<const value<int64_t>*>(&node)).get();
|
||||
if (!val)
|
||||
return 1u;
|
||||
size_t weight = {};
|
||||
if (val < 0)
|
||||
{
|
||||
weight += 1u;
|
||||
val *= -1;
|
||||
}
|
||||
return weight + static_cast<size_t>(log10(static_cast<double>(val))) + 1u;
|
||||
}
|
||||
|
||||
case node_type::floating_point:
|
||||
{
|
||||
auto val = (*reinterpret_cast<const value<double>*>(&node)).get();
|
||||
if (val == 0.0)
|
||||
return 3u; // "0.0"
|
||||
size_t weight = 2u; // ".0"
|
||||
if (val < 0.0)
|
||||
{
|
||||
weight += 1u;
|
||||
val *= -1.0;
|
||||
}
|
||||
return weight + static_cast<size_t>(abs(log10(val))) + 1u;
|
||||
}
|
||||
|
||||
case node_type::boolean: return 5u;
|
||||
case node_type::date: [[fallthrough]];
|
||||
case node_type::time: return 10u;
|
||||
case node_type::date_time: return 30u;
|
||||
case node_type::none: TOML_UNREACHABLE;
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
TOML_INTERNAL_LINKAGE
|
||||
bool toml_formatter_forces_multiline(const node& node, size_t line_wrap_cols, size_t starting_column_bias) noexcept
|
||||
{
|
||||
return (toml_formatter_count_inline_columns(node, line_wrap_cols) + starting_column_bias) >= line_wrap_cols;
|
||||
}
|
||||
}
|
||||
TOML_ANON_NAMESPACE_END;
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print_pending_table_separator()
|
||||
{
|
||||
if (pending_table_separator_)
|
||||
{
|
||||
print_newline(true);
|
||||
print_newline(true);
|
||||
pending_table_separator_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print(const key& k)
|
||||
{
|
||||
print_string(k.str(), false, true, false);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print_inline(const table& tbl)
|
||||
{
|
||||
if (tbl.empty())
|
||||
{
|
||||
print_unformatted("{}"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
print_unformatted("{ "sv);
|
||||
|
||||
bool first = false;
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
if (first)
|
||||
print_unformatted(", "sv);
|
||||
first = true;
|
||||
|
||||
print(k);
|
||||
if (terse_kvps())
|
||||
print_unformatted("="sv);
|
||||
else
|
||||
print_unformatted(" = "sv);
|
||||
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print_inline(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
|
||||
print_unformatted(" }"sv);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print(const array& arr)
|
||||
{
|
||||
if (arr.empty())
|
||||
{
|
||||
print_unformatted("[]"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto original_indent = indent();
|
||||
const auto multiline = force_multiline_arrays()
|
||||
|| TOML_ANON_NAMESPACE::toml_formatter_forces_multiline(
|
||||
arr,
|
||||
120u,
|
||||
indent_columns() * static_cast<size_t>(original_indent < 0 ? 0 : original_indent));
|
||||
|
||||
print_unformatted("["sv);
|
||||
|
||||
if (multiline)
|
||||
{
|
||||
if (original_indent < 0)
|
||||
indent(0);
|
||||
if (indent_array_elements())
|
||||
increase_indent();
|
||||
}
|
||||
else
|
||||
print_unformatted(' ');
|
||||
|
||||
for (size_t i = 0; i < arr.size(); i++)
|
||||
{
|
||||
if (i > 0u)
|
||||
{
|
||||
print_unformatted(',');
|
||||
if (!multiline)
|
||||
print_unformatted(' ');
|
||||
}
|
||||
|
||||
if (multiline)
|
||||
{
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
}
|
||||
|
||||
auto& v = arr[i];
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print_inline(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
if (multiline)
|
||||
{
|
||||
indent(original_indent);
|
||||
print_newline(true);
|
||||
print_indent();
|
||||
}
|
||||
else
|
||||
print_unformatted(' ');
|
||||
|
||||
print_unformatted("]"sv);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print(const table& tbl)
|
||||
{
|
||||
static constexpr auto is_non_inline_array_of_tables = [](const node& n) noexcept
|
||||
{
|
||||
const auto arr = n.as_array();
|
||||
if (!arr || !arr->is_array_of_tables())
|
||||
return false;
|
||||
|
||||
return !reinterpret_cast<const table*>(&(*arr)[0])->is_inline();
|
||||
};
|
||||
|
||||
// values, arrays, and inline tables/table arrays
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
const auto type = v.type();
|
||||
if ((type == node_type::table && !reinterpret_cast<const table*>(&v)->is_inline())
|
||||
|| (type == node_type::array && is_non_inline_array_of_tables(v)))
|
||||
continue;
|
||||
|
||||
pending_table_separator_ = true;
|
||||
print_newline();
|
||||
print_indent();
|
||||
print(k);
|
||||
if (terse_kvps())
|
||||
print_unformatted("="sv);
|
||||
else
|
||||
print_unformatted(" = "sv);
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print_inline(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
|
||||
const auto print_key_path = [&]()
|
||||
{
|
||||
size_t i{};
|
||||
for (const auto k : key_path_)
|
||||
{
|
||||
if (i++)
|
||||
print_unformatted('.');
|
||||
print(*k);
|
||||
}
|
||||
};
|
||||
|
||||
// non-inline tables
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
const auto type = v.type();
|
||||
if (type != node_type::table || reinterpret_cast<const table*>(&v)->is_inline())
|
||||
continue;
|
||||
auto& child_tbl = *reinterpret_cast<const table*>(&v);
|
||||
|
||||
// we can skip indenting and emitting the headers for tables that only contain other tables
|
||||
// (so we don't over-nest)
|
||||
size_t child_value_count{}; // includes inline tables and non-table arrays
|
||||
size_t child_table_count{};
|
||||
size_t child_table_array_count{};
|
||||
for (auto&& [child_k, child_v] : child_tbl)
|
||||
{
|
||||
TOML_UNUSED(child_k);
|
||||
const auto child_type = child_v.type();
|
||||
TOML_ASSUME(child_type != node_type::none);
|
||||
switch (child_type)
|
||||
{
|
||||
case node_type::table:
|
||||
if (reinterpret_cast<const table*>(&child_v)->is_inline())
|
||||
child_value_count++;
|
||||
else
|
||||
child_table_count++;
|
||||
break;
|
||||
|
||||
case node_type::array:
|
||||
if (is_non_inline_array_of_tables(child_v))
|
||||
child_table_array_count++;
|
||||
else
|
||||
child_value_count++;
|
||||
break;
|
||||
|
||||
default: child_value_count++;
|
||||
}
|
||||
}
|
||||
bool skip_self = false;
|
||||
if (child_value_count == 0u && (child_table_count > 0u || child_table_array_count > 0u))
|
||||
skip_self = true;
|
||||
|
||||
key_path_.push_back(&k);
|
||||
|
||||
if (!skip_self)
|
||||
{
|
||||
print_pending_table_separator();
|
||||
if (indent_sub_tables())
|
||||
increase_indent();
|
||||
print_indent();
|
||||
print_unformatted("["sv);
|
||||
print_key_path();
|
||||
print_unformatted("]"sv);
|
||||
pending_table_separator_ = true;
|
||||
}
|
||||
|
||||
print(child_tbl);
|
||||
|
||||
key_path_.pop_back();
|
||||
if (!skip_self && indent_sub_tables())
|
||||
decrease_indent();
|
||||
}
|
||||
|
||||
// table arrays
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
if (!is_non_inline_array_of_tables(v))
|
||||
continue;
|
||||
auto& arr = *reinterpret_cast<const array*>(&v);
|
||||
|
||||
if (indent_sub_tables())
|
||||
increase_indent();
|
||||
key_path_.push_back(&k);
|
||||
|
||||
for (size_t i = 0; i < arr.size(); i++)
|
||||
{
|
||||
print_pending_table_separator();
|
||||
print_indent();
|
||||
print_unformatted("[["sv);
|
||||
print_key_path();
|
||||
print_unformatted("]]"sv);
|
||||
pending_table_separator_ = true;
|
||||
print(*reinterpret_cast<const table*>(&arr[i]));
|
||||
}
|
||||
|
||||
key_path_.pop_back();
|
||||
if (indent_sub_tables())
|
||||
decrease_indent();
|
||||
}
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void toml_formatter::print()
|
||||
{
|
||||
if (dump_failed_parse_result())
|
||||
return;
|
||||
|
||||
switch (auto source_type = source().type())
|
||||
{
|
||||
case node_type::table:
|
||||
{
|
||||
auto& tbl = *reinterpret_cast<const table*>(&source());
|
||||
if (tbl.is_inline())
|
||||
print_inline(tbl);
|
||||
else
|
||||
{
|
||||
decrease_indent(); // so root kvps and tables have the same indent
|
||||
print(tbl);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&source())); break;
|
||||
|
||||
default: print_value(source(), source_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,197 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "unicode_autogenerated.hpp"
|
||||
#include "header_start.hpp"
|
||||
/// \cond
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_string_delimiter(char32_t c) noexcept
|
||||
{
|
||||
return c == U'"' || c == U'\'';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_ascii_letter(char32_t c) noexcept
|
||||
{
|
||||
return (c >= U'a' && c <= U'z') || (c >= U'A' && c <= U'Z');
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_binary_digit(char32_t c) noexcept
|
||||
{
|
||||
return c == U'0' || c == U'1';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_octal_digit(char32_t c) noexcept
|
||||
{
|
||||
return (c >= U'0' && c <= U'7');
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_decimal_digit(char32_t c) noexcept
|
||||
{
|
||||
return (c >= U'0' && c <= U'9');
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_hexadecimal_digit(char32_t c) noexcept
|
||||
{
|
||||
return U'0' <= c && c <= U'f' && (1ull << (static_cast<uint_least64_t>(c) - 0x30u)) & 0x7E0000007E03FFull;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TOML_CONST_GETTER
|
||||
constexpr uint_least32_t hex_to_dec(const T c) noexcept
|
||||
{
|
||||
if constexpr (std::is_same_v<remove_cvref<T>, uint_least32_t>)
|
||||
return c >= 0x41u // >= 'A'
|
||||
? 10u + (c | 0x20u) - 0x61u // - 'a'
|
||||
: c - 0x30u // - '0'
|
||||
;
|
||||
else
|
||||
return hex_to_dec(static_cast<uint_least32_t>(c));
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_horizontal_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return is_ascii_horizontal_whitespace(c) || is_non_ascii_horizontal_whitespace(c);
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_vertical_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return is_ascii_vertical_whitespace(c) || is_non_ascii_vertical_whitespace(c);
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return is_horizontal_whitespace(c) || is_vertical_whitespace(c);
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_bare_key_character(char32_t c) noexcept
|
||||
{
|
||||
return is_ascii_bare_key_character(c)
|
||||
#if TOML_LANG_UNRELEASED // toml/pull/891 (unicode bare keys)
|
||||
|| is_non_ascii_bare_key_character(c)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_value_terminator(char32_t c) noexcept
|
||||
{
|
||||
return is_whitespace(c) || c == U']' || c == U'}' || c == U',' || c == U'#';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_control_character(char c) noexcept
|
||||
{
|
||||
return c <= '\u001F' || c == '\u007F';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_control_character(char32_t c) noexcept
|
||||
{
|
||||
return c <= U'\u001F' || c == U'\u007F';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_nontab_control_character(char32_t c) noexcept
|
||||
{
|
||||
return c <= U'\u0008' || (c >= U'\u000A' && c <= U'\u001F') || c == U'\u007F';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_unicode_surrogate(char32_t c) noexcept
|
||||
{
|
||||
return c >= 0xD800u && c <= 0xDFFF;
|
||||
}
|
||||
|
||||
struct utf8_decoder
|
||||
{
|
||||
// utf8_decoder based on this: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
|
||||
uint_least32_t state{};
|
||||
char32_t codepoint{};
|
||||
|
||||
static constexpr uint8_t state_table[]{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6,
|
||||
6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||
|
||||
0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12,
|
||||
12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12,
|
||||
36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12
|
||||
};
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
constexpr bool error() const noexcept
|
||||
{
|
||||
return state == uint_least32_t{ 12u };
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
constexpr bool has_code_point() const noexcept
|
||||
{
|
||||
return state == uint_least32_t{};
|
||||
}
|
||||
|
||||
TOML_PURE_INLINE_GETTER
|
||||
constexpr bool needs_more_input() const noexcept
|
||||
{
|
||||
return !has_code_point() && !error();
|
||||
}
|
||||
|
||||
constexpr void operator()(uint8_t byte) noexcept
|
||||
{
|
||||
TOML_ASSERT_ASSUME(!error());
|
||||
|
||||
const auto type = state_table[byte];
|
||||
|
||||
codepoint = static_cast<char32_t>(has_code_point() ? (uint_least32_t{ 255u } >> type) & byte
|
||||
: (byte & uint_least32_t{ 63u })
|
||||
| (static_cast<uint_least32_t>(codepoint) << 6));
|
||||
|
||||
state = state_table[state + uint_least32_t{ 256u } + type];
|
||||
}
|
||||
|
||||
TOML_ALWAYS_INLINE
|
||||
constexpr void operator()(char c) noexcept
|
||||
{
|
||||
operator()(static_cast<uint8_t>(c));
|
||||
}
|
||||
|
||||
TOML_ALWAYS_INLINE
|
||||
constexpr void reset() noexcept
|
||||
{
|
||||
state = {};
|
||||
}
|
||||
};
|
||||
|
||||
TOML_PURE_GETTER
|
||||
TOML_ATTR(nonnull)
|
||||
bool is_ascii(const char* str, size_t len) noexcept;
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
/// \endcond
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,60 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
//# {{
|
||||
#include "preprocessor.hpp"
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
|
||||
#include "unicode.hpp"
|
||||
#include "simd.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_PURE_GETTER
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
bool is_ascii(const char* str, size_t len) noexcept
|
||||
{
|
||||
const char* const end = str + len;
|
||||
|
||||
#if TOML_HAS_SSE2 && (128 % CHAR_BIT) == 0
|
||||
{
|
||||
constexpr size_t chars_per_vector = 128u / CHAR_BIT;
|
||||
|
||||
if (const size_t simdable = len - (len % chars_per_vector))
|
||||
{
|
||||
__m128i mask = _mm_setzero_si128();
|
||||
for (const char* const e = str + simdable; str < e; str += chars_per_vector)
|
||||
{
|
||||
const __m128i current_bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(str));
|
||||
mask = _mm_or_si128(mask, current_bytes);
|
||||
}
|
||||
const __m128i has_error = _mm_cmpgt_epi8(_mm_setzero_si128(), mask);
|
||||
|
||||
#if TOML_HAS_SSE4_1
|
||||
if (!_mm_testz_si128(has_error, has_error))
|
||||
return false;
|
||||
#else
|
||||
if (_mm_movemask_epi8(_mm_cmpeq_epi8(has_error, _mm_setzero_si128())) != 0xFFFF)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; str < end; str++)
|
||||
if (static_cast<unsigned char>(*str) > 127u)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
@@ -0,0 +1,182 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#include "header_start.hpp"
|
||||
/// \cond
|
||||
|
||||
#if TOML_GCC && TOML_GCC < 9
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("O1") // codegen bugs
|
||||
#endif
|
||||
|
||||
// the functions in this namespace block are automatically generated by a tool - they are not meant to be hand-edited
|
||||
|
||||
TOML_IMPL_NAMESPACE_START
|
||||
{
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_ascii_horizontal_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return c == U'\t' || c == U' ';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_non_ascii_horizontal_whitespace(char32_t c) noexcept
|
||||
{
|
||||
// 20 code units from 8 ranges (spanning a search area of 65120)
|
||||
|
||||
if (c < U'\xA0' || c > U'\uFEFF')
|
||||
return false;
|
||||
|
||||
const auto child_index_0 = (static_cast<uint_least64_t>(c) - 0xA0ull) / 0x3FAull;
|
||||
if ((1ull << child_index_0) & 0x7FFFFFFFFFFFF75Eull)
|
||||
return false;
|
||||
if (c == U'\xA0' || c == U'\u3000' || c == U'\uFEFF')
|
||||
return true;
|
||||
switch (child_index_0)
|
||||
{
|
||||
case 0x05: return c == U'\u1680' || c == U'\u180E';
|
||||
case 0x07:
|
||||
return (U'\u2000' <= c && c <= U'\u200B') || (U'\u205F' <= c && c <= U'\u2060') || c == U'\u202F';
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_ascii_vertical_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return c >= U'\n' && c <= U'\r';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_non_ascii_vertical_whitespace(char32_t c) noexcept
|
||||
{
|
||||
return (U'\u2028' <= c && c <= U'\u2029') || c == U'\x85';
|
||||
}
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_ascii_bare_key_character(char32_t c) noexcept
|
||||
{
|
||||
#if TOML_LANG_UNRELEASED // toml/issues/644 ('+' in bare keys)
|
||||
if TOML_UNLIKELY(c == U'+')
|
||||
return true;
|
||||
#endif
|
||||
// 64 code units from 5 ranges (spanning a search area of 78)
|
||||
|
||||
if (c < U'-' || c > U'z')
|
||||
return false;
|
||||
|
||||
return (((static_cast<uint_least64_t>(c) - 0x2Dull) / 0x40ull) != 0ull)
|
||||
|| ((1ull << (static_cast<uint_least64_t>(c) - 0x2Dull)) & 0xFFF43FFFFFF01FF9ull);
|
||||
}
|
||||
|
||||
#if TOML_LANG_UNRELEASED // toml/pull/891 (unicode bare keys)
|
||||
|
||||
TOML_CONST_GETTER
|
||||
constexpr bool is_non_ascii_bare_key_character(char32_t c) noexcept
|
||||
{
|
||||
// 971732 code units from 16 ranges (spanning a search area of 982862)
|
||||
|
||||
if (c < U'\xB2' || c > U'\U000EFFFF')
|
||||
return false;
|
||||
|
||||
const auto child_index_0 = (static_cast<uint_least64_t>(c) - 0xB2ull) / 0x3BFEull;
|
||||
if ((1ull << child_index_0) & 0xFFFFFFFFFFFFFFE6ull)
|
||||
return true;
|
||||
switch (child_index_0)
|
||||
{
|
||||
case 0x00: // [0] 00B2 - 3CAF
|
||||
{
|
||||
// 12710 code units from 13 ranges (spanning a search area of 15358)
|
||||
|
||||
TOML_ASSUME(c >= U'\xB2' && c <= U'\u3CAF');
|
||||
|
||||
constexpr uint_least64_t bitmask_table_1[] = {
|
||||
0xFFFFFFDFFFFFDC83u, 0xFFFFFFFFFFFFFFDFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFEFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0x000000000C003FFFu, 0xC000000000006000u, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0x000000003FFFFFFFu,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0xFFFFC00000000000u, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0x0000000000003FFFu, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u,
|
||||
0x0000000000000000u, 0xFFFFFFFFFFFFC000u, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0x3FFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFF8000u, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu,
|
||||
0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0x3FFFFFFFFFFFFFFFu,
|
||||
};
|
||||
return bitmask_table_1[(static_cast<uint_least64_t>(c) - 0xB2ull) / 0x40ull]
|
||||
& (0x1ull << ((static_cast<uint_least64_t>(c) - 0xB2ull) % 0x40ull));
|
||||
}
|
||||
case 0x03: return c <= U'\uD7FF';
|
||||
case 0x04:
|
||||
return (U'\uF900' <= c && c <= U'\uFDCF') || (U'\uFDF0' <= c && c <= U'\uFFFD') || U'\U00010000' <= c;
|
||||
default: TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
TOML_UNREACHABLE;
|
||||
}
|
||||
|
||||
#endif // TOML_LANG_UNRELEASED
|
||||
}
|
||||
TOML_IMPL_NAMESPACE_END;
|
||||
|
||||
#if TOML_GCC && TOML_GCC < 9
|
||||
#pragma GCC pop_options
|
||||
#endif
|
||||
|
||||
/// \endcond
|
||||
#include "header_end.hpp"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#define TOML_LIB_MAJOR 3
|
||||
#define TOML_LIB_MINOR 4
|
||||
#define TOML_LIB_PATCH 0
|
||||
|
||||
#define TOML_LANG_MAJOR 1
|
||||
#define TOML_LANG_MINOR 0
|
||||
#define TOML_LANG_PATCH 0
|
||||
@@ -0,0 +1,139 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "formatter.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
/// \brief A wrapper for printing TOML objects out to a stream as formatted YAML.
|
||||
///
|
||||
/// \availability This class is only available when #TOML_ENABLE_FORMATTERS is enabled.
|
||||
///
|
||||
/// \detail \cpp
|
||||
/// auto some_toml = toml::parse(R"(
|
||||
/// [fruit]
|
||||
/// apple.color = "red"
|
||||
/// apple.taste.sweet = true
|
||||
///
|
||||
/// [fruit.apple.texture]
|
||||
/// smooth = true
|
||||
/// )"sv);
|
||||
/// std::cout << toml::yaml_formatter{ some_toml } << "\n";
|
||||
/// \ecpp
|
||||
///
|
||||
/// \out
|
||||
/// fruit:
|
||||
/// apple:
|
||||
/// color: red
|
||||
/// taste:
|
||||
/// sweet: true
|
||||
/// texture:
|
||||
/// smooth: true
|
||||
/// \eout
|
||||
class TOML_EXPORTED_CLASS yaml_formatter : impl::formatter
|
||||
{
|
||||
private:
|
||||
/// \cond
|
||||
|
||||
using base = impl::formatter;
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print_yaml_string(const value<std::string>&);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::table&, bool = false);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print(const toml::array&, bool = false);
|
||||
|
||||
TOML_EXPORTED_MEMBER_FUNCTION
|
||||
void print();
|
||||
|
||||
static constexpr impl::formatter_constants constants = {
|
||||
//
|
||||
format_flags::quote_dates_and_times | format_flags::indentation, // mandatory
|
||||
format_flags::allow_multi_line_strings, // ignored
|
||||
".inf"sv,
|
||||
"-.inf"sv,
|
||||
".NAN"sv,
|
||||
"true"sv,
|
||||
"false"sv
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
||||
public:
|
||||
/// \brief The default flags for a yaml_formatter.
|
||||
static constexpr format_flags default_flags = constants.mandatory_flags //
|
||||
| format_flags::allow_literal_strings //
|
||||
| format_flags::allow_unicode_strings //
|
||||
| format_flags::allow_octal_integers //
|
||||
| format_flags::allow_hexadecimal_integers;
|
||||
|
||||
/// \brief Constructs a YAML formatter and binds it to a TOML object.
|
||||
///
|
||||
/// \param source The source TOML object.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit yaml_formatter(const toml::node& source, format_flags flags = default_flags) noexcept
|
||||
: base{ &source, nullptr, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#if TOML_DOXYGEN || (TOML_ENABLE_PARSER && !TOML_EXCEPTIONS)
|
||||
|
||||
/// \brief Constructs a YAML formatter and binds it to a toml::parse_result.
|
||||
///
|
||||
/// \availability This constructor is only available when exceptions are disabled.
|
||||
///
|
||||
/// \attention Formatting a failed parse result will simply dump the error message out as-is.
|
||||
/// This will not be valid YAML, but at least gives you something to log or show up in diagnostics:
|
||||
/// \cpp
|
||||
/// std::cout << toml::yaml_formatter{ toml::parse("a = 'b'"sv) } // ok
|
||||
/// << "\n\n"
|
||||
/// << toml::yaml_formatter{ toml::parse("a = "sv) } // malformed
|
||||
/// << "\n";
|
||||
/// \ecpp
|
||||
/// \out
|
||||
/// a: b
|
||||
///
|
||||
/// Error while parsing key-value pair: encountered end-of-file
|
||||
/// (error occurred at line 1, column 5)
|
||||
/// \eout
|
||||
/// Use the library with exceptions if you want to avoid this scenario.
|
||||
///
|
||||
/// \param result The parse result.
|
||||
/// \param flags Format option flags.
|
||||
TOML_NODISCARD_CTOR
|
||||
explicit yaml_formatter(const toml::parse_result& result, format_flags flags = default_flags) noexcept
|
||||
: base{ nullptr, &result, constants, { flags, " "sv } }
|
||||
{}
|
||||
|
||||
#endif
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as YAML.
|
||||
friend std::ostream& TOML_CALLCONV operator<<(std::ostream& lhs, yaml_formatter& rhs)
|
||||
{
|
||||
rhs.attach(lhs);
|
||||
rhs.print();
|
||||
rhs.detach();
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/// \brief Prints the bound TOML object out to the stream as YAML (rvalue overload).
|
||||
friend std::ostream& TOML_CALLCONV operator<<(std::ostream& lhs, yaml_formatter&& rhs)
|
||||
{
|
||||
return lhs << rhs; // as lvalue
|
||||
}
|
||||
};
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,165 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "preprocessor.hpp"
|
||||
//# {{
|
||||
#if !TOML_IMPLEMENTATION
|
||||
#error This is an implementation-only header.
|
||||
#endif
|
||||
//# }}
|
||||
#if TOML_ENABLE_FORMATTERS
|
||||
|
||||
#include "yaml_formatter.hpp"
|
||||
#include "print_to_stream.hpp"
|
||||
#include "table.hpp"
|
||||
#include "array.hpp"
|
||||
#include "header_start.hpp"
|
||||
|
||||
TOML_NAMESPACE_START
|
||||
{
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void yaml_formatter::print_yaml_string(const value<std::string>& str)
|
||||
{
|
||||
if (str->empty())
|
||||
{
|
||||
base::print(str);
|
||||
return;
|
||||
}
|
||||
|
||||
bool contains_newline = false;
|
||||
for (auto c = str->c_str(), e = str->c_str() + str->length(); c < e && !contains_newline; c++)
|
||||
contains_newline = *c == '\n';
|
||||
|
||||
if (contains_newline)
|
||||
{
|
||||
print_unformatted("|-"sv);
|
||||
|
||||
increase_indent();
|
||||
|
||||
auto line_end = str->c_str() - 1u;
|
||||
const auto end = str->c_str() + str->length();
|
||||
while (line_end != end)
|
||||
{
|
||||
auto line_start = line_end + 1u;
|
||||
line_end = line_start;
|
||||
for (; line_end != end && *line_end != '\n'; line_end++)
|
||||
;
|
||||
|
||||
if TOML_LIKELY(line_start != line_end || line_end != end)
|
||||
{
|
||||
print_newline();
|
||||
print_indent();
|
||||
print_unformatted(std::string_view{ line_start, static_cast<size_t>(line_end - line_start) });
|
||||
}
|
||||
}
|
||||
|
||||
decrease_indent();
|
||||
}
|
||||
else
|
||||
print_string(*str, false, true);
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void yaml_formatter::print(const toml::table& tbl, bool parent_is_array)
|
||||
{
|
||||
if (tbl.empty())
|
||||
{
|
||||
print_unformatted("{}"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
increase_indent();
|
||||
|
||||
for (auto&& [k, v] : tbl)
|
||||
{
|
||||
if (!parent_is_array)
|
||||
{
|
||||
print_newline();
|
||||
print_indent();
|
||||
}
|
||||
parent_is_array = false;
|
||||
|
||||
print_string(k.str(), false, true);
|
||||
if (terse_kvps())
|
||||
print_unformatted(":"sv);
|
||||
else
|
||||
print_unformatted(": "sv);
|
||||
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print(*reinterpret_cast<const table*>(&v)); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v)); break;
|
||||
case node_type::string: print_yaml_string(*reinterpret_cast<const value<std::string>*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
|
||||
decrease_indent();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void yaml_formatter::print(const toml::array& arr, bool parent_is_array)
|
||||
{
|
||||
if (arr.empty())
|
||||
{
|
||||
print_unformatted("[]"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
increase_indent();
|
||||
|
||||
for (auto&& v : arr)
|
||||
{
|
||||
if (!parent_is_array)
|
||||
{
|
||||
print_newline();
|
||||
print_indent();
|
||||
}
|
||||
parent_is_array = false;
|
||||
|
||||
print_unformatted("- "sv);
|
||||
|
||||
const auto type = v.type();
|
||||
TOML_ASSUME(type != node_type::none);
|
||||
switch (type)
|
||||
{
|
||||
case node_type::table: print(*reinterpret_cast<const table*>(&v), true); break;
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&v), true); break;
|
||||
case node_type::string: print_yaml_string(*reinterpret_cast<const value<std::string>*>(&v)); break;
|
||||
default: print_value(v, type);
|
||||
}
|
||||
}
|
||||
|
||||
decrease_indent();
|
||||
}
|
||||
|
||||
TOML_EXTERNAL_LINKAGE
|
||||
void yaml_formatter::print()
|
||||
{
|
||||
if (dump_failed_parse_result())
|
||||
return;
|
||||
|
||||
switch (auto source_type = source().type())
|
||||
{
|
||||
case node_type::table:
|
||||
decrease_indent(); // so root kvps and tables have the same indent
|
||||
print(*reinterpret_cast<const table*>(&source()));
|
||||
break;
|
||||
|
||||
case node_type::array: print(*reinterpret_cast<const array*>(&source())); break;
|
||||
|
||||
case node_type::string: print_yaml_string(*reinterpret_cast<const value<std::string>*>(&source())); break;
|
||||
|
||||
default: print_value(source(), source_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
TOML_NAMESPACE_END;
|
||||
|
||||
#include "header_end.hpp"
|
||||
#endif // TOML_ENABLE_FORMATTERS
|
||||
@@ -0,0 +1,7 @@
|
||||
// this file is just for backwards compatibility.
|
||||
#ifndef TOMLPLUSPLUS_H
|
||||
#define TOMLPLUSPLUS_H
|
||||
|
||||
#include "toml.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,234 @@
|
||||
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
|
||||
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#ifndef TOMLPLUSPLUS_HPP
|
||||
#define TOMLPLUSPLUS_HPP
|
||||
|
||||
#define INCLUDE_TOMLPLUSPLUS_H // old guard name used pre-v3
|
||||
#define TOMLPLUSPLUS_H // guard name used in the legacy toml.h
|
||||
|
||||
#include "impl/preprocessor.hpp"
|
||||
|
||||
TOML_PUSH_WARNINGS;
|
||||
TOML_DISABLE_SPAM_WARNINGS;
|
||||
TOML_DISABLE_SWITCH_WARNINGS;
|
||||
TOML_DISABLE_SUGGEST_ATTR_WARNINGS;
|
||||
|
||||
// misc warning false-positives
|
||||
#if TOML_MSVC
|
||||
#pragma warning(disable : 5031) // #pragma warning(pop): likely mismatch
|
||||
#if TOML_SHARED_LIB
|
||||
#pragma warning(disable : 4251) // dll exports for std lib types
|
||||
#endif
|
||||
#elif TOML_CLANG
|
||||
TOML_PRAGMA_CLANG(diagnostic ignored "-Wheader-hygiene")
|
||||
#if TOML_CLANG >= 12
|
||||
TOML_PRAGMA_CLANG(diagnostic ignored "-Wc++20-extensions")
|
||||
#endif
|
||||
#if TOML_CLANG == 13
|
||||
TOML_PRAGMA_CLANG(diagnostic ignored "-Wreserved-identifier")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include "impl/std_new.hpp"
|
||||
#include "impl/std_string.hpp"
|
||||
#include "impl/std_optional.hpp"
|
||||
#include "impl/forward_declarations.hpp"
|
||||
#include "impl/print_to_stream.hpp"
|
||||
#include "impl/source_region.hpp"
|
||||
#include "impl/date_time.hpp"
|
||||
#include "impl/at_path.hpp"
|
||||
#include "impl/path.hpp"
|
||||
#include "impl/node.hpp"
|
||||
#include "impl/node_view.hpp"
|
||||
#include "impl/value.hpp"
|
||||
#include "impl/make_node.hpp"
|
||||
#include "impl/array.hpp"
|
||||
#include "impl/key.hpp"
|
||||
#include "impl/table.hpp"
|
||||
#include "impl/unicode_autogenerated.hpp"
|
||||
#include "impl/unicode.hpp"
|
||||
#include "impl/parse_error.hpp"
|
||||
#include "impl/parse_result.hpp"
|
||||
#include "impl/parser.hpp"
|
||||
#include "impl/formatter.hpp"
|
||||
#include "impl/toml_formatter.hpp"
|
||||
#include "impl/json_formatter.hpp"
|
||||
#include "impl/yaml_formatter.hpp"
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
#if TOML_IMPLEMENTATION
|
||||
|
||||
#include "impl/std_string.inl"
|
||||
#include "impl/print_to_stream.inl"
|
||||
#include "impl/node.inl"
|
||||
#include "impl/at_path.inl"
|
||||
#include "impl/path.inl"
|
||||
#include "impl/array.inl"
|
||||
#include "impl/table.inl"
|
||||
#include "impl/unicode.inl"
|
||||
#include "impl/parser.inl"
|
||||
#include "impl/formatter.inl"
|
||||
#include "impl/toml_formatter.inl"
|
||||
#include "impl/json_formatter.inl"
|
||||
#include "impl/yaml_formatter.inl"
|
||||
|
||||
#endif // TOML_IMPLEMENTATION
|
||||
|
||||
TOML_POP_WARNINGS;
|
||||
|
||||
// macro hygiene
|
||||
#if TOML_UNDEF_MACROS
|
||||
#undef TOML_ABI_NAMESPACE_BOOL
|
||||
#undef TOML_ABI_NAMESPACE_END
|
||||
#undef TOML_ABI_NAMESPACE_START
|
||||
#undef TOML_ABI_NAMESPACES
|
||||
#undef TOML_ABSTRACT_INTERFACE
|
||||
#undef TOML_ALWAYS_INLINE
|
||||
#undef TOML_ANON_NAMESPACE
|
||||
#undef TOML_ANON_NAMESPACE_END
|
||||
#undef TOML_ANON_NAMESPACE_START
|
||||
#undef TOML_ARCH_AMD64
|
||||
#undef TOML_ARCH_ARM
|
||||
#undef TOML_ARCH_ARM32
|
||||
#undef TOML_ARCH_ARM64
|
||||
#undef TOML_ARCH_BITNESS
|
||||
#undef TOML_ARCH_ITANIUM
|
||||
#undef TOML_ARCH_X64
|
||||
#undef TOML_ARCH_X86
|
||||
#undef TOML_ASSERT
|
||||
#undef TOML_ASSERT_ASSUME
|
||||
#undef TOML_ASSUME
|
||||
#undef TOML_ASYMMETRICAL_EQUALITY_OPS
|
||||
#undef TOML_ATTR
|
||||
#undef TOML_CLANG
|
||||
#undef TOML_CLANG_VERSION
|
||||
#undef TOML_CLOSED_ENUM
|
||||
#undef TOML_CLOSED_FLAGS_ENUM
|
||||
#undef TOML_COMPILER_HAS_EXCEPTIONS
|
||||
#undef TOML_COMPILER_HAS_RTTI
|
||||
#undef TOML_CONST
|
||||
#undef TOML_CONST_GETTER
|
||||
#undef TOML_CONST_INLINE_GETTER
|
||||
#undef TOML_CONSTRAINED_TEMPLATE
|
||||
#undef TOML_CPP
|
||||
#undef TOML_DECLSPEC
|
||||
#undef TOML_DELETE_DEFAULTS
|
||||
#undef TOML_DISABLE_ARITHMETIC_WARNINGS
|
||||
#undef TOML_DISABLE_CODE_ANALYSIS_WARNINGS
|
||||
#undef TOML_DISABLE_SPAM_WARNINGS
|
||||
#undef TOML_DISABLE_SPAM_WARNINGS_CLANG_10
|
||||
#undef TOML_DISABLE_SPAM_WARNINGS_CLANG_11
|
||||
#undef TOML_DISABLE_SUGGEST_ATTR_WARNINGS
|
||||
#undef TOML_DISABLE_SWITCH_WARNINGS
|
||||
#undef TOML_DISABLE_WARNINGS
|
||||
#undef TOML_DOXYGEN
|
||||
#undef TOML_EMPTY_BASES
|
||||
#undef TOML_ENABLE_IF
|
||||
#undef TOML_ENABLE_WARNINGS
|
||||
#undef TOML_EVAL_BOOL_0
|
||||
#undef TOML_EVAL_BOOL_1
|
||||
#undef TOML_EXTERNAL_LINKAGE
|
||||
#undef TOML_FLAGS_ENUM
|
||||
#undef TOML_FLOAT_CHARCONV
|
||||
#undef TOML_FLOAT128
|
||||
#undef TOML_FLOAT16_DIG
|
||||
#undef TOML_FLOAT16_LIMITS_SET
|
||||
#undef TOML_FLOAT16_MANT_DIG
|
||||
#undef TOML_FLOAT16_MAX_10_EXP
|
||||
#undef TOML_FLOAT16_MAX_EXP
|
||||
#undef TOML_FLOAT16_MIN_10_EXP
|
||||
#undef TOML_FLOAT16_MIN_EXP
|
||||
#undef TOML_GCC
|
||||
#undef TOML_GCC_LIKE
|
||||
#undef TOML_HAS_ATTR
|
||||
#undef TOML_HAS_BUILTIN
|
||||
#undef TOML_HAS_CHAR8
|
||||
#undef TOML_HAS_CPP_ATTR
|
||||
#undef TOML_HAS_CUSTOM_OPTIONAL_TYPE
|
||||
#undef TOML_HAS_FEATURE
|
||||
#undef TOML_HAS_INCLUDE
|
||||
#undef TOML_HAS_SSE2
|
||||
#undef TOML_HAS_SSE4_1
|
||||
#undef TOML_HIDDEN_CONSTRAINT
|
||||
#undef TOML_ICC
|
||||
#undef TOML_ICC_CL
|
||||
#undef TOML_IMPL_NAMESPACE_END
|
||||
#undef TOML_IMPL_NAMESPACE_START
|
||||
#undef TOML_IMPLEMENTATION
|
||||
#undef TOML_INCLUDE_WINDOWS_H
|
||||
#undef TOML_INLINE_GETTER
|
||||
#undef TOML_INT_CHARCONV
|
||||
#undef TOML_INT128
|
||||
#undef TOML_INTELLISENSE
|
||||
#undef TOML_INTERNAL_LINKAGE
|
||||
#undef TOML_LANG_AT_LEAST
|
||||
#undef TOML_LANG_EFFECTIVE_VERSION
|
||||
#undef TOML_LANG_HIGHER_THAN
|
||||
#undef TOML_LANG_UNRELEASED
|
||||
#undef TOML_LAUNDER
|
||||
#undef TOML_LIFETIME_HOOKS
|
||||
#undef TOML_LIKELY
|
||||
#undef TOML_LIKELY_CASE
|
||||
#undef TOML_LINUX
|
||||
#undef TOML_MAKE_FLAGS
|
||||
#undef TOML_MAKE_FLAGS_
|
||||
#undef TOML_MAKE_FLAGS_1
|
||||
#undef TOML_MAKE_FLAGS_2
|
||||
#undef TOML_MAKE_STRING
|
||||
#undef TOML_MAKE_STRING_1
|
||||
#undef TOML_MAKE_VERSION
|
||||
#undef TOML_MSVC
|
||||
#undef TOML_MSVC_LIKE
|
||||
#undef TOML_NAMESPACE
|
||||
#undef TOML_NEVER_INLINE
|
||||
#undef TOML_NODISCARD
|
||||
#undef TOML_NODISCARD_CTOR
|
||||
#undef TOML_NVCC
|
||||
#undef TOML_OPEN_ENUM
|
||||
#undef TOML_OPEN_FLAGS_ENUM
|
||||
#undef TOML_PARSER_TYPENAME
|
||||
#undef TOML_POP_WARNINGS
|
||||
#undef TOML_PRAGMA_CLANG
|
||||
#undef TOML_PRAGMA_CLANG_GE_10
|
||||
#undef TOML_PRAGMA_CLANG_GE_11
|
||||
#undef TOML_PRAGMA_CLANG_GE_8
|
||||
#undef TOML_PRAGMA_CLANG_GE_9
|
||||
#undef TOML_PRAGMA_GCC
|
||||
#undef TOML_PRAGMA_ICC
|
||||
#undef TOML_PRAGMA_MSVC
|
||||
#undef TOML_PURE
|
||||
#undef TOML_PURE_GETTER
|
||||
#undef TOML_PURE_INLINE_GETTER
|
||||
#undef TOML_PUSH_WARNINGS
|
||||
#undef TOML_REQUIRES
|
||||
#undef TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN
|
||||
#undef TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN_MESSAGE
|
||||
#undef TOML_SA_LIST_BEG
|
||||
#undef TOML_SA_LIST_END
|
||||
#undef TOML_SA_LIST_NEW
|
||||
#undef TOML_SA_LIST_NXT
|
||||
#undef TOML_SA_LIST_SEP
|
||||
#undef TOML_SA_NATIVE_VALUE_TYPE_LIST
|
||||
#undef TOML_SA_NEWLINE
|
||||
#undef TOML_SA_NODE_TYPE_LIST
|
||||
#undef TOML_SA_UNWRAPPED_NODE_TYPE_LIST
|
||||
#undef TOML_SA_VALUE_EXACT_FUNC_MESSAGE
|
||||
#undef TOML_SA_VALUE_FUNC_MESSAGE
|
||||
#undef TOML_SA_VALUE_MESSAGE_CONST_CHAR8
|
||||
#undef TOML_SA_VALUE_MESSAGE_U8STRING_VIEW
|
||||
#undef TOML_SA_VALUE_MESSAGE_WSTRING
|
||||
#undef TOML_SIMPLE_STATIC_ASSERT_MESSAGES
|
||||
#undef TOML_TRIVIAL_ABI
|
||||
#undef TOML_UINT128
|
||||
#undef TOML_UNIX
|
||||
#undef TOML_UNLIKELY
|
||||
#undef TOML_UNLIKELY_CASE
|
||||
#undef TOML_UNREACHABLE
|
||||
#undef TOML_UNUSED
|
||||
#undef TOML_WINDOWS
|
||||
#endif
|
||||
|
||||
#endif // TOMLPLUSPLUS_HPP
|
||||
@@ -0,0 +1,50 @@
|
||||
// Generated by https://github.com/foxglove/foxglove-sdk
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
package foxglove;
|
||||
|
||||
// A single frame of a compressed video bitstream
|
||||
message CompressedVideo {
|
||||
// Timestamp of video frame
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
|
||||
// Frame of reference for the video.
|
||||
//
|
||||
// The origin of the frame is the optical center of the camera. +x points to the right in the video, +y points down, and +z points into the plane of the video.
|
||||
string frame_id = 2;
|
||||
|
||||
// Compressed video frame data.
|
||||
//
|
||||
// For packet-based video codecs this data must begin and end on packet boundaries (no partial packets), and must contain enough video packets to decode exactly one image (either a keyframe or delta frame). Note: Foxglove does not support video streams that include B frames because they require lookahead.
|
||||
//
|
||||
// Specifically, the requirements for different `format` values are:
|
||||
//
|
||||
// - `h264`
|
||||
// - Use Annex B formatted data
|
||||
// - Each CompressedVideo message should contain enough NAL units to decode exactly one video frame
|
||||
// - Each message containing a key frame (IDR) must also include a SPS NAL unit
|
||||
//
|
||||
// - `h265` (HEVC)
|
||||
// - Use Annex B formatted data
|
||||
// - Each CompressedVideo message should contain enough NAL units to decode exactly one video frame
|
||||
// - Each message containing a key frame (IRAP) must also include relevant VPS/SPS/PPS NAL units
|
||||
//
|
||||
// - `vp9`
|
||||
// - Each CompressedVideo message should contain exactly one video frame
|
||||
//
|
||||
// - `av1`
|
||||
// - Use the "Low overhead bitstream format" (section 5.2)
|
||||
// - Each CompressedVideo message should contain enough OBUs to decode exactly one video frame
|
||||
// - Each message containing a key frame must also include a Sequence Header OBU
|
||||
bytes data = 3;
|
||||
|
||||
// Video format.
|
||||
//
|
||||
// Supported values: `h264`, `h265`, `vp9`, `av1`.
|
||||
//
|
||||
// Note: compressed video support is subject to hardware limitations and patent licensing, so not all encodings may be supported on all platforms. See more about [H.265 support](https://caniuse.com/hevc), [VP9 support](https://caniuse.com/webm), and [AV1 support](https://caniuse.com/av1).
|
||||
string format = 4;
|
||||
}
|
||||
+799
-488
File diff suppressed because it is too large
Load Diff
@@ -18,15 +18,7 @@ namespace {
|
||||
|
||||
[[nodiscard]]
|
||||
std::string resolve_client_target(const RuntimeConfig &config) {
|
||||
if (config.input.shm_name.starts_with("cvmmap://")) {
|
||||
return config.input.shm_name;
|
||||
}
|
||||
|
||||
if (!config.input.shm_name.empty() && config.input.shm_name.front() == '/') {
|
||||
return config.input.shm_name.substr(1);
|
||||
}
|
||||
|
||||
return config.input.shm_name;
|
||||
return config.input.uri;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
|
||||
+10
-18
@@ -42,28 +42,20 @@ namespace {
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<ResolvedInputEndpoints, std::string> resolve_input_endpoints(const RuntimeConfig &config) {
|
||||
ResolvedInputEndpoints resolved{
|
||||
.shm_name = config.input.shm_name,
|
||||
.zmq_endpoint = config.input.zmq_endpoint,
|
||||
};
|
||||
|
||||
if (!config.input.shm_name.starts_with("cvmmap://")) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
try {
|
||||
auto target = cvmmap::resolve_cvmmap_target_or_throw(config.input.shm_name);
|
||||
resolved.shm_name = target.shm_name;
|
||||
resolved.zmq_endpoint = target.zmq_addr;
|
||||
auto target = cvmmap::resolve_cvmmap_target_or_throw(config.input.uri);
|
||||
spdlog::info(
|
||||
"ingest real-input URI resolved: shm_name='{}' zmq_endpoint='{}'",
|
||||
resolved.shm_name,
|
||||
resolved.zmq_endpoint);
|
||||
"ingest input URI resolved: uri='{}' shm_name='{}' zmq_endpoint='{}'",
|
||||
config.input.uri,
|
||||
target.shm_name,
|
||||
target.zmq_addr);
|
||||
return ResolvedInputEndpoints{
|
||||
.shm_name = target.shm_name,
|
||||
.zmq_endpoint = target.zmq_addr,
|
||||
};
|
||||
} catch (const std::exception &e) {
|
||||
return std::unexpected(std::string("invalid cvmmap uri in --shm-name: ") + e.what());
|
||||
return std::unexpected(std::string("invalid cvmmap uri in input.uri: ") + e.what());
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
struct SharedMemoryView {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "cvmmap_streamer/encode/encoder_backend.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace cvmmap_streamer::encode {
|
||||
|
||||
std::unique_ptr<EncoderBackend> make_ffmpeg_backend();
|
||||
std::unique_ptr<EncoderBackend> make_gstreamer_legacy_backend();
|
||||
|
||||
std::expected<std::unique_ptr<EncoderBackend>, std::string> make_encoder_backend(const RuntimeConfig &config) {
|
||||
switch (config.encoder.backend) {
|
||||
case EncoderBackendType::FFmpeg:
|
||||
return make_ffmpeg_backend();
|
||||
case EncoderBackendType::GStreamerLegacy: {
|
||||
auto backend = make_gstreamer_legacy_backend();
|
||||
if (!backend) {
|
||||
return std::unexpected("legacy GStreamer backend is not compiled in this build");
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
case EncoderBackendType::Auto:
|
||||
if (config.outputs.rtmp.enabled) {
|
||||
auto backend = make_gstreamer_legacy_backend();
|
||||
if (!backend) {
|
||||
return std::unexpected("RTMP requires the legacy GStreamer backend, but it is not compiled");
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
return make_ffmpeg_backend();
|
||||
}
|
||||
|
||||
return std::unexpected("unknown encoder backend");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
#include "cvmmap_streamer/encode/encoder_backend.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/bsf.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace cvmmap_streamer::encode {
|
||||
|
||||
namespace {
|
||||
|
||||
class FfmpegEncoderBackend final : public EncoderBackend {
|
||||
public:
|
||||
~FfmpegEncoderBackend() override {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::string_view backend_name() const override {
|
||||
return "ffmpeg";
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool using_hardware() const override {
|
||||
return using_hardware_;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> init(const RuntimeConfig &config, const ipc::FrameInfo &frame_info) override {
|
||||
shutdown();
|
||||
|
||||
config_ = &config;
|
||||
frame_info_ = frame_info;
|
||||
codec_ = config.encoder.codec;
|
||||
encoder_pix_fmt_ = pick_encoder_pixel_format(config.encoder.device);
|
||||
|
||||
auto input_pixel_format = to_av_pixel_format(frame_info.pixel_format);
|
||||
if (!input_pixel_format) {
|
||||
return std::unexpected(input_pixel_format.error());
|
||||
}
|
||||
|
||||
input_pix_fmt_ = *input_pixel_format;
|
||||
|
||||
auto encoder_name = pick_encoder_name(config);
|
||||
if (!encoder_name) {
|
||||
return std::unexpected(encoder_name.error());
|
||||
}
|
||||
using_hardware_ = encoder_name->find("nvenc") != std::string::npos;
|
||||
|
||||
const auto *encoder = avcodec_find_encoder_by_name(encoder_name->c_str());
|
||||
if (encoder == nullptr) {
|
||||
return std::unexpected("FFmpeg encoder '" + *encoder_name + "' is unavailable");
|
||||
}
|
||||
|
||||
context_ = avcodec_alloc_context3(encoder);
|
||||
if (context_ == nullptr) {
|
||||
return std::unexpected("failed to allocate FFmpeg encoder context");
|
||||
}
|
||||
|
||||
context_->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
context_->codec_id = encoder->id;
|
||||
context_->width = static_cast<int>(frame_info.width);
|
||||
context_->height = static_cast<int>(frame_info.height);
|
||||
context_->pix_fmt = encoder_pix_fmt_;
|
||||
context_->time_base = AVRational{1, 1000000000};
|
||||
context_->framerate = AVRational{30, 1};
|
||||
context_->gop_size = static_cast<int>(config.encoder.gop);
|
||||
context_->max_b_frames = static_cast<int>(config.encoder.b_frames);
|
||||
context_->thread_count = 1;
|
||||
|
||||
auto codec_setup = configure_codec(*encoder_name, config);
|
||||
if (!codec_setup) {
|
||||
return std::unexpected(codec_setup.error());
|
||||
}
|
||||
|
||||
const auto open_result = avcodec_open2(context_, encoder, nullptr);
|
||||
if (open_result < 0) {
|
||||
return std::unexpected("failed to open FFmpeg encoder '" + *encoder_name + "': " + av_error_string(open_result));
|
||||
}
|
||||
|
||||
scaler_ = sws_getCachedContext(
|
||||
nullptr,
|
||||
static_cast<int>(frame_info.width),
|
||||
static_cast<int>(frame_info.height),
|
||||
input_pix_fmt_,
|
||||
static_cast<int>(frame_info.width),
|
||||
static_cast<int>(frame_info.height),
|
||||
encoder_pix_fmt_,
|
||||
SWS_BILINEAR,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (scaler_ == nullptr) {
|
||||
return std::unexpected("failed to create swscale conversion context");
|
||||
}
|
||||
|
||||
frame_ = av_frame_alloc();
|
||||
if (frame_ == nullptr) {
|
||||
return std::unexpected("failed to allocate FFmpeg frame");
|
||||
}
|
||||
frame_->format = encoder_pix_fmt_;
|
||||
frame_->width = context_->width;
|
||||
frame_->height = context_->height;
|
||||
const auto frame_buffer = av_frame_get_buffer(frame_, 32);
|
||||
if (frame_buffer < 0) {
|
||||
return std::unexpected("failed to allocate FFmpeg frame buffer: " + av_error_string(frame_buffer));
|
||||
}
|
||||
|
||||
packet_ = av_packet_alloc();
|
||||
if (packet_ == nullptr) {
|
||||
return std::unexpected("failed to allocate FFmpeg packet");
|
||||
}
|
||||
|
||||
filtered_packet_ = av_packet_alloc();
|
||||
if (filtered_packet_ == nullptr) {
|
||||
return std::unexpected("failed to allocate FFmpeg filtered packet");
|
||||
}
|
||||
|
||||
auto bitstream_filter = create_bitstream_filter();
|
||||
if (!bitstream_filter) {
|
||||
return std::unexpected(bitstream_filter.error());
|
||||
}
|
||||
|
||||
spdlog::info(
|
||||
"FFMPEG_ENCODER_PATH codec={} device={} encoder={} pix_fmt={}",
|
||||
cvmmap_streamer::to_string(codec_),
|
||||
device_to_string(config.encoder.device),
|
||||
*encoder_name,
|
||||
av_get_pix_fmt_name(encoder_pix_fmt_));
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> poll() override {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> push_frame(const RawVideoFrame &frame) override {
|
||||
if (context_ == nullptr || frame_ == nullptr || scaler_ == nullptr) {
|
||||
return std::unexpected("FFmpeg backend not initialized");
|
||||
}
|
||||
if (frame.bytes.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto make_writable = av_frame_make_writable(frame_);
|
||||
if (make_writable < 0) {
|
||||
return std::unexpected("failed to make FFmpeg frame writable: " + av_error_string(make_writable));
|
||||
}
|
||||
|
||||
AVFrame input_frame{};
|
||||
input_frame.format = input_pix_fmt_;
|
||||
input_frame.width = static_cast<int>(frame_info_.width);
|
||||
input_frame.height = static_cast<int>(frame_info_.height);
|
||||
if (av_image_fill_arrays(
|
||||
input_frame.data,
|
||||
input_frame.linesize,
|
||||
const_cast<std::uint8_t *>(frame.bytes.data()),
|
||||
input_pix_fmt_,
|
||||
input_frame.width,
|
||||
input_frame.height,
|
||||
1) < 0) {
|
||||
return std::unexpected("failed to map input frame into FFmpeg image arrays");
|
||||
}
|
||||
|
||||
sws_scale(
|
||||
scaler_,
|
||||
input_frame.data,
|
||||
input_frame.linesize,
|
||||
0,
|
||||
input_frame.height,
|
||||
frame_->data,
|
||||
frame_->linesize);
|
||||
|
||||
if (!first_source_timestamp_ns_) {
|
||||
first_source_timestamp_ns_ = frame.source_timestamp_ns;
|
||||
}
|
||||
|
||||
frame_->pts = static_cast<std::int64_t>(frame.source_timestamp_ns - *first_source_timestamp_ns_);
|
||||
const auto send_result = avcodec_send_frame(context_, frame_);
|
||||
if (send_result < 0) {
|
||||
return std::unexpected("failed to send frame to FFmpeg encoder: " + av_error_string(send_result));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> drain() override {
|
||||
return drain_packets();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> flush() override {
|
||||
if (context_ == nullptr) {
|
||||
return std::vector<EncodedAccessUnit>{};
|
||||
}
|
||||
const auto flush_result = avcodec_send_frame(context_, nullptr);
|
||||
if (flush_result < 0 && flush_result != AVERROR_EOF) {
|
||||
return std::unexpected("failed to flush FFmpeg encoder: " + av_error_string(flush_result));
|
||||
}
|
||||
return drain_packets();
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
if (bsf_context_ != nullptr) {
|
||||
av_bsf_free(&bsf_context_);
|
||||
}
|
||||
if (filtered_packet_ != nullptr) {
|
||||
av_packet_free(&filtered_packet_);
|
||||
}
|
||||
if (packet_ != nullptr) {
|
||||
av_packet_free(&packet_);
|
||||
}
|
||||
if (frame_ != nullptr) {
|
||||
av_frame_free(&frame_);
|
||||
}
|
||||
if (context_ != nullptr) {
|
||||
avcodec_free_context(&context_);
|
||||
}
|
||||
if (scaler_ != nullptr) {
|
||||
sws_freeContext(scaler_);
|
||||
scaler_ = nullptr;
|
||||
}
|
||||
first_source_timestamp_ns_.reset();
|
||||
using_hardware_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
static std::string av_error_string(int error_code) {
|
||||
char buffer[AV_ERROR_MAX_STRING_SIZE]{};
|
||||
av_strerror(error_code, buffer, sizeof(buffer));
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::expected<AVPixelFormat, std::string> to_av_pixel_format(ipc::PixelFormat format) {
|
||||
switch (format) {
|
||||
case ipc::PixelFormat::BGR:
|
||||
return AV_PIX_FMT_BGR24;
|
||||
case ipc::PixelFormat::RGB:
|
||||
return AV_PIX_FMT_RGB24;
|
||||
case ipc::PixelFormat::BGRA:
|
||||
return AV_PIX_FMT_BGRA;
|
||||
case ipc::PixelFormat::RGBA:
|
||||
return AV_PIX_FMT_RGBA;
|
||||
case ipc::PixelFormat::GRAY:
|
||||
return AV_PIX_FMT_GRAY8;
|
||||
default:
|
||||
return std::unexpected("unsupported raw pixel format for FFmpeg backend (supported: BGR/RGB/BGRA/RGBA/GRAY)");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static AVPixelFormat pick_encoder_pixel_format(EncoderDeviceType device) {
|
||||
if (device == EncoderDeviceType::Software) {
|
||||
return AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
return AV_PIX_FMT_NV12;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::string_view device_to_string(EncoderDeviceType device) {
|
||||
switch (device) {
|
||||
case EncoderDeviceType::Auto:
|
||||
return "auto";
|
||||
case EncoderDeviceType::Nvidia:
|
||||
return "nvidia";
|
||||
case EncoderDeviceType::Software:
|
||||
return "software";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::string, std::string> pick_encoder_name(const RuntimeConfig &config) const {
|
||||
const bool prefer_hardware = config.encoder.device != EncoderDeviceType::Software;
|
||||
const bool prefer_software = config.encoder.device == EncoderDeviceType::Software;
|
||||
if (codec_ == CodecType::H265) {
|
||||
if (prefer_hardware && avcodec_find_encoder_by_name("hevc_nvenc") != nullptr) {
|
||||
return std::string("hevc_nvenc");
|
||||
}
|
||||
if (!prefer_hardware || config.encoder.device == EncoderDeviceType::Auto) {
|
||||
if (avcodec_find_encoder_by_name("libx265") != nullptr) {
|
||||
return std::string("libx265");
|
||||
}
|
||||
}
|
||||
if (!prefer_software && avcodec_find_encoder_by_name("hevc_nvenc") != nullptr) {
|
||||
return std::string("hevc_nvenc");
|
||||
}
|
||||
return std::unexpected("no usable FFmpeg encoder found for h265 (looked for hevc_nvenc, libx265)");
|
||||
}
|
||||
|
||||
if (prefer_hardware && avcodec_find_encoder_by_name("h264_nvenc") != nullptr) {
|
||||
return std::string("h264_nvenc");
|
||||
}
|
||||
if (!prefer_hardware || config.encoder.device == EncoderDeviceType::Auto) {
|
||||
if (avcodec_find_encoder_by_name("libx264") != nullptr) {
|
||||
return std::string("libx264");
|
||||
}
|
||||
}
|
||||
if (!prefer_software && avcodec_find_encoder_by_name("h264_nvenc") != nullptr) {
|
||||
return std::string("h264_nvenc");
|
||||
}
|
||||
return std::unexpected("no usable FFmpeg encoder found for h264 (looked for h264_nvenc, libx264)");
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> configure_codec(std::string_view encoder_name, const RuntimeConfig &config) {
|
||||
av_opt_set(context_->priv_data, "preset", encoder_name.find("nvenc") != std::string_view::npos ? "llhq" : "veryfast", 0);
|
||||
if (encoder_name.find("nvenc") != std::string_view::npos) {
|
||||
av_opt_set(context_->priv_data, "tune", "ull", 0);
|
||||
av_opt_set(context_->priv_data, "zerolatency", "1", 0);
|
||||
av_opt_set(context_->priv_data, "rc-lookahead", "0", 0);
|
||||
} else {
|
||||
av_opt_set(context_->priv_data, "tune", "zerolatency", 0);
|
||||
if (encoder_name == "libx265") {
|
||||
av_opt_set(context_->priv_data, "x265-params", "repeat-headers=1:scenecut=0", 0);
|
||||
}
|
||||
}
|
||||
|
||||
av_opt_set_int(context_->priv_data, "forced-idr", config.latency.force_idr_on_reset ? 1 : 0, 0);
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> create_bitstream_filter() {
|
||||
const char *filter_name = codec_ == CodecType::H265 ? "hevc_mp4toannexb" : "h264_mp4toannexb";
|
||||
const auto *filter = av_bsf_get_by_name(filter_name);
|
||||
if (filter == nullptr) {
|
||||
return std::unexpected(std::string("required FFmpeg bitstream filter '") + filter_name + "' is unavailable");
|
||||
}
|
||||
|
||||
const auto alloc_result = av_bsf_alloc(filter, &bsf_context_);
|
||||
if (alloc_result < 0) {
|
||||
return std::unexpected("failed to allocate FFmpeg bitstream filter: " + av_error_string(alloc_result));
|
||||
}
|
||||
|
||||
const auto copy_result = avcodec_parameters_from_context(bsf_context_->par_in, context_);
|
||||
if (copy_result < 0) {
|
||||
return std::unexpected("failed to copy codec parameters into bitstream filter: " + av_error_string(copy_result));
|
||||
}
|
||||
bsf_context_->time_base_in = context_->time_base;
|
||||
|
||||
const auto init_result = av_bsf_init(bsf_context_);
|
||||
if (init_result < 0) {
|
||||
return std::unexpected("failed to initialize FFmpeg bitstream filter: " + av_error_string(init_result));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> drain_packets() {
|
||||
std::vector<EncodedAccessUnit> access_units{};
|
||||
while (true) {
|
||||
const auto receive_result = avcodec_receive_packet(context_, packet_);
|
||||
if (receive_result == AVERROR(EAGAIN) || receive_result == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
if (receive_result < 0) {
|
||||
return std::unexpected("failed to receive FFmpeg packet: " + av_error_string(receive_result));
|
||||
}
|
||||
|
||||
const auto bsf_send_result = av_bsf_send_packet(bsf_context_, packet_);
|
||||
if (bsf_send_result < 0) {
|
||||
av_packet_unref(packet_);
|
||||
return std::unexpected("failed to send packet to bitstream filter: " + av_error_string(bsf_send_result));
|
||||
}
|
||||
av_packet_unref(packet_);
|
||||
|
||||
while (true) {
|
||||
const auto bsf_receive_result = av_bsf_receive_packet(bsf_context_, filtered_packet_);
|
||||
if (bsf_receive_result == AVERROR(EAGAIN) || bsf_receive_result == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
if (bsf_receive_result < 0) {
|
||||
return std::unexpected("failed to receive filtered packet: " + av_error_string(bsf_receive_result));
|
||||
}
|
||||
|
||||
EncodedAccessUnit access_unit{};
|
||||
access_unit.codec = codec_;
|
||||
access_unit.stream_pts_ns = filtered_packet_->pts == AV_NOPTS_VALUE ? 0ull : static_cast<std::uint64_t>(filtered_packet_->pts);
|
||||
access_unit.source_timestamp_ns = first_source_timestamp_ns_.value_or(0ull) + access_unit.stream_pts_ns;
|
||||
access_unit.keyframe = (filtered_packet_->flags & AV_PKT_FLAG_KEY) != 0;
|
||||
access_unit.annexb_bytes.assign(filtered_packet_->data, filtered_packet_->data + filtered_packet_->size);
|
||||
access_units.push_back(std::move(access_unit));
|
||||
av_packet_unref(filtered_packet_);
|
||||
}
|
||||
}
|
||||
return access_units;
|
||||
}
|
||||
|
||||
const RuntimeConfig *config_{nullptr};
|
||||
ipc::FrameInfo frame_info_{};
|
||||
CodecType codec_{CodecType::H264};
|
||||
AVCodecContext *context_{nullptr};
|
||||
AVPacket *packet_{nullptr};
|
||||
AVPacket *filtered_packet_{nullptr};
|
||||
AVFrame *frame_{nullptr};
|
||||
SwsContext *scaler_{nullptr};
|
||||
AVBSFContext *bsf_context_{nullptr};
|
||||
AVPixelFormat input_pix_fmt_{AV_PIX_FMT_NONE};
|
||||
AVPixelFormat encoder_pix_fmt_{AV_PIX_FMT_NONE};
|
||||
std::optional<std::uint64_t> first_source_timestamp_ns_{};
|
||||
bool using_hardware_{false};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<EncoderBackend> make_ffmpeg_backend() {
|
||||
return std::make_unique<FfmpegEncoderBackend>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
#include "cvmmap_streamer/encode/encoder_backend.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#if __has_include(<gst/app/gstappsink.h>) && __has_include(<gst/app/gstappsrc.h>) && __has_include(<gst/gst.h>)
|
||||
#define CVMMAP_STREAMER_HAS_GSTREAMER 1
|
||||
#include <gst/app/gstappsink.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include <gst/gst.h>
|
||||
#else
|
||||
#define CVMMAP_STREAMER_HAS_GSTREAMER 0
|
||||
#endif
|
||||
|
||||
namespace cvmmap_streamer::encode {
|
||||
|
||||
namespace {
|
||||
|
||||
#if CVMMAP_STREAMER_HAS_GSTREAMER
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<const char *, std::string> pixel_format_to_caps(ipc::PixelFormat format) {
|
||||
switch (format) {
|
||||
case ipc::PixelFormat::BGR:
|
||||
return "BGR";
|
||||
case ipc::PixelFormat::RGB:
|
||||
return "RGB";
|
||||
case ipc::PixelFormat::BGRA:
|
||||
return "BGRA";
|
||||
case ipc::PixelFormat::RGBA:
|
||||
return "RGBA";
|
||||
case ipc::PixelFormat::GRAY:
|
||||
return "GRAY8";
|
||||
default:
|
||||
return std::unexpected("unsupported raw pixel format for legacy GStreamer backend");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_gst_initialized() {
|
||||
static std::once_flag gst_init_flag;
|
||||
std::call_once(gst_init_flag, []() {
|
||||
gst_init(nullptr, nullptr);
|
||||
spdlog::info("GStreamer initialized: {}", gst_version_string());
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::string selected_parser_name(CodecType codec) {
|
||||
return codec == CodecType::H265 ? "h265parse" : "h264parse";
|
||||
}
|
||||
|
||||
struct EncoderChoice {
|
||||
std::string encoder_name;
|
||||
std::string parser_name;
|
||||
bool is_nvenc{false};
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
std::vector<std::string_view> encoder_candidates(CodecType codec, bool prefer_nvenc) {
|
||||
if (codec == CodecType::H265) {
|
||||
if (prefer_nvenc) {
|
||||
return {"nvh265enc", "x265enc", "avenc_libx265"};
|
||||
}
|
||||
return {"x265enc", "avenc_libx265", "nvh265enc"};
|
||||
}
|
||||
|
||||
if (prefer_nvenc) {
|
||||
return {"nvh264enc", "x264enc", "openh264enc", "avenc_h264"};
|
||||
}
|
||||
return {"x264enc", "openh264enc", "avenc_h264", "nvh264enc"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<EncoderChoice, std::string> pick_encoder_choice(CodecType codec, bool prefer_nvenc) {
|
||||
const std::string parser_name = selected_parser_name(codec);
|
||||
if (gst_element_factory_find(parser_name.c_str()) == nullptr) {
|
||||
return std::unexpected("required GStreamer parser element '" + parser_name + "' is unavailable");
|
||||
}
|
||||
|
||||
for (const auto candidate : encoder_candidates(codec, prefer_nvenc)) {
|
||||
if (gst_element_factory_find(candidate.data()) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
EncoderChoice choice{};
|
||||
choice.encoder_name = std::string(candidate);
|
||||
choice.parser_name = parser_name;
|
||||
choice.is_nvenc = choice.encoder_name.starts_with("nvh");
|
||||
return choice;
|
||||
}
|
||||
|
||||
return std::unexpected("no usable GStreamer encoder available");
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::string encoder_input_format(const std::string &encoder_name) {
|
||||
if (encoder_name == "x265enc" || encoder_name == "openh264enc") {
|
||||
return "I420";
|
||||
}
|
||||
return "NV12";
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool has_property(GObject *object, const char *name) {
|
||||
if (object == nullptr || name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return g_object_class_find_property(G_OBJECT_GET_CLASS(object), name) != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool set_property_arg_if_exists(GObject *object, const char *name, const std::string &value) {
|
||||
if (!has_property(object, name)) {
|
||||
return false;
|
||||
}
|
||||
gst_util_set_object_arg(object, name, value.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
class GstreamerLegacyBackend final : public EncoderBackend {
|
||||
public:
|
||||
GstreamerLegacyBackend() = default;
|
||||
~GstreamerLegacyBackend() override {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::string_view backend_name() const override {
|
||||
return "gstreamer_legacy";
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool using_hardware() const override {
|
||||
return using_hardware_;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> init(const RuntimeConfig &config, const ipc::FrameInfo &frame_info) override {
|
||||
shutdown();
|
||||
config_ = &config;
|
||||
ensure_gst_initialized();
|
||||
|
||||
bool prefer_nvenc = config.encoder.device != EncoderDeviceType::Software;
|
||||
auto encoder_choice = pick_encoder_choice(config.encoder.codec, prefer_nvenc);
|
||||
if (!encoder_choice && prefer_nvenc && config.encoder.device == EncoderDeviceType::Auto) {
|
||||
encoder_choice = pick_encoder_choice(config.encoder.codec, false);
|
||||
}
|
||||
if (!encoder_choice) {
|
||||
return std::unexpected(encoder_choice.error());
|
||||
}
|
||||
|
||||
using_hardware_ = encoder_choice->is_nvenc;
|
||||
active_encoder_name_ = encoder_choice->encoder_name;
|
||||
active_parser_name_ = encoder_choice->parser_name;
|
||||
|
||||
auto pixel_format = pixel_format_to_caps(frame_info.pixel_format);
|
||||
if (!pixel_format) {
|
||||
return std::unexpected(pixel_format.error());
|
||||
}
|
||||
|
||||
const std::string codec_caps =
|
||||
config.encoder.codec == CodecType::H265
|
||||
? "video/x-h265,stream-format=byte-stream,alignment=au"
|
||||
: "video/x-h264,stream-format=byte-stream,alignment=au";
|
||||
|
||||
const std::string pipeline_desc =
|
||||
std::string("appsrc name=ingest_src is-live=true format=time do-timestamp=true block=false ") +
|
||||
"! queue leaky=downstream max-size-buffers=1 max-size-bytes=0 max-size-time=0 " +
|
||||
"! videoconvert " +
|
||||
"! video/x-raw,format=" + encoder_input_format(active_encoder_name_) + " " +
|
||||
"! " + active_encoder_name_ + " name=encoder " +
|
||||
"! " + active_parser_name_ + " name=parser config-interval=-1 disable-passthrough=true " +
|
||||
"! " + codec_caps + " " +
|
||||
"! appsink name=encoded_sink emit-signals=false sync=false drop=true max-buffers=1";
|
||||
|
||||
GError *error = nullptr;
|
||||
pipeline_ = gst_parse_launch(pipeline_desc.c_str(), &error);
|
||||
if (error != nullptr) {
|
||||
const std::string message = "failed to create GStreamer pipeline: " + std::string(error->message);
|
||||
g_error_free(error);
|
||||
return std::unexpected(message);
|
||||
}
|
||||
if (pipeline_ == nullptr) {
|
||||
return std::unexpected("failed to create GStreamer pipeline");
|
||||
}
|
||||
|
||||
appsrc_ = gst_bin_get_by_name(GST_BIN(pipeline_), "ingest_src");
|
||||
appsink_ = gst_bin_get_by_name(GST_BIN(pipeline_), "encoded_sink");
|
||||
encoder_ = gst_bin_get_by_name(GST_BIN(pipeline_), "encoder");
|
||||
if (appsrc_ == nullptr || appsink_ == nullptr || encoder_ == nullptr) {
|
||||
return std::unexpected("failed to locate GStreamer pipeline elements");
|
||||
}
|
||||
|
||||
const auto caps_string =
|
||||
"video/x-raw,format=(string)" +
|
||||
std::string(*pixel_format) +
|
||||
",width=(int)" +
|
||||
std::to_string(frame_info.width) +
|
||||
",height=(int)" +
|
||||
std::to_string(frame_info.height) +
|
||||
",framerate=(fraction)30/1";
|
||||
GstCaps *caps = gst_caps_from_string(caps_string.c_str());
|
||||
if (caps == nullptr) {
|
||||
return std::unexpected("failed to create GStreamer caps: " + caps_string);
|
||||
}
|
||||
gst_app_src_set_caps(GST_APP_SRC(appsrc_), caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
gst_app_src_set_stream_type(GST_APP_SRC(appsrc_), GST_APP_STREAM_TYPE_STREAM);
|
||||
gst_app_src_set_max_buffers(GST_APP_SRC(appsrc_), 1);
|
||||
(void)set_property_arg_if_exists(G_OBJECT(appsrc_), "leaky-type", "downstream");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(appsrc_), "block", "false");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "bframes", std::to_string(config.encoder.b_frames));
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "rc-lookahead", "0");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "lookahead", "0");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "zerolatency", "true");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "gop-size", std::to_string(config.encoder.gop));
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "iframeinterval", std::to_string(config.encoder.gop));
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "preset", "llhq");
|
||||
(void)set_property_arg_if_exists(G_OBJECT(encoder_), "tune", "zerolatency");
|
||||
|
||||
bus_ = gst_element_get_bus(pipeline_);
|
||||
if (gst_element_set_state(pipeline_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
|
||||
return std::unexpected("failed to set GStreamer pipeline to PLAYING");
|
||||
}
|
||||
|
||||
spdlog::info(
|
||||
"ENCODER_PATH codec={} mode={} encoder={} backend=gstreamer_legacy",
|
||||
to_string(config.encoder.codec),
|
||||
using_hardware_ ? "hardware" : "software",
|
||||
active_encoder_name_);
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> poll() override {
|
||||
if (bus_ == nullptr) {
|
||||
return {};
|
||||
}
|
||||
while (auto *message = gst_bus_pop_filtered(
|
||||
bus_,
|
||||
static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_WARNING))) {
|
||||
if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_WARNING) {
|
||||
GError *warning = nullptr;
|
||||
gchar *debug = nullptr;
|
||||
gst_message_parse_warning(message, &warning, &debug);
|
||||
spdlog::warn(
|
||||
"legacy backend warning: {} ({})",
|
||||
warning != nullptr ? warning->message : "unknown",
|
||||
debug != nullptr ? debug : "no-debug");
|
||||
if (warning != nullptr) {
|
||||
g_error_free(warning);
|
||||
}
|
||||
if (debug != nullptr) {
|
||||
g_free(debug);
|
||||
}
|
||||
gst_message_unref(message);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_EOS) {
|
||||
gst_message_unref(message);
|
||||
return std::unexpected("legacy backend reached EOS");
|
||||
}
|
||||
|
||||
GError *error = nullptr;
|
||||
gchar *debug = nullptr;
|
||||
gst_message_parse_error(message, &error, &debug);
|
||||
const std::string message_text =
|
||||
"legacy backend error: " +
|
||||
std::string(error != nullptr ? error->message : "unknown") +
|
||||
" (" +
|
||||
std::string(debug != nullptr ? debug : "no-debug") +
|
||||
")";
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
}
|
||||
if (debug != nullptr) {
|
||||
g_free(debug);
|
||||
}
|
||||
gst_message_unref(message);
|
||||
return std::unexpected(message_text);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> push_frame(const RawVideoFrame &frame) override {
|
||||
if (appsrc_ == nullptr) {
|
||||
return std::unexpected("legacy backend appsrc is null");
|
||||
}
|
||||
|
||||
auto *buffer = gst_buffer_new_allocate(nullptr, frame.bytes.size(), nullptr);
|
||||
if (buffer == nullptr) {
|
||||
return std::unexpected("failed to allocate GStreamer buffer");
|
||||
}
|
||||
|
||||
GstMapInfo map{};
|
||||
if (!gst_buffer_map(buffer, &map, GST_MAP_WRITE)) {
|
||||
gst_buffer_unref(buffer);
|
||||
return std::unexpected("failed to map GStreamer buffer");
|
||||
}
|
||||
std::memcpy(map.data, frame.bytes.data(), frame.bytes.size());
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
|
||||
if (!first_source_timestamp_ns_) {
|
||||
first_source_timestamp_ns_ = frame.source_timestamp_ns;
|
||||
}
|
||||
const auto pts_ns =
|
||||
frame.source_timestamp_ns >= *first_source_timestamp_ns_
|
||||
? frame.source_timestamp_ns - *first_source_timestamp_ns_
|
||||
: 0ull;
|
||||
GST_BUFFER_PTS(buffer) = static_cast<GstClockTime>(pts_ns);
|
||||
GST_BUFFER_DTS(buffer) = static_cast<GstClockTime>(pts_ns);
|
||||
|
||||
const auto flow = gst_app_src_push_buffer(GST_APP_SRC(appsrc_), buffer);
|
||||
if (flow != GST_FLOW_OK) {
|
||||
return std::unexpected("legacy backend push failed with flow=" + std::to_string(static_cast<int>(flow)));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> drain() override {
|
||||
return pull_samples();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> flush() override {
|
||||
if (appsrc_ != nullptr) {
|
||||
(void)gst_app_src_end_of_stream(GST_APP_SRC(appsrc_));
|
||||
}
|
||||
return pull_samples();
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state(pipeline_, GST_STATE_NULL);
|
||||
}
|
||||
if (bus_ != nullptr) {
|
||||
gst_object_unref(bus_);
|
||||
bus_ = nullptr;
|
||||
}
|
||||
if (appsrc_ != nullptr) {
|
||||
gst_object_unref(appsrc_);
|
||||
appsrc_ = nullptr;
|
||||
}
|
||||
if (appsink_ != nullptr) {
|
||||
gst_object_unref(appsink_);
|
||||
appsink_ = nullptr;
|
||||
}
|
||||
if (encoder_ != nullptr) {
|
||||
gst_object_unref(encoder_);
|
||||
encoder_ = nullptr;
|
||||
}
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_object_unref(pipeline_);
|
||||
pipeline_ = nullptr;
|
||||
}
|
||||
active_encoder_name_.clear();
|
||||
active_parser_name_.clear();
|
||||
first_source_timestamp_ns_.reset();
|
||||
using_hardware_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
std::expected<std::vector<EncodedAccessUnit>, std::string> pull_samples() {
|
||||
std::vector<EncodedAccessUnit> access_units{};
|
||||
if (appsink_ == nullptr || config_ == nullptr) {
|
||||
return access_units;
|
||||
}
|
||||
|
||||
while (auto *sample = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), 0)) {
|
||||
auto *buffer = gst_sample_get_buffer(sample);
|
||||
if (buffer == nullptr) {
|
||||
gst_sample_unref(sample);
|
||||
continue;
|
||||
}
|
||||
|
||||
GstMapInfo map{};
|
||||
if (!gst_buffer_map(buffer, &map, GST_MAP_READ)) {
|
||||
gst_sample_unref(sample);
|
||||
return std::unexpected("failed to map legacy encoded buffer");
|
||||
}
|
||||
|
||||
EncodedAccessUnit access_unit{};
|
||||
access_unit.codec = config_->encoder.codec;
|
||||
const auto pts = GST_BUFFER_PTS(buffer);
|
||||
if (pts != GST_CLOCK_TIME_NONE) {
|
||||
access_unit.stream_pts_ns = static_cast<std::uint64_t>(pts);
|
||||
}
|
||||
access_unit.source_timestamp_ns =
|
||||
first_source_timestamp_ns_.value_or(0) + access_unit.stream_pts_ns;
|
||||
access_unit.keyframe = !GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
|
||||
access_unit.annexb_bytes.assign(map.data, map.data + map.size);
|
||||
access_units.push_back(std::move(access_unit));
|
||||
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
gst_sample_unref(sample);
|
||||
}
|
||||
|
||||
return access_units;
|
||||
}
|
||||
|
||||
const RuntimeConfig *config_{nullptr};
|
||||
GstElement *pipeline_{nullptr};
|
||||
GstElement *appsrc_{nullptr};
|
||||
GstElement *appsink_{nullptr};
|
||||
GstElement *encoder_{nullptr};
|
||||
GstBus *bus_{nullptr};
|
||||
std::optional<std::uint64_t> first_source_timestamp_ns_{};
|
||||
bool using_hardware_{false};
|
||||
std::string active_encoder_name_{};
|
||||
std::string active_parser_name_{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<EncoderBackend> make_gstreamer_legacy_backend() {
|
||||
#if CVMMAP_STREAMER_HAS_GSTREAMER
|
||||
return std::make_unique<GstreamerLegacyBackend>();
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
+31
-11
@@ -9,17 +9,37 @@ namespace cvmmap_streamer {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::string_view, 10> kHelpLines{
|
||||
"Usage:",
|
||||
" --help, -h\tshow this message",
|
||||
"",
|
||||
"Options:",
|
||||
" --version\tprint version information",
|
||||
"",
|
||||
"Examples:",
|
||||
" cvmmap_streamer --help",
|
||||
" cvmmap_streamer --run-mode pipeline --shm-name cvmmap://default --help",
|
||||
" rtp_receiver_tester --help"};
|
||||
constexpr std::array<std::string_view, 30> kHelpLines{
|
||||
"Usage:",
|
||||
" --help, -h\tshow this message",
|
||||
"",
|
||||
"Options:",
|
||||
" --version\tprint version information",
|
||||
" --config <path>\tload runtime config from TOML",
|
||||
" --input-uri <uri>\tcvmmap source URI (example: cvmmap://default)",
|
||||
" --run-mode <mode>\tpipeline|ingest",
|
||||
" --codec <codec>\th264|h265",
|
||||
" --encoder-backend <backend>\tauto|ffmpeg|gstreamer_legacy",
|
||||
" --encoder-device <device>\tauto|nvidia|software",
|
||||
" --gop <frames>\tencoder GOP length",
|
||||
" --b-frames <count>\tencoder B-frame count",
|
||||
" --rtp\t\tenable RTP output",
|
||||
" --rtp-endpoint <host:port>\tRTP destination",
|
||||
" --rtp-payload-type <pt>\tRTP payload type (96-127)",
|
||||
" --rtp-sdp <path>\twrite SDP sidecar",
|
||||
" --rtmp\t\tenable RTMP output",
|
||||
" --rtmp-url <url>\tadd RTMP destination (repeatable)",
|
||||
" --rtmp-mode <mode>\tenhanced|domestic",
|
||||
" --mcap\t\tenable MCAP recording",
|
||||
" --mcap-path <path>\tMCAP output file",
|
||||
" --mcap-topic <topic>\tMCAP topic name",
|
||||
" --mcap-frame-id <id>\tFoxglove CompressedVideo frame_id",
|
||||
" --mcap-compression <mode>\tnone|lz4|zstd",
|
||||
"",
|
||||
"Examples:",
|
||||
" cvmmap_streamer --help",
|
||||
" cvmmap_streamer --run-mode pipeline --input-uri cvmmap://default --help",
|
||||
" rtp_receiver_tester --help"};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace cvmmap_streamer::core {
|
||||
|
||||
int run_ingest_loop(const RuntimeConfig &config);
|
||||
int run_nvenc_pipeline(const RuntimeConfig &config);
|
||||
int run_pipeline(const RuntimeConfig &config);
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
switch (config->run_mode) {
|
||||
case cvmmap_streamer::RunMode::Pipeline:
|
||||
return cvmmap_streamer::core::run_nvenc_pipeline(*config);
|
||||
return cvmmap_streamer::core::run_pipeline(*config);
|
||||
case cvmmap_streamer::RunMode::Ingest:
|
||||
return cvmmap_streamer::core::run_ingest_loop(*config);
|
||||
}
|
||||
|
||||
+303
-898
File diff suppressed because it is too large
Load Diff
@@ -744,19 +744,19 @@ std::expected<RtmpPublisher, std::string> RtmpPublisher::create(const RuntimeCon
|
||||
return std::unexpected("invalid RTMP publisher init: no RTMP URL configured");
|
||||
}
|
||||
|
||||
if (config.outputs.rtmp.mode == RtmpMode::Domestic && config.codec != CodecType::H265) {
|
||||
if (config.outputs.rtmp.mode == RtmpMode::Domestic && config.encoder.codec != CodecType::H265) {
|
||||
return std::unexpected(
|
||||
"invalid mode matrix: --rtmp-mode domestic requires --codec h265 (h264+domestic is unsupported)");
|
||||
}
|
||||
|
||||
spdlog::info(
|
||||
"RTMP_MODE_SELECTED codec={} mode={} urls={}",
|
||||
to_string(config.codec),
|
||||
to_string(config.encoder.codec),
|
||||
to_string(config.outputs.rtmp.mode),
|
||||
config.outputs.rtmp.urls.size());
|
||||
|
||||
RtmpPublisher publisher{};
|
||||
publisher.codec_ = config.codec;
|
||||
publisher.codec_ = config.encoder.codec;
|
||||
publisher.mode_ = config.outputs.rtmp.mode;
|
||||
publisher.sessions_.reserve(config.outputs.rtmp.urls.size());
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ std::expected<UdpRtpPublisher, std::string> UdpRtpPublisher::create(const Runtim
|
||||
publisher.destination_host_ = *config.outputs.rtp.host;
|
||||
publisher.destination_port_ = *config.outputs.rtp.port;
|
||||
publisher.payload_type_ = config.outputs.rtp.payload_type;
|
||||
publisher.codec_ = config.codec;
|
||||
publisher.codec_ = config.encoder.codec;
|
||||
publisher.sequence_ = compute_initial_sequence();
|
||||
publisher.ssrc_ = compute_ssrc(
|
||||
publisher.destination_host_,
|
||||
@@ -250,7 +250,7 @@ std::expected<UdpRtpPublisher, std::string> UdpRtpPublisher::create(const Runtim
|
||||
return std::unexpected("RTP socket non-blocking setup failed: " + std::string(std::strerror(errno)));
|
||||
}
|
||||
|
||||
const std::string codec_name = config.codec == CodecType::H265 ? "h265" : "h264";
|
||||
const std::string codec_name = config.encoder.codec == CodecType::H265 ? "h265" : "h264";
|
||||
if (config.outputs.rtp.sdp_path && !config.outputs.rtp.sdp_path->empty()) {
|
||||
publisher.sdp_path_ = *config.outputs.rtp.sdp_path;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#define MCAP_IMPLEMENTATION
|
||||
#include <mcap/writer.hpp>
|
||||
|
||||
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
||||
|
||||
#include "protobuf_descriptor.hpp"
|
||||
#include "foxglove/CompressedVideo.pb.h"
|
||||
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace cvmmap_streamer::record {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]]
|
||||
std::string codec_format(CodecType codec) {
|
||||
return codec == CodecType::H265 ? "h265" : "h264";
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
mcap::Compression to_mcap_compression(McapCompression compression) {
|
||||
switch (compression) {
|
||||
case McapCompression::Lz4:
|
||||
return mcap::Compression::Lz4;
|
||||
case McapCompression::None:
|
||||
return mcap::Compression::None;
|
||||
case McapCompression::Zstd:
|
||||
default:
|
||||
return mcap::Compression::Zstd;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
google::protobuf::Timestamp to_proto_timestamp(std::uint64_t timestamp_ns) {
|
||||
google::protobuf::Timestamp timestamp{};
|
||||
timestamp.set_seconds(static_cast<std::int64_t>(timestamp_ns / 1000000000ull));
|
||||
timestamp.set_nanos(static_cast<std::int32_t>(timestamp_ns % 1000000000ull));
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct McapRecordSink::State {
|
||||
mcap::McapWriter writer{};
|
||||
std::string path{};
|
||||
std::string frame_id{};
|
||||
mcap::ChannelId channel_id{0};
|
||||
std::uint32_t sequence{0};
|
||||
};
|
||||
|
||||
McapRecordSink::~McapRecordSink() {
|
||||
close();
|
||||
}
|
||||
|
||||
McapRecordSink::McapRecordSink(McapRecordSink &&other) noexcept
|
||||
: state_(other.state_) {
|
||||
other.state_ = nullptr;
|
||||
}
|
||||
|
||||
McapRecordSink &McapRecordSink::operator=(McapRecordSink &&other) noexcept {
|
||||
if (this != &other) {
|
||||
close();
|
||||
state_ = other.state_;
|
||||
other.state_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::expected<McapRecordSink, std::string> McapRecordSink::create(const RuntimeConfig &config) {
|
||||
McapRecordSink sink{};
|
||||
auto state = std::make_unique<State>();
|
||||
state->path = config.record.mcap.path;
|
||||
state->frame_id = config.record.mcap.frame_id;
|
||||
|
||||
mcap::McapWriterOptions options("");
|
||||
options.compression = to_mcap_compression(config.record.mcap.compression);
|
||||
const auto open_status = state->writer.open(state->path, options);
|
||||
if (!open_status.ok()) {
|
||||
return std::unexpected("failed to open MCAP writer at '" + state->path + "': " + open_status.message);
|
||||
}
|
||||
|
||||
const auto descriptor_set = build_file_descriptor_set(foxglove::CompressedVideo::descriptor());
|
||||
std::string schema_bytes{};
|
||||
if (!descriptor_set.SerializeToString(&schema_bytes)) {
|
||||
return std::unexpected("failed to serialize foxglove.CompressedVideo descriptor set");
|
||||
}
|
||||
|
||||
mcap::Schema schema("foxglove.CompressedVideo", "protobuf", schema_bytes);
|
||||
state->writer.addSchema(schema);
|
||||
|
||||
mcap::Channel channel(config.record.mcap.topic, "protobuf", schema.id);
|
||||
state->writer.addChannel(channel);
|
||||
state->channel_id = channel.id;
|
||||
|
||||
sink.state_ = state.release();
|
||||
return sink;
|
||||
}
|
||||
|
||||
std::expected<void, std::string> McapRecordSink::write_access_unit(const encode::EncodedAccessUnit &access_unit) {
|
||||
if (state_ == nullptr) {
|
||||
return std::unexpected("MCAP sink is not open");
|
||||
}
|
||||
|
||||
foxglove::CompressedVideo message{};
|
||||
*message.mutable_timestamp() = to_proto_timestamp(access_unit.source_timestamp_ns);
|
||||
message.set_frame_id(state_->frame_id);
|
||||
message.set_format(codec_format(access_unit.codec));
|
||||
message.set_data(
|
||||
reinterpret_cast<const char *>(access_unit.annexb_bytes.data()),
|
||||
static_cast<int>(access_unit.annexb_bytes.size()));
|
||||
|
||||
std::string serialized{};
|
||||
if (!message.SerializeToString(&serialized)) {
|
||||
return std::unexpected("failed to serialize foxglove.CompressedVideo");
|
||||
}
|
||||
|
||||
mcap::Message record{};
|
||||
record.channelId = state_->channel_id;
|
||||
record.sequence = state_->sequence++;
|
||||
record.logTime = access_unit.source_timestamp_ns;
|
||||
record.publishTime = access_unit.source_timestamp_ns;
|
||||
record.data = reinterpret_cast<const std::byte *>(serialized.data());
|
||||
record.dataSize = serialized.size();
|
||||
|
||||
const auto write_status = state_->writer.write(record);
|
||||
if (!write_status.ok()) {
|
||||
return std::unexpected("failed to write MCAP message: " + write_status.message);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool McapRecordSink::is_open() const {
|
||||
return state_ != nullptr;
|
||||
}
|
||||
|
||||
std::string_view McapRecordSink::path() const {
|
||||
if (state_ == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return state_->path;
|
||||
}
|
||||
|
||||
void McapRecordSink::close() {
|
||||
if (state_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
state_->writer.close();
|
||||
delete state_;
|
||||
state_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "protobuf_descriptor.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace cvmmap_streamer::record {
|
||||
|
||||
google::protobuf::FileDescriptorSet build_file_descriptor_set(const google::protobuf::Descriptor *descriptor) {
|
||||
google::protobuf::FileDescriptorSet descriptor_set;
|
||||
if (descriptor == nullptr) {
|
||||
return descriptor_set;
|
||||
}
|
||||
|
||||
std::queue<const google::protobuf::FileDescriptor *> pending{};
|
||||
std::unordered_set<std::string> seen{};
|
||||
pending.push(descriptor->file());
|
||||
seen.insert(std::string(descriptor->file()->name()));
|
||||
|
||||
while (!pending.empty()) {
|
||||
const auto *next = pending.front();
|
||||
pending.pop();
|
||||
next->CopyTo(descriptor_set.add_file());
|
||||
for (int index = 0; index < next->dependency_count(); ++index) {
|
||||
const auto *dependency = next->dependency(index);
|
||||
if (dependency == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (seen.insert(std::string(dependency->name())).second) {
|
||||
pending.push(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return descriptor_set;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include <google/protobuf/descriptor.pb.h>
|
||||
|
||||
namespace cvmmap_streamer::record {
|
||||
|
||||
google::protobuf::FileDescriptorSet build_file_descriptor_set(const google::protobuf::Descriptor *descriptor);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#define MCAP_IMPLEMENTATION
|
||||
#include <mcap/reader.hpp>
|
||||
|
||||
#include <foxglove/CompressedVideo.pb.h>
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Config {
|
||||
std::string input_path{};
|
||||
std::optional<std::string> expected_topic{};
|
||||
std::optional<std::string> expected_format{};
|
||||
std::uint32_t min_messages{1};
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<Config, int> parse_args(int argc, char **argv) {
|
||||
Config config{};
|
||||
CLI::App app{"mcap_reader_tester - validate foxglove.CompressedVideo MCAP output"};
|
||||
app.add_option("input", config.input_path, "Input MCAP path")->required();
|
||||
app.add_option("--expect-topic", config.expected_topic, "Expected MCAP topic");
|
||||
app.add_option("--expect-format", config.expected_format, "Expected CompressedVideo format");
|
||||
app.add_option("--min-messages", config.min_messages, "Minimum expected message count")->check(CLI::PositiveNumber);
|
||||
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch (const CLI::ParseError &e) {
|
||||
return std::unexpected(app.exit(e));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto config = parse_args(argc, argv);
|
||||
if (!config) {
|
||||
return config.error();
|
||||
}
|
||||
|
||||
mcap::McapReader reader{};
|
||||
const auto open_status = reader.open(config->input_path);
|
||||
if (!open_status.ok()) {
|
||||
spdlog::error("failed to open MCAP file '{}': {}", config->input_path, open_status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::uint64_t message_count{0};
|
||||
std::uint64_t previous_log_time{0};
|
||||
bool saw_log_time{false};
|
||||
|
||||
auto message_view = reader.readMessages();
|
||||
for (auto it = message_view.begin(); it != message_view.end(); ++it) {
|
||||
if (it->schema == nullptr || it->channel == nullptr) {
|
||||
spdlog::error("MCAP message missing schema or channel metadata");
|
||||
reader.close();
|
||||
return 3;
|
||||
}
|
||||
if (it->schema->encoding != "protobuf" || it->schema->name != "foxglove.CompressedVideo") {
|
||||
continue;
|
||||
}
|
||||
if (it->channel->messageEncoding != "protobuf") {
|
||||
spdlog::error("unexpected MCAP message encoding: {}", it->channel->messageEncoding);
|
||||
reader.close();
|
||||
return 3;
|
||||
}
|
||||
if (config->expected_topic && it->channel->topic != *config->expected_topic) {
|
||||
spdlog::error("unexpected topic: expected '{}' got '{}'", *config->expected_topic, it->channel->topic);
|
||||
reader.close();
|
||||
return 4;
|
||||
}
|
||||
if (saw_log_time && it->message.logTime < previous_log_time) {
|
||||
spdlog::error("non-monotonic logTime detected: {} < {}", it->message.logTime, previous_log_time);
|
||||
reader.close();
|
||||
return 5;
|
||||
}
|
||||
|
||||
foxglove::CompressedVideo message{};
|
||||
if (!message.ParseFromArray(it->message.data, static_cast<int>(it->message.dataSize))) {
|
||||
spdlog::error("failed to parse foxglove.CompressedVideo payload");
|
||||
reader.close();
|
||||
return 6;
|
||||
}
|
||||
if (config->expected_format && message.format() != *config->expected_format) {
|
||||
spdlog::error("unexpected format: expected '{}' got '{}'", *config->expected_format, message.format());
|
||||
reader.close();
|
||||
return 7;
|
||||
}
|
||||
if (message.data().empty()) {
|
||||
spdlog::error("compressed video payload is empty");
|
||||
reader.close();
|
||||
return 8;
|
||||
}
|
||||
|
||||
previous_log_time = it->message.logTime;
|
||||
saw_log_time = true;
|
||||
message_count += 1;
|
||||
}
|
||||
|
||||
reader.close();
|
||||
|
||||
if (message_count < config->min_messages) {
|
||||
spdlog::error("message threshold not met: {} < {}", message_count, config->min_messages);
|
||||
return 9;
|
||||
}
|
||||
|
||||
spdlog::info("validated {} foxglove.CompressedVideo MCAP messages", message_count);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user