From bc203ea9c5bab70654894013e615ff49bdea9fd2 Mon Sep 17 00:00:00 2001 From: Stefan Pranger Date: Mon, 24 Jan 2022 19:26:40 +0100 Subject: [PATCH] initial commit Finished basic implementation of Dinic's algorithm --- .gitignore | 1 + CMakeLists.txt | 27 + Graph.cpp | 211 +++++++ Graph.h | 70 +++ main.cpp | 57 ++ util/Arc.h | 13 + util/CMakeLists.txt | 7 + util/GraphParser.cpp | 76 +++ util/GraphParser.h | 16 + util/OptionParser.cpp | 54 ++ util/OptionParser.h | 15 + util/Vertex.cpp | 35 ++ util/Vertex.h | 32 + util/popl.hpp | 1331 +++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1945 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Graph.cpp create mode 100644 Graph.h create mode 100644 main.cpp create mode 100644 util/Arc.h create mode 100644 util/CMakeLists.txt create mode 100644 util/GraphParser.cpp create mode 100644 util/GraphParser.h create mode 100644 util/OptionParser.cpp create mode 100644 util/OptionParser.h create mode 100644 util/Vertex.cpp create mode 100644 util/Vertex.h create mode 100644 util/popl.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a33f6b4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +include(util/CMakeLists.txt) + +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'") +#add_definitions(-DLOG_DEBUG) + +cmake_minimum_required(VERSION 3.0...3.22) + +set(CMAKE_BUILD_TYPE Debug) + +project( + max_flow + VERSION 1.0 + LANGUAGES CXX) + +#find_package(Boost COMPONENTS program_options REQUIRED) + +#include_directories("." db exceptions) + +add_executable(maxFlow + ${SRCS} + main.cpp + Graph.cpp + ) + +target_link_libraries(maxFlow stdc++fs) diff --git a/Graph.cpp b/Graph.cpp new file mode 100644 index 0000000..f861ed1 --- /dev/null +++ b/Graph.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include + +#include "Graph.h" +#include "util/GraphParser.h" + +namespace data { + Graph::Graph(bool stdout_output, bool file_output, std::string output_filename, bool verbose_max_flow, bool min_cut, int verbosity) + : m_file_output(file_output), m_output_file_name(output_filename), m_verbose_max_flow(verbose_max_flow), m_min_cut(min_cut), m_verbosity(verbosity) { + //if(!stdout_output && file_output) { + // m_stdout_output = false; + //} else { + // m_stdout_output = true; + //} + } + + void Graph::parseFromString(const std::string &graph_string) { + parser::parseString(graph_string, m_arc_list, m_vertices, m_source_id, m_sink_id, m_num_vertices, m_num_arcs); + setSourceAndSinkIterator(); + initMatrices(); + initOstream(); + } + + void Graph::parseFromFile(const std::string &graph_file) { + if(graph_file == m_output_file_name) { + throw std::runtime_error("Input graph file name and output file name are the same. Will not overwrite. Exiting..."); + } + parser::parseFile(graph_file, m_arc_list, m_vertices, m_source_id, m_sink_id, m_num_vertices, m_num_arcs); + setSourceAndSinkIterator(); + initMatrices(); + initOstream(); + } + + void Graph::initMatrices() { + m_flow.resize(m_num_vertices, std::vector(m_num_vertices, 0)); + m_capapcities.resize(m_num_vertices, std::vector(m_num_vertices, 0)); + for(auto const &arc : m_arc_list) { + m_capapcities.at(arc.start - 1).at(arc.end - 1) = arc.capacity; // how to best map arbitrary ids to index in matrix + } + } + + void Graph::setSourceAndSinkIterator() { + auto m_source = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return (v.getID() == m_source_id); }); + auto m_sink = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return (v.getID() == m_sink_id); }); + } + + void Graph::initOstream() { + if(m_file_output) { + m_ofstream = new std::ofstream(m_output_file_name); + } else { + m_ofstream = &std::cout; + } + } + + void Graph::maxFlowDinic() { + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + printInformation(); + do { + constructLevelGraph(); + } while(findAugmentingPaths() != NO_AUGMENTING_PATH_FOUND); + *m_ofstream << "Found max flow |x| = " << m_max_flow << "\n"; + if(m_verbose_max_flow) printMaxFlowInformation(); + if(m_min_cut) printMinCut(); + if(m_verbosity >= 1) printComputationStatistics(start, std::chrono::steady_clock::now()); + } + + int Graph::findAugmentingPaths() { + auto m_sink = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return v.getID() == m_sink_id; }); + if(m_sink->getLevel() == UNDEF_LEVEL) { + return NO_AUGMENTING_PATH_FOUND; + } + for(auto &v : m_vertices) { + v.setVisited(false); + } + auto m_source = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return v.getID() == m_source_id; }); + std::vector path{*m_source}; + buildPath(path); + return 0; + } + + void Graph::buildPath(std::vector ¤t_path) { + Vertex head = current_path.back(); + if(head.getID() == m_sink_id) { + computeFlowForPath(current_path); + } + for(auto const& arc : head.getOutgoingArcs()) { + if(m_capapcities.at(arc.start - 1).at(arc.end - 1) <= 0) continue; + auto it = std::find_if(m_vertices.begin(), m_vertices.end(), [&arc] (const Vertex &v) { return v.getID() == arc.end; }); + if(head.getLevel() + 1 != it->getLevel()) continue; + if(it != m_vertices.end()) { + current_path.push_back(*it); + buildPath(current_path); + } + current_path.pop_back(); + } + if(m_verbosity >= 1) m_num_build_path_calls++; + } + + void Graph::computeFlowForPath(const std::vector ¤t_path) { + std::vector path_capacities; + for(uint i = 0; i < current_path.size() - 1; i++) { + path_capacities.push_back(m_capapcities.at(current_path.at(i).getID() - 1).at(current_path.at(i + 1).getID() - 1)); + } + Capacity flow = *std::min_element(path_capacities.begin(), path_capacities.end()); + m_max_flow += flow; + for(uint i = 0; i < current_path.size() - 1; i++) { + m_capapcities.at(current_path.at(i).getID() - 1).at(current_path.at(i + 1).getID() - 1) -= flow; + m_flow.at(current_path.at(i).getID() - 1).at(current_path.at(i + 1).getID() - 1) += flow; + } + if(m_verbosity >= 1) m_num_paths++; + if(m_verbosity >= 2) { + std::stringstream path; + path << std::to_string(current_path.front().getID()); + for(uint i = 1; i < current_path.size(); i++) { + path << " > " << current_path.at(i).getID(); + } + path << " | flow = " << flow; + m_augmenting_paths.push_back(path.str()); + } + } + + void Graph::constructLevelGraph() { + std::queue q; + for(auto &v : m_vertices) { + v.setLevel(UNDEF_LEVEL); + } + auto m_source = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return (v.getID() == m_source_id); }); + m_source->setLevel(0); + q.push(*m_source); + while(!q.empty()) { + Vertex current_vertex = q.front(); + int current_level = current_vertex.getLevel(); + q.pop(); + // restructure this to use matrix + for(auto const &arc : current_vertex.getOutgoingArcs()) { + if(m_capapcities.at(arc.start - 1).at(arc.end - 1) <= 0) continue; + auto it = std::find_if(m_vertices.begin(), m_vertices.end(), [&arc] (const Vertex &v) { return (v.getID() == arc.end) && !v.hasDefinedLevel(); }); + if(it != m_vertices.end()) { + it->setLevel(current_level + 1); + q.push(*it); + } + } + } + if(m_verbosity >= 1) m_num_level_graphs_built++; + } + + void Graph::printInformation() const { + auto m_source = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return (v.getID() == m_source_id); }); + auto m_sink = std::find_if(m_vertices.begin(), m_vertices.end(), [this] (const Vertex &v) { return (v.getID() == m_sink_id); }); + *m_ofstream << "#Vertices: " << m_num_vertices << std::endl; + *m_ofstream << "#Arc: " << m_num_arcs << std::endl; + *m_ofstream << "Source: " << m_source->getID() << ", Sink: " << m_sink->getID() << std::endl; + *m_ofstream << "Vertices: "; + bool first = true; + for(auto const& v : m_vertices) { + if(first) first = false; + else *m_ofstream << ", "; + *m_ofstream << v.getID(); + } + *m_ofstream << std::endl; + for(auto const& a : m_arc_list) { + *m_ofstream << " " << a.start << " -> " << a.end << " capacity = " << a.capacity << std::endl; + } + *m_ofstream << std::endl; + } + + void Graph::printMaxFlowInformation() const { + *m_ofstream << "Max Flow per arc:\n"; + for(auto const &arc : m_arc_list) { + *m_ofstream << " " << arc.start << " -> " << arc.end << " flow = " << m_flow.at(arc.start - 1 ).at(arc.end - 1) << "/" << arc.capacity << "\n"; + } + } + + void Graph::printMinCut() const { + std::vector min_cut, complement; + for(auto const &vertex : m_vertices) { + if(vertex.getLevel() != UNDEF_LEVEL) { + min_cut.push_back(std::to_string(vertex.getID())); + } else { + complement.push_back(std::to_string(vertex.getID())); + } + } + *m_ofstream << "Min Cut X: {"; + bool first = true; + for(auto const &v : min_cut) { + if(first) first = false; + else *m_ofstream << ", "; + *m_ofstream << v; + } *m_ofstream << "}\nComplement(X): {"; + first = true; + for(auto const &v : complement) { + if(first) first = false; + else *m_ofstream << ", "; + *m_ofstream << v; + } *m_ofstream << "}\n"; + } + + void Graph::printComputationStatistics(const std::chrono::steady_clock::time_point &start, const std::chrono::steady_clock::time_point &end) const { + *m_ofstream << "Elapsed time: " << std::chrono::duration_cast(end - start).count() << "ms (" << std::chrono::duration_cast(end - start).count() << "µs).\n"; + *m_ofstream << "Computation Statistics:\n"; + *m_ofstream << " #level graphs built: " << m_num_level_graphs_built << "\n"; + *m_ofstream << " #augmenting paths computed: " << m_num_paths << "\n"; + if(m_verbosity >= 2) { + for(auto const &path : m_augmenting_paths) *m_ofstream << " " << path << "\n"; + } + *m_ofstream << " #recursive buildPath calls: " << m_num_build_path_calls << "\n"; + } +} diff --git a/Graph.h b/Graph.h new file mode 100644 index 0000000..1b7d734 --- /dev/null +++ b/Graph.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "util/GraphParser.h" +#include "util/Arc.h" + +#define NO_AUGMENTING_PATH_FOUND -1 + +typedef std::vector> CapacityMatrix; + +namespace data { + class Graph { + public: + Graph(bool stdout_output, bool file_output, std::string output_filename, bool verbose_max_flow, bool min_cut, int verbosity); + + void parseFromString(const std::string &graph_string); + void parseFromFile(const std::string &graph_file); + + void maxFlowDinic(); + + private: + void initMatrices(); + void setSourceAndSinkIterator(); + void initOstream(); + + void constructLevelGraph(); + int findAugmentingPaths(); + void buildPath(std::vector ¤t_path); + void computeFlowForPath(const std::vector ¤t_path); + + void printInformation() const; + void printMaxFlowInformation() const; + void printMinCut() const; + void printComputationStatistics(const std::chrono::steady_clock::time_point &start, const std::chrono::steady_clock::time_point &end) const; + + std::vector m_vertices; + std::vector m_arc_list; + VertexID m_source_id; + VertexID m_sink_id; + std::vector::iterator m_source; + std::vector::iterator m_sink; + + CapacityMatrix m_flow; + CapacityMatrix m_capapcities; + + int m_num_vertices; + int m_num_arcs; + + int m_max_flow = 0; + + bool m_stdout_output = true; + bool m_file_output = false; + std::string m_output_file_name; + std::ostream *m_ofstream; + bool m_verbose_max_flow = false; + bool m_min_cut = false; + int m_verbosity = 0; + + uint m_num_paths = 0; + uint m_num_build_path_calls = 0; + uint m_num_level_graphs_built = 0; + std::vector m_augmenting_paths; + }; +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..bb5b270 --- /dev/null +++ b/main.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "util/OptionParser.h" +#include "Graph.h" + +int main(int argc, char* argv[]) { + popl::OptionParser optionParser("Allowed options"); + + auto help_option = optionParser.add("h", "help", "Print this help message."); + auto input_filename = optionParser.add, popl::Attribute::optional>("f", "input-file", "Filename of input graph file."); + auto input_string = optionParser.add, popl::Attribute::optional>("s", "input-string", "Input graph string."); + + auto stdout_output = optionParser.add("o", "stdout", "Output to stdout."); + auto file_output = optionParser.add, popl::Attribute::optional>("p", "output-file", "Filename for output."); + + auto verbose_max_flow = optionParser.add("a", "max-flow", "Include verbose information about the max flow to the output."); + auto min_cut_option = optionParser.add("m", "minimum-cut", "Include the minimum cut set to the output."); + + auto verbose_option = optionParser.add("v", "verbose", "Output verbose algorithmic information and runtime. Pass twice to get step-by-step information."); + + try { + optionParser.parse(argc, argv); + if(parser::checkOption(input_filename->count(), input_string->count()) > 0) { + std::cout << optionParser << std::endl; + return EXIT_FAILURE; + } + + if(help_option->count() > 0) { + std::cout << optionParser << std::endl; + } + } catch (const popl::invalid_option &e) { + return parser::printPoplException(e); + } catch (const std::exception &e) { + std::cerr << "Exception: " << e.what() << "\n"; + return EXIT_FAILURE; + } + + std::string filename = ""; + if(file_output->is_set()) { + filename = file_output->value(0); + } + data::Graph network(stdout_output->is_set(), + file_output->is_set(), + filename, + verbose_max_flow->is_set(), + min_cut_option->is_set(), + verbose_option->count()); + if(input_filename->count() > 0) { + network.parseFromFile(input_filename->value(0)); + } else if (input_string->count()) { + network.parseFromString(input_string->value(0)); + } + network.maxFlowDinic(); + + return 0; +} diff --git a/util/Arc.h b/util/Arc.h new file mode 100644 index 0000000..d4161fa --- /dev/null +++ b/util/Arc.h @@ -0,0 +1,13 @@ +#pragma once + +typedef int VertexID; +typedef int Capacity; +typedef int Flow; + +typedef struct Arc { + VertexID start; + VertexID end; + Capacity capacity; + Capacity residual_capacity; // might not be needed + Flow flow; // might not be needed +} Arc; diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..38f5835 --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,7 @@ +list(APPEND SRCS + ${CMAKE_CURRENT_LIST_DIR}/Arc.h + ${CMAKE_CURRENT_LIST_DIR}/GraphParser.cpp + ${CMAKE_CURRENT_LIST_DIR}/OptionParser.cpp + ${CMAKE_CURRENT_LIST_DIR}/Vertex.cpp + ${CMAKE_CURRENT_LIST_DIR}/popl.hpp +) diff --git a/util/GraphParser.cpp b/util/GraphParser.cpp new file mode 100644 index 0000000..fcdfcff --- /dev/null +++ b/util/GraphParser.cpp @@ -0,0 +1,76 @@ +#include +#include +#include // only debug +#include + +#include "GraphParser.h" +#include "Vertex.h" + +void parseGraphInformation(const std::string &line, int &num_vertices, int &num_arcs, VertexID &source, VertexID &sink) { + int num; + std::vector information; + std::istringstream ss(line); + for (; ss;) { + if (ss >> num) { + information.push_back(num); + } else if (!ss.eof()) { + ss.clear(); + ss.ignore(1); + } + } + if(information.size() != 4) { + std::cerr << "The graph information on line 0 is not well defined:\n"; + std::cerr << line << std::endl; + throw std::runtime_error("The graph information on line 0 is not well defined."); + } + num_vertices = information.at(0); + num_arcs = information.at(1); + source = information.at(2); + sink = information.at(3); +} + +Arc parseArc(const std::string &line, const int &line_count) { + try { + int start, dest, capacity; + std::istringstream(line) >> start >> dest >> capacity; + return Arc{start, dest, capacity, capacity, 0}; // check sanity here + } catch(const std::exception &e) { + std::cerr << "Error on line " << line_count << ":\t" << line << std::endl; + throw std::runtime_error("Error parsing the arc on line " + std::to_string(line_count)); + } +} + +namespace parser { + void parseString(const std::string &graph_string, std::vector &arc_list, std::vector &vertices, VertexID &source_id, VertexID &sink_id, int &num_vertices, int &num_arcs) { + int line_count = 0; + std::set vertex_set; + + std::stringstream stream(graph_string); + std::string line; + std::getline(stream, line); + parseGraphInformation(line, num_vertices, num_arcs, source_id, sink_id); + line_count++; + + while (std::getline(stream, line)) { + Arc arc = parseArc(line, line_count); + arc_list.push_back(arc); + vertex_set.insert(Vertex{arc.start}); + vertex_set.insert(Vertex{arc.end}); + line_count++; + } + + vertices.resize(vertex_set.size()); + std::copy(vertex_set.begin(), vertex_set.end(), vertices.begin()); + + for(auto &arc : arc_list) { + auto it = std::find_if(vertices.begin(), vertices.end(), [&arc] (const Vertex &v) { return v.getID() == arc.start; }); // check if capacity is > 0 + it->addOutgoingArc(arc); + } + } + + void parseFile(const std::string &graph_filename, std::vector &m_arc_list, std::vector &m_vertices, VertexID &source_id, VertexID &sink_id, int &m_num_vertices, int &m_num_arcs) { + std::ifstream input_file(graph_filename); + std::string content((std::istreambuf_iterator(input_file)), (std::istreambuf_iterator())); + parseString(content, m_arc_list, m_vertices, source_id, sink_id, m_num_vertices, m_num_arcs); + } +} diff --git a/util/GraphParser.h b/util/GraphParser.h new file mode 100644 index 0000000..0203c23 --- /dev/null +++ b/util/GraphParser.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "Vertex.h" +#include "Arc.h" + +class Vertex; + +namespace parser { + void parseString(const std::string &graph_string, std::vector &m_arc_list, std::vector &m_vertices, VertexID &source_id, VertexID &sink_id, int &m_num_vertices, int &m_num_arcs); + + void parseFile(const std::string &graph_filename, std::vector &m_arc_list, std::vector &m_vertices, VertexID &source_id, VertexID &sink_id, int &m_num_vertices, int &m_num_arcs); +} diff --git a/util/OptionParser.cpp b/util/OptionParser.cpp new file mode 100644 index 0000000..d8d7501 --- /dev/null +++ b/util/OptionParser.cpp @@ -0,0 +1,54 @@ +#include + +#include "popl.hpp" +#include "OptionParser.h" + +namespace parser { + int printPoplException(const popl::invalid_option &e) { + std::cerr << "Invalid Option Exception: " << e.what() << "\n"; + std::cerr << "error: "; + if (e.error() == popl::invalid_option::Error::missing_argument) { + std::cerr << "missing_argument\n"; + } else if (e.error() == popl::invalid_option::Error::invalid_argument) { + std::cerr << "invalid_argument\n"; + } else if (e.error() == popl::invalid_option::Error::too_many_arguments) { + std::cerr << "too_many_arguments\n"; + } else if (e.error() == popl::invalid_option::Error::missing_option) { + std::cerr << "missing_option\n"; + } + + if (e.error() == popl::invalid_option::Error::missing_option) { + std::string option_name(e.option()->name(popl::OptionName::short_name, true)); + if (option_name.empty()) + option_name = e.option()->name(popl::OptionName::long_name, true); + std::cerr << "option: " << option_name << "\n"; + } + else { + std::cerr << "option: " << e.option()->name(e.what_name()) << "\n"; + std::cerr << "value: " << e.value() << "\n"; + } + return EXIT_FAILURE; + } + + + int checkOption(const int input_filename_count, + const int input_string_count) { + if(input_filename_count > 1) { + std::cerr << "You may only pass one input graph file."; + return INPUT_ERROR; + } + if(input_string_count > 1) { + std::cerr << "You may only pass one input graph string."; + return INPUT_ERROR; + } + if(input_filename_count > 0 && input_string_count > 0) { + std::cerr << "You may only pass either an input graph file or an input graph string."; + return INPUT_ERROR; + } + if(input_filename_count == 0 && input_string_count == 0) { + std::cerr << "You need to pass either and input graph file or an input graph string."; + return INPUT_ERROR; + } + return INPUT_OK; + } +} diff --git a/util/OptionParser.h b/util/OptionParser.h new file mode 100644 index 0000000..3fe43d8 --- /dev/null +++ b/util/OptionParser.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "popl.hpp" + +#define INPUT_ERROR 1 +#define INPUT_OK 0 + +namespace parser { + int printPoplException(const popl::invalid_option &e); + int checkOption(const int input_filename_count, + const int input_string_count); + +} diff --git a/util/Vertex.cpp b/util/Vertex.cpp new file mode 100644 index 0000000..483a11c --- /dev/null +++ b/util/Vertex.cpp @@ -0,0 +1,35 @@ +#include "Vertex.h" + +Vertex::Vertex() {} +Vertex::Vertex(const int &id) : m_id(id) {} + +VertexID Vertex::getID() const { + return m_id; +} +std::vector Vertex::getOutgoingArcs() const { + return m_outgoing_arcs; +} + +int Vertex::getLevel() const { + return m_level; +} + +bool Vertex::visited() const { + return m_visited; +} + +bool Vertex::hasDefinedLevel() const { + return m_level != UNDEF_LEVEL; +} + +void Vertex::addOutgoingArc(const Arc &arc) { + m_outgoing_arcs.push_back(arc); +} + +void Vertex::setLevel(const int &level) { + m_level = level; +} + +void Vertex::setVisited(const bool &visited) { + m_visited = visited; +} diff --git a/util/Vertex.h b/util/Vertex.h new file mode 100644 index 0000000..99748f4 --- /dev/null +++ b/util/Vertex.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Arc.h" +#include "GraphParser.h" + +#define UNDEF_LEVEL -1 + +class Vertex { + public: + Vertex(); + Vertex(const int &id); + + VertexID getID() const; + std::vector getOutgoingArcs() const; + int getLevel() const; + bool visited() const; + bool hasDefinedLevel() const; + + void addOutgoingArc(const Arc &arc); + void setLevel(const int &level); + void setVisited(const bool &visited = true); + + private: + VertexID m_id; + int m_level; + bool m_visited; + std::vector m_outgoing_arcs; +}; + +inline bool operator<(const Vertex& lhs, const Vertex& rhs) { + return lhs.getID() < rhs.getID(); +} diff --git a/util/popl.hpp b/util/popl.hpp new file mode 100644 index 0000000..8fd668b --- /dev/null +++ b/util/popl.hpp @@ -0,0 +1,1331 @@ +/*** + ____ __ ____ __ + ( _ \ / \( _ \( ) + ) __/( O )) __// (_/\ + (__) \__/(__) \____/ + version 1.3.0 + https://github.com/badaix/popl + + This file is part of popl (program options parser lib) + Copyright (C) 2015-2021 Johannes Pohl + + This software may be modified and distributed under the terms + of the MIT license. See the LICENSE file for details. +***/ + +/// checked with clang-tidy: +/// run-clang-tidy-3.8.py -header-filter='.*' +/// -checks='*,-misc-definitions-in-headers,-google-readability-braces-around-statements,-readability-braces-around-statements,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-google-build-using-namespace' + +#ifndef POPL_HPP +#define POPL_HPP + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WINDOWS +#include +#endif + + +namespace popl +{ + +#define POPL_VERSION "1.3.0" + + +/// Option's argument type +/** + * Switch has "no" argument + * Value has "required" argument + * Implicit has "optional" argument + */ +enum class Argument +{ + no = 0, // option never takes an argument + required, // option always requires an argument + optional // option may take an argument +}; + + +/// Option's attribute +/** + * inactive: Option is not set and will not be parsed + * hidden: Option is active, but will not show up in the help message + * required: Option must be set on the command line. Otherwise an exception will be thrown + * optional: Option must not be set. Default attribute. + * advanced: Option is advanced and will only show up in the advanced help message + * expoert: Option is expert and will only show up in the expert help message + */ +enum class Attribute +{ + inactive = 0, + hidden = 1, + required = 2, + optional = 3, + advanced = 4, + expert = 5 +}; + + +/// Option name type. Used in invalid_option exception. +/** + * unspecified: not specified + * short_name: The option's short name + * long_name: The option's long name + */ +enum class OptionName +{ + unspecified, + short_name, + long_name +}; + + +/// Abstract Base class for Options +/** + * Base class for Options + * holds just configuration data, no runtime data. + * Option is not bound to a special type "T" + */ +class Option +{ + friend class OptionParser; + +public: + /// Construct an Option + /// @param short_name the options's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Option(const std::string& short_name, const std::string& long_name, std::string description); + + /// Destructor + virtual ~Option() = default; + + /// default copy constructor + Option(const Option&) = default; + + /// default move constructor + Option(Option&&) = default; + + /// default assignement operator + Option& operator=(const Option&) = default; + + /// default move assignement operator + Option& operator=(Option&&) = default; + + /// Get the Option's short name + /// @return character of the options's short name or 0 if no short name is defined + char short_name() const; + + /// Get the Option's long name + /// @return the long name of the Option. Empty string if no long name is defined + std::string long_name() const; + + /// Get the Option's long or short name + /// @param what_name the option's name to return + /// @param what_hyphen preced the returned name with (double-)hypen + /// @return the requested name of the Option. Empty string if not defined. + std::string name(OptionName what_name, bool with_hypen = false) const; + + /// Get the Option's description + /// @return the description + std::string description() const; + + /// Get the Option's default value + /// @param out stream to write the default value to + /// @return true if a default value is available, false if not + virtual bool get_default(std::ostream& out) const = 0; + + /// Set the Option's attribute + /// @param attribute + void set_attribute(const Attribute& attribute); + + /// Get the Option's attribute + /// @return the Options's attribute + Attribute attribute() const; + + /// Get the Option's argument type + /// @return argument type (no, required, optional) + virtual Argument argument_type() const = 0; + + /// Check how often the Option is set on command line + /// @return the Option's count on command line + virtual size_t count() const = 0; + + /// Check if the Option is set + /// @return true if set at least once + virtual bool is_set() const = 0; + + +protected: + /// Parse the command line option and fill the internal data structure + /// @param what_name short or long option name + /// @param value the value as given on command line + virtual void parse(OptionName what_name, const char* value) = 0; + + /// Clear the internal data structure + virtual void clear() = 0; + + std::string short_name_; + std::string long_name_; + std::string description_; + Attribute attribute_; +}; + + + +/// Value option with optional default value +/** + * Value option with optional default value + * If set, it requires an argument + */ +template +class Value : public Option +{ +public: + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Value(const std::string& short_name, const std::string& long_name, const std::string& description); + + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + /// @param default_val the Option's default value + /// @param assign_to pointer to a variable to assign the parsed command line value to + Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to = nullptr); + + size_t count() const override; + bool is_set() const override; + + /// Assign the last parsed command line value to "var" + /// @param var pointer to the variable where is value is written to + void assign_to(T* var); + + /// Manually set the Option's value. Deletes current value(s) + /// @param value the new value of the option + void set_value(const T& value); + + /// Get the Option's value. Will throw if option at index idx is not available + /// @param idx the zero based index of the value (if set multiple times) + /// @return the Option's value at index "idx" + T value(size_t idx = 0) const; + + /// Get the Option's value, return default_value if not set. + /// @param default_value return value if value is not set + /// @param idx the zero based index of the value (if set multiple times) + /// @return the Option's value at index "idx" or the default value or default_value + T value_or(const T& default_value, size_t idx = 0) const; + + /// Set the Option's default value + /// @param value the default value if not specified on command line + void set_default(const T& value); + + /// Check if the Option has a default value + /// @return true if the Option has a default value + bool has_default() const; + + /// Get the Option's default value. Will throw if no default is set. + /// @return the Option's default value + T get_default() const; + bool get_default(std::ostream& out) const override; + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; + std::unique_ptr default_; + + virtual void update_reference(); + virtual void add_value(const T& value); + void clear() override; + + T* assign_to_; + std::vector values_; +}; + + + +/// Value option with implicit default value +/** + * Value option with implicit default value + * If set, an argument is optional + * -without argument it carries the implicit default value + * -with argument it carries the explicit value + */ +template +class Implicit : public Value +{ +public: + Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to = nullptr); + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + +/// Value option without value +/** + * Value option without value + * Does not require an argument + * Can be either set or not set + */ +class Switch : public Value +{ +public: + Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to = nullptr); + + void set_default(const bool& value) = delete; + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + +using Option_ptr = std::shared_ptr