From 396fab02b32ac75eaabf77c0b9d38e09cd2a816d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 24 Mar 2025 05:30:56 +0300 Subject: [PATCH] add yaml::stringify --- src/coders/yaml.cpp | 141 ++++++++++++++++++++++++++++++++++++++++ src/coders/yaml.hpp | 2 + src/util/stringutil.hpp | 25 +++++++ test/coders/yaml.cpp | 2 +- 4 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/coders/yaml.cpp b/src/coders/yaml.cpp index a7a1101f..a9944ea3 100644 --- a/src/coders/yaml.cpp +++ b/src/coders/yaml.cpp @@ -1,6 +1,8 @@ #include "yaml.hpp" #include "BasicParser.hpp" +#include + using namespace yaml; namespace { @@ -316,3 +318,142 @@ dv::value yaml::parse(std::string_view filename, std::string_view source) { dv::value yaml::parse(std::string_view source) { return parse("[string]", source); } + +static void add_indent(std::stringstream& ss, int indent) { + for (int i = 0; i < indent; i++) { + ss << " "; + } +} + +static void insert_string( + std::stringstream& ss, const std::string& string, int indent +) { + bool has_spec_chars = false; + bool multiline = false; + for (char c : string) { + if (c < ' ' || c == '"' || c == '\'') { + has_spec_chars = true; + if (multiline) { + break; + } + } + if (c == '\n') { + multiline = true; + break; + } + } + if (multiline) { + ss << "|-\n"; + size_t offset = 0; + size_t newoffset = 0; + + do { + offset = newoffset; + if (offset == string.length() - 1 && string[offset] == '\n') { + break; + } + add_indent(ss, indent); + newoffset = string.find('\n', offset + 1); + if (newoffset == std::string::npos) { + ss << string.substr(offset); + break; + } else { + ss << string.substr(offset + 1, newoffset - offset - 1); + } + ss << '\n'; + } while (true); + } else { + if (has_spec_chars || string.empty()) { + ss << util::escape(string, false); + } else { + ss << string; + } + } +} + +static void to_string( + std::stringstream& ss, + const dv::value& value, + int indent, + bool eliminateIndent = false +) { + using dv::value_type; + + switch (value.getType()) { + case value_type::string: + insert_string(ss, value.asString(), indent); + break; + case value_type::number: + ss << std::setprecision(15) << value.asNumber(); + break; + case value_type::integer: + ss << value.asInteger(); + break; + case value_type::boolean: + ss << (value.asBoolean() ? "true" : "false"); + break; + case value_type::none: + ss << "null"; + break; + case value_type::object: { + if (value.empty()) { + ss << "{}"; + break; + } + bool first = true; + for (const auto& [key, elem] : value.asObject()) { + if (!first) { + ss << '\n'; + } + if (!eliminateIndent) { + add_indent(ss, indent); + } else { + eliminateIndent = false; + } + ss << key << ": "; + if ((elem.isObject() || elem.isList()) && !elem.empty()) { + ss << "\n"; + to_string(ss, elem, indent + 1); + } else { + to_string(ss, elem, indent + 1); + } + first = false; + } + break; + } + case value_type::list: { + if (value.empty()) { + ss << "[]"; + break; + } + bool first = true; + for (const auto& elem : value) { + if (!first) { + ss << '\n'; + } + if (!eliminateIndent) { + add_indent(ss, indent); + } else { + eliminateIndent = false; + } + ss << "- "; + to_string(ss, elem, indent + 1, true); + first = false; + } + break; + } + case value_type::bytes: { + const auto& bytes = value.asBytes(); + auto b64 = util::base64_encode(bytes.data(), bytes.size()); + b64 = util::join(util::split_by_n(b64, 64), '\n'); + insert_string(ss, b64, indent); + break; + } + } +} + +std::string yaml::stringify(const dv::value& value) { + std::stringstream ss; + to_string(ss, value, 0); + return ss.str(); +} diff --git a/src/coders/yaml.hpp b/src/coders/yaml.hpp index 2802e523..3d871855 100644 --- a/src/coders/yaml.hpp +++ b/src/coders/yaml.hpp @@ -7,4 +7,6 @@ namespace yaml { dv::value parse(std::string_view filename, std::string_view source); dv::value parse(std::string_view source); + + std::string stringify(const dv::value& value); } diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index d85f93cb..3d8bf16e 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -115,4 +115,29 @@ namespace util { std::string format_data_size(size_t size); std::pair split_at(std::string_view view, char c); + + template + std::vector> split_by_n( + const std::basic_string& str, size_t n + ) { + std::vector> result; + for (size_t i = 0; i < str.length(); i += n) { + result.push_back(str.substr(i, n)); + } + return result; + } + + template + std::basic_string join( + const std::vector>& strings, CharT delimiter + ) { + std::basic_stringstream ss; + for (size_t i = 0; i < strings.size(); ++i) { + ss << strings[i]; + if (i != strings.size() - 1) { + ss << delimiter; + } + } + return ss.str(); + } } diff --git a/test/coders/yaml.cpp b/test/coders/yaml.cpp index 272b3b0e..0401bb19 100644 --- a/test/coders/yaml.cpp +++ b/test/coders/yaml.cpp @@ -14,7 +14,7 @@ TEST(YAML, EncodeDecode) { auto filename = "root:.github/workflows/windows-clang.yml"; try { auto value = yaml::parse(io::read_string(filename)); - std::cout << json::stringify(value, true) << std::endl; + std::cout << yaml::stringify(value) << std::endl; } catch (const parsing_error& error) { std::cerr << error.errorLog() << std::endl; throw error;