commit 3560585db8e6f9d6dd29753b28ff26fb1e05c9a2
parent 2024abd1274376f4ef61716eac202e1ececcc084
Author: Andrew Laack <andrew@laack.co>
Date: Wed, 16 Sep 2026 16:20:29 -0500
Removed prim stuff from c++ which is it's own repo
Diffstat:
19 files changed, 0 insertions(+), 3487 deletions(-)
diff --git a/background/Makefile b/background/Makefile
@@ -1,13 +0,0 @@
-build:
- g++ -Ofast src/main.cpp src/prim.cpp src/graph.cpp src/edge.cpp src/utils.cpp src/vertex.cpp src/background.cpp -lraylib -lX11 -o abg.out
-install: build
- cp abg.out /usr/local/bin/abg
-clean:
- rm abg.out
-test:
- g++ tests/graph_test.cpp src/prim.cpp src/graph.cpp src/edge.cpp src/utils.cpp src/vertex.cpp src/background.cpp -lCatch2Main -lCatch2 -lraylib -lX11 -o graph_tests
- ./graph_tests
- rm graph_tests
- g++ tests/algo_test.cpp src/prim.cpp src/graph.cpp src/edge.cpp src/utils.cpp src/vertex.cpp src/background.cpp -lCatch2Main -lCatch2 -lraylib -lX11 -o algo_tests
- ./algo_tests
- rm algo_tests
diff --git a/background/README b/background/README
@@ -1,23 +0,0 @@
-abg - animated background
-=========================
-
-
-dependencies
-============
-X11, raylib, g++
-
-
-usage
-=====
- Usage: abg [--help] [--version] [--vertices VAR] [--edges VAR]
-
- Optional arguments:
- -h, --help shows help message and exits
- -v, --version prints version information and exits
- --vertices number of vertices in the graph [nargs=0..1] [default: 200]
- -e, --edges number of edges in the graph [nargs=0..1] [default: 1000]
-
-examples
-========
- abg --edges 50 --vertices 10
- abg --edges 1500 --vertices 1000
diff --git a/background/headers/background.hpp b/background/headers/background.hpp
@@ -1,7 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <string>
-#include <vector>
-std::vector<uint32_t> getScreenSize();
-void sendToBg(std::string name);
diff --git a/background/headers/constants.hpp b/background/headers/constants.hpp
@@ -1,10 +0,0 @@
-#include <cstddef>
-#include <raylib.h>
-
-const CLITERAL(Color) DARKERGRAY { 40, 40, 40, 255 };
-
-const float VERTEX_RENDER_SIZE = 4;
-const float EDGE_REDNER_SIZE = 1;
-const std::size_t DEFAULT_EDGE_COUNT = 1000;
-const std::size_t DEFAULT_VERTEX_COUNT = 200;
-const float DEFAULT_SLEEP_TIME = 1;
diff --git a/background/headers/edge.hpp b/background/headers/edge.hpp
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <string>
-
-class Edge {
- public:
- // indices of v1 and v2
- Edge(std::size_t v1, std::size_t v2, double length2, std::size_t identifier);
- std::size_t identifier;
- std::size_t v1Index;
- std::size_t v2Index;
- double length2;
- bool traversed = false;
- std::string toString() const noexcept;
- bool operator<(const Edge& other) const;
- bool operator>(const Edge& other) const;
-};
diff --git a/background/headers/graph.hpp b/background/headers/graph.hpp
@@ -1,25 +0,0 @@
-#pragma once
-
-#include <cstddef>
-#include <cstdint>
-#include <string>
-#include <unordered_map>
-#include <vector>
-#include "edge.hpp"
-#include "vertex.hpp"
-
-class Graph {
- private:
- std::unordered_map<std::size_t, std::vector<Edge>> edges {};
- std::vector<Vertex> vertices {};
- public:
- // based on the edgeCount and vertCount, random edges and vertices will be created.
- Graph(std::size_t edgeCount, std::size_t vertCount, uint32_t xMax, uint32_t yMax);
- std::string toString() noexcept;
- void render() noexcept;
- void traverseVertexIdx(std::size_t idx);
- std::vector<Edge> getEdgesOfVertexIdx(std::size_t idx);
- void setEdgeTraversed(Edge e);
- Vertex getVertex(std::size_t idx);
- std::size_t getVertexCount() const noexcept;
-};
diff --git a/background/headers/prim.hpp b/background/headers/prim.hpp
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <cstdlib>
-#include <unistd.h>
-#include <raylib.h>
-#include <queue>
-#include "../headers/graph.hpp"
-#include <unordered_set>
-
-void explore(
- std::size_t cIdx,
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>>& toVisit,
- Edge& current,
- Graph& g
- );
-void oneStepPrim(
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>>& toVisit,
- std::unordered_set<std::size_t>& visitedIndices,
- Graph& g
- );
diff --git a/background/headers/utils.hpp b/background/headers/utils.hpp
@@ -1,8 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <raylib.h>
-
-float square(float x);
-Vector2 randomPosition(uint32_t xMax, uint32_t yMax);
-float distanceSquared(Vector2 v1, Vector2 v2);
diff --git a/background/headers/vertex.hpp b/background/headers/vertex.hpp
@@ -1,14 +0,0 @@
-#pragma once
-
-#include <raylib.h>
-#include <string>
-
-class Vertex {
- public:
- Vertex(Vector2 position, float drawSize);
- Vector2 position;
- float drawSize;
- std::string toString();
- void render();
- bool visited = false;
-};
diff --git a/background/src/background.cpp b/background/src/background.cpp
@@ -1,61 +0,0 @@
-#include <cstddef>
-#include <cstdint>
-#include <stdexcept>
-#include <string>
-#include <vector>
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#include <unistd.h>
-
-std::vector<uint32_t> getScreenSize() {
- Display* dpy;
- Screen* screen;
- dpy = XOpenDisplay(NULL);
- int count = ScreenCount(dpy);
- if(count == 0) {
- throw std::runtime_error{"Unable to find screens."};
- }
- screen = ScreenOfDisplay(dpy, 0);
- std::vector<uint32_t> res {(uint32_t)screen->width, (uint32_t)screen->height};
- return res;
-}
-
-void sendToBg(std::string name)
-{
- Display* d = XOpenDisplay(nullptr);
- Window root = DefaultRootWindow(d), r, p, *kids;
- uint32_t n;
-
- XQueryTree(d, root, &r, &p, &kids, &n);
- for (unsigned i = 0; i < n; i++) {
-
- char* wn = nullptr;
- XFetchName(d, kids[i], &wn);
-
- if (wn == nullptr) {
- continue;
- }
-
- bool hit = name == wn;
- XFree(wn);
-
- if (!hit) {
- continue;
- }
-
- XSetWindowAttributes a;
-
- a.override_redirect = True;
- XChangeWindowAttributes(d, kids[i], CWOverrideRedirect, &a);
-
- // have to do unmap / map to make it bg for all tags
- XUnmapWindow(d, kids[i]);
- XMapWindow(d, kids[i]);
-
- XLowerWindow(d, kids[i]);
- break;
- }
-
- XFree(kids);
- XCloseDisplay(d);
-}
diff --git a/background/src/edge.cpp b/background/src/edge.cpp
@@ -1,17 +0,0 @@
-#include "../headers/edge.hpp"
-#include <cstddef>
-
-Edge::Edge(std::size_t v1, std::size_t v2, double length2, std::size_t identifier)
- : v1Index(v1), v2Index(v2), length2(length2), identifier(identifier) {}
-
-std::string Edge::toString() const noexcept {
- return "(" + std::to_string(this->v1Index) + ", " + std::to_string(this->v2Index) + ")";
-}
-
-bool Edge::operator<(const Edge& other) const {
- return length2 < other.length2;
-}
-
-bool Edge::operator>(const Edge& other) const {
- return length2 > other.length2;
-}
diff --git a/background/src/graph.cpp b/background/src/graph.cpp
@@ -1,144 +0,0 @@
-#include "../headers/graph.hpp"
-#include "../headers/constants.hpp"
-#include "../headers/vertex.hpp"
-#include "../headers/utils.hpp"
-#include <cstddef>
-#include <cstdint>
-#include <raylib.h>
-#include <stdexcept>
-
-Graph::Graph(std::size_t edgeCount, std::size_t vertCount, uint32_t xMax, uint32_t yMax) {
-
- if(edgeCount > 0 && vertCount <= 1) {
- throw std::invalid_argument("This graph does not support self-loops.");
- }
- if(xMax <= 0 || yMax <= 0) {
- throw std::invalid_argument("xMax and yMax must be > 0.");
- }
-
-
- for(std::size_t i = 0; i < vertCount; ++i) {
- Vector2 rnd = randomPosition(xMax, yMax);
- Vertex v {rnd,VERTEX_RENDER_SIZE};
- this->vertices.push_back(v);
- }
- for(std::size_t i = 0; i < edgeCount; ++i) {
- std::size_t idx1 = 0;
- std::size_t idx2 = 0;
- // no self-edges
- while (idx1 == idx2) {
- idx1 = std::rand() % vertCount;
- idx2 = std::rand() % vertCount;
- }
-
- Edge e {idx1, idx2, distanceSquared(vertices[idx1].position, vertices[idx2].position), i};
- this->edges[idx1].push_back(e);
- this->edges[idx2].push_back(e);
- }
-}
-
-std::string Graph::toString() noexcept {
-
- std::string result = "edges: {";
-
- for(auto pair: this->edges) {
- auto key = pair.first;
- for(auto edge: edges[key]) {
- result += edge.toString();
- }
- }
-
- result += "}";
-
- result += "\nvertices: {";
-
- for(auto vertex: this->vertices) {
- result += vertex.toString();
- }
-
- result += "}";
- return result;
-}
-
-
-void Graph::render() noexcept {
-
- // yes, this will double draw because we track 0 -> 1 and 1 -> 0
-
- std::vector<Edge> visited {};
- for(auto pair: this->edges) {
- auto edges = this->edges[pair.first];
- for(auto edge: edges) {
- std::size_t idx1 = edge.v1Index;
- std::size_t idx2 = edge.v2Index;
- auto v1 = vertices[idx1].position;
- auto v2 = vertices[idx2].position;
- if(edge.traversed) {
- visited.push_back(edge);
- } else {
- DrawLineEx(v1, v2, EDGE_REDNER_SIZE,DARKERGRAY);
- }
- }
- }
-
- for(auto vertex: this->vertices) {
- vertex.render();
- }
-
- // ensure we draw visited over unvisited for better looks
- for(auto edge: visited) {
- std::size_t idx1 = edge.v1Index;
- std::size_t idx2 = edge.v2Index;
- auto v1 = vertices[idx1].position;
- auto v2 = vertices[idx2].position;
- DrawLineEx(v1, v2, EDGE_REDNER_SIZE, WHITE);
- }
-}
-
-void Graph::traverseVertexIdx(std::size_t idx) {
- this->vertices[idx].visited = true;
-}
-
-std::vector<Edge> Graph::getEdgesOfVertexIdx(std::size_t idx) {
- return this->edges[idx];
-}
-
-void Graph::setEdgeTraversed(Edge e) {
-
- std::size_t source = e.v1Index;
- std::size_t destination = e.v2Index;
-
- if(source == destination) {
- return;
- }
-
- auto& cEdges = edges[source];
-
- for(auto& edge : cEdges) {
- if(edge.v2Index == destination || edge.v1Index == destination) {
- edge.traversed = true;
- }
- }
-
- auto& oEdges = edges[destination];
-
- for(auto& edge : oEdges) {
- if(edge.v2Index == source || edge.v1Index == source) {
- edge.traversed = true;
- }
- }
-
-}
-
-Vertex Graph::getVertex(std::size_t idx) {
- // idx can't be negative bc size_t
- if(idx >= vertices.size()) {
- throw std::invalid_argument("idx out of bounds for vertex list");
- }
- return vertices[idx];
-}
-
-
-std::size_t Graph::getVertexCount() const noexcept {
- return vertices.size();
-}
diff --git a/background/src/main.cpp b/background/src/main.cpp
@@ -1,91 +0,0 @@
-#include "../headers/graph.hpp"
-#include "../headers/prim.hpp"
-#include "../headers/background.hpp"
-#include "../headers/constants.hpp"
-#include "../vendor/argparse.hpp"
-#include <cstddef>
-#include <cstdint>
-#include <cstdlib>
-#include <iostream>
-#include <unistd.h>
-#include <raylib.h>
-#include <ctime>
-#include <queue>
-#include <unordered_set>
-
-int main(int argc, char** argv) {
-
- srand(clock());
- // reverse semver
- argparse::ArgumentParser program("abg", "10.0.0");
-
- program.add_argument("--vertices")
- .help("number of vertices in the graph")
- .default_value(DEFAULT_VERTEX_COUNT)
- .scan<'i', std::size_t>();
-
- program.add_argument("--sleep", "-s")
- .help("amount of time to sleep between traversals")
- .default_value(DEFAULT_SLEEP_TIME)
- .scan<'g', float>();
-
-
- // edges != total number of unique edges where uniqueness is defined by vertices
- // this is because we allow multiple edges between two vertices (though they aren't rendered differently)
- // we don't allow self-edges though.
-
- program.add_argument("--edges", "-e")
- .help("number of edges in the graph")
- .default_value(DEFAULT_EDGE_COUNT)
- .scan<'i', std::size_t>();
-
- try {
- program.parse_args(argc, argv);
- }
- catch (const std::exception& err) {
- std::cerr << err.what() << std::endl;
- std::cerr << program;
- std::exit(1);
- }
-
- std::size_t vertexCount = program.get<std::size_t>("--vertices");
- std::size_t edgeCount = program.get<std::size_t>("--edges");
- float sleepTime = program.get<float>("--sleep");
-
- SetTraceLogLevel(LOG_ERROR);
-
- auto ss = getScreenSize();
-
- uint32_t xMax = ss[0];
- uint32_t yMax = ss[1];
-
- // would be nice to do this all in background.cpp, but raylib and x11 can't both be imported
- // by the same file because of some dependency chain thing with Font.
-
- InitWindow(xMax, yMax, "abg");
- sendToBg("abg");
-
- int count = 0;
- while (!WindowShouldClose()) {
- count += 1;
-
- Graph g = Graph(edgeCount, vertexCount, xMax,yMax);
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(0);
- for(auto edge: edges) {
- toVisit.push(edge);
- }
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- while (!WindowShouldClose() && toVisit.size() != 0) {
- BeginDrawing();
- ClearBackground(BLACK);
- g.render();
- EndDrawing();
- usleep((int)(sleepTime * 1000000));
- oneStepPrim(toVisit, visitedIndices, g);
- }
- }
-}
diff --git a/background/src/prim.cpp b/background/src/prim.cpp
@@ -1,61 +0,0 @@
-#include "../headers/graph.hpp"
-#include "../headers/prim.hpp"
-#include <cstdlib>
-#include <unistd.h>
-#include <raylib.h>
-#include <queue>
-#include <unordered_set>
-
-
-void explore(
- std::size_t cIdx,
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>>& toVisit,
- Edge& current,
- Graph& g,
- std::unordered_set<std::size_t>& visitedIndices
- ) {
-
- visitedIndices.insert(cIdx);
- g.traverseVertexIdx(cIdx);
-
- g.setEdgeTraversed(current);
-
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(cIdx);
- for(auto edge: edges) {
- toVisit.push(edge);
- }
-}
-
-void oneStepPrim(
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>>& toVisit,
- std::unordered_set<std::size_t>& visitedIndices,
- Graph& g
- ) {
- bool found = false;
- if(toVisit.size() == 0) {
- return;
- }
- while(found == false) {
- if(toVisit.size() == 0) {
- return;
- }
-
- found = true;
- auto current = toVisit.top();
- toVisit.pop();
-
- if(visitedIndices.find(current.v2Index) == visitedIndices.end()) {
- auto cIdx = current.v2Index;
- explore(cIdx, toVisit, current, g, visitedIndices);
-
- } else if(visitedIndices.find(current.v1Index) == visitedIndices.end()) {
- auto cIdx = current.v1Index;
- explore(cIdx, toVisit, current, g, visitedIndices);
- } else {
- found = false;
- }
-
- }
-}
-
-
diff --git a/background/src/utils.cpp b/background/src/utils.cpp
@@ -1,24 +0,0 @@
-#include "../headers/utils.hpp"
-#include <cstdint>
-#include <cassert>
-#include <cstdlib>
-
-float square(float x) {
- return x * x;
-}
-
-// call srand before invocation as this is a pure function.
-Vector2 randomPosition(uint32_t xMax, uint32_t yMax) {
- uint32_t r1 = rand() % xMax;
- uint32_t r2 = rand() % yMax;
- Vector2 v {(float)r1,(float)r2};
- return v;
-}
-
-float distanceSquared(Vector2 v1, Vector2 v2) {
- float xSquare = square(v1.x - v2.x);
- float ySquare = square(v1.y - v2.y);
- float result = xSquare + ySquare;
- assert(result >= 0);
- return result;
-}
diff --git a/background/src/vertex.cpp b/background/src/vertex.cpp
@@ -1,21 +0,0 @@
-#include "../headers/vertex.hpp"
-#include <string>
-#include <raylib.h>
-
-Vertex::Vertex(Vector2 position, float drawSize)
- : position(position), drawSize(drawSize) {}
-
-
-std::string Vertex::toString() {
- std::string result = "(" + std::to_string(this->position.x) + ", " + std::to_string(this->position.y) + ")";
- return result;
-}
-
-
-void Vertex::render() {
- if(visited) {
- DrawCircle(position.x, position.y, drawSize, WHITE);
- } else {
- DrawCircle(position.x, position.y, drawSize, DARKGRAY);
- }
-}
diff --git a/background/tests/algo_test.cpp b/background/tests/algo_test.cpp
@@ -1,215 +0,0 @@
-#include "../headers/prim.hpp"
-
-#include <catch2/catch_test_macros.hpp>
-#include <cstddef>
-
-TEST_CASE( "Small Prim algorithm", "[small prim algo]" ) {
- std::size_t edgeCount = 1;
- std::size_t vertCount = 2;
-
- float xMax = 5120;
- float yMax = 1440;
-
- Graph g = Graph(edgeCount, vertCount, xMax,yMax);
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(0);
-
- for(auto edge: edges) {
- toVisit.push(edge);
- }
-
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- while (toVisit.size() != 0) {
- oneStepPrim(toVisit, visitedIndices, g);
- }
- // this will be true bc 2 vertices 1 edge...
- // our graph may have multi-edges.
- REQUIRE(visitedIndices.size() == 2);
-}
-
-
-bool isConnected(Graph g) {
-
- std::unordered_set<std::size_t> verticesFound {0};
-
- std::vector<std::size_t> current {};
-
- while(true) {
- for(auto key: verticesFound) {
- auto edges = g.getEdgesOfVertexIdx(key);
- for(auto edge: edges) {
- current.push_back(edge.v1Index);
- current.push_back(edge.v2Index);
- }
- }
- std::size_t len = verticesFound.size();
- for(auto idx: current) {
- verticesFound.insert(idx);
- }
- if (len == verticesFound.size()) {
- break;
- }
- }
-
- return verticesFound.size() == g.getVertexCount();
-}
-
-
-TEST_CASE( "Large Prim algorithm", "[Large prim algo]" ) {
- std::size_t edgeCount = 3000;
- std::size_t vertCount = 1000;
-
- float xMax = 5120;
- float yMax = 1440;
-
- Graph g = Graph(edgeCount, vertCount, xMax,yMax);
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
-
- do {
- g = Graph(edgeCount, vertCount, xMax,yMax);
- }
- while(!isConnected(g));
-
-
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(0);
-
- for(auto edge: edges) {
- toVisit.push(edge);
- }
-
- while (toVisit.size() != 0) {
- oneStepPrim(toVisit, visitedIndices, g);
- }
-
- REQUIRE(visitedIndices.size() == vertCount);
-}
-
-TEST_CASE( "Medium Prim algorithm", "[Medium prim algo]" ) {
- std::size_t edgeCount = 70;
- std::size_t vertCount = 51;
-
- float xMax = 5120;
- float yMax = 1440;
-
- Graph g = Graph(edgeCount, vertCount, xMax,yMax);
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
-
- do {
- g = Graph(edgeCount, vertCount, xMax,yMax);
- }
- while(!isConnected(g));
-
-
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(0);
-
- for(auto edge: edges) {
- toVisit.push(edge);
- }
-
- while (toVisit.size() != 0) {
- oneStepPrim(toVisit, visitedIndices, g);
- }
-
- REQUIRE(visitedIndices.size() == vertCount);
-}
-
-
-TEST_CASE( "Staircase Prim algorithm", "[Staircase prim algo]" ) {
- for(int i = 2; i < 30; ++i) {
- for(int x = i-1; x < 60; ++x) {
- std::size_t edgeCount = x;
- std::size_t vertCount = i;
-
- float xMax = 5120;
- float yMax = 1440;
-
- Graph g = Graph(edgeCount, vertCount, xMax,yMax);
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
-
- do {
- g = Graph(edgeCount, vertCount, xMax,yMax);
- }
- while(!isConnected(g));
-
-
- std::vector<Edge> edges = g.getEdgesOfVertexIdx(0);
- for(auto edge: edges) {
- toVisit.push(edge);
- }
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- while (toVisit.size() != 0) {
- oneStepPrim(toVisit, visitedIndices, g);
- }
-
- REQUIRE(visitedIndices.size() == vertCount);
-
-
- }
- }
-}
-
-TEST_CASE( "Small Prim Test", "[Small full validation]" ) {
- for(int i = 0; i < 100; ++i) {
- std::size_t edgeCount = 10;
- std::size_t vertCount = 5;
-
- float xMax = 5120;
- float yMax = 1440;
-
- Graph g = Graph(edgeCount, vertCount, xMax,yMax);
- do {
- g = Graph(edgeCount, vertCount, xMax,yMax);
- }
- while(!isConnected(g));
-
- std::unordered_set<std::size_t> visitedIndices {};
- std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> toVisit {};
-
- for (auto edge : g.getEdgesOfVertexIdx(0)) toVisit.push(edge);
- g.traverseVertexIdx(0);
- visitedIndices.insert(0);
-
- std::unordered_set<std::size_t> vBefore = visitedIndices;
-
- bool havePrior = false;
- Edge prior = toVisit.top();
- std::unordered_set<std::size_t> visibleAtPrior;
-
- while (toVisit.size() != 0) {
- auto current = toVisit.top();
- if (havePrior) {
- bool wasPresent = visibleAtPrior.count(current.v1Index) > 0
- || visibleAtPrior.count(current.v2Index) > 0;
- // anytime we use the same source node two steps in a row, the second
- // weight must be smaller.
- if (wasPresent) {
- REQUIRE(current.length2 >= prior.length2);
- }
- }
-
- prior = current;
- visibleAtPrior = visitedIndices;
- havePrior = true;
- oneStepPrim(toVisit, visitedIndices, g);
- bool valid = vBefore.size() + 1 == visitedIndices.size()
- || vBefore.size() == vertCount;
- REQUIRE(valid);
- vBefore = visitedIndices;
- }
- REQUIRE(visitedIndices.size() == vertCount);
- }
-}
diff --git a/background/tests/graph_test.cpp b/background/tests/graph_test.cpp
@@ -1,127 +0,0 @@
-#include "../headers/graph.hpp"
-
-#include <catch2/catch_test_macros.hpp>
-#include <cstdlib>
-#include <unordered_set>
-
-TEST_CASE( "Graph traversal invariants", "[graph traversal]" ) {
- int vertCount = 10;
- int edgeCount = 10;
- float xMax = 10;
- float yMax = 10;
- auto g = Graph(edgeCount,vertCount,xMax,yMax);
- int count = 0;
- for(int i = 0; i < vertCount; ++i) {
- g.traverseVertexIdx(i);
- count += 1;
- }
- for(int i = 0; i < vertCount; ++i) {
- REQUIRE(g.getVertex(i).visited);
- }
-
- bool error = false;
-
- try {
- g.getVertex(vertCount + 1);
- } catch (std::invalid_argument e) {
- error = true;
- }
-
- REQUIRE(error);
-}
-
-void createGraph(int vertCount, int edgeCount) {
- auto g = Graph(edgeCount,vertCount,10,10);
-}
-
-TEST_CASE( "Graph gracefully handles stupid fucking inputs", "[graph bad inputs]" ) {
- int vertCount = 0;
- int edgeCount = 0; // some trivial and stupid graph
-
- bool error = false;
-
- try {
- createGraph(vertCount, edgeCount);
- } catch (std::invalid_argument e) {
- error = true;
- }
- REQUIRE(!error);
-
- vertCount = 0;
- edgeCount = 1; // bad
-
- error = false;
- try {
- createGraph(vertCount, edgeCount);
- } catch (std::invalid_argument e) {
- error = true;
- }
-
- REQUIRE(error);
-
-}
-
-TEST_CASE( "Graph respects max x and max y values", "[graph max values]" ) {
- int vertCount = 10;
- int edgeCount = 10;
-
- for(int z = 2; z < 100; ++z) {
-
- float xMax = 0;
- float yMax = 0;
-
- while (xMax == 0 || yMax == 0) {
- xMax = rand() % z;
- yMax = rand() % z;
- }
-
- auto g = Graph(edgeCount,vertCount,xMax,yMax);
-
- for(int i = 0; i < vertCount; ++i) {
- REQUIRE(g.getVertex(i).position.x <= xMax);
- }
- for(int i = 0; i < vertCount; ++i) {
- REQUIRE(g.getVertex(i).position.y <= yMax);
- }
- }
- auto g = Graph(edgeCount,vertCount,UINT32_MAX,UINT32_MAX);
-
- for(int i = 0; i < vertCount; ++i) {
- REQUIRE(g.getVertex(i).position.x <= UINT32_MAX);
- }
- for(int i = 0; i < vertCount; ++i) {
- REQUIRE(g.getVertex(i).position.y <= UINT32_MAX);
- }
-}
-
-
-std::size_t countEdges(Graph g) {
- std::size_t vertexCount = g.getVertexCount();
- std::unordered_set<std::size_t> unique {};
-
- for(std::size_t i = 0; i < vertexCount; ++i) {
- auto edges = g.getEdgesOfVertexIdx(i);
- for(auto edge: edges) {
- unique.insert(edge.identifier);
- }
- }
- return unique.size();
-}
-
-TEST_CASE( "Graph vertex and edge counts", "[graph counts]" ) {
-
- for(int i = 2; i < 100; ++i) {
- for(int x = 1; x < 10; ++x) {
- int vertCount = i;
- int edgeCount = x;
- float xMax = 10;
- float yMax = 10;
-
- auto g = Graph(edgeCount,vertCount,xMax,yMax);
-
- REQUIRE(g.getVertexCount() == vertCount);
- REQUIRE(countEdges(g) == edgeCount);
- }
- }
-
-}
diff --git a/background/vendor/argparse.hpp b/background/vendor/argparse.hpp
@@ -1,2589 +0,0 @@
-/*
- __ _ _ __ __ _ _ __ __ _ _ __ ___ ___
- / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++
-| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse
- \__,_|_| \__, | .__/ \__,_|_| |___/\___|
- |___/|_|
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-SPDX-License-Identifier: MIT
-Copyright (c) 2019-2022 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
-and other contributors.
-
-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.
-*/
-#pragma once
-
-#include <cerrno>
-
-#ifndef ARGPARSE_MODULE_USE_STD_MODULE
-#include <algorithm>
-#include <any>
-#include <array>
-#include <set>
-#include <charconv>
-#include <cstdlib>
-#include <functional>
-#include <iomanip>
-#include <iostream>
-#include <iterator>
-#include <limits>
-#include <list>
-#include <map>
-#include <numeric>
-#include <optional>
-#include <sstream>
-#include <stdexcept>
-#include <string>
-#include <string_view>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include <variant>
-#include <vector>
-#include <filesystem>
-#endif
-
-#ifndef ARGPARSE_CUSTOM_STRTOF
-#define ARGPARSE_CUSTOM_STRTOF strtof
-#endif
-
-#ifndef ARGPARSE_CUSTOM_STRTOD
-#define ARGPARSE_CUSTOM_STRTOD strtod
-#endif
-
-#ifndef ARGPARSE_CUSTOM_STRTOLD
-#define ARGPARSE_CUSTOM_STRTOLD strtold
-#endif
-
-namespace argparse {
-
-namespace details { // namespace for helper methods
-
-template <typename T, typename = void>
-struct HasContainerTraits : std::false_type {};
-
-template <> struct HasContainerTraits<std::string> : std::false_type {};
-
-template <> struct HasContainerTraits<std::string_view> : std::false_type {};
-
-template <typename T>
-struct HasContainerTraits<
- T, std::void_t<typename T::value_type, decltype(std::declval<T>().begin()),
- decltype(std::declval<T>().end()),
- decltype(std::declval<T>().size())>> : std::true_type {};
-
-template <typename T>
-inline constexpr bool IsContainer = HasContainerTraits<T>::value;
-
-template <typename T, typename = void>
-struct HasStreamableTraits : std::false_type {};
-
-template <typename T>
-struct HasStreamableTraits<
- T,
- std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
- : std::true_type {};
-
-template <typename T>
-inline constexpr bool IsStreamable = HasStreamableTraits<T>::value;
-
-constexpr std::size_t repr_max_container_size = 5;
-
-template <typename T> std::string repr(T const &val) {
- if constexpr (std::is_same_v<T, bool>) {
- return val ? "true" : "false";
- } else if constexpr (std::is_convertible_v<T, std::string_view>) {
- return '"' + std::string{std::string_view{val}} + '"';
- } else if constexpr (IsContainer<T>) {
- std::stringstream out;
- out << "{";
- const auto size = val.size();
- if (size > 1) {
- out << repr(*val.begin());
- std::for_each(
- std::next(val.begin()),
- std::next(
- val.begin(),
- static_cast<typename T::iterator::difference_type>(
- std::min<std::size_t>(size, repr_max_container_size) - 1)),
- [&out](const auto &v) { out << " " << repr(v); });
- if (size <= repr_max_container_size) {
- out << " ";
- } else {
- out << "...";
- }
- }
- if (size > 0) {
- out << repr(*std::prev(val.end()));
- }
- out << "}";
- return out.str();
- } else if constexpr (IsStreamable<T>) {
- std::stringstream out;
- out << val;
- return out.str();
- } else {
- return "<not representable>";
- }
-}
-
-namespace {
-
-template <typename T> constexpr bool standard_signed_integer = false;
-template <> constexpr bool standard_signed_integer<signed char> = true;
-template <> constexpr bool standard_signed_integer<short int> = true;
-template <> constexpr bool standard_signed_integer<int> = true;
-template <> constexpr bool standard_signed_integer<long int> = true;
-template <> constexpr bool standard_signed_integer<long long int> = true;
-
-template <typename T> constexpr bool standard_unsigned_integer = false;
-template <> constexpr bool standard_unsigned_integer<unsigned char> = true;
-template <> constexpr bool standard_unsigned_integer<unsigned short int> = true;
-template <> constexpr bool standard_unsigned_integer<unsigned int> = true;
-template <> constexpr bool standard_unsigned_integer<unsigned long int> = true;
-template <>
-constexpr bool standard_unsigned_integer<unsigned long long int> = true;
-
-} // namespace
-
-constexpr int radix_2 = 2;
-constexpr int radix_8 = 8;
-constexpr int radix_10 = 10;
-constexpr int radix_16 = 16;
-
-template <typename T>
-constexpr bool standard_integer =
- standard_signed_integer<T> || standard_unsigned_integer<T>;
-
-template <class F, class Tuple, class Extra, std::size_t... I>
-constexpr decltype(auto)
-apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x,
- std::index_sequence<I...> /*unused*/) {
- return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...,
- std::forward<Extra>(x));
-}
-
-template <class F, class Tuple, class Extra>
-constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) {
- return details::apply_plus_one_impl(
- std::forward<F>(f), std::forward<Tuple>(t), std::forward<Extra>(x),
- std::make_index_sequence<
- std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
-}
-
-constexpr auto pointer_range(std::string_view s) noexcept {
- return std::tuple(s.data(), s.data() + s.size());
-}
-
-template <class CharT, class Traits>
-constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
- std::basic_string_view<CharT, Traits> s) noexcept {
- return s.substr(0, prefix.size()) == prefix;
-}
-
-enum class chars_format {
- scientific = 0xf1,
- fixed = 0xf2,
- hex = 0xf4,
- binary = 0xf8,
- general = fixed | scientific
-};
-
-struct ConsumeBinaryPrefixResult {
- bool is_binary;
- std::string_view rest;
-};
-
-constexpr auto consume_binary_prefix(std::string_view s)
- -> ConsumeBinaryPrefixResult {
- if (starts_with(std::string_view{"0b"}, s) ||
- starts_with(std::string_view{"0B"}, s)) {
- s.remove_prefix(2);
- return {true, s};
- }
- return {false, s};
-}
-
-struct ConsumeHexPrefixResult {
- bool is_hexadecimal;
- std::string_view rest;
-};
-
-using namespace std::literals;
-
-constexpr auto consume_hex_prefix(std::string_view s)
- -> ConsumeHexPrefixResult {
- if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
- s.remove_prefix(2);
- return {true, s};
- }
- return {false, s};
-}
-
-template <class T, auto Param>
-inline auto do_from_chars(std::string_view s) -> T {
- T x{0};
- auto [first, last] = pointer_range(s);
- auto [ptr, ec] = std::from_chars(first, last, x, Param);
- if (ec == std::errc()) {
- if (ptr == last) {
- return x;
- }
- throw std::invalid_argument{"pattern '" + std::string(s) +
- "' does not match to the end"};
- }
- if (ec == std::errc::invalid_argument) {
- throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"};
- }
- if (ec == std::errc::result_out_of_range) {
- throw std::range_error{"'" + std::string(s) + "' not representable"};
- }
- return x; // unreachable
-}
-
-template <class T, auto Param = 0> struct parse_number {
- auto operator()(std::string_view s) -> T {
- return do_from_chars<T, Param>(s);
- }
-};
-
-template <class T> struct parse_number<T, radix_2> {
- auto operator()(std::string_view s) -> T {
- if (auto [ok, rest] = consume_binary_prefix(s); ok) {
- return do_from_chars<T, radix_2>(rest);
- }
- throw std::invalid_argument{"pattern not found"};
- }
-};
-
-template <class T> struct parse_number<T, radix_16> {
- auto operator()(std::string_view s) -> T {
- if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
- if (auto [ok, rest] = consume_hex_prefix(s); ok) {
- try {
- return do_from_chars<T, radix_16>(rest);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- }
- }
- } else {
- // Allow passing hex numbers without prefix
- // Shape 'x' already has to be specified
- try {
- return do_from_chars<T, radix_16>(s);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- }
- }
-
- throw std::invalid_argument{"pattern '" + std::string(s) +
- "' not identified as hexadecimal"};
- }
-};
-
-template <class T> struct parse_number<T> {
- auto operator()(std::string_view s) -> T {
- auto [ok, rest] = consume_hex_prefix(s);
- if (ok) {
- try {
- return do_from_chars<T, radix_16>(rest);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as hexadecimal: " + err.what());
- }
- }
-
- auto [ok_binary, rest_binary] = consume_binary_prefix(s);
- if (ok_binary) {
- try {
- return do_from_chars<T, radix_2>(rest_binary);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as binary: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as binary: " + err.what());
- }
- }
-
- if (starts_with("0"sv, s)) {
- try {
- return do_from_chars<T, radix_8>(rest);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as octal: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as octal: " + err.what());
- }
- }
-
- try {
- return do_from_chars<T, radix_10>(rest);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + std::string(s) +
- "' as decimal integer: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + std::string(s) +
- "' as decimal integer: " + err.what());
- }
- }
-};
-
-namespace {
-
-template <class T> inline const auto generic_strtod = nullptr;
-template <> inline const auto generic_strtod<float> = ARGPARSE_CUSTOM_STRTOF;
-template <> inline const auto generic_strtod<double> = ARGPARSE_CUSTOM_STRTOD;
-template <>
-inline const auto generic_strtod<long double> = ARGPARSE_CUSTOM_STRTOLD;
-
-} // namespace
-
-template <class T> inline auto do_strtod(std::string const &s) -> T {
- if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
- throw std::invalid_argument{"pattern '" + s + "' not found"};
- }
-
- auto [first, last] = pointer_range(s);
- char *ptr;
-
- errno = 0;
- auto x = generic_strtod<T>(first, &ptr);
- if (errno == 0) {
- if (ptr == last) {
- return x;
- }
- throw std::invalid_argument{"pattern '" + s +
- "' does not match to the end"};
- }
- if (errno == ERANGE) {
- throw std::range_error{"'" + s + "' not representable"};
- }
- return x; // unreachable
-}
-
-template <class T> struct parse_number<T, chars_format::general> {
- auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
- throw std::invalid_argument{
- "chars_format::general does not parse hexfloat"};
- }
- if (auto r = consume_binary_prefix(s); r.is_binary) {
- throw std::invalid_argument{
- "chars_format::general does not parse binfloat"};
- }
-
- try {
- return do_strtod<T>(s);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + s +
- "' as number: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + s +
- "' as number: " + err.what());
- }
- }
-};
-
-template <class T> struct parse_number<T, chars_format::hex> {
- auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
- throw std::invalid_argument{"chars_format::hex parses hexfloat"};
- }
- if (auto r = consume_binary_prefix(s); r.is_binary) {
- throw std::invalid_argument{"chars_format::hex does not parse binfloat"};
- }
-
- try {
- return do_strtod<T>(s);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + s +
- "' as hexadecimal: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + s +
- "' as hexadecimal: " + err.what());
- }
- }
-};
-
-template <class T> struct parse_number<T, chars_format::binary> {
- auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
- throw std::invalid_argument{
- "chars_format::binary does not parse hexfloat"};
- }
- if (auto r = consume_binary_prefix(s); !r.is_binary) {
- throw std::invalid_argument{"chars_format::binary parses binfloat"};
- }
-
- return do_strtod<T>(s);
- }
-};
-
-template <class T> struct parse_number<T, chars_format::scientific> {
- auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
- throw std::invalid_argument{
- "chars_format::scientific does not parse hexfloat"};
- }
- if (auto r = consume_binary_prefix(s); r.is_binary) {
- throw std::invalid_argument{
- "chars_format::scientific does not parse binfloat"};
- }
- if (s.find_first_of("eE") == std::string::npos) {
- throw std::invalid_argument{
- "chars_format::scientific requires exponent part"};
- }
-
- try {
- return do_strtod<T>(s);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + s +
- "' as scientific notation: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + s +
- "' as scientific notation: " + err.what());
- }
- }
-};
-
-template <class T> struct parse_number<T, chars_format::fixed> {
- auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
- throw std::invalid_argument{
- "chars_format::fixed does not parse hexfloat"};
- }
- if (auto r = consume_binary_prefix(s); r.is_binary) {
- throw std::invalid_argument{
- "chars_format::fixed does not parse binfloat"};
- }
- if (s.find_first_of("eE") != std::string::npos) {
- throw std::invalid_argument{
- "chars_format::fixed does not parse exponent part"};
- }
-
- try {
- return do_strtod<T>(s);
- } catch (const std::invalid_argument &err) {
- throw std::invalid_argument("Failed to parse '" + s +
- "' as fixed notation: " + err.what());
- } catch (const std::range_error &err) {
- throw std::range_error("Failed to parse '" + s +
- "' as fixed notation: " + err.what());
- }
- }
-};
-
-template <typename StrIt>
-std::string join(StrIt first, StrIt last, const std::string &separator) {
- if (first == last) {
- return "";
- }
- std::stringstream value;
- value << *first;
- ++first;
- while (first != last) {
- value << separator << *first;
- ++first;
- }
- return value.str();
-}
-
-template <typename T> struct can_invoke_to_string {
- template <typename U>
- static auto test(int)
- -> decltype(std::to_string(std::declval<U>()), std::true_type{});
-
- template <typename U> static auto test(...) -> std::false_type;
-
- static constexpr bool value = decltype(test<T>(0))::value;
-};
-
-template <typename T> struct IsChoiceTypeSupported {
- using CleanType = typename std::decay<T>::type;
- static const bool value = std::is_integral<CleanType>::value ||
- std::is_same<CleanType, std::string>::value ||
- std::is_same<CleanType, std::string_view>::value ||
- std::is_same<CleanType, const char *>::value;
-};
-
-template <typename StringType>
-std::size_t get_levenshtein_distance(const StringType &s1,
- const StringType &s2) {
- std::vector<std::vector<std::size_t>> dp(
- s1.size() + 1, std::vector<std::size_t>(s2.size() + 1, 0));
-
- for (std::size_t i = 0; i <= s1.size(); ++i) {
- for (std::size_t j = 0; j <= s2.size(); ++j) {
- if (i == 0) {
- dp[i][j] = j;
- } else if (j == 0) {
- dp[i][j] = i;
- } else if (s1[i - 1] == s2[j - 1]) {
- dp[i][j] = dp[i - 1][j - 1];
- } else {
- dp[i][j] = 1 + std::min<std::size_t>({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
- }
- }
- }
-
- return dp[s1.size()][s2.size()];
-}
-
-template <typename ValueType>
-std::string get_most_similar_string(const std::map<std::string, ValueType> &map,
- const std::string &input) {
- std::string most_similar{};
- std::size_t min_distance = (std::numeric_limits<std::size_t>::max)();
-
- for (const auto &entry : map) {
- std::size_t distance = get_levenshtein_distance(entry.first, input);
- if (distance < min_distance) {
- min_distance = distance;
- most_similar = entry.first;
- }
- }
-
- return most_similar;
-}
-
-} // namespace details
-
-enum class nargs_pattern { optional, any, at_least_one };
-
-enum class default_arguments : unsigned int {
- none = 0,
- help = 1,
- version = 2,
- all = help | version,
-};
-
-inline default_arguments operator&(const default_arguments &a,
- const default_arguments &b) {
- return static_cast<default_arguments>(
- static_cast<std::underlying_type<default_arguments>::type>(a) &
- static_cast<std::underlying_type<default_arguments>::type>(b));
-}
-
-class ArgumentParser;
-
-class Argument {
- friend class ArgumentParser;
- friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
- -> std::ostream &;
-
- template <std::size_t N, std::size_t... I>
- explicit Argument(std::string_view prefix_chars,
- std::array<std::string_view, N> &&a,
- std::index_sequence<I...> /*unused*/)
- : m_accepts_optional_like_value(false),
- m_is_optional((is_optional(a[I], prefix_chars) || ...)),
- m_is_required(false), m_is_repeatable(false), m_is_used(false),
- m_is_hidden(false), m_prefix_chars(prefix_chars) {
- ((void)m_names.emplace_back(a[I]), ...);
- std::sort(
- m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
- return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
- });
- }
-
-public:
- template <std::size_t N>
- explicit Argument(std::string_view prefix_chars,
- std::array<std::string_view, N> &&a)
- : Argument(prefix_chars, std::move(a), std::make_index_sequence<N>{}) {}
-
- Argument &help(std::string help_text) {
- m_help = std::move(help_text);
- return *this;
- }
-
- Argument &metavar(std::string metavar) {
- m_metavar = std::move(metavar);
- return *this;
- }
-
- template <typename T> Argument &default_value(T &&value) {
- m_num_args_range = NArgsRange{0, m_num_args_range.get_max()};
- m_default_value_repr = details::repr(value);
-
- if constexpr (std::is_convertible_v<T, std::string_view>) {
- m_default_value_str = std::string{std::string_view{value}};
- } else if constexpr (details::can_invoke_to_string<T>::value) {
- m_default_value_str = std::to_string(value);
- }
-
- m_default_value = std::forward<T>(value);
- return *this;
- }
-
- Argument &default_value(const char *value) {
- return default_value(std::string(value));
- }
-
- Argument &required() {
- m_is_required = true;
- return *this;
- }
-
- Argument &implicit_value(std::any value) {
- m_implicit_value = std::move(value);
- m_num_args_range = NArgsRange{0, 0};
- return *this;
- }
-
- // This is shorthand for:
- // program.add_argument("foo")
- // .default_value(false)
- // .implicit_value(true)
- Argument &flag() {
- default_value(false);
- implicit_value(true);
- return *this;
- }
-
- template <class F, class... Args>
- auto action(F &&callable, Args &&... bound_args)
- -> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
- Argument &> {
- using action_type = std::conditional_t<
- std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
- void_action, valued_action>;
- if constexpr (sizeof...(Args) == 0) {
- m_actions.emplace_back<action_type>(std::forward<F>(callable));
- } else {
- m_actions.emplace_back<action_type>(
- [f = std::forward<F>(callable),
- tup = std::make_tuple(std::forward<Args>(bound_args)...)](
- std::string const &opt) mutable {
- return details::apply_plus_one(f, tup, opt);
- });
- }
- return *this;
- }
-
- auto &store_into(bool &var) {
- if ((!m_default_value.has_value()) && (!m_implicit_value.has_value())) {
- flag();
- }
- if (m_default_value.has_value()) {
- var = std::any_cast<bool>(m_default_value);
- }
- action([&var](const auto & /*unused*/) {
- var = true;
- return var;
- });
- return *this;
- }
-
- template <typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
- auto &store_into(T &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<T>(m_default_value);
- }
- action([&var](const auto &s) {
- var = details::parse_number<T, details::radix_10>()(s);
- return var;
- });
- return *this;
- }
-
- template <typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
- auto &store_into(T &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<T>(m_default_value);
- }
- action([&var](const auto &s) {
- var = details::parse_number<T, details::chars_format::general>()(s);
- return var;
- });
- return *this;
- }
-
- auto &store_into(std::string &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::string>(m_default_value);
- }
- action([&var](const std::string &s) {
- var = s;
- return var;
- });
- return *this;
- }
-
- auto &store_into(std::filesystem::path &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::filesystem::path>(m_default_value);
- }
- action([&var](const std::string &s) { var = s; });
- return *this;
- }
-
- auto &store_into(std::vector<std::string> &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::vector<std::string>>(m_default_value);
- }
- action([this, &var](const std::string &s) {
- if (!m_is_used) {
- var.clear();
- }
- m_is_used = true;
- var.push_back(s);
- return var;
- });
- return *this;
- }
-
- auto &store_into(std::vector<int> &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::vector<int>>(m_default_value);
- }
- action([this, &var](const std::string &s) {
- if (!m_is_used) {
- var.clear();
- }
- m_is_used = true;
- var.push_back(details::parse_number<int, details::radix_10>()(s));
- return var;
- });
- return *this;
- }
-
- auto &store_into(std::set<std::string> &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::set<std::string>>(m_default_value);
- }
- action([this, &var](const std::string &s) {
- if (!m_is_used) {
- var.clear();
- }
- m_is_used = true;
- var.insert(s);
- return var;
- });
- return *this;
- }
-
- auto &store_into(std::set<int> &var) {
- if (m_default_value.has_value()) {
- var = std::any_cast<std::set<int>>(m_default_value);
- }
- action([this, &var](const std::string &s) {
- if (!m_is_used) {
- var.clear();
- }
- m_is_used = true;
- var.insert(details::parse_number<int, details::radix_10>()(s));
- return var;
- });
- return *this;
- }
-
- auto &append() {
- m_is_repeatable = true;
- return *this;
- }
-
- // Cause the argument to be invisible in usage and help
- auto &hidden() {
- m_is_hidden = true;
- return *this;
- }
-
- template <char Shape, typename T>
- auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
- static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
- "T should not be cv-qualified");
- auto is_one_of = [](char c, auto... x) constexpr {
- return ((c == x) || ...);
- };
-
- if constexpr (is_one_of(Shape, 'd') && details::standard_integer<T>) {
- action(details::parse_number<T, details::radix_10>());
- } else if constexpr (is_one_of(Shape, 'i') &&
- details::standard_integer<T>) {
- action(details::parse_number<T>());
- } else if constexpr (is_one_of(Shape, 'u') &&
- details::standard_unsigned_integer<T>) {
- action(details::parse_number<T, details::radix_10>());
- } else if constexpr (is_one_of(Shape, 'b') &&
- details::standard_unsigned_integer<T>) {
- action(details::parse_number<T, details::radix_2>());
- } else if constexpr (is_one_of(Shape, 'o') &&
- details::standard_unsigned_integer<T>) {
- action(details::parse_number<T, details::radix_8>());
- } else if constexpr (is_one_of(Shape, 'x', 'X') &&
- details::standard_unsigned_integer<T>) {
- action(details::parse_number<T, details::radix_16>());
- } else if constexpr (is_one_of(Shape, 'a', 'A') &&
- std::is_floating_point_v<T>) {
- action(details::parse_number<T, details::chars_format::hex>());
- } else if constexpr (is_one_of(Shape, 'e', 'E') &&
- std::is_floating_point_v<T>) {
- action(details::parse_number<T, details::chars_format::scientific>());
- } else if constexpr (is_one_of(Shape, 'f', 'F') &&
- std::is_floating_point_v<T>) {
- action(details::parse_number<T, details::chars_format::fixed>());
- } else if constexpr (is_one_of(Shape, 'g', 'G') &&
- std::is_floating_point_v<T>) {
- action(details::parse_number<T, details::chars_format::general>());
- } else {
- static_assert(alignof(T) == 0, "No scan specification for T");
- }
-
- return *this;
- }
-
- Argument &nargs(std::size_t num_args) {
- m_num_args_range = NArgsRange{num_args, num_args};
- return *this;
- }
-
- Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
- m_num_args_range = NArgsRange{num_args_min, num_args_max};
- return *this;
- }
-
- Argument &nargs(nargs_pattern pattern) {
- switch (pattern) {
- case nargs_pattern::optional:
- m_num_args_range = NArgsRange{0, 1};
- break;
- case nargs_pattern::any:
- m_num_args_range =
- NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
- break;
- case nargs_pattern::at_least_one:
- m_num_args_range =
- NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
- break;
- }
- return *this;
- }
-
- Argument &remaining() {
- m_accepts_optional_like_value = true;
- return nargs(nargs_pattern::any);
- }
-
- template <typename T> void add_choice(T &&choice) {
- static_assert(details::IsChoiceTypeSupported<T>::value,
- "Only string or integer type supported for choice");
- static_assert(std::is_convertible_v<T, std::string_view> ||
- details::can_invoke_to_string<T>::value,
- "Choice is not convertible to string_type");
- if (!m_choices.has_value()) {
- m_choices = std::vector<std::string>{};
- }
-
- if constexpr (std::is_convertible_v<T, std::string_view>) {
- m_choices.value().push_back(
- std::string{std::string_view{std::forward<T>(choice)}});
- } else if constexpr (details::can_invoke_to_string<T>::value) {
- m_choices.value().push_back(std::to_string(std::forward<T>(choice)));
- }
- }
-
- Argument &choices() {
- if (!m_choices.has_value()) {
- throw std::runtime_error("Zero choices provided");
- }
- return *this;
- }
-
- template <typename T, typename... U>
- Argument &choices(T &&first, U &&... rest) {
- add_choice(std::forward<T>(first));
- choices(std::forward<U>(rest)...);
- return *this;
- }
-
- void find_default_value_in_choices_or_throw() const {
-
- const auto &choices = m_choices.value();
-
- if (m_default_value.has_value()) {
- if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
- choices.end()) {
- // provided arg not in list of allowed choices
- // report error
-
- std::string choices_as_csv =
- std::accumulate(choices.begin(), choices.end(), std::string(),
- [](const std::string &a, const std::string &b) {
- return a + (a.empty() ? "" : ", ") + b;
- });
-
- throw std::runtime_error(
- std::string{"Invalid default value "} + m_default_value_repr +
- " - allowed options: {" + choices_as_csv + "}");
- }
- }
- }
-
- template <typename Iterator>
- bool is_value_in_choices(Iterator option_it) const {
-
- const auto &choices = m_choices.value();
-
- return (std::find(choices.begin(), choices.end(), *option_it) !=
- choices.end());
- }
-
- template <typename Iterator>
- void throw_invalid_arguments_error(Iterator option_it) const {
- const auto &choices = m_choices.value();
- const std::string choices_as_csv = std::accumulate(
- choices.begin(), choices.end(), std::string(),
- [](const std::string &option_a, const std::string &option_b) {
- return option_a + (option_a.empty() ? "" : ", ") + option_b;
- });
-
- throw std::runtime_error(std::string{"Invalid argument "} +
- details::repr(*option_it) +
- " - allowed options: {" + choices_as_csv + "}");
- }
-
- /* The dry_run parameter can be set to true to avoid running the actions,
- * and setting m_is_used. This may be used by a pre-processing step to do
- * a first iteration over arguments.
- */
- template <typename Iterator>
- Iterator consume(Iterator start, Iterator end,
- std::string_view used_name = {}, bool dry_run = false) {
- if (!m_is_repeatable && m_is_used) {
- throw std::runtime_error(
- std::string("Duplicate argument ").append(used_name));
- }
- m_used_name = used_name;
-
- std::size_t passed_options = 0;
-
- if (m_choices.has_value()) {
- // Check each value in (start, end) and make sure
- // it is in the list of allowed choices/options
- const auto max_number_of_args = m_num_args_range.get_max();
- const auto min_number_of_args = m_num_args_range.get_min();
- for (auto it = start; it != end; ++it) {
- if (is_value_in_choices(it)) {
- passed_options += 1;
- continue;
- }
-
- if ((passed_options >= min_number_of_args) &&
- (passed_options <= max_number_of_args)) {
- break;
- }
-
- throw_invalid_arguments_error(it);
- }
- }
-
- const auto num_args_max =
- (m_choices.has_value()) ? passed_options : m_num_args_range.get_max();
- const auto num_args_min = m_num_args_range.get_min();
- std::size_t dist = 0;
- if (num_args_max == 0) {
- if (!dry_run) {
- m_values.emplace_back(m_implicit_value);
- for(auto &action: m_actions) {
- std::visit([&](const auto &f) { f({}); }, action);
- }
- if(m_actions.empty()){
- std::visit([&](const auto &f) { f({}); }, m_default_action);
- }
- m_is_used = true;
- }
- return start;
- }
- if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
- num_args_min) {
- if (num_args_max < dist) {
- end = std::next(start, static_cast<typename Iterator::difference_type>(
- num_args_max));
- }
- if (!m_accepts_optional_like_value) {
- end = std::find_if(
- start, end,
- std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
- dist = static_cast<std::size_t>(std::distance(start, end));
- if (dist < num_args_min) {
- throw std::runtime_error("Too few arguments for '" +
- std::string(m_used_name) + "'.");
- }
- }
- struct ActionApply {
- void operator()(valued_action &f) {
- std::transform(first, last, std::back_inserter(self.m_values), f);
- }
-
- void operator()(void_action &f) {
- std::for_each(first, last, f);
- if (!self.m_default_value.has_value()) {
- if (!self.m_accepts_optional_like_value) {
- self.m_values.resize(
- static_cast<std::size_t>(std::distance(first, last)));
- }
- }
- }
-
- Iterator first, last;
- Argument &self;
- };
- if (!dry_run) {
- for(auto &action: m_actions) {
- std::visit(ActionApply{start, end, *this}, action);
- }
- if(m_actions.empty()){
- std::visit(ActionApply{start, end, *this}, m_default_action);
- }
- m_is_used = true;
- }
- return end;
- }
- if (m_default_value.has_value()) {
- if (!dry_run) {
- m_is_used = true;
- }
- return start;
- }
- throw std::runtime_error("Too few arguments for '" +
- std::string(m_used_name) + "'.");
- }
-
- /*
- * @throws std::runtime_error if argument values are not valid
- */
- void validate() const {
- if (m_is_optional) {
- // TODO: check if an implicit value was programmed for this argument
- if (!m_is_used && !m_default_value.has_value() && m_is_required) {
- throw_required_arg_not_used_error();
- }
- if (m_is_used && m_is_required && m_values.empty()) {
- throw_required_arg_no_value_provided_error();
- }
- } else {
- if (!m_num_args_range.contains(m_values.size()) &&
- !m_default_value.has_value()) {
- throw_nargs_range_validation_error();
- }
- }
-
- if (m_choices.has_value()) {
- // Make sure the default value (if provided)
- // is in the list of choices
- find_default_value_in_choices_or_throw();
- }
- }
-
- std::string get_names_csv(char separator = ',') const {
- return std::accumulate(
- m_names.begin(), m_names.end(), std::string{""},
- [&](const std::string &result, const std::string &name) {
- return result.empty() ? name : result + separator + name;
- });
- }
-
- std::string get_usage_full() const {
- std::stringstream usage;
-
- usage << get_names_csv('/');
- const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
- if (m_num_args_range.get_max() > 0) {
- usage << " " << metavar;
- if (m_num_args_range.get_max() > 1) {
- usage << "...";
- }
- }
- return usage.str();
- }
-
- std::string get_inline_usage() const {
- std::stringstream usage;
- // Find the longest variant to show in the usage string
- std::string longest_name = m_names.front();
- for (const auto &s : m_names) {
- if (s.size() > longest_name.size()) {
- longest_name = s;
- }
- }
- if (!m_is_required) {
- usage << "[";
- }
- usage << longest_name;
- const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
- if (m_num_args_range.get_max() > 0) {
- usage << " " << metavar;
- if (m_num_args_range.get_max() > 1 &&
- m_metavar.find("> <") == std::string::npos) {
- usage << "...";
- }
- }
- if (!m_is_required) {
- usage << "]";
- }
- if (m_is_repeatable) {
- usage << "...";
- }
- return usage.str();
- }
-
- std::size_t get_arguments_length() const {
-
- std::size_t names_size = std::accumulate(
- std::begin(m_names), std::end(m_names), std::size_t(0),
- [](const auto &sum, const auto &s) { return sum + s.size(); });
-
- if (is_positional(m_names.front(), m_prefix_chars)) {
- // A set metavar means this replaces the names
- if (!m_metavar.empty()) {
- // Indent and metavar
- return 2 + m_metavar.size();
- }
-
- // Indent and space-separated
- return 2 + names_size + (m_names.size() - 1);
- }
- // Is an option - include both names _and_ metavar
- // size = text + (", " between names)
- std::size_t size = names_size + 2 * (m_names.size() - 1);
- if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
- size += m_metavar.size() + 1;
- }
- return size + 2; // indent
- }
-
- friend std::ostream &operator<<(std::ostream &stream,
- const Argument &argument) {
- std::stringstream name_stream;
- name_stream << " "; // indent
- if (argument.is_positional(argument.m_names.front(),
- argument.m_prefix_chars)) {
- if (!argument.m_metavar.empty()) {
- name_stream << argument.m_metavar;
- } else {
- name_stream << details::join(argument.m_names.begin(),
- argument.m_names.end(), " ");
- }
- } else {
- name_stream << details::join(argument.m_names.begin(),
- argument.m_names.end(), ", ");
- // If we have a metavar, and one narg - print the metavar
- if (!argument.m_metavar.empty() &&
- argument.m_num_args_range == NArgsRange{1, 1}) {
- name_stream << " " << argument.m_metavar;
- }
- else if (!argument.m_metavar.empty() &&
- argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() &&
- argument.m_metavar.find("> <") != std::string::npos) {
- name_stream << " " << argument.m_metavar;
- }
- }
-
- // align multiline help message
- auto stream_width = stream.width();
- auto name_padding = std::string(name_stream.str().size(), ' ');
- auto pos = std::string::size_type{};
- auto prev = std::string::size_type{};
- auto first_line = true;
- auto hspace = " "; // minimal space between name and help message
- stream << name_stream.str();
- std::string_view help_view(argument.m_help);
- while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
- auto line = help_view.substr(prev, pos - prev + 1);
- if (first_line) {
- stream << hspace << line;
- first_line = false;
- } else {
- stream.width(stream_width);
- stream << name_padding << hspace << line;
- }
- prev += pos - prev + 1;
- }
- if (first_line) {
- stream << hspace << argument.m_help;
- } else {
- auto leftover = help_view.substr(prev, argument.m_help.size() - prev);
- if (!leftover.empty()) {
- stream.width(stream_width);
- stream << name_padding << hspace << leftover;
- }
- }
-
- // print nargs spec
- if (!argument.m_help.empty()) {
- stream << " ";
- }
- stream << argument.m_num_args_range;
-
- bool add_space = false;
- if (argument.m_default_value.has_value() &&
- argument.m_num_args_range != NArgsRange{0, 0}) {
- stream << "[default: " << argument.m_default_value_repr << "]";
- add_space = true;
- } else if (argument.m_is_required) {
- stream << "[required]";
- add_space = true;
- }
- if (argument.m_is_repeatable) {
- if (add_space) {
- stream << " ";
- }
- stream << "[may be repeated]";
- }
- stream << "\n";
- return stream;
- }
-
- template <typename T> bool operator!=(const T &rhs) const {
- return !(*this == rhs);
- }
-
- /*
- * Compare to an argument value of known type
- * @throws std::logic_error in case of incompatible types
- */
- template <typename T> bool operator==(const T &rhs) const {
- if constexpr (!details::IsContainer<T>) {
- return get<T>() == rhs;
- } else {
- using ValueType = typename T::value_type;
- auto lhs = get<T>();
- return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
- std::end(rhs), [](const auto &a, const auto &b) {
- return std::any_cast<const ValueType &>(a) == b;
- });
- }
- }
-
- /*
- * positional:
- * _empty_
- * '-'
- * '-' decimal-literal
- * !'-' anything
- */
- static bool is_positional(std::string_view name,
- std::string_view prefix_chars) {
- auto first = lookahead(name);
-
- if (first == eof) {
- return true;
- }
- if (prefix_chars.find(static_cast<char>(first)) !=
- std::string_view::npos) {
- name.remove_prefix(1);
- if (name.empty()) {
- return true;
- }
- return is_decimal_literal(name);
- }
- return true;
- }
-
-private:
- class NArgsRange {
- std::size_t m_min;
- std::size_t m_max;
-
- public:
- NArgsRange(std::size_t minimum, std::size_t maximum)
- : m_min(minimum), m_max(maximum) {
- if (minimum > maximum) {
- throw std::logic_error("Range of number of arguments is invalid");
- }
- }
-
- bool contains(std::size_t value) const {
- return value >= m_min && value <= m_max;
- }
-
- bool is_exact() const { return m_min == m_max; }
-
- bool is_right_bounded() const {
- return m_max < (std::numeric_limits<std::size_t>::max)();
- }
-
- std::size_t get_min() const { return m_min; }
-
- std::size_t get_max() const { return m_max; }
-
- // Print help message
- friend auto operator<<(std::ostream &stream, const NArgsRange &range)
- -> std::ostream & {
- if (range.m_min == range.m_max) {
- if (range.m_min != 0 && range.m_min != 1) {
- stream << "[nargs: " << range.m_min << "] ";
- }
- } else {
- if (range.m_max == (std::numeric_limits<std::size_t>::max)()) {
- stream << "[nargs: " << range.m_min << " or more] ";
- } else {
- stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
- }
- }
- return stream;
- }
-
- bool operator==(const NArgsRange &rhs) const {
- return rhs.m_min == m_min && rhs.m_max == m_max;
- }
-
- bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
- };
-
- void throw_nargs_range_validation_error() const {
- std::stringstream stream;
- if (!m_used_name.empty()) {
- stream << m_used_name << ": ";
- } else {
- stream << m_names.front() << ": ";
- }
- if (m_num_args_range.is_exact()) {
- stream << m_num_args_range.get_min();
- } else if (m_num_args_range.is_right_bounded()) {
- stream << m_num_args_range.get_min() << " to "
- << m_num_args_range.get_max();
- } else {
- stream << m_num_args_range.get_min() << " or more";
- }
- stream << " argument(s) expected. " << m_values.size() << " provided.";
- throw std::runtime_error(stream.str());
- }
-
- void throw_required_arg_not_used_error() const {
- std::stringstream stream;
- stream << m_names.front() << ": required.";
- throw std::runtime_error(stream.str());
- }
-
- void throw_required_arg_no_value_provided_error() const {
- std::stringstream stream;
- stream << m_used_name << ": no value provided.";
- throw std::runtime_error(stream.str());
- }
-
- static constexpr int eof = std::char_traits<char>::eof();
-
- static auto lookahead(std::string_view s) -> int {
- if (s.empty()) {
- return eof;
- }
- return static_cast<int>(static_cast<unsigned char>(s[0]));
- }
-
- /*
- * decimal-literal:
- * '0'
- * nonzero-digit digit-sequence_opt
- * integer-part fractional-part
- * fractional-part
- * integer-part '.' exponent-part_opt
- * integer-part exponent-part
- *
- * integer-part:
- * digit-sequence
- *
- * fractional-part:
- * '.' post-decimal-point
- *
- * post-decimal-point:
- * digit-sequence exponent-part_opt
- *
- * exponent-part:
- * 'e' post-e
- * 'E' post-e
- *
- * post-e:
- * sign_opt digit-sequence
- *
- * sign: one of
- * '+' '-'
- */
- static bool is_decimal_literal(std::string_view s) {
- auto is_digit = [](auto c) constexpr {
- switch (c) {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- return true;
- default:
- return false;
- }
- };
-
- // precondition: we have consumed or will consume at least one digit
- auto consume_digits = [=](std::string_view sd) {
- // NOLINTNEXTLINE(readability-qualified-auto)
- auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit);
- return sd.substr(static_cast<std::size_t>(it - std::begin(sd)));
- };
-
- switch (lookahead(s)) {
- case '0': {
- s.remove_prefix(1);
- if (s.empty()) {
- return true;
- }
- goto integer_part;
- }
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9': {
- s = consume_digits(s);
- if (s.empty()) {
- return true;
- }
- goto integer_part_consumed;
- }
- case '.': {
- s.remove_prefix(1);
- goto post_decimal_point;
- }
- default:
- return false;
- }
-
- integer_part:
- s = consume_digits(s);
- integer_part_consumed:
- switch (lookahead(s)) {
- case '.': {
- s.remove_prefix(1);
- if (is_digit(lookahead(s))) {
- goto post_decimal_point;
- } else {
- goto exponent_part_opt;
- }
- }
- case 'e':
- case 'E': {
- s.remove_prefix(1);
- goto post_e;
- }
- default:
- return false;
- }
-
- post_decimal_point:
- if (is_digit(lookahead(s))) {
- s = consume_digits(s);
- goto exponent_part_opt;
- }
- return false;
-
- exponent_part_opt:
- switch (lookahead(s)) {
- case eof:
- return true;
- case 'e':
- case 'E': {
- s.remove_prefix(1);
- goto post_e;
- }
- default:
- return false;
- }
-
- post_e:
- switch (lookahead(s)) {
- case '-':
- case '+':
- s.remove_prefix(1);
- }
- if (is_digit(lookahead(s))) {
- s = consume_digits(s);
- return s.empty();
- }
- return false;
- }
-
- static bool is_optional(std::string_view name,
- std::string_view prefix_chars) {
- return !is_positional(name, prefix_chars);
- }
-
- /*
- * Get argument value given a type
- * @throws std::logic_error in case of incompatible types
- */
- template <typename T> T get() const {
- if (!m_values.empty()) {
- if constexpr (details::IsContainer<T>) {
- return any_cast_container<T>(m_values);
- } else {
- return std::any_cast<T>(m_values.front());
- }
- }
- if (m_default_value.has_value()) {
- return std::any_cast<T>(m_default_value);
- }
- if constexpr (details::IsContainer<T>) {
- if (!m_accepts_optional_like_value) {
- return any_cast_container<T>(m_values);
- }
- }
-
- throw std::logic_error("No value provided for '" + m_names.back() + "'.");
- }
-
- /*
- * Get argument value given a type.
- * @pre The object has no default value.
- * @returns The stored value if any, std::nullopt otherwise.
- */
- template <typename T> auto present() const -> std::optional<T> {
- if (m_default_value.has_value()) {
- throw std::logic_error("Argument with default value always presents");
- }
- if (m_values.empty()) {
- return std::nullopt;
- }
- if constexpr (details::IsContainer<T>) {
- return any_cast_container<T>(m_values);
- }
- return std::any_cast<T>(m_values.front());
- }
-
- template <typename T>
- static auto any_cast_container(const std::vector<std::any> &operand) -> T {
- using ValueType = typename T::value_type;
-
- T result;
- std::transform(
- std::begin(operand), std::end(operand), std::back_inserter(result),
- [](const auto &value) { return std::any_cast<ValueType>(value); });
- return result;
- }
-
- void set_usage_newline_counter(int i) { m_usage_newline_counter = i; }
-
- void set_group_idx(std::size_t i) { m_group_idx = i; }
-
- std::vector<std::string> m_names;
- std::string_view m_used_name;
- std::string m_help;
- std::string m_metavar;
- std::any m_default_value;
- std::string m_default_value_repr;
- std::optional<std::string>
- m_default_value_str; // used for checking default_value against choices
- std::any m_implicit_value;
- std::optional<std::vector<std::string>> m_choices{std::nullopt};
- using valued_action = std::function<std::any(const std::string &)>;
- using void_action = std::function<void(const std::string &)>;
- std::vector<std::variant<valued_action, void_action>> m_actions;
- std::variant<valued_action, void_action> m_default_action{
- std::in_place_type<valued_action>,
- [](const std::string &value) { return value; }};
- std::vector<std::any> m_values;
- NArgsRange m_num_args_range{1, 1};
- // Bit field of bool values. Set default value in ctor.
- bool m_accepts_optional_like_value : 1;
- bool m_is_optional : 1;
- bool m_is_required : 1;
- bool m_is_repeatable : 1;
- bool m_is_used : 1;
- bool m_is_hidden : 1; // if set, does not appear in usage or help
- std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
- int m_usage_newline_counter = 0;
- std::size_t m_group_idx = 0;
-};
-
-class ArgumentParser {
-public:
- explicit ArgumentParser(std::string program_name = {},
- std::string version = "1.0",
- default_arguments add_args = default_arguments::all,
- bool exit_on_default_arguments = true,
- std::ostream &os = std::cout)
- : m_program_name(std::move(program_name)), m_version(std::move(version)),
- m_exit_on_default_arguments(exit_on_default_arguments),
- m_parser_path(m_program_name) {
- if ((add_args & default_arguments::help) == default_arguments::help) {
- add_argument("-h", "--help")
- .action([&](const auto & /*unused*/) {
- os << help().str();
- if (m_exit_on_default_arguments) {
- std::exit(0);
- }
- })
- .default_value(false)
- .help("shows help message and exits")
- .implicit_value(true)
- .nargs(0);
- }
- if ((add_args & default_arguments::version) == default_arguments::version) {
- add_argument("-v", "--version")
- .action([&](const auto & /*unused*/) {
- os << m_version << std::endl;
- if (m_exit_on_default_arguments) {
- std::exit(0);
- }
- })
- .default_value(false)
- .help("prints version information and exits")
- .implicit_value(true)
- .nargs(0);
- }
- }
-
- ~ArgumentParser() = default;
-
- // ArgumentParser is meant to be used in a single function.
- // Setup everything and parse arguments in one place.
- //
- // ArgumentParser internally uses std::string_views,
- // references, iterators, etc.
- // Many of these elements become invalidated after a copy or move.
- ArgumentParser(const ArgumentParser &other) = delete;
- ArgumentParser &operator=(const ArgumentParser &other) = delete;
- ArgumentParser(ArgumentParser &&) noexcept = delete;
- ArgumentParser &operator=(ArgumentParser &&) = delete;
-
- explicit operator bool() const {
- auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
- [](auto &it) { return it.second->m_is_used; });
- auto subparser_used =
- std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
- [](auto &it) { return it.second; });
-
- return m_is_parsed && (arg_used || subparser_used);
- }
-
- // Parameter packing
- // Call add_argument with variadic number of string arguments
- template <typename... Targs> Argument &add_argument(Targs... f_args) {
- using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
- auto argument =
- m_optional_arguments.emplace(std::cend(m_optional_arguments),
- m_prefix_chars, array_of_sv{f_args...});
-
- if (!argument->m_is_optional) {
- m_positional_arguments.splice(std::cend(m_positional_arguments),
- m_optional_arguments, argument);
- }
- argument->set_usage_newline_counter(m_usage_newline_counter);
- argument->set_group_idx(m_group_names.size());
-
- index_argument(argument);
- return *argument;
- }
-
- class MutuallyExclusiveGroup {
- friend class ArgumentParser;
-
- public:
- MutuallyExclusiveGroup() = delete;
-
- explicit MutuallyExclusiveGroup(ArgumentParser &parent,
- bool required = false)
- : m_parent(parent), m_required(required), m_elements({}) {}
-
- MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
- MutuallyExclusiveGroup &
- operator=(const MutuallyExclusiveGroup &other) = delete;
-
- MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
- : m_parent(other.m_parent), m_required(other.m_required),
- m_elements(std::move(other.m_elements)) {
- other.m_elements.clear();
- }
-
- template <typename... Targs> Argument &add_argument(Targs... f_args) {
- auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
- m_elements.push_back(&argument);
- argument.set_usage_newline_counter(m_parent.m_usage_newline_counter);
- argument.set_group_idx(m_parent.m_group_names.size());
- return argument;
- }
-
- private:
- ArgumentParser &m_parent;
- bool m_required{false};
- std::vector<Argument *> m_elements{};
- };
-
- MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
- m_mutually_exclusive_groups.emplace_back(*this, required);
- return m_mutually_exclusive_groups.back();
- }
-
- // Parameter packed add_parents method
- // Accepts a variadic number of ArgumentParser objects
- template <typename... Targs>
- ArgumentParser &add_parents(const Targs &... f_args) {
- for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
- for (const auto &argument : parent_parser.m_positional_arguments) {
- auto it = m_positional_arguments.insert(
- std::cend(m_positional_arguments), argument);
- index_argument(it);
- }
- for (const auto &argument : parent_parser.m_optional_arguments) {
- auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
- argument);
- index_argument(it);
- }
- }
- return *this;
- }
-
- // Ask for the next optional arguments to be displayed on a separate
- // line in usage() output. Only effective if set_usage_max_line_width() is
- // also used.
- ArgumentParser &add_usage_newline() {
- ++m_usage_newline_counter;
- return *this;
- }
-
- // Ask for the next optional arguments to be displayed in a separate section
- // in usage() and help (<< *this) output.
- // For usage(), this is only effective if set_usage_max_line_width() is
- // also used.
- ArgumentParser &add_group(std::string group_name) {
- m_group_names.emplace_back(std::move(group_name));
- return *this;
- }
-
- ArgumentParser &add_description(std::string description) {
- m_description = std::move(description);
- return *this;
- }
-
- ArgumentParser &add_epilog(std::string epilog) {
- m_epilog = std::move(epilog);
- return *this;
- }
-
- // Add a un-documented/hidden alias for an argument.
- // Ideally we'd want this to be a method of Argument, but Argument
- // does not own its owing ArgumentParser.
- ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) {
- for (auto it = m_optional_arguments.begin();
- it != m_optional_arguments.end(); ++it) {
- if (&(*it) == &arg) {
- m_argument_map.insert_or_assign(std::string(alias), it);
- return *this;
- }
- }
- throw std::logic_error(
- "Argument is not an optional argument of this parser");
- }
-
- /* Getter for arguments and subparsers.
- * @throws std::logic_error in case of an invalid argument or subparser name
- */
- template <typename T = Argument> T &at(std::string_view name) {
- if constexpr (std::is_same_v<T, Argument>) {
- return (*this)[name];
- } else {
- std::string str_name(name);
- auto subparser_it = m_subparser_map.find(str_name);
- if (subparser_it != m_subparser_map.end()) {
- return subparser_it->second->get();
- }
- throw std::logic_error("No such subparser: " + str_name);
- }
- }
-
- ArgumentParser &set_prefix_chars(std::string prefix_chars) {
- m_prefix_chars = std::move(prefix_chars);
- return *this;
- }
-
- ArgumentParser &set_assign_chars(std::string assign_chars) {
- m_assign_chars = std::move(assign_chars);
- return *this;
- }
-
- /* Call parse_args_internal - which does all the work
- * Then, validate the parsed arguments
- * This variant is used mainly for testing
- * @throws std::runtime_error in case of any invalid argument
- */
- void parse_args(const std::vector<std::string> &arguments) {
- parse_args_internal(arguments);
- // Check if all arguments are parsed
- for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
- argument->validate();
- }
-
- // Check each mutually exclusive group and make sure
- // there are no constraint violations
- for (const auto &group : m_mutually_exclusive_groups) {
- auto mutex_argument_used{false};
- Argument *mutex_argument_it{nullptr};
- for (Argument *arg : group.m_elements) {
- if (!mutex_argument_used && arg->m_is_used) {
- mutex_argument_used = true;
- mutex_argument_it = arg;
- } else if (mutex_argument_used && arg->m_is_used) {
- // Violation
- throw std::runtime_error("Argument '" + arg->get_usage_full() +
- "' not allowed with '" +
- mutex_argument_it->get_usage_full() + "'");
- }
- }
-
- if (!mutex_argument_used && group.m_required) {
- // at least one argument from the group is
- // required
- std::string argument_names{};
- std::size_t i = 0;
- std::size_t size = group.m_elements.size();
- for (Argument *arg : group.m_elements) {
- if (i + 1 == size) {
- // last
- argument_names += std::string("'") + arg->get_usage_full() + std::string("' ");
- } else {
- argument_names += std::string("'") + arg->get_usage_full() + std::string("' or ");
- }
- i += 1;
- }
- throw std::runtime_error("One of the arguments " + argument_names +
- "is required");
- }
- }
- }
-
- /* Call parse_known_args_internal - which does all the work
- * Then, validate the parsed arguments
- * This variant is used mainly for testing
- * @throws std::runtime_error in case of any invalid argument
- */
- std::vector<std::string>
- parse_known_args(const std::vector<std::string> &arguments) {
- auto unknown_arguments = parse_known_args_internal(arguments);
- // Check if all arguments are parsed
- for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
- argument->validate();
- }
- return unknown_arguments;
- }
-
- /* Main entry point for parsing command-line arguments using this
- * ArgumentParser
- * @throws std::runtime_error in case of any invalid argument
- */
- // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
- void parse_args(int argc, const char *const argv[]) {
- parse_args({argv, argv + argc});
- }
-
- /* Main entry point for parsing command-line arguments using this
- * ArgumentParser
- * @throws std::runtime_error in case of any invalid argument
- */
- // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
- auto parse_known_args(int argc, const char *const argv[]) {
- return parse_known_args({argv, argv + argc});
- }
-
- /* Getter for options with default values.
- * @throws std::logic_error if parse_args() has not been previously called
- * @throws std::logic_error if there is no such option
- * @throws std::logic_error if the option has no value
- * @throws std::bad_any_cast if the option is not of type T
- */
- template <typename T = std::string> T get(std::string_view arg_name) const {
- if (!m_is_parsed) {
- throw std::logic_error("Nothing parsed, no arguments are available.");
- }
- return (*this)[arg_name].get<T>();
- }
-
- /* Getter for options without default values.
- * @pre The option has no default value.
- * @throws std::logic_error if there is no such option
- * @throws std::bad_any_cast if the option is not of type T
- */
- template <typename T = std::string>
- auto present(std::string_view arg_name) const -> std::optional<T> {
- return (*this)[arg_name].present<T>();
- }
-
- /* Getter that returns true for user-supplied options. Returns false if not
- * user-supplied, even with a default value.
- */
- auto is_used(std::string_view arg_name) const {
- return (*this)[arg_name].m_is_used;
- }
-
- /* Getter that returns true if a subcommand is used.
- */
- auto is_subcommand_used(std::string_view subcommand_name) const {
- return m_subparser_used.at(std::string(subcommand_name));
- }
-
- /* Getter that returns true if a subcommand is used.
- */
- auto is_subcommand_used(const ArgumentParser &subparser) const {
- return is_subcommand_used(subparser.m_program_name);
- }
-
- /* Indexing operator. Return a reference to an Argument object
- * Used in conjunction with Argument.operator== e.g., parser["foo"] == true
- * @throws std::logic_error in case of an invalid argument name
- */
- Argument &operator[](std::string_view arg_name) const {
- std::string name(arg_name);
- auto it = m_argument_map.find(name);
- if (it != m_argument_map.end()) {
- return *(it->second);
- }
- if (!is_valid_prefix_char(arg_name.front())) {
- const auto legal_prefix_char = get_any_valid_prefix_char();
- const auto prefix = std::string(1, legal_prefix_char);
-
- // "-" + arg_name
- name = prefix + name;
- it = m_argument_map.find(name);
- if (it != m_argument_map.end()) {
- return *(it->second);
- }
- // "--" + arg_name
- name = prefix + name;
- it = m_argument_map.find(name);
- if (it != m_argument_map.end()) {
- return *(it->second);
- }
- }
- throw std::logic_error("No such argument: " + std::string(arg_name));
- }
-
- // Print help message
- friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
- -> std::ostream & {
- stream.setf(std::ios_base::left);
-
- auto longest_arg_length = parser.get_length_of_longest_argument();
-
- stream << parser.usage() << "\n\n";
-
- if (!parser.m_description.empty()) {
- stream << parser.m_description << "\n\n";
- }
-
- const bool has_visible_positional_args = std::find_if(
- parser.m_positional_arguments.begin(),
- parser.m_positional_arguments.end(),
- [](const auto &argument) {
- return !argument.m_is_hidden; }) !=
- parser.m_positional_arguments.end();
- if (has_visible_positional_args) {
- stream << "Positional arguments:\n";
- }
-
- for (const auto &argument : parser.m_positional_arguments) {
- if (!argument.m_is_hidden) {
- stream.width(static_cast<std::streamsize>(longest_arg_length));
- stream << argument;
- }
- }
-
- if (!parser.m_optional_arguments.empty()) {
- stream << (!has_visible_positional_args ? "" : "\n")
- << "Optional arguments:\n";
- }
-
- for (const auto &argument : parser.m_optional_arguments) {
- if (argument.m_group_idx == 0 && !argument.m_is_hidden) {
- stream.width(static_cast<std::streamsize>(longest_arg_length));
- stream << argument;
- }
- }
-
- for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) {
- stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
- for (const auto &argument : parser.m_optional_arguments) {
- if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) {
- stream.width(static_cast<std::streamsize>(longest_arg_length));
- stream << argument;
- }
- }
- }
-
- bool has_visible_subcommands = std::any_of(
- parser.m_subparser_map.begin(), parser.m_subparser_map.end(),
- [](auto &p) { return !p.second->get().m_suppress; });
-
- if (has_visible_subcommands) {
- stream << (parser.m_positional_arguments.empty()
- ? (parser.m_optional_arguments.empty() ? "" : "\n")
- : "\n")
- << "Subcommands:\n";
- for (const auto &[command, subparser] : parser.m_subparser_map) {
- if (subparser->get().m_suppress) {
- continue;
- }
-
- stream << std::setw(2) << " ";
- stream << std::setw(static_cast<int>(longest_arg_length - 2))
- << command;
- stream << " " << subparser->get().m_description << "\n";
- }
- }
-
- if (!parser.m_epilog.empty()) {
- stream << '\n';
- stream << parser.m_epilog << "\n\n";
- }
-
- return stream;
- }
-
- // Format help message
- auto help() const -> std::stringstream {
- std::stringstream out;
- out << *this;
- return out;
- }
-
- // Sets the maximum width for a line of the Usage message
- ArgumentParser &set_usage_max_line_width(size_t w) {
- this->m_usage_max_line_width = w;
- return *this;
- }
-
- // Asks to display arguments of mutually exclusive group on separate lines in
- // the Usage message
- ArgumentParser &set_usage_break_on_mutex() {
- this->m_usage_break_on_mutex = true;
- return *this;
- }
-
- // Format usage part of help only
- auto usage() const -> std::string {
- std::stringstream stream;
-
- std::string curline("Usage: ");
- curline += this->m_parser_path;
- const bool multiline_usage =
- this->m_usage_max_line_width < (std::numeric_limits<std::size_t>::max)();
- const size_t indent_size = curline.size();
-
- const auto deal_with_options_of_group = [&](std::size_t group_idx) {
- bool found_options = false;
- // Add any options inline here
- const MutuallyExclusiveGroup *cur_mutex = nullptr;
- int usage_newline_counter = -1;
- for (const auto &argument : this->m_optional_arguments) {
- if (argument.m_is_hidden) {
- continue;
- }
- if (multiline_usage) {
- if (argument.m_group_idx != group_idx) {
- continue;
- }
- if (usage_newline_counter != argument.m_usage_newline_counter) {
- if (usage_newline_counter >= 0) {
- if (curline.size() > indent_size) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- }
- usage_newline_counter = argument.m_usage_newline_counter;
- }
- }
- found_options = true;
- const std::string arg_inline_usage = argument.get_inline_usage();
- const MutuallyExclusiveGroup *arg_mutex =
- get_belonging_mutex(&argument);
- if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) {
- curline += ']';
- if (this->m_usage_break_on_mutex) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) {
- if ((this->m_usage_break_on_mutex && curline.size() > indent_size) ||
- curline.size() + 3 + arg_inline_usage.size() >
- this->m_usage_max_line_width) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- curline += " [";
- } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) {
- if (cur_mutex != arg_mutex) {
- curline += ']';
- if (this->m_usage_break_on_mutex ||
- curline.size() + 3 + arg_inline_usage.size() >
- this->m_usage_max_line_width) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- curline += " [";
- } else {
- curline += '|';
- }
- }
- cur_mutex = arg_mutex;
- if (curline.size() != indent_size &&
- curline.size() + 1 + arg_inline_usage.size() >
- this->m_usage_max_line_width) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- curline += " ";
- } else if (cur_mutex == nullptr) {
- curline += " ";
- }
- curline += arg_inline_usage;
- }
- if (cur_mutex != nullptr) {
- curline += ']';
- }
- return found_options;
- };
-
- const bool found_options = deal_with_options_of_group(0);
-
- if (found_options && multiline_usage &&
- !this->m_positional_arguments.empty()) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- // Put positional arguments after the optionals
- for (const auto &argument : this->m_positional_arguments) {
- if (argument.m_is_hidden) {
- continue;
- }
- const std::string pos_arg = !argument.m_metavar.empty()
- ? argument.m_metavar
- : argument.m_names.front();
- if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) {
- stream << curline << std::endl;
- curline = std::string(indent_size, ' ');
- }
- curline += " ";
- if (argument.m_num_args_range.get_min() == 0 &&
- !argument.m_num_args_range.is_right_bounded()) {
- curline += "[";
- curline += pos_arg;
- curline += "]...";
- } else if (argument.m_num_args_range.get_min() == 1 &&
- !argument.m_num_args_range.is_right_bounded()) {
- curline += pos_arg;
- curline += "...";
- } else {
- curline += pos_arg;
- }
- }
-
- if (multiline_usage) {
- // Display options of other groups
- for (std::size_t i = 0; i < m_group_names.size(); ++i) {
- stream << curline << std::endl << std::endl;
- stream << m_group_names[i] << ":" << std::endl;
- curline = std::string(indent_size, ' ');
- deal_with_options_of_group(i + 1);
- }
- }
-
- stream << curline;
-
- // Put subcommands after positional arguments
- if (!m_subparser_map.empty()) {
- stream << " {";
- std::size_t i{0};
- for (const auto &[command, subparser] : m_subparser_map) {
- if (subparser->get().m_suppress) {
- continue;
- }
-
- if (i == 0) {
- stream << command;
- } else {
- stream << "," << command;
- }
- ++i;
- }
- stream << "}";
- }
-
- return stream.str();
- }
-
- // Printing the one and only help message
- // I've stuck with a simple message format, nothing fancy.
- [[deprecated("Use cout << program; instead. See also help().")]] std::string
- print_help() const {
- auto out = help();
- std::cout << out.rdbuf();
- return out.str();
- }
-
- void add_subparser(ArgumentParser &parser) {
- parser.m_parser_path = m_program_name + " " + parser.m_program_name;
- auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
- m_subparser_map.insert_or_assign(parser.m_program_name, it);
- m_subparser_used.insert_or_assign(parser.m_program_name, false);
- }
-
- void set_suppress(bool suppress) { m_suppress = suppress; }
-
-protected:
- const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const {
- for (const auto &mutex : m_mutually_exclusive_groups) {
- if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) !=
- mutex.m_elements.end()) {
- return &mutex;
- }
- }
- return nullptr;
- }
-
- bool is_valid_prefix_char(char c) const {
- return m_prefix_chars.find(c) != std::string::npos;
- }
-
- char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
-
- /*
- * Pre-process this argument list. Anything starting with "--", that
- * contains an =, where the prefix before the = has an entry in the
- * options table, should be split.
- */
- std::vector<std::string>
- preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
- std::vector<std::string> arguments{};
- for (const auto &arg : raw_arguments) {
-
- const auto argument_starts_with_prefix_chars =
- [this](const std::string &a) -> bool {
- if (!a.empty()) {
-
- const auto legal_prefix = [this](char c) -> bool {
- return m_prefix_chars.find(c) != std::string::npos;
- };
-
- // Windows-style
- // if '/' is a legal prefix char
- // then allow single '/' followed by argument name, followed by an
- // assign char, e.g., ':' e.g., 'test.exe /A:Foo'
- const auto windows_style = legal_prefix('/');
-
- if (windows_style) {
- if (legal_prefix(a[0])) {
- return true;
- }
- } else {
- // Slash '/' is not a legal prefix char
- // For all other characters, only support long arguments
- // i.e., the argument must start with 2 prefix chars, e.g,
- // '--foo' e,g, './test --foo=Bar -DARG=yes'
- if (a.size() > 1) {
- return (legal_prefix(a[0]) && legal_prefix(a[1]));
- }
- }
- }
- return false;
- };
-
- // Check that:
- // - We don't have an argument named exactly this
- // - The argument starts with a prefix char, e.g., "--"
- // - The argument contains an assign char, e.g., "="
- auto assign_char_pos = arg.find_first_of(m_assign_chars);
-
- if (m_argument_map.find(arg) == m_argument_map.end() &&
- argument_starts_with_prefix_chars(arg) &&
- assign_char_pos != std::string::npos) {
- // Get the name of the potential option, and check it exists
- std::string opt_name = arg.substr(0, assign_char_pos);
- if (m_argument_map.find(opt_name) != m_argument_map.end()) {
- // This is the name of an option! Split it into two parts
- arguments.push_back(std::move(opt_name));
- arguments.push_back(arg.substr(assign_char_pos + 1));
- continue;
- }
- }
- // If we've fallen through to here, then it's a standard argument
- arguments.push_back(arg);
- }
- return arguments;
- }
-
- /*
- * @throws std::runtime_error in case of any invalid argument
- */
- void parse_args_internal(const std::vector<std::string> &raw_arguments) {
- auto arguments = preprocess_arguments(raw_arguments);
- if (m_program_name.empty() && !arguments.empty()) {
- m_program_name = arguments.front();
- }
- auto end = std::end(arguments);
- auto positional_argument_it = std::begin(m_positional_arguments);
- for (auto it = std::next(std::begin(arguments)); it != end;) {
- const auto ¤t_argument = *it;
- if (Argument::is_positional(current_argument, m_prefix_chars)) {
- if (positional_argument_it == std::end(m_positional_arguments)) {
-
- // Check sub-parsers
- auto subparser_it = m_subparser_map.find(current_argument);
- if (subparser_it != m_subparser_map.end()) {
-
- // build list of remaining args
- const auto unprocessed_arguments =
- std::vector<std::string>(it, end);
-
- // invoke subparser
- m_is_parsed = true;
- m_subparser_used[current_argument] = true;
- return subparser_it->second->get().parse_args(
- unprocessed_arguments);
- }
-
- if (m_positional_arguments.empty()) {
-
- // Ask the user if they argument they provided was a typo
- // for some sub-parser,
- // e.g., user provided `git totes` instead of `git notes`
- if (!m_subparser_map.empty()) {
- throw std::runtime_error(
- "Failed to parse '" + current_argument + "', did you mean '" +
- std::string{details::get_most_similar_string(
- m_subparser_map, current_argument)} +
- "'");
- }
-
- // Ask the user if they meant to use a specific optional argument
- if (!m_optional_arguments.empty()) {
- for (const auto &opt : m_optional_arguments) {
- if (!opt.m_implicit_value.has_value()) {
- // not a flag, requires a value
- if (!opt.m_is_used) {
- throw std::runtime_error(
- "Zero positional arguments expected, did you mean " +
- opt.get_usage_full());
- }
- }
- }
-
- throw std::runtime_error("Zero positional arguments expected");
- } else {
- throw std::runtime_error("Zero positional arguments expected");
- }
- } else {
- throw std::runtime_error("Maximum number of positional arguments "
- "exceeded, failed to parse '" +
- current_argument + "'");
- }
- }
- auto argument = positional_argument_it++;
-
- // Deal with the situation of <positional_arg1>... <positional_arg2>
- if (argument->m_num_args_range.get_min() == 1 &&
- argument->m_num_args_range.get_max() == (std::numeric_limits<std::size_t>::max)() &&
- positional_argument_it != std::end(m_positional_arguments) &&
- std::next(positional_argument_it) == std::end(m_positional_arguments) &&
- positional_argument_it->m_num_args_range.get_min() == 1 &&
- positional_argument_it->m_num_args_range.get_max() == 1 ) {
- if (std::next(it) != end) {
- positional_argument_it->consume(std::prev(end), end);
- end = std::prev(end);
- } else {
- throw std::runtime_error("Missing " + positional_argument_it->m_names.front());
- }
- }
-
- it = argument->consume(it, end);
- continue;
- }
-
- auto arg_map_it = m_argument_map.find(current_argument);
- if (arg_map_it != m_argument_map.end()) {
- auto argument = arg_map_it->second;
- it = argument->consume(std::next(it), end, arg_map_it->first);
- } else if (const auto &compound_arg = current_argument;
- compound_arg.size() > 1 &&
- is_valid_prefix_char(compound_arg[0]) &&
- !is_valid_prefix_char(compound_arg[1])) {
- ++it;
- for (std::size_t j = 1; j < compound_arg.size(); j++) {
- auto hypothetical_arg = std::string{'-', compound_arg[j]};
- auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
- if (arg_map_it2 != m_argument_map.end()) {
- auto argument = arg_map_it2->second;
- it = argument->consume(it, end, arg_map_it2->first);
- } else {
- throw std::runtime_error("Unknown argument: " + current_argument);
- }
- }
- } else {
- throw std::runtime_error("Unknown argument: " + current_argument);
- }
- }
- m_is_parsed = true;
- }
-
- /*
- * Like parse_args_internal but collects unused args into a vector<string>
- */
- std::vector<std::string>
- parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
- auto arguments = preprocess_arguments(raw_arguments);
-
- std::vector<std::string> unknown_arguments{};
-
- if (m_program_name.empty() && !arguments.empty()) {
- m_program_name = arguments.front();
- }
- auto end = std::end(arguments);
- auto positional_argument_it = std::begin(m_positional_arguments);
- for (auto it = std::next(std::begin(arguments)); it != end;) {
- const auto ¤t_argument = *it;
- if (Argument::is_positional(current_argument, m_prefix_chars)) {
- if (positional_argument_it == std::end(m_positional_arguments)) {
-
- // Check sub-parsers
- auto subparser_it = m_subparser_map.find(current_argument);
- if (subparser_it != m_subparser_map.end()) {
-
- // build list of remaining args
- const auto unprocessed_arguments =
- std::vector<std::string>(it, end);
-
- // invoke subparser
- m_is_parsed = true;
- m_subparser_used[current_argument] = true;
- return subparser_it->second->get().parse_known_args_internal(
- unprocessed_arguments);
- }
-
- // save current argument as unknown and go to next argument
- unknown_arguments.push_back(current_argument);
- ++it;
- } else {
- // current argument is the value of a positional argument
- // consume it
- auto argument = positional_argument_it++;
- it = argument->consume(it, end);
- }
- continue;
- }
-
- auto arg_map_it = m_argument_map.find(current_argument);
- if (arg_map_it != m_argument_map.end()) {
- auto argument = arg_map_it->second;
- it = argument->consume(std::next(it), end, arg_map_it->first);
- } else if (const auto &compound_arg = current_argument;
- compound_arg.size() > 1 &&
- is_valid_prefix_char(compound_arg[0]) &&
- !is_valid_prefix_char(compound_arg[1])) {
- ++it;
- for (std::size_t j = 1; j < compound_arg.size(); j++) {
- auto hypothetical_arg = std::string{'-', compound_arg[j]};
- auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
- if (arg_map_it2 != m_argument_map.end()) {
- auto argument = arg_map_it2->second;
- it = argument->consume(it, end, arg_map_it2->first);
- } else {
- unknown_arguments.push_back(current_argument);
- break;
- }
- }
- } else {
- // current argument is an optional-like argument that is unknown
- // save it and move to next argument
- unknown_arguments.push_back(current_argument);
- ++it;
- }
- }
- m_is_parsed = true;
- return unknown_arguments;
- }
-
- // Used by print_help.
- std::size_t get_length_of_longest_argument() const {
- if (m_argument_map.empty()) {
- return 0;
- }
- std::size_t max_size = 0;
- for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
- max_size =
- std::max<std::size_t>(max_size, argument->get_arguments_length());
- }
- for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
- max_size = std::max<std::size_t>(max_size, command.size());
- }
- return max_size;
- }
-
- using argument_it = std::list<Argument>::iterator;
- using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
- using argument_parser_it =
- std::list<std::reference_wrapper<ArgumentParser>>::iterator;
-
- void index_argument(argument_it it) {
- for (const auto &name : std::as_const(it->m_names)) {
- m_argument_map.insert_or_assign(name, it);
- }
- }
-
- std::string m_program_name;
- std::string m_version;
- std::string m_description;
- std::string m_epilog;
- bool m_exit_on_default_arguments = true;
- std::string m_prefix_chars{"-"};
- std::string m_assign_chars{"="};
- bool m_is_parsed = false;
- std::list<Argument> m_positional_arguments;
- std::list<Argument> m_optional_arguments;
- std::map<std::string, argument_it> m_argument_map;
- std::string m_parser_path;
- std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
- std::map<std::string, argument_parser_it> m_subparser_map;
- std::map<std::string, bool> m_subparser_used;
- std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
- bool m_suppress = false;
- std::size_t m_usage_max_line_width = (std::numeric_limits<std::size_t>::max)();
- bool m_usage_break_on_mutex = false;
- int m_usage_newline_counter = 0;
- std::vector<std::string> m_group_names;
-};
-
-} // namespace argparse