├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── config.toml ├── demo.png └── imgs │ ├── doals_train_128.gif │ ├── dufomap_leica.gif │ └── two_floor_mid360.gif ├── main.py └── src ├── dufomap.cpp ├── indicators.hpp └── toml.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # VS Code 35 | .vscode/* 36 | 37 | # CLion 38 | .idea/* 39 | 40 | # Tests 41 | ufomap_tests/* 42 | 43 | # Build folder 44 | build/* 45 | 46 | # Install folder 47 | install/* 48 | 49 | # Cache folder 50 | .cache/* 51 | 52 | # Data folder 53 | data/* 54 | 55 | # Docs 56 | docs/html/* 57 | 58 | # coverage report 59 | coverage_report/* 60 | .coverage/ 61 | 62 | *.pcd -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ufomap"] 2 | path = ufomap 3 | url = https://github.com/UnknownFreeOccupied/ufomap 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(dufomap 4 | VERSION 1.0.0 5 | DESCRIPTION "DUFOMap: Efficient Dynamic Awareness Mapping" 6 | LANGUAGES CXX 7 | ) 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | if(NOT CMAKE_BUILD_TYPE) 11 | set(CMAKE_BUILD_TYPE Release) 12 | endif() 13 | 14 | add_subdirectory(ufomap) 15 | include_directories(${CMAKE_SOURCE_DIR}/ufomap) 16 | 17 | add_executable(dufomap_run src/dufomap.cpp) 18 | set_target_properties(dufomap_run 19 | PROPERTIES 20 | VERSION ${PROJECT_VERSION} 21 | SOVERSION ${PROJECT_VERSION_MAJOR} 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | ) 26 | target_link_libraries(dufomap_run PRIVATE UFO::Map) 27 | target_compile_features(dufomap_run 28 | PUBLIC 29 | cxx_std_20 30 | ) 31 | target_compile_options(dufomap_run 32 | PRIVATE 33 | # -Wall 34 | # -Werror 35 | # -Wextra 36 | # -Wpedantic 37 | # -Wunreachable-code 38 | # -Wconversion 39 | # -Wcast-align 40 | # -Wunused 41 | # -Wno-unused-parameter 42 | # -Wold-style-cast 43 | # -Wpointer-arith 44 | # -Wcast-qual 45 | # -Wno-missing-braces 46 | # -Wdouble-promotion 47 | 48 | # -Wshadow 49 | # -march=native 50 | ) 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | LABEL maintainer="Qingwen Zhang " 3 | 4 | # Just in case we need it 5 | ENV DEBIAN_FRONTEND noninteractive 6 | 7 | # install g++10 and tbb 8 | RUN apt update && apt install -y wget git zsh tmux vim g++ curl unzip 9 | RUN apt update && apt install -y gcc-10 g++-10 libtbb-dev liblz4-dev liblzf-dev pkg-config 10 | 11 | # install CMAKE 12 | RUN apt update && apt install -y gnupg gnupg2 software-properties-common 13 | 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 14 | RUN apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' && apt update && apt install -y cmake 15 | 16 | # since we will output pcd file, don't want to root to lock it. normally 1000 is the first user in our desktop also 17 | RUN useradd -ms /bin/bash -u 1000 kin 18 | USER kin 19 | 20 | # setup oh-my-zsh 21 | RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended 22 | RUN printf "y\ny\ny\n\n" | bash -c "$(curl -fsSL https://raw.githubusercontent.com/Kin-Zhang/Kin-Zhang/main/scripts/setup_ohmyzsh.sh)" 23 | 24 | RUN mkdir -p /home/kin/workspace && mkdir -p /home/kin/data 25 | RUN git clone --recurse-submodules -b main --single-branch https://github.com/KTH-RPL/dufomap /home/kin/workspace/dufomap 26 | WORKDIR /home/kin/workspace/dufomap 27 | 28 | # Run container: docker run -it --rm --name dufomap -v /home/kin/data:/home/kin/data zhangkin/dufomap /bin/zsh 29 | # you can also run with root user in existing container: docker exec -it -u 0 dufomap /bin/zsh 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Daniel Duberg 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

DUFOMap: Efficient Dynamic Awareness Mapping

3 |

4 | 5 | [![arXiv](https://img.shields.io/badge/arXiv-2403.01449-b31b1b?logo=arxiv&logoColor=white)](https://arxiv.org/abs/2403.01449) 6 | [![page](https://img.shields.io/badge/Project-Page-green)](https://KTH-RPL.github.io/dufomap) [![poster](https://img.shields.io/badge/RAL2024|Poster-6495ed?style=flat&logo=Shotcut&logoColor=wihte)](https://mit-spark.github.io/Longterm-Perception-WS/assets/proceedings/DUFOMap/poster.pdf) [video coming soon] 7 | 8 | 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: 9 | 10 | | Leica-RTC360 | 128-channel LiDAR | Livox-mid360 | 11 | | ------- | ------- | ------- | 12 | | ![](assets/imgs/dufomap_leica.gif) | ![](assets/imgs/doals_train_128.gif) | ![](assets/imgs/two_floor_mid360.gif) | 13 | 14 | 15 | 🚀 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 unified format first follow [this wiki page](https://kth-rpl.github.io/DynamicMap_Benchmark/data/creation/#custom-data). 16 | 17 | ## 0. Setup 18 | 19 | ```bash 20 | sudo apt update && sudo apt install gcc-10 g++-10 21 | sudo apt install libtbb-dev liblz4-dev 22 | ``` 23 | 24 | 25 | Clone quickly and init submodules: 26 | ```bash 27 | git clone --recursive -b main --single-branch https://github.com/KTH-RPL/dufomap.git 28 | ``` 29 | 30 | Or you can directly build docker image through our [Dockerfile](Dockerfile): 31 | ```bash 32 | docker build -t dufomap . 33 | ``` 34 | 35 | ## 1. Build & Run 36 | 37 | Build: 38 | 39 | ```bash 40 | cmake -B build -D CMAKE_CXX_COMPILER=g++-10 && cmake --build build 41 | ``` 42 | 43 | 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). 44 | 45 | ```bash 46 | wget https://zenodo.org/records/8160051/files/00.zip -p data 47 | unzip data/00.zip -d data 48 | ``` 49 | 50 | Run: 51 | 52 | ```bash 53 | ./build/dufomap_run data/00 assets/config.toml 54 | ``` 55 | 56 | ![dufomap](assets/demo.png) 57 | 58 | ## 2. Evaluation 59 | 60 | Please reference to [DynamicMap_Benchmark](https://github.com/KTH-RPL/DynamicMap_Benchmark) for the evaluation of DUFOMap and comparison with other dynamic removal methods. 61 | 62 | [Evaluation Section link](https://github.com/KTH-RPL/DynamicMap_Benchmark/blob/master/scripts/README.md#evaluation) 63 | 64 | 65 | ## Acknowledgements 66 | 67 | Thanks to HKUST Ramlab's members: Bowen Yang, Lu Gan, Mingkai Tang, and Yingbing Chen, who help collect additional datasets. 68 | 69 | 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. 70 | 71 | Feel free to explore below projects that use [ufomap](https://github.com/UnknownFreeOccupied/ufomap) (attach code links as follows): 72 | - [RA-L'24 DUFOMap, Dynamic Awareness]() 73 | - [RA-L'23 SLICT, SLAM](https://github.com/brytsknguyen/slict) 74 | - [RA-L'20 UFOMap, Mapping Framework](https://github.com/UnknownFreeOccupied/ufomap) 75 | 76 | ### Citation 77 | 78 | Please cite our works if you find these useful for your research. 79 | 80 | ``` 81 | @article{daniel2024dufomap, 82 | author={Duberg, Daniel and Zhang, Qingwen and Jia, MingKai and Jensfelt, Patric}, 83 | journal={IEEE Robotics and Automation Letters}, 84 | title={{DUFOMap}: Efficient Dynamic Awareness Mapping}, 85 | year={2024}, 86 | volume={9}, 87 | number={6}, 88 | pages={1-8}, 89 | doi={10.1109/LRA.2024.3387658} 90 | } 91 | @article{duberg2020ufomap, 92 | author={Duberg, Daniel and Jensfelt, Patric}, 93 | journal={IEEE Robotics and Automation Letters}, 94 | title={{UFOMap}: An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown}, 95 | year={2020}, 96 | volume={5}, 97 | number={4}, 98 | pages={6411-6418}, 99 | doi={10.1109/LRA.2020.3013861} 100 | } 101 | @inproceedings{zhang2023benchmark, 102 | author={Zhang, Qingwen and Duberg, Daniel and Geng, Ruoyu and Jia, Mingkai and Wang, Lujia and Jensfelt, Patric}, 103 | booktitle={IEEE 26th International Conference on Intelligent Transportation Systems (ITSC)}, 104 | title={A Dynamic Points Removal Benchmark in Point Cloud Maps}, 105 | year={2023}, 106 | pages={608-614}, 107 | doi={10.1109/ITSC57777.2023.10422094} 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /assets/config.toml: -------------------------------------------------------------------------------- 1 | 2 | [important] 3 | resolution = 0.1 # voxel size (meters), v in the paper, default 0.1 4 | inflate_unknown = 1 # d_p in the paper, default 1 5 | inflate_hits_dist = 0.2 # d_s in the paper, default 0.2 6 | 7 | 8 | # ----------- No need to modify if you are not professional dufoer or ufoer. 9 | # ----------- Deeper Parameter for debugging potential better result -------- 10 | 11 | [integration] 12 | inflate_unknown_compensation = true 13 | ray_passthrough_hits = false 14 | down_sampling_method = "center" # ["none", "center", "centroid", "uniform"] 15 | max_range = -1 # Maximum range to integrate 16 | only_valid = false # Only do ray casting for points within 'max_range' 17 | hit_depth = 0 # Level of the octree to integrate hits 18 | miss_depth = 0 # Level of the octree to integrate misses 19 | ray_casting_depth = 0 # Level of the octree to perform ray casting 20 | simple_ray_casting = false 21 | simple_ray_casting_factor = 1.0 22 | parallel = true # Use parallel execution for integration 23 | 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 24 | 25 | [map] 26 | levels = 20 # Levels of the octree 27 | 28 | [dataset] 29 | first = 0 # First pcd index 30 | last = -1 # Last pcd index (-1 equals all) 31 | num = -1 # Number of pcd files to read (-1 equals all) 32 | 33 | [printing] 34 | verbose = false 35 | debug = false 36 | 37 | [output] 38 | filename = "dufomap_output" 39 | raycasting = false 40 | -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KTH-RPL/dufomap/5b2884f6c8efa3f035dd538a68e72296dc205ca2/assets/demo.png -------------------------------------------------------------------------------- /assets/imgs/doals_train_128.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KTH-RPL/dufomap/5b2884f6c8efa3f035dd538a68e72296dc205ca2/assets/imgs/doals_train_128.gif -------------------------------------------------------------------------------- /assets/imgs/dufomap_leica.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KTH-RPL/dufomap/5b2884f6c8efa3f035dd538a68e72296dc205ca2/assets/imgs/dufomap_leica.gif -------------------------------------------------------------------------------- /assets/imgs/two_floor_mid360.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KTH-RPL/dufomap/5b2884f6c8efa3f035dd538a68e72296dc205ca2/assets/imgs/two_floor_mid360.gif -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Created: 2024-11-20 13:11 3 | # Copyright (C) 2024-now, RPL, KTH Royal Institute of Technology 4 | # Author: Qingwen Zhang (https://kin-zhang.github.io/) 5 | # 6 | # This file is part of DUFOMap (https://github.com/KTH-RPL/dufomap) and 7 | # DynamicMap Benchmark (https://github.com/KTH-RPL/DynamicMap_Benchmark) projects. 8 | # If you find this repo helpful, please cite the respective publication as 9 | # listed on the above website. 10 | 11 | # Description: Output Cleaned Map through Python API. 12 | """ 13 | from pathlib import Path 14 | import os, fire, time 15 | import numpy as np 16 | from tqdm import tqdm 17 | 18 | from dufomap import dufomap 19 | from dufomap.utils import pcdpy3 20 | def inv_pose_matrix(pose): 21 | inv_pose = np.eye(4) 22 | inv_pose[:3, :3] = pose[:3, :3].T 23 | inv_pose[:3, 3] = -pose[:3, :3].T.dot(pose[:3, 3]) 24 | return inv_pose 25 | class DynamicMapData: 26 | def __init__(self, directory): 27 | self.scene_id = directory.split("/")[-1] 28 | self.directory = Path(directory) / "pcd" 29 | self.pcd_files = [os.path.join(self.directory, f) for f in sorted(os.listdir(self.directory)) if f.endswith('.pcd')] 30 | 31 | def __len__(self): 32 | return len(self.pcd_files) 33 | 34 | def __getitem__(self, index_): 35 | res_dict = { 36 | 'scene_id': self.scene_id, 37 | 'timestamp': self.pcd_files[index_].split("/")[-1].split(".")[0], 38 | } 39 | pcd_ = pcdpy3.PointCloud.from_path(self.pcd_files[index_]) 40 | pc0 = pcd_.np_data[:,:3] 41 | res_dict['pc'] = pc0.astype(np.float32) 42 | res_dict['pose'] = list(pcd_.viewpoint) 43 | return res_dict 44 | 45 | def main_vis( 46 | data_dir: str = "/home/kin/data/00", 47 | ): 48 | dataset = DynamicMapData(data_dir) 49 | 50 | # STEP 0: initialize 51 | mydufo = dufomap(0.1, 0.2, 2, num_threads=12) # resolution, d_s, d_p same with paper. 52 | cloud_acc = np.zeros((0, 3), dtype=np.float32) 53 | for data_id in (pbar := tqdm(range(0, len(dataset)),ncols=100)): 54 | data = dataset[data_id] 55 | now_scene_id = data['scene_id'] 56 | pbar.set_description(f"id: {data_id}, scene_id: {now_scene_id}, timestamp: {data['timestamp']}") 57 | 58 | # STEP 1: integrate point cloud into dufomap 59 | mydufo.run(data['pc'], data['pose'], cloud_transform = False) # since pc already in world frame 60 | cloud_acc = np.concatenate((cloud_acc, data['pc']), axis=0) 61 | 62 | # STEP 2: propagate 63 | mydufo.oncePropagateCluster(if_propagate=True, if_cluster=False) 64 | # STEP 3: Map results 65 | mydufo.outputMap(cloud_acc, voxel_map=False) 66 | # NOTE(Qingwen): You can also save voxeled map directly based on the resolution we set before: 67 | # mydufo.outputMap(cloud_acc, voxel_map=True) 68 | 69 | mydufo.printDetailTiming() 70 | 71 | if __name__ == "__main__": 72 | start_time = time.time() 73 | fire.Fire(main_vis) 74 | print(f"Time used: {time.time() - start_time:.2f} s") -------------------------------------------------------------------------------- /src/dufomap.cpp: -------------------------------------------------------------------------------- 1 | // UFO 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // TOML 16 | #include "toml.hpp" 17 | #include "indicators.hpp" 18 | 19 | // STL 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef UFO_PARALLEL 34 | // STL 35 | #include 36 | #endif 37 | 38 | struct Dataset { 39 | std::size_t first = 0; 40 | std::size_t last = -1; 41 | std::size_t num = -1; 42 | }; 43 | 44 | struct Map { 45 | ufo::node_size_t resolution = 0.1; // In meters 46 | ufo::depth_t levels = 17; // Levels of the octree 47 | }; 48 | 49 | struct Clustering { 50 | bool cluster = false; 51 | float max_distance = 0.2f; 52 | std::size_t min_points = 0; 53 | ufo::depth_t depth = 0; 54 | }; 55 | 56 | struct Printing { 57 | bool verbose = true; 58 | bool debug = false; 59 | }; 60 | 61 | struct Output { 62 | std::string filename = "output"; 63 | bool has_color = false; 64 | bool raycasting = false; 65 | }; 66 | 67 | struct Config { 68 | Dataset dataset; 69 | Map map; 70 | ufo::IntegrationParams integration; 71 | bool propagate = false; 72 | Clustering clustering; 73 | Printing printing; 74 | Output output; 75 | 76 | void read(toml::table tbl) 77 | { 78 | map.resolution = read(tbl["important"]["resolution"], map.resolution); 79 | 80 | dataset.first = read(tbl["dataset"]["first"], dataset.first); 81 | dataset.last = read(tbl["dataset"]["last"], dataset.last); 82 | dataset.num = read(tbl["dataset"]["num"], dataset.num); 83 | map.levels = read(tbl["map"]["levels"], map.levels); 84 | 85 | auto dsm = read(tbl["integration"]["down_sampling_method"], std::string("none")); 86 | integration.down_sampling_method = 87 | "none" == dsm 88 | ? ufo::DownSamplingMethod::NONE 89 | : ("centroid" == dsm ? ufo::DownSamplingMethod::CENTROID 90 | : ("uniform" == dsm ? ufo::DownSamplingMethod::UNIFORM 91 | : ufo::DownSamplingMethod::CENTER)); 92 | integration.hit_depth = read(tbl["integration"]["hit_depth"], integration.hit_depth); 93 | integration.miss_depth = 94 | read(tbl["integration"]["miss_depth"], integration.miss_depth); 95 | integration.min_range = read(tbl["integration"]["min_range"], integration.min_range); 96 | integration.max_range = read(tbl["integration"]["max_range"], integration.max_range); 97 | integration.inflate_unknown = 98 | read(tbl["important"]["inflate_unknown"], integration.inflate_unknown); 99 | integration.inflate_unknown_compensation = 100 | read(tbl["integration"]["inflate_unknown_compensation"], 101 | integration.inflate_unknown_compensation); 102 | integration.ray_passthrough_hits = read(tbl["integration"]["ray_passthrough_hits"], 103 | integration.ray_passthrough_hits); 104 | integration.inflate_hits_dist = 105 | read(tbl["important"]["inflate_hits_dist"], integration.inflate_hits_dist); 106 | integration.ray_casting_method = read(tbl["integration"]["simple_ray_casting"], true) 107 | ? ufo::RayCastingMethod::SIMPLE 108 | : ufo::RayCastingMethod::PROPER; 109 | integration.simple_ray_casting_factor = 110 | read(tbl["integration"]["simple_ray_casting_factor"], 111 | integration.simple_ray_casting_factor); 112 | integration.parallel = tbl["integration"]["parallel"].value_or(integration.parallel); 113 | integration.num_threads = 114 | read(tbl["integration"]["num_threads"], integration.num_threads); 115 | integration.only_valid = 116 | read(tbl["integration"]["only_valid"], integration.only_valid); 117 | 118 | propagate = read(tbl["integration"]["propagate"], propagate); 119 | 120 | clustering.cluster = read(tbl["clustering"]["cluster"], clustering.cluster); 121 | clustering.max_distance = 122 | read(tbl["clustering"]["max_distance"], clustering.max_distance); 123 | clustering.min_points = read(tbl["clustering"]["min_points"], clustering.min_points); 124 | clustering.depth = read(tbl["clustering"]["depth"], clustering.depth); 125 | 126 | printing.verbose = read(tbl["printing"]["verbose"], printing.verbose); 127 | printing.debug = read(tbl["printing"]["debug"], printing.debug); 128 | 129 | output.filename = read(tbl["output"]["filename"], output.filename); 130 | output.has_color = read(tbl["output"]["has_color"], output.has_color); 131 | output.raycasting = read(tbl["output"]["raycasting"], output.raycasting); 132 | } 133 | 134 | void save() const 135 | { 136 | // TODO: Implement 137 | } 138 | 139 | private: 140 | template 141 | std::remove_cvref_t read(toml::node_view node, T&& default_value) 142 | { 143 | // if (!node.is_value()) { 144 | // node.as_array()->push_back("MISSING"); 145 | // missing_config = true; 146 | // std::cout << node << '\n'; 147 | // return default_value; 148 | // } 149 | 150 | return node.value_or(default_value); 151 | } 152 | 153 | private: 154 | bool missing_config{false}; 155 | bool wrong_config{false}; 156 | }; 157 | 158 | std::ostream& operator<<(std::ostream& out, Config const& config) 159 | { 160 | out << "Config\n"; 161 | 162 | out << "\tDataset\n"; 163 | out << "\t\tFirst: " << config.dataset.first << '\n'; 164 | out << "\t\tLast: "; 165 | if (-1 == config.dataset.last) { 166 | out << -1 << '\n'; 167 | } else { 168 | out << config.dataset.last << '\n'; 169 | } 170 | out << "\t\tNum: "; 171 | if (-1 == config.dataset.num) { 172 | out << -1 << '\n'; 173 | } else { 174 | out << config.dataset.num << '\n'; 175 | } 176 | 177 | out << "\tMap\n"; 178 | out << "\t\tResolution: " << config.map.resolution << '\n'; 179 | out << "\t\tLevels: " << +config.map.levels << '\n'; 180 | 181 | out << "\tIntegration\n"; 182 | out << "\t\tDown sampling method: " 183 | << (ufo::DownSamplingMethod::NONE == config.integration.down_sampling_method 184 | ? "none" 185 | : (ufo::DownSamplingMethod::CENTER == 186 | config.integration.down_sampling_method 187 | ? "center" 188 | : (ufo::DownSamplingMethod::CENTROID == 189 | config.integration.down_sampling_method 190 | ? "centroid" 191 | : "uniform"))) 192 | << '\n'; 193 | out << "\t\tHit depth: " << +config.integration.hit_depth << '\n'; 194 | out << "\t\tMiss depth: " << +config.integration.miss_depth << '\n'; 195 | out << "\t\tMin range: " << config.integration.min_range << '\n'; 196 | out << "\t\tMax range: " << config.integration.max_range << '\n'; 197 | out << "\t\tInflate unknown " << config.integration.inflate_unknown 198 | << '\n'; 199 | out << "\t\tInflate unknown compensation " 200 | << config.integration.inflate_unknown_compensation << '\n'; 201 | out << "\t\tRay passthrough hits " << config.integration.ray_passthrough_hits 202 | << '\n'; 203 | out << "\t\tInflate hits dist " << config.integration.inflate_hits_dist 204 | << '\n'; 205 | out << "\t\tEarly stop distance: " << config.integration.early_stop_distance 206 | << '\n'; 207 | out << "\t\tSimple ray casting: " << std::boolalpha 208 | << (ufo::RayCastingMethod::SIMPLE == config.integration.ray_casting_method ? true 209 | : false) 210 | << '\n'; 211 | out << "\t\tSimple ray casting factor: " 212 | << config.integration.simple_ray_casting_factor << '\n'; 213 | out << "\t\tParallel: " << config.integration.parallel << '\n'; 214 | out << "\t\tNum threads: " << config.integration.num_threads << '\n'; 215 | out << "\t\tOnly valid: " << config.integration.only_valid << '\n'; 216 | out << "\t\tSliding window size: " << config.integration.sliding_window_size 217 | << '\n'; 218 | out << "\t\tPropagate: " << std::boolalpha << config.propagate 219 | << '\n'; 220 | 221 | out << "\tClustering\n"; 222 | out << "\t\tCluster: " << std::boolalpha << config.clustering.cluster << '\n'; 223 | out << "\t\tMax distance: " << config.clustering.max_distance << '\n'; 224 | out << "\t\tMin points: " << config.clustering.min_points << '\n'; 225 | out << "\t\tDepth: " << +config.clustering.depth << '\n'; 226 | 227 | out << "\tPrinting\n"; 228 | out << "\t\tVerbose: " << std::boolalpha << config.printing.verbose << '\n'; 229 | out << "\t\tDebug: " << std::boolalpha << config.printing.debug << '\n'; 230 | 231 | out << "\tOutput\n"; 232 | out << "\t\tFilename: " << config.output.filename << '\n'; 233 | out << "\t\tHas color: " << std::boolalpha << config.output.has_color << '\n'; 234 | out << "\t\tRaycasting: " << config.output.raycasting << '\n'; 235 | 236 | return out; 237 | } 238 | 239 | Config readConfig(std::filesystem::path path) 240 | { 241 | Config config; 242 | for (;;) { 243 | if (std::filesystem::exists(path)) { 244 | toml::table tbl; 245 | try { 246 | tbl = toml::parse_file((path).string()); 247 | } catch (toml::parse_error const& err) { 248 | std::cerr << "Configuration parsing failed:\n" << err << '\n'; 249 | exit(1); 250 | } 251 | 252 | config.read(tbl); 253 | if (config.printing.verbose) { 254 | std::cout << "Found: " << (path) << '\n'; 255 | } 256 | 257 | break; 258 | } 259 | if (!path.has_parent_path()) { 260 | std::cout << "Did not find configuration file, using default.\n"; 261 | break; 262 | } 263 | path = path.parent_path(); 264 | } 265 | 266 | if (config.printing.verbose) { 267 | std::cout << config << '\n'; 268 | } 269 | 270 | return config; 271 | } 272 | 273 | ufo::Color randomColor() 274 | { 275 | static std::random_device rd; 276 | static std::mt19937 gen(rd()); 277 | static std::uniform_int_distribution dis(0, -1); 278 | return {dis(gen), dis(gen), dis(gen)}; 279 | } 280 | 281 | template 282 | void cluster(Map& map, Clustering const& clustering) 283 | { 284 | std::unordered_set seen; 285 | std::vector queue; 286 | 287 | auto depth = clustering.depth; 288 | auto max_distance = clustering.max_distance; 289 | auto min_points = clustering.min_points; 290 | 291 | ufo::label_t l{1}; 292 | for (auto node : map.query(ufo::pred::Leaf(depth) && ufo::pred::SeenFree() && 293 | ufo::pred::HitsMin(1) && ufo::pred::Label(0))) { 294 | if (map.label(node.index())) { // FIXME: This is because how the iterator works 295 | continue; 296 | } 297 | 298 | seen = {node}; 299 | queue.assign(1, ufo::Sphere(map.center(node), max_distance)); 300 | 301 | map.setLabel(node, l, false); 302 | 303 | while (!queue.empty()) { 304 | auto p = ufo::pred::Intersects(queue); 305 | queue.clear(); 306 | for (auto const& node : map.query( 307 | ufo::pred::Leaf(depth) && ufo::pred::SeenFree() && ufo::pred::HitsMin(1) && 308 | ufo::pred::Label(0) && std::move(p) && 309 | ufo::pred::Satisfies([&seen](auto n) { return seen.insert(n).second; }))) { 310 | queue.emplace_back(map.center(node), max_distance); 311 | map.setLabel(node, l, false); 312 | } 313 | } 314 | 315 | if (seen.size() < min_points) { 316 | for (auto e : seen) { 317 | if (l == map.label(e)) { 318 | map.setLabel(e, -1, false); 319 | } 320 | } 321 | } 322 | 323 | l += seen.size() >= min_points; 324 | 325 | map.propagateModified(); // FIXME: Should this be here? 326 | } 327 | } 328 | 329 | int main(int argc, char* argv[]) 330 | { 331 | if (1 >= argc) { 332 | std::cout << "[ERROR] Please running by: " << argv[0] << " [pcd_folder] [optional: config_file_path]"; 333 | return 0; 334 | } 335 | 336 | std::filesystem::path path(argv[1]); 337 | std::string config_file_path; 338 | if (argc > 2) 339 | config_file_path = argv[2]; 340 | else 341 | config_file_path = std::string(argv[1]) + "/dufomap.toml"; 342 | 343 | auto config = readConfig(std::filesystem::path(config_file_path)); 344 | std::cout << "[LOG] Step 1: Successfully read configuration from: " << config_file_path < map( 346 | config.map.resolution, config.map.levels); 347 | map.reserve(100'000'000); 348 | 349 | std::vector pcds; 350 | for (const auto& entry : std::filesystem::directory_iterator(path / "pcd")) { 351 | if (!entry.is_regular_file()) { 352 | continue; 353 | } 354 | std::size_t i = std::stoul(entry.path().stem()); 355 | if (config.dataset.first <= i && config.dataset.last >= i) { 356 | pcds.push_back(entry.path().filename()); 357 | } 358 | } 359 | std::ranges::sort(pcds); 360 | // std::cout << config << std::endl; 361 | pcds.resize(std::min(pcds.size(), config.dataset.num)); 362 | 363 | ufo::Timing timing; 364 | timing.start("Total"); 365 | 366 | ufo::PointCloudColor cloud_acc; 367 | 368 | std::cout << "[LOG] Step 2: Starting Processing data from: " << path << '\n'; 369 | indicators::show_console_cursor(false); 370 | indicators::BlockProgressBar bar{ 371 | indicators::option::BarWidth{50}, 372 | indicators::option::Start{"["}, 373 | indicators::option::End{"]"}, 374 | indicators::option::PrefixText{"[LOG] Running dufomap "}, 375 | indicators::option::ForegroundColor{indicators::Color::white}, 376 | indicators::option::ShowElapsedTime{true}, 377 | indicators::option::ShowRemainingTime{true}, 378 | indicators::option::FontStyles{std::vector{indicators::FontStyle::bold}} 379 | }; 380 | 381 | for (std::size_t i{}; std::string filename : pcds) { 382 | bar.set_progress(100 * i / pcds.size()); 383 | ++i; 384 | timing.setTag("Total " + std::to_string(i) + " of " + std::to_string(pcds.size()) + 385 | " (" + std::to_string(100 * i / pcds.size()) + "%)"); 386 | 387 | ufo::PointCloudColor cloud; 388 | ufo::Pose6f viewpoint; 389 | timing[1].start("Read"); 390 | ufo::readPointCloudPCD(path / "pcd" / filename, cloud, viewpoint); 391 | timing[1].stop(); 392 | 393 | cloud_acc.insert(std::end(cloud_acc), std::cbegin(cloud), std::cend(cloud)); 394 | 395 | ufo::insertPointCloud(map, cloud, viewpoint.translation, config.integration, 396 | config.propagate); 397 | 398 | if (config.printing.verbose) { 399 | timing[2] = config.integration.timing; 400 | timing.print(true, true, 2, 4); 401 | } 402 | } 403 | indicators::show_console_cursor(true); 404 | std::cout << "\033[0m\n[LOG] Step 3: Finished Processing data. Start saving map... " << std::endl; 405 | // bar.is_completed(); 406 | if (!config.propagate) { 407 | timing[3].start("Propagate"); 408 | map.propagateModified(); 409 | timing[3].stop(); 410 | } 411 | 412 | timing[4].start("Cluster"); 413 | if (config.clustering.cluster) { 414 | cluster(map, config.clustering); 415 | } 416 | timing[4].stop(); 417 | 418 | timing[5].start("Query"); 419 | ufo::PointCloudColor cloud_static; 420 | 421 | for (auto& p : cloud_acc) { 422 | if (!map.seenFree(p)) 423 | cloud_static.push_back(p); 424 | } 425 | 426 | timing[5].stop(); 427 | 428 | timing[6].start("write"); 429 | ufo::writePointCloudPCD(path / (config.output.filename + ".pcd"), cloud_static); 430 | timing[6].stop(); 431 | timing.stop(); 432 | 433 | timing[2] = config.integration.timing; 434 | timing.print(true, true, 2, 4); 435 | std::cout << "[LOG]: Finished! ^v^.. Clean output map with " << cloud_static.size() << " points save in " << path / (config.output.filename + ".pcd") << '\n'; 436 | } -------------------------------------------------------------------------------- /src/indicators.hpp: -------------------------------------------------------------------------------- 1 | // header from: https://github.com/p-ranav/indicators 2 | #ifndef INDICATORS_COLOR 3 | #define INDICATORS_COLOR 4 | 5 | namespace indicators { 6 | enum class Color { grey, red, green, yellow, blue, magenta, cyan, white, unspecified }; 7 | } 8 | 9 | #endif 10 | 11 | 12 | 13 | #ifndef INDICATORS_FONT_STYLE 14 | #define INDICATORS_FONT_STYLE 15 | 16 | namespace indicators { 17 | enum class FontStyle { bold, dark, italic, underline, blink, reverse, concealed, crossed }; 18 | } 19 | 20 | #endif 21 | 22 | 23 | 24 | #ifndef INDICATORS_PROGRESS_TYPE 25 | #define INDICATORS_PROGRESS_TYPE 26 | 27 | namespace indicators { 28 | enum class ProgressType { incremental, decremental }; 29 | } 30 | 31 | #endif 32 | 33 | //! 34 | //! termcolor 35 | //! ~~~~~~~~~ 36 | //! 37 | //! termcolor is a header-only c++ library for printing colored messages 38 | //! to the terminal. Written just for fun with a help of the Force. 39 | //! 40 | //! :copyright: (c) 2013 by Ihor Kalnytskyi 41 | //! :license: BSD, see LICENSE for details 42 | //! 43 | 44 | #ifndef TERMCOLOR_HPP_ 45 | #define TERMCOLOR_HPP_ 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | // Detect target's platform and set some macros in order to wrap platform 52 | // specific code this library depends on. 53 | #if defined(_WIN32) || defined(_WIN64) 54 | # define TERMCOLOR_TARGET_WINDOWS 55 | #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) 56 | # define TERMCOLOR_TARGET_POSIX 57 | #endif 58 | 59 | // If implementation has not been explicitly set, try to choose one based on 60 | // target platform. 61 | #if !defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) && !defined(TERMCOLOR_USE_WINDOWS_API) && !defined(TERMCOLOR_USE_NOOP) 62 | # if defined(TERMCOLOR_TARGET_POSIX) 63 | # define TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES 64 | # define TERMCOLOR_AUTODETECTED_IMPLEMENTATION 65 | # elif defined(TERMCOLOR_TARGET_WINDOWS) 66 | # define TERMCOLOR_USE_WINDOWS_API 67 | # define TERMCOLOR_AUTODETECTED_IMPLEMENTATION 68 | # endif 69 | #endif 70 | 71 | // These headers provide isatty()/fileno() functions, which are used for 72 | // testing whether a standard stream refers to the terminal. 73 | #if defined(TERMCOLOR_TARGET_POSIX) 74 | # include 75 | #elif defined(TERMCOLOR_TARGET_WINDOWS) 76 | #if defined(_MSC_VER) 77 | #if !defined(NOMINMAX) 78 | #define NOMINMAX 79 | #endif 80 | #endif 81 | # include 82 | # include 83 | #endif 84 | 85 | 86 | namespace termcolor 87 | { 88 | // Forward declaration of the `_internal` namespace. 89 | // All comments are below. 90 | namespace _internal 91 | { 92 | inline int colorize_index(); 93 | inline FILE* get_standard_stream(const std::ostream& stream); 94 | inline bool is_colorized(std::ostream& stream); 95 | inline bool is_atty(const std::ostream& stream); 96 | 97 | #if defined(TERMCOLOR_TARGET_WINDOWS) 98 | inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1); 99 | #endif 100 | } 101 | 102 | inline 103 | std::ostream& colorize(std::ostream& stream) 104 | { 105 | stream.iword(_internal::colorize_index()) = 1L; 106 | return stream; 107 | } 108 | 109 | inline 110 | std::ostream& nocolorize(std::ostream& stream) 111 | { 112 | stream.iword(_internal::colorize_index()) = 0L; 113 | return stream; 114 | } 115 | 116 | inline 117 | std::ostream& reset(std::ostream& stream) 118 | { 119 | if (_internal::is_colorized(stream)) 120 | { 121 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 122 | stream << "\033[00m"; 123 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 124 | _internal::win_change_attributes(stream, -1, -1); 125 | #endif 126 | } 127 | return stream; 128 | } 129 | 130 | inline 131 | std::ostream& bold(std::ostream& stream) 132 | { 133 | if (_internal::is_colorized(stream)) 134 | { 135 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 136 | stream << "\033[1m"; 137 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 138 | #endif 139 | } 140 | return stream; 141 | } 142 | 143 | inline 144 | std::ostream& dark(std::ostream& stream) 145 | { 146 | if (_internal::is_colorized(stream)) 147 | { 148 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 149 | stream << "\033[2m"; 150 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 151 | #endif 152 | } 153 | return stream; 154 | } 155 | 156 | inline 157 | std::ostream& italic(std::ostream& stream) 158 | { 159 | if (_internal::is_colorized(stream)) 160 | { 161 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 162 | stream << "\033[3m"; 163 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 164 | #endif 165 | } 166 | return stream; 167 | } 168 | 169 | inline 170 | std::ostream& underline(std::ostream& stream) 171 | { 172 | if (_internal::is_colorized(stream)) 173 | { 174 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 175 | stream << "\033[4m"; 176 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 177 | _internal::win_change_attributes(stream, -1, COMMON_LVB_UNDERSCORE); 178 | #endif 179 | } 180 | return stream; 181 | } 182 | 183 | inline 184 | std::ostream& blink(std::ostream& stream) 185 | { 186 | if (_internal::is_colorized(stream)) 187 | { 188 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 189 | stream << "\033[5m"; 190 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 191 | #endif 192 | } 193 | return stream; 194 | } 195 | 196 | inline 197 | std::ostream& reverse(std::ostream& stream) 198 | { 199 | if (_internal::is_colorized(stream)) 200 | { 201 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 202 | stream << "\033[7m"; 203 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 204 | #endif 205 | } 206 | return stream; 207 | } 208 | 209 | inline 210 | std::ostream& concealed(std::ostream& stream) 211 | { 212 | if (_internal::is_colorized(stream)) 213 | { 214 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 215 | stream << "\033[8m"; 216 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 217 | #endif 218 | } 219 | return stream; 220 | } 221 | 222 | inline 223 | std::ostream& crossed(std::ostream& stream) 224 | { 225 | if (_internal::is_colorized(stream)) 226 | { 227 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 228 | stream << "\033[9m"; 229 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 230 | #endif 231 | } 232 | return stream; 233 | } 234 | 235 | template inline 236 | std::ostream& color(std::ostream& stream) 237 | { 238 | if (_internal::is_colorized(stream)) 239 | { 240 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 241 | char command[12]; 242 | std::snprintf(command, sizeof(command), "\033[38;5;%dm", code); 243 | stream << command; 244 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 245 | #endif 246 | } 247 | return stream; 248 | } 249 | 250 | template inline 251 | std::ostream& on_color(std::ostream& stream) 252 | { 253 | if (_internal::is_colorized(stream)) 254 | { 255 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 256 | char command[12]; 257 | std::snprintf(command, sizeof(command), "\033[48;5;%dm", code); 258 | stream << command; 259 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 260 | #endif 261 | } 262 | return stream; 263 | } 264 | 265 | template inline 266 | std::ostream& color(std::ostream& stream) 267 | { 268 | if (_internal::is_colorized(stream)) 269 | { 270 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 271 | char command[20]; 272 | std::snprintf(command, sizeof(command), "\033[38;2;%d;%d;%dm", r, g, b); 273 | stream << command; 274 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 275 | #endif 276 | } 277 | return stream; 278 | } 279 | 280 | template inline 281 | std::ostream& on_color(std::ostream& stream) 282 | { 283 | if (_internal::is_colorized(stream)) 284 | { 285 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 286 | char command[20]; 287 | std::snprintf(command, sizeof(command), "\033[48;2;%d;%d;%dm", r, g, b); 288 | stream << command; 289 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 290 | #endif 291 | } 292 | return stream; 293 | } 294 | 295 | inline 296 | std::ostream& grey(std::ostream& stream) 297 | { 298 | if (_internal::is_colorized(stream)) 299 | { 300 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 301 | stream << "\033[30m"; 302 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 303 | _internal::win_change_attributes(stream, 304 | 0 // grey (black) 305 | ); 306 | #endif 307 | } 308 | return stream; 309 | } 310 | 311 | inline 312 | std::ostream& red(std::ostream& stream) 313 | { 314 | if (_internal::is_colorized(stream)) 315 | { 316 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 317 | stream << "\033[31m"; 318 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 319 | _internal::win_change_attributes(stream, 320 | FOREGROUND_RED 321 | ); 322 | #endif 323 | } 324 | return stream; 325 | } 326 | 327 | inline 328 | std::ostream& green(std::ostream& stream) 329 | { 330 | if (_internal::is_colorized(stream)) 331 | { 332 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 333 | stream << "\033[32m"; 334 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 335 | _internal::win_change_attributes(stream, 336 | FOREGROUND_GREEN 337 | ); 338 | #endif 339 | } 340 | return stream; 341 | } 342 | 343 | inline 344 | std::ostream& yellow(std::ostream& stream) 345 | { 346 | if (_internal::is_colorized(stream)) 347 | { 348 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 349 | stream << "\033[33m"; 350 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 351 | _internal::win_change_attributes(stream, 352 | FOREGROUND_GREEN | FOREGROUND_RED 353 | ); 354 | #endif 355 | } 356 | return stream; 357 | } 358 | 359 | inline 360 | std::ostream& blue(std::ostream& stream) 361 | { 362 | if (_internal::is_colorized(stream)) 363 | { 364 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 365 | stream << "\033[34m"; 366 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 367 | _internal::win_change_attributes(stream, 368 | FOREGROUND_BLUE 369 | ); 370 | #endif 371 | } 372 | return stream; 373 | } 374 | 375 | inline 376 | std::ostream& magenta(std::ostream& stream) 377 | { 378 | if (_internal::is_colorized(stream)) 379 | { 380 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 381 | stream << "\033[35m"; 382 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 383 | _internal::win_change_attributes(stream, 384 | FOREGROUND_BLUE | FOREGROUND_RED 385 | ); 386 | #endif 387 | } 388 | return stream; 389 | } 390 | 391 | inline 392 | std::ostream& cyan(std::ostream& stream) 393 | { 394 | if (_internal::is_colorized(stream)) 395 | { 396 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 397 | stream << "\033[36m"; 398 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 399 | _internal::win_change_attributes(stream, 400 | FOREGROUND_BLUE | FOREGROUND_GREEN 401 | ); 402 | #endif 403 | } 404 | return stream; 405 | } 406 | 407 | inline 408 | std::ostream& white(std::ostream& stream) 409 | { 410 | if (_internal::is_colorized(stream)) 411 | { 412 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 413 | stream << "\033[37m"; 414 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 415 | _internal::win_change_attributes(stream, 416 | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED 417 | ); 418 | #endif 419 | } 420 | return stream; 421 | } 422 | 423 | 424 | inline 425 | std::ostream& bright_grey(std::ostream& stream) 426 | { 427 | if (_internal::is_colorized(stream)) 428 | { 429 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 430 | stream << "\033[90m"; 431 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 432 | _internal::win_change_attributes(stream, 433 | 0 | FOREGROUND_INTENSITY // grey (black) 434 | ); 435 | #endif 436 | } 437 | return stream; 438 | } 439 | 440 | inline 441 | std::ostream& bright_red(std::ostream& stream) 442 | { 443 | if (_internal::is_colorized(stream)) 444 | { 445 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 446 | stream << "\033[91m"; 447 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 448 | _internal::win_change_attributes(stream, 449 | FOREGROUND_RED | FOREGROUND_INTENSITY 450 | ); 451 | #endif 452 | } 453 | return stream; 454 | } 455 | 456 | inline 457 | std::ostream& bright_green(std::ostream& stream) 458 | { 459 | if (_internal::is_colorized(stream)) 460 | { 461 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 462 | stream << "\033[92m"; 463 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 464 | _internal::win_change_attributes(stream, 465 | FOREGROUND_GREEN | FOREGROUND_INTENSITY 466 | ); 467 | #endif 468 | } 469 | return stream; 470 | } 471 | 472 | inline 473 | std::ostream& bright_yellow(std::ostream& stream) 474 | { 475 | if (_internal::is_colorized(stream)) 476 | { 477 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 478 | stream << "\033[93m"; 479 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 480 | _internal::win_change_attributes(stream, 481 | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY 482 | ); 483 | #endif 484 | } 485 | return stream; 486 | } 487 | 488 | inline 489 | std::ostream& bright_blue(std::ostream& stream) 490 | { 491 | if (_internal::is_colorized(stream)) 492 | { 493 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 494 | stream << "\033[94m"; 495 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 496 | _internal::win_change_attributes(stream, 497 | FOREGROUND_BLUE | FOREGROUND_INTENSITY 498 | ); 499 | #endif 500 | } 501 | return stream; 502 | } 503 | 504 | inline 505 | std::ostream& bright_magenta(std::ostream& stream) 506 | { 507 | if (_internal::is_colorized(stream)) 508 | { 509 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 510 | stream << "\033[95m"; 511 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 512 | _internal::win_change_attributes(stream, 513 | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY 514 | ); 515 | #endif 516 | } 517 | return stream; 518 | } 519 | 520 | inline 521 | std::ostream& bright_cyan(std::ostream& stream) 522 | { 523 | if (_internal::is_colorized(stream)) 524 | { 525 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 526 | stream << "\033[96m"; 527 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 528 | _internal::win_change_attributes(stream, 529 | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY 530 | ); 531 | #endif 532 | } 533 | return stream; 534 | } 535 | 536 | inline 537 | std::ostream& bright_white(std::ostream& stream) 538 | { 539 | if (_internal::is_colorized(stream)) 540 | { 541 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 542 | stream << "\033[97m"; 543 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 544 | _internal::win_change_attributes(stream, 545 | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY 546 | ); 547 | #endif 548 | } 549 | return stream; 550 | } 551 | 552 | 553 | inline 554 | std::ostream& on_grey(std::ostream& stream) 555 | { 556 | if (_internal::is_colorized(stream)) 557 | { 558 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 559 | stream << "\033[40m"; 560 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 561 | _internal::win_change_attributes(stream, -1, 562 | 0 // grey (black) 563 | ); 564 | #endif 565 | } 566 | return stream; 567 | } 568 | 569 | inline 570 | std::ostream& on_red(std::ostream& stream) 571 | { 572 | if (_internal::is_colorized(stream)) 573 | { 574 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 575 | stream << "\033[41m"; 576 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 577 | _internal::win_change_attributes(stream, -1, 578 | BACKGROUND_RED 579 | ); 580 | #endif 581 | } 582 | return stream; 583 | } 584 | 585 | inline 586 | std::ostream& on_green(std::ostream& stream) 587 | { 588 | if (_internal::is_colorized(stream)) 589 | { 590 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 591 | stream << "\033[42m"; 592 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 593 | _internal::win_change_attributes(stream, -1, 594 | BACKGROUND_GREEN 595 | ); 596 | #endif 597 | } 598 | return stream; 599 | } 600 | 601 | inline 602 | std::ostream& on_yellow(std::ostream& stream) 603 | { 604 | if (_internal::is_colorized(stream)) 605 | { 606 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 607 | stream << "\033[43m"; 608 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 609 | _internal::win_change_attributes(stream, -1, 610 | BACKGROUND_GREEN | BACKGROUND_RED 611 | ); 612 | #endif 613 | } 614 | return stream; 615 | } 616 | 617 | inline 618 | std::ostream& on_blue(std::ostream& stream) 619 | { 620 | if (_internal::is_colorized(stream)) 621 | { 622 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 623 | stream << "\033[44m"; 624 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 625 | _internal::win_change_attributes(stream, -1, 626 | BACKGROUND_BLUE 627 | ); 628 | #endif 629 | } 630 | return stream; 631 | } 632 | 633 | inline 634 | std::ostream& on_magenta(std::ostream& stream) 635 | { 636 | if (_internal::is_colorized(stream)) 637 | { 638 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 639 | stream << "\033[45m"; 640 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 641 | _internal::win_change_attributes(stream, -1, 642 | BACKGROUND_BLUE | BACKGROUND_RED 643 | ); 644 | #endif 645 | } 646 | return stream; 647 | } 648 | 649 | inline 650 | std::ostream& on_cyan(std::ostream& stream) 651 | { 652 | if (_internal::is_colorized(stream)) 653 | { 654 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 655 | stream << "\033[46m"; 656 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 657 | _internal::win_change_attributes(stream, -1, 658 | BACKGROUND_GREEN | BACKGROUND_BLUE 659 | ); 660 | #endif 661 | } 662 | return stream; 663 | } 664 | 665 | inline 666 | std::ostream& on_white(std::ostream& stream) 667 | { 668 | if (_internal::is_colorized(stream)) 669 | { 670 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 671 | stream << "\033[47m"; 672 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 673 | _internal::win_change_attributes(stream, -1, 674 | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED 675 | ); 676 | #endif 677 | } 678 | 679 | return stream; 680 | } 681 | 682 | 683 | inline 684 | std::ostream& on_bright_grey(std::ostream& stream) 685 | { 686 | if (_internal::is_colorized(stream)) 687 | { 688 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 689 | stream << "\033[100m"; 690 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 691 | _internal::win_change_attributes(stream, -1, 692 | 0 | BACKGROUND_INTENSITY // grey (black) 693 | ); 694 | #endif 695 | } 696 | return stream; 697 | } 698 | 699 | inline 700 | std::ostream& on_bright_red(std::ostream& stream) 701 | { 702 | if (_internal::is_colorized(stream)) 703 | { 704 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 705 | stream << "\033[101m"; 706 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 707 | _internal::win_change_attributes(stream, -1, 708 | BACKGROUND_RED | BACKGROUND_INTENSITY 709 | ); 710 | #endif 711 | } 712 | return stream; 713 | } 714 | 715 | inline 716 | std::ostream& on_bright_green(std::ostream& stream) 717 | { 718 | if (_internal::is_colorized(stream)) 719 | { 720 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 721 | stream << "\033[102m"; 722 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 723 | _internal::win_change_attributes(stream, -1, 724 | BACKGROUND_GREEN | BACKGROUND_INTENSITY 725 | ); 726 | #endif 727 | } 728 | return stream; 729 | } 730 | 731 | inline 732 | std::ostream& on_bright_yellow(std::ostream& stream) 733 | { 734 | if (_internal::is_colorized(stream)) 735 | { 736 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 737 | stream << "\033[103m"; 738 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 739 | _internal::win_change_attributes(stream, -1, 740 | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY 741 | ); 742 | #endif 743 | } 744 | return stream; 745 | } 746 | 747 | inline 748 | std::ostream& on_bright_blue(std::ostream& stream) 749 | { 750 | if (_internal::is_colorized(stream)) 751 | { 752 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 753 | stream << "\033[104m"; 754 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 755 | _internal::win_change_attributes(stream, -1, 756 | BACKGROUND_BLUE | BACKGROUND_INTENSITY 757 | ); 758 | #endif 759 | } 760 | return stream; 761 | } 762 | 763 | inline 764 | std::ostream& on_bright_magenta(std::ostream& stream) 765 | { 766 | if (_internal::is_colorized(stream)) 767 | { 768 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 769 | stream << "\033[105m"; 770 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 771 | _internal::win_change_attributes(stream, -1, 772 | BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY 773 | ); 774 | #endif 775 | } 776 | return stream; 777 | } 778 | 779 | inline 780 | std::ostream& on_bright_cyan(std::ostream& stream) 781 | { 782 | if (_internal::is_colorized(stream)) 783 | { 784 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 785 | stream << "\033[106m"; 786 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 787 | _internal::win_change_attributes(stream, -1, 788 | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY 789 | ); 790 | #endif 791 | } 792 | return stream; 793 | } 794 | 795 | inline 796 | std::ostream& on_bright_white(std::ostream& stream) 797 | { 798 | if (_internal::is_colorized(stream)) 799 | { 800 | #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) 801 | stream << "\033[107m"; 802 | #elif defined(TERMCOLOR_USE_WINDOWS_API) 803 | _internal::win_change_attributes(stream, -1, 804 | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY 805 | ); 806 | #endif 807 | } 808 | 809 | return stream; 810 | } 811 | 812 | 813 | 814 | //! Since C++ hasn't a way to hide something in the header from 815 | //! the outer access, I have to introduce this namespace which 816 | //! is used for internal purpose and should't be access from 817 | //! the user code. 818 | namespace _internal 819 | { 820 | // An index to be used to access a private storage of I/O streams. See 821 | // colorize / nocolorize I/O manipulators for details. Due to the fact 822 | // that static variables ain't shared between translation units, inline 823 | // function with local static variable is used to do the trick and share 824 | // the variable value between translation units. 825 | inline int colorize_index() 826 | { 827 | static int colorize_index = std::ios_base::xalloc(); 828 | return colorize_index; 829 | } 830 | 831 | //! Since C++ hasn't a true way to extract stream handler 832 | //! from the a given `std::ostream` object, I have to write 833 | //! this kind of hack. 834 | inline 835 | FILE* get_standard_stream(const std::ostream& stream) 836 | { 837 | if (&stream == &std::cout) 838 | return stdout; 839 | else if ((&stream == &std::cerr) || (&stream == &std::clog)) 840 | return stderr; 841 | 842 | return nullptr; 843 | } 844 | 845 | // Say whether a given stream should be colorized or not. It's always 846 | // true for ATTY streams and may be true for streams marked with 847 | // colorize flag. 848 | inline 849 | bool is_colorized(std::ostream& stream) 850 | { 851 | return is_atty(stream) || static_cast(stream.iword(colorize_index())); 852 | } 853 | 854 | //! Test whether a given `std::ostream` object refers to 855 | //! a terminal. 856 | inline 857 | bool is_atty(const std::ostream& stream) 858 | { 859 | FILE* std_stream = get_standard_stream(stream); 860 | 861 | // Unfortunately, fileno() ends with segmentation fault 862 | // if invalid file descriptor is passed. So we need to 863 | // handle this case gracefully and assume it's not a tty 864 | // if standard stream is not detected, and 0 is returned. 865 | if (!std_stream) 866 | return false; 867 | 868 | #if defined(TERMCOLOR_TARGET_POSIX) 869 | return ::isatty(fileno(std_stream)); 870 | #elif defined(TERMCOLOR_TARGET_WINDOWS) 871 | return ::_isatty(_fileno(std_stream)); 872 | #else 873 | return false; 874 | #endif 875 | } 876 | 877 | #if defined(TERMCOLOR_TARGET_WINDOWS) 878 | //! Change Windows Terminal colors attribute. If some 879 | //! parameter is `-1` then attribute won't changed. 880 | inline void win_change_attributes(std::ostream& stream, int foreground, int background) 881 | { 882 | // yeah, i know.. it's ugly, it's windows. 883 | static WORD defaultAttributes = 0; 884 | 885 | // Windows doesn't have ANSI escape sequences and so we use special 886 | // API to change Terminal output color. That means we can't 887 | // manipulate colors by means of "std::stringstream" and hence 888 | // should do nothing in this case. 889 | if (!_internal::is_atty(stream)) 890 | return; 891 | 892 | // get terminal handle 893 | HANDLE hTerminal = INVALID_HANDLE_VALUE; 894 | if (&stream == &std::cout) 895 | hTerminal = GetStdHandle(STD_OUTPUT_HANDLE); 896 | else if (&stream == &std::cerr) 897 | hTerminal = GetStdHandle(STD_ERROR_HANDLE); 898 | 899 | // save default terminal attributes if it unsaved 900 | if (!defaultAttributes) 901 | { 902 | CONSOLE_SCREEN_BUFFER_INFO info; 903 | if (!GetConsoleScreenBufferInfo(hTerminal, &info)) 904 | return; 905 | defaultAttributes = info.wAttributes; 906 | } 907 | 908 | // restore all default settings 909 | if (foreground == -1 && background == -1) 910 | { 911 | SetConsoleTextAttribute(hTerminal, defaultAttributes); 912 | return; 913 | } 914 | 915 | // get current settings 916 | CONSOLE_SCREEN_BUFFER_INFO info; 917 | if (!GetConsoleScreenBufferInfo(hTerminal, &info)) 918 | return; 919 | 920 | if (foreground != -1) 921 | { 922 | info.wAttributes &= ~(info.wAttributes & 0x0F); 923 | info.wAttributes |= static_cast(foreground); 924 | } 925 | 926 | if (background != -1) 927 | { 928 | info.wAttributes &= ~(info.wAttributes & 0xF0); 929 | info.wAttributes |= static_cast(background); 930 | } 931 | 932 | SetConsoleTextAttribute(hTerminal, info.wAttributes); 933 | } 934 | #endif // TERMCOLOR_TARGET_WINDOWS 935 | 936 | } // namespace _internal 937 | 938 | } // namespace termcolor 939 | 940 | 941 | #undef TERMCOLOR_TARGET_POSIX 942 | #undef TERMCOLOR_TARGET_WINDOWS 943 | 944 | #if defined(TERMCOLOR_AUTODETECTED_IMPLEMENTATION) 945 | # undef TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES 946 | # undef TERMCOLOR_USE_WINDOWS_API 947 | #endif 948 | 949 | #endif // TERMCOLOR_HPP_ 950 | 951 | 952 | 953 | #ifndef INDICATORS_TERMINAL_SIZE 954 | #define INDICATORS_TERMINAL_SIZE 955 | #include 956 | 957 | 958 | #if defined(_WIN32) 959 | #include 960 | 961 | namespace indicators { 962 | 963 | static inline std::pair terminal_size() { 964 | CONSOLE_SCREEN_BUFFER_INFO csbi; 965 | int cols, rows; 966 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); 967 | cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 968 | rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 969 | return {static_cast(rows), static_cast(cols)}; 970 | } 971 | 972 | static inline size_t terminal_width() { return terminal_size().second; } 973 | 974 | } // namespace indicators 975 | 976 | #else 977 | 978 | #include //ioctl() and TIOCGWINSZ 979 | #include // for STDOUT_FILENO 980 | 981 | namespace indicators { 982 | 983 | static inline std::pair terminal_size() { 984 | struct winsize size{}; 985 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); 986 | return {static_cast(size.ws_row), static_cast(size.ws_col)}; 987 | } 988 | 989 | static inline size_t terminal_width() { return terminal_size().second; } 990 | 991 | } // namespace indicators 992 | 993 | #endif 994 | 995 | #endif 996 | 997 | 998 | /* 999 | Activity Indicators for Modern C++ 1000 | https://github.com/p-ranav/indicators 1001 | 1002 | Licensed under the MIT License . 1003 | SPDX-License-Identifier: MIT 1004 | Copyright (c) 2019 Dawid Pilarski . 1005 | 1006 | Permission is hereby granted, free of charge, to any person obtaining a copy 1007 | of this software and associated documentation files (the "Software"), to deal 1008 | in the Software without restriction, including without limitation the rights 1009 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1010 | copies of the Software, and to permit persons to whom the Software is 1011 | furnished to do so, subject to the following conditions: 1012 | 1013 | The above copyright notice and this permission notice shall be included in all 1014 | copies or substantial portions of the Software. 1015 | 1016 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1017 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1018 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1019 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1020 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1021 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1022 | SOFTWARE. 1023 | */ 1024 | #ifndef INDICATORS_SETTING 1025 | #define INDICATORS_SETTING 1026 | 1027 | #include 1028 | // #include 1029 | // #include 1030 | // #include 1031 | #include 1032 | #include 1033 | #include 1034 | #include 1035 | #include 1036 | 1037 | namespace indicators { 1038 | 1039 | namespace details { 1040 | 1041 | template struct if_else; 1042 | 1043 | template <> struct if_else { using type = std::true_type; }; 1044 | 1045 | template <> struct if_else { using type = std::false_type; }; 1046 | 1047 | template struct if_else_type; 1048 | 1049 | template struct if_else_type { 1050 | using type = True; 1051 | }; 1052 | 1053 | template struct if_else_type { 1054 | using type = False; 1055 | }; 1056 | 1057 | template struct conjuction; 1058 | 1059 | template <> struct conjuction<> : std::true_type {}; 1060 | 1061 | template 1062 | struct conjuction 1063 | : if_else_type>::type {}; 1064 | 1065 | template struct disjunction; 1066 | 1067 | template <> struct disjunction<> : std::false_type {}; 1068 | 1069 | template 1070 | struct disjunction 1071 | : if_else_type>::type {}; 1072 | 1073 | enum class ProgressBarOption { 1074 | bar_width = 0, 1075 | prefix_text, 1076 | postfix_text, 1077 | start, 1078 | end, 1079 | fill, 1080 | lead, 1081 | remainder, 1082 | max_postfix_text_len, 1083 | completed, 1084 | show_percentage, 1085 | show_elapsed_time, 1086 | show_remaining_time, 1087 | saved_start_time, 1088 | foreground_color, 1089 | spinner_show, 1090 | spinner_states, 1091 | font_styles, 1092 | hide_bar_when_complete, 1093 | min_progress, 1094 | max_progress, 1095 | progress_type, 1096 | stream 1097 | }; 1098 | 1099 | template struct Setting { 1100 | template ::value>::type> 1102 | explicit Setting(Args &&... args) : value(std::forward(args)...) {} 1103 | Setting(const Setting &) = default; 1104 | Setting(Setting &&) = default; 1105 | 1106 | static constexpr auto id = Id; 1107 | using type = T; 1108 | 1109 | T value{}; 1110 | }; 1111 | 1112 | template struct is_setting : std::false_type {}; 1113 | 1114 | template struct is_setting> : std::true_type {}; 1115 | 1116 | template 1117 | struct are_settings : if_else...>::value>::type {}; 1118 | 1119 | template <> struct are_settings<> : std::true_type {}; 1120 | 1121 | template struct is_setting_from_tuple; 1122 | 1123 | template struct is_setting_from_tuple> : std::true_type {}; 1124 | 1125 | template 1126 | struct is_setting_from_tuple> 1127 | : if_else...>::value>::type {}; 1128 | 1129 | template 1130 | struct are_settings_from_tuple 1131 | : if_else...>::value>::type {}; 1132 | 1133 | template struct always_true { static constexpr auto value = true; }; 1134 | 1135 | template Default &&get_impl(Default &&def) { 1136 | return std::forward(def); 1137 | } 1138 | 1139 | template 1140 | auto get_impl(Default && /*def*/, T &&first, Args &&... /*tail*/) -> 1141 | typename std::enable_if<(std::decay::type::id == Id), 1142 | decltype(std::forward(first))>::type { 1143 | return std::forward(first); 1144 | } 1145 | 1146 | template 1147 | auto get_impl(Default &&def, T && /*first*/, Args &&... tail) -> 1148 | typename std::enable_if<(std::decay::type::id != Id), 1149 | decltype(get_impl(std::forward(def), 1150 | std::forward(tail)...))>::type { 1151 | return get_impl(std::forward(def), std::forward(tail)...); 1152 | } 1153 | 1154 | template ::value, void>::type> 1156 | auto get(Default &&def, Args &&... args) 1157 | -> decltype(details::get_impl(std::forward(def), std::forward(args)...)) { 1158 | return details::get_impl(std::forward(def), std::forward(args)...); 1159 | } 1160 | 1161 | template using StringSetting = Setting; 1162 | 1163 | template using IntegerSetting = Setting; 1164 | 1165 | template using BooleanSetting = Setting; 1166 | 1167 | template struct option_idx; 1168 | 1169 | template 1170 | struct option_idx, counter> 1171 | : if_else_type<(Id == T::id), std::integral_constant, 1172 | option_idx, counter + 1>>::type {}; 1173 | 1174 | template struct option_idx, counter> { 1175 | static_assert(always_true<(ProgressBarOption)Id>::value, "No such option was found"); 1176 | }; 1177 | 1178 | template 1179 | auto get_value(Settings &&settings) 1180 | -> decltype((std::get::type>::value>( 1181 | std::declval()))) { 1182 | return std::get::type>::value>( 1183 | std::forward(settings)); 1184 | } 1185 | 1186 | } // namespace details 1187 | 1188 | namespace option { 1189 | using BarWidth = details::IntegerSetting; 1190 | using PrefixText = details::StringSetting; 1191 | using PostfixText = details::StringSetting; 1192 | using Start = details::StringSetting; 1193 | using End = details::StringSetting; 1194 | using Fill = details::StringSetting; 1195 | using Lead = details::StringSetting; 1196 | using Remainder = details::StringSetting; 1197 | using MaxPostfixTextLen = details::IntegerSetting; 1198 | using Completed = details::BooleanSetting; 1199 | using ShowPercentage = details::BooleanSetting; 1200 | using ShowElapsedTime = details::BooleanSetting; 1201 | using ShowRemainingTime = details::BooleanSetting; 1202 | using SavedStartTime = details::BooleanSetting; 1203 | using ForegroundColor = details::Setting; 1204 | using ShowSpinner = details::BooleanSetting; 1205 | using SpinnerStates = 1206 | details::Setting, details::ProgressBarOption::spinner_states>; 1207 | using HideBarWhenComplete = 1208 | details::BooleanSetting; 1209 | using FontStyles = 1210 | details::Setting, details::ProgressBarOption::font_styles>; 1211 | using MinProgress = details::IntegerSetting; 1212 | using MaxProgress = details::IntegerSetting; 1213 | using ProgressType = details::Setting; 1214 | using Stream = details::Setting; 1215 | } // namespace option 1216 | } // namespace indicators 1217 | 1218 | #endif 1219 | 1220 | 1221 | #ifndef INDICATORS_CURSOR_CONTROL 1222 | #define INDICATORS_CURSOR_CONTROL 1223 | 1224 | #if defined(_MSC_VER) 1225 | #if !defined(NOMINMAX) 1226 | #define NOMINMAX 1227 | #endif 1228 | #include 1229 | #include 1230 | #else 1231 | #include 1232 | #endif 1233 | 1234 | namespace indicators { 1235 | 1236 | #if defined(_MSC_VER) 1237 | 1238 | static inline void show_console_cursor(bool const show) { 1239 | HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 1240 | 1241 | CONSOLE_CURSOR_INFO cursorInfo; 1242 | 1243 | GetConsoleCursorInfo(out, &cursorInfo); 1244 | cursorInfo.bVisible = show; // set the cursor visibility 1245 | SetConsoleCursorInfo(out, &cursorInfo); 1246 | } 1247 | 1248 | static inline void erase_line() { 1249 | auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 1250 | if (!hStdout) 1251 | return; 1252 | 1253 | CONSOLE_SCREEN_BUFFER_INFO csbiInfo; 1254 | GetConsoleScreenBufferInfo(hStdout, &csbiInfo); 1255 | 1256 | COORD cursor; 1257 | 1258 | cursor.X = 0; 1259 | cursor.Y = csbiInfo.dwCursorPosition.Y; 1260 | 1261 | DWORD count = 0; 1262 | 1263 | FillConsoleOutputCharacterA(hStdout, ' ', csbiInfo.dwSize.X, cursor, &count); 1264 | 1265 | FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes, csbiInfo.dwSize.X, 1266 | cursor, &count); 1267 | 1268 | SetConsoleCursorPosition(hStdout, cursor); 1269 | } 1270 | 1271 | #else 1272 | 1273 | static inline void show_console_cursor(bool const show) { 1274 | std::fputs(show ? "\033[?25h" : "\033[?25l", stdout); 1275 | } 1276 | 1277 | static inline void erase_line() { 1278 | std::fputs("\r\033[K", stdout); 1279 | } 1280 | 1281 | #endif 1282 | 1283 | } // namespace indicators 1284 | 1285 | #endif 1286 | 1287 | 1288 | #ifndef INDICATORS_CURSOR_MOVEMENT 1289 | #define INDICATORS_CURSOR_MOVEMENT 1290 | 1291 | #if defined(_MSC_VER) 1292 | #if !defined(NOMINMAX) 1293 | #define NOMINMAX 1294 | #endif 1295 | #include 1296 | #include 1297 | #else 1298 | #include 1299 | #endif 1300 | 1301 | namespace indicators { 1302 | 1303 | #ifdef _MSC_VER 1304 | 1305 | static inline void move(int x, int y) { 1306 | auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 1307 | if (!hStdout) 1308 | return; 1309 | 1310 | CONSOLE_SCREEN_BUFFER_INFO csbiInfo; 1311 | GetConsoleScreenBufferInfo(hStdout, &csbiInfo); 1312 | 1313 | COORD cursor; 1314 | 1315 | cursor.X = csbiInfo.dwCursorPosition.X + x; 1316 | cursor.Y = csbiInfo.dwCursorPosition.Y + y; 1317 | SetConsoleCursorPosition(hStdout, cursor); 1318 | } 1319 | 1320 | static inline void move_up(int lines) { move(0, -lines); } 1321 | static inline void move_down(int lines) { move(0, -lines); } 1322 | static inline void move_right(int cols) { move(cols, 0); } 1323 | static inline void move_left(int cols) { move(-cols, 0); } 1324 | 1325 | #else 1326 | 1327 | static inline void move_up(int lines) { std::cout << "\033[" << lines << "A"; } 1328 | static inline void move_down(int lines) { std::cout << "\033[" << lines << "B"; } 1329 | static inline void move_right(int cols) { std::cout << "\033[" << cols << "C"; } 1330 | static inline void move_left(int cols) { std::cout << "\033[" << cols << "D"; } 1331 | 1332 | #endif 1333 | 1334 | } // namespace indicators 1335 | 1336 | #endif 1337 | 1338 | 1339 | #ifndef INDICATORS_STREAM_HELPER 1340 | #define INDICATORS_STREAM_HELPER 1341 | 1342 | // #include 1343 | #ifndef INDICATORS_DISPLAY_WIDTH 1344 | #define INDICATORS_DISPLAY_WIDTH 1345 | 1346 | #include 1347 | #include 1348 | #include 1349 | #include 1350 | #include 1351 | 1352 | namespace unicode { 1353 | 1354 | namespace details { 1355 | 1356 | /* 1357 | * This is an implementation of wcwidth() and wcswidth() (defined in 1358 | * IEEE Std 1002.1-2001) for Unicode. 1359 | * 1360 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html 1361 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html 1362 | * 1363 | * In fixed-width output devices, Latin characters all occupy a single 1364 | * "cell" position of equal width, whereas ideographic CJK characters 1365 | * occupy two such cells. Interoperability between terminal-line 1366 | * applications and (teletype-style) character terminals using the 1367 | * UTF-8 encoding requires agreement on which character should advance 1368 | * the cursor by how many cell positions. No established formal 1369 | * standards exist at present on which Unicode character shall occupy 1370 | * how many cell positions on character terminals. These routines are 1371 | * a first attempt of defining such behavior based on simple rules 1372 | * applied to data provided by the Unicode Consortium. 1373 | * 1374 | * For some graphical characters, the Unicode standard explicitly 1375 | * defines a character-cell width via the definition of the East Asian 1376 | * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. 1377 | * In all these cases, there is no ambiguity about which width a 1378 | * terminal shall use. For characters in the East Asian Ambiguous (A) 1379 | * class, the width choice depends purely on a preference of backward 1380 | * compatibility with either historic CJK or Western practice. 1381 | * Choosing single-width for these characters is easy to justify as 1382 | * the appropriate long-term solution, as the CJK practice of 1383 | * displaying these characters as double-width comes from historic 1384 | * implementation simplicity (8-bit encoded characters were displayed 1385 | * single-width and 16-bit ones double-width, even for Greek, 1386 | * Cyrillic, etc.) and not any typographic considerations. 1387 | * 1388 | * Much less clear is the choice of width for the Not East Asian 1389 | * (Neutral) class. Existing practice does not dictate a width for any 1390 | * of these characters. It would nevertheless make sense 1391 | * typographically to allocate two character cells to characters such 1392 | * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be 1393 | * represented adequately with a single-width glyph. The following 1394 | * routines at present merely assign a single-cell width to all 1395 | * neutral characters, in the interest of simplicity. This is not 1396 | * entirely satisfactory and should be reconsidered before 1397 | * establishing a formal standard in this area. At the moment, the 1398 | * decision which Not East Asian (Neutral) characters should be 1399 | * represented by double-width glyphs cannot yet be answered by 1400 | * applying a simple rule from the Unicode database content. Setting 1401 | * up a proper standard for the behavior of UTF-8 character terminals 1402 | * will require a careful analysis not only of each Unicode character, 1403 | * but also of each presentation form, something the author of these 1404 | * routines has avoided to do so far. 1405 | * 1406 | * http://www.unicode.org/unicode/reports/tr11/ 1407 | * 1408 | * Markus Kuhn -- 2007-05-26 (Unicode 5.0) 1409 | * 1410 | * Permission to use, copy, modify, and distribute this software 1411 | * for any purpose and without fee is hereby granted. The author 1412 | * disclaims all warranties with regard to this software. 1413 | * 1414 | * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c 1415 | */ 1416 | 1417 | struct interval { 1418 | int first; 1419 | int last; 1420 | }; 1421 | 1422 | /* auxiliary function for binary search in interval table */ 1423 | static inline int bisearch(wchar_t ucs, const struct interval *table, int max) { 1424 | int min = 0; 1425 | int mid; 1426 | 1427 | if (ucs < table[0].first || ucs > table[max].last) 1428 | return 0; 1429 | while (max >= min) { 1430 | mid = (min + max) / 2; 1431 | if (ucs > table[mid].last) 1432 | min = mid + 1; 1433 | else if (ucs < table[mid].first) 1434 | max = mid - 1; 1435 | else 1436 | return 1; 1437 | } 1438 | 1439 | return 0; 1440 | } 1441 | 1442 | /* The following two functions define the column width of an ISO 10646 1443 | * character as follows: 1444 | * 1445 | * - The null character (U+0000) has a column width of 0. 1446 | * 1447 | * - Other C0/C1 control characters and DEL will lead to a return 1448 | * value of -1. 1449 | * 1450 | * - Non-spacing and enclosing combining characters (general 1451 | * category code Mn or Me in the Unicode database) have a 1452 | * column width of 0. 1453 | * 1454 | * - SOFT HYPHEN (U+00AD) has a column width of 1. 1455 | * 1456 | * - Other format characters (general category code Cf in the Unicode 1457 | * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. 1458 | * 1459 | * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) 1460 | * have a column width of 0. 1461 | * 1462 | * - Spacing characters in the East Asian Wide (W) or East Asian 1463 | * Full-width (F) category as defined in Unicode Technical 1464 | * Report #11 have a column width of 2. 1465 | * 1466 | * - All remaining characters (including all printable 1467 | * ISO 8859-1 and WGL4 characters, Unicode control characters, 1468 | * etc.) have a column width of 1. 1469 | * 1470 | * This implementation assumes that wchar_t characters are encoded 1471 | * in ISO 10646. 1472 | */ 1473 | 1474 | static inline int mk_wcwidth(wchar_t ucs) { 1475 | /* sorted list of non-overlapping intervals of non-spacing characters */ 1476 | /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ 1477 | static const struct interval combining[] = { 1478 | {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, 1479 | {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, 1480 | {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, 1481 | {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, 1482 | {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, 1483 | {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, 1484 | {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, 1485 | {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, 1486 | {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, 1487 | {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, 1488 | {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, 1489 | {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, 1490 | {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, 1491 | {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, 1492 | {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, 1493 | {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, 1494 | {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, 1495 | {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, 1496 | {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, 1497 | {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, 1498 | {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, 1499 | {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, 1500 | {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, 1501 | {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, 1502 | {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, 1503 | {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, 1504 | {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, 1505 | {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, 1506 | {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, 1507 | {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, 1508 | {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, 1509 | {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, 1510 | {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, 1511 | {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, 1512 | {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, 1513 | {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, 1514 | {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, 1515 | {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, 1516 | {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, 1517 | {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, 1518 | {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, 1519 | {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, 1520 | {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, 1521 | {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, 1522 | {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, 1523 | {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, 1524 | {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, 1525 | {0xE0100, 0xE01EF}}; 1526 | 1527 | /* test for 8-bit control characters */ 1528 | if (ucs == 0) 1529 | return 0; 1530 | if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) 1531 | return -1; 1532 | 1533 | /* binary search in table of non-spacing characters */ 1534 | if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) 1535 | return 0; 1536 | 1537 | /* if we arrive here, ucs is not a combining or C0/C1 control character */ 1538 | 1539 | return 1 + 1540 | (ucs >= 0x1100 && 1541 | (ucs <= 0x115f || /* Hangul Jamo init. consonants */ 1542 | ucs == 0x2329 || ucs == 0x232a || 1543 | (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ 1544 | (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ 1545 | (ucs >= 0xf900 && 1546 | ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ 1547 | (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ 1548 | (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ 1549 | (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ 1550 | (ucs >= 0xffe0 && ucs <= 0xffe6) || 1551 | (ucs >= 0x20000 && ucs <= 0x2fffd) || 1552 | (ucs >= 0x30000 && ucs <= 0x3fffd))); 1553 | } 1554 | 1555 | static inline int mk_wcswidth(const wchar_t *pwcs, size_t n) { 1556 | int w, width = 0; 1557 | 1558 | for (; *pwcs && n-- > 0; pwcs++) 1559 | if ((w = mk_wcwidth(*pwcs)) < 0) 1560 | return -1; 1561 | else 1562 | width += w; 1563 | 1564 | return width; 1565 | } 1566 | 1567 | /* 1568 | * The following functions are the same as mk_wcwidth() and 1569 | * mk_wcswidth(), except that spacing characters in the East Asian 1570 | * Ambiguous (A) category as defined in Unicode Technical Report #11 1571 | * have a column width of 2. This variant might be useful for users of 1572 | * CJK legacy encodings who want to migrate to UCS without changing 1573 | * the traditional terminal character-width behaviour. It is not 1574 | * otherwise recommended for general use. 1575 | */ 1576 | static inline int mk_wcwidth_cjk(wchar_t ucs) { 1577 | /* sorted list of non-overlapping intervals of East Asian Ambiguous 1578 | * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ 1579 | static const struct interval ambiguous[] = { 1580 | {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, 1581 | {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, {0x00B0, 0x00B4}, 1582 | {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, 1583 | {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, 1584 | {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, 1585 | {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, 1586 | {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101}, 1587 | {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, 1588 | {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133}, 1589 | {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, 1590 | {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, 1591 | {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE}, 1592 | {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, 1593 | {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, 1594 | {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, 1595 | {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, 1596 | {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB}, 1597 | {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1}, 1598 | {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, {0x03C3, 0x03C9}, 1599 | {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451}, 1600 | {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, 1601 | {0x201C, 0x201D}, {0x2020, 0x2022}, {0x2024, 0x2027}, 1602 | {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035}, 1603 | {0x203B, 0x203B}, {0x203E, 0x203E}, {0x2074, 0x2074}, 1604 | {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC}, 1605 | {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109}, 1606 | {0x2113, 0x2113}, {0x2116, 0x2116}, {0x2121, 0x2122}, 1607 | {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154}, 1608 | {0x215B, 0x215E}, {0x2160, 0x216B}, {0x2170, 0x2179}, 1609 | {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, 1610 | {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200}, 1611 | {0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B}, 1612 | {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215}, 1613 | {0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223}, 1614 | {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E}, 1615 | {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248}, 1616 | {0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261}, 1617 | {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F}, 1618 | {0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295}, 1619 | {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, 1620 | {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B}, 1621 | {0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595}, 1622 | {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, 1623 | {0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, 1624 | {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, 1625 | {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606}, 1626 | {0x2609, 0x2609}, {0x260E, 0x260F}, {0x2614, 0x2615}, 1627 | {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640}, 1628 | {0x2642, 0x2642}, {0x2660, 0x2661}, {0x2663, 0x2665}, 1629 | {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F}, 1630 | {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF}, 1631 | {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}}; 1632 | 1633 | /* binary search in table of non-spacing characters */ 1634 | if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1)) 1635 | return 2; 1636 | 1637 | return mk_wcwidth(ucs); 1638 | } 1639 | 1640 | static inline int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) { 1641 | int w, width = 0; 1642 | 1643 | for (; *pwcs && n-- > 0; pwcs++) 1644 | if ((w = mk_wcwidth_cjk(*pwcs)) < 0) 1645 | return -1; 1646 | else 1647 | width += w; 1648 | 1649 | return width; 1650 | } 1651 | 1652 | // convert UTF-8 string to wstring 1653 | #ifdef _MSC_VER 1654 | static inline std::wstring utf8_decode(const std::string& s) { 1655 | auto r = setlocale(LC_ALL, ""); 1656 | std::string curLocale; 1657 | if (r) 1658 | curLocale = r; 1659 | const char* _Source = s.c_str(); 1660 | size_t _Dsize = std::strlen(_Source) + 1; 1661 | wchar_t* _Dest = new wchar_t[_Dsize]; 1662 | size_t _Osize; 1663 | mbstowcs_s(&_Osize, _Dest, _Dsize, _Source, _Dsize); 1664 | std::wstring result = _Dest; 1665 | delete[] _Dest; 1666 | setlocale(LC_ALL, curLocale.c_str()); 1667 | return result; 1668 | } 1669 | #else 1670 | static inline std::wstring utf8_decode(const std::string& s) { 1671 | auto r = setlocale(LC_ALL, ""); 1672 | std::string curLocale; 1673 | if (r) 1674 | curLocale = r; 1675 | const char* _Source = s.c_str(); 1676 | size_t _Dsize = mbstowcs(NULL, _Source, 0) + 1; 1677 | wchar_t* _Dest = new wchar_t[_Dsize]; 1678 | wmemset(_Dest, 0, _Dsize); 1679 | mbstowcs(_Dest, _Source, _Dsize); 1680 | std::wstring result = _Dest; 1681 | delete[] _Dest; 1682 | setlocale(LC_ALL, curLocale.c_str()); 1683 | return result; 1684 | } 1685 | #endif 1686 | 1687 | } // namespace details 1688 | 1689 | static inline int display_width(const std::string &input) { 1690 | using namespace unicode::details; 1691 | return mk_wcswidth(utf8_decode(input).c_str(), input.size()); 1692 | } 1693 | 1694 | static inline int display_width(const std::wstring &input) { 1695 | return details::mk_wcswidth(input.c_str(), input.size()); 1696 | } 1697 | 1698 | } // namespace unicode 1699 | 1700 | #endif 1701 | // #include 1702 | // #include 1703 | 1704 | #include 1705 | #include 1706 | #include 1707 | #include 1708 | #include 1709 | #include 1710 | 1711 | #include 1712 | #include 1713 | 1714 | namespace indicators { 1715 | namespace details { 1716 | 1717 | inline void set_stream_color(std::ostream &os, Color color) { 1718 | switch (color) { 1719 | case Color::grey: 1720 | os << termcolor::grey; 1721 | break; 1722 | case Color::red: 1723 | os << termcolor::red; 1724 | break; 1725 | case Color::green: 1726 | os << termcolor::green; 1727 | break; 1728 | case Color::yellow: 1729 | os << termcolor::yellow; 1730 | break; 1731 | case Color::blue: 1732 | os << termcolor::blue; 1733 | break; 1734 | case Color::magenta: 1735 | os << termcolor::magenta; 1736 | break; 1737 | case Color::cyan: 1738 | os << termcolor::cyan; 1739 | break; 1740 | case Color::white: 1741 | os << termcolor::white; 1742 | break; 1743 | default: 1744 | assert(false); 1745 | } 1746 | } 1747 | 1748 | inline void set_font_style(std::ostream &os, FontStyle style) { 1749 | switch (style) { 1750 | case FontStyle::bold: 1751 | os << termcolor::bold; 1752 | break; 1753 | case FontStyle::dark: 1754 | os << termcolor::dark; 1755 | break; 1756 | case FontStyle::italic: 1757 | os << termcolor::italic; 1758 | break; 1759 | case FontStyle::underline: 1760 | os << termcolor::underline; 1761 | break; 1762 | case FontStyle::blink: 1763 | os << termcolor::blink; 1764 | break; 1765 | case FontStyle::reverse: 1766 | os << termcolor::reverse; 1767 | break; 1768 | case FontStyle::concealed: 1769 | os << termcolor::concealed; 1770 | break; 1771 | case FontStyle::crossed: 1772 | os << termcolor::crossed; 1773 | break; 1774 | default: 1775 | break; 1776 | } 1777 | } 1778 | 1779 | inline std::ostream &write_duration(std::ostream &os, std::chrono::nanoseconds ns) { 1780 | using namespace std; 1781 | using namespace std::chrono; 1782 | using days = duration>; 1783 | char fill = os.fill(); 1784 | os.fill('0'); 1785 | auto d = duration_cast(ns); 1786 | ns -= d; 1787 | auto h = duration_cast(ns); 1788 | ns -= h; 1789 | auto m = duration_cast(ns); 1790 | ns -= m; 1791 | auto s = duration_cast(ns); 1792 | if (d.count() > 0) 1793 | os << setw(2) << d.count() << "d:"; 1794 | if (h.count() > 0) 1795 | os << setw(2) << h.count() << "h:"; 1796 | os << setw(2) << m.count() << "m:" << setw(2) << s.count() << 's'; 1797 | os.fill(fill); 1798 | return os; 1799 | } 1800 | 1801 | class BlockProgressScaleWriter { 1802 | public: 1803 | BlockProgressScaleWriter(std::ostream &os, size_t bar_width) : os(os), bar_width(bar_width) {} 1804 | 1805 | std::ostream &write(float progress) { 1806 | std::string fill_text{"█"}; 1807 | std::vector lead_characters{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"}; 1808 | auto value = (std::min)(1.0f, (std::max)(0.0f, progress / 100.0f)); 1809 | auto whole_width = std::floor(value * bar_width); 1810 | auto remainder_width = fmod((value * bar_width), 1.0f); 1811 | auto part_width = std::floor(remainder_width * lead_characters.size()); 1812 | std::string lead_text = lead_characters[size_t(part_width)]; 1813 | if ((bar_width - whole_width - 1) < 0) 1814 | lead_text = ""; 1815 | for (size_t i = 0; i < whole_width; ++i) 1816 | os << fill_text; 1817 | os << lead_text; 1818 | for (size_t i = 0; i < (bar_width - whole_width - 1); ++i) 1819 | os << " "; 1820 | return os; 1821 | } 1822 | 1823 | private: 1824 | std::ostream &os; 1825 | size_t bar_width = 0; 1826 | }; 1827 | 1828 | class ProgressScaleWriter { 1829 | public: 1830 | ProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill, 1831 | const std::string &lead, const std::string &remainder) 1832 | : os(os), bar_width(bar_width), fill(fill), lead(lead), remainder(remainder) {} 1833 | 1834 | std::ostream &write(float progress) { 1835 | auto pos = static_cast(progress * bar_width / 100.0); 1836 | for (size_t i = 0, current_display_width = 0; i < bar_width;) { 1837 | std::string next; 1838 | 1839 | if (i < pos) { 1840 | next = fill; 1841 | current_display_width = unicode::display_width(fill); 1842 | } else if (i == pos) { 1843 | next = lead; 1844 | current_display_width = unicode::display_width(lead); 1845 | } else { 1846 | next = remainder; 1847 | current_display_width = unicode::display_width(remainder); 1848 | } 1849 | 1850 | i += current_display_width; 1851 | 1852 | if (i > bar_width) { 1853 | // `next` is larger than the allowed bar width 1854 | // fill with empty space instead 1855 | os << std::string((bar_width - (i - current_display_width)), ' '); 1856 | break; 1857 | } 1858 | 1859 | os << next; 1860 | } 1861 | return os; 1862 | } 1863 | 1864 | private: 1865 | std::ostream &os; 1866 | size_t bar_width = 0; 1867 | std::string fill; 1868 | std::string lead; 1869 | std::string remainder; 1870 | }; 1871 | 1872 | class IndeterminateProgressScaleWriter { 1873 | public: 1874 | IndeterminateProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill, 1875 | const std::string &lead) 1876 | : os(os), bar_width(bar_width), fill(fill), lead(lead) {} 1877 | 1878 | std::ostream &write(size_t progress) { 1879 | for (size_t i = 0; i < bar_width;) { 1880 | std::string next; 1881 | size_t current_display_width = 0; 1882 | 1883 | if (i < progress) { 1884 | next = fill; 1885 | current_display_width = unicode::display_width(fill); 1886 | } else if (i == progress) { 1887 | next = lead; 1888 | current_display_width = unicode::display_width(lead); 1889 | } else { 1890 | next = fill; 1891 | current_display_width = unicode::display_width(fill); 1892 | } 1893 | 1894 | i += current_display_width; 1895 | 1896 | if (i > bar_width) { 1897 | // `next` is larger than the allowed bar width 1898 | // fill with empty space instead 1899 | os << std::string((bar_width - (i - current_display_width)), ' '); 1900 | break; 1901 | } 1902 | 1903 | os << next; 1904 | } 1905 | return os; 1906 | } 1907 | 1908 | private: 1909 | std::ostream &os; 1910 | size_t bar_width = 0; 1911 | std::string fill; 1912 | std::string lead; 1913 | }; 1914 | 1915 | } // namespace details 1916 | } // namespace indicators 1917 | 1918 | #endif 1919 | 1920 | 1921 | #ifndef INDICATORS_PROGRESS_BAR 1922 | #define INDICATORS_PROGRESS_BAR 1923 | 1924 | // #include 1925 | 1926 | #include 1927 | #include 1928 | #include 1929 | #include 1930 | // #include 1931 | // #include 1932 | // #include 1933 | #include 1934 | #include 1935 | #include 1936 | #include 1937 | #include 1938 | #include 1939 | #include 1940 | #include 1941 | #include 1942 | 1943 | namespace indicators { 1944 | 1945 | class ProgressBar { 1946 | using Settings = 1947 | std::tuple; 1955 | 1956 | public: 1957 | template ::type...>::value, 1961 | void *>::type = nullptr> 1962 | explicit ProgressBar(Args &&... args) 1963 | : settings_( 1964 | details::get( 1965 | option::BarWidth{100}, std::forward(args)...), 1966 | details::get( 1967 | option::PrefixText{}, std::forward(args)...), 1968 | details::get( 1969 | option::PostfixText{}, std::forward(args)...), 1970 | details::get( 1971 | option::Start{"["}, std::forward(args)...), 1972 | details::get( 1973 | option::End{"]"}, std::forward(args)...), 1974 | details::get( 1975 | option::Fill{"="}, std::forward(args)...), 1976 | details::get( 1977 | option::Lead{">"}, std::forward(args)...), 1978 | details::get( 1979 | option::Remainder{" "}, std::forward(args)...), 1980 | details::get( 1981 | option::MaxPostfixTextLen{0}, std::forward(args)...), 1982 | details::get( 1983 | option::Completed{false}, std::forward(args)...), 1984 | details::get( 1985 | option::ShowPercentage{false}, std::forward(args)...), 1986 | details::get( 1987 | option::ShowElapsedTime{false}, std::forward(args)...), 1988 | details::get( 1989 | option::ShowRemainingTime{false}, std::forward(args)...), 1990 | details::get( 1991 | option::SavedStartTime{false}, std::forward(args)...), 1992 | details::get( 1993 | option::ForegroundColor{Color::unspecified}, 1994 | std::forward(args)...), 1995 | details::get( 1996 | option::FontStyles{std::vector{}}, 1997 | std::forward(args)...), 1998 | details::get( 1999 | option::MinProgress{0}, std::forward(args)...), 2000 | details::get( 2001 | option::MaxProgress{100}, std::forward(args)...), 2002 | details::get( 2003 | option::ProgressType{ProgressType::incremental}, 2004 | std::forward(args)...), 2005 | details::get( 2006 | option::Stream{std::cout}, std::forward(args)...)) { 2007 | 2008 | // if progress is incremental, start from min_progress 2009 | // else start from max_progress 2010 | const auto type = get_value(); 2011 | if (type == ProgressType::incremental) 2012 | progress_ = get_value(); 2013 | else 2014 | progress_ = get_value(); 2015 | } 2016 | 2017 | template 2018 | void set_option(details::Setting &&setting) { 2019 | static_assert( 2020 | !std::is_same( 2021 | std::declval()))>::type>::value, 2022 | "Setting has wrong type!"); 2023 | std::lock_guard lock(mutex_); 2024 | get_value() = std::move(setting).value; 2025 | } 2026 | 2027 | template 2028 | void set_option(const details::Setting &setting) { 2029 | static_assert( 2030 | !std::is_same( 2031 | std::declval()))>::type>::value, 2032 | "Setting has wrong type!"); 2033 | std::lock_guard lock(mutex_); 2034 | get_value() = setting.value; 2035 | } 2036 | 2037 | void 2038 | set_option(const details::Setting< 2039 | std::string, details::ProgressBarOption::postfix_text> &setting) { 2040 | std::lock_guard lock(mutex_); 2041 | get_value() = setting.value; 2042 | if (setting.value.length() > 2043 | get_value()) { 2044 | get_value() = 2045 | setting.value.length(); 2046 | } 2047 | } 2048 | 2049 | void set_option( 2050 | details::Setting 2051 | &&setting) { 2052 | std::lock_guard lock(mutex_); 2053 | get_value() = 2054 | std::move(setting).value; 2055 | auto &new_value = get_value(); 2056 | if (new_value.length() > 2057 | get_value()) { 2058 | get_value() = 2059 | new_value.length(); 2060 | } 2061 | } 2062 | 2063 | void set_progress(size_t new_progress) { 2064 | { 2065 | std::lock_guard lock(mutex_); 2066 | progress_ = new_progress; 2067 | } 2068 | 2069 | save_start_time(); 2070 | print_progress(); 2071 | } 2072 | 2073 | void tick() { 2074 | { 2075 | std::lock_guard lock{mutex_}; 2076 | const auto type = get_value(); 2077 | if (type == ProgressType::incremental) 2078 | progress_ += 1; 2079 | else 2080 | progress_ -= 1; 2081 | } 2082 | save_start_time(); 2083 | print_progress(); 2084 | } 2085 | 2086 | size_t current() { 2087 | std::lock_guard lock{mutex_}; 2088 | return (std::min)( 2089 | progress_, 2090 | size_t(get_value())); 2091 | } 2092 | 2093 | bool is_completed() const { 2094 | return get_value(); 2095 | } 2096 | 2097 | void mark_as_completed() { 2098 | get_value() = true; 2099 | print_progress(); 2100 | } 2101 | 2102 | private: 2103 | template 2104 | auto get_value() 2105 | -> decltype((details::get_value(std::declval()).value)) { 2106 | return details::get_value(settings_).value; 2107 | } 2108 | 2109 | template 2110 | auto get_value() const -> decltype( 2111 | (details::get_value(std::declval()).value)) { 2112 | return details::get_value(settings_).value; 2113 | } 2114 | 2115 | size_t progress_{0}; 2116 | Settings settings_; 2117 | std::chrono::nanoseconds elapsed_; 2118 | std::chrono::time_point start_time_point_; 2119 | std::mutex mutex_; 2120 | 2121 | template friend class MultiProgress; 2122 | template friend class DynamicProgress; 2123 | std::atomic multi_progress_mode_{false}; 2124 | 2125 | void save_start_time() { 2126 | auto &show_elapsed_time = 2127 | get_value(); 2128 | auto &saved_start_time = 2129 | get_value(); 2130 | auto &show_remaining_time = 2131 | get_value(); 2132 | if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { 2133 | start_time_point_ = std::chrono::high_resolution_clock::now(); 2134 | saved_start_time = true; 2135 | } 2136 | } 2137 | 2138 | std::pair get_prefix_text() { 2139 | std::stringstream os; 2140 | os << get_value(); 2141 | const auto result = os.str(); 2142 | const auto result_size = unicode::display_width(result); 2143 | return {result, result_size}; 2144 | } 2145 | 2146 | std::pair get_postfix_text() { 2147 | std::stringstream os; 2148 | const auto max_progress = 2149 | get_value(); 2150 | 2151 | if (get_value()) { 2152 | os << " " 2153 | << (std::min)(static_cast(static_cast(progress_) / 2154 | max_progress * 100), 2155 | size_t(100)) 2156 | << "%"; 2157 | } 2158 | 2159 | auto &saved_start_time = 2160 | get_value(); 2161 | 2162 | if (get_value()) { 2163 | os << " ["; 2164 | if (saved_start_time) 2165 | details::write_duration(os, elapsed_); 2166 | else 2167 | os << "00:00s"; 2168 | } 2169 | 2170 | if (get_value()) { 2171 | if (get_value()) 2172 | os << "<"; 2173 | else 2174 | os << " ["; 2175 | 2176 | if (saved_start_time) { 2177 | auto eta = std::chrono::nanoseconds( 2178 | progress_ > 0 2179 | ? static_cast(std::ceil(float(elapsed_.count()) * 2180 | max_progress / progress_)) 2181 | : 0); 2182 | auto remaining = eta > elapsed_ ? (eta - elapsed_) : (elapsed_ - eta); 2183 | details::write_duration(os, remaining); 2184 | } else { 2185 | os << "00:00s"; 2186 | } 2187 | 2188 | os << "]"; 2189 | } else { 2190 | if (get_value()) 2191 | os << "]"; 2192 | } 2193 | 2194 | os << " " << get_value(); 2195 | 2196 | const auto result = os.str(); 2197 | const auto result_size = unicode::display_width(result); 2198 | return {result, result_size}; 2199 | } 2200 | 2201 | public: 2202 | void print_progress(bool from_multi_progress = false) { 2203 | std::lock_guard lock{mutex_}; 2204 | 2205 | auto &os = get_value(); 2206 | 2207 | const auto type = get_value(); 2208 | const auto min_progress = 2209 | get_value(); 2210 | const auto max_progress = 2211 | get_value(); 2212 | if (multi_progress_mode_ && !from_multi_progress) { 2213 | if ((type == ProgressType::incremental && progress_ >= max_progress) || 2214 | (type == ProgressType::decremental && progress_ <= min_progress)) { 2215 | get_value() = true; 2216 | } 2217 | return; 2218 | } 2219 | auto now = std::chrono::high_resolution_clock::now(); 2220 | if (!get_value()) 2221 | elapsed_ = std::chrono::duration_cast( 2222 | now - start_time_point_); 2223 | 2224 | if (get_value() != 2225 | Color::unspecified) 2226 | details::set_stream_color( 2227 | os, get_value()); 2228 | 2229 | for (auto &style : get_value()) 2230 | details::set_font_style(os, style); 2231 | 2232 | const auto prefix_pair = get_prefix_text(); 2233 | const auto prefix_text = prefix_pair.first; 2234 | const auto prefix_length = prefix_pair.second; 2235 | os << "\r" << prefix_text; 2236 | 2237 | os << get_value(); 2238 | 2239 | details::ProgressScaleWriter writer{ 2240 | os, get_value(), 2241 | get_value(), 2242 | get_value(), 2243 | get_value()}; 2244 | writer.write(double(progress_) / double(max_progress) * 100.0f); 2245 | 2246 | os << get_value(); 2247 | 2248 | const auto postfix_pair = get_postfix_text(); 2249 | const auto postfix_text = postfix_pair.first; 2250 | const auto postfix_length = postfix_pair.second; 2251 | os << postfix_text; 2252 | 2253 | // Get length of prefix text and postfix text 2254 | const auto start_length = get_value().size(); 2255 | const auto bar_width = get_value(); 2256 | const auto end_length = get_value().size(); 2257 | const auto terminal_width = terminal_size().second; 2258 | // prefix + bar_width + postfix should be <= terminal_width 2259 | const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); 2260 | if (prefix_length == -1 || postfix_length == -1) { 2261 | os << "\r"; 2262 | } else if (remaining > 0) { 2263 | os << std::string(remaining, ' ') << "\r"; 2264 | } else if (remaining < 0) { 2265 | // Do nothing. Maybe in the future truncate postfix with ... 2266 | } 2267 | os.flush(); 2268 | 2269 | if ((type == ProgressType::incremental && progress_ >= max_progress) || 2270 | (type == ProgressType::decremental && progress_ <= min_progress)) { 2271 | get_value() = true; 2272 | } 2273 | if (get_value() && 2274 | !from_multi_progress) // Don't std::endl if calling from MultiProgress 2275 | os << termcolor::reset << std::endl; 2276 | } 2277 | }; 2278 | 2279 | } // namespace indicators 2280 | 2281 | #endif 2282 | 2283 | 2284 | #ifndef INDICATORS_BLOCK_PROGRESS_BAR 2285 | #define INDICATORS_BLOCK_PROGRESS_BAR 2286 | 2287 | // #include 2288 | // #include 2289 | 2290 | #include 2291 | #include 2292 | #include 2293 | // #include 2294 | // #include 2295 | #include 2296 | #include 2297 | #include 2298 | #include 2299 | #include 2300 | #include 2301 | #include 2302 | #include 2303 | 2304 | namespace indicators { 2305 | 2306 | class BlockProgressBar { 2307 | using Settings = std::tuple; 2312 | 2313 | public: 2314 | template ::type...>::value, 2317 | void *>::type = nullptr> 2318 | explicit BlockProgressBar(Args &&... args) 2319 | : settings_(details::get( 2320 | option::ForegroundColor{Color::unspecified}, std::forward(args)...), 2321 | details::get(option::BarWidth{100}, 2322 | std::forward(args)...), 2323 | details::get(option::Start{"["}, 2324 | std::forward(args)...), 2325 | details::get(option::End{"]"}, 2326 | std::forward(args)...), 2327 | details::get( 2328 | option::PrefixText{""}, std::forward(args)...), 2329 | details::get( 2330 | option::PostfixText{""}, std::forward(args)...), 2331 | details::get( 2332 | option::ShowPercentage{true}, std::forward(args)...), 2333 | details::get( 2334 | option::ShowElapsedTime{false}, std::forward(args)...), 2335 | details::get( 2336 | option::ShowRemainingTime{false}, std::forward(args)...), 2337 | details::get(option::Completed{false}, 2338 | std::forward(args)...), 2339 | details::get( 2340 | option::SavedStartTime{false}, std::forward(args)...), 2341 | details::get( 2342 | option::MaxPostfixTextLen{0}, std::forward(args)...), 2343 | details::get( 2344 | option::FontStyles{std::vector{}}, std::forward(args)...), 2345 | details::get( 2346 | option::MaxProgress{100}, std::forward(args)...), 2347 | details::get(option::Stream{std::cout}, 2348 | std::forward(args)...)) {} 2349 | 2350 | template 2351 | void set_option(details::Setting &&setting) { 2352 | static_assert(!std::is_same( 2353 | std::declval()))>::type>::value, 2354 | "Setting has wrong type!"); 2355 | std::lock_guard lock(mutex_); 2356 | get_value() = std::move(setting).value; 2357 | } 2358 | 2359 | template 2360 | void set_option(const details::Setting &setting) { 2361 | static_assert(!std::is_same( 2362 | std::declval()))>::type>::value, 2363 | "Setting has wrong type!"); 2364 | std::lock_guard lock(mutex_); 2365 | get_value() = setting.value; 2366 | } 2367 | 2368 | void set_option( 2369 | const details::Setting &setting) { 2370 | std::lock_guard lock(mutex_); 2371 | get_value() = setting.value; 2372 | if (setting.value.length() > get_value()) { 2373 | get_value() = setting.value.length(); 2374 | } 2375 | } 2376 | 2377 | void 2378 | set_option(details::Setting &&setting) { 2379 | std::lock_guard lock(mutex_); 2380 | get_value() = std::move(setting).value; 2381 | auto &new_value = get_value(); 2382 | if (new_value.length() > get_value()) { 2383 | get_value() = new_value.length(); 2384 | } 2385 | } 2386 | 2387 | void set_progress(float value) { 2388 | { 2389 | std::lock_guard lock{mutex_}; 2390 | progress_ = value; 2391 | } 2392 | save_start_time(); 2393 | print_progress(); 2394 | } 2395 | 2396 | void tick() { 2397 | { 2398 | std::lock_guard lock{mutex_}; 2399 | progress_ += 1; 2400 | } 2401 | save_start_time(); 2402 | print_progress(); 2403 | } 2404 | 2405 | size_t current() { 2406 | std::lock_guard lock{mutex_}; 2407 | return (std::min)(static_cast(progress_), 2408 | size_t(get_value())); 2409 | } 2410 | 2411 | bool is_completed() const { return get_value(); } 2412 | 2413 | void mark_as_completed() { 2414 | get_value() = true; 2415 | print_progress(); 2416 | } 2417 | 2418 | private: 2419 | template 2420 | auto get_value() -> decltype((details::get_value(std::declval()).value)) { 2421 | return details::get_value(settings_).value; 2422 | } 2423 | 2424 | template 2425 | auto get_value() const 2426 | -> decltype((details::get_value(std::declval()).value)) { 2427 | return details::get_value(settings_).value; 2428 | } 2429 | 2430 | Settings settings_; 2431 | float progress_{0.0}; 2432 | std::chrono::time_point start_time_point_; 2433 | std::mutex mutex_; 2434 | 2435 | template friend class MultiProgress; 2436 | template friend class DynamicProgress; 2437 | std::atomic multi_progress_mode_{false}; 2438 | 2439 | void save_start_time() { 2440 | auto &show_elapsed_time = get_value(); 2441 | auto &saved_start_time = get_value(); 2442 | auto &show_remaining_time = get_value(); 2443 | if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { 2444 | start_time_point_ = std::chrono::high_resolution_clock::now(); 2445 | saved_start_time = true; 2446 | } 2447 | } 2448 | 2449 | std::pair get_prefix_text() { 2450 | std::stringstream os; 2451 | os << get_value(); 2452 | const auto result = os.str(); 2453 | const auto result_size = unicode::display_width(result); 2454 | return {result, result_size}; 2455 | } 2456 | 2457 | std::pair get_postfix_text() { 2458 | std::stringstream os; 2459 | const auto max_progress = get_value(); 2460 | auto now = std::chrono::high_resolution_clock::now(); 2461 | auto elapsed = std::chrono::duration_cast(now - start_time_point_); 2462 | 2463 | if (get_value()) { 2464 | os << " " << (std::min)(static_cast(progress_ / max_progress * 100.0), size_t(100)) 2465 | << "%"; 2466 | } 2467 | 2468 | auto &saved_start_time = get_value(); 2469 | 2470 | if (get_value()) { 2471 | os << " ["; 2472 | if (saved_start_time) 2473 | details::write_duration(os, elapsed); 2474 | else 2475 | os << "00:00s"; 2476 | } 2477 | 2478 | if (get_value()) { 2479 | if (get_value()) 2480 | os << "<"; 2481 | else 2482 | os << " ["; 2483 | 2484 | if (saved_start_time) { 2485 | auto eta = std::chrono::nanoseconds( 2486 | progress_ > 0 2487 | ? static_cast(std::ceil(float(elapsed.count()) * 2488 | max_progress / progress_)) 2489 | : 0); 2490 | auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); 2491 | details::write_duration(os, remaining); 2492 | } else { 2493 | os << "00:00s"; 2494 | } 2495 | 2496 | os << "]"; 2497 | } else { 2498 | if (get_value()) 2499 | os << "]"; 2500 | } 2501 | 2502 | os << " " << get_value(); 2503 | 2504 | const auto result = os.str(); 2505 | const auto result_size = unicode::display_width(result); 2506 | return {result, result_size}; 2507 | } 2508 | 2509 | public: 2510 | void print_progress(bool from_multi_progress = false) { 2511 | std::lock_guard lock{mutex_}; 2512 | 2513 | auto &os = get_value(); 2514 | 2515 | const auto max_progress = get_value(); 2516 | if (multi_progress_mode_ && !from_multi_progress) { 2517 | if (progress_ > max_progress) { 2518 | get_value() = true; 2519 | } 2520 | return; 2521 | } 2522 | 2523 | if (get_value() != Color::unspecified) 2524 | details::set_stream_color(os, get_value()); 2525 | 2526 | for (auto &style : get_value()) 2527 | details::set_font_style(os, style); 2528 | 2529 | const auto prefix_pair = get_prefix_text(); 2530 | const auto prefix_text = prefix_pair.first; 2531 | const auto prefix_length = prefix_pair.second; 2532 | os << "\r" << prefix_text; 2533 | 2534 | os << get_value(); 2535 | 2536 | details::BlockProgressScaleWriter writer{os, 2537 | get_value()}; 2538 | writer.write(progress_ / max_progress * 100); 2539 | 2540 | os << get_value(); 2541 | 2542 | const auto postfix_pair = get_postfix_text(); 2543 | const auto postfix_text = postfix_pair.first; 2544 | const auto postfix_length = postfix_pair.second; 2545 | os << postfix_text; 2546 | 2547 | // Get length of prefix text and postfix text 2548 | const auto start_length = get_value().size(); 2549 | const auto bar_width = get_value(); 2550 | const auto end_length = get_value().size(); 2551 | const auto terminal_width = terminal_size().second; 2552 | // prefix + bar_width + postfix should be <= terminal_width 2553 | const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); 2554 | if (prefix_length == -1 || postfix_length == -1) { 2555 | os << "\r"; 2556 | } else if (remaining > 0) { 2557 | os << std::string(remaining, ' ') << "\r"; 2558 | } else if (remaining < 0) { 2559 | // Do nothing. Maybe in the future truncate postfix with ... 2560 | } 2561 | os.flush(); 2562 | 2563 | if (progress_ > max_progress) { 2564 | get_value() = true; 2565 | } 2566 | if (get_value() && 2567 | !from_multi_progress) // Don't std::endl if calling from MultiProgress 2568 | os << termcolor::reset << std::endl; 2569 | } 2570 | }; 2571 | 2572 | } // namespace indicators 2573 | 2574 | #endif 2575 | 2576 | 2577 | #ifndef INDICATORS_INDETERMINATE_PROGRESS_BAR 2578 | #define INDICATORS_INDETERMINATE_PROGRESS_BAR 2579 | 2580 | // #include 2581 | 2582 | #include 2583 | #include 2584 | #include 2585 | #include 2586 | // #include 2587 | // #include 2588 | // #include 2589 | #include 2590 | #include 2591 | #include 2592 | #include 2593 | #include 2594 | #include 2595 | #include 2596 | #include 2597 | #include 2598 | 2599 | namespace indicators { 2600 | 2601 | class IndeterminateProgressBar { 2602 | using Settings = 2603 | std::tuple; 2606 | 2607 | enum class Direction { forward, backward }; 2608 | 2609 | Direction direction_{Direction::forward}; 2610 | 2611 | public: 2612 | template ::type...>::value, 2615 | void *>::type = nullptr> 2616 | explicit IndeterminateProgressBar(Args &&... args) 2617 | : settings_(details::get(option::BarWidth{100}, 2618 | std::forward(args)...), 2619 | details::get( 2620 | option::PrefixText{}, std::forward(args)...), 2621 | details::get( 2622 | option::PostfixText{}, std::forward(args)...), 2623 | details::get(option::Start{"["}, 2624 | std::forward(args)...), 2625 | details::get(option::End{"]"}, 2626 | std::forward(args)...), 2627 | details::get(option::Fill{"."}, 2628 | std::forward(args)...), 2629 | details::get(option::Lead{"<==>"}, 2630 | std::forward(args)...), 2631 | details::get( 2632 | option::MaxPostfixTextLen{0}, std::forward(args)...), 2633 | details::get(option::Completed{false}, 2634 | std::forward(args)...), 2635 | details::get( 2636 | option::ForegroundColor{Color::unspecified}, std::forward(args)...), 2637 | details::get( 2638 | option::FontStyles{std::vector{}}, std::forward(args)...), 2639 | details::get(option::Stream{std::cout}, 2640 | std::forward(args)...)) { 2641 | // starts with [<==>...........] 2642 | // progress_ = 0 2643 | 2644 | // ends with [...........<==>] 2645 | // ^^^^^^^^^^^^^^^^^ bar_width 2646 | // ^^^^^^^^^^^^ (bar_width - len(lead)) 2647 | // progress_ = bar_width - len(lead) 2648 | progress_ = 0; 2649 | max_progress_ = get_value() - 2650 | get_value().size() + 2651 | get_value().size() + 2652 | get_value().size(); 2653 | } 2654 | 2655 | template 2656 | void set_option(details::Setting &&setting) { 2657 | static_assert(!std::is_same( 2658 | std::declval()))>::type>::value, 2659 | "Setting has wrong type!"); 2660 | std::lock_guard lock(mutex_); 2661 | get_value() = std::move(setting).value; 2662 | } 2663 | 2664 | template 2665 | void set_option(const details::Setting &setting) { 2666 | static_assert(!std::is_same( 2667 | std::declval()))>::type>::value, 2668 | "Setting has wrong type!"); 2669 | std::lock_guard lock(mutex_); 2670 | get_value() = setting.value; 2671 | } 2672 | 2673 | void set_option( 2674 | const details::Setting &setting) { 2675 | std::lock_guard lock(mutex_); 2676 | get_value() = setting.value; 2677 | if (setting.value.length() > get_value()) { 2678 | get_value() = setting.value.length(); 2679 | } 2680 | } 2681 | 2682 | void 2683 | set_option(details::Setting &&setting) { 2684 | std::lock_guard lock(mutex_); 2685 | get_value() = std::move(setting).value; 2686 | auto &new_value = get_value(); 2687 | if (new_value.length() > get_value()) { 2688 | get_value() = new_value.length(); 2689 | } 2690 | } 2691 | 2692 | void tick() { 2693 | { 2694 | std::lock_guard lock{mutex_}; 2695 | if (get_value()) 2696 | return; 2697 | 2698 | progress_ += (direction_ == Direction::forward) ? 1 : -1; 2699 | if (direction_ == Direction::forward && progress_ == max_progress_) { 2700 | // time to go back 2701 | direction_ = Direction::backward; 2702 | } else if (direction_ == Direction::backward && progress_ == 0) { 2703 | direction_ = Direction::forward; 2704 | } 2705 | } 2706 | print_progress(); 2707 | } 2708 | 2709 | bool is_completed() { return get_value(); } 2710 | 2711 | void mark_as_completed() { 2712 | get_value() = true; 2713 | print_progress(); 2714 | } 2715 | 2716 | private: 2717 | template 2718 | auto get_value() -> decltype((details::get_value(std::declval()).value)) { 2719 | return details::get_value(settings_).value; 2720 | } 2721 | 2722 | template 2723 | auto get_value() const 2724 | -> decltype((details::get_value(std::declval()).value)) { 2725 | return details::get_value(settings_).value; 2726 | } 2727 | 2728 | size_t progress_{0}; 2729 | size_t max_progress_; 2730 | Settings settings_; 2731 | std::chrono::nanoseconds elapsed_; 2732 | std::mutex mutex_; 2733 | 2734 | template friend class MultiProgress; 2735 | template friend class DynamicProgress; 2736 | std::atomic multi_progress_mode_{false}; 2737 | 2738 | std::pair get_prefix_text() { 2739 | std::stringstream os; 2740 | os << get_value(); 2741 | const auto result = os.str(); 2742 | const auto result_size = unicode::display_width(result); 2743 | return {result, result_size}; 2744 | } 2745 | 2746 | std::pair get_postfix_text() { 2747 | std::stringstream os; 2748 | os << " " << get_value(); 2749 | 2750 | const auto result = os.str(); 2751 | const auto result_size = unicode::display_width(result); 2752 | return {result, result_size}; 2753 | } 2754 | 2755 | public: 2756 | void print_progress(bool from_multi_progress = false) { 2757 | std::lock_guard lock{mutex_}; 2758 | 2759 | auto &os = get_value(); 2760 | 2761 | if (multi_progress_mode_ && !from_multi_progress) { 2762 | return; 2763 | } 2764 | if (get_value() != Color::unspecified) 2765 | details::set_stream_color(os, get_value()); 2766 | 2767 | for (auto &style : get_value()) 2768 | details::set_font_style(os, style); 2769 | 2770 | const auto prefix_pair = get_prefix_text(); 2771 | const auto prefix_text = prefix_pair.first; 2772 | const auto prefix_length = prefix_pair.second; 2773 | os << "\r" << prefix_text; 2774 | 2775 | os << get_value(); 2776 | 2777 | details::IndeterminateProgressScaleWriter writer{ 2778 | os, get_value(), 2779 | get_value(), 2780 | get_value()}; 2781 | writer.write(progress_); 2782 | 2783 | os << get_value(); 2784 | 2785 | const auto postfix_pair = get_postfix_text(); 2786 | const auto postfix_text = postfix_pair.first; 2787 | const auto postfix_length = postfix_pair.second; 2788 | os << postfix_text; 2789 | 2790 | // Get length of prefix text and postfix text 2791 | const auto start_length = get_value().size(); 2792 | const auto bar_width = get_value(); 2793 | const auto end_length = get_value().size(); 2794 | const auto terminal_width = terminal_size().second; 2795 | // prefix + bar_width + postfix should be <= terminal_width 2796 | const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); 2797 | if (prefix_length == -1 || postfix_length == -1) { 2798 | os << "\r"; 2799 | } else if (remaining > 0) { 2800 | os << std::string(remaining, ' ') << "\r"; 2801 | } else if (remaining < 0) { 2802 | // Do nothing. Maybe in the future truncate postfix with ... 2803 | } 2804 | os.flush(); 2805 | 2806 | if (get_value() && 2807 | !from_multi_progress) // Don't std::endl if calling from MultiProgress 2808 | os << termcolor::reset << std::endl; 2809 | } 2810 | }; 2811 | 2812 | } // namespace indicators 2813 | 2814 | #endif 2815 | 2816 | 2817 | #ifndef INDICATORS_MULTI_PROGRESS 2818 | #define INDICATORS_MULTI_PROGRESS 2819 | #include 2820 | #include 2821 | #include 2822 | #include 2823 | #include 2824 | 2825 | // #include 2826 | // #include 2827 | // #include 2828 | 2829 | namespace indicators { 2830 | 2831 | template class MultiProgress { 2832 | public: 2833 | template ::type> 2835 | explicit MultiProgress(Indicators &... bars) { 2836 | bars_ = {bars...}; 2837 | for (auto &bar : bars_) { 2838 | bar.get().multi_progress_mode_ = true; 2839 | } 2840 | } 2841 | 2842 | template 2843 | typename std::enable_if<(index >= 0 && index < count), void>::type set_progress(size_t value) { 2844 | if (!bars_[index].get().is_completed()) 2845 | bars_[index].get().set_progress(value); 2846 | print_progress(); 2847 | } 2848 | 2849 | template 2850 | typename std::enable_if<(index >= 0 && index < count), void>::type set_progress(float value) { 2851 | if (!bars_[index].get().is_completed()) 2852 | bars_[index].get().set_progress(value); 2853 | print_progress(); 2854 | } 2855 | 2856 | template 2857 | typename std::enable_if<(index >= 0 && index < count), void>::type tick() { 2858 | if (!bars_[index].get().is_completed()) 2859 | bars_[index].get().tick(); 2860 | print_progress(); 2861 | } 2862 | 2863 | template 2864 | typename std::enable_if<(index >= 0 && index < count), bool>::type is_completed() const { 2865 | return bars_[index].get().is_completed(); 2866 | } 2867 | 2868 | private: 2869 | std::atomic started_{false}; 2870 | std::mutex mutex_; 2871 | std::vector> bars_; 2872 | 2873 | bool _all_completed() { 2874 | bool result{true}; 2875 | for (size_t i = 0; i < count; ++i) 2876 | result &= bars_[i].get().is_completed(); 2877 | return result; 2878 | } 2879 | 2880 | public: 2881 | void print_progress() { 2882 | std::lock_guard lock{mutex_}; 2883 | if (started_) 2884 | move_up(count); 2885 | for (auto &bar : bars_) { 2886 | bar.get().print_progress(true); 2887 | std::cout << "\n"; 2888 | } 2889 | std::cout << termcolor::reset; 2890 | if (!started_) 2891 | started_ = true; 2892 | } 2893 | }; 2894 | 2895 | } // namespace indicators 2896 | 2897 | #endif 2898 | 2899 | 2900 | #ifndef INDICATORS_DYNAMIC_PROGRESS 2901 | #define INDICATORS_DYNAMIC_PROGRESS 2902 | 2903 | #include 2904 | #include 2905 | // #include 2906 | // #include 2907 | // #include 2908 | // #include 2909 | // #include 2910 | #include 2911 | #include 2912 | #include 2913 | 2914 | namespace indicators { 2915 | 2916 | template class DynamicProgress { 2917 | using Settings = std::tuple; 2918 | 2919 | public: 2920 | template explicit DynamicProgress(Indicators &... bars) { 2921 | bars_ = {bars...}; 2922 | for (auto &bar : bars_) { 2923 | bar.get().multi_progress_mode_ = true; 2924 | ++total_count_; 2925 | ++incomplete_count_; 2926 | } 2927 | } 2928 | 2929 | Indicator &operator[](size_t index) { 2930 | print_progress(); 2931 | std::lock_guard lock{mutex_}; 2932 | return bars_[index].get(); 2933 | } 2934 | 2935 | size_t push_back(Indicator &bar) { 2936 | std::lock_guard lock{mutex_}; 2937 | bar.multi_progress_mode_ = true; 2938 | bars_.push_back(bar); 2939 | return bars_.size() - 1; 2940 | } 2941 | 2942 | template 2943 | void set_option(details::Setting &&setting) { 2944 | static_assert(!std::is_same( 2945 | std::declval()))>::type>::value, 2946 | "Setting has wrong type!"); 2947 | std::lock_guard lock(mutex_); 2948 | get_value() = std::move(setting).value; 2949 | } 2950 | 2951 | template 2952 | void set_option(const details::Setting &setting) { 2953 | static_assert(!std::is_same( 2954 | std::declval()))>::type>::value, 2955 | "Setting has wrong type!"); 2956 | std::lock_guard lock(mutex_); 2957 | get_value() = setting.value; 2958 | } 2959 | 2960 | private: 2961 | Settings settings_; 2962 | std::atomic started_{false}; 2963 | std::mutex mutex_; 2964 | std::vector> bars_; 2965 | std::atomic total_count_{0}; 2966 | std::atomic incomplete_count_{0}; 2967 | 2968 | template 2969 | auto get_value() -> decltype((details::get_value(std::declval()).value)) { 2970 | return details::get_value(settings_).value; 2971 | } 2972 | 2973 | template 2974 | auto get_value() const 2975 | -> decltype((details::get_value(std::declval()).value)) { 2976 | return details::get_value(settings_).value; 2977 | } 2978 | 2979 | public: 2980 | void print_progress() { 2981 | std::lock_guard lock{mutex_}; 2982 | auto &hide_bar_when_complete = get_value(); 2983 | if (hide_bar_when_complete) { 2984 | // Hide completed bars 2985 | if (started_) { 2986 | for (size_t i = 0; i < incomplete_count_; ++i) { 2987 | move_up(1); 2988 | erase_line(); 2989 | std::cout << std::flush; 2990 | } 2991 | } 2992 | incomplete_count_ = 0; 2993 | for (auto &bar : bars_) { 2994 | if (!bar.get().is_completed()) { 2995 | bar.get().print_progress(true); 2996 | std::cout << "\n"; 2997 | ++incomplete_count_; 2998 | } 2999 | } 3000 | if (!started_) 3001 | started_ = true; 3002 | } else { 3003 | // Don't hide any bars 3004 | if (started_) 3005 | move_up(static_cast(total_count_)); 3006 | for (auto &bar : bars_) { 3007 | bar.get().print_progress(true); 3008 | std::cout << "\n"; 3009 | } 3010 | if (!started_) 3011 | started_ = true; 3012 | } 3013 | total_count_ = bars_.size(); 3014 | std::cout << termcolor::reset; 3015 | } 3016 | }; 3017 | 3018 | } // namespace indicators 3019 | 3020 | #endif 3021 | 3022 | 3023 | #ifndef INDICATORS_PROGRESS_SPINNER 3024 | #define INDICATORS_PROGRESS_SPINNER 3025 | 3026 | // #include 3027 | 3028 | #include 3029 | #include 3030 | #include 3031 | #include 3032 | // #include 3033 | // #include 3034 | #include 3035 | #include 3036 | #include 3037 | #include 3038 | #include 3039 | #include 3040 | #include 3041 | 3042 | namespace indicators { 3043 | 3044 | class ProgressSpinner { 3045 | using Settings = 3046 | std::tuple; 3051 | 3052 | public: 3053 | template ::type...>::value, 3056 | void *>::type = nullptr> 3057 | explicit ProgressSpinner(Args &&... args) 3058 | : settings_( 3059 | details::get( 3060 | option::ForegroundColor{Color::unspecified}, std::forward(args)...), 3061 | details::get(option::PrefixText{}, 3062 | std::forward(args)...), 3063 | details::get(option::PostfixText{}, 3064 | std::forward(args)...), 3065 | details::get(option::ShowPercentage{true}, 3066 | std::forward(args)...), 3067 | details::get( 3068 | option::ShowElapsedTime{false}, std::forward(args)...), 3069 | details::get( 3070 | option::ShowRemainingTime{false}, std::forward(args)...), 3071 | details::get(option::ShowSpinner{true}, 3072 | std::forward(args)...), 3073 | details::get( 3074 | option::SavedStartTime{false}, std::forward(args)...), 3075 | details::get(option::Completed{false}, 3076 | std::forward(args)...), 3077 | details::get( 3078 | option::MaxPostfixTextLen{0}, std::forward(args)...), 3079 | details::get( 3080 | option::SpinnerStates{ 3081 | std::vector{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}}, 3082 | std::forward(args)...), 3083 | details::get( 3084 | option::FontStyles{std::vector{}}, std::forward(args)...), 3085 | details::get(option::MaxProgress{100}, 3086 | std::forward(args)...), 3087 | details::get(option::Stream{std::cout}, 3088 | std::forward(args)...)) {} 3089 | 3090 | template 3091 | void set_option(details::Setting &&setting) { 3092 | static_assert(!std::is_same( 3093 | std::declval()))>::type>::value, 3094 | "Setting has wrong type!"); 3095 | std::lock_guard lock(mutex_); 3096 | get_value() = std::move(setting).value; 3097 | } 3098 | 3099 | template 3100 | void set_option(const details::Setting &setting) { 3101 | static_assert(!std::is_same( 3102 | std::declval()))>::type>::value, 3103 | "Setting has wrong type!"); 3104 | std::lock_guard lock(mutex_); 3105 | get_value() = setting.value; 3106 | } 3107 | 3108 | void set_option( 3109 | const details::Setting &setting) { 3110 | std::lock_guard lock(mutex_); 3111 | get_value() = setting.value; 3112 | if (setting.value.length() > get_value()) { 3113 | get_value() = setting.value.length(); 3114 | } 3115 | } 3116 | 3117 | void 3118 | set_option(details::Setting &&setting) { 3119 | std::lock_guard lock(mutex_); 3120 | get_value() = std::move(setting).value; 3121 | auto &new_value = get_value(); 3122 | if (new_value.length() > get_value()) { 3123 | get_value() = new_value.length(); 3124 | } 3125 | } 3126 | 3127 | void set_progress(size_t value) { 3128 | { 3129 | std::lock_guard lock{mutex_}; 3130 | progress_ = value; 3131 | } 3132 | save_start_time(); 3133 | print_progress(); 3134 | } 3135 | 3136 | void tick() { 3137 | { 3138 | std::lock_guard lock{mutex_}; 3139 | progress_ += 1; 3140 | } 3141 | save_start_time(); 3142 | print_progress(); 3143 | } 3144 | 3145 | size_t current() { 3146 | std::lock_guard lock{mutex_}; 3147 | return (std::min)(progress_, size_t(get_value())); 3148 | } 3149 | 3150 | bool is_completed() const { return get_value(); } 3151 | 3152 | void mark_as_completed() { 3153 | get_value() = true; 3154 | print_progress(); 3155 | } 3156 | 3157 | private: 3158 | Settings settings_; 3159 | size_t progress_{0}; 3160 | size_t index_{0}; 3161 | std::chrono::time_point start_time_point_; 3162 | std::mutex mutex_; 3163 | 3164 | template 3165 | auto get_value() -> decltype((details::get_value(std::declval()).value)) { 3166 | return details::get_value(settings_).value; 3167 | } 3168 | 3169 | template 3170 | auto get_value() const 3171 | -> decltype((details::get_value(std::declval()).value)) { 3172 | return details::get_value(settings_).value; 3173 | } 3174 | 3175 | void save_start_time() { 3176 | auto &show_elapsed_time = get_value(); 3177 | auto &show_remaining_time = get_value(); 3178 | auto &saved_start_time = get_value(); 3179 | if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { 3180 | start_time_point_ = std::chrono::high_resolution_clock::now(); 3181 | saved_start_time = true; 3182 | } 3183 | } 3184 | 3185 | public: 3186 | void print_progress() { 3187 | std::lock_guard lock{mutex_}; 3188 | 3189 | auto &os = get_value(); 3190 | 3191 | const auto max_progress = get_value(); 3192 | auto now = std::chrono::high_resolution_clock::now(); 3193 | auto elapsed = std::chrono::duration_cast(now - start_time_point_); 3194 | 3195 | if (get_value() != Color::unspecified) 3196 | details::set_stream_color(os, get_value()); 3197 | 3198 | for (auto &style : get_value()) 3199 | details::set_font_style(os, style); 3200 | 3201 | os << get_value(); 3202 | if (get_value()) 3203 | os << get_value() 3204 | [index_ % get_value().size()]; 3205 | if (get_value()) { 3206 | os << " " << std::size_t(progress_ / double(max_progress) * 100) << "%"; 3207 | } 3208 | 3209 | if (get_value()) { 3210 | os << " ["; 3211 | details::write_duration(os, elapsed); 3212 | } 3213 | 3214 | if (get_value()) { 3215 | if (get_value()) 3216 | os << "<"; 3217 | else 3218 | os << " ["; 3219 | auto eta = std::chrono::nanoseconds( 3220 | progress_ > 0 3221 | ? static_cast(std::ceil(float(elapsed.count()) * 3222 | max_progress / progress_)) 3223 | : 0); 3224 | auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); 3225 | details::write_duration(os, remaining); 3226 | os << "]"; 3227 | } else { 3228 | if (get_value()) 3229 | os << "]"; 3230 | } 3231 | 3232 | if (get_value() == 0) 3233 | get_value() = 10; 3234 | os << " " << get_value() 3235 | << std::string(get_value(), ' ') << "\r"; 3236 | os.flush(); 3237 | index_ += 1; 3238 | if (progress_ > max_progress) { 3239 | get_value() = true; 3240 | } 3241 | if (get_value()) 3242 | os << termcolor::reset << std::endl; 3243 | } 3244 | }; 3245 | 3246 | } // namespace indicators 3247 | 3248 | #endif 3249 | --------------------------------------------------------------------------------