diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef1fafb..5fd9c8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,34 +6,66 @@ on: - '*' - '*/*' - '!master' + pull_request: + branches: [ develop ] jobs: compile: strategy: + fail-fast: false matrix: - os: [ ubuntu-16.04, ubuntu-18.04, macos-latest, windows-latest ] - build-opts: [ "-DFORCE_ANSI=ON", "-DFORCE_ANSI=OFF" ] + os: [ ubuntu-20.04, ubuntu-22.04, macos-latest, windows-latest ] + build: [ cli, cli-mt, cli-ansi, cli-ansi-mt, gui, gui-mt ] + include: + - build: cli + cmake-opts: -DCOMPONENT_CLI=ON -DCOMPONENT_GUI=OFF -DFORCE_ANSI=OFF -DUSE_THREADS=OFF + - build: cli-mt + cmake-opts: -DCOMPONENT_CLI=ON -DCOMPONENT_GUI=OFF -DFORCE_ANSI=OFF -DUSE_THREADS=ON + - build: cli-ansi + cmake-opts: -DCOMPONENT_CLI=ON -DCOMPONENT_GUI=OFF -DFORCE_ANSI=ON -DUSE_THREADS=OFF + - build: cli-ansi-mt + cmake-opts: -DCOMPONENT_CLI=ON -DCOMPONENT_GUI=OFF -DFORCE_ANSI=ON -DUSE_THREADS=ON + - build: gui + cmake-opts: -DCOMPONENT_CLI=OFF -DCOMPONENT_GUI=ON -DUSE_THREADS=OFF + - build: gui-mt + cmake-opts: -DCOMPONENT_CLI=OFF -DCOMPONENT_GUI=ON -DUSE_THREADS=ON - name: ${{matrix.os}} (${{matrix.build-opts}}) + name: ${{matrix.os}} (${{matrix.build}}) runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - - name: Dependancies + - name: OpenSSL OSX + if: runner.os == 'macOS' + shell: bash + run: brew install openssl + + - name: Qt - Ubuntu + if: startsWith(matrix.build, 'gui') && runner.os == 'Linux' shell: bash run: | - if [ "$RUNNER_OS" == "macOS" ]; then - brew install openssl + sudo apt-get update + if [ "${{matrix.os}}" == "ubuntu-22.04" ]; then + sudo apt-get install qtbase5-dev=5.15.3+dfsg-2ubuntu0.2 + else : + sudo apt-get install qt5-default=5.12.8+dfsg-0ubuntu2.1 fi + - name: Qt - Windows/Mac + if: startsWith(matrix.build, 'gui') && runner.os != 'Linux' + uses: jurplel/install-qt-action@v3 + with: + version: '5.15.2' + - name: Configure shell: bash run: | + OPTS="${{matrix.cmake-opts}}" if [ "$RUNNER_OS" == "macOS" ]; then - SSL_OPTS="-D OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1" + OPTS="-D OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 ${OPTS}" fi - cmake ${SSL_OPTS} ${{matrix.build-opts}} -B build + cmake ${OPTS} -B build - name: Build shell: bash @@ -42,5 +74,5 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v1 with: - name: ${{matrix.os}}_${{matrix.build-opts}}-build + name: ${{matrix.os}}_${{matrix.build}}-build path: build diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 8398b5e..30106ea 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -9,19 +9,36 @@ jobs: ubuntu: strategy: matrix: - os: [ ubuntu-16.04, ubuntu-18.04 ] - name: ${{matrix.os}} + os: [ ubuntu-20.04, ubuntu-22.04 ] + component: [ cli, gui ] + include: + - os: ubuntu-20.04 + qt: qt5-default=5.12.8+dfsg-0ubuntu2.1 + - os: ubuntu-22.04 + qt: qtbase5-dev=5.15.3+dfsg-2ubuntu0.2 + - component: cli + build-opts: --cli --no-gui -t Release --pkg-type deb + - component: gui + build-opts: --no-cli --gui -t Release --pkg-type deb + name: DEB ${{matrix.os}} (${{matrix.component}}) runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 + + - name: Qt + if: startsWith(matrix.component, 'gui') + shell: bash + run: | + sudo apt-get update + sudo apt-get install ${{matrix.qt}} - name: Package id: package run: | - ./build.sh -t Release --pkg-type deb + ./build.sh ${{matrix.build-opts}} FILE=$(ls build/*.deb | head -1) - echo "::set-output name=filename::$FILE" - echo "::set-output name=name::$(basename $FILE)" + echo "filename=$FILE" >> "$GITHUB_OUTPUT" + echo "name=$(basename $FILE)" >> "$GITHUB_OUTPUT" - name: Upload uses: actions/upload-release-asset@v1 @@ -33,35 +50,105 @@ jobs: asset_path: ${{ steps.package.outputs.filename }} asset_content_type: application/x-deb - # Windows installer - windows: + # RPM package for redhat based systems. + rpm: strategy: matrix: - arch: [ x86, x64 ] - name: Windows (${{matrix.arch}}) - runs-on: windows-latest + container: + - name: "fedora:36" + deps: + gcc-12.2.1-4.fc36.x86_64 + gcc-c++-12.2.1-4.fc36.x86_64 + cmake-3.26.3-1.fc36.x86_64 + openssl1.1-devel-1.1.1q-1.fc36.x86_64 + qt: qt5-qtbase-devel-5.15.3-1.fc36.x86_64 + + - name: "fedora:37" + deps: + gcc-12.3.1-1.fc37.x86_64 + gcc-c++-12.3.1-1.fc37.x86_64 + cmake-3.27.1-1.fc37.x86_64 + openssl-devel-3.0.9-1.fc37.x86_64 + qt: qt5-qtbase-devel-5.15.9-3.fc37.x86_64 + + - name: "fedora:38" + deps: + gcc-13.2.1-1.fc38.x86_64 + gcc-c++-13.2.1-1.fc38.x86_64 + cmake-3.26.2-1.fc38.x86_64 + openssl-devel-1:3.0.9-2.fc38.x86_64 + qt: qt5-qtbase-devel-5.15.10-5.fc38.x86_64 + component: [ cli, gui ] + include: + - component: cli + build-opts: --cli --no-gui -t Release --pkg-type rpm + - component: gui + build-opts: --no-cli --gui -t Release --pkg-type rpm + name: RPM ${{matrix.container.name}} (${{matrix.component}}) + runs-on: ubuntu-latest + container: ${{ matrix.container.name }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - - name: Configure + - name: Dependancies run: | - if ("${{matrix.arch}}" -eq "x86") { - $PLATFORM="Win32" - } else { - $PLATFORM="${{matrix.arch}}" - } - cmake -A $PLATFORM -D CPACK_GENERATOR=NSIS -S . -B build + sudo dnf install -y util-linux rpmdevtools git ${{ matrix.container.deps }} - - name: Build - run: cmake --build build --config Release + - name: Qt + if: startsWith(matrix.component, 'gui') + shell: bash + run: | + sudo dnf install -y ${{ matrix.container.qt }} - name: Package id: package run: | - cmake --build build --target package + ./build.sh ${{matrix.build-opts}} + FILE=$(ls build/*.rpm | head -1) + echo "filename=$FILE" >> "$GITHUB_OUTPUT" + echo "name=$(basename $FILE)" >> "$GITHUB_OUTPUT" + + - name: Upload + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_name: ${{ steps.package.outputs.name }} + asset_path: ${{ steps.package.outputs.filename }} + asset_content_type: application/octet-stream + + # Windows installer + windows: + strategy: + matrix: + arch: [ Win32, x64 ] + include: + - arch: Win32 + qt-arch: win32_msvc2015 + - arch: x64 + qt-arch: win64_msvc2015_64 + name: Windows (${{matrix.arch}}) + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Qt + uses: jurplel/install-qt-action@v3 + with: + arch: ${{ matrix.qt-arch }} + version: '5.11.0' + + - name: Configure + run: cmake -A ${{matrix.arch}} -DCOMPONENT_GUI=ON -DCOMPONENT_CLI=ON -D CMAKE_BUILD_TYPE=Release -D CPACK_GENERATOR=NSIS -S . -B build + + - name: Package + id: package + run: | + cmake --build build --config Release --target package $FILE=(ls build/*.exe) - echo "::set-output name=filename::$FILE" - echo "::set-output name=name::$(([io.fileinfo]"$FILE").basename).exe" + echo "filename=$FILE" >> "$GITHUB_OUTPUT" + echo "name=$(([io.fileinfo]"$FILE").basename)" >> "$GITHUB_OUTPUT" - name: Upload uses: actions/upload-release-asset@v1 diff --git a/.gitignore b/.gitignore index 6f31401..02e2c31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ .vscode/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index d5c1d1d..3074eea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,108 +1,141 @@ cmake_minimum_required(VERSION 3.15) -# Project name and version -project(eosio-keygen - VERSION 1.0.4 - DESCRIPTION "Keygenerator for EOSIO" - HOMEPAGE_URL "https://github.com/eosswedenorg/eosio-keygen" ) +# -------------------------------- +# Project Info +# -------------------------------- + +project(antelope-keygen + VERSION 1.1.0 + DESCRIPTION "Keygenerator for Antelope based blockchain" + HOMEPAGE_URL "https://github.com/eosswedenorg/antelope-keygen" ) set( PROJECT_MAINTAINER "Henrik Hautakoski ") +set( PROJECT_LICENSE_FILE ${CMAKE_CURRENT_LIST_DIR}/LICENSE ) -# Options -option(FORCE_ANSI "Force ANSI console colors even on windows" OFF) +# -------------------------------- +# Options +# -------------------------------- -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") +option(COMPONENT_CLI "Build CLI Component" ON) +option(COMPONENT_GUI "Build GUI Component (Qt5)" OFF) -include(libeoskeygen) +if (NOT COMPONENT_CLI AND NOT COMPONENT_GUI) + message(FATAL_ERROR "Atleast one of BUILD_COMPONENT_GUI,BUILD_COMPONENT_CLI must be set to ON") +endif() -# Use installpath from GNUInstallDirs as default. +# -------------------------------- +# CMake settings +# -------------------------------- + +# Append modules dir +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# Default to debug build if none is set. +if (NOT CMAKE_BUILD_TYPE) + set( CMAKE_BUILD_TYPE Debug ) +endif() + +# Install path include(GNUInstallDirs) set( CMAKE_INSTALL_SHAREDIR ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME} ) if (WIN32) # "Flat" install on windows. - set( CMAKE_INSTALL_BINDIR "." ) + set( CMAKE_INSTALL_BINDIR "bin" ) set( CMAKE_INSTALL_DATADIR "." ) set( CMAKE_INSTALL_SHAREDIR "." ) set( CMAKE_INSTALL_MANDIR "." ) endif (WIN32) -# Configure the compiler options +# Path to a directory outside of CMAKE_BUILD_DIR +# Should be used to cache large downloaded data that won't be deleted on clean builds. +set( DOWNLOAD_CACHE_DIR ${CMAKE_CURRENT_LIST_DIR}/.cache ) + +set( components ) +if (COMPONENT_CLI) + list(APPEND components cli ) +endif() + +if (COMPONENT_GUI) + list(APPEND components gui ) +endif() + +# -------------------------------- +# Compiler settings +# -------------------------------- + set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) set( CMAKE_CXX_EXTENSIONS OFF ) -if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wconversion -Wno-sign-conversion -Wextra" ) -elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_definitions( "-D_CRT_SECURE_NO_WARNINGS=1" ) -endif() +# c++ flags +add_compile_options( + "$<$:-Wall;-Wconversion;-Wno-sign-conversion;-Wextra>" + "$<$:/W3;-D_CRT_SECURE_NO_WARNINGS=1>" -# -------------------------------- -# Program -# -------------------------------- + # Debug + "$<$:$<$:-O0;-g>>" + "$<$:$<$:/Od;/Zi>>" -set (PROGRAM_EXE ${CMAKE_PROJECT_NAME}) + # Release + "$<$:$<$:-O3>>" + "$<$:$<$:/O2>>" -set (PROGRAM_SOURCE - src/isatty.cpp - src/cli_key_search_result.cpp - src/console.cpp - src/benchmark.cpp - src/main.cpp + # MinSizeRel + "$<$:$<$:-Os>>" + "$<$:$<$:/O1>>" ) -if (WIN32 AND NOT FORCE_ANSI) - set (PROGRAM_SOURCE ${PROGRAM_SOURCE} src/console_win32.cpp) -else() - # *nix should have ansi support. - set (PROGRAM_SOURCE ${PROGRAM_SOURCE} src/console_ansi.cpp) -endif() +add_link_options( + # Release + "$<$:$<$:-s>>" +) + +include(CheckPIESupported) +check_pie_supported() +#cmake_policy(SET CMP0083 NEW) + +set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) # Project config file -configure_file(src/config.h.in "${PROJECT_BINARY_DIR}/config.h" @ONLY) +configure_file(config.hpp.in "${PROJECT_BINARY_DIR}/config.hpp" @ONLY) include_directories(${PROJECT_BINARY_DIR}) -add_executable( ${PROGRAM_EXE} ${PROGRAM_SOURCE} ) - -target_link_libraries( ${PROGRAM_EXE} PUBLIC eoskeygen ) - -# -------------------------------- -# Install -# -------------------------------- - -install(TARGETS ${PROGRAM_EXE} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - -# Readme and license -install(FILES README.md LICENSE LICENSE.bitcoin - DESTINATION ${CMAKE_INSTALL_SHAREDIR}) - -# Documentation - -if (UNIX) - configure_file( docs/eosio-keygen.1.in ${PROJECT_BINARY_DIR}/man1/eosio-keygen.1 ) - - install(DIRECTORY ${PROJECT_BINARY_DIR}/man1 - DESTINATION ${CMAKE_INSTALL_MANDIR}) -endif (UNIX) +# Bundle antelope-extras on windows. +if (WIN32) + include(extras) + list(APPEND components extras ) +endif() # -------------------------------- # Package # -------------------------------- +include(CPackConfig) + +set( CPACK_COMPONENTS_ALL ${components} ) + if (CPACK_GENERATOR MATCHES "^[Nn][Ss][Ii][Ss]$") - set( NSIS_LICENSE_FILE ${PROJECT_BINARY_DIR}/NSIS_LICENSE ) - file(WRITE ${NSIS_LICENSE_FILE} "") - foreach(file IN ITEMS LICENSE LICENSE.bitcoin) - file(READ ${CMAKE_CURRENT_LIST_DIR}/${file} content) - file(APPEND ${NSIS_LICENSE_FILE} "--- ${file}\n\n${content}\n") - endforeach() - set( CPACK_RESOURCE_FILE_LICENSE ${NSIS_LICENSE_FILE} ) + set( CPACK_RESOURCE_FILE_LICENSE ${PROJECT_LICENSE_FILE} ) endif() -set( CPACK_DEBIAN_PACKAGE_PRIORITY "optional" ) -set( CPACK_DEBIAN_PACKAGE_SECTION "misc" ) -set( CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON ) +set (CPACK_PROPERTIES_FILE ${PROJECT_BINARY_DIR}/CPackComponentsInclude.cmake) +configure_file(cmake/CPackProperties.cmake.in ${CPACK_PROPERTIES_FILE} @ONLY) -include( cpack_custom ) +# -------------------------------- +# Components +# -------------------------------- + +add_subdirectory( common ) + +foreach(comp ${components}) + if ( EXISTS ${CMAKE_CURRENT_LIST_DIR}/${comp} ) + add_subdirectory( ${comp} ) + endif() +endforeach() + +# -------------------------------- +# CPack +# -------------------------------- +include (CPack) diff --git a/CMakeModules/libeoskeygen.cmake b/CMakeModules/libeoskeygen.cmake deleted file mode 100644 index d58803f..0000000 --- a/CMakeModules/libeoskeygen.cmake +++ /dev/null @@ -1,52 +0,0 @@ - -# -------------------------------- -# Variables -# -------------------------------- -set( LIBEOSKEYGEN_GIT_URL "https://github.com/eosswedenorg/libeoskeygen.git" ) -set( LIBEOSKEYGEN_WANTED_VERSION 0.1.1 ) - -# -------------------------------- -# Macros -# -------------------------------- -macro(fromGit tag) - - message ("Using libeoskeygen from: ${LIBEOSKEYGEN_GIT_URL}@${tag}") - - include(FetchContent) - FetchContent_Declare(libeoskeygen - GIT_REPOSITORY ${LIBEOSKEYGEN_GIT_URL} - GIT_TAG ${tag} - ) - - FetchContent_GetProperties(libeoskeygen) - if (NOT libeoskeygen_POPULATED) - FetchContent_Populate(libeoskeygen) - add_subdirectory(${libeoskeygen_SOURCE_DIR} ${libeoskeygen_BINARY_DIR} EXCLUDE_FROM_ALL) - endif() -endmacro() - -macro(buildLocal src) - message ("Using local libeoskeygen at: ${src}") - add_subdirectory(${src} ${src}/build EXCLUDE_FROM_ALL) -endmacro() - -# If we have a local libeoskeygen -if (LIBEOSKEYGEN_SOURCE_DIR) - buildLocal( ${LIBEOSKEYGEN_SOURCE_DIR} ) -else() - - # Check if version is in fact a version. - if (LIBEOSKEYGEN_WANTED_VERSION MATCHES "^[0-9]+(.[0-9]+)?(.[0-9]+)(-[a-zA-Z0-9]+)?$") - # Try finding the package on the system. - find_package(libeoskeygen ${LIBEOSKEYGEN_WANTED_VERSION} QUIET) - if (libeoskeygen_FOUND) - message ("Using libeoskeygen in: ${libeoskeygen_DIR}") - # Not found, download from git. - else() - fromGit( v${LIBEOSKEYGEN_WANTED_VERSION} ) - endif() - # Assume version contains a git branch. - else() - fromGit( ${LIBEOSKEYGEN_WANTED_VERSION} ) - endif() -endif() diff --git a/LICENSE b/LICENSE index ea09337..635055a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2020 EOS Sw/eden +Copyright (c) 2019-2023 EOS Sw/eden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE.bitcoin b/LICENSE.bitcoin deleted file mode 100644 index 9d54ecb..0000000 --- a/LICENSE.bitcoin +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2009-2019 The Bitcoin Core developers -Copyright (c) 2009-2019 Bitcoin Developers - -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. diff --git a/README.md b/README.md index 3b72adf..5609aa3 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,25 @@ -![](https://github.com/eosswedenorg/eosio-keygen/workflows/CI/badge.svg) -[![GitHub release](https://img.shields.io/github/v/release/eosswedenorg/eosio-keygen?include_prereleases)](https://github.com/eosswedenorg/eosio-keygen/releases/latest) +[![CI Test](https://github.com/eosswedenorg/antelope-keygen/workflows/CI/badge.svg)](https://github.com/eosswedenorg/antelope-keygen/actions) +[![GitHub release](https://img.shields.io/github/v/release/eosswedenorg/antelope-keygen?include_prereleases)](https://github.com/eosswedenorg/antelope-keygen/releases/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -# EOSIO Keygen +# Antelope Keygen -This program generates public and private keypair for [EOS](https://eos.io/) +This program generates public and private keypair for [Antelope IO](https://antelope.io) ## Compile -You will need `libeoskeygen` and `cmake 3.15` or later to compile this project. +You will need `libantelope` and `cmake 3.15` or later to compile this project. + +`Qt 5.9.0` or later is required for the graphical program. ### Linux/MacOS +**NOTE:** Only Ubuntu 20.04 and 22.04 and Fedoora 36 is officially supported. + +The project should compile fine on most versions/distros but it is only tested +and distributed for those distros/versions by [Sw/eden](http://www.eossweden.org). + #### Dependencies #### Linux @@ -25,31 +32,6 @@ First you need to have a compiler. this can be installed with apt. $ apt-get install gcc g++ ``` -You then need `libeoskeygen`. - -This can be installed from [EOS Sweden's APT Repository](https://eosswedenorg.github.io/apt) like this: - -```sh -$ sudo apt-get install software-properties-common -$ curl https://apt.eossweden.org/key 2> /dev/null | sudo apt-key add - -$ sudo apt-add-repository -y 'deb [arch=amd64] https://apt.eossweden.org/main `lsb_release -cs` stable' -$ sudo apt-get install libeoskeygen-dev -``` - -or manually via `.deb` file from [github](https://github.com/eosswedenorg/libeoskeygen/releases) - -```sh -$ wget -$ sudo apt install ./libeoskeygen-dev-.deb -``` - -**Other**: - -Consult the manual for you package manager. -`libeoskeygen` will be downloaded and compiled if it's not installed automatically (however this is slower). - -Consult [libeoskeygen's github](https://github.com/eosswedenorg/libeoskeygen) if you want to compile and install it manually. - **CMake** If your package manager don't provide a sufficiently new version of cmake, you can install it with these commands: @@ -70,8 +52,23 @@ $ wget -O cmake.sh https://github.com/Kitware/CMake/releases/download/v3.15.5/cm Other methods is documanted at https://cmake.org/download +**Qt (only for gui program)** + +You will need to install `qt >= 5.9.0` + +On **ubuntu** you can do this via `apt` + +``` +$ apt-get install qt5-default +``` + +For other distributions, download the official [qt-installer](https://www.qt.io/download-qt-installer). +It is recommended to install the latest stable version. + #### MacOS +**NOTE:** Support for MacOS is still experimental and may or may not work as expected. + You must have a compiler installed. This project is known to build with `Xcode 11.0` but other versions should work. You need to have cmake installed also, this can be done with this `brew` command: @@ -81,7 +78,15 @@ $ brew install cmake If you need a newer version of cmake, you can download the official `.dmg` file: [cmake-3.15.7-Darwin-x86_64.dmg](https://github.com/Kitware/CMake/releases/download/v3.15.7/cmake-3.15.7-Darwin-x86_64.dmg). or see https://cmake.org/download for other versions. -`libeoskeygen` needs to be compiled and installed from source. [Go here](https://github.com/eosswedenorg/libeoskeygen) +`libantelope` needs to be compiled and installed from source. [Go here](https://github.com/eosswedenorg/libantelope) + +**Qt (only for gui program)** + +This can be installed with brew. + +```sh +$ brew install qt +``` #### Build @@ -105,7 +110,9 @@ Download and install `cmake` version `3.15` or newer from [cmake.org](https://cm You will also need a compiler. [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (Selecting C++ during installation) is recommended. +**Qt (only for gui program)** +Download the official [qt-installer](https://www.qt.io/download-qt-installer) and follow the steps. It is recommended to install the latest stable version. #### Build. @@ -117,10 +124,16 @@ you need to set `OPENSSL_ROOT_DIR` to the directory where you unpacked For example: ``` -C:\repo> mkdir build -C:\repo> cd build -C:\repo\build> cmake -D OPENSSL_ROOT_DIR="C:/path/to/openssl-1.1/x86" .. -C:\repo\build> cmake --build . --config Release +C:\repo> cmake -D OPENSSL_ROOT_DIR="C:/path/to/openssl-1.1/x86" -B build +C:\repo> cmake --build build --config Release +``` + +**Qt** + +If you are compiling the gui program. you will need to point `cmake` to the location `qt` where installed. this can be done by the `CMAKE_PREFIX_PATH` variable: + +``` +C:\repo> cmake -D CMAKE_PREFIX_PATH="C:/path/to/qt/msvc2017_64" ``` ## Compile options @@ -128,12 +141,40 @@ C:\repo\build> cmake --build . --config Release These compile options are available: | Cmake | build.sh | Description | -|--------------------------- | ----------------- | ------------------------------------------| -| -DCMAKE_BUILD_TYPE=`value` | -t `value` | Type of build | +|--------------------------- | ----------------- | ----------------------------------------- | +| -DCOMPONENT_CLI=`OFF` | --no-cli | Do not build cli program | +| -DCOMPONENT_GUI=`ON` | --gui | Build gui program (Qt5) | +| -DUSE_THREADS=`OFF` | --disable-threads | Disable thread support | +| -DCMAKE_BUILD_TYPE=`value` | -t `value` | Type of build | | -DFORCE_ANSI=`ON` | --force-ansi | Force ANSI console colors even on windows | For more details about options run `./build.sh -l` or `mkdir build && cmake build -LA` +### libantelope + +To speed up the build process, you can install `libantelope` + +#### Ubuntu + +You can use [Sw/eden's APT Repository](https://eosswedenorg.github.io/apt) like this: + +```sh +$ sudo apt-get install software-properties-common +$ curl https://apt.eossweden.org/key 2> /dev/null | sudo apt-key add - +$ sudo apt-add-repository -y 'deb [arch=amd64] https://apt.eossweden.org/main `lsb_release -cs` stable' +$ sudo apt-get install libantelope-dev +``` +or manually via `.deb` file from [github](https://github.com/eosswedenorg/libantelope/releases) + +```sh +$ wget +$ sudo apt install ./libantelope-dev-.deb +``` + +#### Other + +Consult [libantelope's github](https://github.com/eosswedenorg/libantelope) + ## Install After the project has been compiled. run `sudo ./install.sh` or the following code if you dont want to use that: @@ -154,13 +195,12 @@ Run `sudo ./uninstall.sh` or remove the files listed in `build/install_manifest. ## Security notice -Keys are generated by `OpenSSL`'s `EC_KEY_generate_key` function. The program will -never expose your keys to anything but the computers memory and output of the -program. You are free to inspect the source code and compile yourself to verify. +Keys are generated using [libantelope](https://github.com/eosswedenorg/libantelope) +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). -However, use this at your own risk. we cannot guarantee that the keys are -cryptographically secure as this depends on OpenSSL's implementation (alto it is -widely used and should be safe) +Use at your own risk. The author and [Sw/eden](https://eossweden.org/) does not take responsability +for any damage caused by keys generated by the program. Please read the `LICENSE` file. @@ -175,4 +215,4 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Author -Henrik Hautakoski - [henrik@eossweden.org](mailto:henrik@eossweden.org) +Henrik Hautakoski - [Sw/eden](https://eossweden.org/) - [henrik@eossweden.org](mailto:henrik@eossweden.org) diff --git a/build.sh b/build.sh index 05ef766..7a6d1d7 100755 --- a/build.sh +++ b/build.sh @@ -1,18 +1,20 @@ #!/bin/bash function usage() { - echo "Usage: ${0##*/} [ -h|--help ] [ -t|--type Debug|Release|RelWithDebInfo|MinSizeRel ] [--pkg-type deb|zip|tgz] [ --disable-threads ] [ --force-ansi ]" + echo "Usage: ${0##*/} [ -h|--help ] [ --cli|--no-cli ] [ --gui|--no-gui] [ -t|--type Debug|Release|RelWithDebInfo|MinSizeRel ] [ --libeosio= ] [ --pkg-type nsis|deb|zip|tgz ] [ --disable-threads ] [ --force-ansi ]" exit 1 } -options=$(getopt -n "${0##*/}" -o "lht:" -l "help,type:,pkg-type:,disable-threads,force-ansi" -- "$@") +options=$(getopt -n "${0##*/}" -o "lht:" -l "help,cli,no-cli,gui,no-gui,type:,libeosio:,pkg-type:,disable-threads,force-ansi" -- "$@") [ $? -eq 0 ] || usage eval set -- "$options" +TARGET="all" ONLY_CONFIG=0 ARGS="" +BUILD_ARGS="--clean-first" while true; do case $1 in @@ -23,16 +25,29 @@ while true; do usage } ARGS="${ARGS} -DCMAKE_BUILD_TYPE=${1}" + BUILD_ARGS="${BUILD_ARGS} --config ${1}" ;; --pkg-type) shift - [[ ! "$1" =~ ^(deb|zip|tgz)$ ]] && { + [[ ! "$1" =~ ^(nsis|deb|rpm|zip|tgz)$ ]] && { echo "Incorrect package type '$1' provided" usage } TARGET="package" ARGS="${ARGS} -DCPACK_GENERATOR=${1^^}" ;; + --libeosio) + shift + ARGS="${ARGS} -DLIBEOSIO_SOURCE_DIR=${1}" + ;; + --cli) + ARGS="${ARGS} -DCOMPONENT_CLI=ON" ;; + --no-cli) + ARGS="${ARGS} -DCOMPONENT_CLI=OFF" ;; + --gui) + ARGS="${ARGS} -DCOMPONENT_GUI=ON" ;; + --no-gui) + ARGS="${ARGS} -DCOMPONENT_GUI=OFF" ;; --disable-threads) ARGS="${ARGS} -DUSE_THREADS=OFF" ;; --force-ansi) @@ -48,7 +63,10 @@ while true; do shift done +# Remove cache first +rm build/CMakeCache.txt 2> /dev/null + cmake -B build $ARGS . if [ ${ONLY_CONFIG} -eq 0 ]; then - cmake --build build --clean-first --target ${TARGET} + cmake --build build ${BUILD_ARGS} --target ${TARGET} fi diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt new file mode 100644 index 0000000..ff3512f --- /dev/null +++ b/cli/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.15) + +# -------------------------------- +# Project Info +# -------------------------------- + +project(antelope-keygen + VERSION ${CMAKE_PROJECT_VERSION} + LANGUAGES CXX) + +# Options +option(FORCE_ANSI "Force ANSI console colors even on windows" OFF) + +# -------------------------------- +# Program +# -------------------------------- + +set (PROGRAM_EXE ${PROJECT_NAME}) + +set (PROGRAM_SOURCE + src/isatty.cpp + src/cli_key_search_result.cpp + src/console.cpp + src/benchmark.cpp + src/main.cpp +) + +if (WIN32 AND NOT FORCE_ANSI) + set (PROGRAM_SOURCE ${PROGRAM_SOURCE} src/console_win32.cpp) +else() + # *nix should have ansi support. + set (PROGRAM_SOURCE ${PROGRAM_SOURCE} src/console_ansi.cpp) +endif() + +add_executable( ${PROGRAM_EXE} ${PROGRAM_SOURCE} ) + +# Include CLI11 +include ( ${CMAKE_SOURCE_DIR}/lib/CLI11/CMakeLists.txt ) + +target_include_directories( ${PROGRAM_EXE} PRIVATE ${LIBCLI11_INCLUDE} ) +target_link_libraries( ${PROGRAM_EXE} PUBLIC common ) + +# -------------------------------- +# Install +# -------------------------------- + +install(TARGETS ${PROGRAM_EXE} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT cli) + +install(FILES ${PROJECT_LICENSE_FILE} + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT cli + RENAME LICENSE.cli) + +install (FILES ${LIBCLI11_LICENSE} + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT cli + RENAME LICENSE.libcli11) + + +# Documentation + +configure_file( docs/README.md.in ${PROJECT_BINARY_DIR}/README.cli.md @ONLY ) +install(FILES ${PROJECT_BINARY_DIR}/README.cli.md + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT cli) + +if (UNIX) + configure_file( docs/antelope-keygen.1.in ${PROJECT_BINARY_DIR}/man1/antelope-keygen.1 ) + configure_file( docs/antelope-keygen-search.1.in ${PROJECT_BINARY_DIR}/man1/antelope-keygen-search.1 ) + configure_file( docs/antelope-keygen-benchmark.1.in ${PROJECT_BINARY_DIR}/man1/antelope-keygen-benchmark.1 ) + + install(DIRECTORY ${PROJECT_BINARY_DIR}/man1 + DESTINATION ${CMAKE_INSTALL_MANDIR} + COMPONENT cli) +endif (UNIX) + +# -------------------------------- +# Package +# -------------------------------- + +configure_file(cmake/CPackComponentConfig.cmake.in + ${PROJECT_BINARY_DIR}/CPackProperties.cmake) diff --git a/cli/cmake/CPackComponentConfig.cmake.in b/cli/cmake/CPackComponentConfig.cmake.in new file mode 100644 index 0000000..a8b5500 --- /dev/null +++ b/cli/cmake/CPackComponentConfig.cmake.in @@ -0,0 +1,16 @@ + +# -------------------------------- +# CPack Component Config +# -------------------------------- + +set( CPACK_PACKAGE_CLI_NAME "${PROJECT_NAME}" ) +set( CPACK_PACKAGE_CLI_CONTACT "${PROJECT_MAINTAINER}" ) + +set( CPACK_COMPONENT_CLI_DESCRIPTION "Command line application" ) + +# Debian specific +set( CPACK_DEBIAN_CLI_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}-${CPACK_SYSTEM_NAME}-${CPACK_SYSTEM_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") +set( CPACK_DEBIAN_CLI_PACKAGE_NAME "${PROJECT_NAME}" ) +set( CPACK_DEBIAN_CLI_PACKAGE_PRIORITY "optional" ) +set( CPACK_DEBIAN_CLI_PACKAGE_SECTION "misc" ) +set( CPACK_DEBIAN_CLI_PACKAGE_DEPENDS "libssl1.1 (>= 1.1.0) | libssl3 (>= 3.0.0), libstdc++6 (>= 6)") diff --git a/cli/docs/README.md.in b/cli/docs/README.md.in new file mode 100644 index 0000000..17768e5 --- /dev/null +++ b/cli/docs/README.md.in @@ -0,0 +1,106 @@ + +# @PROJECT_NAME@ (cli) + +Generate public and private keypair for [Antelope IO](https://antelope.io) + +Source code is available at [github.com](https://github.com/eosswedenorg/antelope-keygen) + +## Synopsis + +```shell +@PROJECT_NAME@ [-h|--help] + +@PROJECT_NAME@ [-v] + +@PROJECT_NAME@ search [-m] [--l33t] [--threads ] [--dict ...] [--lang ...] word_list [count] + +@PROJECT_NAME@ benchmark [num_keys] +``` + +## Description + +Output one Antelope key pair if no arguments are given. +Options and subcommands are as follows: + +### General flags + +| syntax | Description | +| --------------- | ---------------- | +| -h, --help | Shows help text. | +| -v | Shows version | + + +### search command + +`@PROJECT_NAME@ search [-m] [--l33t] [--threads ] [--dict ...] [--lang ...] word_list [count]` + +performs a search, finding `count` public keys containing one or more words from `word_list` (separated with ','). + +Instead of a list it is possible to specify a file with words (separated with newline '\n') using `file:/path/to/file` + +#### Search specific options + +#### -m + +Monochrome, disables all color output. + +#### --leet + +Takes each word in `word_list` and find all l33tspeak combinations of that word and uses the new list for the search. + +#### --threads num + +Use `num` of parallel threads for searching. Default is what the operating system recommends. + +#### --dict file +Use words found in `file` (separated by newline) to highlight words in the keys +found (note that the words in this file are not used for search. only for highlight output). + +There can be more then one `--dict` flag. In that case contents of all files are merged into one dictionary. + +#### --lang value + +Same as `--dict` but will use `value` to find a file in `@CMAKE_INSTALL_FULL_DATADIR@/@CMAKE_PROJECT_NAME@/dict`. +There can be more then one `--lang` flag. In that case contents of all files are merged into one dictionary. + +#### count + +Number of keys to search for (default is 10) + + +### benchmark command + +`@PROJECT_NAME@ benchmark [num_keys]` + +performs a benchmark test, generating `num_keys` keys and measuring the time. + +#### Benchmark specific options: + +#### num_keys + +Number of keys to search for (default is 10) + + +## Security notice + +Keys are generated using [libantelope](https://github.com/eosswedenorg/libantelope) +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). + +Use at your own risk. The author and [Sw/eden](https://eossweden.org/) does not take responsability +for any damage caused by keys generated by the program. + +Please read the `LICENSE.cli` file. + +``` +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. +``` + +## Author + +Henrik Hautakoski - [Sw/eden](https://eossweden.org/) - [henrik@eossweden.org](mailto:henrik@eossweden.org) diff --git a/cli/docs/antelope-keygen-benchmark.1.in b/cli/docs/antelope-keygen-benchmark.1.in new file mode 100644 index 0000000..cd16012 --- /dev/null +++ b/cli/docs/antelope-keygen-benchmark.1.in @@ -0,0 +1,67 @@ +.TH @PROJECT_NAME@-benchmark 1 "April, 2023" "@PROJECT_NAME@-benchmark @PROJECT_VERSION@" + +.SH NAME +@PROJECT_NAME@ benchmark - Benchmark the performance of the @PROJECT_NAME@ key generator. + +.SH SYNOPSIS + +.SY @PROJECT_NAME@ +benchmark +.OP \-h|--help +.YS + +.SY @PROJECT_NAME@ +benchmark +.OP num_keys +.YS + +.SH DESCRIPTION + +performs a benchmark test, generating \fInum_keys\fR (default 1000) keys and measuring the time. + +.SH SECURITY NOTICE + +.PP +Keys are generated using +.UR https://github.com/eosswedenorg/libantelope +libantelope +.UE . +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). + +.PP +Use at your own risk. The author and +.UR https://eossweden.org +Sw/eden +.UE +does not take responsability for any damage caused by keys generated by the program. + +.P +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +.br +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +.br +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +.br +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +.br +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +.br +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +.SH BUGS + +Report bugs to +.UR https://github.com/eosswedenorg/eosio-keygen/issues +Github +.UE . Thank you. + +.SH AUTHOR + +.MT henrik@eossweden.org +Henrik Hautakoski +.ME + +.UR https://eossweden.org +EOS Sw/eden +.UE \ No newline at end of file diff --git a/cli/docs/antelope-keygen-search.1.in b/cli/docs/antelope-keygen-search.1.in new file mode 100644 index 0000000..1ab0af3 --- /dev/null +++ b/cli/docs/antelope-keygen-search.1.in @@ -0,0 +1,112 @@ +.TH @PROJECT_NAME@-search 1 "April, 2023" "@PROJECT_NAME@-search @PROJECT_VERSION@" + +.SH NAME +@PROJECT_NAME@ search - Search after +.UR https://antelope.io +Antelope IO +.UE +vanity keys. + +.SH SYNOPSIS + +.SY @PROJECT_NAME@ +search +.OP \-h|--help +.YS + +.SY @PROJECT_NAME@ +search +.OP -m +.OP \--l33t +.OP \--threads +.OP \--dict ... +.OP \--lang ... +.B word_list +.OP count +.YS + +.SH DESCRIPTION + +.PP +performs a search, finding \fIcount\fR public keys containing one or more words from +\fIword_list\fR (separated with ','). +.PP +Instead of a list it is possible to specify a file with words (separated with newline \fB'\\n'\fR) using +\fIfile: and find all l33tspeak combinations of that word and uses the new list for the search. +.TP +\fB\-\-threads\fR \fInum\fR +Use <\fInum\fR> of parallel threads for searching. Default is what the operating system recommends. +.TP +\fB\-\-dict\fR \fIfile\fR +Use words found in \fIfile\fR (separated by newline) to highlight words in the keys found. +.br +There can be more then one \fB\-\-dict\fR flag. +In that case contents of all files are merged into one dictionary. +.br +\fBnote:\fR the words in this file are not used for search. only for highlight output. +.TP +\fB\-\-lang\fR \fIvalue\fR +Same as \fB\-\-dict\fR but will use \fIvalue\fR to find a file in +\fB@CMAKE_INSTALL_FULL_DATADIR@/@CMAKE_PROJECT_NAME@/dict\fR. +.br +There can be more then one \fB\-\-lang\fR flag. In that case contents of all files are merged into one dictionary. +.TP +\fBcount\fR +Number of keys to search for (default is 10) + +.SH SECURITY NOTICE + +.PP +Keys are generated using +.UR https://github.com/eosswedenorg/libantelope +libantelope +.UE . +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). + +.PP +Use at your own risk. The author and +.UR https://eossweden.org +Sw/eden +.UE +does not take responsability for any damage caused by keys generated by the program. + +.P +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +.br +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +.br +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +.br +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +.br +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +.br +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +.SH BUGS + +Report bugs to +.UR https://github.com/eosswedenorg/eosio-keygen/issues +Github +.UE . Thank you. + +.SH AUTHOR + +.MT henrik@eossweden.org +Henrik Hautakoski +.ME + +.UR https://eossweden.org +EOS Sw/eden +.UE \ No newline at end of file diff --git a/cli/docs/antelope-keygen.1.in b/cli/docs/antelope-keygen.1.in new file mode 100644 index 0000000..16273cc --- /dev/null +++ b/cli/docs/antelope-keygen.1.in @@ -0,0 +1,106 @@ +.TH @PROJECT_NAME@ 1 "April, 2023" "@PROJECT_NAME@ @PROJECT_VERSION@" + +.SH NAME +@PROJECT_NAME@ - Generate public and private keypair for +.UR https://antelope.io +Antelope IO +.UE . + +.SH SYNOPSIS + +.SY @PROJECT_NAME@ +.OP \-h|--help +.YS + +.SY @PROJECT_NAME@ +.OP \-v +.YS + +.SY @PROJECT_NAME@ +.OP \--format +.YS + +.SH SUBCOMMANDS + +.PP +\fB@PROJECT_NAME@ search\fR +.RS 4 +Search after +.UR https://antelope.io +Antelope IO +.UE +vanity keys. +.br +see \fB@PROJECT_NAME@-search\fR(1) +.RE + +.PP +\fB@PROJECT_NAME@ benchmark\fR +.RS 4 +Benchmark the performance of the @PROJECT_NAME@ key generator. +.br +see \fB@PROJECT_NAME@-benchmark\fR(1) +.RE + +.SH DESCRIPTION +.PP +Output one Antelope key pair if no arguments are given +.PP +Options and subcommands are as follows: + +.TP +\fB\-h\fR, \fB\-\-help\fR +Shows this help text. +.TP +\fB\-v\fR +Shows version. +.TP +\fB\-\-format\fR \fR\fI\,value\/\fR +What keyformat to use, valid values are: \fIK1\fR, \fIlegacy\fR, \fIfio\fR + +.SH SECURITY NOTICE + +.PP +Keys are generated using +.UR https://github.com/eosswedenorg/libantelope +libantelope +.UE . +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). + +.PP +Use at your own risk. The author and +.UR https://eossweden.org +Sw/eden +.UE +does not take responsability for any damage caused by keys generated by the program. + +.P +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +.br +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +.br +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +.br +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +.br +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +.br +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +.SH BUGS + +Report bugs to +.UR https://github.com/eosswedenorg/eosio-keygen/issues +Github +.UE . Thank you. + +.SH AUTHOR + +.MT henrik@eossweden.org +Henrik Hautakoski +.ME + +.UR https://eossweden.org +EOS Sw/eden +.UE diff --git a/src/benchmark.cpp b/cli/src/benchmark.cpp similarity index 75% rename from src/benchmark.cpp rename to cli/src/benchmark.cpp index 43d8b35..01a8bbf 100644 --- a/src/benchmark.cpp +++ b/cli/src/benchmark.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,33 +22,29 @@ * SOFTWARE. */ #include -#include -#include "benchmark.h" +#include +#include "benchmark.hpp" -using std::chrono::steady_clock; -using std::chrono::duration; -using std::chrono::time_point; +namespace antelopekeygen { -namespace eoskeygen { +std::chrono::duration _run_benchmark(size_t num_keys) { + auto start = std::chrono::steady_clock::now(); + for(size_t i = 0; i < num_keys; i++) { + struct libantelope::ec_keypair k; + libantelope::ec_generate_key(&k); + } + return std::chrono::steady_clock::now() - start; +} void benchmark(size_t num_keys, struct benchmark_result* res) { - time_point start; - if (num_keys < 1) { res->sec = res->kps = 0; return; } - start = steady_clock::now(); - - for(size_t i = 0; i < num_keys; i++) { - struct ec_keypair k; - ec_generate_key(&k); - } - - res->sec = duration(steady_clock::now() - start).count(); + res->sec = _run_benchmark(num_keys).count(); res->kps = static_cast(num_keys) / res->sec; } -} // namespace eoskeygen +} // namespace antelopekeygen diff --git a/src/benchmark.h b/cli/src/benchmark.hpp similarity index 93% rename from src/benchmark.h rename to cli/src/benchmark.hpp index 8b1f57b..180872e 100644 --- a/src/benchmark.h +++ b/cli/src/benchmark.hpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ #include -namespace eoskeygen { +namespace antelopekeygen { struct benchmark_result { float sec; // elapsed seconds. @@ -35,6 +35,6 @@ struct benchmark_result { void benchmark(size_t num_keys, struct benchmark_result* res); -} // namespace eoskeygen +} // namespace antelopekeygen #endif /* EOSIOKEYGEN_BENCHMARK_H */ diff --git a/src/cli_key_search_result.cpp b/cli/src/cli_key_search_result.cpp similarity index 69% rename from src/cli_key_search_result.cpp rename to cli/src/cli_key_search_result.cpp index b61ea3a..d70b7cb 100644 --- a/src/cli_key_search_result.cpp +++ b/cli/src/cli_key_search_result.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include #include -#include -#include -#include "console.h" -#include "cli_key_search_result.h" +#include +#include +#include "console.hpp" +#include "cli_key_search_result.hpp" -namespace eoskeygen { +namespace antelopekeygen { static size_t highlight(console::Color color, const std::string& str, size_t pos, size_t len) { @@ -37,21 +38,23 @@ static size_t highlight(console::Color color, const std::string& str, size_t pos return len; } -CliKeySearchResult::CliKeySearchResult(const Dictionary& dict) : -m_dict (dict) +CliKeySearchResult::CliKeySearchResult(const Dictionary& dict, const libantelope::wif_codec_t& codec) : +m_dict (dict), +m_codec (codec) { } -void CliKeySearchResult::onResult(const struct ec_keypair* key, const struct KeySearch::result& result) { +void CliKeySearchResult::onResult(const struct libantelope::ec_keypair* key, const struct KeySearch::result& result) { - std::string pub = wif_pub_encode(key->pub); + std::string pub = libantelope::wif_pub_encode(key->pub, m_codec.pub); Dictionary::search_result_t dict_res = m_dict.search(pub); + int pub_prefix_len = (int) m_codec.pub.length(); std::cout << "----" << std::endl; std::cout << "Found: " << pub.substr(result.pos, result.len) << std::endl; - std::cout << "Public: EOS"; - for(size_t i = 3; i < pub.length(); ) { + std::cout << "Public: " << m_codec.pub; + for(size_t i = pub_prefix_len; i < pub.length(); ) { if (i == result.pos) { i += highlight(console::red, pub, result.pos, result.len); @@ -68,7 +71,7 @@ void CliKeySearchResult::onResult(const struct ec_keypair* key, const struct Key } std::cout << std::endl - << "Private: " << wif_priv_encode(key->secret) << std::endl; + << "Private: " << libantelope::wif_priv_encode(key->secret, m_codec.pvt) << std::endl; } -} // namespace eoskeygen +} // namespace antelopekeygen diff --git a/src/cli_key_search_result.h b/cli/src/cli_key_search_result.hpp similarity index 72% rename from src/cli_key_search_result.h rename to cli/src/cli_key_search_result.hpp index 2645aeb..1428477 100644 --- a/src/cli_key_search_result.h +++ b/cli/src/cli_key_search_result.hpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,27 +24,31 @@ #ifndef EOSIOKEYGEN_KEY_SEARCH_HELPERS_H #define EOSIOKEYGEN_KEY_SEARCH_HELPERS_H -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -namespace eoskeygen { +namespace antelopekeygen { class Dictionary; class CliKeySearchResult : public IKeySearchResult { public: - CliKeySearchResult(const Dictionary& dict); + CliKeySearchResult(const Dictionary& dict, const libantelope::wif_codec_t& codec); - virtual void onResult(const struct ec_keypair* key, const struct KeySearch::result& result); + virtual void onResult(const struct libantelope::ec_keypair* key, const struct KeySearch::result& result); protected : const Dictionary& m_dict; + + libantelope::wif_codec_t m_codec; }; -} // namespace eoskeygen +} // namespace antelopekeygen #endif /* EOSIOKEYGEN_KEY_SEARCH_HELPERS_H */ diff --git a/src/console.cpp b/cli/src/console.cpp similarity index 89% rename from src/console.cpp rename to cli/src/console.cpp index 2208856..9b3529d 100644 --- a/src/console.cpp +++ b/cli/src/console.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,10 +22,10 @@ * SOFTWARE. */ #include -#include "isatty.h" -#include "console.h" +#include "isatty.hpp" +#include "console.hpp" -namespace eoskeygen { namespace console { +namespace antelopekeygen { namespace console { bool disable_color = false; @@ -45,4 +45,4 @@ bool isColorsSupported(const std::ostream& os) { return disable_color == false && isatty(fd); } -} } // namespace eoskeygen::console +} } // namespace antelopekeygen::console diff --git a/src/console.h b/cli/src/console.hpp similarity index 95% rename from src/console.h rename to cli/src/console.hpp index 65da9aa..0dd2c46 100644 --- a/src/console.h +++ b/cli/src/console.hpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ #include -namespace eoskeygen { +namespace antelopekeygen { namespace console { @@ -86,6 +86,6 @@ namespace console { } // namespace console -} // namespace eoskeygen +} // namespace antelopekeygen #endif /* EOSIOKEYGEN_CONSOLE_H */ diff --git a/src/console_ansi.cpp b/cli/src/console_ansi.cpp similarity index 94% rename from src/console_ansi.cpp rename to cli/src/console_ansi.cpp index 2d34980..f49006c 100644 --- a/src/console_ansi.cpp +++ b/cli/src/console_ansi.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,9 +22,9 @@ * SOFTWARE. */ #include -#include "console.h" +#include "console.hpp" -namespace eoskeygen { +namespace antelopekeygen { namespace console { @@ -80,4 +80,4 @@ std::ostream& operator<<(std::ostream& os, const fg& obj) { } // namespace console -} // namespace eoskeygen +} // namespace antelopekeygen diff --git a/src/console_win32.cpp b/cli/src/console_win32.cpp similarity index 96% rename from src/console_win32.cpp rename to cli/src/console_win32.cpp index d3b1614..ec3b1b1 100644 --- a/src/console_win32.cpp +++ b/cli/src/console_win32.cpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ */ #include #include -#include "console.h" +#include "console.hpp" -namespace eoskeygen { +namespace antelopekeygen { // WinAPI colors #define FG_BLACK 0 @@ -105,4 +105,4 @@ std::ostream& operator<<(std::ostream& os, const fg& obj) { } // namespace console -} // namespace eoskeygen +} // namespace antelopekeygen diff --git a/cli/src/isatty.cpp b/cli/src/isatty.cpp new file mode 100644 index 0000000..75d0778 --- /dev/null +++ b/cli/src/isatty.cpp @@ -0,0 +1,45 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ + +#if _WIN32 +#include +#else +#include +#define _isatty isatty +#define _fileno fileno +#endif +#include "isatty.hpp" + +namespace antelopekeygen { + +bool isatty(int fd) { + return ::_isatty(fd); +} + +bool isatty(FILE* fd) { + // fileno() segfaults if fd is null. + return fd ? isatty(_fileno(fd)) : false; +} + +} // namespace antelopekeygen diff --git a/src/isatty.h b/cli/src/isatty.hpp similarity index 93% rename from src/isatty.h rename to cli/src/isatty.hpp index 0cf40c9..54d1e00 100644 --- a/src/isatty.h +++ b/cli/src/isatty.hpp @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,12 +26,12 @@ #include -namespace eoskeygen { +namespace antelopekeygen { bool isatty(int fd); bool isatty(FILE* fd); -} // namespace eoskeygen +} // namespace antelopekeygen #endif /* EOSIOKEYGEN_CORE_ISATTY_H */ diff --git a/cli/src/main.cpp b/cli/src/main.cpp new file mode 100644 index 0000000..0b8ca7a --- /dev/null +++ b/cli/src/main.cpp @@ -0,0 +1,247 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cli_key_search_result.hpp" +#include "console.hpp" +#include "benchmark.hpp" +#include "config.hpp" + +// Command line options. +bool option_l33t = false; +libantelope::wif_codec_t key_codec; + +#ifdef EOSIOKEYGEN_HAVE_THREADS +size_t option_num_threads; +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + +class CustomFormatter : public CLI::Formatter { +public: + + std::string make_usage(const CLI::App *app, std::string name) const + { + std::stringstream out; + + out << std::endl << CLI::Formatter::make_usage(app, name) + << std::endl + << "Outputs one EOSIO key pair if no subcommand is given" + << std::endl; + + return out.str(); + } +}; + +int cmd_search(const antelopekeygen::strlist_t& words, const antelopekeygen::Dictionary& dict, int count) { + + antelopekeygen::KeySearch ks; + antelopekeygen::CliKeySearchResult rs(dict, key_codec); + + ks.setPrefix(key_codec.pub); + ks.setCallback(&rs); + + for(auto it = words.begin(); it != words.end(); it++) { + size_t p = libantelope::is_base58(*it); + if (p != std::string::npos) { + std::cerr << "The word '" + << *it << "' contains an invalid non-base58 character '" + << (*it)[p] << "'" << std::endl; + return 1; + } + } + + if (option_l33t) { + for(std::size_t i = 0; i < words.size(); i++) { + ks.addList(antelopekeygen::l33twords(words[i])); + } + } else { + ks.addList(words); + } + +#ifdef EOSIOKEYGEN_HAVE_THREADS + ks.setThreadCount(option_num_threads); +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + + std::cout << "Searching for " << count + << " keys containing: " << antelopekeygen::strlist::join(ks.getList(), ",") +#ifdef EOSIOKEYGEN_HAVE_THREADS + << ", Using: " << ks.getThreadCount() << " threads" +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + << std::endl; + + ks.find(count); + + return 0; +} + +void cmd_benchmark(size_t num_keys) { + + struct antelopekeygen::benchmark_result res; + + std::cout << "Benchmark: Generating " + << num_keys << " keys" << std::endl; + + antelopekeygen::benchmark(num_keys, &res); + + std::cout << "Result: Took " << res.sec << " seconds, " + << res.kps << " keys per second." << std::endl; +} + +int main(int argc, char **argv) { + + CLI::App cmd("Keygenerator for Antelope based blockchains", PROGRAM_NAME); + std::vector dict_list; + std::vector lang_list; + std::string search_words; + std::string key_format; + int search_count; + size_t bench_count; + int rc = 0; + + libantelope::ec_init(); + + CLI::Option* version = cmd.add_flag("-v,--version", "Show version"); + cmd.add_option("--format", key_format, "valid values: K1, fio, legacy")->default_val("K1"); + + // Search + CLI::App* search_cmd = cmd.add_subcommand("search", + "performs a search, finding public keys containing " + "one or more words from (separated with \",\")"); + CLI::Option* monocrome = search_cmd->add_flag("-m", "Monochrome, disables all color output."); + + search_cmd->add_flag("--l33t", option_l33t, "Takes each word in and find all l33tspeak" + " combinations of that word and uses the new list for the search."); + +#ifdef EOSIOKEYGEN_HAVE_THREADS + search_cmd->add_option("--threads", option_num_threads, + "Use of parallel threads for searching.\n" + "Default is what the operating system recomends.") + ->default_val(antelopekeygen::KeySearch::max_threads()); + +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + + search_cmd->add_option("--dict", dict_list, ""); + search_cmd->add_option("--lang", lang_list, ""); + search_cmd->add_option("word_list", search_words, + "one or more words (separated with \",\")\n\n" + "Instead of a list it is possible to specify a file with words\n" + "(separated with newline '\\n') using file:")->required(); + search_cmd->add_option("count", search_count, "Number of keys to search for before the program terminates.")->default_val(10); + + // Benchmark + CLI::App* bench_cmd = cmd.add_subcommand("benchmark", "performs a benchmark test, " + "generating keys and measuring the time."); + bench_cmd->add_option("count", bench_count, "")->default_val(1000); + + // Parse command line. + cmd.formatter(std::make_shared()); + + CLI11_PARSE(cmd, argc, argv); + + if (*version) { + std::cout << PROGRAM_NAME << ": v" << PROGRAM_VERSION << std::endl; + goto end; + } + + if (key_format == "fio") { + key_codec = libantelope::wif_create_legacy_codec("FIO"); + } else if (key_format == "legacy") { + key_codec = libantelope::WIF_CODEC_LEG; + } else if (key_format == "K1") { + key_codec = libantelope::WIF_CODEC_K1; + } else { + std::cerr << "invalid key format: " << key_format << std::endl; + goto end; + } + + if (search_cmd->parsed()) { + antelopekeygen::strlist_t words; + antelopekeygen::Dictionary dict; + + if (*monocrome) { + antelopekeygen::console::disable_color = true; + } + + for (auto item : dict_list) { + antelopekeygen::Dictionary d; + + if (d.loadFromFile(item)) { + dict.add(d); + } else { + std::cerr << "Could not load dictionary from file: " << item << std::endl; + } + } + + for (auto item : lang_list) { + antelopekeygen::Dictionary d; + std::string filename(CONFIG_SHARE_FULL_PATH "/dicts/" + item); + + if (d.loadFromFile(filename)) { + dict.add(d); + } else { + std::cerr << "Could not load dictionary from language file: " << filename << std::endl; + } + } + + if (search_words.rfind("file:", 0) == 0) { + std::string filename = search_words.substr(5); + if (!antelopekeygen::readLines(filename, words)) { + std::cerr << "Could not read file: " << filename << std::endl; + goto end; + } + + if (words.size() < 1) { + std::cerr << filename << " did not contain any words" << std::endl; + goto end; + } + } else { + words = antelopekeygen::strlist::splitw(search_words); + } + + rc = cmd_search(words, dict, search_count); + goto end; + + } else if (bench_cmd->parsed()) { + cmd_benchmark(bench_count); + } + // No subcommand given, just generate and print a keypair. + else { + struct libantelope::ec_keypair pair; + libantelope::ec_generate_key(&pair); + libantelope::wif_print_key(&pair, key_codec); + goto end; + } + +end: libantelope::ec_shutdown(); + return rc; +} diff --git a/CMakeModules/cpack_custom.cmake b/cmake/CPackConfig.cmake similarity index 87% rename from CMakeModules/cpack_custom.cmake rename to cmake/CPackConfig.cmake index d4fad9b..8f55ba5 100644 --- a/CMakeModules/cpack_custom.cmake +++ b/cmake/CPackConfig.cmake @@ -93,12 +93,23 @@ set( CPACK_DEBIAN_PACKAGE_RELEASE "1" CACHE STRING "Debian package release versi # So we have to do it here. set( CPACK_DEBIAN_PACKAGE_HOMEPAGE "${PROJECT_HOMEPAGE_URL}" ) -# Set "correct" filename that also include system version and architecture. -set( CPACK_DEBIAN_FILE_NAME - "${PROJECT_NAME}-${PROJECT_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}-${CPACK_SYSTEM_NAME}-${CPACK_SYSTEM_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb" -) +# Always build components for deb packages +set( CPACK_DEB_COMPONENT_INSTALL ON ) +# RPM + +# Always build components for rpm packages +set( CPACK_RPM_COMPONENT_INSTALL ON ) + +# Same as with DEB package. +set( CPACK_RPM_PACKAGE_HOMEPAGE "${PROJECT_HOMEPAGE_URL}" ) + +set( CPACK_RPM_PACKAGE_RELEASE_DIST ON ) +set( CPACK_RPM_PACKAGE_RELEASE "1" CACHE STRING "RPM package release version" ) +set( CPACK_RPM_PACKAGE_LICENSE "MIT" ) +set( CPACK_RPM_FILE_NAME "RPM-DEFAULT" ) + # -------------------------------- # Generator default # -------------------------------- @@ -112,10 +123,3 @@ if (NOT CPACK_GENERATOR) set( CPACK_GENERATOR "ZIP" ) endif() endif() - - -# -------------------------------- -# Include original CPack module. -# -------------------------------- - -include( CPack ) diff --git a/cmake/CPackProperties.cmake.in b/cmake/CPackProperties.cmake.in new file mode 100644 index 0000000..6a75e0c --- /dev/null +++ b/cmake/CPackProperties.cmake.in @@ -0,0 +1,7 @@ + +# Include component specific config +foreach( component @CPACK_COMPONENTS_ALL@ ) + if (EXISTS "@CMAKE_CURRENT_BINARY_DIR@/${component}/CPackProperties.cmake") + include("@CMAKE_CURRENT_BINARY_DIR@/${component}/CPackProperties.cmake") + endif() +endforeach() diff --git a/cmake/extras.cmake b/cmake/extras.cmake new file mode 100644 index 0000000..a23a1fe --- /dev/null +++ b/cmake/extras.cmake @@ -0,0 +1,43 @@ + +set( EXTRAS_DIRECTORY ${DOWNLOAD_CACHE_DIR}/eosio-keygen-extras-0.1.0 ) +set( FILENAME ${DOWNLOAD_CACHE_DIR}/eosio-keygen-extras-0.1.0.zip ) + +# -------------------------------- +# Download/Unpack +# -------------------------------- + +if (NOT EXISTS ${FILENAME}) + + set( URL "https://github.com/eosswedenorg/eosio-keygen-extras/releases/download/v0.1.0/eosio-keygen-extras-0.1.0.zip" ) + set( CHECKSUM "7be3188a52a39876e37986e6a7d78e0d6c89e68e8391cf48821c800563aaa036" ) + + message( STATUS "Downloading ${URL}" ) + + file(DOWNLOAD ${URL} ${FILENAME} + TIMEOUT 60 + EXPECTED_HASH SHA256=${CHECKSUM} + TLS_VERIFY ON) +endif() + +if (NOT EXISTS ${EXTRAS_DIRECTORY}) + + message( STATUS "Unpacking ${FILENAME}" ) + + execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${FILENAME} + WORKING_DIRECTORY ${DOWNLOAD_CACHE_DIR}) +endif() + + +# -------------------------------- +# Install +# -------------------------------- + +install(DIRECTORY ${EXTRAS_DIRECTORY}/dict + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT extras ) + +# -------------------------------- +# CPack +# -------------------------------- + +set( CPACK_COMPONENT_EXTRAS_DESCRIPTION "Dictionary files" ) diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 0000000..3d2564d --- /dev/null +++ b/common/.gitignore @@ -0,0 +1 @@ +include/eoskeygen/config.h* diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..eb3e3a7 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,49 @@ +# ------------------------------------------------------------ +# Common CMake file +# +# Compiles the code that should be shared between the cli +# and gui programs into a static library. +# ------------------------------------------------------------ + +# Options +option(USE_THREADS "Compile with support for threads (if available)." ON) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +# -------------------------------- +# Library +# -------------------------------- +set( COMMON_NAME common ) + +set( COMMON_SOURCE + src/core/file.cpp + src/core/dictionary.cpp + src/core/string.cpp + src/core/strlist.cpp + src/core/leet.cpp + src/key_search.cpp +) + +# Threads support +if (USE_THREADS) + find_package(Threads) + if (Threads_FOUND) + set( ANTELOPEKEYGEN_HAVE_THREADS TRUE ) + set( COMMON_SOURCE ${COMMON_SOURCE} src/key_search_mt.cpp ) + endif (Threads_FOUND) +endif (USE_THREADS) + +# Project config file +configure_file(config.hpp.in "${CMAKE_CURRENT_LIST_DIR}/include/eoskeygen/config.hpp" @ONLY) + +add_library( ${COMMON_NAME} STATIC ${COMMON_SOURCE} ) + +target_include_directories( ${COMMON_NAME} PUBLIC include ) + +# Link with libantelope and threads library. +include( libantelope ) +target_link_libraries( ${COMMON_NAME} + PUBLIC + libantelope + ${CMAKE_THREAD_LIBS_INIT} +) diff --git a/common/cmake/libantelope.cmake b/common/cmake/libantelope.cmake new file mode 100644 index 0000000..85a0ccd --- /dev/null +++ b/common/cmake/libantelope.cmake @@ -0,0 +1,53 @@ +# -------------------------------- +# Variables +# -------------------------------- +set( LIBANTELOPE_GIT_URL "https://github.com/eosswedenorg/libantelope.git" ) +set( LIBANTELOPE_WANTED_VERSION v0.2.2 ) + +# -------------------------------- +# Macros +# -------------------------------- +macro(fromGit tag) + + message ("Using libantelope from: ${LIBANTELOPE_GIT_URL}@${tag}") + + include(FetchContent) + FetchContent_Declare(libantelope + GIT_REPOSITORY ${LIBANTELOPE_GIT_URL} + GIT_TAG ${tag} + SOURCE_DIR ${DOWNLOAD_CACHE_DIR}/libeosio/src + STAMP_DIR ${DOWNLOAD_CACHE_DIR}/libeosio/stamp + ) + + FetchContent_GetProperties(libantelope) + if (NOT libantelope_POPULATED) + FetchContent_Populate(libantelope) + add_subdirectory(${libantelope_SOURCE_DIR} ${libantelope_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() +endmacro() + +macro(buildLocal src) + message ("Using local libantelope at: ${src}") + add_subdirectory(${src} ${src}/build EXCLUDE_FROM_ALL) +endmacro() + +# If we have a local libantelope +if (LIBANTELOPE_SOURCE_DIR) + buildLocal( ${LIBANTELOPE_SOURCE_DIR} ) +else() + + # Check if version is in fact a version. + if (LIBANTELOPE_WANTED_VERSION MATCHES "^[0-9]+(.[0-9]+)?(.[0-9]+)(-[a-zA-Z0-9]+)?$") + # Try finding the package on the system. + find_package(libantelope ${LIBANTELOPE_WANTED_VERSION} QUIET) + if (libantelope_FOUND) + message ("Using libeosio in: ${libantelope_DIR}") + # Not found, download from git. + else() + fromGit( v${LIBANTELOPE_WANTED_VERSION} ) + endif() + # Assume version contains a git branch. + else() + fromGit( ${LIBANTELOPE_WANTED_VERSION} ) + endif() +endif() diff --git a/common/config.hpp.in b/common/config.hpp.in new file mode 100644 index 0000000..1cbe1c8 --- /dev/null +++ b/common/config.hpp.in @@ -0,0 +1,30 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CONFIG_H +#define ANTELOPEKEYGEN_COMMON_CONFIG_H + +// Defined if we have thread support. +#cmakedefine ANTELOPEKEYGEN_HAVE_THREADS + +#endif /* ANTELOPEKEYGEN_COMMON_CONFIG_H */ diff --git a/common/include/eoskeygen/core/dictionary.hpp b/common/include/eoskeygen/core/dictionary.hpp new file mode 100644 index 0000000..8008a30 --- /dev/null +++ b/common/include/eoskeygen/core/dictionary.hpp @@ -0,0 +1,75 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CORE_DICTIONARY_H +#define ANTELOPEKEYGEN_COMMON_CORE_DICTIONARY_H + +#include +#include +#include + +namespace antelopekeygen { + +class Dictionary +{ +public : + // Map that contains position and length for substrings. + // + // key = position in the search string. + // value = length of the word from this position. + typedef std::map< size_t, size_t > search_result_t; + +public : + + // Load words from file. + bool loadFromFile(const std::string& filename); + + // Add a word to the dictionary. + void add(const std::string& word); + + // Add words from another dictionary. + void add(const Dictionary& dictionary); + + // Clear all words from the dictionary. + void clear(); + + // Returns the list of words in the dictionary. + const std::set& getWords() const; + + // Returns true if word exists in the dictionary. + bool contains(const std::string& word) const; + + // Searches the subject for words defined in the dictionary. + // Returns a search_result_t with the words found in subject. + // See search_result_t for more details. + search_result_t search(const std::string& subject) const; + +protected : + + // Words in the dictionary. + std::set m_words; +}; + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_CORE_DICTIONARY_H */ diff --git a/common/include/eoskeygen/core/file.hpp b/common/include/eoskeygen/core/file.hpp new file mode 100644 index 0000000..15ec64c --- /dev/null +++ b/common/include/eoskeygen/core/file.hpp @@ -0,0 +1,35 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CORE_FILE_H +#define ANTELOPEKEYGEN_COMMON_CORE_FILE_H + +#include + +namespace antelopekeygen { + +bool readLines(const std::string& filename, strlist_t& lines); + +} // namespace + +#endif /* ANTELOPEKEYGEN_COMMON_CORE_FILE_H */ diff --git a/common/include/eoskeygen/core/leet.hpp b/common/include/eoskeygen/core/leet.hpp new file mode 100644 index 0000000..6cc3796 --- /dev/null +++ b/common/include/eoskeygen/core/leet.hpp @@ -0,0 +1,36 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CORE_LEET_H +#define ANTELOPEKEYGEN_COMMON_CORE_LEET_H + +#include +#include + +namespace antelopekeygen { + +strlist_t l33twords(std::string str); + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_CORE_LEET_H */ diff --git a/common/include/eoskeygen/core/string.hpp b/common/include/eoskeygen/core/string.hpp new file mode 100644 index 0000000..a1ef0dc --- /dev/null +++ b/common/include/eoskeygen/core/string.hpp @@ -0,0 +1,40 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CORE_STRING_H +#define ANTELOPEKEYGEN_COMMON_CORE_STRING_H + +#include +#include + +namespace antelopekeygen { + +std::string& strtolower(std::string& str); + +std::string& rtrim(std::string& str); +std::string& ltrim(std::string& str); +std::string& trim(std::string& str); + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_CORE_STRING_H */ diff --git a/common/include/eoskeygen/core/strlist.hpp b/common/include/eoskeygen/core/strlist.hpp new file mode 100644 index 0000000..2cc63b6 --- /dev/null +++ b/common/include/eoskeygen/core/strlist.hpp @@ -0,0 +1,50 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_CORE_STRLIST_H +#define ANTELOPEKEYGEN_COMMON_CORE_STRLIST_H + +#include +#include + +namespace antelopekeygen { + +typedef std::vector strlist_t; + +typedef std::string& (*strlist_stripfunc_t)(std::string& str); + +namespace strlist { + +strlist_t splitw(const std::string& str, const std::string& delim = ","); + +strlist_t split(const std::string& str, const std::string& delim); + +std::string join(const strlist_t& list, const std::string& delim); + +strlist_t& strip(strlist_t& list, strlist_stripfunc_t fn); + +} // namespace strlist + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_CORE_STRLIST_H */ diff --git a/common/include/eoskeygen/key_search.hpp b/common/include/eoskeygen/key_search.hpp new file mode 100644 index 0000000..5e0b9ae --- /dev/null +++ b/common/include/eoskeygen/key_search.hpp @@ -0,0 +1,125 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_KEY_SEARCH_H +#define ANTELOPEKEYGEN_COMMON_KEY_SEARCH_H + +#include +#include +#include +#include +#include +#include + +namespace antelopekeygen { + +class IKeySearchResult; + +class KeySearch +{ +public : + + struct result { + size_t pos; // position where the word was found. + size_t len; // the length of the word. + }; + +public : + KeySearch(); + + void setPrefix(const std::string& prefix); + + // Add a word to search for. + void addWord(const std::string& str); + + // Add a list of words to search for. + void addList(const strlist_t& list); + + // get the list of words to search for. + const strlist_t& getList(); + + // Clears the search list. + void clear(); + + // Set callback for search result. + void setCallback(IKeySearchResult* callback); + +#ifdef ANTELOPEKEYGEN_HAVE_THREADS + // Returns the maximum number of threads + // reported by the operating system. + static size_t max_threads(); + + // Set the number of threads to use while searching. + void setThreadCount(size_t num); + + size_t getThreadCount() const; +#endif /* ANTELOPEKEYGEN_HAVE_THREADS */ + + // Aborts find() operation if started. + // This is useful for multithreaded code (like GUI application) + // If find() is started as a seperate thread. This method could be called + // from the gui thread if the user presses "cancel" button. + void abort(); + + // Perform a search. + void find(size_t num_results); + +protected : + + // Check if any word in appears in 's public key. + // returns true if a word was found (stored in ), false otherwise. + bool _contains_word(const struct libantelope::ec_keypair* key, struct result& result); + +#ifdef ANTELOPEKEYGEN_HAVE_THREADS + void _thr_proc(); + + void _search_mt(); +#endif /* ANTELOPEKEYGEN_HAVE_THREADS */ + + void _search_linear(); + +protected : + + // Public key prefix. + std::string m_prefix; + + // List of words to search for. + strlist_t m_words; + + // Max keys to search for. + std::size_t m_max; + + // Current number of keys found. + std::size_t m_count; + +#ifdef ANTELOPEKEYGEN_HAVE_THREADS + // Number of threads to use. + size_t m_threads; +#endif /* ANTELOPEKEYGEN_HAVE_THREADS */ + + IKeySearchResult* m_callback; +}; + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_KEY_SEARCH_H */ diff --git a/common/include/eoskeygen/key_search_result.hpp b/common/include/eoskeygen/key_search_result.hpp new file mode 100644 index 0000000..ef598e0 --- /dev/null +++ b/common/include/eoskeygen/key_search_result.hpp @@ -0,0 +1,40 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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 ANTELOPEKEYGEN_COMMON_KEY_SEARCH_RESULT_H +#define ANTELOPEKEYGEN_COMMON_KEY_SEARCH_RESULT_H + +#include + +namespace antelopekeygen { + +class IKeySearchResult +{ +public : + + virtual void onResult(const struct libantelope::ec_keypair* key, const struct KeySearch::result& result) = 0; +}; + +} // namespace antelopekeygen + +#endif /* ANTELOPEKEYGEN_COMMON_KEY_SEARCH_RESULT_H */ diff --git a/common/src/core/dictionary.cpp b/common/src/core/dictionary.cpp new file mode 100644 index 0000000..2e5033a --- /dev/null +++ b/common/src/core/dictionary.cpp @@ -0,0 +1,129 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include + +namespace antelopekeygen { + +struct StringContains { + StringContains(const std::string& str, std::vector& pos) : m_str(str), m_pos(pos) {} + bool operator()(const std::string& w) { + for(size_t p = m_str.find(w); p != std::string::npos; p = m_str.find(w, p+1)) { + m_pos.push_back(p); + } + return !m_pos.empty(); + } + std::string m_str; + std::vector& m_pos; +}; + +bool Dictionary::loadFromFile(const std::string& filename) +{ + strlist_t lines; + + // Clear before adding. + clear(); + + if (readLines(filename, lines)) { + + // Read each line and add to the dictionary. + for(auto it = lines.begin(); it != lines.end(); it++) { + add(*it); + } + return true; + } + return false; +} + +void Dictionary::add(const std::string& word) +{ + // Do not insert a empty string. + if (word.length()) { + m_words.insert(word); + } +} + +void Dictionary::add(const Dictionary& dictionary) +{ + std::set_union( + m_words.begin(), m_words.end(), + dictionary.m_words.begin(), dictionary.m_words.end(), + std::inserter(m_words, m_words.begin()) + ); +} + +const std::set& Dictionary::getWords() const +{ + return m_words; +} + +void Dictionary::clear() +{ + m_words.clear(); +} + +bool Dictionary::contains(const std::string& word) const +{ + return m_words.find(word) != m_words.cend(); +} + +Dictionary::search_result_t Dictionary::search(const std::string& subject) const +{ + search_result_t res; + + std::vector pos; + StringContains pred(subject, pos); + + // Find all words. + for(auto it = std::find_if(m_words.begin(), m_words.end(), pred); + it != m_words.end(); + it = std::find_if(++it, m_words.end(), pred)) { + + // Go through all found positions. + for (auto it2 = pos.begin(); it2 != pos.end(); it2++) { + + // Insert + auto rit = res.find(*it2); + if (rit == res.end()) { + res.emplace(*it2, it->length()); + } + // Update length if it's longer then the previous we found. + else if (rit->second < it->length()) { + rit->second = it->length(); + } + } + + // Clear positions + pos.clear(); + } + + return res; +} + +} // namespace antelopekeygen diff --git a/common/src/core/file.cpp b/common/src/core/file.cpp new file mode 100644 index 0000000..500d86b --- /dev/null +++ b/common/src/core/file.cpp @@ -0,0 +1,49 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include + +namespace antelopekeygen { + +bool readLines(const std::string& filename, strlist_t& lines) { + + FILE *fd; + char buf[1024]; + + fd = fopen(filename.c_str(), "r"); + if (!fd) { + return false; + } + + while(fgets(buf, sizeof(buf), fd) != NULL) { + std::string line(buf); + lines.push_back(trim(line)); + } + + fclose(fd); + return true; +} + +} // namespace antelopekeygen diff --git a/common/src/core/leet.cpp b/common/src/core/leet.cpp new file mode 100644 index 0000000..763bc11 --- /dev/null +++ b/common/src/core/leet.cpp @@ -0,0 +1,82 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include + +namespace antelopekeygen { + +static bool is_l33t(char ch, char& r) { + + // '1', '2', '3', '4', '5', '6', '7', '8', '9' + static char alphabet[9] = { 'l', 'z', 'e', 'a', 's', 'G', 't', 'B', 'g' }; + + for(std::size_t i = 0; i < sizeof(alphabet) / sizeof(char); i++) { + + if (ch == alphabet[i]) { + r = static_cast('1' + i); + return true; + } + } + return false; +} + +static void _l33t(strlist_t& list, const std::string& a, std::size_t pos) { + + // Find the next character to be replaced. + for(std::size_t i = pos; i < a.length(); i++) { + + char ch; + if (is_l33t(a[i], ch)) { + // create a new string and replace the character. + std::string b = a; + b[i] = ch; + + // Store the new string as the result. + list.push_back(b); + + // Perform the same algorithm for both strings + // at the next position. + _l33t(list, a, i + 1); + _l33t(list, b, i + 1); + break; + } + } +} + +strlist_t l33twords(std::string str) { + + strlist_t list; + + // "l" is abit special and are not included in base58 so we set it to 1. + // All other characters in "l33t" are valid. + std::transform(str.begin(), str.end(), str.begin(), [](char c){ return c == 'l' ? '1' : c; }); + + // Store the original string as the first in list. + list.push_back(str); + + _l33t(list, str, 0); + return list; +} + +} // namespace antelopekeygen diff --git a/common/src/core/string.cpp b/common/src/core/string.cpp new file mode 100644 index 0000000..54acb8a --- /dev/null +++ b/common/src/core/string.cpp @@ -0,0 +1,52 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include + +namespace antelopekeygen { + +std::string& strtolower(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){ return std::tolower(c); }); + return str; +} + +std::string& ltrim(std::string& str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch){ return !std::isspace(ch); }); + str.erase(str.begin(), it); + return str; +} + +std::string& rtrim(std::string& str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch){ return !std::isspace(ch); }); + str.erase(it.base(), str.end()); + return str; +} + +std::string& trim(std::string& str) { + return ltrim(rtrim(str)); +} + +} // namespace antelopekeygen diff --git a/common/src/core/strlist.cpp b/common/src/core/strlist.cpp new file mode 100644 index 0000000..dc3e5db --- /dev/null +++ b/common/src/core/strlist.cpp @@ -0,0 +1,75 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include + +namespace antelopekeygen { + +strlist_t strlist::splitw(const std::string& str, const std::string& delim) { + + strlist_t words = strlist::split(str, delim); + std::for_each(words.begin(), words.end(), trim); + return words; +} + +strlist_t strlist::split(const std::string& str, const std::string& delim) { + + strlist_t r; + size_t s = 0, e = 0, dlen = delim.length(); + + while((e = str.find(delim, s)) != std::string::npos) { + r.push_back(str.substr(s, e - s)); + s = e + dlen; + } + + r.push_back(str.substr(s)); + return r; +} + +std::string strlist::join(const strlist_t& list, const std::string& delim) { + + std::string out; + + for(const std::string& item : list) { + if (item.length() < 1) { + continue; + } + out += item + delim; + } + + if (out.length() > 0) { + out.erase(out.end() - delim.length()); + } + + return out; +} + +strlist_t& strlist::strip(strlist_t& list, strlist_stripfunc_t fn) { + + std::transform(list.begin(), list.end(), list.begin(), fn); + return list; +} + +} // namespace antelopekeygen diff --git a/common/src/key_search.cpp b/common/src/key_search.cpp new file mode 100644 index 0000000..d20597d --- /dev/null +++ b/common/src/key_search.cpp @@ -0,0 +1,131 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +namespace antelopekeygen { + +KeySearch::KeySearch() : + m_prefix ("EOS"), + m_max (0), + m_count (0), +#ifdef EOSIOKEYGEN_HAVE_THREADS + m_threads (0), +#endif + m_callback (NULL) +{ +} + +void KeySearch::setPrefix(const std::string& prefix) +{ + m_prefix = prefix; +} + +void KeySearch::addWord(const std::string& str) +{ + std::string tmp = str; + strtolower(tmp); + m_words.push_back(tmp); +} + +void KeySearch::addList(const strlist_t& list) +{ + for(const std::string& item : list) { + addWord(item); + } +} + +const strlist_t& KeySearch::getList() +{ + return m_words; +} + +void KeySearch::clear() +{ + m_words.clear(); +} + +void KeySearch::setCallback(IKeySearchResult* callback) +{ + m_callback = callback; +} + +void KeySearch::_search_linear() +{ + struct libantelope::ec_keypair pair; + + while (m_count < m_max) { + struct result res; + libantelope::ec_generate_key(&pair); + if (_contains_word(&pair, res)) { + m_callback->onResult(&pair, res); + m_count++; + } + } +} + +void KeySearch::abort() +{ + // exit find() operation by setting m_max to zero. + m_max = 0; +} + +void KeySearch::find(size_t num_results) +{ + m_count = 0; + m_max = num_results; + +#ifdef EOSIOKEYGEN_HAVE_THREADS + // Only do multithread if number of threads makes sense. + if (m_threads >= 2) { + _search_mt(); + return; + } +#endif /* HAVE_THREADS */ + + _search_linear(); +} + +bool KeySearch::_contains_word(const struct libantelope::ec_keypair* key, struct result& result) { + + size_t prefix_len = m_prefix.length(); + std::string pubstr = libantelope::wif_pub_encode(key->pub, m_prefix).substr(prefix_len); + strtolower(pubstr); + + for(auto const& w: m_words) { + size_t p = pubstr.find(w); + if (p != std::string::npos) { + result.pos = p + prefix_len; + result.len = w.length(); + return true; + } + } + return false; +} + +} // namespace antelopekeygen diff --git a/common/src/key_search_mt.cpp b/common/src/key_search_mt.cpp new file mode 100644 index 0000000..f73a635 --- /dev/null +++ b/common/src/key_search_mt.cpp @@ -0,0 +1,101 @@ +/** + * MIT License + * + * Copyright (c) 2019-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include + +namespace antelopekeygen { + +// Mutex guard for m_count. +std::mutex g_count_mtx; + +// Thread process. +void KeySearch::_thr_proc() +{ + struct libantelope::ec_keypair pair; + + while (m_count < m_max) { + struct result res; + + libantelope::ec_generate_key(&pair); + if (_contains_word(&pair, res)) { + + // Guard output with mutex, so we don't get + // interrupted mid write and can write to m_count and res safely. + const std::lock_guard lock(g_count_mtx); + + // It is possible m_count was updated by another thread + // after we checked it in the while loop. + // So while we have the lock, we need to check it again. + if (m_count >= m_max) { + return; + } + + // Update count and call result function. + m_count++; + m_callback->onResult(&pair, res); + } + } +} + +void KeySearch::setThreadCount(size_t num) +{ + m_threads = num; +} + +size_t KeySearch::max_threads() +{ + return std::thread::hardware_concurrency(); +} + +size_t KeySearch::getThreadCount() const +{ + return m_threads; +} + +void KeySearch::_search_mt() +{ + std::vector t; + + t.resize(m_threads - 1); + + // Launch them. + for(std::size_t i = 0; i < t.size(); i++) { + t[i] = std::thread(&KeySearch::_thr_proc, this); + } + + // Use main thread for 1 search + _thr_proc(); + + // Wait for all threads to compelete. + for(std::size_t i = 0; i < t.size(); i++) { + t[i].join(); + } +} + +} // namespace antelopekeygen diff --git a/src/config.h.in b/config.hpp.in similarity index 87% rename from src/config.h.in rename to config.hpp.in index ff5de16..95b3526 100644 --- a/src/config.h.in +++ b/config.hpp.in @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2019-2020 EOS Sw/eden + * Copyright (c) 2019-2021 EOS Sw/eden * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,4 +31,7 @@ #define CONFIG_SHARE_PATH "@CMAKE_INSTALL_DATADIR@/@CMAKE_PROJECT_NAME@" #define CONFIG_SHARE_FULL_PATH "@CMAKE_INSTALL_FULL_DATADIR@/@CMAKE_PROJECT_NAME@" +#define CONFIG_DICT_PATH "@CMAKE_INSTALL_DATADIR@/@CMAKE_PROJECT_NAME@/dict" +#define CONFIG_DICT_FULL_PATH "@CMAKE_INSTALL_FULL_DATADIR@/@CMAKE_PROJECT_NAME@/dict" + #endif /* EOSIOKEYGEN_CONFIG_H */ diff --git a/docs/eosio-keygen.1.in b/docs/eosio-keygen.1.in deleted file mode 100644 index ef12205..0000000 --- a/docs/eosio-keygen.1.in +++ /dev/null @@ -1,153 +0,0 @@ -.TH @PROJECT_NAME@ 1 "January, 2020" "@PROJECT_NAME@ @PROJECT_VERSION@" - -.SH NAME -@PROJECT_NAME@ - Generate public and private keypair for -.UR https://eos.io/ -EOS -.UE . - -.SH SYNOPSIS - -.SY @PROJECT_NAME@ -.OP \-h|--help -.YS - -.SY @PROJECT_NAME@ -.OP \-v -.YS - -.SY @PROJECT_NAME@ -search -.OP -m -.OP \--l33t -.OP \--threads= -.OP \--dict= ... -.OP \--lang= ... -.B word_list -.OP count -.YS - -.SY @PROJECT_NAME@ -benchmark -.OP num_keys -.YS - -.SH DESCRIPTION -.P -Output one EOSIO key pair if no arguments are given -.P -Options and subcommands are as follows: - -.TP 15 -.B -h, --help -Shows this help text. -.TP 15 -.B -v -Shows version. -.TP 15 -.B search -performs a search, finding -.I -public keys containing one or more words from -.I -(separated with ','). -Instead of a list it is possible to specify a file with words (separated with newline '\\n') using -.I file: -.RS 16 -Search specific options: -.RS 2 -.TP 20 -.B -m -Monochrome, disables all color output. -.TP 20 -.B --l33t -Takes each word in -.I -and find all l33tspeak combinations of that word and uses the new list for the search. -.TP 20 -.B --threads= -Use -.I -of parallel threads for searching. Default is what the operating system recomends. -.TP 20 -.B --dict= -Use words found in -.I -(separated by newline) to highlight words in the keys found (note that the words in this - file are not used for search. only for highlight output). There can be more then one -.B --dict -flag. In that case contents of all files are merged into one dictionary. -.TP 20 -.B --lang= -Same as -.B --dict -but will use -.I -to find a file in -.B @CMAKE_INSTALL_FULL_DATADIR@/@CMAKE_PROJECT_NAME@/dict. -There can be more then one -.B --lang -flag. In that case contents of all files are merged into one dictionary. -.TP 20 -.B count -Number of keys to search for (default is 10) -.RE 1 -.TP 15 -.B benchmark -performs a benchmark test, generating -.I -keys and measuring the time. -.PP -.RS 16 -Benchmark specific options: -.RS 2 -.TP 15 -.B num_keys -Number of keys to search for (default is 10) -.RE 1 - - -.SH SECURITY NOTICE - -Keys are generated by OpenSSL\'s -.B EC_KEY_generate_key -function. The program will -never expose your keys to anything but the computers memory and output of the\ -program. You are free to inspect the source code and compile yourself to verify. -.P -However, use this at your own risk. we cannot guarantee that the keys are\ -cryptographically secure as this depends on OpenSSL's implementation (alto it is\ -widely used and should be safe) -.P -Please read the -.I LICENSE -file. -.P -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -.br -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -.br -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -.br -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -.br -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -.br -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -.SH BUGS - -Report bugs to -.UR https://github.com/eosswedenorg/eosio-keygen/issues -Github -.UE . Thank you. - -.SH AUTHOR - -.MT henrik@eossweden.org -Henrik Hautakoski -.ME - -.UR https://eossweden.org -EOS Sw/eden -.UE diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..fec8fec --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,85 @@ +cmake_minimum_required(VERSION 3.15) + +project(antelope-keygen-gui + VERSION ${CMAKE_PROJECT_VERSION} + DESCRIPTION "Keygenerator for Antelope blockchain (gui)" + LANGUAGES CXX) + +# Append modules dir +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(QtUtils) + +# Qt5 needs MOC,RCC and UIC +set( CMAKE_AUTOMOC ON ) +set( CMAKE_AUTORCC ON ) +set( CMAKE_AUTOUIC ON ) + +# Autogenerate about config file + +file( READ ${PROJECT_LICENSE_FILE} GUI_ABOUT_LICENSE ) + +string(REGEX REPLACE "^([^\n]+)" "

\\1

" GUI_ABOUT_LICENSE ${GUI_ABOUT_LICENSE}) +string(REGEX REPLACE "\n\n([^\n]+)" "

\\1

" GUI_ABOUT_LICENSE ${GUI_ABOUT_LICENSE}) +string(REGEX REPLACE "\n" "" GUI_ABOUT_LICENSE ${GUI_ABOUT_LICENSE}) +string(REGEX REPLACE "\<(.+)\>" "- \\1" GUI_ABOUT_AUTHOR ${PROJECT_MAINTAINER}) + +configure_file(gui_text.h.in "${CMAKE_CURRENT_BINARY_DIR}/gui_text.h" @ONLY ESCAPE_QUOTES) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# -------------------------------- +# Program +# -------------------------------- + +set( PROGRAM_EXE ${PROJECT_NAME} ) + +set( PROGRAM_SRC + src/main.cpp + src/MainWindow.cpp + src/GenerateWindow.cpp + src/SearchWindow.cpp + src/MultiSelect.cpp + src/Settings.cpp + src/helpers.cpp +) + +add_executable( ${PROGRAM_EXE} WIN32 ${PROGRAM_SRC} ) + +# Libraries +target_link_libraries( ${PROGRAM_EXE} common ) + +# QT +qt5_app(TARGET ${PROGRAM_EXE} + SKIP_TRANSLATIONS + MODULES Core Gui Widgets Concurrent + INSTALL_COMPONENT gui +) + +# -------------------------------- +# Install +# -------------------------------- + +install(TARGETS ${PROGRAM_EXE} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT gui) + +install(FILES ${PROJECT_LICENSE_FILE} + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT gui + RENAME LICENSE.gui) + +install(FILES README.md + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT gui + RENAME README.gui.md) + +install(FILES LICENSE.qt5 + DESTINATION ${CMAKE_INSTALL_SHAREDIR} + COMPONENT gui) + +# -------------------------------- +# Package +# -------------------------------- + +configure_file(cmake/CPackComponentConfig.cmake.in + ${PROJECT_BINARY_DIR}/CPackProperties.cmake) diff --git a/gui/LICENSE.qt5 b/gui/LICENSE.qt5 new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/gui/LICENSE.qt5 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/gui/README.md b/gui/README.md new file mode 100644 index 0000000..b6d88ae --- /dev/null +++ b/gui/README.md @@ -0,0 +1,32 @@ + +# antelope-keygen (gui) + +This is the graphical version of the [antelope-keygen](https://github.com/eosswedenorg/antelope-keygen) project. + +This program generates public and private keypair for [Antelope IO](https://antelope.io) + +Among the basic functionality the program can also search for keys containing specific words also know as _vanity keys_. + +## Security notice + +Keys are generated using [libantelope](https://github.com/eosswedenorg/libantelope) +while the library does not claim to guarantee cryptographically secure keys. it +relies on widly used open source cryptographic libraries (OpenSSL, libsecp256k1). + +Use at your own risk. The author and [Sw/eden](https://eossweden.org/) does not take responsability +for any damage caused by keys generated by the program. + +Please read the `LICENSE` file. + +``` +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. +``` + +## Author + +Henrik Hautakoski - [Sw/eden](https://eossweden.org/) - [henrik@eossweden.org](mailto:henrik@eossweden.org) diff --git a/gui/cmake/CPackComponentConfig.cmake.in b/gui/cmake/CPackComponentConfig.cmake.in new file mode 100644 index 0000000..4167b21 --- /dev/null +++ b/gui/cmake/CPackComponentConfig.cmake.in @@ -0,0 +1,17 @@ + +# -------------------------------- +# CPack Component Config +# -------------------------------- + +set( CPACK_PACKAGE_GUI_NAME "${PROJECT_NAME}" ) +set( CPACK_PACKAGE_GUI_CONTACT "${PROJECT_MAINTAINER}" ) + +# Additional description +set( CPACK_COMPONENT_GUI_DESCRIPTION "Graphical interface (Qt framework)" ) + +# Debian specific +set( CPACK_DEBIAN_GUI_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}-${CPACK_SYSTEM_NAME}-${CPACK_SYSTEM_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") +set( CPACK_DEBIAN_GUI_PACKAGE_NAME "${PROJECT_NAME}" ) +set( CPACK_DEBIAN_GUI_PACKAGE_PRIORITY "optional" ) +set( CPACK_DEBIAN_GUI_PACKAGE_SECTION "misc" ) +set( CPACK_DEBIAN_GUI_PACKAGE_DEPENDS "libqt5core5a (>= 5.9.5), libqt5concurrent5 (>= 5.9.5), libqt5gui5 (>= 5.9.5), libqt5widgets5 (>= 5.9.5), libssl1.1") diff --git a/gui/cmake/QtUtils.cmake b/gui/cmake/QtUtils.cmake new file mode 100644 index 0000000..7d91a73 --- /dev/null +++ b/gui/cmake/QtUtils.cmake @@ -0,0 +1,110 @@ + +set(_QT_INSTALL_CONFIG_TEMPLATE ${CMAKE_CURRENT_LIST_DIR}/cmake_install_qt.cmake.in) +set(_QT_INSTALL_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/cmake_install_qt.cmake) + +# ------------------------------------------------------------------------------ +# Code Taken and modified from: +# https://github.com/equalsraf/neovim-qt/blob/master/cmake/WinDeployQt.cmake +# +# Wrapper to call windeployqt on Windows +# ------------------------------------------------------------------------------ +function(qt5_win_deploy) + cmake_parse_arguments(_deploy + "COMPILER_RUNTIME;FORCE;SKIP_TRANSLATIONS" + "TARGET;INSTALL_COMPONENT" + "MODULES;EXCLUDE_MODULES" + ${ARGN} + ) + + if(NOT _deploy_TARGET) + message(FATAL_ERROR "A TARGET must be specified") + endif() + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND _ARGS --debug) + elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + list(APPEND _ARGS --release-with-debug-info) + elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + list(APPEND _ARGS --release) + endif() + if(_deploy_COMPILER_RUNTIME) + list(APPEND _ARGS --compiler-runtime) + endif() + if(_deploy_FORCE) + list(APPEND _ARGS --force) + endif() + if(_deploy_SKIP_TRANSLATIONS) + list(APPEND _ARGS --no-translations) + endif() + + foreach(mod ${_deploy_MODULES}) + string(TOLOWER ${mod} mod) + string(REPLACE "qt5::" "" mod ${mod}) + list(APPEND _ARGS "--${mod}") + endforeach() + foreach(mod ${_deploy_EXCLUDE_MODULES}) + string(TOLOWER ${mod} mod) + string(REPLACE "qt5::" "" mod ${mod}) + list(APPEND _ARGS "--no-${mod}") + endforeach() + + find_program(_deploy_PROGRAM windeployqt PATHS $ENV{QTDIR}/bin) + if(_deploy_PROGRAM) + message(STATUS "Found ${_deploy_PROGRAM}") + else() + message(FATAL_ERROR "Unable to find windeployqt") + endif() + + if(COMPILER_RUNTIME AND NOT $ENV{VVVV}) + message(STATUS "not set, the VC++ redistributable installer will NOT be bundled") + endif() + + set( _QT_INSTALL_CONFIG_DEPFILE "${CMAKE_CURRENT_BINARY_DIR}/qt_deps_\${CMAKE_INSTALL_CONFIG_NAME}.cmake" ) + set( _QT_DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/qt_deps_${CMAKE_BUILD_TYPE}.cmake ) + + add_custom_target(${_deploy_TARGET}_windeployqt ALL + COMMAND ${CMAKE_COMMAND} -E echo "$" > ${_QT_DEPFILE} + COMMAND ${_deploy_PROGRAM} --list relative ${_ARGS} $ >> ${_QT_DEPFILE} + DEPENDS ${_deploy_TARGET} + COMMENT "Preparing Qt runtime dependencies") + + configure_file(${_QT_INSTALL_CONFIG_TEMPLATE} ${_QT_INSTALL_CONFIG} @ONLY) + + if (_deploy_INSTALL_COMPONENT) + set(_install_args COMPONENT ${_deploy_INSTALL_COMPONENT}) + endif() + + install(SCRIPT ${_QT_INSTALL_CONFIG} ${_install_args}) +endfunction() + +# ------------------------------------------------------------------------------ +# Macro for compile, link and deploy qt apps. +# ------------------------------------------------------------------------------ +macro(qt5_app) + cmake_parse_arguments(_args + "COMPILER_RUNTIME;FORCE;SKIP_TRANSLATIONS" + "TARGET;INSTALL_COMPONENT" + "MODULES" + ${ARGN} + ) + + if(NOT _args_TARGET) + message(FATAL_ERROR "A TARGET must be specified") + endif() + + if(NOT _args_MODULES) + message(FATAL_ERROR "Must define atleast one QT component") + endif() + + find_package( Qt5 COMPONENTS ${_args_MODULES} REQUIRED) + + # Link targets + list( TRANSFORM _args_MODULES PREPEND "Qt5::" OUTPUT_VARIABLE _qtargets) + target_link_libraries( ${_args_TARGET} ${_qtargets} ) + + + if (WIN32) + # Windows needs alot of extra stuff to copy all dll's. + qt5_win_deploy(${ARGN}) + endif() + +endmacro() diff --git a/gui/cmake/cmake_install_qt.cmake.in b/gui/cmake/cmake_install_qt.cmake.in new file mode 100644 index 0000000..da01764 --- /dev/null +++ b/gui/cmake/cmake_install_qt.cmake.in @@ -0,0 +1,23 @@ + +# Custom script that installs Qt runtime files. + +if (EXISTS @_QT_INSTALL_CONFIG_DEPFILE@) + file(STRINGS @_QT_INSTALL_CONFIG_DEPFILE@ _lines) + + # First line is the output path. + list(GET _lines 0 BASE_PATH) + + # Rest of the lines are the files relative to the path. + list(SUBLIST _lines 1 -1 FILES) + + foreach(file ${FILES}) + get_filename_component(rel_path ${file} DIRECTORY BASE_DIR ${BASE_PATH}) + + set(install_path ${CMAKE_INSTALL_PREFIX}/@CMAKE_INSTALL_BINDIR@) + if (rel_path) + set(install_path "${install_path}/${rel_path}") + endif() + + file(INSTALL ${BASE_PATH}/${file} DESTINATION ${install_path}) + endforeach() +endif() diff --git a/gui/gui_text.h.in b/gui/gui_text.h.in new file mode 100644 index 0000000..e119cb1 --- /dev/null +++ b/gui/gui_text.h.in @@ -0,0 +1,54 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 EOSIOKEYGEN_GUI_TEXT_H +#define EOSIOKEYGEN_GUI_TEXT_H + +#include + +#define EOSIOKEYGEN_GUI_TEXT_ABOUT_TITLE "@PROJECT_NAME@ - About" + +#define EOSIOKEYGEN_GUI_TEXT_ABOUT_BODY \ + "

@PROJECT_NAME@ - v@PROJECT_VERSION@

" \ + "

@PROJECT_DESCRIPTION@

" \ + "

@PROJECT_HOMEPAGE_URL@

" \ + "@GUI_ABOUT_LICENSE@" \ + "

Author

" \ + "

@GUI_ABOUT_AUTHOR@

" + +#define EOSIOKEYGEN_GUI_TEXT_DICT_LANG_TOOLTIP \ + "

Highlight words from given language dictionary in the found keys (note that the words " \ + "are not used for search. only for highlight output).

" \ + "

There can be more then one language. In that case contents " \ + "of all languges are merged into one dictionary.

" \ + "

The langauges are stored in files at " CONFIG_SHARE_FULL_PATH "/dict

" + + +#define EOSIOKEYGEN_GUI_TEXT_DICT_FILE_TOOLTIP \ + "

Use words found in file (separated by newline) to highlight " \ + "words in the keys found (note that the words in this " \ + "file are not used for search. only for highlight output).

" \ + "

There can be more then one file. In that case contents " \ + "of all files are merged into one dictionary.

" + +#endif /* EOSIOKEYGEN_GUI_TEXT_H */ diff --git a/gui/src/GenerateWindow.cpp b/gui/src/GenerateWindow.cpp new file mode 100644 index 0000000..6d5e3f0 --- /dev/null +++ b/gui/src/GenerateWindow.cpp @@ -0,0 +1,128 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Settings.hpp" +#include "GenerateWindow.hpp" + +void _initKeyWidget(QLineEdit& w, const QFont& font) { + w.setFixedWidth(460); + w.setFont(font); + w.setReadOnly(true); +} + +void _initKeyCopyButton(QPushButton& btn, const QIcon& icon) { + btn.setFixedWidth(32); + btn.setIcon(icon); +} + +GenerateWindow::GenerateWindow(QWidget *parent) : +QWidget (parent), +m_btn_gen ("Generate"), +m_btn_copy_both ("Copy keys") +{ + QFont mono = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + QIcon copy_icon = QIcon::fromTheme("edit-copy"); + QGridLayout* layout; + + _initKeyWidget(m_pub, mono); + _initKeyWidget(m_priv, mono); + + _initKeyCopyButton(m_btn_copy_pub, copy_icon); + _initKeyCopyButton(m_btn_copy_priv, copy_icon); + + // Layout + layout = new QGridLayout(); + layout->setAlignment(Qt::AlignCenter); + + // Stretch first and last column to make the widgets horizontally centered. + layout->setColumnStretch(0, 1); + layout->setColumnStretch(4, 1); + + // Public key row + layout->addWidget(new QLabel("Public:"), 0, 1, Qt::AlignRight); + layout->addWidget(&m_pub, 0, 2); + layout->addWidget(&m_btn_copy_pub, 0, 3); + + // Private key row + + layout->addWidget(new QLabel("Private:"), 1, 1, Qt::AlignRight); + layout->addWidget(&m_priv, 1, 2); + layout->addWidget(&m_btn_copy_priv, 1, 3); + + // Bottom row + + m_btn_copy_both.setFixedWidth(80); + + layout->addWidget(&m_btn_gen, 2, 2); + layout->addWidget(&m_btn_copy_both, 2, 3); + + setLayout(layout); + + // Connections + connect(&m_btn_gen, SIGNAL(released()), this, SLOT(generate_key())); + connect(&m_btn_copy_both, SIGNAL(released()), this, SLOT(copy_both_keys())); + connect(&m_btn_copy_pub, SIGNAL(released()), this, SLOT(copy_pub_key())); + connect(&m_btn_copy_priv, SIGNAL(released()), this, SLOT(copy_priv_key())); +} + +void GenerateWindow::generate_key() +{ + std::string pubstr, pvtstr; + struct libantelope::ec_keypair pair; + const libantelope::wif_codec_t& codec = Settings::getKeyCodec(); + + libantelope::ec_generate_key(&pair); + + pubstr = libantelope::wif_pub_encode(pair.pub, codec.pub); + pvtstr = libantelope::wif_priv_encode(pair.secret, codec.pvt); + m_pub.setText(QString::fromStdString(pubstr)); + m_priv.setText(QString::fromStdString(pvtstr)); +} + +void GenerateWindow::copy_both_keys() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(m_pub.text() + "\n" + m_priv.text()); +} + +void GenerateWindow::copy_pub_key() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(m_pub.text()); +} + +void GenerateWindow::copy_priv_key() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(m_priv.text()); +} diff --git a/gui/src/GenerateWindow.hpp b/gui/src/GenerateWindow.hpp new file mode 100644 index 0000000..63a3c99 --- /dev/null +++ b/gui/src/GenerateWindow.hpp @@ -0,0 +1,62 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 GENERATE_WINDOW_H +#define GENERATE_WINDOW_H + +#include +#include +#include + +class GenerateWindow : public QWidget +{ + Q_OBJECT +public: + GenerateWindow(QWidget *parent = 0); + +public slots: + + // Genereate a new key. + void generate_key(); + + // Copy both keys to clipboard + void copy_both_keys(); + + // copy public key to clipboard + void copy_pub_key(); + + // copy private key to clipboard + void copy_priv_key(); + +protected: + + QLineEdit m_pub; + QLineEdit m_priv; + + QPushButton m_btn_gen; + QPushButton m_btn_copy_both; + QPushButton m_btn_copy_priv; + QPushButton m_btn_copy_pub; +}; + +#endif /* GENERATE_WINDOW_H */ diff --git a/gui/src/MainWindow.cpp b/gui/src/MainWindow.cpp new file mode 100644 index 0000000..9b1a043 --- /dev/null +++ b/gui/src/MainWindow.cpp @@ -0,0 +1,125 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include "gui_text.h" +#include "Settings.hpp" +#include "GenerateWindow.hpp" +#include "SearchWindow.hpp" +#include "MainWindow.hpp" + +MainWindow::MainWindow(QWidget *parent) : +QMainWindow (parent), +m_format_fio_action (nullptr), +m_format_legacy_action (nullptr), +m_format_k1_action (nullptr) +{ + libantelope::ec_init(); + + // Create sub windows and stacked widget. + m_stacked = new QStackedWidget(); + m_stacked->addWidget(new GenerateWindow()); + m_stacked->addWidget(new SearchWindow()); + + setCentralWidget(m_stacked); + + // Add to menu bar. + + menuBar()->addAction("Generate", this, SLOT(switchToGenerate())); + menuBar()->addAction("Search", this, SLOT(switchToSearch())); + + // Settings + + QActionGroup* formatGroup = new QActionGroup(this); + + m_format_fio_action = new QAction("FIO", formatGroup); + m_format_fio_action->setCheckable(true); + m_format_legacy_action = new QAction("Legacy", formatGroup); + m_format_legacy_action->setCheckable(true); + m_format_k1_action = new QAction("K1", formatGroup); + m_format_k1_action->setCheckable(true); + + // Set k1 and trigger the changed action so we set the codec. + m_format_k1_action->setChecked(true); + formatK1CheckboxChanged(); + + connect(m_format_fio_action, SIGNAL(triggered()), this, SLOT(formatFioCheckboxChanged())); + connect(m_format_legacy_action, SIGNAL(triggered()), this, SLOT(formatLegacyCheckboxChanged())); + connect(m_format_k1_action, SIGNAL(triggered()), this, SLOT(formatK1CheckboxChanged())); + + QMenu *settings = menuBar()->addMenu("Settings"); + QMenu *format_menu = settings->addMenu("Key Format"); + format_menu->addAction(m_format_k1_action); + format_menu->addAction(m_format_legacy_action); + format_menu->addAction(m_format_fio_action); + + // About + menuBar()->addAction("About", this, SLOT(showAbout())); +} + +MainWindow::~MainWindow() +{ + libantelope::ec_shutdown(); +} + +void MainWindow::switchToGenerate() +{ + m_stacked->setCurrentIndex(0); +} + +void MainWindow::switchToSearch() +{ + m_stacked->setCurrentIndex(1); +} + +void MainWindow::showAbout() +{ + QMessageBox::about(this, + EOSIOKEYGEN_GUI_TEXT_ABOUT_TITLE, + EOSIOKEYGEN_GUI_TEXT_ABOUT_BODY); +} + +void MainWindow::formatFioCheckboxChanged() +{ + if (m_format_fio_action->isChecked()) { + Settings::setKeyCodec(libantelope::wif_create_legacy_codec("FIO")); + } +} + +void MainWindow::formatLegacyCheckboxChanged() +{ + if (m_format_legacy_action->isChecked()) { + Settings::setKeyCodec(libantelope::WIF_CODEC_LEG); + } +} + +void MainWindow::formatK1CheckboxChanged() +{ + if (m_format_k1_action->isChecked()) { + Settings::setKeyCodec(libantelope::WIF_CODEC_K1); + } +} diff --git a/gui/src/MainWindow.hpp b/gui/src/MainWindow.hpp new file mode 100644 index 0000000..3d4b172 --- /dev/null +++ b/gui/src/MainWindow.hpp @@ -0,0 +1,63 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include +#include +#include + +class QStackedWidget; + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + virtual ~MainWindow(); + +private slots : + + // Switch to generate window. + void switchToGenerate(); + + // Switch to search window. + void switchToSearch(); + + void showAbout(); + + void formatFioCheckboxChanged(); + void formatLegacyCheckboxChanged(); + void formatK1CheckboxChanged(); + +private : + + QStackedWidget* m_stacked; + + QPointer m_format_fio_action; + QPointer m_format_legacy_action; + QPointer m_format_k1_action; +}; + +#endif /* MAIN_WINDOW_H */ diff --git a/gui/src/MultiSelect.cpp b/gui/src/MultiSelect.cpp new file mode 100644 index 0000000..1b2ea89 --- /dev/null +++ b/gui/src/MultiSelect.cpp @@ -0,0 +1,150 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include "MultiSelect.hpp" + +MultiSelect::MultiSelect(const QString& text, bool user_can_add, QWidget *parent) : +QPushButton (text + ": none", parent) +{ + QPushButton* btn; + QVBoxLayout* layout; + + m_prefix = text; + + // Dialog + m_dialog = new QDialog(this); + + // Dialog Widgets + m_list = new QListWidget(); + btn = new QPushButton("Select"); + + // Dialog layout + layout = new QVBoxLayout(m_dialog); + layout->addWidget(m_list); + layout->addWidget(btn); + + // Connections + QObject::connect(btn, SIGNAL(clicked()), m_dialog, SLOT(accept())); + QObject::connect(m_dialog, SIGNAL(accepted()), this, SLOT(selectionConfirmed())); + QObject::connect(m_list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(listItemClicked(QListWidgetItem*))); + + // Configured to let users add items. provide button and signal. + if (user_can_add) { + btn = new QPushButton("Add"); + layout->addWidget(btn); + QObject::connect(btn, SIGNAL(clicked()), this, SLOT(addBtnClicked())); + } +} + +void MultiSelect::addItem(const QString& text, bool checked) +{ + QListWidgetItem* item = new QListWidgetItem(text, m_list); + item->setFlags(Qt::ItemIsEnabled); + item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); +} + +void MultiSelect::addItems(const QStringList& list, bool checked) +{ + QStringList::const_iterator it; + + for(it = list.cbegin(); it != list.cend(); it++) { + addItem(*it, checked); + } +} + +void MultiSelect::clearItems() +{ + m_list->clear(); +} + +QStringList MultiSelect::getSelectedItems() const +{ + QListWidgetItem* item; + QStringList ret; + + // Cannot use m_list->selectedItems() as that function only fetches + // _selected_ and not _checked_ items (there is a difference). + for(int i = 0; i < m_list->count(); i++) { + item = m_list->item(i); + // Include in list if checked. + if (item->checkState() == Qt::Checked) { + ret << item->text(); + } + } + return ret; +} + +void MultiSelect::selectionConfirmed() +{ + QStringList selected = getSelectedItems(); + + // Update the text for this widget to + // reflect the selected objects. + QString txt = m_prefix + ": "; + + // Have more than one item. show number of items. + if (selected.count() > 1) { + txt += QString::number(selected.count()) + " selected"; + } + // Just one, we can show the text. + else if (selected.count() == 1) { + txt += selected.at(0); + } + else { + txt += "none"; + } + + setText(txt); + + // Emit the selectionChanged signal with the updated list + emit selectionChanged(selected); +} + +void MultiSelect::listItemClicked(QListWidgetItem *item) +{ + // toggle state when user clicks. + bool checked = item->checkState() == Qt::Checked; + item->setCheckState(checked ? Qt::Unchecked : Qt::Checked); +} + +void MultiSelect::addBtnClicked() +{ + // Just emit addNewItem event. + emit addNewItem(); +} + +void MultiSelect::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + // Show select dialog. + m_dialog->show(); + } else { + // pass on other buttons to base class + QPushButton::mousePressEvent(event); + } +} diff --git a/gui/src/MultiSelect.hpp b/gui/src/MultiSelect.hpp new file mode 100644 index 0000000..5fe5d8a --- /dev/null +++ b/gui/src/MultiSelect.hpp @@ -0,0 +1,96 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 MULTI_SELECT_H +#define MULTI_SELECT_H + +#include +#include +#include + +class QMouseEvent; +class QPushButton; +class QListWidget; +class QListWidgetItem; +class QDialog; +class MultiSelectDialog; + +// +// MultiSelect implements multi selection of items using +// a push button and a dialog with a list of checkbox items. +// +class MultiSelect : public QPushButton +{ + Q_OBJECT +public: + MultiSelect(const QString& text, bool user_add_item = false, QWidget *parent = 0); + + // Items. + + void addItem(const QString& text, bool checked = false); + + void addItems(const QStringList& list, bool checked = false); + + void clearItems(); + + // Get a list of currently selected items. + QStringList getSelectedItems() const; + +signals: + + // This signal is emitted whenever the user has made a new selection. + void selectionChanged(QStringList selected); + + // This signal is emitted whenever the user clicks the "Add" button. + // NOTE: Will only be emitted if `user_add_item` has been set to `true` in the constructor. + void addNewItem(); + +private slots : + + // Called when the dialog is accepted. + void selectionConfirmed(); + + // Called when a list item is clicked on. + void listItemClicked(QListWidgetItem *item); + + // Called when the add button is clicked on. + void addBtnClicked(); + +protected : + + // Event handlers + void mousePressEvent(QMouseEvent *e) override; + +private : + + // Prefix to show on the button before value. + QString m_prefix; + + // List of items + QListWidget* m_list; + + // Dialog to show if this widget are clicked on. + QDialog* m_dialog; +}; + +#endif /* MULTI_SELECT_H */ diff --git a/gui/src/SearchWindow.cpp b/gui/src/SearchWindow.cpp new file mode 100644 index 0000000..e4661e2 --- /dev/null +++ b/gui/src/SearchWindow.cpp @@ -0,0 +1,289 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Settings.hpp" +#include "gui_text.h" +#include "config.hpp" +#include "helpers.hpp" +#include "SearchWindow.hpp" + +SearchWindow::SearchWindow(QWidget *parent, Qt::WindowFlags flags) : +QWidget (parent, flags), +m_status ("status"), +m_leet_cb ("L33t"), +m_dict_lang ("Dictionary Language"), +m_dict_file ("Dictionary File", true), +m_btn_exec ("Search"), +m_btn_clear ("Clear") +{ + setMinimumSize(600, 400); + + // Monospaced font + QFont f_mono("monospace"); + + // Output + m_output.setFont(f_mono); + m_output.setReadOnly(true); + + // Layout + // ------------------------ + setLayout(&m_layout); + m_layout.setColumnStretch(0, 10); + m_layout.setColumnStretch(1, 10); + + // First row. + m_dict_lang.addItems(get_files(CONFIG_DICT_FULL_PATH)); + m_dict_lang.setToolTip(EOSIOKEYGEN_GUI_TEXT_DICT_LANG_TOOLTIP); + m_dict_file.setToolTip(EOSIOKEYGEN_GUI_TEXT_DICT_FILE_TOOLTIP); + + m_layout.addWidget(&m_dict_lang, 0, 0); + m_layout.addWidget(&m_dict_file, 0, 1); + + m_layout.addWidget(&m_leet_cb, 0, 2); + +#ifdef EOSIOKEYGEN_HAVE_THREADS + m_num_threads.setValue((int) antelopekeygen::KeySearch::max_threads()); + m_num_threads.setRange(1, (int) antelopekeygen::KeySearch::max_threads()); + m_num_threads.setSuffix(" Threads"); + m_layout.addWidget(&m_num_threads, 0, 3); +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + + m_num_results.setValue(10); + m_num_results.setRange(1, 99); + m_num_results.setSuffix(" Results"); + m_layout.addWidget(&m_num_results, 0, 4); + + // Second row. + m_layout.addWidget(&m_status, 1, 0, 1, 3); + m_layout.addWidget(&m_txt_search, 1, 0, 1, 3); + m_layout.addWidget(&m_btn_exec, 1, 3); + m_layout.addWidget(&m_btn_clear, 1, 4); + + // Third row. + m_layout.addWidget(&m_output, 2, 0, 1, 0); + + // Search + // ------------------------ + + m_ksearch.setCallback(this); + + initSignals(); + + // Focus search field. + m_txt_search.setFocus(); +} + +void SearchWindow::initSignals() +{ + // Buttons + connect(&m_btn_exec, SIGNAL(released()), this, SLOT(search())); + connect(&m_btn_clear, SIGNAL(released()), &m_output, SLOT(clear())); + + // Worker Thread + connect(&m_worker, SIGNAL(started()), this, SLOT(searchStarted())); + connect(&m_worker, SIGNAL(finished()), this, SLOT(searchFinished())); + + connect(this, SIGNAL(addOutput(QString)), this, SLOT(output(QString))); + + connect(&m_dict_file, SIGNAL(addNewItem()), this, SLOT(langFileAdd())); +} + +void SearchWindow::loadDictionaries() +{ + QStringList list; + antelopekeygen::Dictionary tmpDict; + std::string base_path(CONFIG_DICT_FULL_PATH); + + // Clear dictionary first. + m_dict.clear(); + + // Go through all selected languages. + list = m_dict_lang.getSelectedItems(); + for(QStringList::const_iterator it = list.cbegin(); it != list.cend(); it++) { + + // Load and add them to dictionary. + tmpDict.loadFromFile(base_path + "/" + it->toStdString()); + m_dict.add(tmpDict); + } + + // Go through all selected files. + list = m_dict_file.getSelectedItems(); + for(QStringList::const_iterator it = list.cbegin(); it != list.cend(); it++) { + + // Load and add them to dictionary. + tmpDict.loadFromFile(it->toStdString()); + m_dict.add(tmpDict); + } +} + +void SearchWindow::onResult(const struct libantelope::ec_keypair* key, const struct antelopekeygen::KeySearch::result& result) +{ + int pos = (int) result.pos; + int len = (int) result.len; + libantelope::wif_codec_t codec = Settings::getKeyCodec(); + QString pub = QString::fromStdString(libantelope::wif_pub_encode(key->pub, codec.pub)); + int pub_prefix_len = (int) codec.pub.length(); + QString mid = pub.mid(pos, len); + QString left = pub.left(pos); + QString right = pub.mid(pos + len, pub.size() - pos); + antelopekeygen::Dictionary::search_result_t dict_res = m_dict.search(pub.toStdString()); + + QString out = "Public: " + pub.left(pub_prefix_len); + for(int i = pub_prefix_len; i < pub.length(); ) { + + if (i == pos) { + out += "" + pub.mid(pos, len) + ""; + i += len; + continue; + } + + // Look in the dictionary. + auto dp = dict_res.find(i); + if (dp != dict_res.end()) { + int p = (int) dp->first; + int l = (int) dp->second; + out += "" + pub.mid(p, l) + ""; + i += l; + continue; + } + + out += pub[i++]; + } + + out += "
Private: " + QString::fromStdString(libantelope::wif_priv_encode(key->secret, codec.pvt)); + + // As this function could be called from a non-gui thread. we use signals. + emit addOutput("

" + out + "

"); +} + +// -------------------- +// Slots +// -------------------- + +void SearchWindow::search() +{ + if (m_worker.isRunning()) { + m_ksearch.abort(); + return; + } + + const std::string& input = m_txt_search.text().toLocal8Bit().constData(); + antelopekeygen::strlist_t list; + + if (m_leet_cb.isChecked()) { + list = antelopekeygen::l33twords(input); + } else { + list = antelopekeygen::strlist::splitw(input); + } + + // Validate that we atleast got something to search for. + if (list.size() < 1 || (list.size() == 1 && list[0] == "")) { + QMessageBox::warning( this, + "Empty search field.", + "You must specify atleast one search string" ); + return; + } + + loadDictionaries(); + + m_ksearch.clear(); + m_ksearch.addList(list); +#ifdef EOSIOKEYGEN_HAVE_THREADS + m_ksearch.setThreadCount(m_num_threads.value()); +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + + QFuture future = QtConcurrent::run(&m_ksearch, &antelopekeygen::KeySearch::find, m_num_results.value()); + m_worker.setFuture(future); + + m_status.setText("Searching for: " + QString::fromStdString(antelopekeygen::strlist::join(list, ", "))); +} + +void SearchWindow::output(const std::string& html) +{ + output(QString::fromStdString(html)); +} + +void SearchWindow::output(const QString& html) +{ + if (m_output.toPlainText().size()) { + m_output.setHtml(m_output.toHtml() + html); + } else { + m_output.setHtml(html); + } + + // Force scrollbar to the bottom. + m_output.verticalScrollBar()->setValue(m_output.verticalScrollBar()->maximum()); +} + +void SearchWindow::langFileAdd() +{ + QStringList files = QFileDialog::getOpenFileNames(this, + "Select one or more language files"); + + m_dict_file.addItems(files, true); +} + +void SearchWindow::searchStarted() +{ + // Set prefix for search + m_ksearch.setPrefix(Settings::getKeyCodec().pub); + + m_btn_exec.setText("Cancel"); + + m_txt_search.setEnabled(false); + m_txt_search.setHidden(true); + m_dict_lang.setEnabled(false); + m_dict_file.setEnabled(false); + m_leet_cb.setEnabled(false); + m_btn_clear.setEnabled(false); +#ifdef EOSIOKEYGEN_HAVE_THREADS + m_num_threads.setEnabled(false); +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + m_num_results.setEnabled(false); +} + +void SearchWindow::searchFinished() +{ + m_btn_exec.setText("Search"); + + m_txt_search.setEnabled(true); + m_txt_search.setHidden(false); + m_dict_lang.setEnabled(true); + m_dict_file.setEnabled(true); + m_leet_cb.setEnabled(true); + m_btn_clear.setEnabled(true); +#ifdef EOSIOKEYGEN_HAVE_THREADS + m_num_threads.setEnabled(true); +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + m_num_results.setEnabled(true); +} diff --git a/gui/src/SearchWindow.hpp b/gui/src/SearchWindow.hpp new file mode 100644 index 0000000..9282ef4 --- /dev/null +++ b/gui/src/SearchWindow.hpp @@ -0,0 +1,117 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 SEARCH_WINDOW_H +#define SEARCH_WINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MultiSelect.hpp" + +class SearchWindow : public QWidget, public antelopekeygen::IKeySearchResult +{ + Q_OBJECT +public: + explicit SearchWindow(QWidget *parent = 0, Qt::WindowFlags flags = Qt::WindowFlags()); + + void onResult(const struct libantelope::ec_keypair* key, const struct antelopekeygen::KeySearch::result& result); + +private : + void initSignals(); + + void loadDictionaries(); + +private slots: + + // Start a search + void search(); + + // Output html to screen. + void output(const std::string& html); + void output(const QString& html); + + // Called when a search is started. + void searchStarted(); + + // Called when a search is done. + void searchFinished(); + + // Called when a new language file should be added + void langFileAdd(); + +signals: + void addOutput(const QString& line); + +private: + + // Search worker thread. + QFutureWatcher m_worker; + + antelopekeygen::KeySearch m_ksearch; + + antelopekeygen::Dictionary m_dict; + + // Widgets + // ---------------- + + // Status text. + QLabel m_status; + + // Search input. + QLineEdit m_txt_search; + +#ifdef EOSIOKEYGEN_HAVE_THREADS + // Number of Threads. + QSpinBox m_num_threads; +#endif /* EOSIOKEYGEN_HAVE_THREADS */ + + // Number of Results + QSpinBox m_num_results; + + QCheckBox m_leet_cb; + + MultiSelect m_dict_lang; + + MultiSelect m_dict_file; + + // Buttons + QPushButton m_btn_exec; + QPushButton m_btn_clear; + + // Text output. + QTextEdit m_output; + + // Gui Layout. + QGridLayout m_layout; +}; + +#endif /* SEARCH_WINDOW_H */ diff --git a/gui/src/Settings.cpp b/gui/src/Settings.cpp new file mode 100644 index 0000000..aa8dad9 --- /dev/null +++ b/gui/src/Settings.cpp @@ -0,0 +1,37 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include "Settings.hpp" + +namespace priv { + + libantelope::wif_codec_t key_format = libantelope::WIF_CODEC_K1; +} // namespace priv + +void Settings::setKeyCodec(const libantelope::wif_codec_t& format) { + priv::key_format = format; +} + +const libantelope::wif_codec_t& Settings::getKeyCodec() { + return priv::key_format; +} diff --git a/gui/src/Settings.hpp b/gui/src/Settings.hpp new file mode 100644 index 0000000..08a56cd --- /dev/null +++ b/gui/src/Settings.hpp @@ -0,0 +1,36 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 SETTINGS_H +#define SETTINGS_H + +#include + +namespace Settings +{ + void setKeyCodec(const libantelope::wif_codec_t& format); + + const libantelope::wif_codec_t& getKeyCodec(); +}; + +#endif /* SEARCH_WINDOW_H */ diff --git a/gui/src/helpers.cpp b/gui/src/helpers.cpp new file mode 100644 index 0000000..dc70b2f --- /dev/null +++ b/gui/src/helpers.cpp @@ -0,0 +1,42 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include "helpers.hpp" + +QStringList get_files(const QDir& directory) { + + QFileInfoList list; + QStringList ret; + + list = directory.entryInfoList(QDir::Files); + for (int i = 0; i < list.size(); ++i) { + QFileInfo info = list.at(i); + ret << info.fileName(); + } + return ret; +} + +QStringList get_files(const QString& directory) +{ + return get_files(QDir(directory)); +} diff --git a/gui/src/helpers.hpp b/gui/src/helpers.hpp new file mode 100644 index 0000000..da76ed2 --- /dev/null +++ b/gui/src/helpers.hpp @@ -0,0 +1,38 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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 HELPERS_H +#define HELPERS_H + +#include +#include +#include + +// +// Get a list of files for a given directory. +// NOTE: only filenames are returned. relative to directory. +// +QStringList get_files(const QDir& directory); +QStringList get_files(const QString& directory); + +#endif /* HELPERS_H */ diff --git a/gui/src/main.cpp b/gui/src/main.cpp new file mode 100644 index 0000000..4d8ee5b --- /dev/null +++ b/gui/src/main.cpp @@ -0,0 +1,36 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 EOS Sw/eden + * + * 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. + */ +#include +#include "MainWindow.hpp" + +int main(int argc, char **argv) { + + QApplication app(argc, argv); + + MainWindow window; + + window.show(); + + return app.exec(); +} diff --git a/install.sh b/install.sh index 927f1c7..33dde09 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,3 @@ #!/bin/bash -pushd build > /dev/null -make install $@ -popd > /dev/null +cmake --install build $@ diff --git a/lib/CLI11/CMakeLists.txt b/lib/CLI11/CMakeLists.txt new file mode 100644 index 0000000..fb43efb --- /dev/null +++ b/lib/CLI11/CMakeLists.txt @@ -0,0 +1,2 @@ +set (LIBCLI11_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include) +set (LIBCLI11_LICENSE ${CMAKE_CURRENT_LIST_DIR}/LICENSE) \ No newline at end of file diff --git a/lib/CLI11/LICENSE b/lib/CLI11/LICENSE new file mode 100644 index 0000000..f618d95 --- /dev/null +++ b/lib/CLI11/LICENSE @@ -0,0 +1,25 @@ +CLI11 2.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +Schreiner under NSF AWARD 1414736. All rights reserved. + +Redistribution and use in source and binary forms of CLI11, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/lib/CLI11/include/CLI11/CLI11.hpp b/lib/CLI11/include/CLI11/CLI11.hpp new file mode 100644 index 0000000..f29a5c4 --- /dev/null +++ b/lib/CLI11/include/CLI11/CLI11.hpp @@ -0,0 +1,9190 @@ +// CLI11: Version 2.2.0 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.2.0 +// +// CLI11 2.2.0 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 2 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.2.0" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + + + +// C standard library +// Only needed for existence checking +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + +namespace CLI { + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +inline std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +/// Print subcommand aliases +inline std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and \n would just be annoying to deal with in many places allowing space here has too much potential for + // inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n'); +} + +/// Verify an option/subcommand name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +inline void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +inline std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str, char delimiter = '\0') { + + const std::string delims("\'\"`"); + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); + }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail + + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + + " were given", + ExitCodes::RequiredError); + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } + if(max_option == 1) + return RequiredError("Requires at most 1 options be given from [" + option_list + "]", + ExitCodes::RequiredError); + + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { using type = void; }; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { using type = T; }; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { using type = std::string; }; + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { using type = T; }; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { using type = typename element_type::type::value_type; }; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#pragma diag_suppress 2361 +#endif + TT { std::declval() } +#ifdef __CUDACC__ +#pragma diag_default 2361 +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> + : public conditional_t::value, std::false_type, std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return std::string{}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return std::string("{}"); + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return std::string("[" + detail::join(defaults) + "]"); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { using type = def; }; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { static const int value{0}; }; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; + +/// String and similar direct assignment +template +struct classify_object::value && !std::is_integral::value && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !is_complex::value && + !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + std::string str = std::string(type_name::type>::type>()) + + ',' + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + return false; +} + +/// Convert a flag into an integer value typically binary flags +inline std::int64_t to_flag_value(std::string val) { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + throw std::invalid_argument("unrecognized character"); + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + ret = std::stoll(val); + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + try { + auto out = to_flag_value(input); + output = (out > 0); + return true; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still + // valid all we care about the sign + output = (input[0] != '-'); + return true; + } +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + return val == (input.c_str() + input.size()); +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = detail::lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && detail::lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = detail::lexical_cast(str1, y); + x = XC{0}; + } else { + worked = detail::lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } else { + double dval; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + } + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, ConvertTo>::type v2; + bool retval = lexical_assign(strings[0], v1); + if(strings.size() > 1) { + retval = retval && lexical_assign(strings[1], v2); + } + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } else { + return lexical_assign(strings[0], output); + } +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::max)(mx_count, strings.size())}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = detail::lexical_cast(arg, tv); + if(!comp) { + try { + tv = static_cast(detail::to_flag_value(arg)); + } catch(const std::exception &) { + fail = true; + break; + } + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + if(val <= static_cast(std::numeric_limits::min()) || + val >= static_cast(std::numeric_limits::max()) || + val == static_cast(val)) { + output = detail::value_string(static_cast(val)); + } else { + output = detail::value_string(val); + } + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + std::size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// extract default flag values either {def} or starting with a ! +inline std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; + } + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + Validator description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; + } + /// Generate type description information for the Validator + std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + else + return s1 + s2; + }; + + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; + } + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; + } +}; // namespace CLI + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(file, ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistent; + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num; + for(const auto &var : result) { + bool retval = detail::lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string(); + }; + } +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { + func_ = [](std::string &input_string) { + auto val = DesiredType(); + if(!detail::lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }; + } + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true) : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; + } +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), min() her is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } else { + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); + } +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + local_item_t b; + bool converted = detail::lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + if(!detail::lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + bool converted = detail::lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } + } + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; + } + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } else { + static auto m = init_mapping(false); + return m; + } + } +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr