Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/c_cpp_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Build Cpp By CMake

on:
[ push,pull_request ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
name: cpp-test
# The type of runner that the job will run on
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest,windows-latest,macos-latest ]
# Steps represent a sequence of tasks that will be executed as part of the job
steps:

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: checkout code
uses: actions/checkout@v2

- name: ensure the cmake
run: cmake --version

- name: prepare folder
run: cmake -E make_directory ./CMAKE_DEBUG_PATH

- name: cmake prepare for compile
working-directory: ./CMAKE_DEBUG_PATH
run: cmake .. -DCMAKE_BUILD_TYPE=Release

- name: cmake prepare for compile
working-directory: ./CMAKE_DEBUG_PATH
run: cmake --build . --config Release

- name: cmake ctest
working-directory: ./CMAKE_DEBUG_PATH
run: ctest

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/src/build
.DS_Store
.DS_Store
.idea/
[Cc]make-*/
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 2.8)
project(CMD_LINE_PARSER)

enable_testing()

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)

add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME}
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include)
249 changes: 123 additions & 126 deletions include/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,150 +10,147 @@

namespace cmd_line_parser {

struct parser {
inline static const std::string empty = "";
struct parser {
inline static const std::string empty;

parser(int argc, char** argv)
: m_argc(argc)
, m_argv(argv)
, m_required(0) {}
parser(int argc, char **argv)
: m_argc(argc), m_argv(argv), m_required(0) {}

struct cmd {
std::string shorthand, value, descr;
bool is_boolean;
};
struct cmd {
std::string shorthand, value, descr;
bool is_boolean;
};

bool parse() {
if (size_t(m_argc - 1) < m_required) return abort();
size_t k = 0;
for (int i = 1; i != m_argc; ++i, ++k) {
std::string parsed(m_argv[i]);
if (parsed == "-h" or parsed == "--help") return abort();
size_t id = k;
bool is_optional = id >= m_required;
if (is_optional) {
auto it = m_shorthands.find(parsed);
if (it == m_shorthands.end()) {
std::cerr << "== error: shorthand '" + parsed + "' not found" << std::endl;
return abort();
bool parse() {
if (m_argc - 1 < m_required) { return abort(); }
for (size_t i{1}, k{0}; i != m_argc; ++i, ++k) {
std::string parsed(m_argv[i]);
if (parsed == "-h" || parsed == "--help") { return abort(); }
size_t id{k};
bool is_optional = id >= m_required;
if (is_optional) {
if (const auto it = m_shorthands.find(parsed);it == m_shorthands.end()) {
std::cerr << "== error: shorthand '" + parsed + "' not found" << std::endl;
return abort();
} else {
id = (*it).second;
}
}
id = (*it).second;
}
assert(id < m_names.size());
auto const& name = m_names[id];
auto& c = m_cmds[name];
if (is_optional) {
if (c.is_boolean) {
parsed = "true";
} else {
++i;
if (i == m_argc) return abort();
parsed = m_argv[i];
assert(id < m_names.size());
auto const &name = m_names[id];
auto &c = m_cmds[name];
if (is_optional) {
if (c.is_boolean) {
parsed = "true";
} else {
++i;
if (i == m_argc) { return abort(); }
parsed = m_argv[i];
}
}
c.value = parsed;
}
c.value = parsed;
return true;
}
return true;
}

void help() const {
std::cerr << "Usage: " << m_argv[0] << " [-h,--help]";
auto print = [this](bool with_description) {
for (size_t i = 0; i != m_names.size(); ++i) {
auto const& c = m_cmds.at(m_names[i]);
bool is_optional = i >= m_required;
if (is_optional) std::cerr << " [" << c.shorthand;
if (!c.is_boolean) std::cerr << " " << m_names[i];
if (is_optional) std::cerr << "]";
if (with_description) std::cerr << "\n\t" << c.descr << "\n\n";
}
};
print(false);
std::cerr << "\n\n";
print(true);
std::cerr << " [-h,--help]\n\tPrint this help text and silently exits." << std::endl;
}
void help() const {
std::cerr << "Usage: " << m_argv[0] << " [-h,--help]";
const auto print = [this](bool with_description) {
for (size_t i = 0; i != m_names.size(); ++i) {
auto const &c = m_cmds.at(m_names[i]);
bool is_optional = i >= m_required;
if (is_optional) { std::cerr << " [" << c.shorthand; }
if (!c.is_boolean) { std::cerr << " " << m_names[i]; }
if (is_optional) { std::cerr << "]"; }
if (with_description) { std::cerr << "\n\t" << c.descr << "\n\n"; }
}
};
print(false);
std::cerr << "\n\n";
print(true);
std::cerr << " [-h,--help]\n\tPrint this help text and silently exits." << std::endl;
}

bool add(std::string const& name, std::string const& descr) {
bool ret = m_cmds.emplace(name, cmd{empty, empty, descr, false}).second;
if (ret) {
m_names.push_back(name);
m_required += 1;
bool add(std::string const &name, std::string const &descr) {
bool ret = m_cmds.emplace(name, cmd{empty, empty, descr, false}).second;
if (ret) {
m_names.push_back(name);
m_required += 1;
}
return ret;
}
return ret;
}

bool add(std::string const& name, std::string const& descr, std::string const& shorthand,
bool is_boolean = true) {
bool ret =
m_cmds.emplace(name, cmd{shorthand, is_boolean ? "false" : empty, descr, is_boolean})
.second;
if (ret) {
m_names.push_back(name);
m_shorthands.emplace(shorthand, m_names.size() - 1);
bool add(std::string const &name, std::string const &descr, std::string const &shorthand,
bool is_boolean = true) {
bool ret =
m_cmds.emplace(name, cmd{shorthand, is_boolean ? "false" : empty, descr, is_boolean})
.second;
if (ret) {
m_names.push_back(name);
m_shorthands.emplace(shorthand, m_names.size() - 1);
}
return ret;
}
return ret;
}

template <typename T>
T get(std::string const& name) const {
auto it = m_cmds.find(name);
if (it == m_cmds.end()) throw std::runtime_error("error: '" + name + "' not found");
auto const& value = (*it).second.value;
return parse<T>(value);
}
template<typename T>
T get(std::string const &name) const {
auto it = m_cmds.find(name);
if (it == m_cmds.end()) throw std::runtime_error("error: '" + name + "' not found");
auto const &value = (*it).second.value;
return parse < T > (value);
}

bool parsed(std::string const& name) const {
auto it = m_cmds.find(name);
if (it == m_cmds.end() or (*it).second.value == empty) return false;
return true;
}
bool parsed(std::string const &name) const {
auto it = m_cmds.find(name);
if (it == m_cmds.end() || (*it).second.value == empty) { return false; }
return true;
}

template <typename T>
T parse(std::string const& value) const {
if constexpr (std::is_same<T, std::string>::value) {
return value;
} else if constexpr (std::is_same<T, char>::value or std::is_same<T, signed char>::value or
std::is_same<T, unsigned char>::value) {
return value.front();
} else if constexpr (std::is_same<T, unsigned int>::value or std::is_same<T, int>::value or
std::is_same<T, unsigned short int>::value or
std::is_same<T, short int>::value) {
return std::atoi(value.c_str());
} else if constexpr (std::is_same<T, unsigned long int>::value or
std::is_same<T, long int>::value or
std::is_same<T, unsigned long long int>::value or
std::is_same<T, long long int>::value) {
return std::atoll(value.c_str());
} else if constexpr (std::is_same<T, float>::value or std::is_same<T, double>::value or
std::is_same<T, long double>::value) {
return std::atof(value.c_str());
} else if constexpr (std::is_same<T, bool>::value) {
std::istringstream stream(value);
bool ret;
if (value == "true" or value == "false") {
stream >> std::boolalpha >> ret;
} else {
stream >> std::noboolalpha >> ret;
template<typename T>
T parse(std::string const &value) const {
if constexpr (std::is_same<T, std::string>::value) {
return value;
} else if constexpr (std::is_same<T, char>::value || std::is_same<T, signed char>::value ||
std::is_same<T, unsigned char>::value) {
return value.front();
} else if constexpr (std::is_same<T, unsigned int>::value || std::is_same<T, int>::value ||
std::is_same<T, unsigned short int>::value ||
std::is_same<T, short int>::value) {
return std::strtol(value.c_str(), nullptr, 10);
} else if constexpr (std::is_same<T, unsigned long int>::value ||
std::is_same<T, long int>::value ||
std::is_same<T, unsigned long long int>::value ||
std::is_same<T, long long int>::value) {
return std::strtoll(value.c_str(), nullptr, 10);
} else if constexpr (std::is_same<T, float>::value || std::is_same<T, double>::value ||
std::is_same<T, long double>::value) {
return std::strtod(value.c_str(), nullptr);
} else if constexpr (std::is_same<T, bool>::value) {
std::istringstream stream(value);
bool ret;
if (value == "true" || value == "false") {
stream >> std::boolalpha >> ret;
} else {
stream >> std::noboolalpha >> ret;
}
return ret;
}
return ret;
assert(false); // should never happen
throw std::runtime_error("unsupported type");
}
assert(false); // should never happen
throw std::runtime_error("unsupported type");
}

private:
int m_argc;
char** m_argv;
size_t m_required;
std::unordered_map<std::string, cmd> m_cmds;
std::unordered_map<std::string, int> m_shorthands;
std::vector<std::string> m_names;
private:
size_t m_argc;
char **m_argv;
size_t m_required;
std::unordered_map<std::string, cmd> m_cmds;
std::unordered_map<std::string, int> m_shorthands;
std::vector<std::string> m_names;

bool abort() const {
help();
return false;
}
};
bool abort() const {
help();
return false;
}
};

} // namespace cmd_line_parser
40 changes: 21 additions & 19 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
cmake_minimum_required(VERSION 2.8)
project(CMD_PARSER)
set(CMAKE_CXX_STANDARD 17)

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
MESSAGE( STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE} )
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif ()
MESSAGE(STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif ()
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-missing-braces")
if (USE_SANITIZERS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
endif ()

if(UNIX)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-missing-braces")
endif ()

if(USE_SANITIZERS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
endif()
enable_testing()
add_executable(example ${CMAKE_CURRENT_SOURCE_DIR}/example.cpp)
target_link_libraries(example PUBLIC CMD_LINE_PARSER)

endif()
add_executable(test_parse ${CMAKE_CURRENT_SOURCE_DIR}/test_parse.cpp)

add_executable(example example.cpp)
add_executable(test_parse test_parse.cpp)
add_test(test_parse_CTEST ${CMAKE_BINARY_DIR}/test_parse)
Loading