commit 63d788288d60c1ab7ae8ee6e7d7712dcb922d826
Author: wehub-resource-sync
Date: Mon Jul 13 12:36:55 2026 +0800
chore: import upstream snapshot with attribution
diff --git a/.github/workflows/issue_stale.yaml b/.github/workflows/issue_stale.yaml
new file mode 100644
index 0000000..7c11c2b
--- /dev/null
+++ b/.github/workflows/issue_stale.yaml
@@ -0,0 +1,30 @@
+name: Close inactive issues
+on:
+ schedule:
+ - cron: "35 11 * * 5"
+
+env:
+ DAYS_BEFORE_ISSUE_STALE: 30
+ DAYS_BEFORE_ISSUE_CLOSE: 14
+
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v5
+ with:
+ days-before-issue-stale: ${{ env.DAYS_BEFORE_ISSUE_STALE }}
+ days-before-issue-close: ${{ env.DAYS_BEFORE_ISSUE_CLOSE }}
+ stale-issue-label: "stale"
+ stale-issue-message: |
+ This issue is stale because it has been open for ${{ env.DAYS_BEFORE_ISSUE_STALE }} days with no activity.
+ It will be closed if no further activity occurs. Let us know if you still need help!
+ close-issue-message: |
+ This issue is being closed because it has been stale for ${{ env.DAYS_BEFORE_ISSUE_CLOSE }} days with no activity.
+ If you still need help, please feel free to leave comments.
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d347336
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,62 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# VS Code
+.vscode/*
+
+# CLion
+.idea/*
+
+# Tests
+ufomap_tests/*
+
+# Build folder
+build/*
+
+# Install folder
+install/*
+
+# Cache folder
+.cache/*
+
+# Data folder
+data/*
+
+# Docs
+docs/html/*
+
+# coverage report
+coverage_report/*
+.coverage/
+
+*.pcd
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..25e9045
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ufomap"]
+ path = ufomap
+ url = https://github.com/UnknownFreeOccupied/ufomap
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..abc4930
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.17)
+
+project(dufomap
+ VERSION 1.0.0
+ DESCRIPTION "DUFOMap: Efficient Dynamic Awareness Mapping"
+ LANGUAGES CXX
+)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release)
+endif()
+
+add_subdirectory(ufomap)
+include_directories(${CMAKE_SOURCE_DIR}/ufomap)
+
+add_executable(dufomap_run src/dufomap.cpp)
+set_target_properties(dufomap_run
+ PROPERTIES
+ VERSION ${PROJECT_VERSION}
+ SOVERSION ${PROJECT_VERSION_MAJOR}
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED YES
+ CXX_EXTENSIONS NO
+)
+target_link_libraries(dufomap_run PRIVATE UFO::Map)
+target_compile_features(dufomap_run
+ PUBLIC
+ cxx_std_20
+)
+target_compile_options(dufomap_run
+ PRIVATE
+ # -Wall
+ # -Werror
+ # -Wextra
+ # -Wpedantic
+ # -Wunreachable-code
+ # -Wconversion
+ # -Wcast-align
+ # -Wunused
+ # -Wno-unused-parameter
+ # -Wold-style-cast
+ # -Wpointer-arith
+ # -Wcast-qual
+ # -Wno-missing-braces
+ # -Wdouble-promotion
+
+ # -Wshadow
+ # -march=native
+)
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a250676
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+FROM ubuntu:focal
+LABEL maintainer="Qingwen Zhang "
+
+# Just in case we need it
+ENV DEBIAN_FRONTEND noninteractive
+
+# install g++10 and tbb
+RUN apt update && apt install -y wget git zsh tmux vim g++ curl unzip
+RUN apt update && apt install -y gcc-10 g++-10 libtbb-dev liblz4-dev liblzf-dev pkg-config
+
+# install CMAKE
+RUN apt update && apt install -y gnupg gnupg2 software-properties-common
+RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null
+RUN apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' && apt update && apt install -y cmake
+
+# since we will output pcd file, don't want to root to lock it. normally 1000 is the first user in our desktop also
+RUN useradd -ms /bin/bash -u 1000 kin
+USER kin
+
+# setup oh-my-zsh
+RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
+RUN printf "y\ny\ny\n\n" | bash -c "$(curl -fsSL https://raw.githubusercontent.com/Kin-Zhang/Kin-Zhang/main/scripts/setup_ohmyzsh.sh)"
+
+RUN mkdir -p /home/kin/workspace && mkdir -p /home/kin/data
+RUN git clone --recurse-submodules -b main --single-branch https://github.com/KTH-RPL/dufomap /home/kin/workspace/dufomap
+WORKDIR /home/kin/workspace/dufomap
+
+# Run container: docker run -it --rm --name dufomap -v /home/kin/data:/home/kin/data zhangkin/dufomap /bin/zsh
+# you can also run with root user in existing container: docker exec -it -u 0 dufomap /bin/zsh
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e6674cc
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2024, Daniel Duberg
+
+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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f724f97
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+
+
DUFOMap: Efficient Dynamic Awareness Mapping
+
+
+[](https://arxiv.org/abs/2403.01449)
+[](https://KTH-RPL.github.io/dufomap)
+[](https://mit-spark.github.io/Longterm-Perception-WS/assets/proceedings/DUFOMap/poster.pdf)
+[](https://youtu.be/isDnAVoVD5M)
+
+Quick Demo: Run with the **same parameter setting** without tuning for different sensor (e.g 16, 32, 64, and 128 channel LiDAR and Livox-series mid360), the following shows the data collected from:
+
+| Leica-RTC360 | 128-channel LiDAR | Livox-mid360 |
+| ------- | ------- | ------- |
+|  |  |  |
+
+
+🚀 2024-11-20: Update dufomap Python API from [SeFlow](https://github.com/KTH-RPL/SeFlow) try it now! `pip install dufomap` and run `python main.py --data_dir data/00` to get the cleaned map directly. Support all >=Python 3.8 in Windows and Linux. Please extract your own data to **the unified format** first follow [this wiki page](https://kth-rpl.github.io/DynamicMap_Benchmark/data/creation/#custom-data).
+
+
+Clone quickly and init submodules:
+```bash
+git clone --recursive -b main --single-branch https://github.com/KTH-RPL/dufomap.git
+
+
+# The easiest way to run DUFOMap:
+pip install dufomap
+python main.py --data_dir data/00
+```
+
+
+### Dependencies
+
+If you want to compile the C++ source version, please install the following dependencies:
+
+```bash
+sudo apt update && sudo apt install gcc-10 g++-10
+sudo apt install libtbb-dev liblz4-dev
+```
+
+Or you can directly build docker image through our [Dockerfile](Dockerfile):
+```bash
+docker build -t dufomap .
+```
+
+### 1. Build & Run
+
+Build:
+
+```bash
+cmake -B build -D CMAKE_CXX_COMPILER=g++-10 && cmake --build build
+```
+
+Prepare Data: Teaser data (KITTI 00: 384.4Mb) can be downloaded via follow commands, more data detail can be found in the [dataset section](https://kth-rpl.github.io/DynamicMap_Benchmark/data) or format your own dataset follow [custom dataset section](https://kth-rpl.github.io/DynamicMap_Benchmark/data/creation/#custom-data).
+
+```bash
+wget https://zenodo.org/records/8160051/files/00.zip -p data
+unzip data/00.zip -d data
+```
+
+Run:
+
+```bash
+./build/dufomap_run data/00 assets/config.toml
+```
+
+
+
+## 2. Evaluation
+
+Please reference to [DynamicMap_Benchmark](https://github.com/KTH-RPL/DynamicMap_Benchmark) for the evaluation of DUFOMap and comparison with other dynamic removal methods.
+
+[Evaluation Section link](https://github.com/KTH-RPL/DynamicMap_Benchmark/blob/master/scripts/README.md#evaluation)
+
+
+## Acknowledgements
+
+Thanks to HKUST Ramlab's members: Bowen Yang, Lu Gan, Mingkai Tang, and Yingbing Chen, who help collect additional datasets.
+
+This work was partially supported by the Wallenberg AI, Autonomous Systems and Software Program ([WASP](https://wasp-sweden.org/)) funded by the Knut and Alice Wallenberg Foundation including the WASP NEST PerCorSo.
+
+Feel free to explore below projects that use [ufomap](https://github.com/UnknownFreeOccupied/ufomap) (attach code links as follows):
+- [RA-L'24 DUFOMap, Dynamic Awareness]()
+- [RA-L'23 SLICT, SLAM](https://github.com/brytsknguyen/slict)
+- [RA-L'20 UFOMap, Mapping Framework](https://github.com/UnknownFreeOccupied/ufomap)
+
+### Citation
+
+Please cite our works if you find these useful for your research.
+
+```
+@article{daniel2024dufomap,
+ author={Duberg, Daniel and Zhang, Qingwen and Jia, MingKai and Jensfelt, Patric},
+ journal={IEEE Robotics and Automation Letters},
+ title={{DUFOMap}: Efficient Dynamic Awareness Mapping},
+ year={2024},
+ volume={9},
+ number={6},
+ pages={1-8},
+ doi={10.1109/LRA.2024.3387658}
+}
+@article{duberg2020ufomap,
+ author={Duberg, Daniel and Jensfelt, Patric},
+ journal={IEEE Robotics and Automation Letters},
+ title={{UFOMap}: An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown},
+ year={2020},
+ volume={5},
+ number={4},
+ pages={6411-6418},
+ doi={10.1109/LRA.2020.3013861}
+}
+@inproceedings{zhang2023benchmark,
+ author={Zhang, Qingwen and Duberg, Daniel and Geng, Ruoyu and Jia, Mingkai and Wang, Lujia and Jensfelt, Patric},
+ booktitle={IEEE 26th International Conference on Intelligent Transportation Systems (ITSC)},
+ title={A Dynamic Points Removal Benchmark in Point Cloud Maps},
+ year={2023},
+ pages={608-614},
+ doi={10.1109/ITSC57777.2023.10422094}
+}
+```
diff --git a/README.wehub.md b/README.wehub.md
new file mode 100644
index 0000000..8000671
--- /dev/null
+++ b/README.wehub.md
@@ -0,0 +1,7 @@
+# WeHub 来源说明
+
+- 原始项目:`KTH-RPL/dufomap`
+- 原始仓库:https://github.com/KTH-RPL/dufomap
+- 导入方式:上游默认分支的最新快照
+- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
+- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
diff --git a/assets/config.toml b/assets/config.toml
new file mode 100644
index 0000000..10d1c77
--- /dev/null
+++ b/assets/config.toml
@@ -0,0 +1,40 @@
+
+[important]
+resolution = 0.1 # voxel size (meters), v in the paper, default 0.1
+inflate_unknown = 1 # d_p in the paper, default 1
+inflate_hits_dist = 0.2 # d_s in the paper, default 0.2
+
+
+# ----------- No need to modify if you are not professional dufoer or ufoer.
+# ----------- Deeper Parameter for debugging potential better result --------
+
+[integration]
+inflate_unknown_compensation = true
+ray_passthrough_hits = false
+down_sampling_method = "center" # ["none", "center", "centroid", "uniform"]
+min_range = 0.2 # For filter out the ego points (some SLAM algorithm will include a pose point etc.
+max_range = -1 # Maximum range to integrate
+only_valid = false # Only do ray casting for points within 'max_range'
+hit_depth = 0 # Level of the octree to integrate hits
+miss_depth = 0 # Level of the octree to integrate misses
+ray_casting_depth = 0 # Level of the octree to perform ray casting
+simple_ray_casting = false
+simple_ray_casting_factor = 1.0
+parallel = true # Use parallel execution for integration
+num_threads = 0 # Number of threads to use, 8 times number of physical cores seems to be a good number and it is chosen if 0 is given
+
+[map]
+levels = 20 # Levels of the octree
+
+[dataset]
+first = 0 # First pcd index
+last = -1 # Last pcd index (-1 equals all)
+num = -1 # Number of pcd files to read (-1 equals all)
+
+[printing]
+verbose = false
+debug = false
+
+[output]
+filename = "dufomap_output"
+raycasting = false
diff --git a/assets/demo.png b/assets/demo.png
new file mode 100644
index 0000000..03e8d24
Binary files /dev/null and b/assets/demo.png differ
diff --git a/assets/imgs/doals_train_128.gif b/assets/imgs/doals_train_128.gif
new file mode 100755
index 0000000..0e6e116
Binary files /dev/null and b/assets/imgs/doals_train_128.gif differ
diff --git a/assets/imgs/dufomap_leica.gif b/assets/imgs/dufomap_leica.gif
new file mode 100644
index 0000000..5892364
Binary files /dev/null and b/assets/imgs/dufomap_leica.gif differ
diff --git a/assets/imgs/two_floor_mid360.gif b/assets/imgs/two_floor_mid360.gif
new file mode 100755
index 0000000..e2d42d5
Binary files /dev/null and b/assets/imgs/two_floor_mid360.gif differ
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..635ed5e
--- /dev/null
+++ b/main.py
@@ -0,0 +1,82 @@
+"""
+# Created: 2024-11-20 13:11
+# Copyright (C) 2024-now, RPL, KTH Royal Institute of Technology
+# Author: Qingwen Zhang (https://kin-zhang.github.io/)
+#
+# This file is part of DUFOMap (https://github.com/KTH-RPL/dufomap) and
+# DynamicMap Benchmark (https://github.com/KTH-RPL/DynamicMap_Benchmark) projects.
+# If you find this repo helpful, please cite the respective publication as
+# listed on the above website.
+
+# Description: Output Cleaned Map through Python API.
+"""
+from pathlib import Path
+import os, fire, time
+import numpy as np
+from tqdm import tqdm
+
+from dufomap import dufomap
+from dufomap.utils import pcdpy3
+def inv_pose_matrix(pose):
+ inv_pose = np.eye(4)
+ inv_pose[:3, :3] = pose[:3, :3].T
+ inv_pose[:3, 3] = -pose[:3, :3].T.dot(pose[:3, 3])
+ return inv_pose
+
+MIN_AXIS_RANGE = 0.2 # HARD CODED: remove ego vehicle points
+MAX_AXIS_RANGE = 50 # HARD CODED: remove far away points
+
+class DynamicMapData:
+ def __init__(self, directory):
+ self.scene_id = directory.split("/")[-1]
+ self.directory = Path(directory) / "pcd"
+ self.pcd_files = [os.path.join(self.directory, f) for f in sorted(os.listdir(self.directory)) if f.endswith('.pcd')]
+
+ def __len__(self):
+ return len(self.pcd_files)
+
+ def __getitem__(self, index_):
+ res_dict = {
+ 'scene_id': self.scene_id,
+ 'timestamp': self.pcd_files[index_].split("/")[-1].split(".")[0],
+ }
+ pcd_ = pcdpy3.PointCloud.from_path(self.pcd_files[index_])
+ pc0 = pcd_.np_data[:,:3]
+ res_dict['pc'] = pc0.astype(np.float32)
+ res_dict['pose'] = list(pcd_.viewpoint)
+ return res_dict
+
+def main_vis(
+ data_dir: str = "/home/kin/data/00",
+ voxel_map: bool = True, # output voxel-level map or raw point-level.
+):
+ dataset = DynamicMapData(data_dir)
+
+ # STEP 0: initialize
+ mydufo = dufomap(0.1, 0.2, 2, num_threads=12) # resolution, d_s, d_p same with paper.
+ cloud_acc = np.zeros((0, 3), dtype=np.float32)
+ for data_id in (pbar := tqdm(range(0, len(dataset)),ncols=100)):
+ data = dataset[data_id]
+ now_scene_id = data['scene_id']
+ pbar.set_description(f"id: {data_id}, scene_id: {now_scene_id}, timestamp: {data['timestamp']}")
+ norm_pc0 = np.linalg.norm(data['pc'][:, :3] - data['pose'][:3], axis=1)
+ range_mask = (
+ (norm_pc0>MIN_AXIS_RANGE) &
+ (norm_pc0
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// TOML
+#include "toml.hpp"
+#include "indicators.hpp"
+
+// STL
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef UFO_PARALLEL
+// STL
+#include
+#endif
+
+struct Dataset {
+ std::size_t first = 0;
+ std::size_t last = -1;
+ std::size_t num = -1;
+};
+
+struct Map {
+ ufo::node_size_t resolution = 0.1; // In meters
+ ufo::depth_t levels = 17; // Levels of the octree
+};
+
+struct Clustering {
+ bool cluster = false;
+ float max_distance = 0.2f;
+ std::size_t min_points = 0;
+ ufo::depth_t depth = 0;
+};
+
+struct Printing {
+ bool verbose = true;
+ bool debug = false;
+};
+
+struct Output {
+ std::string filename = "output";
+ bool has_color = false;
+ bool raycasting = false;
+};
+
+struct Config {
+ Dataset dataset;
+ Map map;
+ ufo::IntegrationParams integration;
+ bool propagate = false;
+ Clustering clustering;
+ Printing printing;
+ Output output;
+
+ void read(toml::table tbl)
+ {
+ map.resolution = read(tbl["important"]["resolution"], map.resolution);
+
+ dataset.first = read(tbl["dataset"]["first"], dataset.first);
+ dataset.last = read(tbl["dataset"]["last"], dataset.last);
+ dataset.num = read(tbl["dataset"]["num"], dataset.num);
+ map.levels = read(tbl["map"]["levels"], map.levels);
+
+ auto dsm = read(tbl["integration"]["down_sampling_method"], std::string("none"));
+ integration.down_sampling_method =
+ "none" == dsm
+ ? ufo::DownSamplingMethod::NONE
+ : ("centroid" == dsm ? ufo::DownSamplingMethod::CENTROID
+ : ("uniform" == dsm ? ufo::DownSamplingMethod::UNIFORM
+ : ufo::DownSamplingMethod::CENTER));
+ integration.hit_depth = read(tbl["integration"]["hit_depth"], integration.hit_depth);
+ integration.miss_depth =
+ read(tbl["integration"]["miss_depth"], integration.miss_depth);
+ integration.min_range = read(tbl["integration"]["min_range"], integration.min_range);
+ integration.max_range = read(tbl["integration"]["max_range"], integration.max_range);
+ integration.inflate_unknown =
+ read(tbl["important"]["inflate_unknown"], integration.inflate_unknown);
+ integration.inflate_unknown_compensation =
+ read(tbl["integration"]["inflate_unknown_compensation"],
+ integration.inflate_unknown_compensation);
+ integration.ray_passthrough_hits = read(tbl["integration"]["ray_passthrough_hits"],
+ integration.ray_passthrough_hits);
+ integration.inflate_hits_dist =
+ read(tbl["important"]["inflate_hits_dist"], integration.inflate_hits_dist);
+ integration.ray_casting_method = read(tbl["integration"]["simple_ray_casting"], true)
+ ? ufo::RayCastingMethod::SIMPLE
+ : ufo::RayCastingMethod::PROPER;
+ integration.simple_ray_casting_factor =
+ read(tbl["integration"]["simple_ray_casting_factor"],
+ integration.simple_ray_casting_factor);
+ integration.parallel = tbl["integration"]["parallel"].value_or(integration.parallel);
+ integration.num_threads =
+ read(tbl["integration"]["num_threads"], integration.num_threads);
+ integration.only_valid =
+ read(tbl["integration"]["only_valid"], integration.only_valid);
+
+ propagate = read(tbl["integration"]["propagate"], propagate);
+
+ clustering.cluster = read(tbl["clustering"]["cluster"], clustering.cluster);
+ clustering.max_distance =
+ read(tbl["clustering"]["max_distance"], clustering.max_distance);
+ clustering.min_points = read(tbl["clustering"]["min_points"], clustering.min_points);
+ clustering.depth = read(tbl["clustering"]["depth"], clustering.depth);
+
+ printing.verbose = read(tbl["printing"]["verbose"], printing.verbose);
+ printing.debug = read(tbl["printing"]["debug"], printing.debug);
+
+ output.filename = read(tbl["output"]["filename"], output.filename);
+ output.has_color = read(tbl["output"]["has_color"], output.has_color);
+ output.raycasting = read(tbl["output"]["raycasting"], output.raycasting);
+ }
+
+ void save() const
+ {
+ // TODO: Implement
+ }
+
+ private:
+ template
+ std::remove_cvref_t read(toml::node_view node, T&& default_value)
+ {
+ // if (!node.is_value()) {
+ // node.as_array()->push_back("MISSING");
+ // missing_config = true;
+ // std::cout << node << '\n';
+ // return default_value;
+ // }
+
+ return node.value_or(default_value);
+ }
+
+ private:
+ bool missing_config{false};
+ bool wrong_config{false};
+};
+
+std::ostream& operator<<(std::ostream& out, Config const& config)
+{
+ out << "Config\n";
+
+ out << "\tDataset\n";
+ out << "\t\tFirst: " << config.dataset.first << '\n';
+ out << "\t\tLast: ";
+ if (-1 == config.dataset.last) {
+ out << -1 << '\n';
+ } else {
+ out << config.dataset.last << '\n';
+ }
+ out << "\t\tNum: ";
+ if (-1 == config.dataset.num) {
+ out << -1 << '\n';
+ } else {
+ out << config.dataset.num << '\n';
+ }
+
+ out << "\tMap\n";
+ out << "\t\tResolution: " << config.map.resolution << '\n';
+ out << "\t\tLevels: " << +config.map.levels << '\n';
+
+ out << "\tIntegration\n";
+ out << "\t\tDown sampling method: "
+ << (ufo::DownSamplingMethod::NONE == config.integration.down_sampling_method
+ ? "none"
+ : (ufo::DownSamplingMethod::CENTER ==
+ config.integration.down_sampling_method
+ ? "center"
+ : (ufo::DownSamplingMethod::CENTROID ==
+ config.integration.down_sampling_method
+ ? "centroid"
+ : "uniform")))
+ << '\n';
+ out << "\t\tHit depth: " << +config.integration.hit_depth << '\n';
+ out << "\t\tMiss depth: " << +config.integration.miss_depth << '\n';
+ out << "\t\tMin range: " << config.integration.min_range << '\n';
+ out << "\t\tMax range: " << config.integration.max_range << '\n';
+ out << "\t\tInflate unknown " << config.integration.inflate_unknown
+ << '\n';
+ out << "\t\tInflate unknown compensation "
+ << config.integration.inflate_unknown_compensation << '\n';
+ out << "\t\tRay passthrough hits " << config.integration.ray_passthrough_hits
+ << '\n';
+ out << "\t\tInflate hits dist " << config.integration.inflate_hits_dist
+ << '\n';
+ out << "\t\tEarly stop distance: " << config.integration.early_stop_distance
+ << '\n';
+ out << "\t\tSimple ray casting: " << std::boolalpha
+ << (ufo::RayCastingMethod::SIMPLE == config.integration.ray_casting_method ? true
+ : false)
+ << '\n';
+ out << "\t\tSimple ray casting factor: "
+ << config.integration.simple_ray_casting_factor << '\n';
+ out << "\t\tParallel: " << config.integration.parallel << '\n';
+ out << "\t\tNum threads: " << config.integration.num_threads << '\n';
+ out << "\t\tOnly valid: " << config.integration.only_valid << '\n';
+ out << "\t\tSliding window size: " << config.integration.sliding_window_size
+ << '\n';
+ out << "\t\tPropagate: " << std::boolalpha << config.propagate
+ << '\n';
+
+ out << "\tClustering\n";
+ out << "\t\tCluster: " << std::boolalpha << config.clustering.cluster << '\n';
+ out << "\t\tMax distance: " << config.clustering.max_distance << '\n';
+ out << "\t\tMin points: " << config.clustering.min_points << '\n';
+ out << "\t\tDepth: " << +config.clustering.depth << '\n';
+
+ out << "\tPrinting\n";
+ out << "\t\tVerbose: " << std::boolalpha << config.printing.verbose << '\n';
+ out << "\t\tDebug: " << std::boolalpha << config.printing.debug << '\n';
+
+ out << "\tOutput\n";
+ out << "\t\tFilename: " << config.output.filename << '\n';
+ out << "\t\tHas color: " << std::boolalpha << config.output.has_color << '\n';
+ out << "\t\tRaycasting: " << config.output.raycasting << '\n';
+
+ return out;
+}
+
+Config readConfig(std::filesystem::path path)
+{
+ Config config;
+ for (;;) {
+ if (std::filesystem::exists(path)) {
+ toml::table tbl;
+ try {
+ tbl = toml::parse_file((path).string());
+ } catch (toml::parse_error const& err) {
+ std::cerr << "Configuration parsing failed:\n" << err << '\n';
+ exit(1);
+ }
+
+ config.read(tbl);
+ if (config.printing.verbose) {
+ std::cout << "Found: " << (path) << '\n';
+ }
+
+ break;
+ }
+ if (!path.has_parent_path()) {
+ std::cout << "Did not find configuration file, using default.\n";
+ break;
+ }
+ path = path.parent_path();
+ }
+
+ if (config.printing.verbose) {
+ std::cout << config << '\n';
+ }
+
+ return config;
+}
+
+ufo::Color randomColor()
+{
+ static std::random_device rd;
+ static std::mt19937 gen(rd());
+ static std::uniform_int_distribution dis(0, -1);
+ return {dis(gen), dis(gen), dis(gen)};
+}
+
+template
+void cluster(Map& map, Clustering const& clustering)
+{
+ std::unordered_set seen;
+ std::vector queue;
+
+ auto depth = clustering.depth;
+ auto max_distance = clustering.max_distance;
+ auto min_points = clustering.min_points;
+
+ ufo::label_t l{1};
+ for (auto node : map.query(ufo::pred::Leaf(depth) && ufo::pred::SeenFree() &&
+ ufo::pred::HitsMin(1) && ufo::pred::Label(0))) {
+ if (map.label(node.index())) { // FIXME: This is because how the iterator works
+ continue;
+ }
+
+ seen = {node};
+ queue.assign(1, ufo::Sphere(map.center(node), max_distance));
+
+ map.setLabel(node, l, false);
+
+ while (!queue.empty()) {
+ auto p = ufo::pred::Intersects(queue);
+ queue.clear();
+ for (auto const& node : map.query(
+ ufo::pred::Leaf(depth) && ufo::pred::SeenFree() && ufo::pred::HitsMin(1) &&
+ ufo::pred::Label(0) && std::move(p) &&
+ ufo::pred::Satisfies([&seen](auto n) { return seen.insert(n).second; }))) {
+ queue.emplace_back(map.center(node), max_distance);
+ map.setLabel(node, l, false);
+ }
+ }
+
+ if (seen.size() < min_points) {
+ for (auto e : seen) {
+ if (l == map.label(e)) {
+ map.setLabel(e, -1, false);
+ }
+ }
+ }
+
+ l += seen.size() >= min_points;
+
+ map.propagateModified(); // FIXME: Should this be here?
+ }
+}
+
+template
+void filterminDistance(PointCloud& cloud, ufo::Point origin, float min_distance)
+{
+ float sqrt_dist = min_distance * min_distance;
+ std::erase_if(cloud, [origin, sqrt_dist](auto const& point) {
+ return origin.squaredDistance(point) < sqrt_dist;
+ });
+}
+
+int main(int argc, char* argv[])
+{
+ if (1 >= argc) {
+ std::cout << "[ERROR] Please running by: " << argv[0] << " [pcd_folder] [optional: config_file_path]";
+ return 0;
+ }
+
+ std::filesystem::path path(argv[1]);
+ std::string config_file_path;
+ if (argc > 2)
+ config_file_path = argv[2];
+ else
+ config_file_path = std::string(argv[1]) + "/dufomap.toml";
+
+ auto config = readConfig(std::filesystem::path(config_file_path));
+ std::cout << "[LOG] Step 1: Successfully read configuration from: " << config_file_path < map(
+ config.map.resolution, config.map.levels);
+ map.reserve(100'000'000);
+
+ std::vector pcds;
+ for (const auto& entry : std::filesystem::directory_iterator(path / "pcd")) {
+ if (!entry.is_regular_file()) {
+ continue;
+ }
+ std::size_t i = std::stoul(entry.path().stem());
+ if (config.dataset.first <= i && config.dataset.last >= i) {
+ pcds.push_back(entry.path().filename());
+ }
+ }
+ std::ranges::sort(pcds);
+ // std::cout << config << std::endl;
+ pcds.resize(std::min(pcds.size(), config.dataset.num));
+
+ ufo::Timing timing;
+ timing.start("Total");
+
+ ufo::PointCloudColor cloud_acc;
+
+ std::cout << "[LOG] Step 2: Starting Processing data from: " << path << '\n';
+ indicators::show_console_cursor(false);
+ indicators::BlockProgressBar bar{
+ indicators::option::BarWidth{50},
+ indicators::option::Start{"["},
+ indicators::option::End{"]"},
+ indicators::option::PrefixText{"[LOG] Running dufomap "},
+ indicators::option::ForegroundColor{indicators::Color::white},
+ indicators::option::ShowElapsedTime{true},
+ indicators::option::ShowRemainingTime{true},
+ indicators::option::FontStyles{std::vector{indicators::FontStyle::bold}}
+ };
+
+ for (std::size_t i{}; std::string filename : pcds) {
+ bar.set_progress(100 * i / pcds.size());
+ ++i;
+ timing.setTag("Total " + std::to_string(i) + " of " + std::to_string(pcds.size()) +
+ " (" + std::to_string(100 * i / pcds.size()) + "%)");
+
+ ufo::PointCloudColor cloud;
+ ufo::Pose6f viewpoint;
+ timing[1].start("Read");
+ ufo::readPointCloudPCD(path / "pcd" / filename, cloud, viewpoint);
+ timing[1].stop();
+
+ // NOTE(Qingwen): lots of user facing about ego points inside data, we filterminDistance around ego agent.
+ filterminDistance(cloud, viewpoint.translation, config.integration.min_range);
+
+ cloud_acc.insert(std::end(cloud_acc), std::cbegin(cloud), std::cend(cloud));
+
+ ufo::insertPointCloud(map, cloud, viewpoint.translation, config.integration,
+ config.propagate);
+
+ if (config.printing.verbose) {
+ timing[2] = config.integration.timing;
+ timing.print(true, true, 2, 4);
+ }
+ }
+ indicators::show_console_cursor(true);
+ std::cout << "\033[0m\n[LOG] Step 3: Finished Processing data. Start saving map... " << std::endl;
+ // bar.is_completed();
+ if (!config.propagate) {
+ timing[3].start("Propagate");
+ map.propagateModified();
+ timing[3].stop();
+ }
+
+ timing[4].start("Cluster");
+ if (config.clustering.cluster) {
+ cluster(map, config.clustering);
+ }
+ timing[4].stop();
+
+ timing[5].start("Query");
+ ufo::PointCloudColor cloud_static;
+
+ for (auto& p : cloud_acc) {
+ if (!map.seenFree(p))
+ cloud_static.push_back(p);
+ }
+
+ timing[5].stop();
+
+ timing[6].start("write");
+ ufo::writePointCloudPCD(path / (config.output.filename + ".pcd"), cloud_static);
+ timing[6].stop();
+ timing.stop();
+
+ timing[2] = config.integration.timing;
+ timing.print(true, true, 2, 4);
+ std::cout << "[LOG]: Finished! ^v^.. Clean output map with " << cloud_static.size() << " points save in " << path / (config.output.filename + ".pcd") << '\n';
+}
\ No newline at end of file
diff --git a/src/indicators.hpp b/src/indicators.hpp
new file mode 100644
index 0000000..21256d3
--- /dev/null
+++ b/src/indicators.hpp
@@ -0,0 +1,3248 @@
+// header from: https://github.com/p-ranav/indicators
+#ifndef INDICATORS_COLOR
+#define INDICATORS_COLOR
+
+namespace indicators {
+enum class Color { grey, red, green, yellow, blue, magenta, cyan, white, unspecified };
+}
+
+#endif
+
+
+
+#ifndef INDICATORS_FONT_STYLE
+#define INDICATORS_FONT_STYLE
+
+namespace indicators {
+enum class FontStyle { bold, dark, italic, underline, blink, reverse, concealed, crossed };
+}
+
+#endif
+
+
+
+#ifndef INDICATORS_PROGRESS_TYPE
+#define INDICATORS_PROGRESS_TYPE
+
+namespace indicators {
+enum class ProgressType { incremental, decremental };
+}
+
+#endif
+
+//!
+//! termcolor
+//! ~~~~~~~~~
+//!
+//! termcolor is a header-only c++ library for printing colored messages
+//! to the terminal. Written just for fun with a help of the Force.
+//!
+//! :copyright: (c) 2013 by Ihor Kalnytskyi
+//! :license: BSD, see LICENSE for details
+//!
+
+#ifndef TERMCOLOR_HPP_
+#define TERMCOLOR_HPP_
+
+#include
+#include
+#include
+
+// Detect target's platform and set some macros in order to wrap platform
+// specific code this library depends on.
+#if defined(_WIN32) || defined(_WIN64)
+# define TERMCOLOR_TARGET_WINDOWS
+#elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
+# define TERMCOLOR_TARGET_POSIX
+#endif
+
+// If implementation has not been explicitly set, try to choose one based on
+// target platform.
+#if !defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) && !defined(TERMCOLOR_USE_WINDOWS_API) && !defined(TERMCOLOR_USE_NOOP)
+# if defined(TERMCOLOR_TARGET_POSIX)
+# define TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES
+# define TERMCOLOR_AUTODETECTED_IMPLEMENTATION
+# elif defined(TERMCOLOR_TARGET_WINDOWS)
+# define TERMCOLOR_USE_WINDOWS_API
+# define TERMCOLOR_AUTODETECTED_IMPLEMENTATION
+# endif
+#endif
+
+// These headers provide isatty()/fileno() functions, which are used for
+// testing whether a standard stream refers to the terminal.
+#if defined(TERMCOLOR_TARGET_POSIX)
+# include
+#elif defined(TERMCOLOR_TARGET_WINDOWS)
+#if defined(_MSC_VER)
+#if !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+#endif
+# include
+# include
+#endif
+
+
+namespace termcolor
+{
+ // Forward declaration of the `_internal` namespace.
+ // All comments are below.
+ namespace _internal
+ {
+ inline int colorize_index();
+ inline FILE* get_standard_stream(const std::ostream& stream);
+ inline bool is_colorized(std::ostream& stream);
+ inline bool is_atty(const std::ostream& stream);
+
+ #if defined(TERMCOLOR_TARGET_WINDOWS)
+ inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1);
+ #endif
+ }
+
+ inline
+ std::ostream& colorize(std::ostream& stream)
+ {
+ stream.iword(_internal::colorize_index()) = 1L;
+ return stream;
+ }
+
+ inline
+ std::ostream& nocolorize(std::ostream& stream)
+ {
+ stream.iword(_internal::colorize_index()) = 0L;
+ return stream;
+ }
+
+ inline
+ std::ostream& reset(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[00m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, -1);
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bold(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[1m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& dark(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[2m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& italic(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[3m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& underline(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[4m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, COMMON_LVB_UNDERSCORE);
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& blink(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[5m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& reverse(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[7m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& concealed(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[8m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& crossed(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[9m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ template inline
+ std::ostream& color(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[12];
+ std::snprintf(command, sizeof(command), "\033[38;5;%dm", code);
+ stream << command;
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ template inline
+ std::ostream& on_color(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[12];
+ std::snprintf(command, sizeof(command), "\033[48;5;%dm", code);
+ stream << command;
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ template inline
+ std::ostream& color(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[20];
+ std::snprintf(command, sizeof(command), "\033[38;2;%d;%d;%dm", r, g, b);
+ stream << command;
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ template inline
+ std::ostream& on_color(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[20];
+ std::snprintf(command, sizeof(command), "\033[48;2;%d;%d;%dm", r, g, b);
+ stream << command;
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[30m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ 0 // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[31m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[32m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[33m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[34m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[35m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[36m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[37m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& bright_grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[90m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ 0 | FOREGROUND_INTENSITY // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[91m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_RED | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[92m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[93m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[94m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[95m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[96m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& bright_white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[97m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& on_grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[40m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ 0 // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[41m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[42m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[43m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[44m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[45m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE | BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[46m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[47m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED
+ );
+ #endif
+ }
+
+ return stream;
+ }
+
+
+ inline
+ std::ostream& on_bright_grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[100m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ 0 | BACKGROUND_INTENSITY // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[101m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_RED | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[102m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[103m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[104m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[105m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[106m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_bright_white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[107m";
+ #elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY
+ );
+ #endif
+ }
+
+ return stream;
+ }
+
+
+
+ //! Since C++ hasn't a way to hide something in the header from
+ //! the outer access, I have to introduce this namespace which
+ //! is used for internal purpose and should't be access from
+ //! the user code.
+ namespace _internal
+ {
+ // An index to be used to access a private storage of I/O streams. See
+ // colorize / nocolorize I/O manipulators for details. Due to the fact
+ // that static variables ain't shared between translation units, inline
+ // function with local static variable is used to do the trick and share
+ // the variable value between translation units.
+ inline int colorize_index()
+ {
+ static int colorize_index = std::ios_base::xalloc();
+ return colorize_index;
+ }
+
+ //! Since C++ hasn't a true way to extract stream handler
+ //! from the a given `std::ostream` object, I have to write
+ //! this kind of hack.
+ inline
+ FILE* get_standard_stream(const std::ostream& stream)
+ {
+ if (&stream == &std::cout)
+ return stdout;
+ else if ((&stream == &std::cerr) || (&stream == &std::clog))
+ return stderr;
+
+ return nullptr;
+ }
+
+ // Say whether a given stream should be colorized or not. It's always
+ // true for ATTY streams and may be true for streams marked with
+ // colorize flag.
+ inline
+ bool is_colorized(std::ostream& stream)
+ {
+ return is_atty(stream) || static_cast(stream.iword(colorize_index()));
+ }
+
+ //! Test whether a given `std::ostream` object refers to
+ //! a terminal.
+ inline
+ bool is_atty(const std::ostream& stream)
+ {
+ FILE* std_stream = get_standard_stream(stream);
+
+ // Unfortunately, fileno() ends with segmentation fault
+ // if invalid file descriptor is passed. So we need to
+ // handle this case gracefully and assume it's not a tty
+ // if standard stream is not detected, and 0 is returned.
+ if (!std_stream)
+ return false;
+
+ #if defined(TERMCOLOR_TARGET_POSIX)
+ return ::isatty(fileno(std_stream));
+ #elif defined(TERMCOLOR_TARGET_WINDOWS)
+ return ::_isatty(_fileno(std_stream));
+ #else
+ return false;
+ #endif
+ }
+
+ #if defined(TERMCOLOR_TARGET_WINDOWS)
+ //! Change Windows Terminal colors attribute. If some
+ //! parameter is `-1` then attribute won't changed.
+ inline void win_change_attributes(std::ostream& stream, int foreground, int background)
+ {
+ // yeah, i know.. it's ugly, it's windows.
+ static WORD defaultAttributes = 0;
+
+ // Windows doesn't have ANSI escape sequences and so we use special
+ // API to change Terminal output color. That means we can't
+ // manipulate colors by means of "std::stringstream" and hence
+ // should do nothing in this case.
+ if (!_internal::is_atty(stream))
+ return;
+
+ // get terminal handle
+ HANDLE hTerminal = INVALID_HANDLE_VALUE;
+ if (&stream == &std::cout)
+ hTerminal = GetStdHandle(STD_OUTPUT_HANDLE);
+ else if (&stream == &std::cerr)
+ hTerminal = GetStdHandle(STD_ERROR_HANDLE);
+
+ // save default terminal attributes if it unsaved
+ if (!defaultAttributes)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+ return;
+ defaultAttributes = info.wAttributes;
+ }
+
+ // restore all default settings
+ if (foreground == -1 && background == -1)
+ {
+ SetConsoleTextAttribute(hTerminal, defaultAttributes);
+ return;
+ }
+
+ // get current settings
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+ return;
+
+ if (foreground != -1)
+ {
+ info.wAttributes &= ~(info.wAttributes & 0x0F);
+ info.wAttributes |= static_cast(foreground);
+ }
+
+ if (background != -1)
+ {
+ info.wAttributes &= ~(info.wAttributes & 0xF0);
+ info.wAttributes |= static_cast(background);
+ }
+
+ SetConsoleTextAttribute(hTerminal, info.wAttributes);
+ }
+ #endif // TERMCOLOR_TARGET_WINDOWS
+
+ } // namespace _internal
+
+} // namespace termcolor
+
+
+#undef TERMCOLOR_TARGET_POSIX
+#undef TERMCOLOR_TARGET_WINDOWS
+
+#if defined(TERMCOLOR_AUTODETECTED_IMPLEMENTATION)
+# undef TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES
+# undef TERMCOLOR_USE_WINDOWS_API
+#endif
+
+#endif // TERMCOLOR_HPP_
+
+
+
+#ifndef INDICATORS_TERMINAL_SIZE
+#define INDICATORS_TERMINAL_SIZE
+#include
+
+
+#if defined(_WIN32)
+#include
+
+namespace indicators {
+
+static inline std::pair terminal_size() {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ int cols, rows;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
+ cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ return {static_cast(rows), static_cast(cols)};
+}
+
+static inline size_t terminal_width() { return terminal_size().second; }
+
+} // namespace indicators
+
+#else
+
+#include //ioctl() and TIOCGWINSZ
+#include // for STDOUT_FILENO
+
+namespace indicators {
+
+static inline std::pair terminal_size() {
+ struct winsize size{};
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
+ return {static_cast(size.ws_row), static_cast(size.ws_col)};
+}
+
+static inline size_t terminal_width() { return terminal_size().second; }
+
+} // namespace indicators
+
+#endif
+
+#endif
+
+
+/*
+Activity Indicators for Modern C++
+https://github.com/p-ranav/indicators
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2019 Dawid Pilarski .
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#ifndef INDICATORS_SETTING
+#define INDICATORS_SETTING
+
+#include
+// #include
+// #include
+// #include
+#include
+#include
+#include
+#include
+#include
+
+namespace indicators {
+
+namespace details {
+
+template struct if_else;
+
+template <> struct if_else { using type = std::true_type; };
+
+template <> struct if_else { using type = std::false_type; };
+
+template struct if_else_type;
+
+template struct if_else_type {
+ using type = True;
+};
+
+template struct if_else_type {
+ using type = False;
+};
+
+template struct conjuction;
+
+template <> struct conjuction<> : std::true_type {};
+
+template
+struct conjuction
+ : if_else_type>::type {};
+
+template struct disjunction;
+
+template <> struct disjunction<> : std::false_type {};
+
+template
+struct disjunction
+ : if_else_type>::type {};
+
+enum class ProgressBarOption {
+ bar_width = 0,
+ prefix_text,
+ postfix_text,
+ start,
+ end,
+ fill,
+ lead,
+ remainder,
+ max_postfix_text_len,
+ completed,
+ show_percentage,
+ show_elapsed_time,
+ show_remaining_time,
+ saved_start_time,
+ foreground_color,
+ spinner_show,
+ spinner_states,
+ font_styles,
+ hide_bar_when_complete,
+ min_progress,
+ max_progress,
+ progress_type,
+ stream
+};
+
+template struct Setting {
+ template ::value>::type>
+ explicit Setting(Args &&... args) : value(std::forward(args)...) {}
+ Setting(const Setting &) = default;
+ Setting(Setting &&) = default;
+
+ static constexpr auto id = Id;
+ using type = T;
+
+ T value{};
+};
+
+template struct is_setting : std::false_type {};
+
+template struct is_setting> : std::true_type {};
+
+template
+struct are_settings : if_else...>::value>::type {};
+
+template <> struct are_settings<> : std::true_type {};
+
+template struct is_setting_from_tuple;
+
+template struct is_setting_from_tuple> : std::true_type {};
+
+template
+struct is_setting_from_tuple>
+ : if_else...>::value>::type {};
+
+template
+struct are_settings_from_tuple
+ : if_else...>::value>::type {};
+
+template struct always_true { static constexpr auto value = true; };
+
+template Default &&get_impl(Default &&def) {
+ return std::forward(def);
+}
+
+template
+auto get_impl(Default && /*def*/, T &&first, Args &&... /*tail*/) ->
+ typename std::enable_if<(std::decay::type::id == Id),
+ decltype(std::forward(first))>::type {
+ return std::forward(first);
+}
+
+template
+auto get_impl(Default &&def, T && /*first*/, Args &&... tail) ->
+ typename std::enable_if<(std::decay::type::id != Id),
+ decltype(get_impl(std::forward(def),
+ std::forward(tail)...))>::type {
+ return get_impl(std::forward(def), std::forward(tail)...);
+}
+
+template ::value, void>::type>
+auto get(Default &&def, Args &&... args)
+ -> decltype(details::get_impl(std::forward(def), std::forward(args)...)) {
+ return details::get_impl(std::forward(def), std::forward(args)...);
+}
+
+template using StringSetting = Setting;
+
+template using IntegerSetting = Setting;
+
+template using BooleanSetting = Setting;
+
+template struct option_idx;
+
+template
+struct option_idx, counter>
+ : if_else_type<(Id == T::id), std::integral_constant,
+ option_idx, counter + 1>>::type {};
+
+template struct option_idx, counter> {
+ static_assert(always_true<(ProgressBarOption)Id>::value, "No such option was found");
+};
+
+template
+auto get_value(Settings &&settings)
+ -> decltype((std::get::type>::value>(
+ std::declval()))) {
+ return std::get::type>::value>(
+ std::forward(settings));
+}
+
+} // namespace details
+
+namespace option {
+using BarWidth = details::IntegerSetting;
+using PrefixText = details::StringSetting;
+using PostfixText = details::StringSetting;
+using Start = details::StringSetting;
+using End = details::StringSetting;
+using Fill = details::StringSetting;
+using Lead = details::StringSetting;
+using Remainder = details::StringSetting;
+using MaxPostfixTextLen = details::IntegerSetting;
+using Completed = details::BooleanSetting;
+using ShowPercentage = details::BooleanSetting;
+using ShowElapsedTime = details::BooleanSetting;
+using ShowRemainingTime = details::BooleanSetting;
+using SavedStartTime = details::BooleanSetting;
+using ForegroundColor = details::Setting;
+using ShowSpinner = details::BooleanSetting;
+using SpinnerStates =
+ details::Setting, details::ProgressBarOption::spinner_states>;
+using HideBarWhenComplete =
+ details::BooleanSetting;
+using FontStyles =
+ details::Setting, details::ProgressBarOption::font_styles>;
+using MinProgress = details::IntegerSetting;
+using MaxProgress = details::IntegerSetting;
+using ProgressType = details::Setting;
+using Stream = details::Setting;
+} // namespace option
+} // namespace indicators
+
+#endif
+
+
+#ifndef INDICATORS_CURSOR_CONTROL
+#define INDICATORS_CURSOR_CONTROL
+
+#if defined(_MSC_VER)
+#if !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+#include
+#include
+#else
+#include
+#endif
+
+namespace indicators {
+
+#if defined(_MSC_VER)
+
+static inline void show_console_cursor(bool const show) {
+ HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_CURSOR_INFO cursorInfo;
+
+ GetConsoleCursorInfo(out, &cursorInfo);
+ cursorInfo.bVisible = show; // set the cursor visibility
+ SetConsoleCursorInfo(out, &cursorInfo);
+}
+
+static inline void erase_line() {
+ auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (!hStdout)
+ return;
+
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(hStdout, &csbiInfo);
+
+ COORD cursor;
+
+ cursor.X = 0;
+ cursor.Y = csbiInfo.dwCursorPosition.Y;
+
+ DWORD count = 0;
+
+ FillConsoleOutputCharacterA(hStdout, ' ', csbiInfo.dwSize.X, cursor, &count);
+
+ FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes, csbiInfo.dwSize.X,
+ cursor, &count);
+
+ SetConsoleCursorPosition(hStdout, cursor);
+}
+
+#else
+
+static inline void show_console_cursor(bool const show) {
+ std::fputs(show ? "\033[?25h" : "\033[?25l", stdout);
+}
+
+static inline void erase_line() {
+ std::fputs("\r\033[K", stdout);
+}
+
+#endif
+
+} // namespace indicators
+
+#endif
+
+
+#ifndef INDICATORS_CURSOR_MOVEMENT
+#define INDICATORS_CURSOR_MOVEMENT
+
+#if defined(_MSC_VER)
+#if !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+#include
+#include
+#else
+#include
+#endif
+
+namespace indicators {
+
+#ifdef _MSC_VER
+
+static inline void move(int x, int y) {
+ auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (!hStdout)
+ return;
+
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(hStdout, &csbiInfo);
+
+ COORD cursor;
+
+ cursor.X = csbiInfo.dwCursorPosition.X + x;
+ cursor.Y = csbiInfo.dwCursorPosition.Y + y;
+ SetConsoleCursorPosition(hStdout, cursor);
+}
+
+static inline void move_up(int lines) { move(0, -lines); }
+static inline void move_down(int lines) { move(0, -lines); }
+static inline void move_right(int cols) { move(cols, 0); }
+static inline void move_left(int cols) { move(-cols, 0); }
+
+#else
+
+static inline void move_up(int lines) { std::cout << "\033[" << lines << "A"; }
+static inline void move_down(int lines) { std::cout << "\033[" << lines << "B"; }
+static inline void move_right(int cols) { std::cout << "\033[" << cols << "C"; }
+static inline void move_left(int cols) { std::cout << "\033[" << cols << "D"; }
+
+#endif
+
+} // namespace indicators
+
+#endif
+
+
+#ifndef INDICATORS_STREAM_HELPER
+#define INDICATORS_STREAM_HELPER
+
+// #include
+#ifndef INDICATORS_DISPLAY_WIDTH
+#define INDICATORS_DISPLAY_WIDTH
+
+#include
+#include
+#include
+#include
+#include
+
+namespace unicode {
+
+namespace details {
+
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static inline int bisearch(wchar_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static inline int mk_wcwidth(wchar_t ucs) {
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489},
+ {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2},
+ {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603},
+ {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670},
+ {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED},
+ {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A},
+ {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902},
+ {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D},
+ {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981},
+ {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD},
+ {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C},
+ {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D},
+ {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC},
+ {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD},
+ {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C},
+ {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D},
+ {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0},
+ {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48},
+ {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC},
+ {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD},
+ {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D},
+ {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6},
+ {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E},
+ {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC},
+ {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35},
+ {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E},
+ {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97},
+ {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030},
+ {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039},
+ {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F},
+ {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753},
+ {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD},
+ {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD},
+ {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922},
+ {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B},
+ {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34},
+ {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42},
+ {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF},
+ {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063},
+ {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F},
+ {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B},
+ {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F},
+ {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB},
+ {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F},
+ {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169},
+ {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
+ {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F},
+ {0xE0100, 0xE01EF}};
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 &&
+ ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+
+static inline int mk_wcswidth(const wchar_t *pwcs, size_t n) {
+ int w, width = 0;
+
+ for (; *pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcswidth(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+static inline int mk_wcwidth_cjk(wchar_t ucs) {
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
+ static const struct interval ambiguous[] = {
+ {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
+ {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, {0x00B0, 0x00B4},
+ {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
+ {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
+ {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
+ {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
+ {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
+ {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
+ {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
+ {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
+ {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
+ {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
+ {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
+ {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
+ {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
+ {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
+ {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
+ {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1},
+ {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, {0x03C3, 0x03C9},
+ {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451},
+ {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019},
+ {0x201C, 0x201D}, {0x2020, 0x2022}, {0x2024, 0x2027},
+ {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035},
+ {0x203B, 0x203B}, {0x203E, 0x203E}, {0x2074, 0x2074},
+ {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC},
+ {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109},
+ {0x2113, 0x2113}, {0x2116, 0x2116}, {0x2121, 0x2122},
+ {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154},
+ {0x215B, 0x215E}, {0x2160, 0x216B}, {0x2170, 0x2179},
+ {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2},
+ {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200},
+ {0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B},
+ {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215},
+ {0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223},
+ {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E},
+ {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248},
+ {0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261},
+ {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F},
+ {0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295},
+ {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF},
+ {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B},
+ {0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595},
+ {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3},
+ {0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1},
+ {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1},
+ {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606},
+ {0x2609, 0x2609}, {0x260E, 0x260F}, {0x2614, 0x2615},
+ {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640},
+ {0x2642, 0x2642}, {0x2660, 0x2661}, {0x2663, 0x2665},
+ {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F},
+ {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF},
+ {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}};
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+static inline int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) {
+ int w, width = 0;
+
+ for (; *pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+// convert UTF-8 string to wstring
+#ifdef _MSC_VER
+static inline std::wstring utf8_decode(const std::string& s) {
+ auto r = setlocale(LC_ALL, "");
+ std::string curLocale;
+ if (r)
+ curLocale = r;
+ const char* _Source = s.c_str();
+ size_t _Dsize = std::strlen(_Source) + 1;
+ wchar_t* _Dest = new wchar_t[_Dsize];
+ size_t _Osize;
+ mbstowcs_s(&_Osize, _Dest, _Dsize, _Source, _Dsize);
+ std::wstring result = _Dest;
+ delete[] _Dest;
+ setlocale(LC_ALL, curLocale.c_str());
+ return result;
+}
+#else
+static inline std::wstring utf8_decode(const std::string& s) {
+ auto r = setlocale(LC_ALL, "");
+ std::string curLocale;
+ if (r)
+ curLocale = r;
+ const char* _Source = s.c_str();
+ size_t _Dsize = mbstowcs(NULL, _Source, 0) + 1;
+ wchar_t* _Dest = new wchar_t[_Dsize];
+ wmemset(_Dest, 0, _Dsize);
+ mbstowcs(_Dest, _Source, _Dsize);
+ std::wstring result = _Dest;
+ delete[] _Dest;
+ setlocale(LC_ALL, curLocale.c_str());
+ return result;
+}
+#endif
+
+} // namespace details
+
+static inline int display_width(const std::string &input) {
+ using namespace unicode::details;
+ return mk_wcswidth(utf8_decode(input).c_str(), input.size());
+}
+
+static inline int display_width(const std::wstring &input) {
+ return details::mk_wcswidth(input.c_str(), input.size());
+}
+
+} // namespace unicode
+
+#endif
+// #include
+// #include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace indicators {
+namespace details {
+
+inline void set_stream_color(std::ostream &os, Color color) {
+ switch (color) {
+ case Color::grey:
+ os << termcolor::grey;
+ break;
+ case Color::red:
+ os << termcolor::red;
+ break;
+ case Color::green:
+ os << termcolor::green;
+ break;
+ case Color::yellow:
+ os << termcolor::yellow;
+ break;
+ case Color::blue:
+ os << termcolor::blue;
+ break;
+ case Color::magenta:
+ os << termcolor::magenta;
+ break;
+ case Color::cyan:
+ os << termcolor::cyan;
+ break;
+ case Color::white:
+ os << termcolor::white;
+ break;
+ default:
+ assert(false);
+ }
+}
+
+inline void set_font_style(std::ostream &os, FontStyle style) {
+ switch (style) {
+ case FontStyle::bold:
+ os << termcolor::bold;
+ break;
+ case FontStyle::dark:
+ os << termcolor::dark;
+ break;
+ case FontStyle::italic:
+ os << termcolor::italic;
+ break;
+ case FontStyle::underline:
+ os << termcolor::underline;
+ break;
+ case FontStyle::blink:
+ os << termcolor::blink;
+ break;
+ case FontStyle::reverse:
+ os << termcolor::reverse;
+ break;
+ case FontStyle::concealed:
+ os << termcolor::concealed;
+ break;
+ case FontStyle::crossed:
+ os << termcolor::crossed;
+ break;
+ default:
+ break;
+ }
+}
+
+inline std::ostream &write_duration(std::ostream &os, std::chrono::nanoseconds ns) {
+ using namespace std;
+ using namespace std::chrono;
+ using days = duration>;
+ char fill = os.fill();
+ os.fill('0');
+ auto d = duration_cast(ns);
+ ns -= d;
+ auto h = duration_cast(ns);
+ ns -= h;
+ auto m = duration_cast(ns);
+ ns -= m;
+ auto s = duration_cast(ns);
+ if (d.count() > 0)
+ os << setw(2) << d.count() << "d:";
+ if (h.count() > 0)
+ os << setw(2) << h.count() << "h:";
+ os << setw(2) << m.count() << "m:" << setw(2) << s.count() << 's';
+ os.fill(fill);
+ return os;
+}
+
+class BlockProgressScaleWriter {
+public:
+ BlockProgressScaleWriter(std::ostream &os, size_t bar_width) : os(os), bar_width(bar_width) {}
+
+ std::ostream &write(float progress) {
+ std::string fill_text{"█"};
+ std::vector lead_characters{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"};
+ auto value = (std::min)(1.0f, (std::max)(0.0f, progress / 100.0f));
+ auto whole_width = std::floor(value * bar_width);
+ auto remainder_width = fmod((value * bar_width), 1.0f);
+ auto part_width = std::floor(remainder_width * lead_characters.size());
+ std::string lead_text = lead_characters[size_t(part_width)];
+ if ((bar_width - whole_width - 1) < 0)
+ lead_text = "";
+ for (size_t i = 0; i < whole_width; ++i)
+ os << fill_text;
+ os << lead_text;
+ for (size_t i = 0; i < (bar_width - whole_width - 1); ++i)
+ os << " ";
+ return os;
+ }
+
+private:
+ std::ostream &os;
+ size_t bar_width = 0;
+};
+
+class ProgressScaleWriter {
+public:
+ ProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill,
+ const std::string &lead, const std::string &remainder)
+ : os(os), bar_width(bar_width), fill(fill), lead(lead), remainder(remainder) {}
+
+ std::ostream &write(float progress) {
+ auto pos = static_cast(progress * bar_width / 100.0);
+ for (size_t i = 0, current_display_width = 0; i < bar_width;) {
+ std::string next;
+
+ if (i < pos) {
+ next = fill;
+ current_display_width = unicode::display_width(fill);
+ } else if (i == pos) {
+ next = lead;
+ current_display_width = unicode::display_width(lead);
+ } else {
+ next = remainder;
+ current_display_width = unicode::display_width(remainder);
+ }
+
+ i += current_display_width;
+
+ if (i > bar_width) {
+ // `next` is larger than the allowed bar width
+ // fill with empty space instead
+ os << std::string((bar_width - (i - current_display_width)), ' ');
+ break;
+ }
+
+ os << next;
+ }
+ return os;
+ }
+
+private:
+ std::ostream &os;
+ size_t bar_width = 0;
+ std::string fill;
+ std::string lead;
+ std::string remainder;
+};
+
+class IndeterminateProgressScaleWriter {
+public:
+ IndeterminateProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill,
+ const std::string &lead)
+ : os(os), bar_width(bar_width), fill(fill), lead(lead) {}
+
+ std::ostream &write(size_t progress) {
+ for (size_t i = 0; i < bar_width;) {
+ std::string next;
+ size_t current_display_width = 0;
+
+ if (i < progress) {
+ next = fill;
+ current_display_width = unicode::display_width(fill);
+ } else if (i == progress) {
+ next = lead;
+ current_display_width = unicode::display_width(lead);
+ } else {
+ next = fill;
+ current_display_width = unicode::display_width(fill);
+ }
+
+ i += current_display_width;
+
+ if (i > bar_width) {
+ // `next` is larger than the allowed bar width
+ // fill with empty space instead
+ os << std::string((bar_width - (i - current_display_width)), ' ');
+ break;
+ }
+
+ os << next;
+ }
+ return os;
+ }
+
+private:
+ std::ostream &os;
+ size_t bar_width = 0;
+ std::string fill;
+ std::string lead;
+};
+
+} // namespace details
+} // namespace indicators
+
+#endif
+
+
+#ifndef INDICATORS_PROGRESS_BAR
+#define INDICATORS_PROGRESS_BAR
+
+// #include
+
+#include
+#include
+#include
+#include
+// #include
+// #include
+// #include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace indicators {
+
+class ProgressBar {
+ using Settings =
+ std::tuple;
+
+public:
+ template ::type...>::value,
+ void *>::type = nullptr>
+ explicit ProgressBar(Args &&... args)
+ : settings_(
+ details::get(
+ option::BarWidth{100}, std::forward(args)...),
+ details::get(
+ option::PrefixText{}, std::forward(args)...),
+ details::get(
+ option::PostfixText{}, std::forward(args)...),
+ details::get(
+ option::Start{"["}, std::forward(args)...),
+ details::get(
+ option::End{"]"}, std::forward(args)...),
+ details::get(
+ option::Fill{"="}, std::forward(args)...),
+ details::get(
+ option::Lead{">"}, std::forward(args)...),
+ details::get(
+ option::Remainder{" "}, std::forward(args)...),
+ details::get(
+ option::MaxPostfixTextLen{0}, std::forward(args)...),
+ details::get(
+ option::Completed{false}, std::forward(args)...),
+ details::get(
+ option::ShowPercentage{false}, std::forward(args)...),
+ details::get(
+ option::ShowElapsedTime{false}, std::forward(args)...),
+ details::get(
+ option::ShowRemainingTime{false}, std::forward(args)...),
+ details::get(
+ option::SavedStartTime{false}, std::forward(args)...),
+ details::get(
+ option::ForegroundColor{Color::unspecified},
+ std::forward(args)...),
+ details::get(
+ option::FontStyles{std::vector{}},
+ std::forward(args)...),
+ details::get(
+ option::MinProgress{0}, std::forward(args)...),
+ details::get(
+ option::MaxProgress{100}, std::forward(args)...),
+ details::get(
+ option::ProgressType{ProgressType::incremental},
+ std::forward(args)...),
+ details::get(
+ option::Stream{std::cout}, std::forward(args)...)) {
+
+ // if progress is incremental, start from min_progress
+ // else start from max_progress
+ const auto type = get_value