From 05b6e2d6e358f425d3f32208725f876b8e31d559 Mon Sep 17 00:00:00 2001 From: quincy-huhn98 Date: Tue, 17 Feb 2026 23:01:31 -0600 Subject: [PATCH 1/3] Updated READMEs ROM App --- ROM/README.md | 43 + ROM/examples/2gcheckerboard/README.md | 34 + ROM/examples/README.md | 31 + ROM/examples/checkerboard/README.md | 25 + .../checkerboard/run_rom_checkerboard.py | 2 +- ROM/examples/reed/README.md | 27 + ROM/examples/reed/base_reed.py | 1 - ROM/examples/reed/run_rom_reed.py | 2 +- ROM/python/README.md | 24 + ROM/src/README.md | 22 + ROM/src/rom.cc | 4 + ROM/src/rom/README.md | 22 + examples/python/plot_errors.py | 84 + examples/python/plotting.py | 116 + examples/python/utils.py | 125 + external/cxxopts/cxxopts.h | 2908 +++++++++++++++++ external/mpicpp-lite/impl/Communicator.h | 1141 +++++++ external/mpicpp-lite/impl/Datatype.h | 141 + external/mpicpp-lite/impl/Enums.h | 8 + external/mpicpp-lite/impl/Environment.h | 74 + external/mpicpp-lite/impl/Error.h | 74 + external/mpicpp-lite/impl/Group.h | 195 ++ external/mpicpp-lite/impl/Operation.h | 260 ++ external/mpicpp-lite/impl/Request.h | 35 + external/mpicpp-lite/impl/Status.h | 81 + external/mpicpp-lite/impl/Test.h | 75 + external/mpicpp-lite/impl/Wait.h | 64 + external/mpicpp-lite/mpicpp-lite.h | 15 + 28 files changed, 5630 insertions(+), 3 deletions(-) create mode 100644 ROM/README.md create mode 100644 ROM/examples/2gcheckerboard/README.md create mode 100644 ROM/examples/README.md create mode 100644 ROM/examples/checkerboard/README.md create mode 100644 ROM/examples/reed/README.md create mode 100644 ROM/python/README.md create mode 100644 ROM/src/README.md create mode 100644 ROM/src/rom/README.md create mode 100644 examples/python/plot_errors.py create mode 100644 examples/python/plotting.py create mode 100644 examples/python/utils.py create mode 100644 external/cxxopts/cxxopts.h create mode 100644 external/mpicpp-lite/impl/Communicator.h create mode 100644 external/mpicpp-lite/impl/Datatype.h create mode 100644 external/mpicpp-lite/impl/Enums.h create mode 100644 external/mpicpp-lite/impl/Environment.h create mode 100644 external/mpicpp-lite/impl/Error.h create mode 100644 external/mpicpp-lite/impl/Group.h create mode 100644 external/mpicpp-lite/impl/Operation.h create mode 100644 external/mpicpp-lite/impl/Request.h create mode 100644 external/mpicpp-lite/impl/Status.h create mode 100644 external/mpicpp-lite/impl/Test.h create mode 100644 external/mpicpp-lite/impl/Wait.h create mode 100644 external/mpicpp-lite/mpicpp-lite.h diff --git a/ROM/README.md b/ROM/README.md new file mode 100644 index 0000000..54fc309 --- /dev/null +++ b/ROM/README.md @@ -0,0 +1,43 @@ +Standalone ROM application built around OpenSn and libROM, with supporting Python tooling and example input decks. + +This repo is organized to: +- build a C++ application/binary that links against OpenSn and libROM, +- run transport problems and ROM workflows from Python drivers, +- keep reproducible example cases in a single place. + +## Repository layout + +- [src/](src/README.md) — C++ sources for the application (python bindings, app-specific code) +- [python/](python/README.md) — Python drivers/utilities (e.g., job management, ROM pipeline orchestration) +- [examples/](examples/README.md) — Example decks, runs, and reference cases + +# Quickstart + +## Dependencies + +This repository builds a standalone application that links against a libROM installation, an OpenSn installation and certain OpenSn dependencies. + +You must have the following available: + +- OpenSn +- libROM +- OpenSn dependencies (VTK, Caliper, MPI, Python with pybind11) + +--- + +## Build + +From the repository root: + +```bash +mkdir build +cd build +cmake .. \ + -DOpenSn_DIR= \ + -DlibROM_DIR= +make -j +``` +An example can be run from its respective folder with +```bash +python run_rom_*.py --exe=path/to/app/exe +``` \ No newline at end of file diff --git a/ROM/examples/2gcheckerboard/README.md b/ROM/examples/2gcheckerboard/README.md new file mode 100644 index 0000000..c7349c9 --- /dev/null +++ b/ROM/examples/2gcheckerboard/README.md @@ -0,0 +1,34 @@ +# 2gcheckerboard/ + +**Location:** [repo root](../../README.md) / [examples/](../README.md) / `2gcheckerboard/` + +Two-group checkerboard/lattice ROM example. + +This case extends the checkerboard configuration to a two-group problem +with separate absorber and scatterer material definitions. + +--- + +## Files + +- `base_2gcheckerboard.py` + Base two-group OpenSn deck. + +- `checkerboard_problem_2g.py` + Problem definition and ROM configuration. + +- `run_rom_2gcheckerboard.py` + Entry-point script for running the ROM workflow. + +- `absorber_base.txt` + Baseline absorber material definition. + +- `scatterer_base.txt` + Baseline scatterer material definition. + +--- + +## How to Run + +```bash +python run_rom_2gcheckerboard.py --exe=path/to/app/exe diff --git a/ROM/examples/README.md b/ROM/examples/README.md new file mode 100644 index 0000000..5e9cc3a --- /dev/null +++ b/ROM/examples/README.md @@ -0,0 +1,31 @@ +# examples/ + +**Location:** [repo root](../README.md) / `examples/` + +Example input decks and run scripts demonstrating how to use the application and ROM pipeline. + +Each example folder typically contains: +- a base OpenSn input deck, +- a problem definition helper, +- a `run_rom_*.py` entry script you execute, +- optional reference data (e.g., base cross sections). + +## Examples + +- [`reed/`](reed/README.md) + Reed 1D benchmark example. + +- [`checkerboard/`](checkerboard/README.md) + 2-D Checkerboard/lattice problem configuration. + +- [`2gcheckerboard/`](2gcheckerboard/README.md) + Two-group checkerboard variant with absorber/scatterer material files. + +## Running + +Each example folder includes a `run_rom_*.py` script. Start with the folder README for details. + +Execution pattern: + +```bash +python run_rom_.py --exe=path/to/app/exe diff --git a/ROM/examples/checkerboard/README.md b/ROM/examples/checkerboard/README.md new file mode 100644 index 0000000..2db3edd --- /dev/null +++ b/ROM/examples/checkerboard/README.md @@ -0,0 +1,25 @@ +# checkerboard/ + +**Location:** [repo root](../../README.md) / [examples/](../README.md) / `checkerboard/` + +Single-group checkerboard/lattice example and ROM driver. + +--- + +## Files + +- `base_checkerboard.py` + Base deck defining geometry, materials, and solver setup. + +- `checkerboard_problem.py` + Problem definition and ROM configuration. + +- `run_rom_checkerboard.py` + Entry-point script for running the ROM workflow. + +--- + +## How to Run + +```bash +python run_rom_checkerboard.py --exe=path/to/app/exe diff --git a/ROM/examples/checkerboard/run_rom_checkerboard.py b/ROM/examples/checkerboard/run_rom_checkerboard.py index 46bb3c8..0d18683 100644 --- a/ROM/examples/checkerboard/run_rom_checkerboard.py +++ b/ROM/examples/checkerboard/run_rom_checkerboard.py @@ -42,4 +42,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/ROM/examples/reed/README.md b/ROM/examples/reed/README.md new file mode 100644 index 0000000..af2da46 --- /dev/null +++ b/ROM/examples/reed/README.md @@ -0,0 +1,27 @@ +# reed/ + +**Location:** [repo root](../../README.md) / [examples/](../README.md) / `reed/` + +Reed example and ROM driver. + +--- + +## Files + +- `base_reed.py` + Base OpenSn deck for the Reed problem. + +- `reed_problem.py` + Problem definition and parameterization for the Reed example. + +- `run_rom_reed.py` + Entry-point script that runs the ROM workflow. + +--- + +## How to Run + +From this directory: + +```bash +python run_rom_reed.py --exe=path/to/app/exe diff --git a/ROM/examples/reed/base_reed.py b/ROM/examples/reed/base_reed.py index 6d4fa91..114f494 100644 --- a/ROM/examples/reed/base_reed.py +++ b/ROM/examples/reed/base_reed.py @@ -124,4 +124,3 @@ if phase == "offline": phys.WriteFluxMoments("output/fom") - diff --git a/ROM/examples/reed/run_rom_reed.py b/ROM/examples/reed/run_rom_reed.py index 7839ccd..34feb1f 100644 --- a/ROM/examples/reed/run_rom_reed.py +++ b/ROM/examples/reed/run_rom_reed.py @@ -42,4 +42,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/ROM/python/README.md b/ROM/python/README.md new file mode 100644 index 0000000..f1e70a9 --- /dev/null +++ b/ROM/python/README.md @@ -0,0 +1,24 @@ +# python/ + +**Location:** [repo root](../README.md) / `python/` + +Python drivers and utilities for running example problems and ROM workflows using the application implemented in [`src/`](../src/README.md). + +This folder is intended to: +- standardize launching (local vs batch/HPC), +- keep ROM pipeline orchestration in one place, +- centralize plotting and small utilities. + +## Key modules + +- [`rom_driver.py`](rom_driver.py) + Orchestrates the ROM pipeline (e.g., offline, merge, system, online phases). + +- [`job_manager.py`](job_manager.py) + Launch abstraction for running the executable (local execution vs MPI/Slurm-style workflows). + +- [`plotting.py`](plotting.py) + Plotting helpers for example runs and ROM result summaries. + +- [`utils.py`](utils.py) + Shared utilities (loading fluxes from h5, basic sampling capabilities, and updating OpenSn .xs files). \ No newline at end of file diff --git a/ROM/src/README.md b/ROM/src/README.md new file mode 100644 index 0000000..c0ccebe --- /dev/null +++ b/ROM/src/README.md @@ -0,0 +1,22 @@ +# src/ + +**Location:** [repo root](../README.md) / `src/` + +C++ sources for the application. This folder contains the main entry point, ROM implementation, and the Python-app bridge used to integrate with OpenSn problems. + +## Key files + +- [`main.cc`](main.cc) + Application entry point. + +- [`rom.cc`](rom.cc) + Top-level ROM plumbing/glue code. + +- [`rom_py_app.h`](rom_py_app.h), [`rom_py_app.cc`](rom_py_app.cc) + ROM “Python app” integration layer (app interface invoked from Python-side workflows). + +- [`py_wrappers.h`](py_wrappers.h) + C++ declarations for Python-facing wrappers (bindings/glue utilities). + +- [`rom/`](rom/README.md) + ROM core implementation (problem definition, solver, structs). \ No newline at end of file diff --git a/ROM/src/rom.cc b/ROM/src/rom.cc index 4d90902..ecbea6d 100644 --- a/ROM/src/rom.cc +++ b/ROM/src/rom.cc @@ -2,7 +2,11 @@ // SPDX-License-Identifier: MIT #include "py_wrappers.h" +<<<<<<< HEAD #include "rom/rom_problem.h" +======= +#include "rom/ROM_problem.h" +>>>>>>> dd951c6 (ROM App) #include "rom/steady_state_rom_solver.h" #include "modules/solver.h" #include diff --git a/ROM/src/rom/README.md b/ROM/src/rom/README.md new file mode 100644 index 0000000..28721a5 --- /dev/null +++ b/ROM/src/rom/README.md @@ -0,0 +1,22 @@ +# src/rom/ + +**Location:** [repo root](../../README.md) / [src/](../README.md) / `rom/` + +Core reduced-order modeling (ROM) implementation: problem definition, solver(s), and shared ROM data structures. + +## Contents + +### Problem definition + +- [`rom_problem.h`](rom_problem.h), [`rom_problem.cc`](rom_problem.cc) + Defines the ROM problem object and its responsibilities (assembly, operators, state, I/O hooks, etc.). + +### Solver + +- [`steady_state_rom_solver.h`](steady_state_rom_solver.h), [`steady_state_rom_solver.cc`](steady_state_rom_solver.cc) + Steady-state ROM solver implementation. + +### Shared structs / types + +- [`rom_structs.h`](rom_structs.h) + Shared ROM data containers and configuration structs. \ No newline at end of file diff --git a/examples/python/plot_errors.py b/examples/python/plot_errors.py new file mode 100644 index 0000000..471b1e3 --- /dev/null +++ b/examples/python/plot_errors.py @@ -0,0 +1,84 @@ +import numpy as np +import matplotlib.pyplot as plt + +problem = "../checkerboard" + +# List of sample sizes +sample_sizes = [100, 150, 200, 250, 300, 350, 400, 450, 500] + +# Collect error data +all_errors = [] +for size in sample_sizes: + filename = f"{problem}/results/errors_{size}.txt" + data = np.loadtxt(filename) + all_errors.append(data) + +# Make the boxplot +plt.figure(figsize=(8, 5)) +plt.boxplot(all_errors, positions=sample_sizes, widths=20, showfliers=False) + +plt.xlabel("Training Set Size") +plt.ylabel("$L_2$ Error") +plt.yscale("log") +plt.grid(True, linestyle="--", alpha=0.6) + +plt.savefig(f'{problem}/results/errors_set_size.jpg') +plt.close() + +# Collect speedup data +all_speedups = [] +for size in sample_sizes: + filename = f"{problem}/results/speedups_{size}.txt" + data = np.loadtxt(filename) + all_speedups.append(data) + +# Make the boxplot +plt.figure(figsize=(8, 5)) +plt.boxplot(all_speedups, positions=sample_sizes, widths=20, showfliers=False) + +plt.xlabel("Training Set Size") +plt.ylabel("Speedup") +plt.yscale("log") +plt.grid(True, linestyle="--", alpha=0.6) + +plt.savefig(f'{problem}/results/speedups_set_size.jpg') + +# List of ranks +ranks = [5, 10, 15, 20] + +# Collect error data +all_errors = [] +for rank in ranks: + filename = f"{problem}/results/errors_{rank}.txt" + data = np.loadtxt(filename) + all_errors.append(data) + +# Make the boxplot +plt.figure(figsize=(8, 5)) +plt.boxplot(all_errors, positions=ranks, widths=2, showfliers=False) + +plt.xlabel("Rank") +plt.ylabel("$L_2$ Error") +plt.yscale("log") +plt.grid(True, linestyle="--", alpha=0.6) + +plt.savefig(f'{problem}/results/errors_rank.jpg') +plt.close() + +# Collect speedup data +all_speedups = [] +for rank in ranks: + filename = f"{problem}/results/speedups_{rank}.txt" + data = np.loadtxt(filename) + all_speedups.append(data) + +# Make the boxplot +plt.figure(figsize=(8, 5)) +plt.boxplot(all_speedups, positions=ranks, widths=2, showfliers=False) + +plt.xlabel("Rank") +plt.ylabel("Speedup") +plt.yscale("log") +plt.grid(True, linestyle="--", alpha=0.6) + +plt.savefig(f'{problem}/results/speedups_rank.jpg') \ No newline at end of file diff --git a/examples/python/plotting.py b/examples/python/plotting.py new file mode 100644 index 0000000..5f703ab --- /dev/null +++ b/examples/python/plotting.py @@ -0,0 +1,116 @@ +import numpy as np +import matplotlib.pyplot as plt +from utils import * +import scipy.interpolate +from matplotlib.colors import LogNorm + + +def plot_2d_flux(file_pattern, ranks, moment=0, prefix="fom", grid_res=200, pid=0): + """Create smooth full-color plots (not scatter) for each energy group.""" + xs, ys, vals, G = load_2d_flux(file_pattern, ranks, moment=moment) + + for g in range(G): + # Create regular grid + xi = np.linspace(xs[g].min(), xs[g].max(), grid_res) + yi = np.linspace(ys[g].min(), ys[g].max(), grid_res) + X, Y = np.meshgrid(xi, yi) + + # Interpolate data onto grid + Z = scipy.interpolate.griddata((xs[g], ys[g]), vals[g], (X, Y), method="linear") + + vmin = max(np.nanmin(Z), 1e-10) + vmax = np.nanmax(Z) + norm = LogNorm(vmin=vmin, vmax=vmax) + + plt.figure(figsize=(6, 5)) + im = plt.imshow( + Z, + extent=[xi.min(), xi.max(), yi.min(), yi.max()], + origin="lower", + aspect="equal", + cmap="viridis", + norm=norm + ) + plt.title(f"{prefix.upper()} Group {g} (moment {moment})") + plt.xlabel("x") + plt.ylabel("y") + cbar = plt.colorbar(im) + cbar.set_label("Flux") + outpath = f"results/{prefix}_group_{g}_{pid}.png" + plt.tight_layout() + plt.savefig(outpath, dpi=200) + plt.close() + +def plot_2d_lineout(ranks, y_target=4.0, moment=0, grid_res=200, pid=0): + """Plot lineout at y_target of ROM and FOM.""" + xs, ys, vals, G = load_2d_flux("output/rom{}.h5", ranks, moment=moment) + xs_, ys_, vals_, G = load_2d_flux("output/fom{}.h5", ranks, moment=moment) + + for g in range(G): + # Create regular grid + xi = np.linspace(xs[g].min(), xs[g].max(), grid_res) + yi = np.linspace(ys[g].min(), ys[g].max(), grid_res) + X, Y = np.meshgrid(xi, yi) + + # Interpolate data onto grid + Z = scipy.interpolate.griddata((xs[g], ys[g]), vals[g], (X, Y), method="linear") + Z_ = scipy.interpolate.griddata((xs[g], ys[g]), vals_[g], (X, Y), method="linear") + + # Find the row index closest to y=4 + row_idx = np.argmin(np.abs(yi - y_target)) + + # Extract data along y = 4 + rom_line = Z[row_idx, :] + fom_line = Z_[row_idx, :] + + # Plot ROM vs FOM + plt.figure(figsize=(8,5)) + plt.plot(xi, rom_line, label='ROM', color='blue') + plt.plot(xi, fom_line, label='FOM', color='orange', linestyle='--') + plt.xlabel('X') + plt.ylabel('Scalar Value') + plt.title(f'ROM vs FOM at y={y_target}') + plt.legend() + plt.grid(True) + plt.tight_layout() + plt.savefig(f"results/line_y{y_target}_rom_fom_{pid}_{g}.jpg") + plt.close() + + error = np.linalg.norm(np.asarray(vals_)-np.asarray(vals))/np.linalg.norm(np.asarray(vals_)) + return error + + +def plot_sv(num_groups): + for i in range(num_groups): + S = np.loadtxt("data/singular_values_g{}.txt".format(i)) + plt.semilogy(S, 'o-') + plt.xlabel("Mode index") + plt.ylabel("Singular value") + plt.title("Singular value decay") + plt.grid(True) + plt.tight_layout() + plt.savefig("results/svd_decay_{}.jpg".format(i)) + plt.close() + + +def plot_1d_flux(fom_pattern, rom_pattern, ranks, moment=0, prefix="reed_ommi", pid=0): + """Compare FOM vs ROM 1-D flux.""" + fom_x, fom_vals, G = load_1d_flux(fom_pattern, ranks, moment=moment) + rom_x, rom_vals, G = load_1d_flux(rom_pattern, ranks, moment=moment) + + errors = [] + for g in range(G): + plt.figure(figsize=(6, 4)) + plt.plot(fom_x[g], fom_vals[g], "-", label="FOM") + plt.plot(rom_x[g], rom_vals[g], "--", label="ROM") + plt.xlabel("x") + plt.ylabel("Flux") + plt.grid() + plt.legend() + outpath = f"results/{prefix}_{pid}_g_{g}.png" + plt.tight_layout() + plt.savefig(outpath, dpi=200) + plt.close() + + error = np.linalg.norm(np.array(rom_vals) - np.array(fom_vals)) / np.linalg.norm(fom_vals) + return error \ No newline at end of file diff --git a/examples/python/utils.py b/examples/python/utils.py new file mode 100644 index 0000000..8a16c49 --- /dev/null +++ b/examples/python/utils.py @@ -0,0 +1,125 @@ +import numpy as np +import subprocess +import h5py + +def run_opensn(cmd): + args = cmd.split(" ") + print(args) + process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + output, errors = process.communicate() + print("Output:", output) + print("Errors:", errors) + + +def load_2d_flux(file_pattern, ranks, moment=0): + """Load (x, y, flux) grouped by energy group from HDF5 files.""" + with h5py.File(file_pattern.format(ranks[0]), "r") as f0: + num_groups = int(f0.attrs["num_groups"]) + num_moments = int(f0.attrs["num_moments"]) + + xs = [[] for _ in range(num_groups)] + ys = [[] for _ in range(num_groups)] + vals = [[] for _ in range(num_groups)] + + for r in ranks: + fp = file_pattern.format(r) + with h5py.File(fp, "r") as f: + x = f["mesh/nodes_x"][:] + y = f["mesh/nodes_y"][:] + values = f["values"][:] + num_nodes = x.size + + values = values.reshape(num_nodes, num_moments, num_groups) + for g in range(num_groups): + xs[g].append(x) + ys[g].append(y) + vals[g].append(values[:, moment, g]) + + for g in range(num_groups): + xs[g] = np.concatenate(xs[g]) + ys[g] = np.concatenate(ys[g]) + vals[g] = np.concatenate(vals[g]) + + return xs, ys, vals, num_groups + +def load_1d_flux(file_pattern, ranks, moment=0): + """Load concatenated 1-D (x, flux) data per energy group.""" + with h5py.File(file_pattern.format(ranks[0]), "r") as f0: + num_groups = int(f0.attrs["num_groups"]) + num_moments = int(f0.attrs["num_moments"]) + + xs = [[] for _ in range(num_groups)] + vals = [[] for _ in range(num_groups)] + + for r in ranks: + fp = file_pattern.format(r) + with h5py.File(fp, "r") as f: + x = f["mesh/nodes_z"][:] + values = f["values"][:] + num_nodes = x.size + + values = values.reshape(num_nodes, num_moments, num_groups) + + for g in range(num_groups): + xs[g].append(x) + vals[g].append(values[:, moment, g]) + + for g in range(num_groups): + xs[g] = np.concatenate(xs[g]) + vals[g] = np.concatenate(vals[g]) + + return xs, vals, num_groups + + +def sample_parameter_space(bounds, n_samples): + n_dim = len(bounds) + n_vertices = 2**n_dim + n_random = n_samples - n_vertices + + # Random interior samples + random_samples = np.array([ + [np.random.uniform(low, high) for (low, high) in bounds] + for _ in range(n_random) + ]) + + # Vertices of domain + vertices = np.zeros((n_vertices, n_dim)) + for i in range(n_vertices): + for d, (low, high) in enumerate(bounds): + if (i >> d) & 1: + vertices[i, d] = high + else: + vertices[i, d] = low + + samples = np.vstack([random_samples, vertices]) + return samples + + +def update_xs(in_file, out_file, sigma_t_vec, S): + with open(in_file, "r") as f: + lines = f.readlines() + + # --- SIGMA_T block --- + b = next(i for i, s in enumerate(lines) if "SIGMA_T_BEGIN" in s) + e = next(i for i, s in enumerate(lines) if "SIGMA_T_END" in s) + for i in range(b+1, e): + toks = lines[i].split() + g = int(toks[0]) + toks[1] = f"{float(sigma_t_vec[g]):.12g}" + lines[i] = " ".join(toks) + "\n" + + # --- TRANSFER_MOMENTS block --- + tb = next(i for i, s in enumerate(lines) if "TRANSFER_MOMENTS_BEGIN" in s) + te = next(i for i, s in enumerate(lines) if "TRANSFER_MOMENTS_END" in s) + + G = len(sigma_t_vec) + new_tm = [] + for gprime in range(G): + for g in range(G): + val = float(S[gprime][g]) + new_tm.append(f"M_GPRIME_G_VAL 0 {gprime} {g} {val:.12g}\n") + + lines[tb+1:te] = new_tm + + with open(out_file, "w") as f: + f.writelines(lines) \ No newline at end of file diff --git a/external/cxxopts/cxxopts.h b/external/cxxopts/cxxopts.h new file mode 100644 index 0000000..1b59fde --- /dev/null +++ b/external/cxxopts/cxxopts.h @@ -0,0 +1,2908 @@ +/* + +Copyright (c) 2014-2022 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// vim: ts=2:sw=2:expandtab + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH +}; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts { + +using String = icu::UnicodeString; + +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator +{ + public: + + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); +} + +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) +{ + for (std::size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; +} + +inline +size_t +stringLength(const String& s) +{ + return static_cast(s.length()); +} + +inline +std::string +toUTF8String(const String& s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +inline +bool +empty(const String& s) +{ + return s.isEmpty(); +} + +} // namespace cxxopts + +namespace std { + +inline +cxxopts::UnicodeStringIterator +begin(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, 0); +} + +inline +cxxopts::UnicodeStringIterator +end(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, s.length()); +} + +} // namespace std + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts { + +using String = std::string; + +template +T +toLocalString(T&& t) +{ + return std::forward(t); +} + +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} + +inline +String& +stringAppend(String&s, const String& a) +{ + return s.append(a); +} + +inline +String& +stringAppend(String& s, std::size_t n, char c) +{ + return s.append(n, c); +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template +std::string +toUTF8String(T&& t) +{ + return std::forward(t); +} + +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} + +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this +{ + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + add(const std::string& text) const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; +}; + +class specification : public exception +{ + public: + + explicit specification(const std::string& message) + : exception(message) + { + } +}; + +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) + { + } +}; + +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } +}; + +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) + { + } +}; + +class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string& text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } +}; + +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } +}; + +class missing_argument : public parsing +{ + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } +}; + +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } +}; + +class gratuitous_argument_for_option : public parsing +{ + public: + gratuitous_argument_for_option + ( + const std::string& option, + const std::string& arg + ) + : parsing( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } +}; + +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; + +class option_has_no_value : public exception +{ + public: + explicit option_has_no_value(const std::string& option) + : exception( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } +}; + +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; + +} // namespace exceptions + + +template +void throw_or_mimic(const std::string& text) +{ + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} + +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) + { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char* option_name_valid_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} + +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; +CXXOPTS_LINKONCE +const char* const truthy_pattern = + "(t|T)(rue)?|1"; +CXXOPTS_LINKONCE +const char* const falsy_pattern = + "(f|F)(alse)?|0"; +CXXOPTS_LINKONCE +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; +CXXOPTS_LINKONCE +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; +CXXOPTS_LINKONCE +const char* const option_specifier_separator_pattern = ", *"; + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + static const std::basic_regex integer_matcher(integer_pattern); + + std::smatch match; + std::regex_match(text, match, integer_matcher); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + static const std::basic_regex truthy_matcher(truthy_pattern); + std::smatch result; + std::regex_match(text, result, truthy_matcher); + return !result.empty(); +} + +inline bool IsFalseText(const std::string &text) +{ + static const std::basic_regex falsy_matcher(falsy_pattern); + std::smatch result; + std::regex_match(text, result, falsy_matcher); + return !result.empty(); +} + +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) +{ + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) + { + throw_or_mimic(text); + } + + OptionNames split_names; + + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + static const std::basic_regex option_matcher(option_pattern); + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; +} + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template +struct SignedCheck; + +template +struct SignedCheck +{ + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } +}; + +template +struct SignedCheck +{ + template + void + operator()(bool, U, const std::string&) const {} +}; + +template +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck::is_signed>()(negative, value, text); +} + +} // namespace detail + +template +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); +} + +template +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic(text); +} + +template +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast(std::numeric_limits::min()))); + } + else + { + limit = std::numeric_limits::max(); + } + + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) + { + throw_or_mimic(text); + } + + result = static_cast(result * base + digit); + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } +} + +template +void stringstream_parser(const std::string& text, T& value) +{ + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } +} + +template ::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} + +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic(text); +} + +inline +void +parse_value(const std::string& text, std::string& value) +{ + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template ::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + +template +void +parse_value(const std::string& text, std::vector& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +template +void +add_value(const std::string& text, T& value) +{ + parse_value(text, value); +} + +template +void +add_value(const std::string& text, std::vector& value) +{ + T v; + add_value(text, v); + value.emplace_back(std::move(v)); +} + +template +struct type_is_container +{ + static constexpr bool value = false; +}; + +template +struct type_is_container> +{ + static constexpr bool value = true; +}; + +template +class abstract_value : public Value +{ + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + add(const std::string& text) const override + { + add_value(text, *m_store); + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; +}; + +template +class standard_value : public abstract_value +{ + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } +}; + +template <> +class standard_value : public abstract_value +{ + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; + +} // namespace values + +template +std::shared_ptr +value() +{ + return std::make_shared>(); +} + +template +std::shared_ptr +value(T& t) +{ + return std::make_shared>(&t); +} + +class OptionAdder; + +CXXOPTS_NODISCARD +inline +const std::string& +first_or_empty(const OptionNames& long_names) +{ + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} + +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(first_long_name() + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + first_long_name() const + { + return first_or_empty(m_long); + } + + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } + + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } + + std::size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + std::size_t m_hash{}; +}; + +struct HelpOptionDetails +{ + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails +{ + std::string name{}; + std::string description{}; + std::vector options{}; +}; + +class OptionValue +{ + public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& details) + { + m_long_names = &details->long_names(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif + + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); + } + + return CXXOPTS_RTTI_CAST&>(*m_value).get(); + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional() const + { + if (m_value == nullptr) { + return std::nullopt; + } + return as(); + } +#endif + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const OptionNames * m_long_names = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue +{ + public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; + +class ParseResult +{ + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; + + Iterator() = default; + Iterator(const Iterator&) = default; + +// GCC complains about m_iter not being initialised in the member +// initializer list +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) + { + if (end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } +CXXOPTS_DIAGNOSTIC_POP + + Iterator& operator++() + { + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator& other) const + { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator& other) const + { + return !(*this == other); + } + + const KeyValue& operator*() + { + return *m_iter; + } + + const KeyValue* operator->() + { + return m_iter.operator->(); + } + + private: + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + Iterator + begin() const + { + return Iterator(this); + } + + Iterator + end() const + { + return Iterator(this, true); + } + + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional(const std::string& option) const + { + auto iter = m_keys.find(option); + if (iter != m_keys.end()) + { + auto viter = m_values.find(iter->second); + if (viter != m_values.end()) + { + return viter->second.as_optional(); + } + } + return std::nullopt; + } +#endif + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + const std::vector& + defaults() const + { + return m_defaults; + } + + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; + +struct Option +{ + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(const std::shared_ptr& value, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options +{ + public: + + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list