diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..61235ca --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,296 @@ +name: build +on: + push: + branches: [ main, devel ] + tags: + - "*" +jobs: + build-linux: + name: 'Build Linux' + runs-on: 'ubuntu-latest' + steps: + - name: Checkout source + uses: actions/checkout@v2 + - name: Build source + run: | + sudo apt-get -y -qq update && sudo apt-get -y -q upgrade 2>&1 > /dev/null + sudo apt-get -y -qq install libc++-12-dev libc++abi-12-dev lld 2>&1 > /dev/null + sudo apt-get -y -qq install g++-10-i686-linux-gnu 2>&1 > /dev/null + sudo apt-get -y -qq install g++-10-arm-linux-gnueabihf 2>&1 > /dev/null + sudo apt-get -y -qq install g++-10-aarch64-linux-gnu 2>&1 > /dev/null + sudo apt-get -y -qq install libncurses-dev 2>&1 > /dev/null + wget -q http://ftp.de.debian.org/debian/pool/main/l/llvm-toolchain-12/libc++-12-dev_12.0.1-20+b1_i386.deb 2>&1 > /dev/null + dpkg-deb -x libc++-12-dev_12.0.1-20+b1_i386.deb clang+llvm-12.0.1-i386-linux-gnu 2>&1 > /dev/null + wget -q http://ftp.de.debian.org/debian/pool/main/l/llvm-toolchain-12/libc++abi-12-dev_12.0.1-20+b1_i386.deb 2>&1 > /dev/null + dpkg-deb -x libc++abi-12-dev_12.0.1-20+b1_i386.deb clang+llvm-12.0.1-i386-linux-gnu 2>&1 > /dev/null + wget -q http://de.archive.ubuntu.com/ubuntu/pool/main/n/ncurses/libncurses-dev_6.2-0ubuntu2_i386.deb 2>&1 > /dev/null + dpkg-deb -x libncurses-dev_6.2-0ubuntu2_i386.deb libncurses-dev_6.2-0ubuntu2_i386 2>&1 > /dev/null + wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/clang+llvm-12.0.1-aarch64-linux-gnu.tar.xz 2>&1 > /dev/null + tar xf clang+llvm-12.0.1-aarch64-linux-gnu.tar.xz 2>&1 > /dev/null + wget -q https://mirrors.ecnu.edu.cn/ubuntu-ports/ubuntu-ports/pool/main/n/ncurses/libncurses-dev_6.2-0ubuntu2_arm64.deb 2>&1 > /dev/null + dpkg-deb -x libncurses-dev_6.2-0ubuntu2_arm64.deb libncurses-dev_6.2-0ubuntu2_arm64 2>&1 > /dev/null + wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/clang+llvm-12.0.1-armv7a-linux-gnueabihf.tar.xz 2>&1 > /dev/null + tar xf clang+llvm-12.0.1-armv7a-linux-gnueabihf.tar.xz 2>&1 > /dev/null + wget -q https://mirrors.ecnu.edu.cn/ubuntu-ports/ubuntu-ports/pool/main/n/ncurses/libncurses-dev_6.2-0ubuntu2_armhf.deb 2>&1 > /dev/null + dpkg-deb -x libncurses-dev_6.2-0ubuntu2_armhf.deb libncurses-dev_6.2-0ubuntu2_armhf 2>&1 > /dev/null + wget -q https://dl.google.com/android/repository/android-ndk-r23b-linux.zip 2>&1 > /dev/null + unzip -qq android-ndk-r23b-linux.zip 2>&1 > /dev/null + # + clang++-12 --target=i686-linux-gnu --sysroot=/usr/i686-linux-gnu \ + -L$(pwd)/clang+llvm-12.0.1-i386-linux-gnu/usr/lib/llvm-12/lib \ + -I$(pwd)/libncurses-dev_6.2-0ubuntu2_i386/usr/include \ + -L$(pwd)/libncurses-dev_6.2-0ubuntu2_i386/usr/lib/i386-linux-gnu \ + noso-2m.cpp md5-c.cpp -o noso-2m-linux-i686 \ + -std=c++20 -Ofast -DNDEBUG \ + --stdlib=libc++ -fuse-ld=lld \ + -lpthread -lc++ -lc++abi \ + -lncurses -lform -ltermcap \ + -static -s + file noso-2m-linux-i686 + # + clang++-12 \ + noso-2m.cpp md5-c.cpp -o noso-2m-linux-x86_64 \ + -std=c++20 -Ofast -DNDEBUG \ + --stdlib=libc++ -fuse-ld=lld \ + -lpthread -lc++ -lc++abi \ + -lncurses -lform -ltermcap \ + -static -s + file noso-2m-linux-x86_64 + # + clang++-12 --target=arm-linux-gnueabihf --sysroot=/usr/arm-linux-gnueabihf \ + -L$(pwd)/clang+llvm-12.0.1-armv7a-linux-gnueabihf/lib \ + -I$(pwd)/libncurses-dev_6.2-0ubuntu2_armhf/usr/include \ + -L$(pwd)/libncurses-dev_6.2-0ubuntu2_armhf/usr/lib/arm-linux-gnueabihf \ + noso-2m.cpp md5-c.cpp -o noso-2m-linux-armv7a \ + -std=c++20 -Ofast -DNDEBUG \ + --stdlib=libc++ -fuse-ld=lld \ + -lpthread -lc++ -lc++abi \ + -lncurses -lform -ltermcap \ + -static -s + file noso-2m-linux-armv7a + # + clang++-12 --target=aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu \ + -L$(pwd)/clang+llvm-12.0.1-aarch64-linux-gnu/lib \ + -I$(pwd)/libncurses-dev_6.2-0ubuntu2_arm64/usr/include \ + -L$(pwd)/libncurses-dev_6.2-0ubuntu2_arm64/usr/lib/aarch64-linux-gnu \ + noso-2m.cpp md5-c.cpp -o noso-2m-linux-aarch64 \ + -std=c++20 -Ofast -DNDEBUG \ + --stdlib=libc++ -fuse-ld=lld \ + -lpthread -lc++ -lc++abi \ + -lncurses -lform -ltermcap \ + -static -s + file noso-2m-linux-aarch64 + # + wget -q https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.2.tar.gz 2>&1 > /dev/null + tar xf ncurses-6.2.tar.gz 2>&1 > /dev/null + mkdir armv7a-linux-androideabi-ncurses + mkdir aarch64-linux-android-ncurses + cd ncurses-6.2 + CC=$(pwd)/../android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi31-clang \ + STRIP=$(pwd)/../android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip \ + ./configure \ + --host=armv7a-linux-androideabi \ + --prefix=$(pwd)/../armv7a-linux-androideabi-ncurses \ + --without-manpages --without-progs --without-tack --without-tests 2>&1 > /dev/null + make 2>&1 > /dev/null + make install 2>&1 > /dev/null + CC=$(pwd)/../android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang \ + STRIP=$(pwd)/../android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip \ + ./configure \ + --host=aarch64-linux-android \ + --prefix=$(pwd)/../aarch64-linux-android-ncurses \ + --without-manpages --without-progs --without-tack --without-tests 2>&1 > /dev/null + make 2>&1 > /dev/null + make install 2>&1 > /dev/null + cd .. + android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi31-clang++ \ + -I$(pwd)/armv7a-linux-androideabi-ncurses/include \ + -I$(pwd)/armv7a-linux-androideabi-ncurses/include/ncurses \ + -L$(pwd)/armv7a-linux-androideabi-ncurses/lib \ + noso-2m.cpp md5-c.cpp -o noso-2m-android-armv7a \ + -std=c++20 -Ofast -DNDEBUG \ + -lncurses -lform \ + -static -s + file noso-2m-android-armv7a + # + android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang++ \ + -I$(pwd)/aarch64-linux-android-ncurses/include \ + -I$(pwd)/aarch64-linux-android-ncurses/include/ncurses \ + -L$(pwd)/aarch64-linux-android-ncurses/lib \ + noso-2m.cpp md5-c.cpp -o noso-2m-android-aarch64 \ + -std=c++20 -Ofast -DNDEBUG \ + -lncurses -lform \ + -static -s + file noso-2m-android-aarch64 + shell: bash + - name: Upload artifacs + uses: actions/upload-artifact@v2 + with: + name: 'ubuntu-latest' + path: | + README.md + noso-2m-linux-i686 + noso-2m-linux-x86_64 + noso-2m-linux-armv7a + noso-2m-linux-aarch64 + noso-2m-android-armv7a + noso-2m-android-aarch64 + build-windows: + name: 'Build Windows' + runs-on: 'windows-latest' + steps: + - name: Checkout source + uses: actions/checkout@v2 + - name: Build source + run: | + clang++ --target=i686-pc-win32 \ + -Imingw-w64-clang-i686-ncurses-6_3\\include \ + -Imingw-w64-clang-i686-ncurses-6_3\\include\\ncurses \ + mingw-w64-clang-i686-ncurses-6_3\\lib\\libncurses.dll.a \ + mingw-w64-clang-i686-ncurses-6_3\\lib\\libform.dll.a \ + noso-2m.cpp md5-c.cpp -o noso-2m-i686.exe \ + -std=c++20 -Ofast -DNOGDI -DNDEBUG \ + -Wl,-machine:x86 -nostdlib -lWs2_32.lib -lmsvcrt + clang++ --target=x86_64-pc-win32 \ + -Imingw-w64-clang-x86_64-ncurses-6_3\\include \ + -Imingw-w64-clang-x86_64-ncurses-6_3\\include\\ncurses \ + mingw-w64-clang-x86_64-ncurses-6_3\\lib\\libncurses.dll.a \ + mingw-w64-clang-x86_64-ncurses-6_3\\lib\\libform.dll.a \ + noso-2m.cpp md5-c.cpp -o noso-2m-x86_64.exe \ + -std=c++20 -Ofast -DNOGDI -DNDEBUG \ + -Wl,-machine:x64 -nostdlib -lWs2_32.lib -lmsvcrt + shell: bash + - name: Upload artifacs + uses: actions/upload-artifact@v2 + with: + name: 'windows-latest' + path: | + README.md + mingw-w64-clang-i686-ncurses-6_3\\bin\\libncurses6.dll + mingw-w64-clang-i686-ncurses-6_3\\bin\\libform6.dll + noso-2m-i686.exe + mingw-w64-clang-x86_64-ncurses-6_3\\bin\\libncurses6.dll + mingw-w64-clang-x86_64-ncurses-6_3\\bin\\libform6.dll + noso-2m-x86_64.exe + build-macos: + name: 'Build macOS' + runs-on: 'macos-latest' + steps: + - name: Checkout source + uses: actions/checkout@v2 + - name: Build source + run: | + clang++ \ + noso-2m.cpp md5-c.cpp -o noso-2m-darwin \ + -std=c++20 -Ofast -DNDEBUG \ + -lncurses -lform -ltermcap + strip -x noso-2m-darwin + file noso-2m-darwin + shell: bash + - name: Upload artifacs + uses: actions/upload-artifact@v2 + with: + name: 'macos-latest' + path: | + README.md + noso-2m-darwin + release-linux: + if: contains(github.ref, '/tags/') + name: 'Release ubuntu-latest' + runs-on: 'ubuntu-latest' + needs: [build-linux] + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: Package artifacts + run: | + tag=${{ github.event.ref }} + tag=${tag#"refs/tags/"} + cp ubuntu-latest/README.md README.md + # + cp ubuntu-latest/noso-2m-linux-i686 noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-linux-i686.tar.gz noso-2m README.md + # + cp ubuntu-latest/noso-2m-linux-x86_64 noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-linux-x86_64.tar.gz noso-2m README.md + # + cp ubuntu-latest/noso-2m-linux-armv7a noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-linux-armv7a.tar.gz noso-2m README.md + # + cp ubuntu-latest/noso-2m-linux-aarch64 noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-linux-aarch64.tar.gz noso-2m README.md + # + cp ubuntu-latest/noso-2m-android-armv7a noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-android-armv7a.tar.gz noso-2m README.md + # + cp ubuntu-latest/noso-2m-android-aarch64 noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-android-aarch64.tar.gz noso-2m README.md + shell: bash + - name: Upload artifacts + uses: softprops/action-gh-release@v1 + with: + files: | + noso-2m-*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release-macos: + if: contains(github.ref, '/tags/') + name: 'Release macos-latest' + runs-on: 'macos-latest' + needs: [build-macos] + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: Package artifacts + run: | + tag=${{ github.event.ref }} + tag=${tag#"refs/tags/"} + cp macos-latest/README.md README.md + cp macos-latest/noso-2m-darwin noso-2m + chmod +x noso-2m + tar -zcvf noso-2m-${tag}-darwin.tar.gz noso-2m README.md + shell: bash + - name: Upload artifacts + uses: softprops/action-gh-release@v1 + with: + files: | + noso-2m-*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release-windows: + if: contains(github.ref, '/tags/') + name: 'Release windows-latest' + runs-on: 'windows-latest' + needs: [build-windows] + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: Package artifacts + run: | + tag=${{ github.event.ref }} + tag=${tag#"refs/tags/"} + cp windows-latest/README.md README.md + # + cp windows-latest/noso-2m-x86_64.exe noso-2m.exe + cp windows-latest/mingw-w64-clang-x86_64-ncurses-6_3\\bin\\libncurses6.dll libncurses6.dll + cp windows-latest/mingw-w64-clang-x86_64-ncurses-6_3\\bin\\libform6.dll libform6.dll + 7z a -tzip noso-2m-${tag}-win64-x86_64.zip noso-2m.exe libncurses6.dll libform6.dll README.md + # + cp windows-latest/noso-2m-i686.exe noso-2m.exe + cp windows-latest/mingw-w64-clang-i686-ncurses-6_3\\bin\\libncurses6.dll libncurses6.dll + cp windows-latest/mingw-w64-clang-i686-ncurses-6_3\\bin\\libform6.dll libform6.dll + 7z a -tzip noso-2m-${tag}-win32-i686.zip noso-2m.exe libncurses6.dll libform6.dll README.md + shell: bash + - name: Upload artifacts + uses: softprops/action-gh-release@v1 + with: + files: | + noso-2m-*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ff7dab --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Local clang build flags +noso-2m +compile_commands.json +compile_flags.txt +cache +*.log +.DS_Store + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +#*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +#*.a +*.lib + +# Executables +*.exe +*.out +*.app + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ab00994 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +Version 0.2.3 + +- Support config file +- `--pools` option now supports both IP address and domain names +- Correct summary report of actual hashrate, balance, payment +- Fixed unexpected exits in Windows. +- Minor refining + +Version 0.2.2 + +- Quick patch number overflow in block summary + +Version 0.2.2 + +- Protect new target from racing conditions +- Block summary fixing +- Update pool protocol for providing miner app's information +- Update seed nodes +- Mainnet timestamp check before mininig + +Version 0.2.0 + +- Support pool mining mode, plus pools failover +- Hashrate improvements +- Support CI/CD build for Linux, Android(Termux), macOS and Windows on amd64/x86-64, i686, arm64/aarch64, arm(v7a) 64-bits and 32-bits versions +- Eliminate redundant information in the summary report (will be improved more next versions) +- Bugfixes and several improvements +- Pump version series to v0.2.x + +Version 0.1.3 + +- Update mainnet seed nodes + +Version 0.1.2 + +- Consensus only once at beginning each blocks +- Now buildable with C++14 +- Bug fixed and refining algo + +Version 0.1.1 + +- Windows support + +Version 0.1.0 + +- First release diff --git a/README.md b/README.md new file mode 100644 index 0000000..8945d89 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# NOSO-2M +![Build Status](https://github.com/f04ever/noso-2m/actions/workflows/build-release.yml/badge.svg) +[![Support Linux](https://img.shields.io/badge/support-Linux-blue?logo=Linux)](https://github.com/f04ever/noso-2m/releases/latest) +[![Support Windows](https://img.shields.io/badge/support-Windows-blue?logo=Windows)](https://github.com/f04ever/noso-2m/releases/latest) +[![Support macOs](https://img.shields.io/badge/support-macOS-blue?logo=macOS)](https://github.com/f04ever/noso-2m/releases/latest) +[![Support Android](https://img.shields.io/badge/support-Android-blue?logo=Android)](https://github.com/f04ever/noso-2m/releases/latest) +[![Downloads](https://img.shields.io/github/downloads/f04ever/noso-2m/total)](https://github.com/f04ever/noso-2m/releases) + +A miner for Nosocryptocurrency Protocol-2. + +`noso-2m` supports mining both ***solo*** and ***pool*** modes on mainnet. `noso-2m` supports *_failover_* to other pools in pool mining modes. + +`noso-2m` be developed using C/C++, compatible with standards C++17/20. It is expected be buildable and executable on a wide range of hardware architectures (Intel, AMD, arm, aarch64) and operating systems (Linux, macOS, Android (Termux), and Windows). + +From version 0.2.4, noso-2m supports a simple text UI that expects to help new users starting with noso-2m easier. A logging file `noso-2m.log` is provided as well for advanced users. A command line provides information during mining. The command `pools` shows information of pools listed in config file or provided when run the `noso-2m` program. Use command `help` for more utilised commands and helps. On Microsoft Windows, don't try to resize the console window since a problematic inherited from the NCURSES library causes text shows weird (but not effect the mining jobs). + +![Screenshot](images/textui.png) + +## Run `noso-2m` miner + +`noso-2m` currently provides executable 64-bits and 32-bits versions for Linux, Android(Termux), macOS, and Windows pre-built on architectures amd64/x86\_64, aarch64/arm64, i686, and arm. Just download the appropriate version, uncompress the archive and run it from command shell as bellow: + +### On Linux, MacOS, or Android (Termux) + +`./noso-2m -a WALLETADDRESS -t THREADCOUNT 2>errors.txt` + +### On Windows + +`.\noso-2m.exe -a WALLETADDRESS -t THREADCOUNT 2> errors.txt` + +** NODES: + +- By default, `noso-2m` does mining using arguments loading from config file `noso-2m.cfg` at the same location of `noso-2m` program if exist. + +- Config file formation like below +
+

`address WALLETADDRESS`

+

`minerid MINERID****`

+

`threads THREADCOUNT`

+

`pools POOL-URL-LIST`

+

`solo false`

+
+ +- Config file can locate at diffent location and be specified using option `--config="PATH-TO-CONFIG-FILE"`. + +- Arguments loading from config file be overwrited by options provided in the running command. + +- By default, `noso-2m` does mining using `pool mining mode`, and does failover between two pools `f04ever` pool and `devnoso` pool. + +- Provide pool addresses to the running command by using option `--pools="POOL-URL-LIST"` (opening and closing quotation marks (`"`) are mandatory if having more than one pool URLs provided) + +- If more than one pool address be provided, `noso-2m` will do failover between provided pools when the current mining pool unreacheable (pool off, network problem, ...). + +- Formation of `POOL-URL-LIST` as following: + + + `POOL-URL-LIST` is a list of `POOL-URL`s, separate each other pool by a semicolon (`;`), ex.: `POOL-URL-1;POOL-URL-2;POOL-URL-3` + + + `POOL-URL` has formation: `POOL-NAME:POOL-ADDRESS:POOL-PORT`, the colon (`:`) be used to separate parts. + + + `POOL-NAME` is an arbitrary name, ex.: devnoso, my-pool, pool-1, pool-2, ... + + + `POOL-ADDRESS` is either a valid IP address or a domain name of pool. + + + `POOL-PORT` is a valid port number of pool, omitted `POOL-PORT` then the default port `8082` be used instead. + + + Pools `f04ever` and `devnoso` can also be provided to `POOL-URL-LIST` in a short form by their names and omit `POOL-IP-ADDRESS` and `POOL-PORT` as well. + + + An example: `./noso-2m -a N3G1HhkpXvmLcsWFXySdAxX3GZpkMFS -i 1000 -t 8 --pools="f04ever:209.126.80.203:8082;devnoso:45.146.252.103:8082"` + +- Use option `--solo` for mainnet solo mining mode instead. Should provide `Miner ID` by option `-i` / `--minerid` if mining from more than one machine using one wallet address in solo mode. + +- Use `--help` for the more command detail. + +## Build from source + +On Linux/macOS/Android(Termux), requires clang, or gcc. + +On Windows, requires clang and Build Tools for Visual Studio. + +From version 0.2.4, need library NCURSES for the text UI. NCURSES is provided already (or very easy to install) on Linux/macOS/Android platforms, on Windows requires a NCURSES on top of MinGW. + +** NOTES: + +- Currently `noso-2m` is compatiple with C++14/17/20. So, clang version 3.4 or later, or gcc version 6.1 or later. Recommend to build `noso-2m` with `c++20`. + +- Can replace `c++20` in the build commands below by `c++17` for older versions of clang, gcc, or Windows Build Tools + +Simple command for download source code and build `noso-2m` as below: + +### On Linux, MacOS, or Android (Termux) + +`clang++ noso-2m.cpp md5-c.cpp -o noso-2m -std=c++20 -O3 -DNDEBUG --stdlib=libc++ -fuse-ld=lld -lpthread -lc++abi -lncurses -lform -ltermcap` + +Or use gcc, + +`g++ noso-2m.cpp md5-c.cpp -o noso-2m -std=c++20 -O3 -DNDEBUG -lpthread -lncurses -lform -ltermcap` + +### On Windows + +`clang++ noso-2m.cpp md5-c.cpp -o noso-2m.exe -std=c++20 -O2 -DNDEBUG -lWs2_32.lib` + +Or use clang compatible driver mode for Microsoft Build Tools + +`clang-cl noso-2m.cpp md5-c.cpp /o noso-2m.exe /std:c++20 /O2 /EHsc /DNDEBUG /link Ws2_32.lib` + +## Donations + +Nosocoin: `devteam_donations` + +** The donations will go to `devteam_donations` - the wallet address of the [nosocoin's development team](https://www.nosocoin.com/) as they deserve (***it is not my personal address***) diff --git a/cxxopts.hpp b/cxxopts.hpp new file mode 100644 index 0000000..2dd4da6 --- /dev/null +++ b/cxxopts.hpp @@ -0,0 +1,2714 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 0 +#define CXXOPTS__VERSION_PATCH 0 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + using String = icu::UnicodeString; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, UChar32 c) + { + for (size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + using String = std::string; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, const String& a) + { + return s.append(a); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } // namespace + +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + class OptionException : public std::exception + { + public: + explicit OptionException(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + explicit OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + explicit OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + explicit option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + explicit invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + explicit option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + explicit option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + explicit missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + explicit option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + explicit option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class option_has_no_value_exception : public OptionException + { + public: + explicit option_has_no_value_exception(const std::string& option) + : OptionException( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + explicit argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + explicit option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + template + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif + } + + namespace values + { + namespace parser_tool + { + struct IntegerDesc + { + std::string negative = ""; + std::string base = ""; + std::string value = ""; + }; + struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; + }; +#ifdef CXXOPTS_NO_REGEX + inline IntegerDesc SplitInteger(const std::string &text) + { + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; + } + + inline bool IsFalseText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; + } + + inline std::pair SplitSwitchDef(const std::string &text) + { + std::string short_sw, long_sw; + const char *pdata = text.c_str(); + if (isalnum(*pdata) && *(pdata + 1) == ',') { + short_sw = std::string(1, *pdata); + pdata += 2; + } + while (*pdata == ' ') { pdata += 1; } + if (isalnum(*pdata)) { + const char *store = pdata; + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { + pdata += 1; + } + if (*pdata == '\0') { + long_sw = std::string(store, pdata - store); + } else { + throw_or_mimic(text); + } + } + return std::pair(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; + } + +#else // CXXOPTS_NO_REGEX + + namespace + { + + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + } // namespace + + inline IntegerDesc SplitInteger(const std::string &text) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); + } + + inline bool IsFalseText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); + } + + inline std::pair SplitSwitchDef(const std::string &text) + { + std::match_results result; + std::regex_match(text.c_str(), result, option_specifier); + if (result.empty()) + { + throw_or_mimic(text); + } + + const std::string& short_sw = result[2]; + const std::string& long_sw = result[3]; + + return std::pair(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; + } + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) const {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } // namespace detail + + template + void + checked_negate(R& r, T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); + } + + template + void + checked_negate(R&, T&&, const std::string& text, std::false_type) + { + throw_or_mimic(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } + + template ::value>::type* = nullptr + > + void parse_value(const std::string& text, T& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template ::value>::type* = nullptr + > + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } // namespace values + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + std::string short_, + std::string long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(m_long + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + long_name() const + { + return m_long; + } + + size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + std::string m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + size_t m_hash{}; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name{}; + std::string description{}; + std::vector options{}; + }; + + class OptionValue + { + public: + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_name = &details->long_name(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_name = &details->long_name(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& details) + { + m_long_name = &details->long_name(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + + CXXOPTS_NODISCARD + size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic pop +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_name == nullptr ? "" : *m_long_name); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const std::string* m_long_name = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + using ParsedHashMap = std::unordered_map; + using NameHashMap = std::unordered_map; + + class ParseResult + { + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; + + Iterator() = default; + Iterator(const Iterator&) = default; + + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) + , m_iter(end? pr->m_defaults.end(): pr->m_sequential.begin()) + { + } + + Iterator& operator++() + { + ++m_iter; + if(m_iter == m_pr->m_sequential.end()) + { + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator& other) const + { + return m_iter == other.m_iter; + } + + bool operator!=(const Iterator& other) const + { + return !(*this == other); + } + + const KeyValue& operator*() + { + return *m_iter; + } + + const KeyValue* operator->() + { + return m_iter.operator->(); + } + + private: + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + }; + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + Iterator + begin() const + { + return Iterator(this); + } + + Iterator + end() const + { + return Iterator(this, true); + } + + size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + const std::vector& + defaults() const + { + return m_defaults; + } + + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; + }; + + struct Option + { + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; + + using OptionMap = std::unordered_map>; + using PositionalList = std::vector; + using PositionalListIterator = PositionalList::const_iterator; + + class OptionParser + { + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; + }; + + class Options + { + public: + + explicit Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list