visualizations

Programmatic visualizations
git clone git://git.laack.co/visualizations.git
Log | Files | Refs | README

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:
Dbackground/Makefile | 13-------------
Dbackground/README | 23-----------------------
Dbackground/headers/background.hpp | 7-------
Dbackground/headers/constants.hpp | 10----------
Dbackground/headers/edge.hpp | 17-----------------
Dbackground/headers/graph.hpp | 25-------------------------
Dbackground/headers/prim.hpp | 20--------------------
Dbackground/headers/utils.hpp | 8--------
Dbackground/headers/vertex.hpp | 14--------------
Dbackground/src/background.cpp | 61-------------------------------------------------------------
Dbackground/src/edge.cpp | 17-----------------
Dbackground/src/graph.cpp | 144-------------------------------------------------------------------------------
Dbackground/src/main.cpp | 91-------------------------------------------------------------------------------
Dbackground/src/prim.cpp | 61-------------------------------------------------------------
Dbackground/src/utils.cpp | 24------------------------
Dbackground/src/vertex.cpp | 21---------------------
Dbackground/tests/algo_test.cpp | 215-------------------------------------------------------------------------------
Dbackground/tests/graph_test.cpp | 127-------------------------------------------------------------------------------
Dbackground/vendor/argparse.hpp | 2589-------------------------------------------------------------------------------
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 &current_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 &current_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