From 9c5d8ea262a051dfd6301be5ebee11b13aec2208 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Wed, 30 Dec 2015 12:03:37 -1000 Subject: [PATCH] Add HumanizeNumber and DirectoryEnumerator utilities --- hecl/include/HECL/HECL.hpp | 171 ++++++++++++++++++++++++++++++++++++ hecl/lib/CMakeLists.txt | 1 + hecl/lib/HumanizeNumber.cpp | 164 ++++++++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 hecl/lib/HumanizeNumber.cpp diff --git a/hecl/include/HECL/HECL.hpp b/hecl/include/HECL/HECL.hpp index 634c0ce34..7607b8399 100644 --- a/hecl/include/HECL/HECL.hpp +++ b/hecl/include/HECL/HECL.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "../extern/xxhash/xxhash.h" namespace HECL @@ -53,6 +54,27 @@ extern LogVisor::LogModule LogModule; std::string WideToUTF8(const std::wstring& src); std::wstring UTF8ToWide(const std::string& src); +/* humanize_number port from FreeBSD's libutil */ +enum class HNFlags +{ + None = 0, + Decimal = 0x01, + NoSpace = 0x02, + B = 0x04, + Divisor1000 = 0x08, + IECPrefixes = 0x10 +}; +ENABLE_BITWISE_ENUM(HNFlags) + +enum class HNScale +{ + None = 0, + AutoScale = 0x20 +}; +ENABLE_BITWISE_ENUM(HNScale) + +std::string HumanizeNumber(int64_t quotient, size_t len, const char* suffix, int scale, HNFlags flags); + #if HECL_UCS2 typedef wchar_t SystemChar; static inline size_t StrLen(const SystemChar* str) {return wcslen(str);} @@ -482,6 +504,155 @@ public: bool operator>=(const Time& other) const {return ts >= other.ts;} }; +/** + * @brief Case-insensitive comparator for std::map sorting + */ +struct CaseInsensitiveCompare +{ + bool operator()(const std::string& lhs, const std::string& rhs) const + { +#if _WIN32 + if (_stricmp(lhs.c_str(), rhs.c_str()) < 0) +#else + if (strcasecmp(lhs.c_str(), rhs.c_str()) < 0) +#endif + return true; + return false; + } + +#if _WIN32 + bool operator()(const std::wstring& lhs, const std::wstring& rhs) const + { + if (_wcsicmp(lhs.c_str(), rhs.c_str()) < 0) + return true; + return false; + } +#endif +}; + +/** + * @brief Directory traversal tool for accessing sorted directory entries + */ +class DirectoryEnumerator +{ +public: + enum class Mode + { + Native, + DirsSorted, + FilesSorted, + DirsThenFilesSorted + }; + struct Entry + { + HECL::SystemString m_path; + HECL::SystemString m_name; + size_t m_fileSz; + bool m_isDir; + + private: + friend class DirectoryEnumerator; + Entry(HECL::SystemString&& path, const HECL::SystemChar* name, size_t sz, bool isDir) + : m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir) {} + }; + +private: + std::vector m_entries; + +public: + DirectoryEnumerator(const HECL::SystemString& path, Mode mode=Mode::DirsThenFilesSorted) + : DirectoryEnumerator(path.c_str(), mode) {} + DirectoryEnumerator(const HECL::SystemChar* path, Mode mode=Mode::DirsThenFilesSorted) + { + HECL::Sstat theStat; + if (HECL::Stat(path, &theStat) || !S_ISDIR(theStat.st_mode)) + return; +#if _WIN32 +#else + DIR* dir = opendir(path); + if (!dir) + return; + const dirent* d; + switch (mode) + { + case Mode::Native: + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + HECL::SystemString fp(path); + fp += '/'; + fp += d->d_name; + HECL::Sstat st; + if (HECL::Stat(fp.c_str(), &st)) + continue; + + size_t sz = 0; + bool isDir = false; + if (S_ISDIR(st.st_mode)) + isDir = true; + else if (S_ISREG(st.st_mode)) + sz = st.st_size; + else + continue; + + m_entries.push_back(std::move(Entry(std::move(fp), d->d_name, sz, isDir))); + } + break; + case Mode::DirsThenFilesSorted: + case Mode::DirsSorted: + { + std::map sort; + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + HECL::SystemString fp(path); + fp += '/'; + fp += d->d_name; + HECL::Sstat st; + if (HECL::Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode)) + continue; + sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, 0, true))); + } + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + if (mode == Mode::DirsSorted) + break; + rewinddir(dir); + } + case Mode::FilesSorted: + { + if (mode == Mode::FilesSorted) + m_entries.clear(); + std::map sort; + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + HECL::SystemString fp(path); + fp += '/'; + fp += d->d_name; + HECL::Sstat st; + if (HECL::Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode)) + continue; + sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, st.st_size, false))); + } + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + break; + } + } + closedir(dir); +#endif + } + + operator bool() const {return m_entries.size() != 0;} + size_t size() const {return m_entries.size();} + std::vector::const_iterator begin() const {return m_entries.cbegin();} + std::vector::const_iterator end() const {return m_entries.cend();} +}; + /** * @brief Special ProjectRootPath class for opening HECLDatabase::IProject instances * diff --git a/hecl/lib/CMakeLists.txt b/hecl/lib/CMakeLists.txt index 5c18cb0b3..00b7f3c2d 100644 --- a/hecl/lib/CMakeLists.txt +++ b/hecl/lib/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(HECLCommon HECL.cpp ProjectPath.cpp WideStringConvert.cpp + HumanizeNumber.cpp CVar.cpp CVarManager.cpp ../include/HECL/CVar.hpp diff --git a/hecl/lib/HumanizeNumber.cpp b/hecl/lib/HumanizeNumber.cpp new file mode 100644 index 000000000..22b8c128d --- /dev/null +++ b/hecl/lib/HumanizeNumber.cpp @@ -0,0 +1,164 @@ +#include "HECL/HECL.hpp" +#include + +/* + * Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc. + * Copyright 2013 John-Mark Gurney + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, + * NASA Ames Research Center, by Luke Mewburn and by Tomas Svensson. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace HECL +{ +static LogVisor::LogModule Log("HECL::HumanizeNumber"); + +static const int maxscale = 7; + +std::string HumanizeNumber(int64_t quotient, size_t len, const char* suffix, int scale, HNFlags flags) +{ + const char *prefixes, *sep; + int i, r, remainder, s1, s2, sign; + int divisordeccut; + int64_t divisor, max; + size_t baselen; + + /* validate args */ + if (suffix == nullptr) + suffix = ""; + if ((flags & HNFlags::Divisor1000) != HNFlags::None && (flags & HNFlags::IECPrefixes) != HNFlags::None) + Log.report(LogVisor::FatalError, "invalid flags combo"); + + /* setup parameters */ + remainder = 0; + + if ((flags & HNFlags::IECPrefixes) != HNFlags::None) { + baselen = 2; + /* + * Use the prefixes for power of two recommended by + * the International Electrotechnical Commission + * (IEC) in IEC 80000-3 (i.e. Ki, Mi, Gi...). + * + * HN_IEC_PREFIXES implies a divisor of 1024 here + * (use of HN_DIVISOR_1000 would have triggered + * an assertion earlier). + */ + divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ + if ((flags & HNFlags::B) != HNFlags::None) + prefixes = "B\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; + else + prefixes = "\0\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; + } else { + baselen = 1; + if ((flags & HNFlags::Divisor1000) != HNFlags::None) { + divisor = 1000; + divisordeccut = 950; + if ((flags & HNFlags::B) != HNFlags::None) + prefixes = "B\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + } else { + divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ + if ((flags & HNFlags::B) != HNFlags::None) + prefixes = "B\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + } + } + +#define SCALE2PREFIX(scale) (&prefixes[(scale) * 3]) + + if (quotient < 0) { + sign = -1; + quotient = -quotient; + baselen += 2; /* sign, digit */ + } else { + sign = 1; + baselen += 1; /* digit */ + } + if ((flags & HNFlags::NoSpace) != HNFlags::None) + sep = ""; + else { + sep = " "; + baselen++; + } + baselen += strlen(suffix); + + /* Check if enough room for `x y' + suffix */ + if (len < baselen) + Log.report(LogVisor::FatalError, + "buffer size %" PRISize "insufficient for minimum size %" PRISize, + len, baselen); + std::string ret(len, '\0'); + len += 1; + + if ((scale & int(HNScale::AutoScale)) != 0) { + /* See if there is additional columns can be used. */ + for (max = 1, i = len - baselen; i-- > 0;) + max *= 10; + + /* + * Divide the number until it fits the given column. + * If there will be an overflow by the rounding below, + * divide once more. + */ + for (i = 0; + (quotient >= max || (quotient == max - 1 && + remainder >= divisordeccut)) && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } + } else { + for (i = 0; i < scale && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } + } + + /* If a value <= 9.9 after rounding and ... */ + /* + * XXX - should we make sure there is enough space for the decimal + * place and if not, don't do HN_DECIMAL? + */ + if (((quotient == 9 && remainder < divisordeccut) || quotient < 9) && + i > 0 && (flags & HNFlags::Decimal) != HNFlags::None) { + s1 = (int)quotient + ((remainder * 10 + divisor / 2) / + divisor / 10); + s2 = ((remainder * 10 + divisor / 2) / divisor) % 10; + r = snprintf(&ret[0], len, "%d%s%d%s%s%s", + sign * s1, localeconv()->decimal_point, s2, + sep, SCALE2PREFIX(i), suffix); + } else + r = snprintf(&ret[0], len, "%" PRId64 "%s%s%s", + sign * (quotient + (remainder + divisor / 2) / divisor), + sep, SCALE2PREFIX(i), suffix); + + return ret; +} + +}