From 8fd42a13c7814988f820aa64d1b6cc2b4eb1b7a9 Mon Sep 17 00:00:00 2001
From: mor0146 <vojtech.moravec.st@vsb.cz>
Date: Tue, 30 Jul 2019 21:41:19 +0200
Subject: [PATCH] Added CliOptionGroup. Improved help print.

---
 czi/source_code/app/main.cpp                  |  37 ++--
 .../include/azgra/cli/cli_arguments.h         |  12 ++
 .../azgra_lib/include/azgra/cli/cli_option.h  |  40 ++++-
 .../include/azgra/string/simple_string.h      |  16 +-
 .../azgra_lib/src/cli/cli_arguments.cpp       | 158 ++++++++++++------
 .../azgra_lib/src/string/simple_string.cpp    |  98 ++++++++---
 6 files changed, 263 insertions(+), 98 deletions(-)

diff --git a/czi/source_code/app/main.cpp b/czi/source_code/app/main.cpp
index d0b02f4..8ff41a0 100644
--- a/czi/source_code/app/main.cpp
+++ b/czi/source_code/app/main.cpp
@@ -173,12 +173,15 @@ int main(azgra::i32 argc, const char **argv)
                                                            'b', "bins", false, 10);
 
     azgra::cli::CliFlag flagVerbose("Verbose", "Make program output verbose.", 'v', "verbose");
+
+
     azgra::cli::CliFlag flagGzipCompressionOption("GZIP", "gzip (zlib) compression", '\0', "gzip");
     azgra::cli::CliFlag flagLzmaCompressionOption("LZMA", "lzma compression", '\0', "lzma");
     azgra::cli::CliFlag flagBzip2CompressionOption("BZIP2", "bzip2 compression", '\0', "bzip2");
     azgra::cli::CliFlag flagB3dCompressionOption("B3D", "B3D cuda compression", '\0', "b3d");
 
-
+    azgra::cli::CliOptionGroup compressorGroup("CompressorGroup", {&flagGzipCompressionOption, &flagLzmaCompressionOption,
+                                                               &flagBzip2CompressionOption, &flagB3dCompressionOption});
 
     // Methods
     azgra::cli::CliMethod methodVersion("version",
@@ -188,42 +191,40 @@ int main(azgra::i32 argc, const char **argv)
                                        "Report information about loaded czi file.");
 
     azgra::cli::CliMethod methodDump("dump",
-                                     "Dump raw image data. [subblock]",
-                                     {&flagFolder});
+                                     "Dump raw image data.",
+                                     {&flagFolder}, {&valFlagSubblock});
 
     azgra::cli::CliMethod methodCompressionBenchmark("compressors-benchmark",
-                                                     "Compressors benchmark for subblocks in czi file. [compression-level] [subblock]"
-                                                     " [compressor]",
-                                                     {&flagReportFile});
+                                                     "Compressors benchmark for subblocks in czi file.",
+                                                     {&flagReportFile}, {&valFlagCompressionLevel, &valFlagSubblock, &compressorGroup});
 
     azgra::cli::CliMethod methodFrameDiffCompression("frame-diff-compression",
-                                                     "Compress frames by their difference. [compression-level] [compressor]",
-                                                     {&flagReportFile});
+                                                     "Compress frames by their difference.",
+                                                     {&flagReportFile}, {&valFlagCompressionLevel, &compressorGroup});
 
     azgra::cli::CliMethod methodFrameDiffHistogram("frame-diff-histogram",
-                                                   "Save histograms of frame differences. [compression-level] [bin-count]",
-                                                   {&flagFolder});
+                                                   "Save histograms of frame differences.",
+                                                   {&flagFolder}, {&valFlagCompressionLevel, &valFlagHistogramBinCount});
 
     azgra::cli::CliMethod methodHistogram("histogram",
-                                          "Save histograms of all frames or just one specific frame. [subblock] [bin-count]",
-                                          {&flagFolder});
+                                          "Save histograms of all frames or just one specific frame.",
+                                          {&flagFolder}, {&valFlagSubblock, &valFlagHistogramBinCount});
 
     azgra::cli::CliMethod methodCudaCompress("b3d",
-                                             "Test only b3d compression. [subblock]",
-                                             {&flagReportFile});
+                                             "Test only b3d compression.",
+                                             {&flagReportFile}, {&valFlagSubblock});
 
     azgra::cli::CliMethod methodCudaCompressLossyTest("b3d-quantization-test",
-                                                      "Test quantization steps of lossy compression. <report-file> <subblock>",
+                                                      "Test quantization steps of lossy compression.",
                                                       {&flagFolder, &flagReportFile});
 
     args.flags = {&flagCziFile, &flagFolder, &flagReportFile, &valFlagSubblock, &valFlagCompressionLevel, &valFlagHistogramBinCount,
-                  &flagVerbose, &flagGzipCompressionOption, &flagLzmaCompressionOption, &flagBzip2CompressionOption,
-                  &flagB3dCompressionOption};
+                  &flagVerbose};
+    args.add_group(compressorGroup);
     args.methods = {&methodVersion, &methodReport, &methodDump, &methodCompressionBenchmark, &methodFrameDiffCompression,
                     &methodFrameDiffHistogram, &methodHistogram, &methodCudaCompress,
                     &methodCudaCompressLossyTest};
 
-
     if (!args.parse(argc, argv))
     {
         fprintf(stderr, "%s\n", args.get_error().c_str());
diff --git a/czi/source_code/azgra_lib/include/azgra/cli/cli_arguments.h b/czi/source_code/azgra_lib/include/azgra/cli/cli_arguments.h
index d9176af..0bf68a4 100644
--- a/czi/source_code/azgra_lib/include/azgra/cli/cli_arguments.h
+++ b/czi/source_code/azgra_lib/include/azgra/cli/cli_arguments.h
@@ -18,22 +18,33 @@ namespace azgra
         class CliArguments
         {
         private:
+            const int FIRST_COLUMN_WIDTH = 30;
+            const int SECOND_COLUMN_WIDTH = 60;
             std::stringstream errorStream;
             string::SmartStringView<char> appName;
             string::SmartStringView<char> appDescription;
             int outputWidth = 80;
             bool someMethodMatched = false;
+            std::vector<CliOptionGroup> groups;
 
             bool process_matched_flag(const string::SmartStringView<char> &match, bool shortMatch, const char **arguments, int &parseIndex);
+
             bool process_matched_value_flag(CliOption *matchedFlag, const char *rawFlagValue);
+
             bool process_matched_method(const string::SmartStringView<char> &match);
+
             bool process_multiflag(const string::SmartStringView<char> &match);
+
+            void print_flags(std::stringstream &outStream, const std::vector<CliOption *> &flags) const;
+            std::vector<CliOption*> get_flags_not_in_group() const;
+
         public:
             std::vector<CliMethod *> methods;
             std::vector<CliOption *> flags;
 
             CliArguments(const string::SmartStringView<char> &name, const string::SmartStringView<char> &description, int width = 80);
 
+            void add_group(const CliOptionGroup &flagGroup);
 
             bool parse(const int argc, const char **argv);
 
@@ -42,6 +53,7 @@ namespace azgra
             std::string get_error() const;
 
             bool is_any_method_matched() const;
+
         };
     };
 };
\ No newline at end of file
diff --git a/czi/source_code/azgra_lib/include/azgra/cli/cli_option.h b/czi/source_code/azgra_lib/include/azgra/cli/cli_option.h
index 63b8ba4..2feb3f4 100644
--- a/czi/source_code/azgra_lib/include/azgra/cli/cli_option.h
+++ b/czi/source_code/azgra_lib/include/azgra/cli/cli_option.h
@@ -16,11 +16,12 @@ namespace azgra
             bool isMatched = false;
             bool isRequired = false;
             bool hasMatchCharacter = true;
+            bool isInGroup = false;
             string::SmartStringView<char> name;
             string::SmartStringView<char> description;
 
-            CliOption(const string::SmartStringView<char>& name, const string::SmartStringView<char>& description,
-                      char matchCharacter, const string::SmartStringView<char>& matchString, bool required = false)
+            CliOption(const string::SmartStringView<char> &name, const string::SmartStringView<char> &description,
+                      char matchCharacter, const string::SmartStringView<char> &matchString, bool required = false)
             {
                 this->name = name;
                 this->description = description;
@@ -48,12 +49,15 @@ namespace azgra
         {
         private:
             std::vector<CliOption *> requiredFlags;
+            std::vector<CliOption *> optionalFlags;
         public:
             CliMethod(string::SmartStringView<char> methodMatchName, string::SmartStringView<char> description,
-                      const std::vector<CliOption *> &requiredFlags = std::vector<CliOption *>()) :
+                      const std::vector<CliOption *> &requiredFlags = std::vector<CliOption *>(),
+                      const std::vector<CliOption *> &optionalFlags = std::vector<CliOption *>()) :
                     CliOption(methodMatchName, description, '\0', methodMatchName, false)
             {
                 this->requiredFlags = requiredFlags;
+                this->optionalFlags = optionalFlags;
                 hasMatchCharacter = false;
             }
 
@@ -62,14 +66,19 @@ namespace azgra
                 return requiredFlags;
             }
 
+            std::vector<CliOption *> const &get_optional_flags() const
+            {
+                return optionalFlags;
+            }
+
         };
 
 
         class CliFlag : public CliOption
         {
         public:
-            CliFlag(const string::SmartStringView<char>& flagName, const string::SmartStringView<char>& description, char matchChar,
-                    const string::SmartStringView<char>& matchString, bool required = false) :
+            CliFlag(const string::SmartStringView<char> &flagName, const string::SmartStringView<char> &description, char matchChar,
+                    const string::SmartStringView<char> &matchString, bool required = false) :
                     CliOption(flagName, description, matchChar, matchString, required)
             {
                 if (matchChar == '\0')
@@ -88,8 +97,8 @@ namespace azgra
         private:
             ValueType internalValue;
         public:
-            CliValueFlag(const string::SmartStringView<char>& flagName, const string::SmartStringView<char>& description, char matchChar,
-                         const string::SmartStringView<char>& matchString, bool required = false, ValueType defaultValue = ValueType()) :
+            CliValueFlag(const string::SmartStringView<char> &flagName, const string::SmartStringView<char> &description, char matchChar,
+                         const string::SmartStringView<char> &matchString, bool required = false, ValueType defaultValue = ValueType()) :
                     CliOption(flagName, description, matchChar, matchString, required)
             {
                 internalValue = defaultValue;
@@ -104,5 +113,22 @@ namespace azgra
                 return internalValue;
             }
         };
+
+        class CliOptionGroup : public CliOption
+        {
+            friend class CliArguments;
+
+        private:
+            //NOTE: We can have group rules. Like AtLeastOne, OnlyOne, All
+            std::vector<CliOption *> options;
+        public:
+            CliOptionGroup(const string::SmartStringView<char> &name, const std::vector<CliOption *> &flagsInGroup) :
+                    CliOption(name, "", '\0', "", false)
+            {
+                hasMatchCharacter = false;
+                isRequired = false;
+                options = flagsInGroup;
+            }
+        };
     };
 };
\ No newline at end of file
diff --git a/czi/source_code/azgra_lib/include/azgra/string/simple_string.h b/czi/source_code/azgra_lib/include/azgra/string/simple_string.h
index c85f797..f6b7b9c 100644
--- a/czi/source_code/azgra_lib/include/azgra/string/simple_string.h
+++ b/czi/source_code/azgra_lib/include/azgra/string/simple_string.h
@@ -16,6 +16,7 @@ namespace azgra
         class SimpleString
         {
         private:
+            bool _isEmpty = true;
             /**
              * @brief Internal string memory.
              */
@@ -73,6 +74,11 @@ namespace azgra
             SimpleString(char *cString, const size_t length, bool noAlloc = false);
 
         public:
+            /**
+             * Empty string constructor.
+             */
+            SimpleString();
+
             /**
              * @brief Construct a new Simple String object.
              *
@@ -80,6 +86,8 @@ namespace azgra
              */
             SimpleString(const char *cString);
 
+            SimpleString(const std::vector<const char *> &strings);
+
             ~SimpleString();
 
             /**
@@ -324,8 +332,10 @@ namespace azgra
             bool operator==(const SimpleString &string) const;
 
             void operator+=(const char *cString);
+            void operator+=(const char c);
 
             SimpleString operator+(const char *cString) const;
+            SimpleString operator+(const char c) const;
 
             /**
              * @brief Create string by replicating.
@@ -336,11 +346,11 @@ namespace azgra
              */
             static SimpleString replicate(const char *cString, const azgra::i32 replicationCount);
 
-            void pad_left(const char padChar, const size_t padCount);
+            void pad_left(const char padChar, const size_t desiredWidth);
 
-            void pad_right(const char padChar, const size_t padCount);
+            void pad_right(const char padChar, const size_t desiredWidth);
 
-            void centerize(const char padChar, const size_t outputWidth);
+            void centerize(const char padChar, const size_t desiredWidth);
         };
     }; // namespace string
 } // namespace azgra
diff --git a/czi/source_code/azgra_lib/src/cli/cli_arguments.cpp b/czi/source_code/azgra_lib/src/cli/cli_arguments.cpp
index 30f40be..63e78d9 100644
--- a/czi/source_code/azgra_lib/src/cli/cli_arguments.cpp
+++ b/czi/source_code/azgra_lib/src/cli/cli_arguments.cpp
@@ -138,6 +138,83 @@ namespace azgra
             return allFound;
         }
 
+        std::vector<CliOption *> CliArguments::get_flags_not_in_group() const
+        {
+            std::vector<CliOption *> result;
+            for (CliOption *flag : flags)
+            {
+                if (!flag->isInGroup)
+                {
+                    result.push_back(flag);
+                }
+            }
+            return result;
+        }
+
+        void CliArguments::print_flags(std::stringstream &outStream, const std::vector<CliOption *> &flagsToPrint) const
+        {
+
+
+            for (const CliOption *flag : flagsToPrint)
+            {
+                const char *req = (flag->isRequired ? "Required" : "Optional");
+
+
+                if (flag->hasMatchCharacter)
+                {
+                     char matchCharString[2] = {flag->matchCharacter, '\0'};
+
+                    string::SimpleString matcherString({"{-", matchCharString, ", --",
+                                                        flag->matchString.data(), "} "});
+                    matcherString.pad_right(' ', FIRST_COLUMN_WIDTH);
+                    outStream << '\t' << matcherString.get_c_string();
+                }
+                else
+                {
+                    string::SimpleString matcherString({"{--", flag->matchString.data(), "} "});
+                    matcherString.pad_right(' ', FIRST_COLUMN_WIDTH);
+                    outStream << '\t' << matcherString.get_c_string();
+                }
+
+
+                string::SimpleString flagNameDesc({flag->name.data(), " - ", flag->description.data()});
+                flagNameDesc.pad_right(' ', SECOND_COLUMN_WIDTH);
+
+                outStream << flagNameDesc.get_c_string() << " [" << req;;
+
+
+                auto *intFlag = dynamic_cast<const CliValueFlag<int> *> (flag);
+                if (intFlag)
+                    outStream << ", int";
+
+                auto *uintFlag = dynamic_cast<const CliValueFlag<unsigned int> *> (flag);
+                if (uintFlag)
+                {
+                    outStream << ", uint";
+                }
+
+                auto *floatFlag = dynamic_cast<const CliValueFlag<float> *> (flag);
+                if (floatFlag)
+                {
+                    outStream << ", float";
+                }
+
+                auto *constCharFlag = dynamic_cast<const CliValueFlag<const char *> *> (flag);
+                if (constCharFlag)
+                {
+                    outStream << ", const char*";
+                }
+
+                auto *stringFlag = dynamic_cast<const CliValueFlag<std::string> *> (flag);
+                if (stringFlag)
+                {
+                    outStream << ", std::string";
+                }
+                outStream << "]" << std::endl;
+            }
+        }
+
+
         bool CliArguments::parse(const int argc, const char **argv)
         {
             const char *helpIdentifier = "--help";
@@ -234,65 +311,36 @@ namespace azgra
             helpStream << "Methods:" << std::endl;
             for (const auto &method : methods)
             {
-                helpStream << "\t" << method->name.string_view() << " - " << method->description.string_view();
-                auto methodReqFlags = method->get_required_flags();
-                if (!methodReqFlags.empty())
-                {
-                    helpStream << "\tRequired flags: ";
-                    for (size_t i = 0; i < methodReqFlags.size(); ++i)
-                    {
-                        helpStream << '<' << methodReqFlags[i]->name.string_view() << "> ";
-                    }
-                }
-                helpStream << std::endl;
-            }
-
-            helpStream << "Flags:" << std::endl;
-            for (const CliOption *flag : flags)
-            {
-                const char *req = (flag->isRequired ? "Required" : "Optional");
-
-                if (flag->hasMatchCharacter)
-                {
-                    helpStream << "\t -" << flag->matchCharacter << ", --"
-                               << flag->matchString.string_view() << "=" <<
-                               flag->name.string_view() << " - " << flag->description.string_view() << " [" << req;
-                }
-                else
-                {
-                    helpStream << "\t --" << flag->matchString.string_view() << "=" <<
-                               flag->name.string_view() << " - " << flag->description.string_view() << " [" << req;
-                }
-
+                string::SimpleString methodName(method->name.data());
+                string::SimpleString methodDesc(method->description.data());
+                methodName.pad_right(' ', FIRST_COLUMN_WIDTH);
+                methodDesc.pad_right(' ', SECOND_COLUMN_WIDTH);
+                helpStream << "\t" << methodName.get_c_string() << methodDesc.get_c_string();
 
-                auto *intFlag = dynamic_cast<const CliValueFlag<int> *> (flag);
-                if (intFlag)
-                    helpStream << ", int";
-
-                auto *uintFlag = dynamic_cast<const CliValueFlag<unsigned int> *> (flag);
-                if (uintFlag)
+                auto methodReqFlags = method->get_required_flags();
+                auto methodOptionalFlags = method->get_optional_flags();
+                if (!methodReqFlags.empty() || !methodOptionalFlags.empty())
                 {
-                    helpStream << ", uint";
+                    helpStream << " Flags: ";
                 }
 
-                auto *floatFlag = dynamic_cast<const CliValueFlag<float> *> (flag);
-                if (floatFlag)
+                for (const auto &methodReqFlag : methodReqFlags)
                 {
-                    helpStream << ", float";
+                    helpStream << '<' << methodReqFlag->name.string_view() << "> ";
                 }
-
-                auto *constCharFlag = dynamic_cast<const CliValueFlag<const char *> *> (flag);
-                if (constCharFlag)
+                for (const auto &methodOptionalFlag : methodOptionalFlags)
                 {
-                    helpStream << ", const char*";
+                    helpStream << '[' << methodOptionalFlag->name.string_view() << "] ";
                 }
+                helpStream << std::endl;
+            }
 
-                auto *stringFlag = dynamic_cast<const CliValueFlag<std::string> *> (flag);
-                if (stringFlag)
-                {
-                    helpStream << ", std::string";
-                }
-                helpStream << "]" << std::endl;
+            helpStream << "Flags:" << std::endl;
+            print_flags(helpStream, get_flags_not_in_group());
+            for (const CliOptionGroup &group : groups)
+            {
+                helpStream << "Flags - " << group.name.string_view() << std::endl;
+                print_flags(helpStream, group.options);
             }
 
             fprintf(stdout, "%s", helpStream.str().c_str());
@@ -308,5 +356,15 @@ namespace azgra
         {
             return someMethodMatched;
         }
+
+        void CliArguments::add_group(const CliOptionGroup &flagGroup)
+        {
+            groups.push_back(flagGroup);
+            for (CliOption *flag : flagGroup.options)
+            {
+                flag->isInGroup = true;
+                flags.push_back(flag);
+            }
+        }
     };
 };
\ No newline at end of file
diff --git a/czi/source_code/azgra_lib/src/string/simple_string.cpp b/czi/source_code/azgra_lib/src/string/simple_string.cpp
index 46cecbe..c4a8746 100644
--- a/czi/source_code/azgra_lib/src/string/simple_string.cpp
+++ b/czi/source_code/azgra_lib/src/string/simple_string.cpp
@@ -25,7 +25,7 @@ namespace azgra
 
         void SimpleString::free_string(char *memory)
         {
-            if (memory != nullptr)
+            if (memory != nullptr && memory[0] != '\0')
             {
                 ::operator delete(memory);
                 memory = nullptr;
@@ -47,6 +47,7 @@ namespace azgra
         {
             size_t inputStringLen = c_string_length(string);
 
+            _isEmpty = (inputStringLen == 0);
             _length = inputStringLen;
             _string = alloc_string(_length + 1);
 
@@ -56,11 +57,51 @@ namespace azgra
             _string[_length] = '\0';
         }
 
+        SimpleString::SimpleString(const char *cString)
+        {
+            internal_initalize(cString);
+        }
+
         SimpleString::SimpleString(char *cString, const size_t length, bool noAlloc)
         {
             assert(noAlloc);
             _string = cString;
             _length = length;
+            _isEmpty = (length == 0);
+        }
+
+        SimpleString::SimpleString(const std::vector<const char *> &strings)
+        {
+            _isEmpty = true;
+            size_t finalLen = 0;
+            std::vector<size_t> lengths;
+            for (const char *string : strings)
+            {
+                size_t len = c_string_length(string);
+                lengths.push_back(len);
+                finalLen += len;
+            }
+            if (finalLen > 0)
+            {
+                _isEmpty = false;
+                _string = alloc_string(finalLen + 1);
+                size_t offset = 0;
+                for (size_t stringIndex = 0; stringIndex < strings.size(); ++stringIndex)
+                {
+                    memcpy(_string + offset, strings[stringIndex], lengths[stringIndex] );
+                    offset += lengths[stringIndex];
+                }
+
+                _string[finalLen] = '\0';
+                _length = finalLen;
+            }
+        }
+
+        SimpleString::SimpleString()
+        {
+            _string = nullptr;
+            _length = 0;
+            _isEmpty = true;
         }
 
         SimpleString::operator const char *() const
@@ -84,6 +125,12 @@ namespace azgra
             concat(cString);
         }
 
+        void SimpleString::operator+=(const char c)
+        {
+            char stringToAdd[1] = {c};
+            this->operator+=(stringToAdd);
+        }
+
         SimpleString SimpleString::operator+(const char *cString) const
         {
             SimpleString result(_string);
@@ -91,6 +138,14 @@ namespace azgra
             return result;
         }
 
+        SimpleString SimpleString::operator+(const char c) const
+        {
+            SimpleString result(_string);
+            char stringToAdd[1] = {c};
+            result.concat(stringToAdd);
+            return result;
+        }
+
         bool SimpleString::operator==(const char *cString) const
         {
             return equals(cString);
@@ -101,11 +156,6 @@ namespace azgra
             return equals(string);
         }
 
-        SimpleString::SimpleString(const char *cString)
-        {
-            internal_initalize(cString);
-        }
-
         SimpleString::~SimpleString()
         {
             free_string(_string);
@@ -540,40 +590,48 @@ namespace azgra
             return result;
         }
 
-        void SimpleString::pad_left(const char padChar, const size_t padCount)
+        void SimpleString::pad_left(const char padChar, const size_t desiredWidth)
         {
-            size_t resultLen = _length + padCount;
-            char *result = alloc_string(resultLen);
+            assert(_length <= desiredWidth);
+            if (_length == desiredWidth)
+                return;
 
-            for (int i = 0; i < padCount; ++i)
+            char *result = alloc_string(desiredWidth + 1);
+            int fillSize = desiredWidth - _length;
+            for (int i = 0; i < fillSize; ++i)
             {
                 result[i] = padChar;
             }
-
             for (int j = 0; j < _length; ++j)
             {
-                result[padCount + j] = _string[j];
+                result[fillSize + j] = _string[j];
             }
+            result[desiredWidth] = '\0';
+
             free_string(_string);
             _string = result;
-            _length = resultLen;
+            _length = desiredWidth;
         }
 
-        void SimpleString::pad_right(const char padChar, const size_t padCount)
+        void SimpleString::pad_right(const char padChar, const size_t desiredWidth)
         {
-            size_t resultLen = _length + padCount;
-            _string = realloc_string(resultLen + 1, _string, _length);
-            for (int i = 0; i < padCount; ++i)
+            assert(_length <= desiredWidth);
+            if (_length == desiredWidth)
+                return;
+
+            _string = realloc_string(desiredWidth + 1, _string, _length);
+            int fillSize = desiredWidth - _length;
+            for (int i = 0; i < fillSize; ++i)
             {
                 _string[_length + i] = padChar;
             }
-            _length = resultLen;
-            _string[resultLen] = '\0';
+            _length = desiredWidth;
+            _string[desiredWidth] = '\0';
         }
 
         void SimpleString::centerize(const char padChar, const size_t outputWidth)
         {
-            size_t actualOutputWidth = (_length < outputWidth)  ? outputWidth : (_length + 4);
+            size_t actualOutputWidth = (_length < outputWidth) ? outputWidth : (_length + 4);
             size_t halfPadSize = (actualOutputWidth - 2 - static_cast<int>(_length)) / 2;
             char *result = alloc_string(actualOutputWidth + 1);
 
-- 
GitLab